Knockout Search / Filter - javascript

I'm very new to Knockout JS and I'm having a hell of a time trying to get this project completed.
I've created a map website which displays a series of pins on the page for popular locations around town. The idea is that the search bar to the left of the page will filter out pins on the map with names that do not match the search query. There is also a "master" list on the left of the page that the search bar will filter from too.
I used an example I found on jsfiddle here: http://jsfiddle.net/mythical/XJEzc/ but I'm having troubles applying that same logic to my code.
Here it is:
HTML:
<li>
<input class="form-control" placeholder="Search…" type="search" name="filter" data-bind="value: query, valueUpdate: 'keyup'" autocomplete="off">
</li>
<ul data-bind="template: {name:'pin', foreach: pins}"></ul>
</ul>
<script type="text/html" id="pin">
<li>
<strong data-bind="text: name"></strong>
</li>
</script>
JS:
self.pins = ko.observableArray([
new self.mapPin("Anchorage Alaska", 61.190491, -149.868937, "test1"),
new self.mapPin("Anchorage Alaska", 61.190491, -149.868937, "test2")
]);
self.query = ko.observable('');
self.filterPins = ko.dependentObservable(function () {
var search = self.query().toLowerCase();
return ko.utils.arrayFilter(name, function (pin) {
return pin.toLowerCase().indexOf(search) >= 0;
});
});
With the logic I've setup if the name is removed from the pin constructor it will remove it from the map.
Here is my somewhat working example: http://jamesiv.es/projects/map/

HTML
<ul data-bind="template: {name:'pin', foreach: pins}"></ul>
change to
<ul data-bind="template: {name:'pin', foreach: filterPins}"></ul>
Javascript
self.filterPins = ko.dependentObservable(function () {
var search = self.query().toLowerCase();
return ko.utils.arrayFilter(self.name, function (pin) {
return pin.toLowerCase().indexOf(search) >= 0;
});
});
change to
self.filterPins = ko.computed(function () {
var search = this.query().toLowerCase();
return ko.utils.arrayFilter(self.pins(), function (pin) {
return pin.name().toLowerCase().indexOf(search) >= 0;
});
});

Just want to update the code if using Knockout version newer than 3.2
Change from value and valueUpdate to textInput, as reccomended here
HTML:
<input class="form-control" placeholder="Search…" type="search" name="filter" data-bind="textInput: query" autocomplete="off" />
JS:
this.query = ko.observable('');
this.filteredPins = ko.computed(function () {
if (this.query()) {
var search = this.query().toLowerCase();
return ko.utils.arrayFilter(this.pins(), function (pin) {
return pin.name().toLowerCase().indexOf(search) >= 0;
});
} else {
return pins
}}, this);

Related

Sending only the updated object from ko.observableArray

How can I send only the updated model from an observable Array instead of sending the entire array?
var student = function (){
this.studentId=0;
this.firstName=ko.obserable();
this.lastName=ko.obserable();
}
var course= function (){
this.courseId=0;
this.students=ko.obserableArray([]);
this.Name=ko.obserable();
}
Now I want to get only that particular student from course whose info is updated. Assuming that when we add a new class we can dynamically add new students to it on the go. Supposing that you have to validate the previous student before adding a new one. When I get that particular student I want to send that student info back to the server.
Thanks.
If I understood your task right, you could use "arrayChange" event type to get exact changed (added/removed) items:
sourceArray = ko.observableArray();
sourceArray.subscribe(function (changes) {
changes.forEach(function(arrayChange) {
if(arrayChange.status === 'added') {
// some code on add
} else if(arrayChange.status === 'deleted') {
// some code on delete
}
});
}, null, "arrayChange");
If you want to get list of students which have been modified, you can provide a flag to identify if an object has been modified in student object. Use .subscribe to modify that flag whenever a value is updated. Then use ko.computed or ko.pureComputed to get that list.
Also it supposes to be observable.
var student = function (id, firstName, lastName) {
var self = this;
self.hasChanged = ko.observable(false);
var modified = function(){
self.hasChanged(true);
};
self.studentId = ko.observable(id);
self.firstName = ko.observable(firstName);
self.firstName.subscribe(modified);
self.lastName = ko.observable(lastName);
self.lastName.subscribe(modified);
}
var course= function (){
var self = this;
self.courseId = 0;
self.students = ko.observableArray([new student(1, "Cristiano", "Ronaldo"), new student(2, "Lionel", "Messi")]);
self.modifiedStudent = ko.computed(function(){
return ko.utils.arrayFilter(self.students(), function(student) {
return student.hasChanged();
});
}, self);
self.Name = ko.observable("Programming 101");
}
$(document).ready(function () {
var myViewModel = new course();
ko.applyBindings(myViewModel);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
List of all students:
<div data-bind="foreach: students">
<div>
<span data-bind="text: studentId"></span>
<input type="text" data-bind="value: firstName" />
<input type="text" data-bind="value: lastName" />
</div>
</div>
<br/>
List of students which has been modified:
<div data-bind="foreach: modifiedStudent">
<div>
<span data-bind="text: studentId"></span>
<input type="text" data-bind="value: firstName" readonly />
<input type="text" data-bind="value: lastName" readonly />
</div>
</div>

How to use user input with filter function in Knockout.js

I have a filter function that I would like to adapt to user input, but I don't really know how to do it. I'm fairly new to Knockout so I would appreciate some help with this.
When I click the filter button (see code below) i get this in the input area:
function observable() {
if (arguments.length > 0) {
// Write
// Ignore writes if the value hasn't changed
if (observable.isDifferent(observable[observableLatestValue], arguments[0])) {
observable.valueWillMutate();
observable[observableLatestValue] = arguments[0];
observable.valueHasMutated();
return this; // Permits chained assignments
} else {
// Read
ko.dependencyDetection.registerDependency(observable); // The caller only needs to be notified of changes if they did a "read" operation
return observable[observableLatestValue];
}
}
What I want to achieve is to write a value in the input area, click the button (for now, will use submit later) and have the search results filtered. The array employeeList is an observable array that is populated through an ajax call (the search function).
KO Code:
self.employeeList = ko.observableArray([]);
self.currentFilter = ko.observable();
self.filterEmpl = ko.computed(function () {
if (!self.currentFilter()) {
return self.employeeList();
} else {
return ko.utils.arrayFilter(self.employeeList(), function (employee) {
return employee.DepartmentName == self.currentFilter();
});
}
});
self.filter = function (value) {
self.currentFilter(value);
} //filter
HTML:
<form>
<input type="text" placeholder="Department" id="department" class="filterInput" data-bind="value: currentFilter" />
<button data-bind="click: function () { filter(currentFilter) }">Filter</button>
<br />
<input type="text" placeholder="Office" class="filterInput" />
<br />
<input type="text" placeholder="Skills" class="filterInput lastInput" />
</form>
Thanks!
Your filterEmpl is a ko.computed. This means it automatically updates once one of the observable values it uses is updated.
In your case, it will update whenever either self.employeeList or self.currentFilter changes.
To try this out, type one of the DepartmentNames in the example below. Once you remove focus from the input, the value data-bind updates currentFilter, and self.filterEmpl is updated.
var VM = function() {
self.employeeList = ko.observableArray([
{ DepartmentName: "Test1", Name: "Employee 1" },
{ DepartmentName: "Test2", Name: "Employee 2" }
]);
self.currentFilter = ko.observable();
self.filterEmpl = ko.computed(function() {
if (!self.currentFilter()) {
return self.employeeList();
} else {
return ko.utils.arrayFilter(self.employeeList(), function(employee) {
return employee.DepartmentName == self.currentFilter();
});
}
});
}
ko.applyBindings(new VM());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<h2>Type "Test1" or "Test2" and blur focus to filter</h2>
<form>
<input type="text" placeholder="Department" data-bind="value: currentFilter" />
</form>
<h2>All employees:</h2>
<ul data-bind="foreach: employeeList">
<li data-bind="text: Name"></li>
</ul>
<h2>Filtered employees:</h2>
<ul data-bind="foreach: filterEmpl">
<li data-bind="text: Name"></li>
</ul>
Now, if you want to filter only when a button is pressed, you don't need the ko.computed. You define a second ko.observableArray and write to it from within the filter function. Note that you don't need to pass it any arguments; the viewmodel is already aware of the currentFilter value via the value binding.
var VM = function() {
self.employeeList = ko.observableArray([
{ DepartmentName: "Test1", Name: "Employee 1" },
{ DepartmentName: "Test2", Name: "Employee 2" }
]);
self.currentFilter = ko.observable();
self.filterEmpl = ko.observableArray(self.employeeList());
self.filter = function() {
var result = self.employeeList(),
filter = self.currentFilter();
if (filter) {
result = ko.utils.arrayFilter(result, function(employee) {
return employee.DepartmentName == filter;
});
}
self.filterEmpl(result);
};
}
ko.applyBindings(new VM());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<h2>Type "Test1" or "Test2" and tap button to filter</h2>
<form>
<input type="text" placeholder="Department" data-bind="value: currentFilter" />
<button data-bind="click: filter">filter</button>
</form>
<h2>All employees:</h2>
<ul data-bind="foreach: employeeList">
<li data-bind="text: Name"></li>
</ul>
<h2>Filtered employees:</h2>
<ul data-bind="foreach: filterEmpl">
<li data-bind="text: Name"></li>
</ul>
Personally, I like to use the computed approach. You can extend the observable using the rateLimit option if performance is limiting. Ultimately, it's mostly a UX decision.
P.S. The input you did get in the input area is knockout's definition of the ko.observable function. In your <button>, you pass currentFilter without getting its value using currentFilter(). In filter, you write this to currentFilter which is data-bound to the <input>. I figured it'd be more useful to explain the two approaches, but you still might want to know where the strange input came from...

Javascript: Empty an input field and press enter

I use knockout framework. I have an observable array that can be filtered using ko.utils.arrayFilter
Now, I want to add a button to reset the array.
As the array gets reset when the input field is emptied using keystrokes, I wanted to simulate it with emptying the input field and then pressing the enter key
self.resetFilter = function (){
$('#filter').val('');
var e = $Event("keypress");
e.which = 13;
$("#filter").trigger(e);
};
Not sure, if the code is wrong. Or if this is a problem with Knockout, as I am not using Knockout to reset.
Below the entire code for the filtering function and the data binding in the HTML
Javascript
self.stringStartsWith = function(string, startsWith) {
string = string || "";
if (startsWith.length > string.length)
return false;
return string.substring(0, startsWith.length) === startsWith;
};
self.filter = ko.observable('');
self.filteredItems = ko.computed(function() {
var filter = self.filter().toLowerCase();
self.resetFilter = function() {
$('#filter').val('');
var e = $Event("keypress");
e.which = 13;
$("#filter").trigger(e);
};
if (!filter) {
return self.venueList();
} else {
return ko.utils.arrayFilter(self.venueList(), function(venue) {
console.log(venue);
return self.stringStartsWith(venue.name.toLowerCase(), filter);
console.log(venue);
});
}
}, self.venueList);
};
HTML
<li>
<input placeholder="Search" id="filter" type="text" data-bind="value: filter, valueUpdate: 'afterkeydown'" autocomplete="off">
<button data-bind="click: function(){resetFilter();}">Reset</button>
</li>
The beauty of knockout is that you can use data-bindings to accomplish what you want to do. While it plays nicely with other libraries like jQuery, you can probably find a more elegant knockout-only solution. You mentioned that you're not using knockout to reset. Is there a reason for that?
Further, you can use the textInput binding instead of using valueUpdate: 'afterkeydown' I'm not sure what your desired result is in terms of the flow of your search, but based on what you've provided in your question I put this example together. Pressing enter resets your filter - I'm not sure if that is your intended behavior as it seems a little strange from a UX perspective, but there it is nonetheless
var ViewModel = function() {
var self = this;
self.filter = ko.observable();
self.list = ko.observableArray([
"fruit",
"bread",
"dad",
"zoo",
"keyboard",
"monkey",
"tiger",
"apple",
"bicycle",
"father",
"mother",
"test",
"computer",
"programming",
"ninja",
"love",
"earth",
"nothing",
"money"
]);
self.filteredList = ko.computed(function() {
return ko.utils.arrayFilter(self.list(), function(item) {
return item.toLowerCase().indexOf(self.filter()) > -1;
});
});
self.clearFilter = function() {
self.filter('');
}
self.onEnter = function(d, e) {
if (e.keyCode === 13) {
//alert("You want to search for: " + self.filter());
self.clearFilter();
}
return true;
}
}
ko.applyBindings(new ViewModel())
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<input type="text" placeholder="Search" data-bind="textInput: filter, event: { keypress: onEnter }" />
<input type="button" data-bind="click: clearFilter" value="Reset" />
<ul data-bind="foreach: filteredList">
<li data-bind="text: $data"></li>
</ul>

KnockoutJS, Checkbox list reflecting which groups my currently selected object belongs to?

This is the first time I post a question here, please let me know if I need to improve it. (including the title)
I have in my model two types of objects, Device and Group. A Device can belong to a Group or not, and a Group can contain Devices or not.
What I would like to do is to have a selectlist on the left, showing a list of Device.Name. When the user selects one of them, the details of the selected Device shows in the middle of the screen, and then on the right-hand side, have a list of checkboxes, each checkbox representing a Group.
The problem is I cannot find a way to define the Checked bindings of my checkboxes. I have tried defining an observableArray of CheckedGroup{ Group, bool }, but could not get the bool value to update dynamically according to the currently selected device.
My viewmodel:
function Device(data) {
this.DeviceId = ko.observable(data.DeviceId);
this.Name = ko.observable(data.Name);
this.Number = ko.observable(data.Number);
this.IpAddress = ko.observable(data.IpAddress);
this.Description = ko.observable(data.Description);
this.Groups = ko.observableArray();
this.Groups = $.map(data.Groups, function (grp, i) {
return grp;
});
}
function Group(data) {
var self = this;
self.GroupId = ko.observable(data.GroupId);
self.Name = ko.observable(data.Name);
self.Description = ko.observable(data.Description);
}
function DevicesViewModel() {
// Data
var self = this;
self.devices = ko.observableArray([]);
self.groups = ko.observableArray([]);
self.currentdevice = ko.observable();
$.getJSON(deviceapiuri, function (data) {
var mappeddevices = $.map(data, function (dev, i) {
return new Device(dev)
});
self.devices(mappeddevices);
});
$.getJSON(groupapiuri, function (data) {
var mappedgroups = $.map(data, function (grp, i) {
return new Group(grp)
});
self.groups(mappedgroups);
});
}
My View :
// The device list on the left side
<select id="devicelist_input" size="2" data-bind="options: devices, optionsText: function (i) { return i.Name }, value: currentdevice"> </select>
//One of the input fields that are bound to my currentdevice - this correctly updates when selecting my device.
<input id="stbdescription_input" type="text" data-bind="value: currentdevice() && currentdevice().Description" />
//The list of checkboxes I an trying to bind - the list is populated correctly, but the boxes' Checked property won't update according to the currentdevice
<div id= "group_check_list_input" data-bind="foreach: groups">
<input type="checkbox" class="groupcheckbox" data-bind="value: $data, Checked:$root.currentdevice() && $root.currentdevice().Groups" />
It's quite difficult to get the checkbox selecting. The key thing to doing this is you must be using the same object in both the DevicesViewModel.groups and Device.groups. It needs to be exactly the same object and not just an object with the same data.
You can do this by passing in the viewModel groups to the device constructor and then if the the groupId matches then you add the group to the device groups. I did this using the following code:
function Device (device, groups) {
this.DeviceId = ko.observable(device.DeviceId);
this.Name = ko.observable(device.Name);
this.Number = ko.observable(device.Number);
this.IpAddress = ko.observable(device.IpAddress);
this.Description = ko.observable(device.Description);
var mappedGroups = $.map(device.Groups, function (grp) {
var group = ko.utils.arrayFilter(groups, function(group)
{
return group.GroupId() == grp.GroupId;
});
return group[0];
});
this.Groups = ko.observableArray(mappedGroups);
};
Other problems with your code is you are using value binding for the checkbox when you need to use checkedValue and checked can be just be set to $parent.Groups if you are using a with for the currentdevice.
<select data-bind="options: devices, optionsText: 'Name', value: currentdevice, optionsCaption: 'Select a device'"></select>
<div data-bind="with: currentdevice">
<label>DeviceId:</label>
<span data-bind="text: DeviceId"></span>
<label>Name:</label>
<span data-bind="text: Name"></span>
<label>Number:</label>
<span data-bind="text: Number"></span>
<label>IpAddress:</label>
<span data-bind="text: IpAddress"></span>
<label>Description:</label>
<span data-bind="text: Description"></span>
<div data-bind="foreach: $root.groups">
<input type="checkbox" data-bind="checked: $parent.Groups, checkedValue: $data"/><span data-bind="text: Name"></span>
</div>
</div>
Here is the jsfiddle of it working:
http://jsfiddle.net/k4kAU/1/

knockout observable array not updating after ajax addition

I have an observable array of a complex object. The initial load is fine, and all the expected data looks fine. Now I am working on POSTing new items to that array. NOTE: The observable array is being loaded via ASP.NET ajax web api call.
posting a new item works fine as far as saving it to the database, but my DOM is not getting updated with the new item and I don't know what I am missing.
Here is the entire ViewModel
function ClientList() {
//data
var self = this;
self.initialized = ko.observable(false);
self.clients = ko.observableArray();
self.userId = ko.observable("");
self.name = ko.observable("");
self.logo = ko.observable("");
self.projects = ko.observableArray();
self.clientAddress = ko.observableArray();
self.addClient = function () {
var client = {
UserId: self.userId,
Name: self.name,
Logo: self.logo,
}
client = ko.toJSON(client);
lucidServer.addClient(client);
self.clients.push(client);
}.bind(self);
(function () {
$.ajax({
url: lucidServer.getClients(1),
success: function (data) {
ko.mapping.fromJS(data, {}, self.clients);
self.initialized(true);
}
});
})();
};
function IncompleteStoriesList() {
//data
var self = this;
self.initialized = ko.observable(false);
self.stories = ko.observableArray();
(function () {
$.ajax({
url: lucidServer.getIncompleteStory(1),
success: function (data) {
ko.mapping.fromJS(data, {}, self.stories);
self.initialized(true);
}
});
})();
};
function ViewModel() {
var self = this;
self.clientList = new ClientList();
self.storyList = new IncompleteStoriesList();
}
ko.applyBindings(new ViewModel());
Here is the particular snippet where I am doing the POST (within the ClientList() function).
self.addClient = function () {
self.client = {
UserId: self.userId(),
Name: self.name(),
Logo: self.logo(),
}
//make client object to send to server
var client = ko.toJSON(self.client);
lucidServer.addClient(client);
//push the self.client to the observablearray of clients
self.clients.push(self.client);
}.bind(self);
I verified it is JSON that is sitting inside the client variable, and no error messages are getting thrown, so I am confused. After I add an item and refresh the entire page, it will show up in the list.
EDIT: here is the html associated:
<form data-bind="submit: clientList.addClient">
<div>
<label>userId</label>
<input type="text" data-bind="value: clientList.userId" />
</div>
<div>
<label>name</label>
<input type="text" data-bind="value: clientList.name" />
</div>
<div>
<label>logo</label>
<input type="text" data-bind="value: clientList.logo" />
</div>
<button type="submit">Add</button>
</form>
<!-- ko ifnot: clientList.initialized -->
<span>Loading...</span>
<!-- /ko -->
<ul data-bind="template:{name: 'clientList', foreach:clientList.clients}">
</ul>
And the external template looks like this:
<div id="clientListOutput">
<li><span data-bind="text: name"></span>
<div data-bind="template: {foreach: clientAddress}">
<span data-bind="text: city"></span>
<span data-bind="text: state"></span>
</div>
<hr />
<ul data-bind="template: {foreach: projects}">
<li>
<span data-bind="text: name"></span>
<span data-bind="text: summary"></span>
<span data-bind="text: description"></span>
</li>
</ul>
</li>
I am quite certian you have a typo in your HTML. Here is a working example using ko.observablearray
HTML:
<form data-bind="submit: addItem">
prop1: <input data-bind='value: prop1, valueUpdate: "afterkeydown"' />
prop2: <input data-bind='value: prop2, valueUpdate: "afterkeydown"' />
<button type="submit">Add</button>
<p>Your items:</p>
<div data-bind="foreach: items">
<span data-bind="text: prop1"></span> &nbsp - &nbsp
<span data-bind="text: prop2"></span>
<br />
</div>
</form>
JS:
var SimpleListModel = function() {
this.items = ko.observableArray();
this.prop1 = ko.observable("");
this.prop2 = ko.observable("");
this.addItem = function() {
this.items.push({prop1:this.prop1, prop2: this.prop2});
}.bind(this); // Ensure that "this" is always this view model
};
ko.applyBindings(new SimpleListModel());
http://jsfiddle.net/NjSBg/2/
I suppose it's also possible you forgot to apply the bindings...
Edit
I appologize for posting the wrong fiddle, right one up now.
self.addClient = function () {
var client = {
UserId: self.userId(),
Name: self.name(),
Logo: self.logo()
}
lucidServer.addClient(ko.toJSON(client));
self.clients.push(client);
}.bind(self);
You add the parenthesis to get the current static value of the observable

Categories

Resources