I am using Knockout.js to populate a set of HTML5 <details>
elements. Here is the structure:
<div class="items" data-bind="foreach: Playlists"> <details class="playlist-details" data-bind="attr: {id: 'playlist-details-' + $index()}"> <summary> <span data-bind="text: name"></span> - <span data-bind="text: count"></span> item(s) <div class="pull-right"> <button data-bind="click: $parent.play, css: {disabled: count() == 0}, attr: {title: playbtn_title}" class="btn"><i class="icon-play"></i> Play</button> <button data-bind="click: $parent.deleteList" class="btn btn-danger"><i class="icon-trash"></i> Delete</button> </div> </summary> <div class="list" data-bind="with: items" style="padding-top: 2px;"> ... </div> </details> </div>
The data in the ViewModel looks something like this:
var VM = { Playlists: [ { name: "My Playlist1", count: 3, items: [<LIST OF SONG ID'S>], playbtn_title: "Play this playlist" }, { name: "My Playlist2", count: 5, items: [<LIST OF SONG ID'S>], playbtn_title: "Play this playlist" }, { name: "My Playlist3", count: 0, items: [], playbtn_title: "You need to add items to this list before you can play it!" } ] };
I want to add the ability to remember the open or closed state of the details view. I have implemented this behavior previously using jQuery
and localStorage
1, but for this project I want to use Knockout natively instead of using jQuery.
I have added an isOpen
property to the playlists in the ViewModel which is retrieved from localStorage
when the page loads. However, it seems that I can’t use the attr
binding in Knockout because the HTML5 spec says to look only for the presence or absence of the open
attribute, not for a value.
How would I get Knockout to add and remove the open
property of the <details>
element as the isOpen
property of the ViewModel changes?
1: Like this:
// On the initial page load. contents += '<details ' + ((localStorage['tl_open_playlist-details-' + counter] == 1) ? 'open' : '') ' class="playlist-details" id="playlist-details-' + counter + '" data-name="' + escape(listname) + '">' ... // Update storage when things are clicked. $(document).on('DOMSubtreeModified', 'details.playlist-details', function() { if ($(this).prop('open')) { localStorage['tl_open_' + this.id] = 1; } else { delete localStorage['tl_open_' + this.id]; } });
Advertisement
Answer
You can modify the attr
binding to take into account another binding option (named attrRemoveWhenFalse
here) and remove the attribute for you:
<input class='testInput' type="text" data-bind="attr: { disabled: isDisabled }, attrRemoveWhenFalse: true" /> var originalAttr = { init: ko.bindingHandlers.attr.init, update: ko.bindingHandlers.attr.update } ko.bindingHandlers.attr.update = function (element, valueAccessor, allBindingAccessor, viewModel, bindingContext) { if (typeof originalAttr.update === 'function') originalAttr.update(element, valueAccessor, allBindingAccessor, viewModel, bindingContext); if (allBindingAccessor().attrRemoveWhenFalse) { for (var prop in valueAccessor()) { if (!ko.utils.unwrapObservable(valueAccessor()[prop])) { element.removeAttribute(prop); } } } }