I'm in the process of creating a customized filter plugin for jQuery.
So far' I've managed to implement the following code:
(function ($) {
$.fn.filterbyDate = function (filterValue) {
var tableDate = new Date($(':eq(3)', $(val)).text());
var days = numDaysBetween(tableDate, new Date());
if (filterValue === "-1") {
$(val).show();
} else {
switch (filterValue) {
case "This Week":
if (days > 7) {
$(val).hide();
} else {
$(val).show();
}
break;
case "This Month":
if (days > 30) {
$(val).hide();
} else {
$(val).show();
}
break;
case "This Year":
if (days > 365) {
$(val).hide();
} else {
$(val).show();
}
break;
default:
}
}
};
$.fn.filterbyClient = function (filterValue) {
var $table = $(".tablefilter");
$.each($table.find("tbody>tr"), function (ind, val) {
var name = $(':eq(2)', $(val)).text();
if (filterValue === "-1") {
$(val).show();
} else {
if (name.trim() !== filterValue) {
$(val).hide();
} else {
$(val).show();
}
}
});
};
}(jQuery));
It's implemented by:
controller1.filterByDate(date);
controller2.filterByClient(client);
This works exactly as it should separately, but when I try 'filterByClient' after 'filterByDate', it naturally overwrites the former for the latter.
So essentially I would like to filterByClient based on the results on filterByDate
Is there any best practice for somehow 'combining' methods where need be, to filter only on the latest results?
Note: The methods are called by two different controllers (select lists)
Eg:
$("#dateFilter").change(function () {
$(this).filterbyDate($(this).val());
});
$("#clientFilter").change(function() {
$(this).filterbyClient($(this).val());
});
You can use method chaining. Just return this; at the end of your functions and use them like controller.filterByDate(date).filterByClient(client);
So your methods would look like
$.fn.filterbyDate = function (filterValue) {
//Do Stuff
return this;
};
$.fn.filterbyClient = function (filterValue) {
// Do Stuff
return this;
};
EDIT:
If its used by different controllers, you have to abstract your controllers, so that the methods don't have to filter the controllers itself, but a list which they use.
So you can do something like:
var list = controller1.list.filterByDate(date);
controller2.list = list.filterByClient(client);
You could pass the list to be filtered as an argument to your function:
(function ($) {
$.fn.filterbyDate = function (list, filterValue) {
var filteredList = [];
//apply filter on list and put result into filteredList...
return filteredList;
};
$.fn.filterbyClient = function (list, filterValue) {
var filteredList = [];
//apply filter on list and put result into filteredList...
return filteredList;
};
}(jQuery));
var entireList = [...];
var filterdList = controller1.filterByDate(entireList, date);
var doubleFilteredList = controller2.filterByClient(filterdList , client);
Update:
Or merge your filter functions into 1 and allow multiple fields to be filtered at once:
$.fn.filterby = function (options) {
//apply filters
if (options.date !== undefined) {
// apply date filter based on value of options.date
}
if (options.client !== undefined) {
// apply client filter on value of options.client
}
};
Related
I'm trying to prevent an object that has a bunch of arrays that will contain objects from having duplicated objects (I know it looks weid but try to see my code below you will understand what I'm saying)
JavaScript
var UIController = (function() {
var DOMstrings = {
inputDay: ".optionList",
inputTimeF: ".inputTime",
inputTimeT: ".inputTime2",
inputText: ".inputText",
goingToCkecked: ".checkboxx",
inputBtn: ".add__btn",
planContainer: ".container",
errorCase: ".error-case",
optionList: ".optionList",
optionListId: "#optionList",
errorDes: "error-description",
};
return {
getInput: function() {
return {
inputDay: document.querySelector(DOMstrings.inputDay).value,
inputTimeF: document.querySelector(DOMstrings.inputTimeF).value,
inputTimeT: document.querySelector(DOMstrings.inputTimeT).value,
inputText: document.querySelector(DOMstrings.inputText).value,
goingToCkecked: document.querySelector(DOMstrings.goingToCkecked).checked,
};
},
getDOMstrings: function() {
return DOMstrings;
},
};
})(); //END OF THE UICONTROLLER MODULE
var internalController = (function(UICtrl) {
var Plan = function(id, from, to, text, goingToCkecked) {
this.id = id;
this.from = from;
this.to = to;
this.text = text;
this.goingToCkecked = goingToCkecked;
};
var data = {
Monday: [],
Tuesday: [],
Wednesday: [],
Thursday: [],
Friday: [],
Saturday: [],
Sunday: []
};
function hasObject( day, object ) {
const dataset = data[day];
// console.log(object.inputTimeF);
return dataset.some(el => {
return (
console.log(el.inputTimeF)
//el.inputTimeF=== object.inputTimeF
// el.to === object.to
// el.txt === object.txt
);
});
}
var Dom = UICtrl.getDOMstrings();
return {
addItem: function(day, from, to, text, goingToCkecked) {
var newPlan, ID;
//CREATE NEW ID
if (data[day].length > 0) {
ID = data[day][data[day].length - 1].id + 1;
} else {
ID = 0;
}
//CREATE NEW PLAN BASED ON THE PICKED DAY
if (day === "Monday" || day === "Tuesday" || day === "Wednesday" || day === "Thursday" || day === "Friday" || day === "Saturday" || day === "Sunday") {
newPlan = new Plan(ID, from, to, text, goingToCkecked);
}
//PUSH IT INTO OUR DATA STRUCTURE
data[day].push(newPlan);
//RETURN THE NEW ELEMENT
return newPlan;
}, //END OF THE ADD ITEM METHOD
duplicatedObject: function(day,object) {
return hasObject(day,object);
}
}; //end of the return object of the internal controller module
})(UIController);
var controller = (function(interCtrl, UICtrl) {
var input, newPlan, DOM;
DOM = UICtrl.getDOMstrings();
function setupEventListeners() {
document.querySelector(DOM.inputBtn).addEventListener("click", ctrlAddPlans);
document.addEventListener("keypress", function(e) {
if (e.keyCode === 13) {
document.activeElement.blur();
ctrlAddPlans();
}
});
}
var ctrlAddPlans = function() {
//3.get the filed input data
input = UICtrl.getInput();
// console.log(input);
//5.add the plan to the internalController
newPlan = interCtrl.addItem(input.inputDay, input.inputTimeF, input.inputTimeT, input.inputText, input.goingToCkecked);
// 4.Refuse duplicated plans
var res = interCtrl.duplicatedObject(input.inputDay, input);
console.log(res)
};
return {
init: function() {
console.log('the app has started');
setupEventListeners();
},
};
})(internalController, UIController);
controller.init();
that big object is the data object and he has a bunch of arrays as you are seeing and these arrays will contain objects from some inputs that the user will input.
So when I'm trying to tackle this problem(I don't wanna any array to have duplicated object) I'm using the some() method (try to see the has object function in my code above).
I'm comparing the object's from parmeter and the el's from parameter but I'm getting always false as a returned value so when I tried to debug my code I found that the el.inputTimeF is returning undefined
screenshot
I googled this but I didn't found anything useful
First, make sure that el.inputTimeF is not undefined then you can chage your code like the following:
return dataset.some(el => {
return el.inputTimeF;
});
Last, do not return console.log because it will return undefined.
I have a function that loops through in indeterminate number of items and does an asynchronous call on each one to get additional data (the content of html template files). The callback does some checking. The resulting function should be thenable. $q is injected earlier, this code is part of a factory.
function searchHelpTopics(topics, searchPhrase) {
if (topics == null || topics.length == 0) return "No search results";
var results = [];
var promises = [];
for (var i = 0; i < topics.length; i++) {
var templateURL = topics[i].URL;
var topic = topics[i];
if (topics[i].HelpTopicId != "Search") {
var promise = $templateRequest(templateURL).then(function (template) {
var text = HTMLToText(template, true);
// do the search
if (text.indexOf(searchPhrase) > -1) {
if (text.length > 50) text = text.substring(0, 50);
var result = {};
result.title = topic.Title;
result.excerpt = text;
result.helpID = topic.HelpTopicID;
results.push(result);
}
});
promises.push(promise);
}
}
return $q.all(promises).then(function () {
return results;
})
The problem here is that the for loop does not wait for the callbacks obviously and so the topic being used by the callback is not the correct one. I need a way to pass topic into the callback on each loop.
Because JS has only function scope you can rewrite your code to use function instead of 'for' loop (which is usually better).
To do that you can use JS built-in forEach (which is available starting from version 1.6 so almost for all browsers) or good functional style libraries like underscore.js or lodash.js.
Or even better - to use Array.map and Array.filter - see the code
function processTemplate(topic, template) {
var text = HTMLToText(template, true);
// do the search
if (text.indexOf(searchPhrase) < 0) {
return;
}
if (text.length > 50) {
text = text.substring(0, 50);
}
return {
title: topic.Title,
excerpt: text,
helpID: topic.HelpTopicID
};
}
function searchHelpTopics(topics, searchPhrase) {
if (!topics || topics.length === 0) {
return "No search results";
}
var promises = topics
.filter(function(topic) { return topic.HelpTopicId !== "Search"; })
.map(function(topic) {
return $templateRequest(topic.URL).then(processTemplate);
});
return $q.all(promises)
.then(function (results) {
return results.filter(function (result) {
return result; // filters out 'undefined'
});
});
}
The is not a complete solution but enough to indicate how it works
somefactory.getHelpTopics().then(function (topics) {
somefactory.searchHelpTopics(topics, searchText).then(function (searchResults) {
vm.searchResults = searchResults;
vm.helpID = "Search";
});
});
--- some factory functions ----
function searchHelpTopics(topics, searchPhrase) {
if (!topics || topics.length === 0) return "No search results";
var promises = topics
.filter(function (topic) { return topic.HelpTopicId !== "Search"; })
.map(function (topic) {
return $templateRequest(topic.URL).then(function (template) {
return searchHelpTemplate(template, topic, searchPhrase);
});
});
return $q.all(promises).then(function (results) {
return results.filter(function (result) {
return result; // filters out 'undefined'
});
});
}
function searchHelpTemplate(template, topic, searchPhrase) {
var text = HTMLToText(template, true);
// do the search
if (text.indexOf(searchPhrase) < 0 && topic.Title.indexOf(searchPhrase) < 0) {
return;
}
if (text.length > 50) {
text = text.substring(0, 50);
}
return {
title: topic.Title,
excerpt: text,
helpID: topic.HelpTopicId
};
}
I have a function that keeps repeating itself in my controller.
It looks like this:
//FUNCTION 1
$scope.selectedage = [];
$scope.pushage = function (age) {
age.chosen = true;
$scope.selectedage.push(age);
console.log($scope.selectedage);
};
$scope.unpushage = function (age) {
age.chosen = false;
var index=$scope.selectedage.indexOf(age)
$scope.selectedage.splice(index,1);
console.log($scope.selectedage);
}
//FUNCTION 2
$scope.selectedgender = [];
$scope.pushgender = function (gender) {
gender.chosen = true;
$scope.selectedgender.push(gender);
console.log($scope.selectedgender);
};
$scope.unpushgender = function (gender) {
gender.chosen = false;
var index=$scope.selectedgender.indexOf(gender)
$scope.selectedgender.splice(index,1);
console.log($scope.selectedgender);
}
I have it like 8 times for 8 different arrays.
Is there any way to write it once and reuse it just changing some values?
You can make a generic function that accepts a value (container) where it needs to write the "value". Like:
$scope.push = function(container, value){
value.chosen = true;
container.push(value);
console.log(container);
}
$scope.unpush = function(container, value){
value.chosen = false;
var index = container.indexOf(value)
container.splice(index, 1);
console.log(container);
}
//sample
$scope.push($scope.selectedage, 10);
$scope.push($scope.selectedgender, "Male");
function togglePushStatusOfItem(item, itemKey, chosenStatus){
item.status = chosenStatus;
if(chosenStatus == true){
$scope[itemKey].push(item);
} else {
var index=$scope[itemKey].indexOf(item)
$scope[itemKey].splice(index,1);
}
console.log($scope[itemKey]);
}
togglePushStatusOfItem(user, 'selectedAge',true);
refactoring code to be reused in service
(function() {
'use strict';
angular
.module('myApp')
.factory('ItemFactory', ItemFactory);
ItemFactory.$inject = [];
/* #ngInject */
function ItemFactory() {
var service = {
toggleItemStatus: toggleItemStatus
};
return service;
////////////////
/*
itemContainer - equivalent to scope
item - item to replace or push
itemKey - itemKey
chosenStatus - true to push and false to remove
*/
function toggleItemStatus(itemContainer, item, itemKey, chosenStatus) {
item.status = chosenStatus;
if (chosenStatus == true) {
itemContainer[itemKey].push(item);
} else {
var index = $scope[itemKey].indexOf(item)
itemContainer[itemKey].splice(index, 1);
}
console.log(itemContainer[itemKey]);
}
}
})();
in your controller, you may use it like this
ItemFactory.toggleItemStatus($scope, item, 'selectedAge', true);// to push item
ItemFactory.toggleItemStatus($scope, item, 'selectedAge', false);// to remove item
The only difference I made is that I used the same function to push and unpush item. I hope this doesn't confuse you.
I'm building this Builder pattern and I'd like to merge some of the elements in a list together. But I'd like to do this in a cleaner way. This is what I've come up with so far which works but I'm sure there's a better way to do this.
function FiltersBuilder() {
this.filters = [];
};
FiltersBuilder.prototype.addFilter = function(options) {
var filter = {
'type': 'selector',
'dimension': options.dimension,
'value': options.value
}
this.filters.push(filter);
return this;
}
FiltersBuilder.prototype.addRegexFilter = function(options) {
var filter = {
'type': 'regex',
'dimension': options.dimension,
'pattern': options.value
}
this.filters.push(filter);
return this;
}
FiltersBuilder.prototype.not = function() {
var not = {
'type': 'not'
};
this.filters.push(not);
return this;
}
FiltersBuilder.prototype.getFilters = function() {
var result = [];
this.filters.forEach(function each(filter, index, theFilters) {
if (filter.type === 'not') {
var filterToMerge = theFilters[index + 1];
var mergedFilter = _.merge(filter, {field: filterToMerge});
result.push(mergedFilter);
} else {
if(index > 0) {
notFilter = theFilters[index - 1];
if(notFilter.type === 'not') {
return;
}
}
result.push(filter);
}
});
return result;
}
var filterBuilder = new FiltersBuilder();
filterBuilder.addFilter({
dimension: '_o',
value: 'origin'
});
filterBuilder.not().addRegexFilter({
dimension: 'cTy',
value: 'value'
});
console.log(filterBuilder.getFilters());
If not method is called before adding filter, I would like to merge the not filter with the next element and add that object to the result list. But if not is not called before adding filter then don't do anything just add the filter to result list.
https://jsfiddle.net/9wyqbovu/
Instead of treating not as a filter to be added to the list of filters, and processed when you get the filters, treat it as a modifier--a flag. Maintain a not property on the FiltersBuilder object, initialized to false, which a call to FilterBuilders.not will toggle. On each filter, also add a not property, which is set by the current value of the not flag on FilterBuilders (which is then reset). In other words:
function FiltersBuilder() {
this.filters = [];
this.not = false;
};
FiltersBuilder.prototype.addFilter = function(options) {
var filter = {
'type': 'selector',
'dimension': options.dimension,
'value': options.value,
not: this.not
}
this.not = false;
this.filters.push(filter);
return this;
}
FiltersBuilder.prototype.addRegexFilter = function(options) {
var filter = {
'type': 'regex',
'dimension': options.dimension,
'pattern': options.value,
not: this.not
}
this.not = false;
this.filters.push(filter);
return this;
}
FiltersBuilder.prototype.not = function() {
this.not = !this.not;
}
FiltersBuilder.prototype.getFilters = function() {
return this.filters;
}
var filterBuilder = new FiltersBuilder();
filterBuilder.addFilter({
dimension: '_o',
value: 'origin'
});
filterBuilder.not().addRegexFilter({
dimension: 'cTy',
value: 'value'
});
console.log(filterBuilder.getFilters());
If you would prefer to be able to say filterBuilder.not.addFilter (without the parentheses after not), then define not as a getter, ie
Object.defineProperty(FiltersBuilder.prototype, "not", {
get: function() {
this.not = !this.not;
return this;
}
});
Well, how about if we do iterate it from the right and use unshift instead of push:
getFilters = function(){
return this.filters.reduceRight(function(results, filter){
if(filter.type === 'not'){
//get filter to 'not'
var notFilter = results[0];
Object.assign(notFilter, filter); //Or _.merge
} else {
results.unshift(filter);
}
return results;
}, []);
}
Alternatively, you can use push instead of unshift, grab the last element of the array as the filter to negate (instead of the first one) and reverse the results.
getFilters = function(){
return this.filters.reduceRight(function(results, filter){
if(filter.type === 'not'){
//get filter to 'not'
var notFilter = results[results.length - 1];
Object.assign(notFilter, filter); //Or _.merge
} else {
results.push(filter);
}
return results;
}, []).reverse();
}
Cursors can be easily converted to arrays using .toArray(foo) method:
var cursor = col.find({});
cursor.toArray(function (err, itemsArray) {
/* do something */
});
But is it possible to convert itemsArray in a cursor so I will have all cursor functions?
var newCursor = foo (itemsArray);
typeof newCursor.toArray === "function" // true
Well it is all just JavaScript so why not create your own iterator:
var Iterator = function () {
var items = [];
var index = 0;
return {
"createCursor" : function (listing) {
items = listing;
},
"next" : function () {
if ( this.hasNext() ) {
return items[index++];
} else {
return null;
}
},
"hasNext" : function () {
if ( index < items.length ) {
return true;
} else {
return false;
}
}
}
}();
So then you use it like this with an array:
var cursor = new Iterator();
cursor.createCursor( array );
cursor.next(); // returns just the first element of the array
So just a general way to write an iterator. If you want more functionality then just add the other methods to the prototype.