I have a js fiddle (located here) that I want to mimic using knockout.js. The idea is that each button has a corresponding div tag. If the corresponding div tag is visible, it should hide when the button is clicked. Otherwise, it should show. If any of the other non-corresponding divs are visible, they should hide and then show the corresponding div. How can I mimic this jQuery version using knockout? The js fiddle for the knockout version is located here. It works but it still seems really verbose. It seems like there should be a way to make it more dynamic and reduce the amount of work. Any help is greatly appreciated.
<style type="text/css">
.text { background-color: lightgray; }
</style>
<script type="text/javascript">
$(document).ready(function () {
var viewModel = {
showHide1: ko.observable(false),
showHide2: ko.observable(false),
showHide3: ko.observable(false),
toggle1: function () {
this.showHide1(true);
this.showHide2(false);
this.showHide3(false);
},
toggle2: function () {
this.showHide1(false);
this.showHide2(true);
this.showHide3(false);
},
toggle3: function () {
this.showHide1(false);
this.showHide2(false);
this.showHide3(true);
}
};
ko.applyBindings(viewModel);
});
</script>
<div id="text1" class="text" data-bind="if: showHide1">Text 1</div>
<div id="text2" class="text" data-bind="if: showHide2">Text 2</div>
<div id="text3" class="text" data-bind="if: showHide3">Text 3</div>
<br />
<br />
<button id="button1" type="button" data-bind="click: toggle1">Button 1</button>
<button id="button2" type="button" data-bind="click: toggle2">Button 2</button>
<button id="button3" type="button" data-bind="click: toggle3">Button 3</button>
I propose using a function that returns the handler. I find it's an essential method to writing sane Knockout code.
Basic
demo
We can simplify the HTML to this. It checks what should be showing
, has our buttons change what is showing
.
<div id="text1" class="text" data-bind="if: showing() === '1'">Text 1</div>
<div id="text2" class="text" data-bind="if: showing() === '2'">Text 2</div>
<div id="text3" class="text" data-bind="if: showing() === '3'">Text 3</div>
<button id="button1" type="button" data-bind="click: show('1')">Button 1</button>
<button id="button2" type="button" data-bind="click: show('2')">Button 2</button>
<button id="button3" type="button" data-bind="click: show('3')">Button 3</button>
Our ViewModel
is also simplified. First, we turn it into a function, for easier extensiblity. Our showing
is simply a string value. show
is the meat of our code. It returns a function that sets showing
.
This way, we could rename the elements from 1
, 2
, and 3
; to main
, about
, and contact
without touching the JavaScript.
ViewModel = function(){
var self = this;
self.showing = ko.observable('');
self.show = function(what) {
return function(){ self.showing(what); };
}
};
ko.applyBindings(new ViewModel);
Transition Animation
demo
To use jQuery's slide in/down we can use code provided by KnockoutJS's documentation. This is the JavaScript code with comments removed:
ko.bindingHandlers.slideVisible = {
update: function(element, valueAccessor, allBindingsAccessor) {
var value = valueAccessor(), allBindings = allBindingsAccessor();
var valueUnwrapped = ko.utils.unwrapObservable(value);
var duration = allBindings.slideDuration || 400;
if (valueUnwrapped == true)
$(element).slideDown(duration); // Make the element visible
else
$(element).slideUp(duration); // Make the element invisible
}
};
To put this into the HTML, simply replace the if
binding with slideVisible
.
<div id="text1" class="text" data-bind="slideVisible: showing() === '1'">Text 1</div>
<div id="text2" class="text" data-bind="slideVisible: showing() === '2'">Text 2</div>
<div id="text3" class="text" data-bind="slideVisible: showing() === '3'">Text 3</div>
You could use the template
binding with a dynamic template name like:
<div class="text" data-bind="template: { 'if': current, name: current() }"></div>
<button type="button" data-bind="click: toggle1">Button 1</button>
<button type="button" data-bind="click: toggle2">Button 2</button>
<button type="button" data-bind="click: toggle3">Button 3</button>
<script id="text1" type="text/html">text 1</script>
<script id="text2" type="text/html">text 2</script>
<script id="text3" type="text/html">text 3</script>
with a view model like:
var viewModel = {
current: ko.observable(''),
toggle1: function () {
this.current("text1");
},
toggle2: function () {
this.current("text2");
},
toggle3: function () {
this.current("text3");
},
};
ko.applyBindings(viewModel);
Sample here: http://jsfiddle.net/rniemeyer/Zh9Qy/
Note that in KO 2.3, you would not have to pass current()
for the name
option and could just pass current
, as it will now be unwrapped properly.
You could create a viewModel that contains the id of div to show. This property will be set accordingly of the button on which you click.
View model:
$(document).ready(function () {
var ViewModel = function(){
var self = this;
self.onClick =function(data, event) {
var element = event.target.id.replace('button', 'text');
self.shownElement(element)
};
self.shownElement =ko.observable(null);
};
ko.applyBindings(new ViewModel());
});
View :
<div id="text1" class="text" data-bind="visible: shownElement() == 'text1'">Text 1</div>
<div id="text2" class="text" data-bind="visible: shownElement() == 'text2'">Text 2</div>
<div id="text3" class="text" data-bind="visible: shownElement() == 'text3'">Text 3</div>
<br />
<br />
<button id="button1" type="button" data-bind="click: onClick">Button 1</button>
<button id="button2" type="button" data-bind="click: onClick">Button 2</button>
<button id="button3" type="button" data-bind="click: onClick">Button 3</button>
See fiddle
I hope it helps.