Knockoutjs: dynamic content and applyBindings - javascript

I am "dynamically" populating my page like this:
<script type="text/html" id="ContainerTemplate">
<span data-bind="template: {
name: contentTemplate,
data: contentData }"></span>
</script>
<script type="text/html" id="fooTemplate">
<span data-bind="text: barAttribute"></span>
</script>
<button data-bind="click: complete">complete</button>
Hello
<span data-bind="template: { name: 'ContainerTemplate', foreach: myContents }"></span>
!
ViewModel:
var viewModel = {
myContents: ko.observableArray([]),
complete: function() {
viewModel.myContents.push({
contentTemplate:'fooTemplate',
contentData:{barAttribute:'world'}});
}
};
ko.applyBindings(viewModel);
A particularity is that template names are dynamic. It seems to work like this (you can try it on http://jsfiddle.net/hPQNx/ ), but I wonder if I'm doing things correctly. Some template features like root or parent don't seem to be working.
Should I manually re-call applyBindings at some point ? I have seen this must be done on the related DOM nodes, but how can I access those nodes in my setup ?

I added a property to your view model and showed how you can add a root property and reference it with $root and $parent can work here in this fiddle.
var viewModel = {
a: ko.observable('foo'),
myContents: ko.observableArray([]),
complete: function() {
viewModel.myContents.push({
contentTemplate: 'fooTemplate',
b: 'goo',
contentData: {
barAttribute: 'world'
}
});
}
};
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/2.0.0/knockout-min.js"></script>
<script type="text/html" id="ContainerTemplate">
<span data-bind="template: {
name: contentTemplate,
data: contentData }"></span>
</script>
<script type="text/html" id="fooTemplate">
<span data-bind="text: barAttribute"></span>
<div data-bind="text: $root.a"></div>
<div data-bind="text: $parent.b"></div>
</script>
<button data-bind="click: complete">complete</button>
Hello
<span data-bind="template: { name: 'ContainerTemplate', foreach: myContents }"></span>
!

Related

Knockout JS show hide div based on page URL

I want to split two different section based on page URL. I'm not aware how to do it using knockout.
The below example I have tried so far.
<!-- ko if: attr: { href: https://stackoverflow.com } -->
<p>Div 1</p>
<!-- /ko -->
<!-- ko if: attr: { href: https://getbootstrap.com } -->
<p>Div 2</p>
<!-- /ko -->
Any clarification please drop a comment. Thanks in Advance.
You can use the template system to perform this kind of switch :
ko.applyBindings({
stackoverflowData : {
totalQuestions : 321
},
bootstrapData : {
version : '5.0.2'
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.5.1/knockout-latest.js"></script>
<div data-bind="template: { name: 'stackoverflow-template', data: stackoverflowData }"></div>
<hr />
<div data-bind="template: { name: 'bootstrapData-template', data: bootstrapData }"></div>
<script type="text/html" id="stackoverflow-template">
stackoverflow specific view <br />
Questions: <span data-bind="text: totalQuestions"></span>
</script>
<script type="text/html" id="bootstrapData-template">
bootstrapData specific view <br />
Version <span data-bind="text: version"></span>
</script>
If you want something dynamic you can create a function that returns the template to use based on the viewmodel.
function getTemplate(url) {
// use reg ex
if (url == 'https://stackoverflow.com')
return 'stackoverflow';
if (url == 'https://getbootstrap.com')
return 'bootstrap';
return null;
}
var websites = [{
url: 'https://stackoverflow.com',
specificData: {
totalQuestions: 321
}
},
{
url: 'https://getbootstrap.com',
specificData: {
version: '5.0.2'
}
}
];
websites.map(function(website) {
website.template = ko.computed(function() {
return getTemplate(this.url);
}, website);
return website;
})
ko.applyBindings({
websites: websites
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.5.1/knockout-latest.js"></script>
<div data-bind="foreach: websites">
<div data-bind="template: { name: (template()+'-template'), data: specificData }">
<hr />
</div>
</div>
<script type="text/html" id="stackoverflow-template">
stackoverflow specific view <br /> Questions: <span data-bind="text: totalQuestions"></span>
</script>
<script type="text/html" id="bootstrap-template">
bootstrapData specific view <br /> Version <span data-bind="text: version"></span>
</script>
To make this work, you'll need to bring the url into your viewmodel. You could do this with a simple custom url bindinghandler. When initialized, it stores the current url in the bound observable (in this case myUrl) and adds an event listener for storing a changed url (in this case I use hash change to be able to make the example work).
Now, in HTML you can show or hide divs based on this observable value. Have a look at the example below.
ko.bindingHandlers.url = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
valueAccessor()(location.href);
window.addEventListener('hashchange', function(ev) {
valueAccessor()(ev.newURL);
});
}
};
ko.applyBindings({
myUrl: ko.observable(),
showDiv1: function() {
window.location.hash = 'div1'
},
showDiv2: function() {
window.location.hash = 'div2'
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<p>
Current url: <em data-bind="text: myUrl"></em>
</p>
<button data-bind="click: showDiv1">Show Div 1</button>
<button data-bind="click: showDiv2">Show Div 2</button>
<div data-bind="url: myUrl">
<!-- ko if: myUrl() == "https://stacksnippets.net/js#div1" -->
<p>Div 1</p>
<p data-bind="text: myUrl"></p>
<!-- /ko -->
<!-- ko if: myUrl() == "https://stacksnippets.net/js#div2" -->
<p>Div 2</p>
<p data-bind="text: myUrl"></p>
<!-- /ko -->
</div>

Kendo MVVM - template not updating

In the code snippet, I want to update a Kendo Template by changing an item of an array.
The problem is: it updates data-bind="text: foo", but it doesn't update #:foo#.
Why?
var viewModel = kendo.observable({
myArray: [
{foo: 'foo not updated'}
],
update() {
this.set('myArray[0].foo', 'foo updated!')
}
});
kendo.bind($(document.body), viewModel);
<div data-bind="source: myArray" data-template="tmp"></div>
<script id="tmp" type="text/x-kendo-template">
<div data-bind="text: foo"></div>
<div>#:foo#</div>
</script>
<button type="button" data-bind="click: update">Update</button>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://kendo.cdn.telerik.com/2018.2.620/js/jquery.min.js"></script>
<script src="https://kendo.cdn.telerik.com/2018.2.620/js/kendo.all.min.js"></script>

Knockout binding with a handle to templated element

Knockout is just great but I'm a little bit confused on how to deal with DOM elements after they are generated. For example I have a collection of users. Each user has an Id:
var user = {
id : 123,
name : 'testUser',
age: 45
};
Using Knockout I bind my collection of described above data structure with the following html template:
<div data-bind="foreach: users">
<div class='user-wrapper'>
<span data-bind="text: name"></span>
<span data-bind="text: age"></span>
</div>
</div>
and now I want to change background color on user click:
$(".user-wrapper").click(function (e) {
//doesn't work - toggelClass is not a function
e.target.toggleClass("user-selected");
});
Once I hit a user target could be different (span or div), I need to make sure that I'm getting the right div. Moreover e.target doesn't work with "not a fucntion" error.
How can I access calling element to toggle the class?
How can I get a user id of that element to access other controls related to that id?
You should use the click binding in conjunction with the css binding:
<div data-bind="foreach: users">
<div class='user-wrapper' data-bind="click: toggleSelected, css: { 'user-selected': isSelected }">
<span data-bind="text: name"></span>
<span data-bind="text: age"></span>
</div>
</div>
Note that if you're ever tempted to use jQuery to manipulate DOM while you're using KnockoutJS (or client side MVVM libraries in general): don't. If you absolutely must, you probably need a custom binding handler, much like you'd use a directive for DOM manipulation in "that other" mvvm framework.
Here's a demo:
var user = {
id : 123,
name : 'testUser',
age: 45
};
var UserVm = function(data) {
var self = this;
self.name = data.name;
self.age = data.age;
self.isSelected = ko.observable(false);
self.toggleSelected = function() {
self.isSelected(!self.isSelected());
}
};
ko.applyBindings({ users: [new UserVm(user)] });
.user-selected { background-color: red; }
.user-wrapper:hover { cursor: pointer; background-color: pink; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="foreach: users">
<div class='user-wrapper' data-bind="click: toggleSelected, css: { 'user-selected': isSelected }">
<span data-bind="text: name"></span>
<span data-bind="text: age"></span>
</div>
</div>

Knockoutjs: Keep track of recursion depth?

I'm using a recursive template which can shortly be summarized as follows:
<script id="template-name" type="text/html">
<div data-bind="foreach: $data">
<span data-bind="text: title"></span>
<div data-bind="template: { name: 'template-name', data: children }"></div>
</div>
</script>
<div data-bind="template: { name: 'template-name', data: $data }"></div>
What I would like to do, however, is to keep track of the current recursion depth somehow. How would I approach this using Knockout?
I've tried a number of approaches, but I'm not familiar enough with Knockout to figure out where I'm making a mistake.
<script id="template-name" type="text/html">
<div data-bind="foreach: $data[0]">
<span data-bind="text: $data[1].toString()"></span>:<span data-bind="text: title"></span>
<div data-bind="template: { name: 'template-name', data: ko.observableArray([children, ++$data[1]]) }"></div>
</div>
</script>
<div data-bind="template: { name: 'template-name', data: ko.observableArray([$data, 0]) }"></div>
The problem was easy to figure out once I found a way to debug the current context for Knockout. You can use either the Knockout Context Debugger or just throw raw json to your document using
<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>
In the end, the syntax I went with was
<script id="template-name" type="text/html">
<div data-bind="foreach: $data.children">
<span data-bind="text: $parent.depth"></span>:<span data-bind="text: title"></span>
<div data-bind="template: { name: 'template-name', data: { children: children, depth: ($parent.depth + 1) } }"></div>
</div>
</script>
<div data-bind="template: { name: 'template-name', data: { children: $data, depth: 0 } }"></div>

Knockout Conditional Tool

I need to add a conditional tool tip based on the menu item name. I am new to knockout and not sure on the best way to approach this.
<div id="pageMenu" data-bind="foreach: Pages">
<div data-bind="visible: $data.accessAllowed() ">
<a data-bind="click: $parent.openPage, css: { 'selected': Selected }"><div data-bind="text: MenuItemName"></div></a>
In this example title depends whether foo and bar have the same text. If you change foo's text to be foo for example the title will be title2
function bla(){
self.text = ko.observable("Some text");
self.bar = ko.observable("bar");
self.foo = ko.observable("bar");
}
ko.applyBindings(new bla());
<p data-bind="text: text,
attr:{
'title': bar() === foo() ? 'title1' : 'title2'
}">
</p>
you can define function in your view model where you can put your condition for showing the tooltip or hide it like this :
function ViewModel(menuItemName) {
......
self.visibleDiv = ko.computed(function () {
//here you have the item that your are displaying.
//So put here your logic and then
//you should return false or true
.......
});
}
then in your html
<div class="editor-label" data-bind="text: MenuItemName,visible: $root.visibleDiv(MenuItemName)">
This is should be what you are looking for.
<div id="pageMenu" data-bind="foreach: Pages">
<div data-bind="visible: $data.accessAllowed() ">
<a data-bind="click: $parent.openPage, css: { 'selected': Selected }">
<div data-bind="attr : {'title' : ($data.MenuItemName() == 'criteria'? 'tooltip1' : 'tooltip2')} "></div>
</a>
</div>
</div>
I hope it helps.

Categories

Resources