Isotope: Combined multiple checkbox and searchbox filtering - checkbox

I'm trying to combine the Isotope multiple checkbox filtering with a searchbox.
I used the example with the checkbox filters from here and tried to implement the searchbox but with no luck.
Just the checkbox filtering works well. I think i'm close to the solution but my javascript skills are at a very beginner level.
I commented out the section of what i've tried to implement.
Thank you for some hints
// quick search regex
var qsRegex;
var $grid;
var filters = {};
var $grid = $('.grid');
//set initial options
$grid.isotope({
layoutMode: 'fitRows'
});
$(function() {
$grid = $('#grid');
$grid.isotope();
// do stuff when checkbox change
$('#options').on('change', function(jQEvent) {
var $checkbox = $(jQEvent.target);
manageCheckbox($checkbox);
var comboFilter = getComboFilter(filters);
/*var searchResult = qsRegex ? $(this).text().match(qsRegex) : true;
var filterResult = function() {
return comboFilter && searchResult;
}*/
$grid.isotope({
filter: comboFilter //or filterResult
});
});
});
function getComboFilter(filters) {
var i = 0;
var comboFilters = [];
var message = [];
for (var prop in filters) {
message.push(filters[prop].join(' '));
var filterGroup = filters[prop];
// skip to next filter group if it doesn't have any values
if (!filterGroup.length) {
continue;
}
if (i === 0) {
// copy to new array
comboFilters = filterGroup.slice(0);
} else {
var filterSelectors = [];
// copy to fresh array
var groupCombo = comboFilters.slice(0); // [ A, B ]
// merge filter Groups
for (var k = 0, len3 = filterGroup.length; k < len3; k++) {
for (var j = 0, len2 = groupCombo.length; j < len2; j++) {
filterSelectors.push(groupCombo[j] + filterGroup[k]); // [ 1, 2 ]
}
}
// apply filter selectors to combo filters for next group
comboFilters = filterSelectors;
}
i++;
}
var comboFilter = comboFilters.join(', ');
return comboFilter;
}
// use value of search field to filter
var $quicksearch = $('.quicksearch').keyup(debounce(function() {
qsRegex = new RegExp($quicksearch.val(), 'gi');
$grid.isotope();
}, ));
// debounce so filtering doesn't happen every millisecond
function debounce(fn, threshold) {
var timeout;
threshold = threshold || 100;
return function debounced() {
clearTimeout(timeout);
var args = arguments;
var _this = this;
function delayed() {
fn.apply(_this, args);
}
timeout = setTimeout(delayed, threshold);
}
}
function manageCheckbox($checkbox) {
var checkbox = $checkbox[0];
var group = $checkbox.parents('.option-set').attr('data-group');
// create array for filter group, if not there yet
var filterGroup = filters[group];
if (!filterGroup) {
filterGroup = filters[group] = [];
}
var isAll = $checkbox.hasClass('all');
// reset filter group if the all box was checked
if (isAll) {
delete filters[group];
if (!checkbox.checked) {
checkbox.checked = 'checked';
}
}
// index of
var index = $.inArray(checkbox.value, filterGroup);
if (checkbox.checked) {
var selector = isAll ? 'input' : 'input.all';
$checkbox.siblings(selector).prop('checked', false);
if (!isAll && index === -1) {
// add filter to group
filters[group].push(checkbox.value);
}
} else if (!isAll) {
// remove filter from group
filters[group].splice(index, 1);
// if unchecked the last box, check the all
if (!$checkbox.siblings('[checked]').length) {
$checkbox.parents('.option-set').find(selector).prop('checked', false);
}
}

I found the solution by myself, but i had to add a second function for returning the searchresult. Otherwise the search function is triggered only after using a checkbox or leaving the search box input field.
How could i avoid this redundand code?
JS:
// use value of search field to filter
var $quicksearch = $('.quicksearch').keyup(debounce(function() {
qsRegex = new RegExp($quicksearch.val(), 'gi');
$grid.isotope();
}, 200));
$(function() {
$grid = $('#grid');
$grid.isotope({
filter: function() {
var searchResult = qsRegex ? $(this).text().match(qsRegex) : true;
return searchResult;
}
});
// do stuff when checkbox change
$('#options').on('change', function(jQEvent) {
var $checkbox = $(jQEvent.target);
manageCheckbox($checkbox);
var comboFilter = getComboFilter(filters);
$grid.isotope({
filter: function() {
var buttonResult = comboFilter ? $(this).is(comboFilter) : true;
var searchResult = qsRegex ? $(this).text().match(qsRegex) : true;
return buttonResult && searchResult;
}
});
});
});

Related

Display more than 100 markers in angularjs google maps with rotation

I have been using ngMap with my angularjs code for displaying markers. However, with more than 100 markers I have noticed that there is a considerable decrease in performance mainly related to ng-repeat and two way binding. I would like to add markers with custom HTML elements similar to CustomMarker but using ordinary Markers and modified from controller when required.
Challenges faced :
I have SVG images which need to be dynamically coloured based on the conditions (These SVGs are not single path ones, hence doesn't seem to work well when I used it with Symbol)
These are vehicle markers and hence should support rotation
I have solved this by creating CustomMarker with Overlay and then adding the markers that are only present in the current map bounds to the map so that map doesn't lag.
Below is the code snippet with which I achieved it.
createCustomMarkerComponent();
/**
* options : [Object] : options to be passed on
* - position : [Object] : Google maps latLng object
* - map : [Object] : Google maps instance
* - markerId : [String] : Marker id
* - innerHTML : [String] : innerHTML string for the marker
**/
function CustomMarker(options) {
var self = this;
self.options = options || {};
self.el = document.createElement('div');
self.el.style.display = 'block';
self.el.style.visibility = 'hidden';
self.visible = true;
self.display = false;
for (var key in options) {
self[key] = options[key];
}
self.setContent();
google.maps.event.addListener(self.options.map, "idle", function (event) {
//This is the current user-viewable region of the map
var bounds = self.options.map.getBounds();
checkElementVisibility(self, bounds);
});
if (this.options.onClick) {
google.maps.event.addDomListener(this.el, "click", this.options.onClick);
}
}
function checkElementVisibility(item, bounds) {
//checks if marker is within viewport and displays the marker accordingly - triggered by google.maps.event "idle" on the map Object
if (bounds.contains(item.position)) {
//If the item isn't already being displayed
if (item.display != true) {
item.display = true;
item.setMap(item.options.map);
}
} else {
item.display = false;
item.setMap(null);
}
}
var supportedTransform = (function getSupportedTransform() {
var prefixes = 'transform WebkitTransform MozTransform OTransform msTransform'.split(' ');
var div = document.createElement('div');
for (var i = 0; i < prefixes.length; i++) {
if (div && div.style[prefixes[i]] !== undefined) {
return prefixes[i];
}
}
return false;
})();
function createCustomMarkerComponent() {
if (window.google) {
CustomMarker.prototype = new google.maps.OverlayView();
CustomMarker.prototype.setContent = function () {
this.el.innerHTML = this.innerHTML;
this.el.style.position = 'absolute';
this.el.style.cursor = 'pointer';
this.el.style.top = 0;
this.el.style.left = 0;
};
CustomMarker.prototype.getPosition = function () {
return this.position;
};
CustomMarker.prototype.getDraggable = function () {
return this.draggable;
};
CustomMarker.prototype.setDraggable = function (draggable) {
this.draggable = draggable;
};
CustomMarker.prototype.setPosition = function (position) {
var self = this;
return new Promise(function () {
position && (self.position = position); /* jshint ignore:line */
if (self.getProjection() && typeof self.position.lng == 'function') {
var setPosition = function () {
if (!self.getProjection()) {
return;
}
var posPixel = self.getProjection().fromLatLngToDivPixel(self.position);
var x = Math.round(posPixel.x - (self.el.offsetWidth / 2));
var y = Math.round(posPixel.y - self.el.offsetHeight + 10); // 10px for anchor; 18px for label if not position-absolute
if (supportedTransform) {
self.el.style[supportedTransform] = "translate(" + x + "px, " + y + "px)";
} else {
self.el.style.left = x + "px";
self.el.style.top = y + "px";
}
self.el.style.visibility = "visible";
};
if (self.el.offsetWidth && self.el.offsetHeight) {
setPosition();
} else {
//delayed left/top calculation when width/height are not set instantly
setTimeout(setPosition, 300);
}
}
});
};
CustomMarker.prototype.setZIndex = function (zIndex) {
if (zIndex === undefined) return;
(this.zIndex !== zIndex) && (this.zIndex = zIndex); /* jshint ignore:line */
(this.el.style.zIndex !== this.zIndex) && (this.el.style.zIndex = this.zIndex);
};
CustomMarker.prototype.getVisible = function () {
return this.visible;
};
CustomMarker.prototype.setVisible = function (visible) {
if (this.el.style.display === 'none' && visible) {
this.el.style.display = 'block';
} else if (this.el.style.display !== 'none' && !visible) {
this.el.style.display = 'none';
}
this.visible = visible;
};
CustomMarker.prototype.addClass = function (className) {
var classNames = this.el.className.trim().split(' ');
(classNames.indexOf(className) == -1) && classNames.push(className); /* jshint ignore:line */
this.el.className = classNames.join(' ');
};
CustomMarker.prototype.removeClass = function (className) {
var classNames = this.el.className.split(' ');
var index = classNames.indexOf(className);
(index > -1) && classNames.splice(index, 1); /* jshint ignore:line */
this.el.className = classNames.join(' ');
};
CustomMarker.prototype.onAdd = function () {
this.getPanes().overlayMouseTarget.appendChild(this.el);
// this.getPanes().markerLayer.appendChild(label-div); // ??
};
CustomMarker.prototype.draw = function () {
this.setPosition();
this.setZIndex(this.zIndex);
this.setVisible(this.visible);
};
CustomMarker.prototype.onRemove = function () {
this.el.parentNode.removeChild(this.el);
// this.el = null;
};
} else {
setTimeout(createCustomMarkerComponent, 200);
}
}
The checkElementVisibility function helps in identifying whether a marker should appear or not.
In case there are better solutions please add it here.Thanks!

How to make a filter attached to $scope of a controller (angular)?

I wrote a litlle program in angular using ui-select. And I wrote a filter that do an OR search in different fields.
Here is my original filter : (whic works perfectly)
app.filter('orSearchFilter', function($parse) {
return function(items, props) {
var out = [];
if (angular.isArray(items)) {
var keys = Object.keys(props);
items.forEach(function(item) {
var itemMatches = false;
for (var i = 0; i < keys.length; i++) {
var prop = $parse(keys[i])(item);
var text = props[keys[i]].toLowerCase();
if (prop && prop.toString().toLowerCase().indexOf(text) !== -1) {
itemMatches = true;
break;
}
}
if (itemMatches) {
out.push(item);
}
});
} else {
out = items;
}
return out;
};
});
And here is my original plunker (which works) : http://plnkr.co/edit/IdqO5dtLXmC6gtqLxRdP?p=preview
The problem is that my filter won't be generic and I will use it in my final code just inside its controller. So, I want to attach it.
Here is the new version of the filter which is attached to the controller : (I didn't do any change...)
$scope.orSearchFilter = function($parse) {
return function(items, props) {
var out = [];
if (angular.isArray(items)) {
var keys = Object.keys(props);
items.forEach(function(item) {
var itemMatches = false;
for (var i = 0; i < keys.length; i++) {
var prop = $parse(keys[i])(item);
var text = props[keys[i]].toLowerCase();
if (prop && prop.toString().toLowerCase().indexOf(text) !== -1) {
itemMatches = true;
break;
}
}
if (itemMatches) {
out.push(item);
}
});
} else {
out = items;
}
return out;
};
};
Finally, in my html, I called this new filter by using this line :
<ui-select-choices group-by="groupByLetter"
repeat="contract in (contracts |
filter : orSearchFilter(contracts, {id.id: $select.search, policy.info.name : $select.search } ) |
orderBy: 'name') track by contract.name">
{{contract.name}} - {{contract.value}} ---- {{contract.id.id}} *** {{contract.policy.info.name }}
</ui-select-choices>
Can you help me please to fix that problem and help me to attach this filter to the scope of the controller?
Thank you !
Use the $filter service to programmatically fetch your filter function.
//Don't forget to inject $filter in your controller ofcourse
$scope.orSearchFilter = $filter('orSearchFilter');
Attach the filter directly to scope:
/* REMOVE constructor function
$scope.orSearchFilter = function($parse) {
return function(items, props) {
*/
// INSTEAD
$scope.orSearchFilter = function(items, props) {
var out = [];
//...
return out;
};
//};
Of course, also be sure that $parse is added to the injectables of the controller construction function.

Infdig in Custom groupBy

i recently needed some kinda customized repeater, which group data by their key, but not simply group them all together, so i read several references and things,... and at last i come up to copy
groupBy from this article which he seem to complete it at best, ...
http://sobrepere.com/blog/2014/10/14/creating-groupby-filter-angularjs/
And then i customized so it become like this:
the things my group by do... is:
Group Data Together Until It Reach Differences.
but the matter is that though it work, it still generate infdig, i know it's because the digest done call other one, but what i don't know is how to solve it in very easy and understandable manner, as i'm new to JavaScript...
app.filter('groupBy', function () {
var results = {};
return function (data, key) { //Data => My Objects Array - Key => Name Of Filtered Property
if (!(data && key)) return;
var result;
if (!this.$id) {
result = {};
} else {
var scopeId = this.$id;
if (!results[scopeId]) {
results[scopeId] = {};
this.$on("$destroy", function () {
delete results[scopeId];
});
}
result = results[scopeId];
}
for (var groupKey in result)
result[groupKey].splice(0, result[groupKey].length);
var grpKey = -1; //GroupKey
var lastUserId;
for (var i = 0; i < data.length; i++) {
if (!result[grpKey] || lastUserId && lastUserId != data[i][key]) // Ex.: result[data[0]["UserId"]]{ => return UserId
result[++ grpKey] = [];
result[grpKey].push(data[i]);
lastUserId = data[i][key];
}
var keys = Object.keys(result);
for (var k = 0; k < keys.length; k++) {
if (result[keys[k]].length === 0)
delete result[keys[k]];
}
return result;
};
});
In this url is working perfectly...
http://plnkr.co/edit/8jB4wSRtKfVmEsTGZtfV?p=preview
app.filter('groupBy', function ($timeout) {
return function (data, key) {
if (!key) return data;
var outputPropertyName = '__groupBy__' + key;
if(!data[outputPropertyName]){
var result = {};
for (var i=0;i<data.length;i++) {
if (!result[data[i][key]])
result[data[i][key]]=[];
result[data[i][key]].push(data[i]);
}
Object.defineProperty(result, 'length', {enumerable: false, value: Object.keys(result).length});
Object.defineProperty(data, outputPropertyName, {enumerable:false, configurable:true, writable: false, value:result});
$timeout(function(){delete data[outputPropertyName];},0,false);
}
return data[outputPropertyName];
};
});

getting the inner html of a contenteditable div in angularjs

I am trying to get innerHTML of a contenteditable div via function defined in controller of angularjs but it returns undefined every time.. what are the alternatives or how can I handle this issue?
$scope.genrate_HTML=function()
{
var read_string=document.getElementsByClassName("MainPage");
//console.log(read_string);
var p_tag= '\n<p id="test"> \n'+read_string.innerHTML+'\n </p>';
//document.getElementById("createdHTML").value = p_tag ;
//$compile( document.getElementById('createdHTML') )($scope);
}
the contenteditble div's classs name is "MainPage"
VisualEditor.controller("GenrateHTML",function($scope){
$scope.savefile=function()
{
$scope.genratedHTML_text=document.getElementById("createdHTML").value;
var text_file_blob= new Blob([$scope.genratedHTML_text],{type:'text/html'});
$scope.file_name_to_save=document.getElementById("file_name").value ;
var downloadLink=document.createElement("a");
downloadLink.download=$scope.file_name_to_save;
downloadLink.innerHTML="Download File";
if(window.URL!=null)
{
downloadLink.href=window.URL.createObjectURL(text_file_blob);
}
else
{
downloadLink.href = window.URL.createObjectURL(text_file_blob);
downloadLink.onclick = destroyClickedElement;
downloadLink.style.display = "none";
document.body.appendChild(downloadLink);
}
downloadLink.click();
}
function destroyClickedElement(event)
{
document.body.removeChild(event.target);
}
$scope.toggleModal = function(){
$scope.showModal = !$scope.showModal;
};
///add details
$scope.details=[];
$scope.addDetails=function(){
$scope.details.push({
Title:$scope.Details_Title,
meta_chars:$scope.Details_metaChars,
version:$scope.Details_version,
Auth_name:$scope.Details_AuthName,
copyRights:$scope.Details_copyRights
});
document.getElementById("createdHTML").innerHTML = $scope.details;
};
$scope.$watch('details', function (value) {
console.log(value);
}, true);
/////////////////////
$scope.genrate_HTML=function()
{
var read_string=document.getElementsByClassName("MainPage");
//console.log(read_string);
var p_tag = '';
for (var i = 0; i < read_string.length; i++) {
p_tag += '\n<p id="test_"' + i + '> \n' + read_string[i].innerHTML + '\n </p>';
document.getElementById("createdHTML").value = p_tag;
}
//$compile( document.getElementById('createdHTML') )($scope);
}
});
getElementsByClassName returns an Array, so, your read_string variable is an Array type. you should iterate through the elements of read_string with for loop.
NOTE: Please check the p element's id here aswell. Because id must be unique!
$scope.genrate_HTML = function() {
var read_string = document.getElementsByClassName("MainPage");
var p_tag = '';
for (var i = 0; i < read_string.length; i++) {
p_tag += '\n<p id="test_"'+i+'> \n'+read_string[i].innerHTML+'\n </p>';
}
/* Other code here... */
}
UPDATE: Don't use the code below! If read_string returns with no elements than your code will crash!
But if it's a 1 element Array then you can take the value like:
$scope.genrate_HTML = function() {
var read_string = document.getElementsByClassName("MainPage");
var p_tag= '\n<p id="test"> \n'+read_string[0].innerHTML+'\n </p>';
/* Other code here... */
}
I hope that helps. If it doesn't then paste the full code of the Controller.

checkbox filter for json array in Angularjs

I have create a filter but this filter is not working with array inside array.
'http://plnkr.co/edit/oygy79j3xyoGJmiPHm4g?p=info'
Above plkr link is working demo.
app.filter('checkboxFilter', function($parse) {
var cache = { //create an cache in the closure
result: [],
checkboxData: {}
};
function prepareGroups(checkboxData) {
var groupedSelections = {};
Object.keys(checkboxData).forEach(function(prop) {
//console.log(prop);
if (!checkboxData[prop]) {
return;
} //no need to create a function
var ar = prop.split('=');
//console.log("ar is - "+ar);
if (ar[1] === 'true') {
ar[1] = true;
} //catch booleans
if (ar[1] === 'false') {
ar[1] = false;
} //catch booleans
/* replacing 0 with true for show all offers */
if(ar[0]=='SplOfferAvailable.text'){
ar[1]='true';
}else{
}
//make sure the selection is there!
groupedSelections[ar[0]] = groupedSelections[ar[0]] || [];
//at the value to the group.
groupedSelections[ar[0]].push(ar[1]);
});
return groupedSelections;
}
function prepareChecks(checkboxData) {
var groupedSelections = prepareGroups(checkboxData);
var checks = [];
//console.log(groupedSelections);
Object.keys(groupedSelections).forEach(function(group) {
//console.log("groupedSelections- "+groupedSelections);
//console.log("group- "+group);
var needToInclude = function(item) {
//console.log("item- "+item);
// use the angular parser to get the data for the comparson out.
var itemValue = $parse(group)(item);
var valueArr = groupedSelections[group];
//console.log("valueArr- "+valueArr);
function checkValue(value) { //helper function
return value == itemValue;
}
//check if one of the values is included.
return valueArr.some(checkValue);
};
checks.push(needToInclude); //store the function for later use
});
return checks;
}
return function(input, checkboxData, purgeCache) {
if (!purgeCache) { //can I return a previous 'run'?
// is the request the same as before, and is there an result already?
if (angular.equals(checkboxData, cache.checkboxData) && cache.result.length) {
return cache.result; //Done!
}
}
cache.checkboxData = angular.copy(checkboxData);
var result = []; // this holds the results
//prepare the checking functions just once.
var checks = prepareChecks(checkboxData);
input.every(function(item) {
if (checks.every(function(check) {
return check(item);
})) {
result.push(item);
}
return result.length < 10000000; //max out at 100 results!
});
cache.result = result; //store in chache
return result;
};
});
above code is for check box filter.
when i click on checkbox called "Availability" it does not filter the result.
Please help me out.
Thanks.
I think that the way you are navigating through json is wrong because if you put in this way it works
"Location": "Riyadh",
"AvlStatus": "AVAILABLE"
"Rooms": {.....
You have to go in some way through Rooms and right now I think you're not doing that

Resources