Knockout JS: setting value in foreach - javascript

I'm trying to reassign an object value in foreach with click on removePollOption function
<div data-bind="foreach: pollOptions">
<input data-bind="value: title">
<div data-bind="text: destroy">
<a href='#' data-bind='click: $root.removePollOption'></a>
</div>
pollOptions array:
this.pollOptions = ko.observableArray(ko.utils.arrayMap(optionsInitialData, function(pollOption) {
return { id: pollOption.id, title: pollOption.title, destroy: pollOption.destroy };
}));
but when I try to do it in function value is not changing dynamically
this.removePollOption = function() {
this.destroy = true;
};
If i try this.destroy(true); I get an error Uncaught TypeError: boolean is not a function

I understood that I should declare destroy as observable like
this.pollOptions = ko.observableArray(ko.utils.arrayMap(optionsInitialData, function(pollOption) {
return { id: pollOption.id, title: pollOption.title, destroy: ko.observable(false) };
}));

Related

Using a method inside a key value pair to truncate a string

I am using the Lodash and jQuery library inside my javascript and I am trying to figure out how to call a method that will allow me to truncate the results of a key value pair used to create a list inside my .html code. The html looks as follows:
<div class="slide-in-panel">
<ul class="list-unstyled slide-in-menu-navigation" data-bind="foreach: __plugins">
<li class="btn-block">
<div class="btn btn-default btn-block" data-bind="click: $parent.showPlugin, tooltip: 'Shoebox.Panel'">
<span data-bind="text: config.title"></span>
<em class="webgis-Icon webgis-Cross slide-in-menu-remove-shoebox-button"
data-bind="click: $parent.showRemoveConfirmBox, tooltip: 'Shoebox.RemoveShoebox'">
</em>
</div>
</li>
</ul>
</div>
The key component is the data-bind="text: config.title" part. This populates the list with name for that button. The config.title is created in the javascript file below. My goal is to apply a method such as .truncate() to the config.title part in the javascript to keep whatever name is being populated, from being to long. How would I do this?
return this.__backendShoeboxClient.createShoebox(this.__shoeboxName()).then((function(_this) {
return function(shoebox) {
return $when.join(shoebox.getName(), shoebox.getId(), shoebox.getUserName()).then(function(arg) {
var shoeboxId, shoeboxName, userName;
shoeboxName = arg[0], shoeboxId = arg[1], userName = arg[2];
return _this.__shoeboxContentFactory.create({
shoeboxId: shoeboxId,
shoeboxName: shoeboxName,
userName: userName
}).then(function(arg1) {
var activeShoeboxHandle, config, shoeboxContent;
shoeboxContent = arg1.shoeboxContent, activeShoeboxHandle = arg1.activeShoeboxHandle;
_this.__activeShoeboxHandleMain.loadModel(activeShoeboxHandle);
config = {
plugin: shoeboxContent,
title: shoeboxName,
userName: userName,
id: shoeboxId,
handle: activeShoeboxHandle,
icon: ""
};
_this.add(config, null, null);
activeShoeboxHandle.loadModel(shoebox);
_this.__shoeboxName.useDefaultValue();
return _this.__shoeboxName.clearError();
});
})["catch"](function(error) {
__logger__.error("Error while calling request " + error);
return $when.reject(new Error("Error while calling request. " + error));
});
};
})(this));
};
I am also trying to use the knockout style binding like this, but without any success:
<span data-bind="style: { textOverflow: ellipsis }, text: config.title"></span>
This should do it:
Use the truncate function like this: config.title = _.truncate(config.title, {'length': maxLength});
return this.__backendShoeboxClient.createShoebox(this.__shoeboxName()).then((function(_this) {
return function(shoebox) {
return $when.join(shoebox.getName(), shoebox.getId(), shoebox.getUserName()).then(function(arg) {
var shoeboxId, shoeboxName, userName;
shoeboxName = arg[0], shoeboxId = arg[1], userName = arg[2];
return _this.__shoeboxContentFactory.create({
shoeboxId: shoeboxId,
shoeboxName: shoeboxName,
userName: userName
}).then(function(arg1) {
var activeShoeboxHandle, config, shoeboxContent;
shoeboxContent = arg1.shoeboxContent, activeShoeboxHandle = arg1.activeShoeboxHandle;
_this.__activeShoeboxHandleMain.loadModel(activeShoeboxHandle);
config = {
plugin: shoeboxContent,
title: shoeboxName,
userName: userName,
id: shoeboxId,
handle: activeShoeboxHandle,
icon: ""
};
config.title = _.truncate(config.title, {'length': 15});
_this.add(config, null, null);
activeShoeboxHandle.loadModel(shoebox);
_this.__shoeboxName.useDefaultValue();
return _this.__shoeboxName.clearError();
});
})["catch"](function(error) {
__logger__.error("Error while calling request " + error);
return $when.reject(new Error("Error while calling request. " + error));
});
};
})(this));
};
So, for edification's sake, I was able to find a solution to this problem using the substring method inside a simple if statement. The issue seemed to be that I was putting this in the wrong part of my code so I want to clarify what worked for me for future readers. I was able to apply the following inside the key: value pair and it totally worked:
config =
plugin: shoeboxContent
title: if name.length > 24
"#{name.substring 0, 24}..."
else
name
userName: shoebox.getUserName()
id: shoebox.getId()
handle: activeShoeboxHandle
icon: ""
#add config, null, null

How to calculate grand total from a list

I'm making an POS application on mobile phone and I have a question. How do I calculate the grand total from the list of item from the database?
Here's my code
order-detail.dxview
POSApp.OrderDetail = function (params, viewInfo) {
"use strict";
var id = params.id,
order = new POSApp.OrderViewModel(),
isReady = $.Deferred(),
// Item List
shouldReload = false,
dataSourceObservable = ko.observable(),
dataSource;
function handleViewShown() {
POSApp.db.Orders.byKey(id).done(function (data) {
order.fromJS(data);
isReady.resolve();
});
// Item List
if (!dataSourceObservable()) {
dataSourceObservable(dataSource);
dataSource.load().always(function () {
isReady.resolve();
});
}
else if (shouldReload) {
refreshList();
}
// Item List
}
// Item List
function handleViewDisposing() {
POSApp.db.OrderDetails.off("modified", handleOrderDetailsModification);
}
function handleOrderDetailsModification() {
shouldReload = true;
}
// Item List
dataSource = new DevExpress.data.DataSource({
store: POSApp.db.OrderDetails,
map: function (item) {
return new POSApp.OrderDetailViewModel(item);
},
expand: ["Item"],
sort: { field: "OrderDetailId", desc: false },
filter: ["OrderId", parseInt(id)]
});
POSApp.db.OrderDetails.on("modified", handleOrderDetailsModification);
var viewModel = {
grandTotal: ko.observable(total),
handleDelete: function () {
DevExpress.ui.dialog.confirm("Are you sure you want to delete this item?", "Delete item").then(function (result) {
if (result)
handleConfirmDelete();
});
},
handleConfirmDelete: function () {
POSApp.db.Orders.remove(id).done(function () {
if (viewInfo.canBack) {
POSApp.app.navigate("Orders", { target: "back" });
}
else {
POSApp.app.navigate("Blank", { target: "current" });
}
});
},
//Item List
refreshList: function () {
shouldReload = false;
dataSource.pageIndex(0);
dataSource.load();
},
//Item List
// Return
id: id,
order: order,
viewShown: handleViewShown,
isReady: isReady.promise(),
// Item List
dataSource: dataSourceObservable,
viewDisposing: handleViewDisposing,
// Item List
// Return
};
return viewModel;
};
order-detail.js
<div data-options="dxView : { name: 'OrderDetail', title: 'Order' } " >
<div data-bind="dxCommand: { onExecute: '#OrderEdit/{id}', direction: 'none', id: 'edit', title: 'Edit', icon: 'edit' }"></div>
<div data-bind="dxCommand: { onExecute: handleDelete, id: 'delete', title: 'Delete', icon: 'remove' }"></div>
<div data-options="dxContent : { targetPlaceholder: 'content' } " class="dx-detail-view dx-content-background" data-bind="dxDeferRendering: { showLoadIndicator: true, staggerItemSelector: 'dx-fieldset-header,.dx-field', animation: 'detail-item-rendered', renderWhen: isReady }" >
<div data-bind="dxScrollView: { }">
<div class="dx-fieldset">
<div class="dx-fieldset-header" data-bind="text: order.PhoneNumber"></div>
<div class="dx-field">
<div class="dx-field-label">Order id</div>
<div class="dx-field-value-static" data-bind="text: order.OrderId"></div>
</div>
<div class="dx-field">
<div class="dx-field-label">Phone number</div>
<div class="dx-field-value-static" data-bind="text: order.PhoneNumber"></div>
</div>
<div class="dx-field">
<div class="button-info" data-bind="dxButton: { text: 'Add Item', onClick: '#AddItem/{id}', icon: 'add', type: 'success' }"></div>
<!-- Item List -->
<div data-bind="dxList: { dataSource: dataSource, pullRefreshEnabled: true }">
<div data-bind="dxAction: '#OrderDetailDetails/{OrderDetailId}'" data-options="dxTemplate : { name: 'item' } ">
<!--<div class="list-item" data-bind="text: Item().ItemName"></div>
<div class="list-item" style="float:right;" data-bind="text: Amount"></div>-->
<div class="item-name" data-bind="text: Item().ItemName"></div>
<div class="item-amount" data-bind="text: Amount"></div>
<div class="clear-both"></div>
</div>
</div>
</div>
<div class="dx-field">
<div class="dx-field-label">Grand total</div>
<!--<div class="dx-field-value-static" data-bind="text: order.GrandTotal"></div>-->
<div class="dx-field-value-static" data-bind="text: grandTotal"></div>
</div>
</div>
<div data-options="dxContentPlaceholder : { name: 'view-footer', animation: 'none' } " ></div>
</div>
</div>
</div>
I've tried by using get element by class name and it still doesn't work.
I've tried by using get element by class name and it still doesn't work.
You shouldn't try to get the data from your view; it's already in your viewmodel!
This documentation page tells me you can get an array of items from your DataSource instance by calling the items method.
From your data source's map function and your text: Amount data-bind, I figured each item probably has an Amount property which holds an integer.
grandTotal can be a computed that adds these values together whenever dataSourceObservable changes:
grandTotal: ko.computed(function() {
var total = 0;
var currentDS = dataSourceObservable();
if (currentDS) {
var currentItems = currentDS.items();
total = currentItems.reduce(function(sum, item) {
return sum + item.Amount;
}, total);
}
return total;
});
Here, the source is a Knockout observable array and the dxList data source. A value of grand totals is stored in the 'total' variable which is a computed observable depending on 'source'. So, once 'source' is changed, 'total' is re-calculated as well.
var grandTotal = ko.observable(0);
dataSource = new DevExpress.data.DataSource({
// ...
onChanged: function () {
grandTotal(0);
var items = dataSource.items();
for (var i = 0; i < items.length; i++) {
grandTotal(grandTotal() + items[i].Amount());
}
}
});
return {
// ...
grandTotal: grandTotal
};

Edit not working for newly added data - knockout

I have been following some tutorials and trying to follow knockout.js. I am not able to edit any data that is newly added.
My JS Code:
var data = [
new ListData("Programmer", 1),
new ListData("Testing", 2),
new ListData("DBA", 3),
new ListData("Lead", 4),
new ListData("Manager", 5)
];
function ListData(desig, id) {
return {
Desig: ko.observable(desig),
Id: ko.observable(id)
};
}
var viewModel = {
list: ko.observableArray(data),
dataToAdd: ko.observable(""),
selectedData: ko.observable(null),
addTag: function () {
this.list.push({ Desig: this.dataToAdd() });
this.tagToAdd("");
},
selecData: function () {
viewModel.selectedData(this);
}
};
$(document).on("click", ".editData", function () {
});
ko.applyBindings(viewModel);
My View Code:
<input type="text" data-bind="value: dataToAdd" />
<button data-bind="click: addData">
+ Add
</button>
<ul data-bind="foreach: list">
<li data-bind="click: $parent.selecData">
<span data-bind="text: Desig"></span>
<div>
Edit
</div>
</li>
</ul>
<div id="EditData" data-bind="with: selectedData">
Designation:
<input type="text" data-bind="value: Desig" />
</div>
I am able to edit the data which already exists like - Programmer, Testing, DBA...but if I add a new data..I am not able to edit. Please assist.
Your addData (you named it addTag in the code, but call addData in the HTML) function doesn't construct new elements the same way your initial data creation does. Note: since your ListData constructor explicitly returns an object, new is superfluous.
addData: function () {
this.list.push(ListData(this.dataToAdd(), ""));
},

Item selection MVC view with KnockoutJS

I am trying to implement a generic ASP.net MVC view. The UI should display a list of available and selected items loading data (basically list of string) from server. User can make changes into the list i.e. can select new items from available item list and also can remove items from selected list.
I wanted to do it using KnockoutJS as to take advantage of binding.
I manage to complete it upto the point everything is working except showing selected item as checked when the view is initialized in available list. E.g. As Shown Here
I tried various options (using template (closest to what I want to achieve), Checked attr, possible options), the issue is if I manage to display item checked some other functionality breaks. Tried defining a template but could not get it to work in my case.
HTML:
<div class='moverBoxOuter'>
<div id='contactsList'>
<span data-bind="visible: availableItems().length > 0">Available countries: </span>
<ul data-bind="foreach: availableItems, visible: availableItems().length > 0">
<li>
<input type="checkbox" data-bind="checkedValue: $data, checked: $root.selectedItems" />
<span data-bind="text: title"></span>
</li>
</ul>
<span data-bind="visible: selectedItems().length > 0">Selected countries: </span>
<ul data-bind="foreach: selectedItems, visible: selectedItems().length > 0">
<li>
<span data-bind="text: title"></span>
Delete
</li>
</ul>
</div>
JS:
var initialData = [
{
availableItems: [
{ title: "US", isSelected: true },
{ title: "Canada", isSelected: false },
{ title: "India", isSelected: false }]
},
{
selectedItems: [
{ "title": "US" },
{ "title": "Canada" }
]
}
];
function Item(titleText, isSelected) {
this.title = ko.observable(titleText);
this.isSelected = ko.observable(isSelected);
}
var SelectableItemViewModel = function (items) {
// Data
var self = this;
self.availableItems = ko.observableArray(ko.utils.arrayMap(items[0].availableItems, function (item) {
return new Item(item.title, item.isSelected);
}));
self.selectedItems = ko.observableArray(ko.utils.arrayMap(items[1].selectedItems, function (item) {
return new Item(item.title, item.isSelected);
}));
// Operations
self.selectItem = function (item) {
self.selectedItems.push(item);
item.isSelected(!item.isSelected());
};
self.removeItem = function (removedItem) {
self.selectedItems.remove(removedItem);
$.each(self.availableItems, function (item) {
if (item.title === removedItem.title) {
item.isSelected = false;
}
});
};
}
var vm = new SelectableItemViewModel(initialData);
$(document).ready(function () {
ko.applyBindings(vm);
});
Could you please help, see jsfiddle below:
http://jsfiddle.net/sbirthare/KR4a6/6/
**Update: Follow up question below **
Its followup question:
I need to add a combobox on same UI e.g. for US state. The available items are counties, based on user selection in state combo I need to filter out counties. I am getting data from server using AJAX and its all successful BUT the displayed list is not refreshing. I was expecting having binding setup correctly, if we change the observable array in viewmodel, the UI should change. I tried forcing change to availableItems but it just display all items. Please see if you can spot the problem in below code where I am updating ViewModel observable array.
function multiselect_change() {
console.log("event: openmultiselect_change");
var selectedState = $("#stateDropdownSelect").val();
var propertyName = $("#PropertyName").val();
var searchId = #Model.SearchId;
var items;
var model = { propertyName: propertyName, searchId: searchId, stateName: selectedState };
$.ajax({
url: '#Url.Action("GetFilterValues", "Search")',
contentType: 'application/json; charset=utf-8',
type: 'POST',
dataType: 'html',
data: JSON.stringify(model)
})
.success(function(result) {
debugger;
items = JSON.parse(result);
vm.availableItems(items.AvailableItems);
//vm.availableItems.valueHasMutated();
//var item = document.getElementById('availableItemId');
//ko.cleanNode(item);
//ko.applyBindings(vm, item);
vm.filter(selectedState);
})
.error(function(xhr, status) {
alert(status);
});
}
As user3426870 mentioned, you need to change the value you passed to the checked binding to boolean.
<input type="checkbox" data-bind="checkedValue: $data, checked: isSelected" />
Also, I don't think you need to have selectedItems in the initial data.
Instead in the viewModel, you can do something like:
self.selectedItems = ko.computed(function() {
return ko.utils.arrayFilter(self.availableItems(), function (item) {
return item.isSelected();
});
});
It's because you give an array to the binding checked while it's supposed to be a value comparable to true or false (like undefind or an empty string).
I would use a function checking if the $data is in your array and returning a boolean to your binding.
Something like that!

UI does not update when Updating ObservableArray

When a user clicks on a row from a list in the UI (html table tblAllCert), I have a click event that fires to populate another observable array that should populate a second html table (tblCertDetails). My click event fires, I can see data come back, and i can see data in the observable array, but my view does not update.
Here is an overview of the sequence in the code-
1. user clicks on row from html table tblAllCert, which fires selectThing method.
2. In viewmodel code, selectThing passes the row data from the row selected to the GetCertificateDetails(row) method.
3. GetCertificateDetails(row) method calls getCertDetails(CertificateDetails, source) function on my data service (the data service gets data from a web api). getCertDetails(CertificateDetails, source) function returns an observable array.
4. once the data is returned to selectThing, CertificateDetails observable array is populated. It does get data to this point.
Step 5 should be updating the UI (specifically tblCertDetails html table), however the UI does not get updated.
Here is my view-
<table id="tblAllCert" border="0" class="table table-hover" width="100%">
<tbody data-bind="foreach: allCertificates">
<tr id="AllCertRow" style="cursor: pointer" data-bind="click: $parent.selectThing, css: { highlight: $parent.isSelected() == $data.lwCertID }">
<td>
<ul style="width: 100%">
<b><span data-bind=" text: clientName"></span> (<span data-bind=" text: clientNumber"></span>) <span data-bind=" text: borrowBaseCount"></span> Loan(s) </b>
<br />
Collateral Analyst: <span data-bind=" text: userName"></span>
<br />
Certificate: <span data-bind="text: lwCertID"></span> Request Date: <span data-bind=" text: moment(requestDate).format('DD/MMM/YYYY')"></span>
</td>
</tr>
</tbody>
</table>
<table id="tblCertDetails" border="0" class="table table-hover" width="100%">
<tbody data-bind="foreach: CertificateDetails">
<tr id="Tr1" style="cursor: pointer">
<td>
<ul style="width: 100%">
<b>Loan: <span data-bind=" text: loanNum"></span>
</td>
</tr>
</tbody>
</table>
My viewmodel-
define(['services/logger', 'durandal/system', 'durandal/plugins/router', 'services/CertificateDataService'],
function (logger, system, router, CertificateDataService) {
var allCertificates = ko.observableArray([]);
var myCertificates = ko.observableArray([]);
var isSelected = ko.observable();
var serverSelectedOptionID = ko.observable();
var CertificateDetails = ko.observableArray([]);
var serverOptions = [
{ id: 1, name: 'Certificate', OptionText: 'lwCertID' },
{ id: 2, name: 'Client Name', OptionText: 'clientName' },
{ id: 3, name: 'Client Number', OptionText: 'clientNumber' },
{ id: 4, name: 'Request Date', OptionText: 'requestDate' },
{ id: 5, name: 'Collateral Analyst', OptionText: 'userName' }
];
var activate = function () {
// go get local data, if we have it
return SelectAllCerts(), SelectMyCerts();
};
var vm = {
activate: activate,
allCertificates: allCertificates,
myCertificates: myCertificates,
CertificateDetails: CertificateDetails,
title: 'Certificate Approvals',
SelectMyCerts: SelectMyCerts,
SelectAllCerts: SelectAllCerts,
theOptionId: ko.observable(1),
serverOptions: serverOptions,
serverSelectedOptionID: serverSelectedOptionID,
SortUpDownAllCerts: SortUpDownAllCerts,
isSelected: isSelected,
selectThing: function (row, event) {
CertificateDetails = GetCertificateDetails(row);
isSelected(row.lwCertID);
}
};
serverSelectedOptionID.subscribe(function () {
var sortCriteriaID = serverSelectedOptionID();
allCertificates.sort(function (a, b) {
var fieldname = serverOptions[sortCriteriaID - 1].OptionText;
if (a[fieldname] == b[fieldname]) {
return a[fieldname] > b[fieldname] ? 1 : a[fieldname] < b[fieldname] ? -1 : 0;
}
return a[fieldname] > b[fieldname] ? 1 : -1;
});
});
return vm;
function GetCertificateDetails(row) {
var source = {
'lwCertID': row.lwCertID,
'certType': row.certType,
}
return CertificateDataService.getCertDetails(CertificateDetails, source);
}
function SortUpDownAllCerts() {
allCertificates.sort();
}
function SelectAllCerts() {
return CertificateDataService.getallCertificates(allCertificates);
}
function SelectMyCerts() {
return CertificateDataService.getMyCertificates(myCertificates);
}
});
And releveant excerpt from my data service-
var getCertDetails = function (certificateDetailsObservable, source) {
var dataObservableArray = ko.observableArray([]);
$.ajax({
type: "POST",
dataType: "json",
url: "/api/caapproval/CertDtlsByID/",
data: source,
async: false,
success: function (dataIn) {
ko.mapping.fromJSON(dataIn, {}, dataObservableArray);
},
error: function (error) {
jsonValue = jQuery.parseJSON(error.responseText);
//jError('An error has occurred while saving the new part source: ' + jsonValue, { TimeShown: 3000 });
}
});
return dataObservableArray;
}
Ideas of why the UI is not updating?
When you're using Knockout arrays, you should be changing the contents of the array rather than the reference to the array itself. In selectThing, when you set
CertificateDetails = GetCertificateDetails(row);
the new observableArray that's been assigned to CertificateDetails isn't the same observableArray that KO bound to your table.
You're very close to a solution, though. In your AJAX success callback, instead of creating a new dataObservableArray and mapping into that, you can perform the mapping directly into parameter certificateDetailsObservable. ko.mapping.fromJSON should replace the contents of the table-bound array (you would also get rid of the assignment in selectThing).

Categories