1

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.

3
  • Your observable, as it is now, will toggle the visibility of ALL textareas. Is that your desired outcome? Commented Apr 5, 2017 at 20:25
  • @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. Commented Apr 5, 2017 at 20:27
  • perfect, see my answer below! Happy coding! Commented Apr 5, 2017 at 20:29

4 Answers 4

2

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/

Sign up to request clarification or add additional context in comments.

4 Comments

Your code is wrong. It should be var self = this; Not var self = {};
I'm using var self = {} intentionally If i were to use var self = this. I would have to say var vm = new MyViewModel() insted. Same goes for the TextAreaModel constructor.
That makes no sense. this is analogous to {} at the point you declare.
Actually, since the new keyword is not used in this code, this would be set to the same as window instead of a new object. This code as written would actually not work correctly with var self = this;
0

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>

Comments

0

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.

Comments

0

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>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.