Episerver promotions - episerver

protected override RewardDescription Evaluate(EntryPromotion promotionData, PromotionProcessorContext context)
{
var lineItems = GetLineItems(context.OrderForm);
var condition = promotionData.Condition;
var applicableCodes = targetEvaluator.GetApplicableCodes(lineItems, condition.Targets, condition.MatchRecursive);
var filteredLineItems = GetFilteredLineItems(lineItems, condition.RequiredQuantity);
var filteredApplicableCodes = GetFilteredApplicableCodes(applicableCodes, filteredLineItems);
if (applicableCodes.NotNullOrEmpty() && filteredApplicableCodes.IsNullOrEmpty())
{
return RewardDescription.CreatePercentageReward(
FulfillmentStatus.PartiallyFulfilled,
Enumerable.Empty<RedemptionDescription>(),
promotionData,
promotionData.Percentage,
Enum.GetName(typeof(RequestFulfillmentStatus), RequestFulfillmentStatus.PartiallyFulfilled));
}
var fulfillmentStatus = fulfillmentEvaluator.GetStatusForBuyQuantityPromotion(
filteredApplicableCodes,
filteredLineItems,
condition.RequiredQuantity,
condition.RequiredQuantity);
return RewardDescription.CreatePercentageReward(
fulfillmentStatus,
GetRedemptions(filteredApplicableCodes, promotionData, context, lineItems),
promotionData,
promotionData.Percentage,
fulfillmentStatus.GetRewardDescriptionText(localizationService));
}
We have customized promotion to apply promotion only for lineitem that has required quantity. Now, when we exclude a promotion from other and two line items applicable for both promotions, only one promotion is getting applied for both.
eg: we want one line item to get applied "buy 10 items and get 10%" and others to "buy 20 items and get a 20% offer".
If it is a single line item applicable for promotion, it works fine!. (We are using Commerce 12.5.1)

Thanks for the reply.
We achieved the solution by using affected entries.
////To check applied discount entries.
var isPromotionApplied = context.EntryPrices.Prices.Count() > 0 ? true : false;
if (isPromotionApplied)
{
////To filter the item if it have already discount.
var codesAlreadyDiscounted = context.EntryPrices.Prices.Select(x => x.ParentItem.Code);
lineItems = lineItems.Where(x => !codesAlreadyDiscounted.Contains(x.Code));
}

Related

Best way to get an element using its id from an array in Angular [duplicate]

I've got an array:
myArray = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}, etc.]
I'm unable to change the structure of the array. I'm being passed an id of 45, and I want to get 'bar' for that object in the array.
How do I do this in JavaScript or using jQuery?
Use the find() method:
myArray.find(x => x.id === '45').foo;
From MDN:
The find() method returns the first value in the array, if an element in the array satisfies the provided testing function. Otherwise undefined is returned.
If you want to find its index instead, use findIndex():
myArray.findIndex(x => x.id === '45');
From MDN:
The findIndex() method returns the index of the first element in the array that satisfies the provided testing function. Otherwise -1 is returned.
If you want to get an array of matching elements, use the filter() method instead:
myArray.filter(x => x.id === '45');
This will return an array of objects. If you want to get an array of foo properties, you can do this with the map() method:
myArray.filter(x => x.id === '45').map(x => x.foo);
Side note: methods like find() or filter(), and arrow functions are not supported by older browsers (like IE), so if you want to support these browsers, you should transpile your code using Babel (with the polyfill).
As you are already using jQuery, you can use the grep function which is intended for searching an array:
var result = $.grep(myArray, function(e){ return e.id == id; });
The result is an array with the items found. If you know that the object is always there and that it only occurs once, you can just use result[0].foo to get the value. Otherwise you should check the length of the resulting array. Example:
if (result.length === 0) {
// no result found
} else if (result.length === 1) {
// property found, access the foo property using result[0].foo
} else {
// multiple items found
}
Another solution is to create a lookup object:
var lookup = {};
for (var i = 0, len = array.length; i < len; i++) {
lookup[array[i].id] = array[i];
}
... now you can use lookup[id]...
This is especially interesting if you need to do many lookups.
This won't need much more memory since the IDs and objects will be shared.
ECMAScript 2015 (JavaScript ES6) provides the find()
method on arrays:
var myArray = [
{id:1, name:"bob"},
{id:2, name:"dan"},
{id:3, name:"barb"},
]
// grab the Array item which matchs the id "2"
var item = myArray.find(item => item.id === 2);
// print
console.log(item.name);
It works without external libraries. But if you want older browser support you might want to include this polyfill.
Underscore.js has a nice method for that:
myArray = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'},etc.]
obj = _.find(myArray, function(obj) { return obj.id == '45' })
I think the easiest way would be the following, but it won't work on Internet Explorer 8 (or earlier):
var result = myArray.filter(function(v) {
return v.id === '45'; // Filter out the appropriate one
})[0].foo; // Get result and access the foo property
Try the following
function findById(source, id) {
for (var i = 0; i < source.length; i++) {
if (source[i].id === id) {
return source[i];
}
}
throw "Couldn't find object with id: " + id;
}
myArray.filter(function(a){ return a.id == some_id_you_want })[0]
A generic and more flexible version of the findById function above:
// array = [{key:value},{key:value}]
function objectFindByKey(array, key, value) {
for (var i = 0; i < array.length; i++) {
if (array[i][key] === value) {
return array[i];
}
}
return null;
}
var array = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}];
var result_obj = objectFindByKey(array, 'id', '45');
Performance
Today 2020.06.20 I perform test on MacOs High Sierra on Chrome 81.0, Firefox 77.0 and Safari 13.1 for chosen solutions.
Conclusions for solutions which use precalculations
Solutions with precalculations (K,L) are (much much) faster than other solutions and will not be compared with them - probably they are use some special build-in browser optimisations
surprisingly on Chrome and Safari solution based on Map (K) are much faster than solution based on object {} (L)
surprisingly on Safari for small arrays solution based on object {} (L) is slower than traditional for (E)
surprisingly on Firefox for small arrays solution based on Map (K) is slower than traditional for (E)
Conclusions when searched objects ALWAYS exists
solution which use traditional for (E) is fastest for small arrays and fast for big arrays
solution using cache (J) is fastest for big arrays - surprisingly for small arrays is medium fast
solutions based on find (A) and findIndex (B) are fast for small arras and medium fast on big arrays
solution based on $.map (H) is slowest on small arrays
solution based on reduce (D) is slowest on big arrays
Conclusions when searched objects NEVER exists
solution based on traditional for (E) is fastest on small and big arrays (except Chrome-small arrays where it is second fast)
solution based on reduce (D) is slowest on big arrays
solution which use cache (J) is medium fast but can be speed up if we save in cache also keys which have null values (which was not done here because we want to avoid unlimited memory consumption in cache in case when many not existing keys will be searched)
Details
For solutions
without precalculations: A
B
C
D
E
F
G
H
I
J (the J solution use 'inner' cache and it speed depend on how often searched elements will repeat)
with precalculations
K
L
I perform four tests. In tests I want to find 5 objects in 10 loop iterations (the objects ID not change during iterations) - so I call tested method 50 times but only first 5 times have unique id values:
small array (10 elements) and searched object ALWAYS exists - you can perform it HERE
big array (10k elements) and searched object ALWAYS exist - you can perform it HERE
small array (10 elements) and searched object NEVER exists - you can perform it HERE
big array (10k elements) and searched object NEVER exists - you can perform it HERE
Tested codes are presented below
function A(arr, id) {
return arr.find(o=> o.id==id);
}
function B(arr, id) {
let idx= arr.findIndex(o=> o.id==id);
return arr[idx];
}
function C(arr, id) {
return arr.filter(o=> o.id==id)[0];
}
function D(arr, id) {
return arr.reduce((a, b) => (a.id==id && a) || (b.id == id && b));
}
function E(arr, id) {
for (var i = 0; i < arr.length; i++) if (arr[i].id==id) return arr[i];
return null;
}
function F(arr, id) {
var retObj ={};
$.each(arr, (index, obj) => {
if (obj.id == id) {
retObj = obj;
return false;
}
});
return retObj;
}
function G(arr, id) {
return $.grep(arr, e=> e.id == id )[0];
}
function H(arr, id) {
return $.map(myArray, function(val) {
return val.id == id ? val : null;
})[0];
}
function I(arr, id) {
return _.find(arr, o => o.id==id);
}
let J = (()=>{
let cache = new Map();
return function J(arr,id,el=null) {
return cache.get(id) || (el=arr.find(o=> o.id==id), cache.set(id,el), el);
}
})();
function K(arr, id) {
return mapK.get(id)
}
function L(arr, id) {
return mapL[id];
}
// -------------
// TEST
// -------------
console.log('Find id=5');
myArray = [...Array(10)].map((x,i)=> ({'id':`${i}`, 'foo':`bar_${i}`}));
const mapK = new Map( myArray.map(el => [el.id, el]) );
const mapL = {}; myArray.forEach(el => mapL[el.id]=el);
[A,B,C,D,E,F,G,H,I,J,K,L].forEach(f=> console.log(`${f.name}: ${JSON.stringify(f(myArray, '5'))}`));
console.log('Whole array',JSON.stringify(myArray));
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
This snippet only presents tested codes
Example tests results for Chrome for small array where searched objects always exists
As others have pointed out, .find() is the way to go when looking for one object within your array. However, if your object cannot be found using this method, your program will crash:
const myArray = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}];
const res = myArray.find(x => x.id === '100').foo; // Uh oh!
/*
Error:
"Uncaught TypeError: Cannot read property 'foo' of undefined"
or in newer chrome versions:
Uncaught TypeError: Cannot read properties of undefined (reading 'foo')
*/
This can be fixed by checking whether the result of .find() is defined before using .foo on it. Modern JS allows us to do this easily with optional chaining, returning undefined if the object cannot be found, rather than crashing your code:
const myArray = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}];
const res = myArray.find(x => x.id === '100')?.foo; // No error!
console.log(res); // undefined when the object cannot be found
If you do this multiple times, you may set up a Map (ES6):
const map = new Map( myArray.map(el => [el.id, el]) );
Then you can simply do a O(1) lookup:
map.get(27).foo
You can get this easily using the map() function:
myArray = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}];
var found = $.map(myArray, function(val) {
return val.id == 45 ? val.foo : null;
});
//found[0] == "bar";
Working example: http://jsfiddle.net/hunter/Pxaua/
Using native Array.reduce
var array = [ {'id':'73' ,'foo':'bar'} , {'id':'45' ,'foo':'bar'} , ];
var id = 73;
var found = array.reduce(function(a, b){
return (a.id==id && a) || (b.id == id && b)
});
returns the object element if found, otherwise false
You can use filters,
function getById(id, myArray) {
return myArray.filter(function(obj) {
if(obj.id == id) {
return obj
}
})[0]
}
get_my_obj = getById(73, myArray);
While there are many correct answers here, many of them do not address the fact that this is an unnecessarily expensive operation if done more than once. In an extreme case this could be the cause of real performance problems.
In the real world, if you are processing a lot of items and performance is a concern it's much faster to initially build a lookup:
var items = [{'id':'73','foo':'bar'},{'id':'45','foo':'bar'}];
var lookup = items.reduce((o,i)=>o[i.id]=o,{});
you can then get at items in fixed time like this :
var bar = o[id];
You might also consider using a Map instead of an object as the lookup: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Map
Recently, I have to face the same thing in which I need to search the string from a huge array.
After some search I found It'll be easy to handle with simple code:
Code:
var items = mydata.filter(function(item){
return item.word.toLowerCase().startsWith( 'gk );
})
See https://jsfiddle.net/maheshwaghmare/cfx3p40v/4/
Iterate over any item in the array. For every item you visit, check that item's id. If it's a match, return it.
If you just want teh codez:
function getId(array, id) {
for (var i = 0, len = array.length; i < len; i++) {
if (array[i].id === id) {
return array[i];
}
}
return null; // Nothing found
}
And the same thing using ECMAScript 5's Array methods:
function getId(array, id) {
var obj = array.filter(function (val) {
return val.id === id;
});
// Filter returns an array, and we just want the matching item.
return obj[0];
}
You may try out Sugarjs from http://sugarjs.com/.
It has a very sweet method on Arrays, .find. So you can find an element like this:
array.find( {id: 75} );
You may also pass an object with more properties to it to add another "where-clause".
Note that Sugarjs extends native objects, and some people consider this very evil...
As long as the browser supports ECMA-262, 5th edition (December 2009), this should work, almost one-liner:
var bFound = myArray.some(function (obj) {
return obj.id === 45;
});
Here's how I'd go about it in pure JavaScript, in the most minimal manner I can think of that works in ECMAScript 3 or later. It returns as soon as a match is found.
var getKeyValueById = function(array, key, id) {
var testArray = array.slice(), test;
while(test = testArray.pop()) {
if (test.id === id) {
return test[key];
}
}
// return undefined if no matching id is found in array
return;
}
var myArray = [{'id':'73', 'foo':'bar'}, {'id':'45', 'foo':'bar'}]
var result = getKeyValueById(myArray, 'foo', '45');
// result is 'bar', obtained from object with id of '45'
More generic and short
function findFromArray(array,key,value) {
return array.filter(function (element) {
return element[key] == value;
}).shift();
}
in your case Ex. var element = findFromArray(myArray,'id',45) that will give you the whole element.
We can use Jquery methods $.each()/$.grep()
var data= [];
$.each(array,function(i){if(n !== 5 && i > 4){data.push(item)}}
or
var data = $.grep(array, function( n, i ) {
return ( n !== 5 && i > 4 );
});
use ES6 syntax:
Array.find, Array.filter, Array.forEach, Array.map
Or use Lodash https://lodash.com/docs/4.17.10#filter, Underscore https://underscorejs.org/#filter
Building on the accepted answer:
jQuery:
var foo = $.grep(myArray, function(e){ return e.id === foo_id})
myArray.pop(foo)
Or CoffeeScript:
foo = $.grep myArray, (e) -> e.id == foo_id
myArray.pop foo
Use Array.prototype.filter() function.
DEMO: https://jsfiddle.net/sumitridhal/r0cz0w5o/4/
JSON
var jsonObj =[
{
"name": "Me",
"info": {
"age": "15",
"favColor": "Green",
"pets": true
}
},
{
"name": "Alex",
"info": {
"age": "16",
"favColor": "orange",
"pets": false
}
},
{
"name": "Kyle",
"info": {
"age": "15",
"favColor": "Blue",
"pets": false
}
}
];
FILTER
var getPerson = function(name){
return jsonObj.filter(function(obj) {
return obj.name === name;
});
}
You can do this even in pure JavaScript by using the in built "filter" function for arrays:
Array.prototype.filterObjects = function(key, value) {
return this.filter(function(x) { return x[key] === value; })
}
So now simply pass "id" in place of key and "45" in place of value, and you will get the full object matching an id of 45. So that would be,
myArr.filterObjects("id", "45");
I really liked the answer provided by Aaron Digulla but needed to keep my array of objects so I could iterate through it later. So I modified it to
var indexer = {};
for (var i = 0; i < array.length; i++) {
indexer[array[i].id] = parseInt(i);
}
//Then you can access object properties in your array using
array[indexer[id]].property
Use:
var retObj ={};
$.each(ArrayOfObjects, function (index, obj) {
if (obj.id === '5') { // id.toString() if it is int
retObj = obj;
return false;
}
});
return retObj;
It should return an object by id.
This solution may helpful as well:
Array.prototype.grep = function (key, value) {
var that = this, ret = [];
this.forEach(function (elem, index) {
if (elem[key] === value) {
ret.push(that[index]);
}
});
return ret.length < 2 ? ret[0] : ret;
};
var bar = myArray.grep("id","45");
I made it just like $.grep and if one object is find out, function will return the object, rather than an array.
Dynamic cached find
In this solution, when we search for some object, we save it in cache. This is middle point between "always search solutions" and "create hash-map for each object in precalculations".
let cachedFind = (()=>{
let cache = new Map();
return (arr,id,el=null) =>
cache.get(id) || (el=arr.find(o=> o.id==id), cache.set(id,el), el);
})();
// ---------
// TEST
// ---------
let myArray = [...Array(100000)].map((x,i)=> ({'id':`${i}`, 'foo':`bar_${i}`}));
// example usage
console.log( cachedFind(myArray,'1234').foo );
// Benchmark
let bench = (id) => {
console.time ('time for '+id );
console.log ( cachedFind(myArray,id).foo ); // FIND
console.timeEnd('time for '+id );
}
console.log('----- no cached -----');
bench(50000);
bench(79980);
bench(99990);
console.log('----- cached -----');
bench(79980); // cached
bench(99990); // cached

material-table: How to make a summary row?

How can I make a summary row like this using a material table?
Please help me, thank you.
If by "Summary row" you're referring to table title, that's a prop "title" you just add to the <MaterialTable /> component.
However, I suspect you need the row with Total result, which I couldn't find in the examples, either.
Here's a custom function you could use to calculate a total by your own, add it to your data set and achieve similar result:
const addTotal = (data, byColumn) => {
let keys = Object.keys(data[0]);
let total = data.reduce((acc, el) => {
return acc += +(el[byColumn]);
}, 0);
let totalRow = {};
let emptyRow = {};
for (let key of keys) {
if (key === keys[0]) {
totalRow[key] = 'Total';
} else if (key === byColumn) {
totalRow[key] = total;
} else {
totalRow[key] = '';
}
emptyRow[key] = '';
}
return [...data, emptyRow, totalRow];
}
This will add an empty row and a total with the argument you put as byColumn. You need to be careful about the values you are summing (i.e. add type checking or validate the column name with hasOwnProperty).
I might be a little late, but here’s another question that reminded me that material table happen to have a footer row api.
You can use it alongside a specific method to sum values (or else) that you can call before setting the datasource for example, or on demand.

Angular table sorting

I need to sort table items by clicking table header. My column values number as string. So sorting is not applying as I wish. I know I should convert my string into float. I need comma separator and double point precision as well. I tried to do parseFloat but that's not working.
Example. I want to sort "123,456.00", "345", "-34.00", "7.78" kind of strings. I shared jsfiddle link to explain my scenario.
http://jsfiddle.net/k4seL1yx/1/
function SortableTableCtrl() {
var scope = this;
scope.head = {
a: "Poured",
b: "Sold",
c: "Loss"
};
scope.body = [{"ID":"September-2016", "Product":"September-2016", "Poured":"111759.07", "Sold":"107660.97", "Loss":"-4098.10", "Variance":"-3.67", "startDate":"2016-09-01", "endDate":"2016-09-22"}, {"ID":"November-2015", "Product":"November-2015", "Poured":"53690.25", "Sold":"52953.60", "Loss":"-736.65", "Variance":"-1.37", "startDate":"2015-11-20", "endDate":"2015-11-30"}, {"ID":"May-2016", "Product":"May-2016", "Poured":"156401.65", "Sold":"151192.51", "Loss":"-5209.14", "Variance":"-3.33", "startDate":"2016-05-03", "endDate":"2016-05-31"}, {"ID":"March-2016", "Product":"March-2016", "Poured":"49260.22", "Sold":"49399.14", "Loss":"138.92", "Variance":"0.28", "startDate":"2016-03-01", "endDate":"2016-03-09"}, {"ID":"June-2016", "Product":"June-2016", "Poured":"162126.88", "Sold":"161718.62", "Loss":"-408.26", "Variance":"-0.25", "startDate":"2016-06-01", "endDate":"2016-06-30"}, {"ID":"July-2016", "Product":"July-2016", "Poured":"160185.68", "Sold":"154882.40", "Loss":"-5303.28", "Variance":"-3.31", "startDate":"2016-07-01", "endDate":"2016-07-31"}, {"ID":"January-2016", "Product":"January-2016", "Poured":"355509.26", "Sold":"179696.72", "Loss":"-175812.54", "Variance":"-49.45", "startDate":"2016-01-01", "endDate":"2016-01-31"}, {"ID":"February-2016", "Product":"February-2016", "Poured":"150980.73", "Sold":"146248.72", "Loss":"-4732.01", "Variance":"-3.13", "startDate":"2016-02-01", "endDate":"2016-02-29"}, {"ID":"December-2015", "Product":"December-2015", "Poured":"167843.42", "Sold":"163732.95", "Loss":"-4110.47", "Variance":"-2.45", "startDate":"2015-12-01", "endDate":"2015-12-31"}, {"ID":"August-2016", "Product":"August-2016", "Poured":"168853.51", "Sold":"160024.84", "Loss":"-8828.67", "Variance":"-5.23", "startDate":"2016-08-01", "endDate":"2016-08-31"}]
scope.sort = {
column: 'b',
descending: false
};
scope.selectedCls = function(column) {
return column == scope.sort.column && 'sort-' + scope.sort.descending;
};
scope.changeSorting = function(column) {
var sort = scope.sort;
if (sort.column == column) {
sort.descending = !sort.descending;
} else {
sort.column = column;
sort.descending = false;
}
};
}
I found a solution for my question. Actually I am very focused on convert "poured, sold, loss" from string to float. But why I should not create a new column as clone of this and the format of that is float. parseFloat done the magic. So I can use Poured value for table show and use pouredClone value for sorting on the behind.
//Initially I got the above scope.body only. To perform sorting I modified scope.body as like below
scope.body.forEach(function(value) {
value.pouredClone = parseFloat(value.Poured)
value.soldClone = parseFloat(value.Sold)
value.lossClone = parseFloat(value.Loss)
value.varianceClone = parseFloat(value.Variance)
});
Here is the link of fiddle with solution.
http://jsfiddle.net/q7mcu6fx/1/

AngularJS filter causes infinite $digest loop exception

The problem I am trying to describe here is quite complex and I'm aware that my English is far from perfect, so if anyone will have enough patience to read this through I'll be very grateful.
Ad rem: I have a custom directive (called metadata) in which I use "angularjs-dropdown-multiselect" module:http://dotansimha.github.io/angularjs-dropdown-multiselect/ to provide a multiple selection field. It works fine, but here's the problem: the directive is called several times (as I need several fields of that type) and some of these fields are interdependent. For example, I have two fields: 'Living place' and 'Person'. When the user selects one or more living places in the multi-select field, some of the 'Person' field options should be filtered out to leave only people that live in the selected places. I can't make it right: no matter what I try, the filtering process triggers an 'infinite $digest loop' exception.
The list of the possible choices for each field is extracted from the json data file (called meta_data.json), which has the following format:
[{"person": "JeJu1929", "living-place": "Zabrid'"},
{"person": "VM1996", "living-place": "Velykyj Bereznyj"},
{"person": "VB1957", "living-place": "Zabrid'"}]
So, when the user chooses 'Zabrid', the options list of the 'Person' field should contain two entries: 'JeJu1929' and 'VB1957'.
The choices user made are stored in the separate factory function (called queryKeeper) as well as the data from the file mentioned above.
The architecture of the whole process is as follows:
The factory function reads the json file (meta_data.json) containing data and stores it in a list of objects (metaValues)
In a directive controller (invoked for, say, 'Living place' field) the data object (metaField) is read and a list of all possible options is obtained
angularjs-dropdown-multiselect requires that the options list consists objects in the format {id: value, label: option-name}, so in the process of obtaining the options list, each option is converted from the string type to the required object format, so - for the 'Living place' field - I end up with the options list as follows:
[{id: 'Zabrid', label: 'Zabrid'}, {id: 'Velykyj Bereznyj', label: 'Velykyj Bereznyj'}]
When the user chooses some options in the field, this information goes to the factory function (and is stored in the variable metaFields)
Let's say, that we want to filter out the 'Person' field options according to the choices user made in the 'Living place' field. The filter function gets these choices (from the metaFields variable), the data object (metaValues) and a list of options to be narrowed down (which contains in our example three options: "JeJu1929", "VM1996", "VB1957"). If the user chose in the 'Living place' field an option 'Zabrid', the filter function throws out the person "VM1996" as its living place is not 'Zabrid'.
To this point, everything works fine. But when the new options list (which is a subset of the old list) is returned by the filter, the $digest loop fires off again and again and finally the 'infinite $digest loop' exception is thrown. I thought that the problem is in comparison of objects (that even when the objects are in fact identical, angular still thinks that they are different), but this is rather not the case: when filter function is called for a 'Person' field options, it gets as parameter, say, four options, and returns, say, two. Then the filter is called for other fields (like 'Living place' etc.) and once again for the 'Person' - with the same parameter (i.e. four options, not two) just like for the first time. This is confusing and I'm stuck.
The relevant parts of the code:
file: metadata.html (template)
<div>
<div ng-if="meta.type == 'select'" ng-dropdown-multiselect="" options="values|matchValues:meta.name" selected-model="model" extra-settings="multiSettings" translation-texts="hint" checkboxes="true">
</div>
file: metadata.js (my custom directive)
corpus.directive('metadata', ['queryKeeper', 'gettextCatalog', function(queryKeeper, gettextcatalog) {
return {
templateUrl: 'jsapp/languageQuery/metadata/metadata.html',
restrict: 'E',
scope: {
index: '=',
place: '='
},
controller: function($scope, gettextCatalog) {
// function obtains the list of all possible options for the field
getValues = function () {
values = [];
metadata = queryKeeper.getMetaValues ();
name = $scope.meta.name.slice (5);
for (i = 0; i < metadata.length; ++i)
if (typeof metadata[i][name] != 'unknown')
{
var tmp = angular.copy (metadata)[i];
tmp['name'] = name;
tmp['id'] = metadata[i][name];
tmp['label'] = tmp[name];
values.push (tmp);
}
values = values.slice().sort(function(a,b){return a[name] > b[name]}).reduce(function(a,b){if (a.length == 0 || a.slice(-1)[0][name] !== b[name]) a.push(b);return a;},[]);
return values;
}
$scope.meta = queryKeeper.getMeta($scope.index, $scope.place); // provides access to the field data, like field name, choices made etc.
$scope.values = getValues (); // actual options list (to be filtered)
$scope.model = []; // stores the user choices
$scope.multiSettings = {showCheckAll: false, buttonClasses: 'btn btn-default meta', dynamicTitle: false, scrollable: true, scrollableHeight: '150px'}; // angularjs-dropdown-multiselect settings
$scope.hint = {buttonDefaultText: $scope.meta.hint};
// watch function that sends the user choices to the factory function
$scope.$watchCollection ('model', function (newValue) {
queryKeeper.setMetaMulti ($scope.index, $scope.place, newValue);
});
}
};
}]);
file: corpus.js (main module)
corpus.factory('queryKeeper', ['loadLanguages', 'loadMetaData', function(loadLanguages, loadMetaData) {
var metaFields = [];
var metaValues = [];
loadMetaData.getMetaFile('settings/meta_data.json').then(function(response) {
metaValues = angular.fromJson(response);});
return {
getMeta: function(i, place) {
return metaFields[i * 3 + place];
},
setMetaMulti: function (i, place, item) {
metaFields[i * 3 + place].multiValue = [];
for (j = 0; j < item.length; ++j)
{
metaFields[i * 3 + place].multiValue.push (item[j].id);
}
return item;
},
}
}]);
corpus.filter ('matchValues', ['queryKeeper', function (queryKeeper) {
return function (values, name) {
metaSet = queryKeeper.getMetaAll ();
var tag = '';
if (name == 'meta_living-place')
tag = 'variety';
else if (name == 'meta_person')
tag = 'living-place';
else
return values;
console.log ('init:', name, values);
metadata = queryKeeper.getMetaValues ();
newValues = [];
var found = [];
for (i = 0; i < metaSet.length; ++i) {
if (metaSet[i].name == 'meta_' + tag) {
found = metaSet[i].multiValue;
break;
}
}
if (typeof found == 'undefined' || found.length == 0)
return values;
for (j = 0; j < values.length; ++j) {
for (i = 0; i < found.length; ++i)
if (found[i] == values[j][tag])
newValues.push (values[j]);
}
console.log ('new:', name, newValues);
return newValues;
}
}]);
UPDATE
It took me a while, but I've created JSFiddle: https://jsfiddle.net/HB7LU/17604/. To my surprise, in my code was another issue I wasn't aware of: in my module I used 'ngAnimate' module, which prevented filterning of the list of choices. When I removed this dependency, the list began to behave correctly. I'm curious why. Still, the infinite $digest loop problem remains.

How to remove the summary grouping of particular row in EXTJS

If I apply groupField property in the grid and store, extjs will automatically sort records by groupField.
For example: http://dev.sencha.com/deploy/ext-4.0.0/examples/grid/group-summary-grid.html, it used 'project' as groupField.
My Question is How can I add an extra row, which does not belong to any group, into a grid with groupField. (Like this: http://i59.tinypic.com/30s973l.jpg)
By default group is made for each value from groupField, also for empty groupField (but it appears first). If it is enough for you then you don't have to do anything.
Fiddle: http://jsfiddle.net/tme462tj/
When you want to have behavior like on attached image, then some work has to be done.
To put empty value at the end, you can create convert function for model field (eg: convert: function(v) { return v ? v : defaultDepartment; }). If you want to hide header and summary for this empty group, you can attach event listener to viewready event and hide them using styles:
viewready: function() {
var els = this.getEl().query('.x-grid-group-hd');
els = Ext.Array.filter(els, function(e) { return e.textContent.trim() == defaultDepartment; });
if (els.length !== 1) {
return;
}
var header = Ext.fly(els[0]);
header.setStyle('display', 'none');
var summary = header.up('.x-group-hd-container').down('.x-grid-row-summary');
if (summary) {
summary.setStyle('display', 'none');
}
}
Fiddle: http://jsfiddle.net/hdyokgnx/1/
As per my comment on the accepted solution, this revision makes the grouping header hide permanently for the duration of that grid view.
This solves my problem of needing a grouping grid but with one group that does not have a group header row.
store.load
({
params :
{
arg1 : 1
,arg2 : '2'
}
,callback : function( records, operation, success )
{
var els = me.theGrid.getEl().query('.x-grid-group-hd');
els = Ext.Array.filter( els, function(e)
{
return e.textContent.trim().includes( "*** A SUBSTRING IN GROUPING FIELD ***" );
});
if (els.length !== 1)
{
return;
}
var header = Ext.fly(els[0]);
header.setStyle('display', 'none');
var summary = header.up('.x-group-hd-container').down('.x-grid-row-summary');
if (summary)
{
summary.setStyle('display', 'none');
}
}
});

Resources