Reputation: 969
What i want to do is create a search that would find items that match search criteria. For example if i want to search for a book called "a song of ice and fire"... a "a song" needs to be enough to find a filter results. Search critieria is search by book title
I'm getting my data from sql server database, via mvc controller and on client-site with this method, which actually works fine and displays data in table (as i'm only testing if i get this data).
PS: The important thing here is that final result is, that i am not displaying all the books in any grid/table and doing search/filtering on that table, it all should be inbeded in search and only found by search method.
update1
self.search = function (value) {
self.AllBooks.removeAll();
for (var x in self.AllBooks) {
if (self.AllBooks[x].toLowerCase().indexOf(value.toLowerCase()) >= 0) {
self.AllBooks.push(self.AllBooks[x]);
}
}
}
Upvotes: 0
Views: 1370
Reputation: 23372
Since you're using knockout, I'll show you a more "reactive", typical knockout way of tackling this feature.
Your viewmodel should have one, private data source property. In your case, a list of all books that never changes. You should not clear this list.
Then, in another property, you store a computed
list of data (books). This list takes a filter
function and applies it to the full data source.
The filter method relies on your search query (value
, in your search method). Let's make it observable
so we know when to trigger updates!
Here's an example with minimal changes to your original code putting it all together:
function Book(data) {
var self = this;
for (var key in data) {
this[key] = ko.observable(data[key] == null ? "" : data[key]);
}
}
function BookViewModel(data) {
var self = this;
self.title = ko.observable();
self.searchQuery = ko.observable("");
// The data source
self.ListOfBooks = ko.observableArray([]);
for (var i = 0; i < data.length; i++) {
self.ListOfBooks.push(new BookList(data[i]));
};
// The filtered list
self.searchResults = ko.pureComputed(function() {
var query = self.searchQuery().trim().toLowerCase();
if (!query) return [];
return self.ListOfBooks().filter(function(list) {
// The actual search method, easily moved or replaced
// by some other logic
var book = list.Book();
return Object.keys(book).some(function(k) {
return ko.unwrap(book[k]).toLowerCase().includes(query);
})
});
});
}
function BookList(data) {
var self = this;
for (var key in data) {
if (key != "Book")
this[key] = ko.observable(data[key] == null ? "" : data[key]);
}
self.Book = ko.observable(data.Book)
}
var _allBooks = [
{ Book: { title: "To Kill a Mockingbird", author: "Harper Lee" }},
{ Book: { title: "Animal Farm", author: "George Orwell" }},
{ Book: { title: "The Lord of the Rings", author: "J.R.R. Tolkien" }}
];
ko.applyBindings(new BookViewModel(_allBooks == null ? new Array() : _allBooks));
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<input type="search" data-bind="textInput: searchQuery">
<ul data-bind="foreach: searchResults">
<li data-bind="with: Book">
<span data-bind="text: title"></span>, by <span data-bind="text: author"></span>
</li>
</ul>
<div data-bind="visible: !searchQuery()" style="opacity: .5">
<p>(Try searching for these books:)</p>
<ul data-bind="foreach: ListOfBooks">
<li data-bind="with: Book">
<span data-bind="text: title"></span>, by <span data-bind="text: author"></span>
</li>
</ul>
A second example, in which I refactored some other parts as well, move some stuff around and use some more modern js features:
function Book(data) {
for (var key in data) {
this[key] = ko.observable(data[key] == null ? "" : data[key]);
}
this.searchString = ko.pureComputed(() =>
Object
.keys(data)
.map(k => this[k])
.map(ko.unwrap)
.join("||")
.toLowerCase()
);
};
Book.fromData = data => new Book(data);
Book.matchesQuery = query => book => book.matchesQuery(query);
Book.prototype.matchesQuery = function(query) {
return this.searchString().includes(query.trim().toLowerCase());
};
function BookViewModel(data) {
this.searchQuery = ko.observable("");
// The data source
this.allBooks = ko.observableArray(data.map(Book.fromData));
// The filtered list
this.searchResults = ko.pureComputed(() => {
return this.searchQuery()
? this.allBooks().filter(Book.matchesQuery(this.searchQuery()))
: [];
});
}
var _allBooks = [
{ title: "To Kill a Mockingbird", author: "Harper Lee" },
{ title: "Animal Farm", author: "George Orwell" },
{ title: "The Lord of the Rings", author: "J.R.R. Tolkien" }
];
ko.applyBindings(new BookViewModel(_allBooks));
* { box-sizing: border-box; position: relative;}
.searchwidget > input {
width: 50%;
padding: .5rem;
border-radius: 0;
outline: none;
border: 1px solid #ccc;
-webkit-appearance: none;
}
.searchwidget ul {
margin: 0;
padding: 0;
position: absolute;
width: 50%;
}
.searchwidget li {
margin: 0;
padding: .5em;
list-style: none;
border: 1px solid #ccc;
border-top-width: 0;
background: #efefef;
opacity: .95;
cursor: pointer;
}
.searchwidget li:nth-child(even) {
background: #dfdfdf;
}
.searchwidget > li:hover {
background: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div class="searchwidget">
<input type="search" data-bind="textInput: searchQuery">
<ul data-bind="foreach: searchResults">
<li>
<span data-bind="text: title"></span>, by <span data-bind="text: author"></span>
</li>
</ul>
</div>
<div data-bind="visible: !searchQuery()" style="opacity: .5">
<p>(Try searching for these books:)</p>
<ul data-bind="foreach: allBooks">
<li>
<span data-bind="text: title"></span>, by <span data-bind="text: author"></span>
</li>
</ul>
Upvotes: 1
Reputation: 6809
Using Underscore JS it is more clean and efficient,
var _=require("underscore");
var ListOfBooks = ["JavaBook", "C#Book", "JavaScriptBook", "HTMLBook"];
var searchBook="Java";
var filteredBooks = _.filter(ListOfBooks, function(books) {
return books.toLowerCase().indexOf(searchBook.toLowerCase() )>= 0;
});
console.log("filteredBooks " +JSON.stringify(filteredBooks));
Upvotes: -1
Reputation: 41
In the search function you are iterating over "ListOfBooks"array and adding the element matching to search criteria to same array ListOfBooks. This duplicates the elements which matches the criteria.
var ListOfBooks = ["JavaBook", "C#Book", "JavaScriptBook", "HTMLBook"];
var value = "Java";
for (var x in ListOfBooks) {
if (ListOfBooks[x].toLowerCase().indexOf(value.toLowerCase()) >= 0) {
ListOfBooks.push(ListOfBooks[x]);
}
}
console.log(ListOfBooks);
If you want to get results in separate array you can do:
var ListOfBooks = ["JavaBook", "C#Book", "JavaScriptBook", "HTMLBook"];
var value = "Java";
var SearchResults = [];
for (var x in ListOfBooks) {
if (ListOfBooks[x].toLowerCase().indexOf(value.toLowerCase()) >= 0) {
SearchResults.push(ListOfBooks[x]);
}
}
console.log(SearchResults);
Or if you want to retain all the elements matching the criteria you use splice method:
var ListOfBooks = ["JavaBook", "C#Book", "JavaScriptBook", "HTMLBook"];
var value = "Java";
for (var x in ListOfBooks) {
if (ListOfBooks[x].toLowerCase().indexOf(value.toLowerCase()) < 0) {
ListOfBooks.splice(x, 1);
}
}
console.log(ListOfBooks);
Upvotes: 1