I have a function that is supposed to sort a list with objects alphabetically, depending if clicked on ascending or descending (a select with 2 options)
The problem is that when I change the value the first time, nothing happens, it only activates after the second change.
Here's my code:
function modulesSorter(a, b) {
let moduleName1 = a.module.toLowerCase();
let moduleName2 = b.module.toLowerCase();
let place = 0
if (value === "descending" || value == null) {
if (moduleName1 > moduleName2) {
place = 1
} else if (moduleName1 < moduleName2) {
place = -1
}
} else if (value === "ascending") {
if (moduleName1 > moduleName2) {
place = -1
} else if (moduleName1 < moduleName2) {
place = 1
}
}
return place;
}
let value
function changeFilter(e) {
e.preventDefault();
renderModules(modules.sort(modulesSorter));
value = document.querySelector('#sortby').value;
console.log(value)
}
I also activate the function once in the init so that it is sorted ascending as that is the default value.
It is very possible the "first" attempt sorts the objects into the same order they are current in.
The reason you see the effect you describe, is because during the first run value is undefined until after the sorting algorithm runs:
function changeFilter(e) {
e.preventDefault();
renderModules(modules.sort(modulesSorter));
value = document.querySelector('#sortby').value;
console.log(value)
}
Change the order of those two statements like this:
function changeFilter(e) {
e.preventDefault();
value = document.querySelector('#sortby').value;
renderModules(modules.sort(modulesSorter));
console.log(value)
}
Related
I apologize if I didn't ask my question in the correct way. Let me explain my problem.
I'm working on a search function in a table, I'm adding the option to search in specific columns.
Right now I'm manually checking if the checkboxes are selected or not, then return the proper search function.
However I want to make it dynamically, I don't want to edit this code after every time I add new columns to the table. That's where I'm struggling.
This is my current code:
vm.$watch('searchTerm', function (searchTerm) {
if (!searchTerm) {
vm.filteredTable = angular.copy(vm.table);
} else {
searchTerm = searchTerm.toLowerCase();
return vm.filteredTable.rows = vm.table.rows.filter(function (row) {
if (vm.searchFilter[0] === true ){
return (contains(row[vm.table.columns[0].value], searchTerm))
}
if (vm.searchFilter[1] === true ){
return (contains(row[vm.table.columns[1].value], searchTerm))
}
if (vm.searchFilter[2] === true ){
return (contains(row[vm.table.columns[2].value], searchTerm))
}
if (vm.searchFilter[3] === true ){
return (contains(row[vm.table.columns[3].value], searchTerm))
}
if (vm.searchFilter[4] === true ){
return (contains(row[vm.table.columns[4].value], searchTerm))
}
if (vm.searchFilter[5] === true ){
return (contains(row[vm.table.columns[5].value], searchTerm))
}
if (vm.searchFilter[6] === true ){
return (contains(row[vm.table.columns[6].value], searchTerm))
}
if (vm.searchFilter[7] === true ){
return (contains(row[vm.table.columns[7].value], searchTerm))
}
});
}
});
This is the way I created it, I tried to use a loop, but I can't return the functions if there is a loop. Also adding return before the for loop wont help either.
vm.$watch('searchTerm', function (searchTerm) {
if (!searchTerm) {
vm.filteredTable = angular.copy(vm.table);
} else {
searchTerm = searchTerm.toLowerCase();
return vm.filteredTable.rows = vm.table.rows.filter(function (row) {
for (let i=0; i<vm.searchFilter.length; i++){
if (vm.searchFilter[i] === true ){
return (contains(row[vm.table.columns[i].value], searchTerm))
}
}
});
}
});
This is probably a very easy case for the most of you, so I apologize if I'm just being stupid right now. I'm working since 2 hours on this till now...
EDIT:
The function I mean:
const contains = (value, searchTerm) => {
if (!value) return false;
return value.toLowerCase().includes(searchTerm);
}
2nd EDIT:
I just realized after a kind member told me that, that the first version isnt working either the way I want it.
There is the option to have a multiple selection, so if I select the first two checkboxes, then it should search in BOTH of them and not only one.
You can just iterate over the whole thing like this:
vm.$watch('searchTerm', function (searchTerm) {
if (!searchTerm) {
vm.filteredTable = angular.copy(vm.table);
} else {
searchTerm = searchTerm.toLowerCase();
return vm.filteredTable.rows = vm.table.rows.filter(row =>
vm.searchFilter.some((filter, index) => filter && contains(row[vm.table.columns[index].value])))
}
By using Array.prototype.some you essentially ask whether your row matches at least one of the filters.
I have a class for validation which have different validation functions.
Now what i am trying to do is make an object in another file and send all the validation data using a constructor.This constructor will receive an object that looks like this "rules[is_empty:value]". In this left part is function name and value is the value fetched from input field.Now how do I call the function and send the value as an argument.Also what should i do when there are functions that has more than one argument.
I have already tried using map method and split method but not able to access the function.
class Validator {
constructor(rules) {
let rule_obj = {}
// rules[is_empty:value,has_valid_min_length:2;3]
}
/**this is to check if the field has an empty value or not */
is_empty = (value) => {
if (value == '' || value == null) {
return true
}
else {
return false
}
}
/**this is to check if value has required minimum length or not */
has_valid_min_length = (value, min_length = 0) => {
if (this.is_empty(value)) {
return false
}
if (value.length < min_length) {
return false
}
else {
return true
}
}
/**this is to check if value has more characters than maximum length */
has_valid_max_length = (value, max_length = 0) => {
if (this.is_empty(value)) {
return false
}
if (value.length > max_length) {
return false
}
else {
return true
}
}
//this is to check if selected date is less than given limit
is_before_min_date = (value_date, min_date) => {
if (this.is_empty(value)) {
return false
}
if (value_date < min_date) {
return true
}
else { return false }
}
//this is to check if selected date is higher than given limit
is_after_max_date = (value_date, max_date) => {
if (this.is_empty(value)) {
return false
}
if (value_date > max_date) {
return true
}
else {
return false
}
}
}
I want to call the function which is before ':' sign in the array and give that function argument which is in value that is at the right side of ':'.
Please help.
You could send an object through the constructor...
x = {
"is_empty": [0, 2, 2],
"has_valid_min_length": [ [value, min_length], [value, min_length] ],
"has_valid_max_length": [ [value, max_length], [value, max_length] ],
"is_before_min_date": [ [value_date, min_date], [value_date, min_date] ],
"is_after_max_date": [ [value_date, max_date], [value_date, max_date] ]
}
and then in your constructor, set up a loop through the object and value arrays...
constructor(to_be_validated) {
let validator_names = Object.keys(to_be_validated);
for (let validator of validator_names) {
let values = to_be_validated[validator];
if (validator === "is_empty") {
for (let value of values) {
this.is_empty(value);
}
} else if (validator === "has_valid_min_length") {
for (let value of values) {
this.has_valid_min_length(value[0], value[1]);
}
} etc...
}
}
and then when you call the function, the methods should execute
let my_validator = new Validator(x);
I echo the comment(s) above. Wanting the approach and specific syntax of,
let my_validator = new Validator(rules[validator_name:values]);
all in one clean line like that, is a bit off. I've never seen it done like that.
You probably want an additional function in your class that calls all the check-functions according to your rules.
class Validator {
constructor(rules) {
this.rules = rules;
// rules={is_empty:[],has_valid_min_length:[2]};
}
validate = (value) => {
let pass = true;
Object.keys(this.rules).forEach(k=>pass = pass && this[k](value, ...this.rules[k]));
return pass;
}
...
}
The rules-Objects has key-value-pairs, where the keys are the function-names of the individual checks and the values are arrays of parameters that will be passed. This array can have zero or more entries.
Edit: forEach will iterate over all the keys. On every turn k will hold the current key. pass is a boolean that collects all the return values of your checker-functions. (true && false === false) That way the final return-value will be false if any of the checks returned false.
You would then apply the rules to your data like this:
myValidatorObj.validate(data);
It's driving me crazy. I've created a list with several entries. I added a filtering function, which seems to work fine. I've checked the number of results returned, but somehow it just showing the result number beginning at the first row.
For explanation:
Let's assume I search for "Zonen" and my filter function returns 4 rows with ID 23, 25, 59 and 60, the rows with ID's 1,2,3 and 4 are displayed. What I'm doing wrong!?
...
render() {
let filteredList = this.state.freights.filter((freight) => {
let search = this.state.search.toLowerCase();
var values = Object.keys(freight).map(function(itm) { return freight[itm]; });
var flag = false;
values.forEach((val) => {
if(val != undefined && typeof val === 'object') {
var objval = Object.keys(val).map(function(objitm) { return val[objitm]; });
objval.forEach((objvalue) => {
if(objvalue != undefined && objvalue.toString().toLowerCase().indexOf(search) > -1) {
flag = true;
return;
}
});
}
else {
if(val != undefined && val.toString().toLowerCase().indexOf(search) > -1) {
flag = true;
return;
}
}
});
if(flag)
return freight;
});
...
<tbody>
{
filteredList.map((freight)=> {
return (
<Freight freight={freight} onClick={this.handleFreightClick.bind(this)} key={freight.id} />
);
})
}
</tbody>
...
UPDATE
freights is loaded and filled via AJAX JSON result. One object of freights looks like this:
I have a textbox where a user can perform a search. This search should return all freight objects which properties contain the search string.
The filter is so complex, because I want to also to search in sub-objects of freight. Maybe there is a more simple way?
"Zones" was just an example for a search string the user can search for.
Now that your intentions are clearer, I suggest this much less complex solution.
First, you can write a recursive utility fn to get all values of all keys in an n-depth object. Like this, for example (I'm using lodash's utility fn isObject there):
const getAllValues = (obj) => {
return Object.keys(obj).reduce(function(a, b) {
const keyValue = obj[b];
if (_.isObject(keyValue)){
return a.concat(getAllValues(keyValue));
} else {
return a.concat(keyValue);
}
}, []);
}
Now that you have an array of all object's values, it makes your filter very simple:
let filteredList = this.state.freights.filter((freightItem) => {
const allItemValues = getAllValues(freightItem);
return allItemValues.includes(this.state.search);
});
That should be it. If something is not working, gimme a shout.
I have found the solution why the "wrong" freight entries are displayed.
I needed to add in freight component the componentWillReceiveProps method:
componentWillReceiveProps(nextProps) {
if(nextProps.freight) {
this.setState({
freight: nextProps.freight
});
}
}
Then everything worked fine.
I'm trying to check if an object has the same observable values of other objects with the same observable properties inside an observable array.
I created a foreach loop which evaluates if any of the observables match. The problem I'm having is that condition always evaluates to true, even though these values are different. I'm using typescript and knockout.
Here's the code :
export function addPDFToPackage(heat: MTRHeat): void {
var koHeat: MTRHeatWithInclude = ko.mapping.fromJS(heat);
koHeat.Include = ko.observable(true);
var arrayOfHeats = model.mtrPackage.Heats();
var addToHeats = () => model.mtrPackage.Heats.push(koHeat);
var duplicate = false;
arrayOfHeats.forEach(function (koHeat, i) {
if (arrayOfHeats[i].MTRID() == koHeat.MTRID() && arrayOfHeats[i].HeatID() == koHeat.HeatID() && arrayOfHeats[i].PartID() == koHeat.PartID()) {
duplicate = true;
}
else
duplicate = false;
})
if (!!model.mtrPackage.PackageID()) {
if (duplicate) {
var c = confirm("Warning: Duplicate MTR located on current package.Proceed ?")
if (c) {
ServiceMethods.addHeatToPackage(model.mtrPackage.PackageID(), heat.HeatID).done(addToHeats);
}
if (!c) {
return;
}
}
}
}
First problem: Your loop compares each object to itself because you re-use the variable name koHeat. I believe you really wanted to refer to the "outer" koHeat.
Second problem: You overwrite the duplicate variable in every loop iteration. This is probably not what you intend. Instead you want to stop the loop as soon as a duplicate is found.
How about something along those lines?
export function addPDFToPackage(heat: MTRHeat): void {
var koHeat: MTRHeatWithInclude = ko.mapping.fromJS(heat);
var packageId = model.mtrPackage.PackageID();
koHeat.Include = ko.observable(true);
function equals(a: MTRHeatWithInclude, b: MTRHeatWithInclude): boolean {
return a.MTRID() == b.MTRID() && a.HeatID() == b.HeatID() && a.PartID() == b.PartID();
}
if ( !!packageId && (
!model.mtrPackage.Heats().some(item => equals(item, koHeat)) ||
confirm("Warning: Duplicate MTR located on current package.Proceed ?")
)
) {
ServiceMethods.addHeatToPackage(packageId, heat.HeatID).done(() => {
model.mtrPackage.Heats.push(koHeat);
});
}
}
The equals() function should ideally be a method of the MTRHeatWithInclude class.
I think you're getting a clash between koHeat defined here:
var koHeat: MTRHeatWithInclude = ko.mapping.fromJS(heat);
koHeat.Include = ko.observable(true);
And the variable defined within the forEach call. It's always returning true as (within the scope of the forEach) arrayOfHeats[i] === koHeat.
Try this:
export function addPDFToPackage(heat: MTRHeat): void {
var koHeat: MTRHeatWithInclude = ko.mapping.fromJS(heat);
koHeat.Include = ko.observable(true);
var arrayOfHeats = model.mtrPackage.Heats();
var addToHeats = () => model.mtrPackage.Heats.push(koHeat);
var duplicate = false;
arrayOfHeats.forEach(function (koHeat2, i) {
if (koHeat2.MTRID() == koHeat.MTRID() &&
koHeat2.HeatID() == koHeat.HeatID() &&
koHeat2.PartID() == koHeat.PartID()) {
duplicate = true;
}
})
if (!!model.mtrPackage.PackageID()) {
if (duplicate) {
var c = confirm("Warning: Duplicate MTR located on current package.Proceed ?")
if (c) {
ServiceMethods.addHeatToPackage(model.mtrPackage.PackageID(), heat.HeatID).done(addToHeats);
} else {
return;
}
}
}
}
I'm currently using javascript to do some experiments and although I'm not new to JS I have a doubt that I hope the good folks at SO will be able to help.
Basically I'm making a jsonp request to a webservice that returns me the amount/length on the reply (just counting objects).
Then I'm randomly selecting 9 of those objects to place in an array and here lies the problem. I would like to make sure that none of those 9 objects is repeated.
To achieve this I'm using the following code:
function randomizer() {
return Math.ceil(Math.random()*badges.length);
}
function dupsVerify(array, number) {
array.forEach(function () {
if(array === number) {
return true;
}
});
return false;
}
// Randomly choose 9 array indexes
var randomBadge = function () {
var selectedIndexes = new Array();
while(selectedIndexes.length < 9) {
var found = false;
var randomNumber = randomizer();
if(!dupsVerify(selectedIndexes, randomNumber)) {
selectedIndexes.push(randomNumber);
} else {
newRandom = randomizer();
dupsVerify(selectedIndexes, newRandom);
}
}
return selectedIndexes;
}
I've tried a couple different methods of doing this verification but I've been thinking if it wouldn't be possible to do the following:
Generate the random number and go through the array to verify if it already exists in the array. If it exists, generate another random number (randomize call) and verify again.. If it doesn't exist in the array, then push it to the "final" array.
How can I achieve this? Using callbacks?
Am I doing this right or should I chance the code? Is there a simpler way of doing this?
Best Regards,
This would get you the desired behavior:
function getRandomPositions(sourcearray, desiredcount){
var result = [];
while(result.lentgth < desiredcount){
var rnd = Math.ceil(Math.random()*sourcearray.length);
if (result.indexOf(rnd) == -1){
result.push(rnd);
}
}
return result;
}
Instead of generating X random numbers, just generate a random number, but don't add it if it already exists.
I ended up finding the best solution for this scenario by using the following code:
function randomizer() {
return Math.ceil(Math.random()*badges.length);
}
function dupsVerify(array, number) {
var result;
if(array.length === 0) {result = false;}
array.forEach(function (item) {
if(item === number) {
result = true;
} else {
result = false;
}
});
return result;
}
// Randomly choose 9 array indexes
function randomBadge() {
while(cards.length < 9) {
var randomNumber = randomizer();
if(!dupsVerify(cards, randomNumber)) {
cards.push(randomNumber);
} else {
randomBadge();
}
}
return cards;
}
This represents the same behavior (and a few minor code corrections) but ensures that I will never get an array with 2 repeated objects.