I have a ko.observableArray that when the page gets initialized 1 item is added. I then use a and a data-bind="foreach items" to create a div for each item in the ko.observableArray. I have a button and textbox on the page that when you add text to the input and click the button a new item gets pushed on to the ko.observableArray. This works fine I can add a new items with each button click. The items in the ko.observableArray have a price associated with them and the price changes. I want to update the price while still being able to add new items to the ko.observableArray. The price and item name are also ko.observable.
self.items= ko.observableArray(ko.utils.arrayMap(items, function(item) {
return { name: ko.observable(item.name), price: ko.observable(item.price) };
How to I update the underlying item values (price) and not recreate the ko.observable array? Do I have to loop through each item in the ko.observable array? The data is coming from a JSON call. BTW I am new to Knockout.js.
Here is my attempt at a JSFiddle but I could not get it fully working. Adding an item works fine but when I update if I have a different amount of item..like less items the ones not getting updated are destroyed. Anyway around this? I do not want to fetch data that does not have any changes in it.
Do something like this instead
Javascript
var itemObject = function(data){
var self = this;
//I recommend using the mapping plugin
//ko.mapping.fromJS(data, {}, self);
//If you use the mapping plugin, you don't have to hand bind each property
this.Id = ko.observable(data.Id);
.... etc ....
};
var baseViewModel = function(){
var self = this;
this.Items = ko.observableArray();
this.Setup = function(items){
//using underscore.js to map the items.
//This turns each item into an "itemObject", which you can
//then manipulate and have all changes shown on the screen, even
//inside the array.
self.Items(_.map(items, function(item){
return new itemObject(item);
}));
};
};
$(function(){
var myApp = new baseViewModel();
myApp.Setup(items);
ko.applyBindings(myApp);
});
html
<div data-bind="foreach: Items">
<div data-bind="text: Id"></div>
<!-- other binding things here -->
</div>
Related
I want to create a observable binding like a image resize calculation. After a few days of trying to accomplish it by my self I get frustrated. Perhaps someone can help me out.
The view has two input fields with numbers. Lets say the first 800 and the second 600. When I change the number in the first input field, the second should also update but with a calculation for proportional resizing. So for this task, I need the old and new value from the first input field and make a calculation with the value from the second input field. The result should then passed to the second input field. This should work also vista verca.
EDIT: After a little bit distance I think I found a working solution. Below I post the code so when someone has a suggestion for a improvement, your welcome:
// View has custom binding handler 'dimensionBinding' bound to an observable. Additional it put the contrary observable to the allBindings object.
<input data-bind="dimensionBinding: valueHeight, contrary: valueWidth" type="number" >
<input data-bind="dimensionBinding: valueWidth, contrary: valueHeight" type="number" >
// viewModel
self.valueHeight = ko.observable(800);
self.valueWidth = ko.observable(600);
// bindingHandlers
ko.bindingHandlers.dimensionBinding = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
// get the initially value from the observable and put it into the view
$(element).val( ko.unwrap(valueAccessor()) );
$(element).on('change', function(event) {
// get the value from the contrary input field
const contraryValue = ko.unwrap(allBindings.get('contrary'));
// get the value from this input before change
const valueBeforeChange = ko.unwrap(valueAccessor());
// get the new value from this input field
const newValue = event.target.value;
// calc proportional and set the returning value to the contrary observable
allBindings.get('contrary')( contraryValue / valueBeforeChange * newValue)
valueAccessor()(newValue)
});
},
update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
const value = ko.unwrap(valueAccessor());
// update the view
$(element).val(value);
}
};
I am using dojo EnhancedGrid to display some data and handling "onRowClick" event to allow the user to click on a row in the grid to view more details about this row like below.
dojo.connect(grid, "onRowClick", grid, dojo.partial(displayDetailsForSelectedElement, type) );
Now, I want to allow the user to delete an item from the grid by providing a delete button in separate column for each of the row in the grid. The code for the button is provided below.
function buttonFormatterRemove(col, rowIndex){
var w = new dijit.form.Button({
label: "Delete", //label: '<img src="images/del.png" />',
iconClass: "dijitIconDelete", //"dijitEditorIcon dijitIconCut",
showLabel: false,
onClick: function() {
console.log(this);
//if (confirm("Are you sure you want to delete the assertion?")){
alert("Do I come here for delete...");
//var item = grid.selection.getSelected();
//var work_id = grid.store.getValue(item[0], "work_id");
var item = this.grid.getItem(rowIndex);
var id = item['id'];
alert("delete row with id = " + id);
//Send POST request to delete
require(["dojo/request"], function(request){
request.del(contextPath + "/rest/items/" + id)
.then(function(text){
console.log("The server returned: ", text);
});
});
//}
}//function
});
w._destroyOnRemove=true;
return w;
}
The issue I am having is that when I click on the delete button for a row, though it does come inside onClick(), the code after alert("Do I come here for delete..."); doesn't get invoked. After, it executed first alert(), it calls displayDetailsForSelectedElement() to handle 'onRowClick'.
I am not sure if this issue is due to the fact that 2 events are fired when I click on delete button and if there is a solution to fix this? Any help and advice would be much appreciated.
You may call dojo.stopEvent(e) firstly to stop the event propagation in your delete method.
I'm using Google places AutoComplete on a textbox and it's essentially working, picking the locations and stuffing them into the textboxes.
The problem is that I want to only stuff the selection name - not the full name + address formatting out of the list that the AutoComplete list produces. I can't seem to find a way to override what goes into the textbox.
var $name = $("#txtName");
$name.focus();
var autocomplete = new google.maps.places.Autocomplete($name[0]);
google.maps.event.addListener(autocomplete, 'place_changed', function () {
var place = autocomplete.getPlace();
// explicitly update model
// but doesn't display in UI
$scope.locationData.Name = place.name;
// explicitly update the input box - doesn't update UI
$name.val(place.name);
return false; // doesn't work - updates UI
});
Basically what I'd like to do is take over the input box text assignment myself by doing the assignment myself and forcing autocomplete to not set the textbox value.
Is this possible?
Set the value with a short delay.
$('#NameBox').on('blur change', function () { $(this).val(place.name).off('blur change'); });
setTimeout(function () { $('#NameBox').val(place.name); }, 150);
Using .off() allows the user to edit the name if they wish. If you wish to only allow the places API name remove .off().
I have a case where I databind to a date field inside model in a list:
function Model(data) {
var self = this;
ko.mapping.fromJS(data, {}, this);
}
<div id="fieldOnPage" data-bind="text: formatDate(myDateField())"></div>
Then, in a modal, I display the same date field so it can be edited:
<div id="fieldInModal" data-bind="text: formatDate(myDateField())"></div>
However, since I'm calling the formatDate function does its work on the unwrapped observable, I'm unable to see the changes get written real-time back onto the main page when I edit the value in the modal.
Another caveat is that I using the ko.mapping plugin so I don't necessarily have a specific ko.computed field on myDateField. Is this possible to do with an external function like this? If not, how would I do it using the ko.computed if I had to specifically override the myDateField binding?
You could do something like
function Model(data) {
var self = this;
ko.mapping.fromJS(data, {}, this);
this.formattedDate = ko.computed(function () {
return formatDate(ko.utils.unwrapObservable(self.myDateField));
});
}
The bind to the formatted Date
<div id="fieldInModal" data-bind="text: formattedDate"></div>
Hope this helps.
I try to bind observableArray to div on my page and everything is ok. This array contains simple JSON objects, not observable, obtained from WebService.
After that, I want to be able to modify those objects in array and would like view to be refreshed with each modification. For example, when checkbox gets clicked I would like to change the flag on my JSON object (this seems to work automatically all right) and at the same time my UI should get updated, which does not happen. Could anyone provide me with the reason (is this because those objects are simple, not observable?) and solution?
var DocumentContentModel = function () {
var self = this;
self.content = ko.observableArray();
self.ElementApprovalChanged = function (element) {
DocumentService.DoSomething(
element.Id,
function (result) {
if (!result) {
var negatedApproved = !element.Approved;
element.Approved = negatedApproved;
}
},
function (error) {
alert(error);
});
return true;
};
};
$(document).ready(function () {
var contentModel = new ContentModel();
DocumentService.GetContent(1,
function (result) {
contentModel.content(JSON.parse(result));
});
ko.applyBindings(contentModel);
});
UI
<div class="ContentContainer">
<div data-bind='foreach: content'>
<div class="ContentElement" data-bind='css: { NotApproved: !Approved} '>
<div class="ContentValue" data-bind='html: Value'></div>
<div class="Approval">
<input type="checkbox" data-bind='checked: Approved, click: $root.ElementApprovalChanged' />
</div>
</div>
</div>
</div>
What is happening is on checkbox click I send request to webservice and if this call returns false I want to reset element's Approved flag. And even whithout that, selecting checkbox should change div class attribute to mark it as NotApproved when needed. But none of this happens.
An observableArray only tracks the array. So if something is added, removed or replaced in the array this will trigger an update to your view.
An observableArray does NOT track the state of individual properties on the items in the array. So if you have an Approved flag on your objects this needs to be an observable for the UI to reflect changes to that property.
So you would have something like:
element.Approved = ko.observable(false);
....
....
if (!result) {
var negatedApproved = !element.Approved();
element.Approved(negatedApproved);
}
(or if you want to be more consise:
element.Approved(!element.Approved());
)