Rutvik Lhase
Rutvik Lhase

Reputation: 33

KnockoutJS - How to hide certain elements inside foreach using Observable Arrays?

I have a list of WebsiteOwners. I'm trying to build a UI which will display more information about the owners when I click on them.

       this.toExpand = ko.observableArray(); //initialize an observable array
       this.invertExpand = ko.observable("");


        this.invertExpand = function (index) {
                if (self.invertExpand[index] == false) {
                self.invertExpand[index] = true;
                alert(self.invertExpand[index]); //testing whether the value changed
            }
            else {
                self.invertExpand[index] = false;
                alert(self.invertExpand[index]); //testing whether the value changed
            }
           
        };

Here's the HTML code :

  <div data-bind="foreach: WebsiteOwners">
  <div>
        <button data-bind="click: $root.invertExpand.bind(this,$index())" class="label label-default">>Click to Expand</button>
    </div>
  <div data-bind="visible: $root.toExpand()[$index]">
   
  

  Primary Owner: <span data-bind="text:primaryOwner"></span>
  Website Name : <span data-bind="text:websiteName"></span>
  //...additional information

  </div>
  </div>

Upvotes: 3

Views: 256

Answers (1)

user3297291
user3297291

Reputation: 23372

You can store one of your WebsiteOwner items directly in your observable. No need to use an index.

Don't forget you read an observable by calling it without arguments (e.g. self.invertExpand()) and you write to it by calling with a value (e.g. self.invertExpand(true))

I've included 3 examples in this answer:

  • One that allows only a single detail to be opened using knockout
  • One that allows all details to be opened and closed independently using knockout
  • One that does not use knockout but uses plain HTML instead 🙂

1. Accordion

Here's an example for a list that supports a single expanded element:

const websiteOwners = [
  { name: "Jane", role: "Admin" },
  { name: "Sarah", role: "Employee" },
  { name: "Hank", role: "Employee" }
];

const selectedOwner = ko.observable(null);

const isSelected = owner => selectedOwner() === owner;
const toggleSelect = owner => {
  selectedOwner(
    isSelected(owner) ? null : owner
  );
}

ko.applyBindings({ websiteOwners, isSelected, toggleSelect });
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

<ul data-bind="foreach: { data: websiteOwners, as: 'owner' }">
  <li>
    <span data-bind="text: name"></span>
    <button data-bind="
      click: toggleSelect,
      text: isSelected(owner) ? 'collapse' : 'expand'"></button>
      
    <div data-bind="
      visible: isSelected(owner),
      text: role"></div>
  </li>
</ul>

2. Independent

If you want each of them to be able to expand/collapse independently, I suggest adding that state to an owner viewmodel:

const websiteOwners = [
  { name: "Jane", role: "Admin" },
  { name: "Sarah", role: "Employee" },
  { name: "Hank", role: "Employee" }
];

const OwnerVM = owner => ({
  ...owner,
  isSelected: ko.observable(null),
  toggleSelect: self => self.isSelected(!self.isSelected())
});
  
ko.applyBindings({ websiteOwners: websiteOwners.map(OwnerVM) });
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

<ul data-bind="foreach: websiteOwners">
  <li>
    <span data-bind="text: name"></span>
    <button data-bind="
      click: toggleSelect,
      text: isSelected() ? 'collapse' : 'expand'"></button>
      
    <div data-bind="
      visible: isSelected,
      text: role"></div>
  </li>
</ul>

3. Using <details>

This one leverages the power of the <details> element. It's probably more accessible and by far easier to implement!

const websiteOwners = [
  { name: "Jane", role: "Admin" },
  { name: "Sarah", role: "Employee" },
  { name: "Hank", role: "Employee" }
];

ko.applyBindings({ websiteOwners });
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

<ul data-bind="foreach: websiteOwners">
  <li>
    <details>
      <summary data-bind="text: name"></summary>
      <div data-bind="text: role"></div>
    </details>
  </li>
</ul>

Upvotes: 2

Related Questions