Knockout - Fetch Options for Select and select one of them - javascript

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.

Related

Change event binding's function called on page load

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>

Applying jquery plugin (selectric) to ko-bound dropdown

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/

What is best way to bind a Kendo UI dropdownlist to a ViewModel that is populated by a datasource?

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

Save data from a jQuery Accordion

I have a sortable accordion loaded with a foreach-template loop over a ko.observableArray() named "Tasks".
In the accordion I render the TaskId, the TaskName, and a task Description - all ko.observable().
TaskName and Description is rendered in input/textarea elements.
Whenever TaskName or Description is changed, an item is de-selected, or another item is clicked on, I want to call a function saveEdit(item) to send the updated TaskName and Description to the database via an ajax request.
I need to match the TaskId with the Tasks-array to fetch the actual key/value-pair to send to the saveEdit().
This is the HTML:
<div id="accordion" data-bind="jqAccordion:{},template: {name: 'task-template',foreach: Tasks,afteradd: function(elem){$(elem).trigger('valueChanged');}}"></div>
<script type="text/html" id="task-template">
<div data-bind="attr: {'id': 'Task' + TaskId}" class="group">
<h3><b><span data-bind="text: TaskId"></span>: <input name="TaskName" data-bind="value: TaskName /></b></h3>
<p>
<label for="Description" >Description:</label><textarea name="Description" data-bind="value: Description"></textarea>
</p>
</div>
</script>
This is the binding:
ko.bindingHandlers.jqAccordion = {
init: function(element, valueAccessor) {
var options = valueAccessor();
$(element).accordion(options);
$(element).bind("valueChanged",function(){
ko.bindingHandlers.jqAccordion.update(element,valueAccessor);
});
},
update: function(element,valueAccessor) {
var options = valueAccessor();
$(element).accordion('destroy').accordion(
{
// options put here....
header: "> div > h3"
, collapsible: true
, active: false
, heightStyle: "content"
})
.sortable({
axis: "y",
handle: "h3",
stop: function (event, ui) {
var items = [];
ui.item.siblings().andSelf().each(function () {
//compare data('index') and the real index
if ($(this).data('index') != $(this).index()) {
items.push(this.id);
}
});
// IE doesn't register the blur when sorting
// so trigger focusout handlers to remove .ui-state-focus
ui.item.children("h3").triggerHandler("focusout");
if (items.length) $("#sekvens3").text(items.join(','));
ui.item.parent().trigger('stop');
}
})
.on('stop', function () {
$(this).siblings().andSelf().each(function (i) {
$(this).data('index', i);
});
})
.trigger('stop');
};
};
My first thought was to place the line
$root.SelectedTask( ui.options.active );
in an .on('click') event function where SelectedTask is a ko.observable defined in my viewModel. However, the .on('click') event seems to be called a lot and it's generating a lot of traffic. Also, I canĀ“t quite figure out where to put the save(item) call that sends the selected "item" from Tasks via an ajax-function to the database.
Any help is highly appreciated. Thanks in advance! :)
Whenever TaskName or Description is changed, an item is de-selected, or another item is clicked on, I want to call a function saveEdit(item) to send the updated TaskName and Description to the database via an ajax request.
This sounds like the core of what you want to do. Let's start out with a Task model
function Task (data) {
var self = this;
data = data || {};
self.id = ko.observable(data.id);
self.name = ko.observable(data.name);
self.description = ko.observable(data.description);
}
And then we need our View Model:
function ViewModel () {
var self = this;
self.tasks = ko.observableArray();
self.selectedTask = ko.observable();
self.saveTask = function (task) {
$.ajax({ ... });// ajax call that sends the changed data to the server
};
var taskSubscription = function (newValue) {
self.saveTask(self.selectedTask());
};
var nameSubscription, descriptionSubscription;
self.selectedTask.subscribe(function (newlySelectedTask) {
if (newlySelectedTask instanceof Task) {
nameSubscription =
newlySelectedTask.name.subscribe(taskSubscription);
descriptionSubscription =
newlySelectedTask.description.subscribe(taskSubscription);
self.saveTask(newlySelectedTask);// But why?
}
});
self.selectedTask.subscribe(function (currentlySelectedTask) {
if (currentlySelectedTask instanceof Task) {
nameSubscription.dispose();
descriptionSubscription.dispose();
self.saveTask(currentlySelectedTask);// But why?
}
}, null, 'beforeChange');
}
So what's going on here? Most of this should be pretty self explanatory so I'm just going to focus on the subscriptions. We created a taskSubscription function so we're not constantly having it defined every time the self.selectedTask changes.
We have two subscriber functions. The first fires after the selectedTask's value has changed and the second fires before it changes. In both, we verify that the new value is an instance of a Task object. In the after change subscription, we set up two subscriptions on the name and description properties. Then I capture the return value from the subscription function into two private variables. These are used in the before change function to dispose of those subscriptions so that if those Tasks are ever updated when they're not currently selected, then we don't continue to fire off the saveTask function.
I've also added self.saveTask in each of the subscriptions to the selectedTask observable. I asked why in here because, why save it if we don't know if the value has changed or not? You may be making ajax requests needlessly here.
Also, as demonstrated by this code, you can set up these subscriptions to make ajax requests every time the value changes but that may end up making a LOT of requests. A better option might be to set up functionality in your Task model that can track whether or not it is 'dirty' or not. Meaning one or more of its values have changed that requires updating.
function Task (data) {
var self = this;
// Make a copy of the data object coming in and use this to save previous values
self._data = data = $.extend(true, { id: null, name: null, description: null }, data);
self.id = ko.observable(data.id);
self.name = ko.observable(data.name);
self.description = ko.observable(data.description);
for (var prop in data) {
if (ko.isSubscribable(self[prop])) {
self[prop].subscribe(function (oldValue) {
data[prop] = oldValue;
}, null, 'beforeChange');
}
}
}
Task.prototype.isDirty = function () {
var self = this;
for (var prop in self._data) {
if (ko.isSubscribable(self[prop])) {
if (self._data[prop] !== self[prop]())
return true;
}
}
return false;
};
And of course you need a way to save it, or make it not dirty
Task.prototype.save = function () {
var self = this;
for (var prop in self._data) {
if (ko.isSubscribable(self[prop])) {
self._data[prop] = self[prop]();
}
}
};
Using the same concept you can also create Task.prototype.revert that does the opposite of what .save does. With all this in place, you could forego setting up the subscriptions on the individual name and description properties. I wanted to show that option to just demonstrate how one might want to use the .dispose method on a subscription. But now you can just subscribe to the selectedTask observable ('beforeChange') and see if the currently selected task that you're about to swap out isDirty. If it is, call the saveTask function, and when that completes, call the .save function on the Task so that it is no longer dirty.
This is probably the route I would go in implementing something like this. The beauty of it is, I haven't written a single line of code that has anything to do with the manipulating the View. You can set the selectedTask any way you see fit. What I would do is, bind the selectedTask observable to a click binding on the <h3> element inside of the accordion. That way, every time a user clicks on any of the accordions, it will potentially save the previously selected task (if any of the property values had changed).
Hopefully that addresses your scenario here of trying to save a Task when certain events are triggered.

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