KnockoutJS: Passing click event data to a div - javascript

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.

Related

Knockout - Fetch Options for Select and select one of them

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.

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 );

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.

knockout data-bind on dynamically generated elements

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.

Categories

Resources