I have the following situation (using KendoUI):
I have a grid binded to a datasource.
When I select a row in the grid I invoke its "change" event to get the selected dataItem e show its values through other HTML elements.
Something like the following:
$("grid-element").kendoGrid({
change: setElements
});
function setElements() {
var grid = $("#grid-element").data("kendoGrid");
var selectedItem = grid.dataItem(grid.select());
$("#span-field1").text(selectedItem.field1);
$("#span-field2").text(selectedItem.field2);
$("#span-field3").text(selectedItem.field3);
}
My question is: is it possibile to achieve the same through MVVM or a better KendoUI model binding solution?
So far I have found the following solution:
=== JAVASCRIPT ===
var vm = kendo.observable({
gridSelectedItem: null,
_field1: function() {
return this.get("gridSelectedItem.field1");
},
_field2: function() {
return this.get("gridSelectedItem.field2");
}
});
$("#grid-element").kendoGrid({
change: function(e) {
var selectedItem = this.dataItem(this.select());
vm.set("gridSelectedItem", selectedItem);
}
});
=== HTML ===
<span data-bind="text: _field1"></span>
<span data-bind="text: _field2"></span>
Is there a better way?
Indeed there you are on the right track,
Here is what I can suggest you to try:
=== JAVASCRIPT ===
var vm = kendo.observable({
gridSelectedItem: null
});
$("#grid-element").kendoGrid({
change: function(e) {
var selectedItem = this.dataItem(this.select());
vm.set("gridSelectedItem", selectedItem);
}
});
=== HTML ===
<span data-bind="text: gridSelectedItem.field1"></span>
<span data-bind="text: gridSelectedItem.field2"></span>
It should be slightly more compact.
Related
i have two seperate viewmodels in a page
function AModel() {
...
}
function BModel() {
...
self.testValue= ko.observable('test')
}
$(document).ready(function() {
var AModel1= new AModel();
var BModel1= new BModel();
ko.applyBindings(AModel1);
ko.applyBindings(BModel1);
});
now in html page
how do i make it work?
<span data-bind="text: BModel1.testValue" ></span>
You should not call ko.applyBindings multiple times on the same DOM element, this can lead to problems or to an exceptions since KO 2.3.
What you can do is to create one "wrapper" viewmodel and call ko.applyBindings with it:
$(document).ready(function() {
var AModel1= new AModel();
var BModel1= new BModel();
ko.applyBindings({ AModel1: AModel1, BModel1: BModel1 });
});
Then you can use your view:
<span data-bind="text: BModel1.testValue" ></span>
Demo JSFiddle.
I have a input field where value is equal to the id's and a button. When that button is triggered I want to remove the id in the input field also the button where the value is equal to the data stored in the input field or the id. Here http://jsfiddle.net/leonardeveloper/hcfzL/3/
HTML:
<form id="materialForm" action="#" method="post">
<input id="materials" type="text" name="materials" value="1,2,3" readonly="readonly" disabled="disabled" />
</form>
<div id="display">
<button class="removes" value="1">x</button>
<button class="removes" value="2">x</button>
<button class="removes" value="3">x</button>
</div>
JS:
$(document).on('click', '.removes', function () {
var id = $(this).val();
alert(id);
$('#materials').remove(":contains('" + id + "')");
$('#display').remove(":contains('" + id + "')");
return false;
});
.remove() is for removing DOM elements, not text from values. And it removes the element it's applied to, not elements that are contained within it.
$(document).on('click', '.removes', function () {
var id = $(this).val();
alert(id);
var materials = $('#materials').val().split(',');
materials = materials.filter(function(e) {
return e != id;
});
$('#materials').val(materials.join(','));
$(this).remove();
return false;
});
FIDDLE
The :contains selector is for selecting DOM nodes that contain other DOM nodes. In your case you look to be selecting input elements which have a particular string in their value.
You should probably use .filter to filter to select the input elements that match the filter.
Try
$(document).on('click','.removes',function(){
var id = $(this).val();
$('#materials').val(function(){
var value = this.value, array = value.split(',');
var idx = $.inArray(id, array);
if(idx >=0 ){
array.splice(idx, 1)
value = array.join(',')
}
return value;
})
$(this).remove();
return false;
});
Demo: Fiddle
I am learning to use MVC 4/MVVM/Knockout for a web-managed data project. I have been running into a problem updating the View when using the remove function on an observable array. The updates happen when using push or unshift, but not remove. Using the debugger in chrome I can see that the data is being removed from the array, the update event just isn't working.
Snippet from the html is the table below, there is a form I did not include for adding or editing data.
<div id="MessageDiv" data-bind="message: Message"></div>
<div class="tableContainer hiddenHead">
<div class="headerBackground"></div>
<div class="tableContainerInner">
<table id="adapter-table" class="grid" data-bind="sortTable: true">
<thead>
<tr>
<th class="first">
<span class="th-inner">Name</span>
</th>
<th>
<span class="th-inner">DeviceID</span>
</th>
<th>
<span class="th-inner"></span>
</th>
<th>
<span class="th-inner"></span>
</th>
</tr>
</thead>
<tbody data-bind="template: { name: 'AdaptersTemplate', foreach: Adapters }">
</tbody>
</table>
<script id="AdaptersTemplate" type="text/html">
<tr>
<td data-bind="text: Name"></td>
<td data-bind="text: DeviceID"></td>
<td>Edit
<td>Delete
</tr>
</script>
</div>
<input type="button" data-bind='click: addAdapter' value="Add New Adapter" />
<input type="button" data-bind='click: saveAll' value="Save Changes" id="SaveChangesButton" />
</div>
My javascript has been set up to manage the VM as restful and caches the changes. Add, Edit, and Saving/Deleting data all seems to work without throwing errors that I am seeing in the debugger in Chrome. Confirming changes seems to work fine and makes the changes to the database as expected.
$(function () {
var viewModel = new AdaptersModel();
getData(viewModel);
});
function getData(viewModel) {
$.getJSON("/api/AdapterList",
function (data) {
if (data && data.length > 0) {
viewModel.SetAdaptersFromJSON(data);
}
ko.applyBindings(viewModel);
});
}
//#region AdapterVM
function Adapter(name, siFamily, deviceIDs) {
var self = this;
self.Name = ko.observable(name);
self.DeviceID = ko.observable(deviceIDs);
self.ID = 0;
}
function AdaptersModel() {
var self = this;
self.Adapters = ko.observableArray([]);
self.DeleteAdapters = ko.observableArray([]);
self.NewAdapter = ko.observable(new Adapter("", "", "", ""));
self.Message = ko.observable("");
self.SetAdaptersFromJSON = function (jsData) {
self.Adapters = ko.mapping.fromJS(jsData);
};
//#region Edit List Options: confirmChanges
self.confirmChanges = function () {
if (self.NewAdapter().ID == 0) {
self.Adapters.push(self.NewAdapter());
}
};
//#endregion
//#region Adapter List Options: addAdapter, selectItem, deleteItem, saveAll
self.addAdapter = function () {
self.NewAdapter(new Adapter("", "", "", ""));
};
self.selectItem = function (item) {
self.NewAdapter(item);
};
self.deleteItem = function(item) {
self.DeleteAdapters.push(item.ID());
self.Adapters.remove(item);
};
self.saveAll = function () {
if (self.Adapters && self.Adapters().length > 0) {
var filtered = ko.utils.arrayFilter(self.Adapters(),
function(adapter) {
return ((!isEmpty(adapter.Manufacturer())) &&
(!isEmpty(adapter.Name())) &&
(!isEmpty(adapter.DeviceIDs()))
);
}
);
var updateSuccess = true;
if (self.DeleteAdapters().length > 0) {
jsonData = ko.toJSON(self.DeleteAdapters());
$.ajax({
url: "/api/AdapterList",
cache: false,
type: "DELETE",
data: jsonData,
dataType: "json",
contentType: "application/json; charset=utf-8",
success: function () { updateSuccess = true; },
error: function () { updateSuccess = false; }
});
}
var jsonData = ko.toJSON(filtered);
$.ajax({
url: "/api/AdapterList",
type: "POST",
data: jsonData,
dataType: "json",
contentType: "application/json; charset=utf-8",
success: function(data) {
self.SetAdaptersFromJSON(data);
updateSuccess = true && updateSuccess;
},
error: function () { updateSuccess = false; }
});
if (updateSuccess == true) { self.Message("Update Successfull"); }
else { self.Message("Update Failed"); }
}
};
//#endregion
}
//#endregion
ko.bindingHandlers.message = {
update: function(element, valueAccessor) {
$(element).hide();
ko.bindingHandlers.text.update(element, valueAccessor);
$(element).fadeIn();
$(element).fadeOut(4000);
}
};
ko.bindingHandlers.sortTable = {
init: function (element, valueAccessor) {
setTimeout(function () {
$(element).addClass('tablesorter');
$(element).tablesorter({ widgets: ['zebra'] });
}, 0);
}
};
function isEmpty(obj) {
if (typeof obj == 'undefined' || obj === null || obj === '') return true;
if (typeof obj == 'number' && isNaN(obj)) return true;
if (obj instanceof Date && isNaN(Number(obj))) return true;
return false;
}
The specific script portion that is failing to update my html table is:
self.deleteItem = function(item) {
self.DeleteAdapters.push(item.ID());
self.Adapters.remove(item);
};
Everything seems to work except for the remove, so I seem to be at a loss for what to look at next, and I am too new to javascript or knockout to know if this is a clue: If I run ko.applyBindings() command in the self.deleteItem function, I get the update to happen but it does give me an unhandled error:
Uncaught Error: Unable to parse bindings.
Message: ReferenceError: Message is not defined;
Bindings value: message: Message
Message was defined in the VM before binding... was there something I missed in all this?
In the beginning of your Js file you are defining var viewModel = new AdaptersModel(); but lower you are stating that function Adapter() is the view model in your region declaration. It is making your code difficult to read. I am going to take another stab at what you can do to troubleshoot, but I would suggest that your viewmodel contains the adapters and your model contains a class-like instance of what each adapter should be.
The specific error you are getting is because you are binding Message() to something and then deleting Message(). One thing you could do to trouble shoot this is to change your div to something like :
<div id="MessageDiv" data-bind="with: Message">
<h5 data-bind="message: $data"><h5>
</div>
If you could create a fiddle I could give a more definite example of why, but basically if Message() is blank the with binding should not show the header which is undefined after deletion.
What you probably need to do though is look at what is being sent as 'item' and make sure it is not your viewmodel.
self.deleteItem = function(item) {
console.log(item); // << Check console and see what is being returned
self.DeleteAdapters.push(item.ID());
self.Adapters.remove(item);
};
You are probably deleting more than just a single adapter.
This will lead you the right direction, but I would seriously consider either renaming your code.
There was a lot of help solving surrounding issues but nothing actually solved the "why" of the problem. The updates worked perfectly sometimes but not other times. When I was troubleshooting it and started to get it dumbed down and working in JSFiddle I didn't include the data-bind="sortTable: true" in all my working versions. Apparently, if you sort a table or using the code as I did it will not work. The example code I have seen floating around is here at http://jsfiddle.net/gregmason/UChLF/16/, pertinent code:
ko.bindingHandlers.tableSorter = {
init: function (element) {
setTimeout(function () { $(element).tablesorter(); }, 0);
},
update: function (element, valueAccessor) {
ko.utils.unwrapObservable(valueAccessor()); //just to get a dependency
$(element).trigger("update");
}
};
The errant behavior can be obvious by clicking the delete link on the row.
If you click on the row without sorting, you will see the row disappear correctly.
If you first click on a column to re-sort in a different order, THEN delete the row, it remains in the table and appears to have cached.
This can be handled by binding each of the table headers instead of the table itself and replacing the tableSorter code with a custom sort behavior as discussed in this thread:
knockout js - Table sorting using column headers. The sort replacement is here:
ko.bindingHandlers.sort = {
init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var asc = false;
element.style.cursor = 'pointer';
element.onclick = function(){
var value = valueAccessor();
var prop = value.prop;
var data = value.arr;
asc = !asc;
if(asc){
data.sort(function(left, right){
return left[prop]() == right[prop]() ? 0 : left[prop]() < right[prop]() ? -1 : 1;
});
} else {
data.sort(function(left, right){
return left[prop]() == right[prop]() ? 0 : left[prop]() > right[prop]() ? -1 : 1;
});
}
}
}
};
This has fixed my sorting/editing/deleting issues and a working jsFiddle is here: http://jsfiddle.net/gregmason/UChLF/18/
I have an observable object which defines a pointer to a datasource binded to a grid, and a custom field which should return an aggregate value I declared in the datasource.
I would like to bind the second field ("totAmount") to a custom HTML element.
I do not understand how to update its value: when I call the "read()" method of the datasource shouldn't the binded value also be updated on the interface? Does it work only with "primitive" model fields?
=== JAVASCRIPT ===
var vm = kendo.observable({
gridDatasource: new kendo.data.DataSource({ ... }),
totAmount: function() {
var ds = this.get("gridDatasource");
var value = (ds.aggregates()) ? ds.aggregates().totAmount : 0;
return value;
}
});
=== HTML ===
<span data-bind="text: totAmount"></span>
My previous answer was not totally correct: it binds the model update on grid change (on each row selection). It is better to bind it to the "change" event of the datasource:
=== JAVASCRIPT ===
var vm = kendo.observable({
gridDatasource: new kendo.data.DataSource({ ... }),
totAmount: 0
});
vm.gridDatasource.bind("change", function(e) {
vm.set("totAmount", this.aggregates().totAmount);
});
=== HTML ===
<span data-bind="text: totAmount"></span>
So far I have found a solution similar to my previous post (bind HTML elements to grid selected row/dataItem), setting the value in the "change" event of the grid binded to the datasource:
=== JAVASCRIPT ===
var vm = kendo.observable({
gridDatasource: new kendo.data.DataSource({ ... }),
totAmount: 0
});
$("#grid").kendoGrid({
change: function(e) {
vm.set("totAmount", this.dataSource.aggregates().totAmount);
}
});
=== HTML ===
<span data-bind="text: totAmount"></span>
links:
<ul id="topics">
<? while ($row = mysql_fetch_object($result)) { ?>
<li>- <?=$row->t_topic;?></li>
<? } mysql_free_result($result); ?>
</ul>
jQuery code:
$(function() {
$("a").click(function(){
var title = $("a").attr("title");
$("#main").html(title);
});
});
'title' is different on every link. When I clicked a link, it doesn't read var 'title'.
The code needs to read the title from a specific a tag that was clicked upon. This simple change should do it:
$(function() {
$("a").click(function(){
var title = $(this).attr("title"); // Note "this" here
$("#main").html(title);
});
});