I've searched high and low for an answer to this but can't for the life of me figure out what I'm doing differently from the official example, aside from the fact that I think my use case is a bit more complicated:
http://knockoutjs.com/documentation/component-binding.html
Basically, I'm trying to create reusable UI elements. The behavior will essentially be passed to them via the "params" object. I want multiple elements to be able to exist on a single page, however, which is where I'm running into difficulty.
I'm using browserify to bundle my code and have the following (some entries truncated for brevity):
index.html
<div data-bind='component: { name: "toggle" , params: {
enabledText: "Parental controls are enabled",
disabledText: "Parental controls are disabled"
}}'></div>
<div data-bind='component: { name: "toggle" , params: {
enabledText: "Same component, different behavior: enabled",
disabledText: "Same component, different behavior: disabled"
}}'></div>
main.js
var ko = window.ko = require('knockout'),
toggle = require('./components/toggle/toggle');
ko.components.register('toggle', toggle);
function Container() {
}
var con = new Container();
ko.applyBindings(con);
components/toggle/toggle.js
var ko = require('knockout'),
template = require('./toggle.html');
function vm(params) {
var self = this;
self.enabled = ko.observable(false);
self.label = ko.computed(function() {
return self.enabled() ? params.enabledText : params.disabledText;
});
}
module.exports = { viewModel: vm, template: template };
And finally, in components/toggle/toggle.html:
<input type='checkbox' data-bind='checked: enabled' id='switch-checkbox' class='switch-input' />
<label for='switch-checkbox' class='switch-input-label'>
<span data-bind='text: label'></span>
</label>
The issue I'm having is that the components both appear on the page appropriately, but clicking the second one activates the first one (and does nothing for the seconD). I'm new to Knockout and am clearly missing something, but I can't figure out how to fix my issue. Any help would be immensely appreciated!
The strange thing is that the labels are appropriately unique, indicating that the components (view models) being instantiated for each HTML entity are, in fact, unique...however, it seems knockout's "checked" binding is only binding to the first.
EDIT: I know it's customary to include an example, so here's one on codepen. I apologize for using browserified code but hopefully it's still readable:
http://codepen.io/sunny-mittal/pen/OVBNwp
Knockout now supports multiple model binding. The ko. applyBindings() method takes an optional parameter - the element and its descendants to which the binding will be activated. This restricts the activation to the element with ID someElementId and its descendants.
applyBindings do, The first parameter says what view model object you want to use with the declarative bindings it activates. Optionally, you can pass a second parameter to define which part of the document you want to search for data-bind attributes. For example, ko.
Knockout. js is a JavaScript implementation of the MVVM pattern with templates. There are several advantages of Knockout. js and its MVVM architecture design.
A ViewModel can be any type of JavaScript variable. In Example 1-3, let's start with a simple JavaScript structure that contains a single property called name .
When you load multiple instances of this component the id
attribute on the input
tag and the for
attribute on the label
tag, that make up your component, are no longer going to be unique to the page.
You essentially have two input
tags with the same id
and two label
tags targeting one id
. Your label for="switch-checkbox"
on the second component is picking up input id="switch-component"
from the first component and not the second.
Though knockout components are great, unfortunately there is no dom isolation between instances of components.
To resolve this issue you need to ensure the values for id
and for
in each instance of your component are unique to the entire page.
I have included a snippet below of this working.
var uid = function(){
var seed = 1;
return {
new: function(p){
return p + (seed++);
}
}
}();
var template =
"<input type='checkbox' data-bind='checked: enabled, attr: {id:id}' class='switch-input' />\n<label data-bind='attr: {for: id}' class='switch-input-label'>\n <span data-bind='text: label'></span>\n</label>\n";
var viewModel = function vm(params) {
var self = this;
self.id = uid.new('switch-checkbox-');
self.enabled = ko.observable(false);
self.label = ko.computed(function() {
return self.enabled() ? params.enabledText : params.disabledText;
});
}
var component = {
viewModel: viewModel,
template: template
};
ko.components.register('toggle', component);
var vm = {};
ko.applyBindings(vm);
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
position: relative;
height: 100%;
min-height: 100%;
}
.switch-input {
display: none;
}
.switch-input-label {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-o-user-select: none;
user-select: none;
position: relative;
display: inline-block;
cursor: pointer;
font-weight: 500;
text-align: left;
margin: 16px;
padding: 16px 0 16px 44px;
}
.switch-input-label:before,
.switch-input-label:after {
content: '';
position: absolute;
margin: 0;
outline: 0;
top: 50%;
transform: translate(0, -50%);
transition: all 0.3s ease;
}
.switch-input-label:before {
left: 1px;
width: 34px;
height: 14px;
background-color: #9e9e9e;
border-radius: 8px;
}
.switch-input-label:after {
left: 0;
width: 20px;
height: 20px;
background-color: #fafafa;
border-radius: 50%;
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.14), 0 2px 2px 0 rgba(0, 0, 0, 0.098), 0 1px 5px 0 rgba(0, 0, 0, 0.084);
}
.switch-input:checked + .switch-input-label:before {
background-color: #a5d6a7;
}
.switch-input:checked + .switch-input-label:after {
background-color: #4caf50;
transform: translate(80%, -50%);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind='component: { name: "toggle" , params: {
enabledText: "Parental controls are enabled",
disabledText: "Parental controls are disabled"
}}'></div>
<div data-bind='component: { name: "toggle" , params: {
enabledText: "Same component, different behavior: enabled",
disabledText: "Same component, different behavior: disabled"
}}'></div>
<div data-bind='component: { name: "toggle" , params: {
enabledText: "Same component, another instance: enabled",
disabledText: "Same component, another instance: disabled"
}}'></div>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With