1
votes

Within a foreach, I have a span tag and a textarea. Whenever I click the span tag, I want to toggle the visibility of the textarea.

This works partially except it toggles the visibility of all of the textareas within the foreach instead of just the textarea for the particular item I am on.

Here is my code. The code doesn't actually run, but I think there's enough there for you to see what I am trying to do.

function MyViewModel(data) {
 var self = this;
 self.checkListItems = [1,2,3];
 self.textAreaVisible = ko.observable(false);
 
 self.toggleTextArea = function () {
        self.textAreaVisible(!self.textAreaVisible());
 }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div data-bind="foreach: MyViewModel.checkListItems">
         <span data-bind="click: toggleTextArea">Add Comments ></span>
         <textarea data-bind="value: comments, visible: textAreaVisible"></textarea>
</div>

I found this link here http://knockoutjs.com/documentation/foreach-binding.html which sounds like maybe I should be using $data somehow, but I'm not sure how to get that to work in this context.

I appreciate any help you can provide.

4
Your observable, as it is now, will toggle the visibility of ALL textareas. Is that your desired outcome? - pim
@PimBrouwers no, it is not. I'd like it to only toggle the visibility of the current item. So if I click on the third span on the view, then show the third textarea on the view and none of the other textareas. - t_m27
perfect, see my answer below! Happy coding! - pim

4 Answers

2
votes

You can make a constructor for your textarea model. And have a self contained variable for visibility, and toggling visibility

function TextAreaModel(text){
    var self = {};
    self.comments = ko.observable(text);
    self.visible = ko.observable(false);
    self.toggleVisible = function(){
    self.visible(!self.visible());
    };
    return self;
}

function MyViewModel() {
    var self = {};
    self.checkListItems = [
        TextAreaModel("This is some text"), 
        TextAreaModel("This is some more text")
    ];
    return self;
}

var vm = MyViewModel();
ko.applyBindings(vm);

Working example: https://jsfiddle.net/8n6pghuo/

0
votes

As it is right now, your issue is more like intended behavior because the textAreaVisible value is the same for all of your items on your array since it's a property of your root viewmodel.

You need another viewmodel with its own observables for it to work as you like, so you'd have a ko.observableArray of viewmodels with each of them with their observables for control flow.

0
votes

Your Checklist items should look more like this (i.e. objects):

self.checkListItems = [
    { value: 1, visible: ko.observable(false) },
    { value: 2, visible: ko.observable(false) },
    { value: 3, visible: ko.observable(false) },
];

Doing this enables you to iterate like this:

<div data-bind="foreach: MyViewModel.checkListItems">
         <span data-bind="click: function(){ visible(!visible()) }">Add Comments ></span>
         <textarea data-bind="value: value, visible: visible"></textarea>
</div>

Should you want to cleanup the click handler, you can modify your viewmodel as follows:

function MyViewModel(data) {
 //rest of the code

 self.toggleTextArea = function (item) {
    item.visible(!item.visible());
 }
}

Changing the dom to this:

 <div data-bind="foreach: MyViewModel.checkListItems">
             <span data-bind="click: $parent.toggleTextArea">Add Comments ></span>
             <textarea data-bind="value: value, visible: visible"></textarea>
    </div>
0
votes

This is how you do it. The boolean to hide and show must be placed inside the array so each of the object has its own boolean to show or hide.

function checkListItemViewModel(number) {
  var self = this;
  self.item = ko.observable(number);
  self.comments = ko.observable("");
  self.isVisible = ko.observable(false);
  self.toggleTextArea = function () {
    self.isVisible(!self.isVisible());
  }
}

function MyViewModel(data) {
  var self = this;
  self.checkListItems = ko.observableArray();
  for (var i = 0; i<data.length; i++) {
    self.checkListItems.push(new checkListItemViewModel(data[i]));
  }
}
ko.applyBindings(new MyViewModel([1, 2, 3]));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

<div data-bind="foreach: checkListItems">
  <button data-bind="click: toggleTextArea">Add Comments ></button>
  <textarea data-bind="value: comments, visible: isVisible">
  </textarea>
  <span data-bind="text: comments"></span><br/>
</div>