Filtering function: use KO struggling - javascript

Try to convert this code:
JS:
self.filter = function() {
var s = $('#searchField').val();
console.log(s.toLowerCase().replace(/\b[a-z]/g,"KC"));
s = s.toLowerCase().replace(/\b[a-z]/g, function(self) {
console.log(self.toUpperCase());
return self.toUpperCase();
});
$(".locationList > li").each(function() {
console.log(this);
$(this).text().search(s) > -1 ? $(this).show() : $(this).hide();
});
for(var i = 0; i < self.placeList().length; i++) {
console.log(self.map);
self.placeList()[i].marker.setMap(self.placeList()[i].marker.title.search(s) > -1 ? map : null);
}
};
};
HTML:
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
<input id="searchField" data-bind="event: {keyup: filter}" type="text" placeholder='search by name or city' value="">
<hr>
<ul class="locationList" data-bind="foreach: placeList">
<li>
...
to something like this:
Javascript
self.filterText = ko.observable("");
self.filteredList = ko.computed(function(){
var filter = self.filterText().toLowerCase();
return // your filter function. make sure you return an array of what you want!
}, this);

You should use the visible-binding on the li-tag in your repeater. Then it is straight forward to hide items not meeting the criteria. Something like this:
<ul data-bind="foreach:placeList">
<li data-bind="text:$data, visible: filter"></li>
</ul>

Related

Filter "Multi-Categories" with jQuery-EasyFilter

I try to modify jQuery EasyFilter: currently, each item has one unique category passed by data-easyitem of the item. I'd like to assign multiple categories for one item. For demonstration please take a look at this pen.
Item 4 is assigned to Category 1 and Category 4: data-easyitem="cat1,cat4".
So it should show up when I activate Category 1 and also when I click Category 4.
I think I need to .split the data-easyitem passed by the item and compare it to the category value.
This might be the right place to do it?
$(this.wrap)
.find("[data-easyitem]")
.each(function() {
var item = $(this);
// Compare if the ['data-easyfilter'] is diferent from *
if (value !== "*") {
if (item.attr("data-easyitem") == value) {
showItems.push(item[0]);
} else {
hiddeItems.push(item[0]);
}
} else {
showItems.push(item[0]);
}
});
Unfortunately, I really don't know how. I would be thankful for any tips and hints.
You first need to get the attr("data-easyitem") value and split them .Then use for-loop to loop through values got after splitting and finally compare these values with the category selected depending on this show or hide items.
Demo Code :
(function($, window, document, undefined) {
"use strict";
// Default options
var defaults = {
firstFilter: "*",
animation: "slide",
duration: 400
};
// The plugin constructor
function EasyFilter(element, options) {
// Merge user settings with default, recursively
this.options = $.extend(true, {}, defaults, options);
// Wrap container element
this.wrap = $(element);
// Call initial method
this.init();
}
$.extend(EasyFilter.prototype, {
init: function() {
var object = this;
this._addEvents();
this.filter(object.options.firstFilter);
},
filter: function(value) {
console.clear()
var object = this;
var showItems = [];
var hiddeItems = [];
$(this.wrap)
.find("[data-easyitem]")
.each(function() {
var item = $(this);
if (value !== "*") {
//get attr value and split between `,`
var items_ = item.attr("data-easyitem").split(",");
//loop through the splits values
for (var i = 0; i < items_.length; i++) {
//check if the value i.e : `(data-easyfilter)` is == to split value
if (items_[i] == value) {
showItems.push(item[0]); //show them
} else {
hiddeItems.push(item[0]); //hide
}
}
} else {
showItems.push(item[0]);
}
});
object._toggleItems(hiddeItems, showItems);
},
_slideItemsEffect: function(value, showItems) {
var object = this;
$(value).slideUp(object.options.duration, function() {
setTimeout(function() {
$.each(showItems, function(index, value) {
$(value).slideDown(object.options.duration, function() {});
});
}, 300);
});
},
_fadeItemsEffect: function(value, showItems) {
var object = this;
$(value).fadeOut(object.options.duration, function() {
setTimeout(function() {
$.each(showItems, function(index, value) {
$(value).fadeIn(object.options.duration, function() {});
});
}, 300);
});
},
_toggleItems: function(hiddeItems, showItems) {
var object = this;
// Compare if there is more than one item to hide
if (hiddeItems.length > 0) {
// Hide and show item from arrays
$.each(hiddeItems, function(index, value) {
switch (object.options.animation) {
case "slide":
object._slideItemsEffect(value, showItems);
break;
case "fade":
object._fadeItemsEffect(value, showItems);
break;
default:
object._slideItemsEffect(value, showItems);
}
});
} else {
//Show all items
$.each(showItems, function(index, value) {
switch (object.options.animation) {
case "slide":
$(value).slideDown(object.options.duration, function() {});
break;
case "fade":
$(value).fadeIn(object.options.duration, function() {});
break;
default:
$(value).slideDown(object.options.duration, function() {});
}
});
}
},
_addEvents: function() {
var object = this;
// Click
$(this.wrap)
.find("[data-easyfilter]")
.click(function() {
object.filter($(this).attr("data-easyfilter"));
});
}
});
// Easy Filter Wraper
$.fn.easyFilter = function(options) {
this.each(function() {
new EasyFilter(this, options);
});
};
})(jQuery, window, document);
$('#easy-filter-wrap').easyFilter();
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.3/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootswatch/4.5.2/materia/bootstrap.min.css">
<div id="jquery-script-menu">
<div class="container">
<div id="easy-filter-wrap">
<div class="btn-group btn-group-toggle mb-3" data-toggle="buttons">
<label class="btn btn-primary active" data-easyfilter="*">
<input type="radio" name="options" id="option0" checked>
All
</label>
<label class="btn btn-primary" data-easyfilter="cat1">
<input type="radio" name="options" id="option1">
Category 01
</label>
<label class="btn btn-primary" data-easyfilter="cat2">
<input type="radio" name="options" id="option2">
Category 02
</label>
<label class="btn btn-primary" data-easyfilter="cat3">
<input type="radio" name="options" id="option3">
Category 03
</label>
<label class="btn btn-primary" data-easyfilter="cat4">
<input type="radio" name="options" id="option4">
Category 04
</label>
</div>
<br /><br />
<!--put both value here as well-->
<div data-easyitem="cat1,cat4" class="alert alert-danger">
Item 01 (Category 1)
</div>
<div data-easyitem="cat2" class="alert alert-danger">
Item 02 (Category 2)
</div>
<div data-easyitem="cat3" class="alert alert-danger">
Item 03 (Category 3)
</div>
<div data-easyitem="cat1,cat4" class="alert alert-danger">
Item 04 (Category 4)
</div>
</div>
</div>
</div>

How to get an element that has a jquery .data('index') applied to it?

I am setting an element's data attribute onclick of it to the current index, of the element... for example, take the following html:
<ul>
<li class="get-me">
Click Me <input type="text" value="" name="name1" />
</li>
<li class="get-me">
Click Me <input type="text" value="" name="name2" />
</li>
<li class="not-me">
<input type="text" value="A value" name="aName" readonly />
</li>
<li class="get-me">
Click Me <input type="text" value="" name="name3" />
</li>
</ul>
Now, onclick, of the a.alink links I add data('index') to each input sibling... example:
jQuery(document).ready(function($) {
$('.alink').on('click', function(event) {
event.preventDefault();
var $this = $(this),
$input = $this.next('input');
if ($input.length)
{
$('.alink').each(function(i, obj) {
if (obj == $this.eq(0))
{
$(obj).next('input').data('index', i);
return false;
}
});
}
});
});
Now the order of these li's can change, after the links have been clicked on, but the jQuery.data stays the same, so wondering if it's possible to get input element that has .data('index') == 1 somehow, as a jQuery object element (like $(this))? I know that it's possible if the data attribute existed, but this is not the case as it's being set in code instead. And I'm wondering if this can be selected directly instead of looping through all input elements and checking their .data('index') properties.
EDIT
So I have split this into a function here:
function inputFilter(selector, index)
{
var obj = null;
if (selector.trim() !== '')
{
obj = $(selector).filter(function() {
return $(this).data('index') == index;
});
}
return obj;
}
But trying to use it returns all input elements:
var $object = inputFilter('input', 1);
What am I doing wrong?
Use a .filter function that looks for the data you want.
$('input').filter(function() {
return $(this).data('index') == '1';
});
function inputFilter(selector, index) {
var obj = null;
if (selector.trim() !== '') {
obj = $(selector).filter(function() {
return $(this).data('index') == index;
});
}
return obj;
}
$("#input1").data("index", 1);
$("#input2").data("index", 3);
console.log(inputFilter('input', 1).map(function() {
return this.id;
}).get());
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="input1">
<input id="input2">
<input id="input3">

Uncheck first filter after click in second

I am doing a table, with two filters checkbox. First is "360" - when you click it in table shows only 360, the same works filter "2D" - when you click it in table shows only 2D. I do this in JS, but what i want to do is when you click "360", in table shows only 360, but when you click "2D" at this time "360" is uncheck. I try to do this by radio button, but then my css checkbox don't works.
JSON "datas":
[{"type":"2D"},{"type":"2D"},{"type":"360"},{"type":"2D"},{"type":"360"},{"type":"2D"},{"type":"360"},{"type":"2D"},{"type":"360"},{"type":"2D"}]
My Html:
<div class="form-group" style="display:block;width:40px;padding-left: 5px;float:left;height:70px;">
<input type="checkbox" id='checkbox-360' class='tags-checkbox sr-only' value="" ng-model="type2.type360"><label for='checkbox-360'>
<i class='glyphicon glyphicon-unchecked' style="color:darkgrey;width:2px;font-size:25px;"></i>
<i class='glyphicon glyphicon-check' style="color:cornflowerblue;width:2px;font-size:25px;"></i>
<h4><small>360</small></h4>
</label>
</div>
<div class="form-group" style="display:block;width:40px;padding-left:5px;float:left;height:70px;">
<input type="checkbox" id='checkbox-20' class='tags-checkbox sr-only' value="" ng-model="type1.type2D"><label for='checkbox-20'>
<i class='glyphicon glyphicon-unchecked' style="color:darkgrey;width:2px;font-size:25px;"></i>
<i class='glyphicon glyphicon-check' style="color:cornflowerblue;width:2px;font-size:25px;"></i>
<h4><small>2D</small></h4>
</label>
</div>
<tr ng-repeat="data in datas |TypeFilter360:type2.type360 | TypeFilter2D:type1.type2D>
<td>{{data.type}}</td>
</tr>
CSS :
input[type='checkbox'].tags-checkbox:checked + label > i:first-of-type,
input[type='checkbox'].tags-checkbox + label > i:last-of-type{
display: none;
}
input[type='checkbox'].tags-checkbox:checked + label > i:last-of-type{
display: inline-block;
}
JS
var app = angular.module('app', []);
app.service('service', function($http, $q){
this.getDatas = function () {
var datas = $http.get('datas.json', {cache: false});
return $q.all({datas});
};
});
app.controller('FirstCtrl', function($scope, service, $http) {
var promise = service.getDatas();
promise.then(function (data) {
$scope.datas = data.datas.data;
console.log($scope.datas);
})
})
.filter('TypeFilter2D', function(){
return function(data, type2D){
if (!type2D) return data;
var filtered2D = [];
angular.forEach(data, function(item){
if(type2D == false) {
filtered2D.push(item);
}
else if(type2D == true && item.type == "2D"){
filtered2D.push(item);
}
});
return filtered2D;
};
})
.filter('TypeFilter360', function(){
return function(data, type360){
if (!type360) return data;
var filtered360 = [];
angular.forEach(data, function(item){
if(type360 == false) {
filtered360.push(item);
}
else if(type360 == true && item.type == "360"){
filtered360.push(item);
}
});
return filtered360;
};
})
Well what i want to do - when "360" filter is clicked, and then i click "2D" filter "360" should uncheck.
Thanks for answers in advance!
That behavior can actually be defined in your HTML using:
<input type="radio" name="filterOptions" id="checkbox-360" ... />
...
<input type="radio" name="filterOptions" id="checkbox-20" ... />
https://www.w3schools.com/tags/tag_input.asp
https://docs.angularjs.org/api/ng/input/input%5Bradio%5D
https://www.w3schools.com/html/tryit.asp?filename=tryhtml_radio

How to list autocomplete suggestions as text instead of links

I got this code from a tutorial that I am following for Elasticsearch and AngularJS.
Trying to figure out how to have the autocomplete function form a list of sugggestions in a dropdown as user input is typed instead of displaying links as suggestions.
Here is the html markup:
<ul class="suggestions" ng-show="showAutocomplete">
<li ng-repeat="suggestion in autocomplete.suggestions" ng-show="suggestion.options.length > 0">
<small>Search for —</small> {{suggestion.options[0].text}}
<li ng-repeat="result in autocomplete.results">
{{result._source.title}}
</li>
Here is the js:
//Autocomplete
$scope.autocomplete = {
suggestions: []
};
$scope.showAutocomplete = false;
$scope.evaluateTerms = function(event) {
var inputTerms = $scope.searchTerms ? $scope.searchTerms.toLowerCase() : null;
if (event.keycode === 13) {
return;
}
if (inputTerms && inputTerms.length > 3) {
getSuggestions(inputTerms);
}
else if (!inputTerms) {
$scope.autocomplete = {};
$scope.showAutocomplete = false;
}
};
$scope.searchForSuggestion = function() {
$scope.searchTerms = $scope.autocomplete.suggestions[0].options[0].text;
$scope.search();
$scope.showAutocomplete = false;
};
var getSuggestions = function(query) {
searchService.getSuggestions(query).then(function(es_return) {
var suggestions = es_return.suggest.phraseSuggestion;
var results = es_return.hits.hits;
if (suggestions.length > 0) {
$scope.autocomplete.suggestions = suggestions;
}
else {
$scope.autocomplete.suggestions = [];
}
if (suggestions.length > 0) {
$scope.showAutocomplete = true;
}
else {
$scope.showAutocomplete = false;
}
});
};
The first list item in the html markup gives 1 suggestion (in the form of a link) and the second list item gives a list of links as suggestions. I like the list idea of multiple suggestions, but just want text phrases instead of links that the user can select. Any ideas?
Something like this should work, but I don't' have you object structure so I had to guess in a few places.
<select class="" style="font-size: 12px"
ng-options="suggestion.options[0].text as suggestion for suggestion in autocomplete.suggestions"
ng-change="searchForSuggestion()"
ng-show="suggestion.options.length > 0">
</select>

Dynamically accessing properties of knockoutjs observable array

I am using the below code for handling sort functionality. It is working for me. But is there any way to make the code as common and so i can use it whenever needed.
<span class="sorting" data-bind="click: function(){ ui.items.sort(function(a,b){ return a.Username < b.Username ? -1 : 1; }); }">User Name</span>
<span class="sorting" data-bind="click: function(){ ui.items.sort(function(a,b){ return a.Firstname < b.Firstname ? -1 : 1; }); }">
First Name</span>
<span class="sorting" data-bind="click: function(){ ui.items.sort(function(a,b){ return a.Lastname < b.Lastname ? -1 : 1; }); }">
Last Name</span>
scripts
ui = new ListUI(config);
ko.applyBindings(ui);
var ListUI = function ListUIF(config) {
this.items = ko.observableArray([]);
}
var item = function itemF(data) {
var self = this;
self.Username = ko.observable(data.Username);
self.Firstname = ko.observable(data.Firstname);
self.Lastname = ko.observable(data.Lastname);
}
The code above is working fine, but i didn't want the sorting code to be repeated.
<span class="sorting" data-bind="click: function(){ ui.items.sort(function(a,b){ return a.Lastname < b.Lastname ? -1 : 1; }); }">
Last Name</span>
Instead i want something like this
<span class="sorting" data-bind="click: sortFunction">
Last Name</span>
var sortFunction = function sortFunctionF(a, b){
return a.Username < b.Username : -1 : 1; //How to make this common for other property also like Firstname, Lastname etc.
}
There's basically two options: Have three separate functions sortByUsername, sortByFirstname and sortByLastname, or you could do a custom binding that takes in additional information and sets up the sort.
The second one could look something like this:
<span class="sorting" data-bind="sortFunction: 'Username'">User Name</span>
<span class="sorting" data-bind="sortFunction: 'Firstname'">
First Name</span>
<span class="sorting" data-bind="sortFunction: 'Lastname'}">
Last Name</span>
And then the custom binding for sortFunction:
ko.bindingHandlers.sortFunction = {
update: function(element, valueAccessor, allBindingsAccessor, viewModel) {
var sortBy = ko.utils.unwrapObservable(valueAccessor());
var items = viewModel.items;
$(element).unbind('click.sort').bind('click.sort', function() {
items.sort(function(a, b) {
return a[sortBy]() < b[sortBy]() ? -1 : 1;
});
});
}
}
Fiddle
Another option as mentioned by Joeseph would be to pass the property name into the click function, which would then return a function. I don't think this is as nice an option as the custom binding, but Here's a fiddle that does that:
<span class="sorting" data-bind="click: getSortFunction('Username')">User Name</span>
<span class="sorting" data-bind="click: getSortFunction('Firstname')">
First Name</span>
<span class="sorting" data-bind="click: getSortFunction('Lastname')}">
Last Name</span>
And then your viewmodel would change to add the function:
var ListUI = function ListUIF(items) {
var self = this;
self.items = ko.observableArray(items);
self.getSortFunction = function(prop) {
return function() {
self.items.sort(function(a, b) {
return a[prop]() < b[prop]() ? -1 : 1;
});
};
};
return self;
}

Categories