knockout data-bind on dynamically generated elements - javascript

How is it possible to make knockout data-bind work on dynamically generated elements? For example, I insert a simple html select menu inside a div and want to populate options using the knockout options binding. This is what my code looks like:
$('#menu').html('<select name="list" data-bind="options: listItems"></select>');
but this method doesn't work. Any ideas?

If you add this element on the fly after you have bound your viewmodel it will not be in the viewmodel and won't update. You can do one of two things.
Add the element to the DOM and re-bind it by calling ko.applyBindings(); again
OR add the list to the DOM from the beginning and leave the options collection in your viewmodel empty. Knockout won't render it until you add elements to options on the fly later.

Knockout 3.3
ko.bindingHandlers.htmlWithBinding = {
'init': function() {
return { 'controlsDescendantBindings': true };
},
'update': function (element, valueAccessor, allBindings, viewModel, bindingContext) {
element.innerHTML = valueAccessor();
ko.applyBindingsToDescendants(bindingContext, element);
}
};
Above code snippet allows you to inject html elements dynamically with the "htmlWithBinding" property. The child elements which are added are then also evaluated... i.e. their data-bind attributes.

rewrite html binding code or create a new. Because html binding prevents "injected bindings" in dynamical html:
ko.bindingHandlers['html'] = {
//'init': function() {
// return { 'controlsDescendantBindings': true }; // this line prevents parse "injected binding"
//},
'update': function (element, valueAccessor) {
// setHtml will unwrap the value if needed
ko.utils.setHtml(element, valueAccessor());
}
};

For v3.4.0 use the custom binding below:
ko.bindingHandlers['dynamicHtml'] = {
'update': function (element, valueAccessor, allBindings, viewModel, bindingContext) {
// setHtml will unwrap the value if needed
ko.utils.setHtml(element, valueAccessor());
ko.applyBindingsToDescendants(bindingContext, element);
}
};

EDIT: It seems that this doesn't work since version 2.3 IIRC as pointed by LosManos
You can add another observable to your view model using myViewModel[newObservable] = ko.observable('')
After that, call again to ko.applyBindings.
Here is a simple page where I add paragraphs dynamically and the new view model and the bindings work flawlessly.
// myViewModel starts only with one observable
var myViewModel = {
paragraph0: ko.observable('First')
};
var count = 0;
$(document).ready(function() {
ko.applyBindings(myViewModel);
$('#add').click(function() {
// Add a new paragraph and make the binding
addParagraph();
// Re-apply!
ko.applyBindings(myViewModel);
return false;
});
});
function addParagraph() {
count++;
var newObservableName = 'paragraph' + count;
$('<p data-bind="text: ' + newObservableName + '"></p>').appendTo('#placeholder');
// Here is where the magic happens
myViewModel[newObservableName] = ko.observable('');
myViewModel[newObservableName](Math.random());
// You can also test it in the console typing
// myViewModel.paragraphXXX('a random text')
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/2.2.1/knockout-min.js"></script>
<div id="placeholder">
<p data-bind="text: paragraph0"></p>
</div>
<a id="add" href="#">Add paragraph</a>

It's an old question but here's my hopefully up-to-date answer (knockout 3.3.0):
When using knockout templates or custom components to add elements to prebound observable collections, knockout will bind everything automatically. Your example looks like an observable collection of menu items would do the job out of the box.

Based on this existing answer, I've achived something similar to your initial intentions:
function extendBinding(ko, container, viewModel) {
ko.applyBindings(viewModel, container.children()[container.children().length - 1]);
}
function yourBindingFunction() {
var container = $("#menu");
var inner = $("<select name='list' data-bind='options: listItems'></select>");
container.empty().append(inner);
extendBinding(ko, container, {
listItems: ["item1", "item2", "item3"]
});
}
Here is a JSFiddle to play with.
Be warned, once the new element is part of the dom, you cannot re-bind it with a call to ko.applyBindings- that is why I use container.empty(). If you need to preserve the new element and make it change as the view model changes, pass an observable to the viewModel parameter of the extendBinding method.

Checkout this answer: How do define a custom knockout 'options binding' with predefined Text and Value options
ko.applyBindingsToNode is particularly useful.

Related

Knockout two way binding

i am trying to make a two way binding in Knockout.js, but i am not pretty sure, that my approach is the right suggestion.
What i need is very simple:
I need the id of the binded element of my observable.
Here is my first approach:
HTML:
<div id='test' data-bind="attr {id: 'test'}, html: id"></div>
Javascript:
var vm = {
id: ko.observable()
};
ko.applyBindings(vm);
In the end, i need the id iformation in my viewmodel.
Maybe it´s not possible and not really reliable to knockout. But i dont want to go through the domtree with jquery selector if dont have the information in my viewmodel.
Thanks in advance
You need to give id in observable
id: ko.observable('test')
this will produce id
Fiddle Demo
From the comments on the original question, I don't think you're looking for two-way binding - you're looking for a way to cache the jQuery selector so that it can be accessed in your view model.
For that, I would suggest the following:
Add properties or variables in your view model that will hold the selector results. These do not need to be observables, as the IDs of your elements will never change.
Create a function that you call once on initialization of your view model, that will assign the results of the jQuery selectors to their respective properties/variables.
Subscribe to whatever observable contains the data, and trigger your animation from there.
Here's an example of how this could be done in your view model (JSFiddle example):
var ViewModel = ( function () {
var ViewModel = function () {
// ... stuff
this.data = ko.observable( 'No data here :(' );
this.data.subscribe( this.animate.bind( this ) );
};
// This is the function where you store the result of the jQuery selectors
ViewModel.prototype.cacheSelectors = function () {
this.testElement = $( '#test' );
};
// This is an example function that will load your data
ViewModel.prototype.loadData = function () {
this.data( 'Oh wait, here\'s some data!' );
};
// This is an example function that you could trigger to animate your element
ViewModel.prototype.animate = function () {
this.testElement.animate( { 'padding-left': '+=250px' }, 'slow' );
};
return new ViewModel();
}() );
ViewModel.cacheSelectors();
ko.applyBindings( ViewModel );

Knockout binding and CK Editor toolbar not appearing

I am totally new to knock-out custom binding, I am trying to integrate ckeditor with knock-out biding, I have the following binding got from Google search,
ko.bindingHandlers.wysiwyg = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var value = valueAccessor();
var valueUnwrapped = ko.unwrap(value);
var allBindings = allBindingsAccessor();
var $element = $(element);
$element.attr('contenteditable', true);
if (ko.isObservable(value)) {
var isSubscriberChange = false;
var isEditorChange = true;
$element.html(value());
var isEditorChange = false;
$element.on('input, change, keyup, mouseup', function () {
if (!isSubscriberChange) {
isEditorChange = true;
value($element.html());
isEditorChange = false;
}
});
value.subscribe(function (newValue) {
if (!isEditorChange) {
isSubscriberChange = true;
$element.html(newValue);
isSubscriberChange = false;
}
});
}
}
}
I have the following code to bind,
$(function () {
$.getJSON("/getdata", function (data) {
ko.applyBindings({
testList: [{
test: ko.observable()
},
{
test: ko.observable()
}]
}, document.getElementById('htmled'));
});
});
HTML as follows
<div id="htmled" data-bind="foreach:testList">
Data
<div class="editor" data-bind="wysiwyg: test">Edit this data</div>
</div>
The binding works and show the toolbar when I call the ko.applyBindings outside the $.getJSON method. But when I call applyBindings inside, the toolbars not appearing. Can any body help me on this? I must be missing something for sure, any help on this is greatly appreciated.
Jsfiddle Added
Working :http://jsfiddle.net/jogejyothish/h4Lt3/1/
Not Working : http://jsfiddle.net/jogejyothish/Se8yR/2/
Jyothish
What's happening is this:
Your page loads with the single div. KO has yet to be applied to this div.
document.ready() fires. The CKEditor script applied CKEditor to any matching divs (none).
You make your ajax call.
The Ajax call completes. You apply bindings.
KO inserts two new divs, neither of which has CKEditor.
In order to fix it, you need to add some code inside your ajax success function to manually initialise the CKEditors, like:
$(".editor").each(function(idx, el) {
CKEDITOR.inline(el)
});
Here it is, working in your fiddle:
http://jsfiddle.net/Se8yR/5/
The reason your working version works is because the bindings are applied in document.ready, so KO renders the two div elements in time, and the CKEditor is successfully applied to them.
CKEditor takes some time to load.
In your first example, it loads after ko applies, which works fine.
In the second example, it loads before ko applies. The problem is that CKEditor looks for the contenteditable attribute which you set with ko, so the editor is not created.
You can create it manually with:
CKEDITOR.inline(element).setData(valueUnwrapped || $element.html());
Doc
Demo

Knockout.js binding with multiple Select2

My Question is when ever I bind my Select2 with Multiple with Knockout View Model. After selecting one of the options, the data is lost for the second time
KnockOutCode
$(window).load(function () {
ko.bindingHandlers.select2 = {
init: function (element, valueAccessor, allBindingsAccessor) {
var obj = valueAccessor(),
allBindings = allBindingsAccessor(),
lookupKey = allBindings.lookupKey;
$(element).select2(obj);
if (lookupKey) {
var value = ko.utils.unwrapObservable(allBindings.value);
$(element).select2('data', ko.utils.arrayFirst(obj.data.results, function (item) {
return item[lookupKey] === value;
}));
}
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).select2('destroy');
});
},
update: function (element) {
$(element).trigger('change');
}
};
ko.applyBindings(new ViewModel());
function ViewModel() {
var self = this;
self.MetricsModel = ko.observableArray([]);
GetMetrics();
function GetMetrics() {
$.ajax({
url: '/Admin/GetMetrics',
type: "POST",
dataType: "json",
success: function (returndata) {
self.MetricsModel(returndata);
},
error: function () {
alert("eRROR GET Applications");
}
});
};
}
$("#application-select-metrics").select2();
}
HTML File
<select multiple="multiple" id="application-select-metrics" class="form-control" data-bind="options: MetricsModel, optionsText: 'Metrics_Name', OptionsValue:'Metrics_ID', optionsCaption: 'Choose...', select2: {}"></select>
#*<select multiple="multiple" id="application-select-metrics" class="form-control">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
</select>*#
Please note that the commented sections, i.e, hardcoded values works, and it allows me to select multiple values, and using Knockout it works for the first time, i get a list populated, but after selecting once, for the second time the data is lost.
Please help,
Thanks,
EDIT:
As mentioned by Hanes, I've edited the code, and introduced custom binding, but still it does not work, I dont think the update section of the custom binding is working properly,as the drop down populate once but fails to bind for the second time. Any help would be gladly appreciated.
#rniemeyer threw this up on a JSFiddle not too long ago that should help you out:
http://jsfiddle.net/rniemeyer/R8UF5/
His fiddle, updated
Use the following binding combined with a couple fiddles for when a value is updated:
ko.bindingHandlers.select2 = {
init: function(element, valueAccessor, allBindingsAccessor) {
var obj = valueAccessor(),
allBindings = allBindingsAccessor(),
lookupKey = allBindings.lookupKey;
setTimeout(function() {
$(element).select2(obj);
}, 0);
if (lookupKey) {
var value = ko.utils.unwrapObservable(allBindings.value);
$(element).select2('data', ko.utils.arrayFirst(obj.data.results, function(item) {
return item[lookupKey] === value;
}));
}
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$(element).select2('destroy');
});
},
update: function(element) {
$(element).trigger('change');
}
};
First, in response to the comments: your code was correct. The JSFiddle done by Jeroen introduced the error in the mocked ajax call: he returned an array of ints, not of objects with the correct attributes. The problem only occurs when the select2 is applied.
Cause
You're applying select2, but select2 is not playing nice with Knockout. And why should it? It doesn't know anything about Knockout and your viewmodel, and it doesn't know how to play nice with it.
Solution
You need a knockout custom binding for the select2 control. A knockout custom binding is the way to create integration between your Knockout code and 3rd party plugins. To write and explain such a custom binding for you would be a bit too much for this answer, so instead I'll give you the following link:
https://github.com/ivaynberg/select2/wiki/Knockout.js-Integration
There's a solution that will help you fix the problem. They also link to a JSFiddle, and all in all you should be able to find all you need there. If this one is too complex for you, you might try googling 'select2 knockout custom binding' and see if you can find something less complex.
A reference to the concept of Knockout custom bindings: http://knockoutjs.com/documentation/custom-bindings.html
Good luck!

Knockout change while jQuery Datepicker is open breaks Datepicker

I've set up a jsFiddle to demonstrate my problem.
What I'm doing:
I'm using Knockout with a jQuery Datepicker as shown in this question.
My model contains an observableArray which contains objects with date properties. Knockout renders an <input type="text"> for every object, and binds the value to the date using RP Niemeyer's datepicker binding.
What's going wrong:
If the user has a datepicker open when knockout's model changes (e.g., adding a new item), the datepicker stops working correctly. From what I can tell, the last <input type="text"> created while the datepicker is open becomes the new datepicker target. I need to fix the binding so that this does not happen.
Sample HTML:
<ul data-bind="foreach: dateBoxes">
<li>
<span data-bind="text: index"></span>
<input type="text" data-bind="datepicker: date"/>
</li>
</ul>
Sample Javascript:
ko.bindingHandlers.datepicker = {
init: function(element, valueAccessor, allBindingsAccessor) {
//initialize datepicker with some optional options
var options = allBindingsAccessor().datepickerOptions || {};
$(element).datepicker(options);
//handle the field changing
ko.utils.registerEventHandler(element, "change", function() {
var observable = valueAccessor();
observable($(element).datepicker("getDate"));
});
//handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$(element).datepicker("destroy");
});
},
update: function(element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
//handle date data coming via json from Microsoft
if (String(value).indexOf('/Date(') == 0) {
value = new Date(parseInt(value.replace(/\/Date\((.*?)\)\//gi, "$1")));
}
var current = $(element).datepicker("getDate");
if (value - current !== 0) {
$(element).datepicker("setDate", value);
}
}
};
var model;
var id = 0;
function DateBox(d) {
var self = this;
self.date = ko.observable(d);
self.index = ko.observable(id++);
}
function Model() {
var self = this;
self.dateBoxes = ko.observableArray([]);
self.changeCount = function() {
if (self.dateBoxes().length < 2) {
self.dateBoxes.push(new DateBox(new Date()));
} else if (self.dateBoxes().length > 1) {
self.dateBoxes.splice(0,1);
}
}
self.tick = function() {
self.changeCount();
setTimeout(function() {
self.tick();
}, 5000);
}
self.tick();
}
model = new Model();
ko.applyBindings(model);
Note: This answer isn't complete. However, as it's too large for a comment and probably helpful (towards a solution) anyhow I've taken the liberty and posted it as an answer. Anyone that can complete this answer is invited to do so through an edit or by spinning off into another better answer.
The thing that seems to be spoiling the party (taken from the documentation, emphasis mine):
This will be called once when the binding is first applied to an element, and again whenever the associated observable changes value.
If I hackisly prevent the update from doing its datepicker calls in the first update the datepicker won't break anymore (other issues do arise though). For the init I've added this line at the end:
element.isFirstRun = true;
Then the update method will do the following just before the datepicker calls:
if (element.isFirstRun) {
$(element).val(value);
element.IsFirstRun = false;
return;
}
See this updated fiddle for the results, which are:
the mentioned scenario now updates the correct textbox (a good thing);
the initial value is now a more verbose DateTime string and stays that way after updates (kinda not nice);
Hopefully this will help towards a more complete solution.

KnockoutJS: Passing click event data to a div

I need to pass data from a click: event into another div. Here is a scenario:
There is a link on one side of the page.
<a data-bind="text: Name, click: $root.editAction"></a>
On the other side of the page, there is a hidden div.
<div data-bind="if: $root.editActionShow">
<input type="text" data-bind="value: Name"/>
</div>
I need to be able to pass $data from the click: event, do that hidden div.
Perhaps I am over-thinking this, but my viewModel has many different Actions buried deep in viewModel.DataGroups.DataGroup.ActionDataGroup and there is only 1 HTML form to edit action information, so I can't figure out how to make the form only show that one particular action I want to edit.
Here is another kicker. I prefer not to add any observables to my viewModel. Reason being is that I have to do .toJS() map it at the end, and then convert JSON into XML, which must validate against a pretty strict schema, so having extra elements is a bad thing. It will not pass validation, unless I manually remove them before conversion. However, I can add this.blah = function() {} objects to my viewModel, because .toJS() strips them during conversion.
UPDATE:
Aaand solution to all this is hands down hilarious
viewModel.editAction = function(data) {
viewModel.editActionFormShow(true);
ko.applyBindings(data, $('#myHiddenDiv')[0]);
};
From what I understand, you want something like a 'click-to-edit' function, which can be solved pretty neatly with just 2 custom bindings!
The great advantage about this approach is you won't polute your viewModel with extra observables.
Bindings:
ko.bindingHandlers.hidden = {
update: function(element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
ko.bindingHandlers.visible.update(element, function() {
return!value; });
}
};
ko.bindingHandlers.clickToEdit = {
init: function(element, valueAccessor,allBindingsAccessor){
var value = valueAccessor(),
input = document.createElement('input'),
link = document.createElement('a');
element.appendChild(input);
element.appendChild(link);
value.isEditing = ko.observable(false);
ko.applyBindingsToNode(link,{
text: value,
hidden: value.isEditing,
click: function(){
value.isEditing(true);
}
});
ko.applyBindingsToNode(input,{
value: value,
visible: value.isEditing,
hasfocus: value.isEditing
});
}
};
ViewModel
var vm = {
name: ko.observable()
}
The HTML
<div data-bind="clickToEdit: name"></div>
Working fiddle: http://jsfiddle.net/8Qamd/
All credit goes to Ryan Niemeyer.

Categories

Resources