Knockoutjs: Checkbox list many to many Relation - javascript

I have a Knockout model that have created in the snippet below.
What I'm trying to do is create a many to many relationship in a popup dialog from option rows that ive added to a option(I'm hoping this make sense when you look at the jsfiddler). When you click on the lookup link on the children column on the rows I list ALL the option rows in a dialog and with a check box list and allow the user to select the children relations to the to a specific row, hence a many to many relationship between rows.
I have defaulted the data of of my desired as a output in the first option group but I am not sure how to make this work.
I'm not sure if I'm going about this correct way and am hoping some Knockout guru can point me in the right direction to get this functionality in the popup.
/*Select Options*/
var initialData = [{
title: "User Band",
productoptionrows: [{
id: "1",
title: "25-100",
related: [{
id: "4",
title: '1 Year'
}, {
id: "5",
title: '2 Year'
}, {
id: "6",
title: '3 Year'
}]
}, {
id: "2",
title: "101-250",
related: [{
id: "7",
title: '1 Year'
}, {
id: "8",
title: '2 Year'
}, {
id: "9",
title: 'qwe'
}]
}, {
id: "3",
title: "251-500",
related: [{
id: "10",
title: '1 Year'
}, {
id: "11",
title: '2 Year'
}, {
id: "12",
title: '3 Year'
}]
}]
}, {
title: "Please select the number of years license",
productoptionrows: [{
id: "4",
title: "1 Year",
related: []
}, {
id: "5",
title: "2 Year",
related: []
}, {
id: "6",
title: "3 Year",
related: []
}, {
id: "7",
title: "1 Year",
related: []
}, {
id: "8",
title: "2 Year",
related: []
}, {
id: "9",
title: "3 Year",
related: []
}, {
id: "10",
title: "1 Year",
related: []
}, {
id: "11",
title: "2 Year",
related: []
}, {
id: "12",
title: "3 Year",
related: []
}]
}];
$(document).ready(function () {
/*Models*/
var mappingOptions = {
'productoptionrows': {
create: function (options) {
return new productoptionrow(options.data);
}
}
};
var mappingOptionsPR = {
create: function (options) {
return new productoptionrow(options.data);
}
};
var productoptionrow = function (por) {
var self = ko.mapping.fromJS(por, {}, this);
self.relatedcsv = ko.computed(function () {
return $(por.related).map(function () {
return this.id;
}).get().join(',');
}, self);
self.selectedrelated = ko.observableArray($(por.related).map(function () {
return this.id;
}).get());
};
var ProductOptionModel = function (data) {
var self = this;
self.productoptions = ko.mapping.fromJS(data, mappingOptions);
self.isOpen = ko.observable(false);
self.selectedrelated = ko.observableArray([]);
/*Control Events*/
self.addProductOption = function () {
var newoption = ko.mapping.fromJS({
title: "Please select the number of years license",
productoptionrows: ko.observableArray([{
id: "15",
title: "25-100",
related: []
}, {
id: "16",
title: "101-250",
related: []
}, {
id: "17",
title: "251-500",
related: []
}])
}, mappingOptions);
self.productoptions.push(newoption);
};
self.copyProductOption = function (productoption) {
var copy = ko.mapping.fromJS(ko.mapping.toJS(productoption), mappingOptions);
self.productoptions.push(copy);
};
self.removeProductOption = function (productoption) {
self.productoptions.remove(productoption);
};
self.addProductOptionRow = function (productoption) {
var newrow = ko.mapping.fromJS({
id: "15",
title: "25-100",
related: []
}, mappingOptionsPR);
productoption.productoptionrows.push(newrow);
};
self.removeProductOptionRow = function (productoption) {
$.each(self.productoptions(), function () {
this.productoptionrows.remove(productoption)
})
};
self.open = function (productoption, event) {
self.selectedrelated(productoption.related);
self.isOpen(true);
};
self.close = function () {
self.isOpen(false);
}
self.associaterelated = function (record, elem) {
//console.log(ko.mapping.toJS(record));
}
};
ko.applyBindings(new ProductOptionModel(initialData), document.getElementById('page-wrapper'));
});
<link href="https://code.jquery.com/ui/1.12.1/themes/ui-lightness/jquery-ui.css" rel="stylesheet" />
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>
<script src="https://cdn.rawgit.com/gvas/knockout-jqueryui/075b303a/dist/knockout-jqueryui.min.js"></script>
<div id="page-wrapper">
<div>
<button title="Add Option" type="button" data-bind='click: $root.addProductOption'>Add Option</button>
</div>
<div id="options" data-bind="foreach: productoptions">
<div style="padding:10px;margin:20px;background-color:whitesmoke">
<table class="option-header" cellpadding="0" cellspacing="0">
<thead>
<tr>
<th>Group Title <span class="required">*</span></th>
<th>
<button title="Copy" type="button" class="" style="" data-bind='click: $root.copyProductOption'>Copy</button>
<button title="Delete Option" type="button" data-bind='click: $root.removeProductOption'>Delete Option</button>
</th>
</tr>
</thead>
<tbody>
<tr style="height:36px;">
<td>
<input type="text" data-bind='value: title'>
</td>
<td>
</td>
</tr>
</tbody>
</table>
<div>
<table class="option-header-rows" cellpadding="0" cellspacing="0">
<thead>
<tr class="headings">
<th>Id</th>
<th colspan="2" class="type-title">Title <span class="required">*</span></th>
<th>Children</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: productoptionrows">
<tr>
<td align="center">
<input required type="text" style="width:40px" data-bind='value: id'>
</td>
<td colspan="2">
<input type="text" value="25-100" data-bind='value: title'>
</td>
<td>
<input type="text" data-bind='value: relatedcsv' name="isdefault">Lookup</td>
<td>
<button title="Delete Row" type="button" data-bind='click: $root.removeProductOptionRow'>Delete Row</button>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td align="right">
<button title="Add New Row" type="button" data-bind='click: $root.addProductOptionRow'>Add New Row</button>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
<!-- popup -->
<div data-bind="dialog: { isOpen: isOpen,title:'Select relations', modal:true }">
<div data-bind="foreach: $root.productoptions">
<div data-bind='text: title'></div>
<div data-bind="foreach: productoptionrows">
<div>
<input type="checkbox" data-bind="value: id, checked: $root.selectedrelated" style="width:auto" />
ID <span data-bind='text: id'></span> - <span data-bind='text: title'></span>
</div>
</div>
</div>
</div>
<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>
</div>

I've tried to distill the many-to-many issue down to a simple example. Here, persons and things are mapped to each other. To track the mapping, each person has a list of things. For each possible thing, there is a checkbox with that value. Its checked binding is bound to the thingList for the current person.
Binding to an array makes the checkbox insert or remove its value to/from the array.
function person(name) {
return {
name,
thingList: ko.observableArray()
};
}
const vm = {
people: ko.observableArray(['Abby', 'Bill', 'Charlie'].map((n) => person(n))),
things: ko.observableArray(['apple', 'bullet', 'cup', 'doll', 'egg'])
};
ko.applyBindings(vm);
.checklist {
display: inline-block;
}
.no-bullets {
list-style-type: none;
}
.no-bullets li {
display: inline;
padding-right: 15px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div data-bind="foreach:people">
<div>Name: <span data-bind="text:name"></span>
<div class="checklist" data-bind="foreach:$parent.things">
<label><input type="checkbox" data-bind="value:$data, checked:$parent.thingList"><span data-bind="text:$data"></span></label>
</div>
<ul class="no-bullets" data-bind="foreach:thingList"><li data-bind="text:$data"></li></ul>
</div>
</div>

Related

Xeditable not working AngularJS

http://plnkr.co/edit/bQyQNBcId9sp1l69UvTO?p=preview
On click of edit the value of Id is not getting passed to the editable text box for Id.
Also it shows the read only value as well a editable text box.
Please help!
Here is the code:
HTML:
<!DOCTYPE html>
<html ng-app="xeditableTable">
<head>
<title></title>
<meta charset="utf-8" />
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.2/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-xeditable/0.6.0/css/xeditable.css"></script>
<link href="../Content/bootstrap.css" rel="stylesheet" />
<script src="app%20.js"></script>
<script src="xeditable.js"></script>
</head>
<body ng-controller="XeditableTableController">
<form editable-form name="formTable" onaftersave="saveTable(usersTable)" oncancel="cancel()">
<table class="table table-bordered table-hover table-condensed table-striped">
<tr>
<td> Id</td>
<td>Name</td>
<td>Options</td>
<td>Comment</td>
<td><span ng-show="formTable.$visible">Action</span></td>
</tr>
<tr ng-repeat="user in usersTable">
<td>
<span editable-number="user.Id" e-form="formTable">
{{user.Id}}
</span>
</td>
<td>
<span editable-text="user.Name" e-form="formTable">
{{user.Name}}
</span>
</td>
<td>
<span editable-select="user.Option" e-form="formTable" e-ng-options="o.value as o.text for o in options">
{{showOptions(user)}}
</span>
</td>
<td>
<span editable-textarea="user.Comment" e-form="formTable">
{{user.Comment}}
</span>
</td>
<td>
<button type="button" ng-show="formTable.$visible" class="btn btn-danger pull-right">Delete</button>
</td>
</tr>
</table>
<div class="btn-edit">
<button type="button" class="btn btn-default" ng-show="!formTable.$visible" ng-click="formTable.$show() ; editUsersTableFunc(usersTable)">
Edit
</button>
</div>
<div class="btn-edit" ng-show="formTable.$visible" >
<button type="submit" class="btn btn-default" ng-disabled="formTable.$waiting">
Save
</button>
<button type="button" class="btn btn-default" ng-disabled="formTable.$waiting" ng-click="formTable.$cancel()">
Cancel
</button>
</div>
</form>
</body>
</html>
JS:
function XeditableTableController($scope, $filter) {
$scope.usersTable = [{ Id: '1', Name: 'a', Option: '2', Comment: 'Awesome' },
{ Id: '2', Name: 'b', Option: '1', Comment: 'Amazing' },
{ Id: '3', Name: 'c', Option: '4', Comment: 'MindBlowing' },
{ Id: '4', Name: 'd', Option: '3', Comment: 'Like cellRenderers, cellEditor components ' },
{ Id: '5', Name: 'e', Option: '2', Comment: 'Like cellRenderers, cellEditor components ' }];
$scope.options = [
{ value: 1, text: 'Option1' },
{ value: 2, text: 'Option2' },
{ value: 3, text: 'Option3' },
{ value: 4, text: 'Option4' }
];
$scope.showOptions = function (user) {
var selected = [];
if (user.Option) {
selected = $filter('filter')($scope.options, { value: user.status });
}
return selected.length ? selected[0].text : 'Not set';
};
$scope.editing = false;
$scope.newField = {};
$scope.editUsersTableFunc = function (usersTable) {
$scope.newField = angular.copy(usersTable);
}
$scope.saveTable = function (usersTable) {
debugger;
$scope.newField = angular.copy(usersTable);
$scope.usersTable = $scope.newField;
}
$scope.cancel = function () {
$scope.usersTable = $scope.newField;
}
}
XeditableTableController.$inject = ['$scope', '$filter'];
angular.module('xeditableTable', ['xeditable']);
angular.module('xeditableTable').controller('XeditableTableController', XeditableTableController);
You are not hiding your fields anywhere that are being displayed. So table data should be like this
<td>
<span editable-number="user.Id" e-form="formTable" ng-show="!formTable.$visible">
{{user.Id}}
</span>
</td>
<td>
<span editable-text="user.Name" e-form="formTable" ng-show="!formTable.$visible">
{{user.Name}}
</span>
</td>
<td>
<span editable-select="user.Option" e-form="formTable" e-ng-options="o.value as o.text for o in options" ng-show="!formTable.$visible">
{{showOptions(user)}}
</span>
</td>
<td>
<span editable-textarea="user.Comment" e-form="formTable" ng-show="!formTable.$visible">
{{user.Comment}}
</span>
</td>
It's because in the userTable the IDs where strings rather than numbers and you were trying to put it into a numeric field. This is the new version of userTable:
$scope.usersTable = [{ Id: 1, Name: 'a', Option: '2', Comment: 'Awesome' },
{ Id: 2, Name: 'b', Option: '1', Comment: 'Amazing' },
{ Id: 3, Name: 'c', Option: '4', Comment: 'MindBlowing' },
{ Id: 4, Name: 'd', Option: '3', Comment: 'Like cellRenderers, cellEditor components ' },
{ Id: 5, Name: 'e', Option: '2', Comment: 'Like cellRenderers, cellEditor components ' }];
New version: http://plnkr.co/edit/LXcuRNuAXVaKDA6VvOtW?p=preview
Another option is instead to change the field to editable-text instead of editable-number.

Adding a select list to knockout VM not populating selects value

I am trying to learn a little bit of knockout.js so I am running through the examples on the website in particular the contact example. (Knockout Contacts Editor)
I am trying to extend this example by adding a select list with a property of 'ContactType'
ContactType is a basic list containing 2 objects.
I have created a fork of the example and extended it slightly My Extended Example fiddle
var initialData = {
"names": [{
firstName: "Danny",
lastName: "LaRusso",
contactTypeId: 1,
phones: [{
type: "Mobile",
number: "(555) 121-2121"
}, {
type: "Home",
number: "(555) 123-4567"
}]
}, {
firstName: "Sensei",
lastName: "Miyagi",
contactTypeId: 2,
phones: [{
type: "Mobile",
number: "(555) 444-2222"
}, {
type: "Home",
number: "(555) 999-1212"
}]
}],
"contactTypes": [{
"id": 1,
"type": "Family"
}, {
"id": 2,
"type": "Friend"
}]
}
var ContactsModel = function (contacts) {
var self = this;
self.contactTypes = ko.observableArray(ko.utils.arrayMap(contacts.contactTypes,
function (contactType) {
return {
id: contactType.id,
type: contactType.type
};
}));
self.contacts = ko.observableArray(ko.utils.arrayMap(contacts.names, function (contact) {
return {
firstName: contact.firstName,
lastName: contact.lastName,
phones: ko.observableArray(contact.phones),
contactTypeId: ko.observable(contact.contactTypeId)
};
}));
self.addContact = function () {
self.contacts.push({
firstName: "",
lastName: "",
phones: ko.observableArray(),
contactTypeId: ko.observable()
});
};
self.removeContact = function (contact) {
self.contacts.remove(contact);
};
self.addPhone = function (contact) {
contact.phones.push({
type: "",
number: ""
});
};
self.removePhone = function (phone) {
$.each(self.contacts(), function () {
this.phones.remove(phone)
})
};
self.save = function () {
self.lastSavedJson(JSON.stringify(ko.toJS(self.contacts), null, 2));
};
self.lastSavedJson = ko.observable("")
};
ko.applyBindings(new ContactsModel(initialData));
HTML
<div class='liveExample'>
<h2>Contacts</h2>
<div id='contactsList'>
<table class='contactsEditor'>
<tr>
<th>First name</th>
<th>Last name</th>
<th>Contact Type</th>
<th>Phone numbers</th>
</tr>
<tbody data-bind="foreach: contacts">
<tr>
<td>
<input data-bind='value: firstName' />
<div><a href='#' data-bind='click: $root.removeContact'>Delete</a>
</div>
</td>
<td>
<input data-bind='value: lastName' />
</td>
<td>
<select class="form-control" data-bind="options: $root.contactTypes,
optionsText: 'type',
optionsValue:'id',
value:'contactTypeId',
optionsCaption: 'Please Select...'"></select>
</td>
<td>
<table>
<tbody data-bind="foreach: phones">
<tr>
<td>
<input data-bind='value: type' />
</td>
<td>
<input data-bind='value: number' />
</td>
<td><a href='#' data-bind='click: $root.removePhone'>Delete</a>
</td>
</tr>
</tbody>
</table> <a href='#' data-bind='click: $root.addPhone'>Add number</a>
</td>
</tr>
</tbody>
</table>
</div>
<p>
<button data-bind='click: addContact'>Add a contact</button>
<button data-bind='click: save, enable: contacts().length > 0'>Save to JSON</button>
</p>
<textarea data-bind='value: lastSavedJson' rows='5' cols='60' disabled='disabled'></textarea>
</div>
I have 2 problems which I am not sure what I am doing wrong with.
When the page is loaded the select list is not displaying the correct value. Instead its just displaying the 'Please Select' value
When I try save the record the selected value from the select list is not saved.
Am I doing something obviously wrong here?
The issue was you were setting the value binding incorrectly. Instead of value: 'contactTypeId' when it should just be value: contactTypeId without the quotations.
<select class="form-control" data-bind="options: $root.contactTypes,
optionsText: 'type',
optionsValue: 'id',
value: contactTypeId,
optionsCaption: 'Please Select...'">
</select>
Also for the save function it's better to use use knockouts ko.toJSON instead of JSON.stringify(ko.toJS
self.save = function () {
self.lastSavedJson(ko.toJSON(self.contacts), null, 2);
};

using KnockoutJS with JQuery with drag and drop

I want the $root.addField action to happen whenever I drag my items onto the DROP THINGS HERE div. Just like what happens when you click the add field button.
Here is a fiddle so you can play with it -- http://jsfiddle.net/pt6k26kh/
JS
$(document).ready(function(){
var initialData = [
{ formTitle: "formTitle", formDescription: "formDesc", fields: [
{ fieldId: "text1", title: "title", description: "description Field", isReq: true },
{ fieldId: "text2", title: "ttitle22", description: "description Field 2", isReq: false }]
},
{ formTitle: "formTitle", formDescription: "formDesc", fields: [
{ fieldId: "text1", title: "title", description: "description Field", isReq: true },
{ fieldId: "text2", title: "ttitle22", description: "description Field 2", isReq: false }]
}
];
var FieldsModel = function(fieldTemplates) {
var self = this;
self.fieldTemplates = ko.observableArray(ko.utils.arrayMap(fieldTemplates, function(fieldTemplate) {
return {
formTitle: fieldTemplate.formTitle, formDescription: fieldTemplate.formDescription,
fields: ko.observableArray(fieldTemplate.fields) };
}));
self.addfieldTemplate = function() {
self.fieldTemplates.push({
formTitle: "",
formDescription: "",
fields: ko.observableArray()
});
};
self.removefieldTemplate = function(fieldTemplate) {
self.fieldTemplates.remove(fieldTemplate);
};
self.addField = function(fieldTemplate, e) {
console.log("---addField");
console.log(e);
fieldTemplate.fields.push({
fieldId: "text",
title: "",
description: "",
isReq: false
});
};
self.removeField = function(field) {
$.each(self.fieldTemplates(), function() { this.fields.remove(field) })
};
self.save = function() {
self.lastSavedJson(JSON.stringify(ko.toJS(self.fieldTemplates), null, 2));
};
self.lastSavedJson = ko.observable("")
};
ko.applyBindings(new FieldsModel(initialData));
});
HTML
<h2>Forms</h2>
<div id='contactsList'>
<table class='contactsEditor'>
<tr>
<th>Form Title</th>
<th>Form Description</th>
<th>Fields</th>
</tr>
<tbody data-bind="foreach: fieldTemplates">
<tr>
<td>
<input data-bind='value: formTitle' />
<div><a href='#' data-bind='click: $root.removefieldTemplate'>Delete</a></div>
</td>
<td><input data-bind='value: formDescription' /></td>
<td>
<table>
<tbody data-bind="foreach: fields">
<tr>
<td><input data-bind='value: title' /></td>
<td><input data-bind='value: description' /></td>
<td><input type="checkbox" data-bind='checked: isReq' /></td>
<td><a href='#' data-bind='click: $root.removeField'>Delete</a></td>
</tr>
</tbody>
</table>
<a href='#' data-bind='click: $root.addField'>Add field</a>
<div class="dropped" data-bind='event: {drop: $root.addField}'>DROP THINGS HERE</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="droppings" >
<div data-bind='drag: {value: $data}' class="toDrop">toDrop 1</div>
<div data-bind='drag: {value: $data}' class="toDrop">toDrop 2</div>
<div data-bind='drag: {value: $data}' class="toDrop">toDrop 3</div>
</div>
<div class="dropped" data-bind='event: {drop: $root.addField}'>DROP THINGS HERE</div>
<script type="text/javascript">
$(".droppings .toDrop").draggable({
helper: "clone",
drop: function(event, ui){
}
});
$(".dropped").droppable({
drop: function(event, ui){
$(".dropped").append('<div class="beenDropped">beenDropped</div>');
console.log("dropped");
}
});
</script>
Based on the discussion above and my understanding of the problem I think we should do the following.
Add a new method called
self.globalAddField = function(){
self.addField(self.fieldTemplates()[self.fieldTemplates().length-1])
}
The new method will pass the last item of the self.fieldTemplates() as an argument to the existing addField method. The rest of the functionality will remain same.
Modify the HTML as below.
<div class="dropped" data-bind='event: {drop: $root.globalAddField}'>DROP THINGS HERE</div>
Updated fiddle.
http://jsfiddle.net/sherin81/pt6k26kh/2/

"with" databinding on a multi-dimensional array in Knockout Js

I have an array that looks something like the following
var initData = [
new Order({
orderId: "183175",
name: "Columbus Africentric",
production: [{
pType: "Art Time",
by: "MJ"
}, {
pType: "Front Pocket",
by: "WB"
}]
}),
new Order({
orderId: "198675",
name: "Stanford High",
production: [{
pType: "Art Time",
by: "MJ"
}, {
pType: "Full Back",
by: "WB"
}]
})
]
I'm trying to do a with binding to show only extra information for on order when the item is clicked on. So I have a foreach for the orders that shows the orderId and the name in a table, and a button to click that then should show all production items for the chosen order. Something like the following
<tbody data-bind="foreach:orders">
<tr>
<td>
<label class="read" data-bind="text:orderId, visible:true" />
</td>
<td>
<label class="read" data-bind="text:name, visible:!$root.isItemEditing($data)" />
</td>
<td>
<td class="tools">
<div data-bind="if: production"><button data-bind="click: $root.toggleProductionMode">Production</button>
</div>
</td>
</tr>
<tr data-bind="visible: showProductionOrder, with: production">
<td colspan="5">
<h3>Production Summary</h3>
<table class="ko-grid">
<thead>
<tr>
<th>Type</th>
<th>By</th>
</tr>
</thead>
<tbody
<tr>
<td>
<label class="read" data-bind="text:pType, visible:!$root.isItemEditing($data)" />
</td>
<td>
<label class="read" data-bind="text:by, visible:!$root.isItemEditing($data)" />
</td>
</tr>
</tbody>
</table>
</tr>
I think I need to use a foreach to get to the production information. Can a foreach binding be used inside a with binding? Or do I even need one? If I have it bound using the "with" binding, is there a certain way to get to the multiple production items? I know this is super easy and it's probably staring me right in the face.
also, when creating the Item Model I'm doing the following, which I think might be incorrect.
function Order(data) {
self.orderId = ko.observable();
self.name = ko.observable();
self.production = ko.observableArray([
[
self.pType = ko.observable(),
self.by = ko.observable()
]
]);
}
You don't have to create a new table inside the main table. For a child collection you have to use "ko foreach: production" as a html comment and then add your tr tags afterwards to display the production items. Have a look into this JSFiddle example.
// HTML
<table>
<tr>
<th>Student ID</th>
<th>Student Name</th>
</tr>
<tbody data-bind="foreach: Students">
<tr>
<td data-bind="text: StudentID"></td>
<td data-bind="text: StudentName"></td>
</tr>
<!-- ko foreach: Courses -->
<tr>
<td style='padding-left:20px;' data-bind="text: CourseID"></td>
<td style='padding-left:20px;' data-bind="text: CourseName"></td>
</tr>
<!-- /ko -->
</tbody>
</table>
// KNOCKOUT CODE
function StudentViewModel() {
var self = this;
self.Students = [
{ StudentID: "1", StudentName: "Ali",
Courses: [ { CourseID: "100", CourseName: "Math" }, { CourseID: "102", CourseName: "Physics" } ]
},
{ StudentID: "2", StudentName: "Isa" ,
Courses: [ { CourseID: "103", CourseName: "Chemistry" }, { CourseID: "104", CourseName: "Social Studies" } ] },
{ StudentID: "3", StudentName: "Zoya" ,
Courses: [ { CourseID: "100", CourseName: "Math" }, { CourseID: "106", CourseName: "Stats" } ] },
];
}
ko.applyBindings(new StudentViewModel());

knockout js table filtering broken on newer versions

I am trying to implement that functionality on this demo:
http://opensoul.org/2011/06/23/live-search-with-knockoutjs/
I managed to get everything working, but the demo uses a very old version of knockout.
When I upgrade the knockout version the functionality breaks.
This is my updated code:
$(function() {
var assets = [
{id: "1", poster: "Pic010.jpg", name: "Mike", category: "category1", type: "Movie", popup: "1" },
{id: "2", poster: "Pic06.jpg", name: "James", category: "category2", type: "Movie", popup: "2" },
{id: "3", poster: "Pic04.jpg", name: "John", category: "category1", type: "Pop-up", popup: "3" },
{id: "4", poster: "Pic07.jpg", name: "Bob", category: "category2", type: "Pop-up", popup: "4" },
{id: "5", poster: "Pic011.jpg", name: "Mary", category: "category3", type: "Promo", popup: "5" }
];
var viewModel = {
assets: ko.observableArray(assets),
query: ko.observable(''),
search: function(value) {
viewModel.assets.removeAll();
for(var x in assets) {
if(assets[x].name.toLowerCase().indexOf(value.toLowerCase()) >= 0) {
viewModel.assets.push(assets[x]);
}
}
}
};
viewModel.query.subscribe(viewModel.search);
ko.applyBindings(viewModel);
});
The display:
<form action="#">
<input class="form-control" placeholder="Search…" type="search" name="q" data-bind="value: query, valueUpdate: 'keyup'" autocomplete="off">
</form>
<div class="content">
<table>
<tbody data-bind="foreach:assets">
<tr>
<td style="vertical-align: middle" data-bind="text: id"> </td>
<td><img data-bind="attr:{ src: '/manager/files/' + poster }" height="100px" /></td>
<td style="vertical-align: middle" data-bind="text: name"></td>
<td style="vertical-align: middle" data-bind="text: category"></td>
<td style="vertical-align: middle" data-bind="text: type"></td>
<td style="vertical-align: middle" data-bind="text: popup"></td>
<td class="actions" style="vertical-align: middle">
<i class="fa fa-pencil-square-o"></i>
<form data-bind="attr:{ action: '/Assets/delete/' + id, name: 'delete_' + id, id: 'delete_'+ id }" style="display:none;" method="post"><input type="hidden" name="_method" value="POST"></form>
</i>
</td>
</tr>
</tbody>
</table>
</div>
here are examples, working with Knockout version 1.21: http://jsfiddle.net/7ZLdk/1/ example not working with version 3.0: http://jsfiddle.net/7ZLdk/2/
This behavior has been changed back in 2011: Updated remove and removeAll to modify their underlying arrays rather han creating new arrays
So now when you call removeAll on an ko.observableArray it removes the items form the underlying arrays, so in your case it empties your original assets array.
A quick fix would be to clone the array when assigning to your ko.observableArray:
assets: ko.observableArray(assets.slice(0)),
Demo JSFiddle.
A better and more modern solution would be to use a ko.computed property and the arrayFilter helper method to do the filtering:
assets: ko.computed(function () {
if (!viewModel.query())
return assets;
return ko.utils.arrayFilter(assets, function(item) {
return item.name.toLowerCase().indexOf(viewModel.query().toLowerCase()) >= 0;
});
}, null, {deferEvaluation: true})
Demo JSFiddle.

Categories

Resources