JQGrid custom summary match - javascript

Have successfully used JQGrid for a few projects, but struggling to get it to do what I want in this example.
I think I need to create a custom summaryType that checks whether records match, rather than sum, avg, min, max etc.
I need to check whether record 'Us', matches 'Them' and display the text 'Match' where the red X's are, could anyone give me some pointers on how to do this.

Surprisingly simple when you understand how it works, thanks to the comments on this answer
the jqGrid will call your function for every row (this is why you pass it to the option only by name, jqGrid requires the actual function not its result) - for first row the val should be empty and for next rows it should be the result of previous call.
Set a summaryType in your colModel as your function name, and use these functions
function numberMatch(val, name, record) {
if (val || 0 - record[name] == 0) {
return "Match";
} else {
return "unmatched";
}
}
function textMatch(val, name, record) {
if (val || '' === record[name]) {
return "Match";
} else {
return "unmatched";
}
}

Related

Javascript filter with multiple criteria

I am filtering items based off of multiple criteria. in my sample I have three different criteria but the final code will have 5 different criteria. My current code works but it is not efficient and now that I have added a third criteria it is getting blotted. I know there has to be a better more efficient way to write the code.
My three filters are: Search, Year & Weeks.
return this.list.filter((item) =>{
let results
// if search only
if(this.search) {
results = item.title.toLowerCase().match(this.search.toLowerCase())
}
// if a year is selected only
if(this.whichYear) {
results = this.checkForDefaultYear(item).includes(this.whichYear)
}
// if num of weeks only
if(this.numOfWeeks) {
results = item.units.length === this.numOfWeeks
}
// if both search and year
if(this.search && this.whichYear) {
results = this.checkForDefaultYear(item).includes(this.whichYear) && item.title.toLowerCase().match(this.search.toLowerCase())
}
// if neither search or year
if(!this.search && !this.whichYear && !this.numOfWeeks)
results = item.title
return results
})
The below above works but now that I have added Weeks I have to do something like
if(this.search && this.whichYear && this.numOfWeeks) {}
if(this.search && this.whichYear) {}
if(this.search && this.numOfWeeks) {}
if(this.whichYear && this.numOfWeeks) {}
This is going to get out of control once I have all 5 filters. This does not seem like the best way to write this code.
I'm not sure if I understand correctly, but from what I understand you want to run a series of checks together, depending on whether a user selected certain options or not.
To do so, you can try with this approach:
return this.list.filter((item) =>{
let results = !!item.title; // default filter
// if previous filters and search
if(this.search) {
results = results && item.title.toLowerCase().match(this.search.toLowerCase());
}
// if previous filters and year
if(this.whichYear) {
results = results && this.checkForDefaultYear(item).includes(this.whichYear)
}
// if previous filters and weeks
if(this.numOfWeeks) {
results = results && item.units.length === this.numOfWeeks
}
return results;
});
Here, it works like this:
Apply default filter, in this case, the title must be not empty
If a user selected the search filter, you apply an additional condition to the previous ones (here only default)
If a user selected the whichYear filter, you apply an additional condition to the previous ones (here default and search)
If a user selected the numOfWeeks filter, you apply an additional condition to the previous ones (here default, search, and year)
So you basically extend the conditions chain depending on what your user selects. And this way you can add more filters according to your needs.
You could also do a similar thing using arrays: create a filters array and push() a filter with every condition and in the end, check if every filter returned true.
The filter callback needs a true or false value.
So the most common way is to return false if a check fails, end return true if it's correct :
return this.list.filter((item) =>{
if (condition1) {
return false; // Invalid
}
if (condition2) {
return false; // Invalid
}
// Valid
return true;
})
So in your case, you can do somthing like:
return this.list.filter((item) =>{
// if search only
if (item.title.toLowerCase().match(this.search.toLowerCase()) {
return false
}
// if a year is selected only
if (this.checkForDefaultYear(item).includes(this.whichYear)) {
return false
}
// if num of weeks only
if (item.units.length === this.numOfWeeks) {
return false
}
// ...and the rest...
// Valid
return true;
})

Why does this code work for counting one item in a list but not the others?

I am using trying to count the number of "Manager"'s "MIT"'s and "Instore"'s in a .csv file. The code I have works for finding all the "Manager"'s but wont work for the other two.
I've tried splitting the function up into 3 separate functions as I thought the problem may have been using more than one dc.numberDisplay in a function but that didn't work. I've tried the function so there's only one, if it's looking for Managers it works, MIT's it does not and Instores it does not. I've tried changing the order of the code and still nothing. I've put console.log(p.mit_count) within each line of add_item, remove_item and initialise. I've put console.log(d.value.mit_count) within the valueAccessor.
Scott,Instore,3,BMC,96
Mark,Instore,4,Intro,94
Wendy,Instore,3,Intro,76
Lucas,Instore,2,Intro,96
.defer(d3.csv, "data/Results.csv")
.await(makeGraphs)
//function for making and rendering graphs
function makeGraphs(error, staffData) {
var ndx = crossfilter(staffData);
show_number_of_staff(ndx);
dc.renderAll();
}
function show_number_of_staff(ndx) {
var dim = ndx.dimension(dc.pluck('Rank'));
function add_item(p, v) {
if (v.Rank == "Manager") {
p.manager_count++;
}
else if (v.Rank == "MIT") {
p.mit_count++;
}
else if (v.Rank == "Instore") {
p.instore_count++;
}
return p;
}
function remove_item(p, v) {
if (v.Rank == "Manager") {
p.manager_count--;
}
else if (v.Rank == "MIT") {
p.mit_count--;
}
else if (v.Rank == "Instore") {
p.instore_count--;
}
return p;
}
function initialise(p, v) {
return { manager_count: 0, mit_count: 0, instore_count: 0 };
}
var staffCounter = dim.group().reduce(add_item, remove_item, initialise);;
dc.numberDisplay("#managerCount")
.formatNumber(d3.format(".0"))
.valueAccessor(function(d) {
return d.value.manager_count;
})
.group(staffCounter);
dc.numberDisplay("#mitCount")
.formatNumber(d3.format(".0"))
.valueAccessor(function(d) {
return d.value.mit_count;
})
.group(staffCounter);
dc.numberDisplay("#instoreCount")
.formatNumber(d3.format(".0"))
.valueAccessor(function(d) {
return d.value.instore_count;
})
.group(staffCounter);
}
console.log(p.mit_count) shows that it counts to 13 (as I am expecting it to), but then in the valueAccessor console.log(d.value.mit_count) shows 0. I cannot get why this works for "Manager" but nothing else. I'm almost embarrassed that this has taken me over a week. It just seems so simple!
I think if you log staffCounter.all() you'll find that it is counting managers, MITs, and Instores in 3 separate bins. This is because your dimension is sorting by Rank, then your group is binning again by Rank, so you end up with one bin per rank.
Normally you will want to pass a groupAll object, with one bin, to the numberDisplay. However, it is permissive and will also accept ordinary groups with multiple bins (source). It will sort the bins and take the last.
Why? I'm not sure. I think in almost every case you will want to reduce to one bin, but someone clearly had a use case where they wanted to display the largest value out of many bins.
I was surprised to find that the behavior is not documented, so I've updated the numberDisplay documentation.
With a groupAll your code will look something like:
var staffCounter = ndx.groupAll().reduce(add_item, remove_item, initialise);
dc.numberDisplay("#managerCount")
.formatNumber(d3.format(".0"))
.valueAccessor(function(d) {
return d.manager_count; // no .value here
})
.group(staffCounter);
Note that no crossfilter dimension is used here.

Angular JavaScript filter function, updating with $scope input

I'm currently writing a little function to take a $scope value from an input field, then add it to a new array containing invited users.
To prevent duplicate entries into the array I'm trying to use JavaScript's filter method.
My problem is when I look at whats being output to the console, the console.log inside the filter function always prints the same Email value, it doesn't seem to update and consider the new input. e.g. a user has added 2 users, the first will be added and the invitesArr will only contain the first Email.
var invitesArr = $scope.invites;
console.log(invitesArr)
console.log($scope.searchContacts)
if ($scope.invites.length >= 0) {
invitesArr.filter(function(invitesArr) {
console.log(invitesArr.Email)
return invitesArr.Email === $scope.searchContacts;
});
if (invitesArr == false) {
courseAccountService.inviteGuestToCourse($scope.courseId,$scope.searchContacts).
then(function(invitation) {
$scope.invites.push(invitation);
});
}
}
Try the below code:
var invitesArr = $scope.invites;
console.log(invitesArr)
console.log($scope.searchContacts)
if ($scope.invites.length >= 0) {
//use arrow operator instead of function.
var duplicateEmails = invitesArr.filter((item) => {
console.log(invitesArr.Email)
//assuming datatype of item.Email and $scope.searchContacts are same else use == for comparision
return item.Email === $scope.searchContacts;
});
//Check for length and then push to your invites
if (duplicateEmails.length==0) {
courseAccountService.inviteGuestToCourse($scope.courseId,$scope.searchContacts).
then(function(invitation) {
$scope.invites.push(invitation);
});
}
}
Hope it helps!!

Script debugger confirms intended conditional check between JSON key and string, however I am getting an undesired result?

I am looking to modify the output table I am getting from this handy Json-to-HTML-Table script I stumbled upon here on SO. There is a point (line 86) where json-to-table.js passes a JSON object and generates array keys to be used as table headers. Optionally, this array_key function can generate only one key for a specified search_value parameter passed. I however [attempted] to modify it so that ALL array keys that did NOT match the search_value would be returned. Here is the function after my changes:
function array_keys(input, search_value, argStrict)
{
var search = typeof search_value !== 'undefined', tmp_arr = [], strict = !!argStrict, include = '', key = '';
if (input && typeof input === 'object' && input.change_key_case) { // Duck-type check for our own array()-created PHPJS_Array
return input.keys(search_value, argStrict);
}
for (key in input)
{
if (input.hasOwnProperty(key))
{
include = false;
if (search)
{
if (strict && input[key] == search_value)
include = false;
else if (input[key] == search_value)
include = false;
else
include = true;
}
if (include)
tmp_arr[tmp_arr.length] = key;
}
}
return tmp_arr;
}
Now, the reason I did this is because I want my generated table to not include a specific column from my JSON object:
{
"examItemCategories": [
{
"catgoryName": "01-Normal processes",
"catgoryPath": "General Area\\01-Normal processes",
"numberOfItems": 2,
"percentage": "6.06"
}
]
}
Given that I can not modify the original JSON obj passed, I was determining whether or not to attempt to modify the table post creation (e.g. remove column), or during. After looking at the array_keys function, I felt I could easily invert the conditional checking for the search_value.
I now call array_keys(parsedJson[0], 'catgoryPath'); from json-to-table script. (Yes, catgoryPath is the correctly spelled name haha). Then I set a break point at the for loop within array_keys function to follow it through in Firebug.
First iteration: catgoryName is added to tmp_arr, 2nd iteration: catgoryPath is added to tmp_arr...and continues through 3rd and 4th iterations. I do not wantcatgoryPath added.
Looking at script debugger, on the 2nd iteration, whether or not catgoryPath gets added comes down to the conditional: else if (input[key] == search_value) line. The thing is, on the respective iteration both key and search_value variables equal "catgoryPath" according to Firebug. So therefore, include = false; should fire correct?
Any help is appreciated, apologies for the length and liberal usage of inline code.
Instead of using the array_keys function from the json-to-table script, if you are using JS > 1.8.5 you can use Object.keys(obj) to return an array of a given object's own enumerable properties.
The returned array of keys are then used as table headers under which the table populates with JSON data thanks to the script. Prior to the creation of table, I took my array of table headers and used array.splice(index, howMany) instead of delete (see here) to preserve my array index values.

lack of identity between jQuery selector and jQuery variable?

I'm running into a maddening problem where I set a variable to point to a jQuery selector, such as: var foobar=jQuery(this); I then pass this variable to a function to be worked on. Let's simplify a little and say the function looks like this:
function SetFieldValue (selector) {
selector.val('test');
console.log ( selector );
console.log ( jQuery('#' + selector.attr('id')) );
}
In this situation if you assume that:
the selector is always a form element (and therefore val() is a valid operation)
the selector does resolve to a single dom element which has an 'id' attribute
You would then expect the two console.log statements to output the same result, right? Well I'm running into a situation where this condition only happens about 90% of the time.
In order to give more context I've created a short screencast demonstrating the problem:
SCREENCAST LINK
For reference purposes, here's the actual SetFieldValue code that is shown in the screencast:
function SetFieldValue ( domObject, value ) {
// as a safety function, check if a string representation of the domObject was passed in and convert it to a jQuery object if it was
if ( jQuery.type(domObject) === "string") {
console.log ("Value passed into SetFieldValue was a string representation so converting to jQuery object");
domObject = jQuery(domObject);
}
if ( jQuery.inArray (domObject.prop('tagName').toLowerCase(),['input' , 'select' , 'textarea']) >= 0 ) {
console.log ("setting to value attribute: " + value);
if ( domObject.hasAttr('id') ) {
domObject.val(value);
//jQuery('#' + domObject.attr('id')).val(value);
} else {
domObject.attr('value',value);
}
console.log ("Using jQuery ID it is set to: " + jQuery('#' + domObject.attr('id')).val() );
console.log ("Using jQuery selector variable it is set to: " + domObject.val() );
} else {
console.log ("setting to html attribute");
domObject.html( value );
}
return domObject;
}
Lets examine the code a bit.
First assigning back to a parameter is not a good practice adding a var at the start of your function would be a lot better, as scope can be lost.
//Suggestion change parameter to domItem
var domObject
Your missing an error handler for when the parameter is not String.
when identifying the type use
<VARNAME>.constructor.toString().match(/function (\w*)/)[1] === "<TYPE>"
It's more efficient and handles custom types.
No need for all the logic in assignment of value attribute. Any dom Object can be made to have a value attribute. also not sure why you are setting the val versus the value.
domObject.attr('value',value);
It is at this point that I can see your code could really use some documentation to help explain purpose
If you are explicitly only wanting to set value on Input fields and set value as innerhtml on non input fields then yes the logic would be needed but could be simplified to ... as the value doesn't need to be detected to overwritten.
if (jQuery.inArray (domObject.prop('tagName').toLowerCase(), ['input' , 'select' , 'textarea']) >= 0) {
domObject.attr('value',value);
} else {
domObject.html( value );
}
No Idea why you are returning the domObject out.
So a quick rewrite without the return and keeping most of the logic adding error handling results in
/*jslint sloppy: true*/
/*global jQuery*/
function SetFieldValue(domString, value) {
// as a safety function, check if a string representation of the domObjects was passed in and convert it to a jQuery object if it was
var domObjects, index;
//errorhandling
if (domString === undefined || domString === null) {
throw {error : "domString must have a value."};
}
if (domString.constructor.toString().match(/function (\w*)/)[1] !== "string") {
if (domString.constructor.toString().match(/function (\w*)/)[1].match(/HTML[a-zA-Z]*Element/) === null) {
throw {error : "domString expected to be String or domObjects"};
}
} else {
if (jQuery(domString).length === 0) {
throw {error : "domString does not resolve to a detectable domObjects."};
}
}
//errorhandling
//action
if (domString.constructor.toString().match(/function (\w*)/)[1].match(/HTML[a-zA-Z]*Element/)) {
//made as an array to normalize as jQuery returns an array allows code to be simplified
domObjects = [domString];
} else {
domObjects = jQuery(domString);
}
//given that domObjects are an array need to step through the array
for (index = domObjects.length - 1; index >= 0; index -= 1) {
if (
jQuery.inArray(
domObjects[index].tagName.toLowerCase(),
['input', 'select', 'textarea']
) >= 0
) {
if (domObjects[index].hasAttr('id')) {
domObjects[index].val(value);
} else {
domObjects[index].attr('value', value);
}
} else {
domObjects[index].html(value);
}
}
}
The above passes JSLint
I know I didn't provide enough context for people to really dig into this problem but I have in the end solved it. What was the issue? Well it was #Kobi who first asked is the DOM element's ID unique ... to which I happily reported it was. And it had been but in fact that WAS the problem. Jesus. It's always the obvious things that you then go onto overlook that get you in trouble.
Anyway, thanks for your patience and help.

Categories

Resources