Knockoutjs and changing template dynamically - javascript

I use a knockoutjs with templating plugin (Using Underscore Template with Knockout using interpolate due to asp.net)
If i have a header and a list:
<ul data-bind="template: { name: 'people' }"></ul>
<script type="text/html" id="people">
<h2>{{= hdr}}</h2>
{{ _.each(people(), function(item) { }}
<li>{{=item.name }} ({{=item.age }})</li>
{{ }); }}
</script>
also a button
<button id="bindclick">Click</button>
and a ja code where i use knockout:
ko.applyBindings({
hdr: "People",
people: ko.observableArray([{name:"name1",age: 45},{name:"name2",age: 33}])
});
How to do, that template value can be change with clicking a button instead of "Uncaught Error: You cannot apply bindings multiple times to the same element."?:
$("#bindclick").click(function() {
ko.applyBindings({
hdr: "People2",
people: ko.observableArray([{name:"name1",age: 45}])
});
});
Thanks

You should only need to call applyBindings once with a model object.
Later on in your click handler, you would simply update your model.
For example:
var theModel = {
hdr: ko.observable('People'),
people: ko.observableArray([{name:"name1",age: 45},{name:"name2",age: 33}])
};
ko.applyBindings(theModel);
$('#bindclick').click(function () {
theModel.hdr('People2');
theModel.people([{name:"name1",age: 45}]);
});
Updating the model should update your previously bound content.

Related

Pass click action using array and v-for (Vue.js)

I am trying to pass through a v-for the text and the #click action of each li. For the text I know how to do it...but for the click action?enter code here
Each item of the array menuOptions (which is in the 'data' part of my Vue component) is structured like this :
{name: "firstOption",action: "console.log('first option called')"}
The first parameter is the name of the option, the
<ul>
<li v-for="option in menuOptions" #click="option.action">{{option.name}}</li>
</ul>
Do you have some ideas? (I guess that's maybe a pure JS question, but maybe there are possibilities to do it Vue too?)
Pass a function expression to the action property, instead of a "stringified" function.
{
name: 'firstOption',
action: function() {console.log('first option called'}
}
var app = new Vue({
el: '#app',
data: {
menuOptions: [{
action: function() {
console.log('foo')
},
name: 'foo'
}, {
action: function() {
console.log('bar')
},
name: 'bar'
}],
}
})
<script src="https://unpkg.com/vue"></script>
<div id='app'>
<ul>
<li v-for="option in menuOptions" #click="option.action">{{option.name}}</li>
</ul>
</div>

Why aren't my template's if clauses update reactively?

For whatever reason I am unable to solve this issue with countless hours of troubleshooting. I have some simple helpers working with a Bootstrap 3 nav-tabs list.
I want to render a different template based on which list item is active. Here are my helpers:
Template.Profile.helpers({
'personal':function(){
if($('.profile-info').hasClass('active')) {
return true;
} else {
return false;
}
},
'groups':function(){
if($('.profile-groups').hasClass('active')) {
return true;
} else {
return false;
}
},
'commitments':function(){
if($('.profile-commitments').hasClass('active')) {
return true;
} else {
return false;
}
}
});
And here is my HTML:
<ul class="nav nav-tabs">
<li class="active profile-info">Personal Info</li>
<li class="profile-groups">Groups</li>
<li class="profile-commitments">Commitments</li>
</ul>
{{#if personal}}
{{> ProfilePersonal}}
{{else}}
{{#if groups}}
{{> ProfileGroups}}
{{else}}
{{> ProfileCommits}}
{{/if}}
{{/if}}
The helpers will not be re-run when you click a tab, as there is no reactive data change to invalidate the computation.
A more Meteor-ish approach would be to add a reactive variable to hold the tab state and change that in an event listener.
<template name="Profile">
<ul class="nav nav-tabs">
{{#each tabs}}
<li class="{{isActive #index}} profile-{{name}}">{{title}}</li>
{{/each}}
</ul>
{{> Template.dynamic template=tpl}}
</template>
#index references the index of the current loop, and it's provided as an argument to the isActive helper.
Then, your JavaScript file can include a definition for the tabs and the handling code:
var tabs = [{
idx: 0,
name: "info",
title: "Personal Info",
template: "ProfilePersonal"
}, {
idx: 1,
name: "groups",
title: "Groups",
template: "ProfileGroups"
}, {
idx: 2,
name: "commitments",
title: "Commitments",
template: "ProfileCommits"
}];
The tabs are a plain JS array. The following code uses them in the template's context:
Template.Profile.helpers({
// get current sub-template name
tpl: function() {
var tpl = Template.instance();
return tabs[tpl.tabIdx.get()].template;
},
// get the tabs array
tabs: function() {
return tabs;
},
// compare the active tab index to the current index in the #each loop.
isActive: function(idx) {
var tpl = Template.instance();
return tpl.tabIdx.get() === idx ? "active" : "";
}
});
Template.Profile.events({
'click .nav-tabs > li': function(e, tpl) {
tpl.tabIdx.set(this.idx);
}
});
Template.Profile.onCreated(function() {
this.tabIdx = new ReactiveVar();
this.tabIdx.set(0);
});
When the template is created (onCreated()), a new reactive variable is added as an instance variable. This variable can then be accessed in helpers and set in event handlers.
The event handler receives the event object and template instance as parameters and has the data context set as the this pointer; therefore, tpl.tabIdxrefers the reactive variable and this refers to the object that represents the clicked tab (for example,
{
idx: 0,
name: "info",
title: "Personal Info",
template: "ProfilePersonal"
}
for the first tab, as this was the template's data context when the first tab was rendered.
The helper functions get the Template instance using a call to Template.instance(). Then, it queries the value of the reactive array.
This creates a computation in a reactive context (helpers are reactive contexts and they are rerun when the computation they create is invalidated, and that happens when an Mongo cursor, or a reactive variable that is read in the computation is changed).
Therefore, when the reactive variable is set in the event handler, the helpers are re-run and the template reflects the new value.
These are all fundamental to Meteor and are explained in the full Meteor documentation and in many resources.

knockout multi view using templates

I have a list of products where the name is a link to the product's details view. The list of products is the "Results" view
Samsumg
iPhone
When the user clicks on a product, the "Details" template is shown, and the "Results" template is not shown; at least that is the behavior that I want.
I am using the following code to accomplish this behavior, and have the jsFiddle here http://jsfiddle.net/justinnafe/mLf5G/:
<div data-bind="template: displayMode"></div>
<script type="text/html" id="Result">
<ul data-bind="foreach: products">
<li></li>
</ul>
</script>
<script type="text/html" id="Details">
<p data-bind="text: name"></p>
<p data-bind="text: description"></p>
</script>
and the javascript:
var view = {
name: "Result"
};
var initialProducts = [{
name: "Samsumg",
description: "The best phone"
},{
name: "iPhone",
description: "The other best phone"
}];
var viewModel = (function (){
var products = ko.observableArray(initialProducts),
displayMode = ko.observable(view),
switchDisplayMode = function(item){
if (displayMode() == 'Result') {
displayMode({ name: "Details", data: item });
}
else {
displayMode({ name: "Result", data: item });
}
};
return {
products: products,
displayMode: displayMode,
switchDisplayMode: switchDisplayMode
};
})();
ko.applyBindings(viewModel);
I am trying to pass that product to the Details template, but have been unsuccessful. Any clues or tips would be helpful.
I am currently getting a "ReferenceError: products is not defined" error when I click on a link, but not sure how to fix it. Maybe if I fix that error, the switching views will behave as expected.
In your function to switch the template, you are forgetting that your displayMode observable is holding an object - not a string value.
So inside switchDisplayMode, displayMode() = { name: 'Result' }. Switching that to displayMode().name fixes the problem. See updated fiddle

KnockoutJs: Getting value from dropdownlist

I'm having hard time getting the selected value of dropdown list using Knockout JS
jsFiddle
HTML
<select id="l" data-bind="options: locations, value=selectedLocation"></select>
<select id="j" data-bind="options: jobTypes, value=selectedJobType"></select>
<button data-bind="click: myFunction"> Display </button>
Script
var viewModel = {
locations: ko.observableArray(['All Locations', 'Sydney', 'Melbourne', 'Brisbane', 'Darwin', 'Perth', 'Adelaide']),
selectedLocation: ko.observable(),
jobTypes: ko.observableArray(['All Vacancies', 'Administration', 'Engineering', 'Legal', 'Sales', 'Accounting']),
selectedJobType: ko.observable(),
myFunction: function() {
alert(selectedJobType + ' ' +selectedLocation );
}
};
// ... then later ...
//viewModel.availableCountries.push('China');
// Adds another option
ko.applyBindings(viewModel);
That should be
value:selectedLocation
and:
value:selectedJobType
in you bindings. Bindings use the same syntax as an object literal.
Also, in your alert, you need viewModel.selectedJobType(), because (a) it's a property of viewModel not of global and (b) it's an observable so you need to call it to get the value. Same for selectedLocation.
Here's a working fiddle

In knockout.js, is it possible to use a dynamic binding value?

In knockout.js, is it possible to let the right-hand-side of a binding (the value of the binding) be dynamic? For example,
<input data-bind="value: dynamicBinding()"/>
<script type="text/javascript">
var vm = {
dynamicBinding : function() {
return "foo().bar";
},
foo : ko.observable({
bar : ko.observable("hi");
}
};
ko.applyBindings(vm);
</script>
the result should be that the the dynamicBinding function is executed while applying the bindings and the resulting string is used as the binding. The input element should be bound to foo().bar, which is the observable with the value "hi".
If you wonder why I would want this, I am trying to render a dynamic table with knockout, where both the rows and the columns are observableArrays, and I want to allow the column definitions to contain the expression of the binding for that column. I.e., I want to be able to do this:
<table data-bind="foreach: data">
<tr data-bind="foreach: $root.columns">
<td data-bind="text: cellValueBinding()"></td>
</tr>
</table>
<script type="text/javascript">
var vm = {
data: ko.mapping.fromJS([
{title: "Brave New World", author: { name : "Aldous Huxley" },
{title: "1984", author: { name : "George Orwell" },
{title: "Pale Fire", author: { name : "Vladimir Nabokov" }]),
columns: ko.observableArray([
{header: "Title", cellValueBinding: function () { return "$parent.title"; }},
{header: "Author", cellValueBinding: function () { return "$parent.author().name"; }}
])
};
ko.applyBindings(vm);
</script>
As you can see from the example, the column definition knows how to extract the value from the data. The table markup itself is more or less a placeholder. But as far as I can tell, this does not work, due to the way knockout processes the bindings. Are there any other options available?
Thanks.
Solution: I ended up using Ilya's suggestion - I can let cellValueBinding be a function that accepts the row and column as arguments, and returns an observable. This technique is demonstrated in this fiddle.
Use ko.computed for it.
Look on example
JSFiddle
EDIT
In your second example, you can pass $parent value ti the function
<td data-bind="text: cellValueBinding($parent)"></td>
and in model
{header: "Title", cellValueBinding: function (parent) { return parent.title; }},
{header: "Author", cellValueBinding: function (parent) { return parent.author().name; }}
JSFiddle

Categories

Resources