Java_Alert Java_Alert - 3 months ago 46
HTML Question

Expand one element and collapse all other element in knockout js

In knockout is it possible to collapse all other opened row and expand only clicked row.

I am referring this Fiddle example for it.

view -

<ul data-bind="foreach: items"> <a href="#" data-bind="click: toggle, text:linkLabel"></a>
<button data-bind="text:name"></button>
<div data-bind="visible:expanded">
<input data-bind="value:name"></input>
</div>
</ul>


viewModel -

function Sample(item) {
var self = this;
self.name = ko.observable(item.name);
self.id = ko.observable(item.id);
self.expanded = ko.observable(false);
self.toggle = function (item) {
self.expanded(!self.expanded());
};
self.linkLabel = ko.computed(function () {
return self.expanded() ? "collapse" : "expand";
}, self);
}

var viewModel = function () {
var self = this;

var json = [{
"name": "bruce",
"id": 1
}, {
"name": "greg",
"id": 2
}]

var data = ko.utils.arrayMap(json, function (item) {
return new Sample(item); // making things independent here
});
self.items = ko.observableArray(data);
};

ko.applyBindings(new viewModel());


Here its not collapsing already opened row. I tried to fetch complete items in toggle function but it did not work.

I am new to knock out. please suggest.

Update -

I tried this code to make first one extended by default -

var index=0;
var data = ko.utils.arrayMap(json, function(item) {
if(index++===0){
return new Sample(item,true);
}else{
return new Sample(item,false);
}
});


But above given code is not working as expected.

Answer

This is very common "problem" when you're working with knockout. You want to keep your Sample instances independent, while their behavior might still influence the behavior of any siblings... I usually pick one of three options:

  • Move the functionality that influences siblings to the parent viewmodel. For example:

    var viewModel = function() {
      /* ... */
      self.toggle = function(sample) {
        self.items().forEach(function(candidateSample) {
          candidateSample.expanded(sample === candidateSample);
        });
      }
    };
    

    With data-bind:

    <a data-bind="click: $parent.toggle"></a>
    

    Personally, I'd go with this option. Here's it implemented in your fiddle: http://jsfiddle.net/cxzLsz56/

  • Pass siblings to each item:

    self.items = ko.observableArray();
    
    var data = ko.utils.arrayMap(json, function (item) {
      return new Sample(item, self.items);
    });
    
    self.items(data);
    

    And in Sample:

    function Sample(item, siblings) {
      self.toggle = function() {
        siblings().forEach(/* collapse */);
        self.expanded(true); // Expand
      };
    };
    
  • Create some sort of postbox/eventhub/mediator mechanism and make a Sample trigger an event. Each Sample listens to this event and collapses when another Sample triggers it.