I'm building a simple query interface for a table of data.
I have two dropdowns - one to select the field from the table, and one to select the query to perform on that field. The query dropdown items depend on the data type, so I have to wait until the field is selected to populate it.
All the bindings work if I use plain old select elements. But I want to apply the selectric plugin to them. Problem is, after calling $(element).selectric() on the elements, I don't know how to get it to "refresh" the items - so only the first dropdown contains the bound elements because it's initially populated. The second one never seems to get the updated 'query' elements.
I've tried using a custom ko binding, and calling .selectric() on the update like so:
ko.bindingHandlers.selectric = {
init: function(element, valueAccessor)
{
$(element).selectric();
},
update: function(element, valueAccessor)
{
$(element).selectric();
}
};
Here's my bindings for the two drop downs:
<select data-bind="options: $parent.fields,
optionsCaption: 'Select field...',
value: field_name,
event: { change: fieldSelected },
selectric: {}"></select>
<select data-bind="options: queries,
optionsCaption: 'Select query...',
selectric: queries"></select>
Here's the fiddle w/ viewmodel, etc. http://jsfiddle.net/rUNJY/12/. If you disable the selectric binding, it will work... how can I get the plugin to re-create the dropdown with updated items?
There are many ways to do this. Here are two:
1) Listen to the observable specified in your binding. This same as your way.
Usage:
<select data-bind="options: queries,
optionsCaption: 'Select query...',
selectric: queries"></select>
Code:
ko.bindingHandlers.selectric = {
update: function(element, valueAccessor)
{
ko.unwrap(valueAccessor()); //must use value in order for update to be called
$(element).selectric('refresh'); //must specify that plugin should refresh. See selectric documentation
}
};
Sample: http://jsfiddle.net/p4X4j/
2) Use the observable specified in the options binding. I prefer this solution since I don't need to specify the same observable in two bindings.
Usage:
<select data-bind="options: queries,
optionsCaption: 'Select query...',
selectric: {}"></select>
Code:
ko.bindingHandlers.selectric = {
init: function (element, valueAccessor, allBindingsAccessor) {
$(element).selectric('');
if(allBindingsAccessor().options.subscribe) {
var optionsSubscription = allBindingsAccessor().options.subscribe(function (newValue) {
$(element).selectric('refresh');
});
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
optionsSubscription.dispose();
});
}
}
};
Sample: http://jsfiddle.net/LbRGz/1/
Related
When I load my page i have a <select> positioned. I use knockout data-bind to fetch the values as
<select data-bind="attr: { id: 'input-' + id(), required: !allowBlank },
options: valuesArray,
optionsText: 'description',
optionsValue: 'itemValue',
optionsCaption: 'Select One...',
value: value,
enable: isEnabled,
event: { focus: $parent.getOptions}"
class="form-control" />
Right now I'm using the focus event because i can't seem to get hold of a event that behaves like onReady...
My problems are 2:
How can I trigger $parent.getOptions as soon as possible? Preferably before user interaction...
The property value always has a value, how can I set it? I imagine that I will have to w8 for the $parent.getOptions to return... or can I "force" it?!? And yes, they key I plan to force will be available in the set of the call mentioned above.
You are trying to populate the options of your select in a given event. In knockout this is accomplished by change the value of an observable, knockout takes care of the rest. You can bind this event to a function and inside this function you can call your $parent.getOptions logic. This is an example
var exampleVM = function () {
var self = this;
self.valuesArray = ko.observableArray([]);
self.value = ko.observable();
self.getOptions = function () {
var responseFromTheServer;
//Make ajax calls in here to get the values and format those to
//an array in the form [{ description: ..., itemValue: ... }]
self.valuesArray(responseFromTheServer);
// In here knockout will update the select in the html with your options
};
self.isEnable = ko.observable(true);
// The rest of your viewmodel ....
}
Dont forget to call apply bindings. The html then will look like
<select data-bind="attr: { id: 'input-' + id(), required: !allowBlank },
options: valuesArray,
optionsText: 'description',
optionsValue: 'itemValue',
optionsCaption: 'Select One...',
value: value,
enable: isEnabled,
event: { focus: getOptions}"
class="form-control" />
Calling $parent is only used when you have a viewmodel inside a viewmodel or inside an foreach binding. In your case everything is in the same viewmodel so no point in calling $parent.
If you want to trigger this as soon as possible you should call self.getOptions at the end of your viewmodel declaration like this.
var exampleVm = function () {
var self = this;
........
//This will execute as soon the viewmodel is created.
//Remember getOptions is just a plain javascript function
self.getOptions();
}
To set a value you need first to have the options populated and later set using the associated observable like this.
self.value(myNewValue);
Knockout will do nothing if you dont have any options in the select so you have to populate your options first and right after set the value.
{Edit}
I forgot to mention if you load your options right from the start you have no need for the focus event anymore. Remove this or the call to the server will be triggered again on focus.
I have a select and I am binding the change event on it so upon changes my function will fire.
The thing is: when my page loads it fires this function, which I don't want to happen. I only want it to fire when the user actually changes the value of the dropdown.
My select dropdown:
<select data-bind="options: Main.Items,
optionsText: 'Name',
value: Main.SelectedItems,
optionsCaption: 'Please Select',
event: { change: function(data,event) { ItemClick(null) }}">
</select>
If anyone knows why it is firing on load, as well as how I can sort this out, it would be very much appreciated.
This is usually a red flag when using Knockout. Use either computed observables or subscriptions to trigger logic upon value changes. E.g.:
var MainViewModel = function(){
var self = this;
self.Items = [{Name: 'apple'}, {Name: 'orange'}];
self.SelectedItems = ko.observable();
function ItemClick(newValue) {
alert(ko.toJSON(newValue));
}
self.SelectedItems.subscribe(ItemClick);
}
ko.applyBindings({ Main: new MainViewModel() });
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="options: Main.Items,
optionsText: 'Name',
value: Main.SelectedItems,
optionsCaption: 'Please Select'"></select>
Make sure you set SelectedItems as above, or explicitly to undefined. That is, if you were to initialize with null, the UI would use the optionsCaption to immediately set it to undefined again, triggering the subscription.
PS. Your View suggests multiple items can be selected (since the property's pluralized), if so you'd need the multiple attribute on your selecte as well as a combination of an observableArray for SelectedItems and the selectedOptions binding.
PPS. The code you posted does not behave the way your actual code does. That is, in the following snippet, you can see the event bound ItemClick function is not triggered until first time you change the drop down.
function ItemClick(x) {
alert(x);
}
var MainViewModel = function(){
var self = this;
self.Items = [{Name: 'apple'}, {Name: 'orange'}];
self.SelectedItems = ko.observable();
}
ko.applyBindings({ Main: new MainViewModel() });
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="options: Main.Items,
optionsText: 'Name',
value: Main.SelectedItems,
optionsCaption: 'Please Select',
event: { change: function(data,event) { ItemClick(null) }}"></select>
I have a kendoUI dropdownlist that is in a template and is bound to a ViewModel, along with a span that is bound to the data item that is selected in the dropdownlist:
<p>
<label>Appointment Type: </label>
<select id="appointmentTypeDropDownList"
data-text-field="Name"
data-value-field="AppointmentTypeId"
data-role="dropdownlist"
data-bind="source:appointmentTypes, value:AppointmentTypeId"
data-autobind="true"
data-select="onSelected" />
</p>
<p><label>Duration:</label>
<span data-bind="text:selectedAppointment.Duration"></span> minutes
</p>
My ViewModel:
var viewModel = new kendo.observable({
appointmentTypes: appointmentTypesDataSource,
selectedAppointment : null,
});
Originally, I was using a hardcoded array of appointmentTypes, and setting the selectedAppointment to appointmentTypes[0] in the above viewModel declaration. That doesn't work now, because the datasource loads asynchronously. The viewModel is updated in the onSelected function:
var onSelected = function (e) {
var dataItem = this.dataItem(e.item.index());
viewModel.set("selectedAppointment", dataItem);
};
The template is in a window, and the span is empty the first time it loads, and then works thereafter (once the data has loaded from the first request).
My question is, how can I get the data binding of the span to work on the first request, so that it will display the Duration of the currently selected appointmentType from the list that is returned by the data source? Do I try and bind it to the selected data item of the dropdownlist? Is there a callback somewhere I should be using to do this? The template is inside of a kendoScheduler, if that matters:
var template = kendo.template($("#editor").html());
$("#scheduler").kendoScheduler({
editable: {
template: template()
}
});
Thanks!
Update: The template I've been working with is an editor for a Kendo UI Scheduler, which was not bound to its viewmodel, but was using part of the viewmodel for its datasource. In this case, trying to use the data-bind="events:{...}" syntax was causing the weird type errors I was getting. To wire up the databound event, Atanas let me know to use data-bound="onDataBound" and a global handler function (or alternately to configure my scheduler declaratively and bind it to the viewmodel). Using data-bound combined with the answer(s) below worked for me.
You could use the dataBound event on the dropdown and set selectedAppointment from there:
data-bind="source:appointmentTypes, value:AppointmentTypeId, events: { dataBound: onDataBound }"
and your view model:
var viewModel = new kendo.observable({
appointmentTypes: appointmentTypesDataSource,
selectedAppointment : null,
onDataBound: function(e) {
e.sender.select(0); // will trigger your change handler
}
});
You need to set the initial value of the selectedAppointment. This is the only way the span text will be set before the data source has been populated. Here is a runnable demo based on Northwind's Products:
<span data-bind="text:selectedProduct.ProductName"></span>
<select data-bind="source: products, value: selectedProduct"
data-text-field="ProductName"
data-value-field="ProductID"
data-role="dropdownlist"></select>
<script>
var o = kendo.observable({
selectedProduct: {
ProductID: 2,
ProductName: "Chang"
},
products: new kendo.data.DataSource({
transport: {
read: {
url: "http://demos.telerik.com/kendo-ui/service/products",
dataType: "jsonp"
}
}
})
});
kendo.bind(document.body, o);
</script>
Here is a live demo: http://jsbin.com/pawiz/1/edit
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!
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.