MJ Suriya MJ Suriya - 22 days ago 13
CSS Question

Knockout JS: Display binded subList foreach meta items using exapnd option

This is my data structure.

=MetaData=
School
College
Home

=SubDetails=
School
ClassRooms
Library
OfficeRoom
College
AdminOffice
Departments
Lab
PlacementHall
Home
LivingRoom
StudyRoom
Hall
DiningHall
Portico


The meta data values will be selected using drop-down menu. For example if school is the value chosen from drop-down menu then the display area should show as:

+ school x


Once I clicked that + expand icon in my case it should list the details as in data.

- School x
ClassRooms
Library
OfficeRoom


For example if I continuously choosing more than one item, say school, college, home etc

- School x
ClassRooms
Library
OfficeRoom
+ College x
- Home x
LivingRoom
StudyRoom
Hall
DiningHall
Portico


Code I tried:

HTML:

<div class="DataDisplay">
<!-- ko foreach: selectedMetaDataList -->
<span class="metaHeader">
<span>
<a href="#" data-bind="click: $parent.expandMetaData"><i class="fa fa-plus" style="padding-right: 3px;"></i></a>
<span data-bind="text: $data"></span>
<span class="combineImgText">
<img data-bind="click: $parent.remove" class="delfilter" src="@Url.Content("~/Content/images/icon_x_purple_on.png")" width="15" height="15" />
</span>
</span>
</span><br />
<!-- /ko -->
<span class="subDetails" style="display: none">
<!-- ko foreach: selectedSubDetailsList -->
<span data-bind="text: $data" style="margin-left:25px"></span><span class="combineImgText">
<img data-bind="click: $parent.remove" class="delfilter" src="@Url.Content("~/Content/images/icon_x_purple_on.png")" width="15" height="15" />
</span><br />
<!-- /ko -->
</span>
</div>


JS:

if(//matching condition)
{
selectedMetaDataList.push(selectedMetaData().toString()); //ko.observableArray([])
selectedSubDetailsList.push(value[i]); //ko.observableArray([])
}

expandMetaData: function () {
expandData($(".subDetails").is(':visible') ? true : false);
$(".subDetails").toggle();
},


Snap shot for selecting one item is:

enter image description here

And the issue is:

enter image description here

The problem is if I select more than one, then only lastly added element if expandable and it included all the sub-details of the previously selected items. My question is How to bind particular selected meta data to display its sub details content. I am struggling with this for past two days :(

Any suggestion would be helpfui!

Answer

The best advice I can give you, without seeing your viewmodels, is:

  • Your view models don't have to copy your data-structure. They are a layer between your data and your view, so you can make changes that benefit the way you want to render things.
  • Don't use jQuery to hide/show stuff, use knockout's default data-binds: visible or css

To illustrate these points:

  • A viewmodel that includes a reference to details inside the main category
  • A visible binding to hide and show
  • An expanded state in the viewmodels that automatically updates several parts of the UI

var metaData = [
  "School",
  "College",
  "Home"
];

var subDetails = {
  School: [
    "ClassRooms",
    "Library",
    "OfficeRoom"
  ],
  College: [
    "AdminOffice",
    "Departments",
    "Lab",
    "PlacementHall"
  ],
  Home: [
    "LivingRoom",
    "StudyRoom",
    "Hall",
    "DiningHall",
    "Portico"
  ]
};

// An important purpose of this view model is to include details _inside_ a meta item
var MetaViewModel = function(label) {
  // Expanded is used to bind to the visiblity of the details
  this.expanded = ko.observable(false);
  
  // Add a '+' or '-' sign based on expanded state
  this.label = ko.pureComputed(function() {
    return (this.expanded() ? "- " : "+ ") + label;
  }, this);
  
  // Here, we add the details to the item
  this.details = ko.observableArray(subDetails[label]);
  
  // Toggle flips the state of expanded
  this.toggle = function() {
    this.expanded(!this.expanded());
  }.bind(this);
  
  // Removes a detail from the item's details list
  this.remove = function(str) {
    this.details.remove(str);
  }.bind(this);
};

MetaViewModel.create = function(label) { return new MetaViewModel(label); };

var viewModel = {
   meta: metaData.map(MetaViewModel.create) // Creates a new viewmodel for each meta data category
}

ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<ul data-bind="foreach: meta">
  <li>
    <div data-bind="text: label, click: toggle"></div>
    <ul data-bind="foreach: details, visible: expanded">
      <li data-bind="text: $data + ' X', click: $parent.remove"></li>
    </ul>
</ul>