Knockout.js show / hide block visibility pattern

In the following case, I have a problem with code duplication . On my page, I have many blocks that I need to show / hide by clicking the link:

<div> <a data-bind="click: showHiddenFirst, visible: isVisibleFirst"href="#">Show first</a> <div data-bind="visible: !isVisibleFirst()" style="display:none"> hidden content first </div> </div> <div> <a data-bind="click: showHiddenSecond, visible: isVisibleSecond"href="#">Show second</a> <div data-bind="visible: !isVisibleSecond()" style="display:none"> hidden content second </div> </div> 

And my js

 var vm = function(){ this.isVisibleFirst = ko.observable(true); this.showHiddenFirst = function(){ this.isVisibleFirst(false) }; this.isVisibleSecond = ko.observable(true); this.showHiddenSecond = function(){ this.isVisibleSecond(false) }; }; ko.applyBindings(new vm()); 

Here is a jsfiddle example http://jsfiddle.net/sstude/brCT9/2/

The question is how to avoid all this show / visible duplication? Maybe I need some kind of custom binding where I add the identifier of my hidden block or smth. still? What patterns can you offer?

+7
source share
3 answers

Here was the idea of ​​fully encapsulating this function in the observable for your specific scenario:

 ko.bindingHandlers.clickVisible = { init: function(element) { var visible = ko.observable(true), opposite = ko.computed(function() { return !visible(); }), clickHandler = visible.bind(null, false); //apply bindings to anchor ko.applyBindingsToNode(element, { click: clickHandler, visible: visible }); var sibling = element.nextSibling; //find the div (as text nodes, etc. will show up in nextSibling) while (sibling && sibling.nodeType != 1) { sibling = sibling.nextSibling; } //apply bindings to div if (sibling) { ko.applyBindingsToNode(sibling, { visible: opposite }); } } }; 

It can be changed as necessary if, possibly, the value passed to the binding has a value.

Example: http://jsfiddle.net/rniemeyer/gCgy5/

+4
source

You can use the template along with a separate model for hidden elements:

HTML

 <div data-bind="template: { name: 'hidden-template', data: first }"></div> <div data-bind="template: { name: 'hidden-template', data: second }"></div> <script type="text/html" id="hidden-template"> <a data-bind="click: showHidden, visible: isVisible, text : linkText" href="#"></a> <div data-bind="visible: !isVisible(), html: content" style="display:none"></div> </script> 

Js

 var hiddenModel = function(linkText, content) { this.linkText = linkText; this.content = content; this.isVisible = ko.observable(true); this.showHidden = function(){ this.isVisible(false) }; } var vm = function() { this.first = new hiddenModel('Show first', 'hidden content first'); this.second = new hiddenModel('Show second', 'hidden content second'); }; 

Note. For these two elements, this may be too much overhead, but as soon as you need more hidden elements, it pays off. Any additional element needs only one short line of HTML and JS.

UPDATE FOR INTEGRATED DETECTION TEMPLATE:

If your HTML content contains the bindings themselves, you can put it in templates and load them dynamically

Working example

HTML

 <div data-bind="template: { name: 'hidden-template', data: first }"></div> <script type="text/html" id="content-first"> test simple content </script> <div data-bind="template: { name: 'hidden-template', data: second }"></div> <script type="text/html" id="content-second"> test content <a href="#" data-bind="click:testBtn">with binding</a> </script> <script type="text/html" id="hidden-template"> <a data-bind="click: showHidden, visible: isVisible, text : linkText" href="#"></a> <div data-bind="visible: !isVisible(), template: { name: content, data: $parent }" style="display:none"></div> </script> 

Js

 var hiddenModel = function(linkText, content) { this.linkText = linkText; this.content = content; this.isVisible = ko.observable(true); this.showHidden = function(){ this.isVisible(false) }; } var vm = function() { this.testBtn = function(){alert('it works');} this.first = new hiddenModel('Show first', 'content-first'); this.second = new hiddenModel('Show second', 'content-second'); }; 

content now a template identifier instead of an HTML string

+3
source

Why not use an observable array with some identifiers (one for each flag)?

Then you may have methods such as:

 model hideElement = function(id) { model.hiddenElements.push(id); } model.showElement = function(id) { model.hiddenElements.remove(id); } 

And in your binding:

 <div data-bind="click: function() { hideElement('two') }, visible: !hiddenElements().contains('one')"></div> 

Edit : I updated your script to show possible implementations: http://jsfiddle.net/brCT9/4/

+1
source

All Articles