How do I get a more complex sort on a query, I have this query currently:
var store = Ext.create('Rally.data.custom.Store',{
data: changes,
limit: 'Infinity',
pageSize: 5000,
sorters: [
{
property: 'ReleaseScope',
direction: 'ASC'
},
{
property: 'ScheduleState',
direction: 'DESC'
}
]
});
Because the ScheduleState is hydrated I can't sort by the normal numerics, can I define the order using some kind of matcher?
i.e. say I want to show in order [Accepted, Completed, In-Progress, Defined, Backlog]
And, if I wanted to complicate this further and show stories with story points first, something like
All stories with a story point value != 0
Sorted by schedulestate [accepted, completed, in-progress, defined etc..]
stories with no story point value
some other sort here possibly
You can pass a sorterFn rather than a property/direction combo to implement custom sort logic:
sorters: [
{
sorterFn: function(a, b) {
var scheduleStates = ['Accepted', 'Completed', 'In-Progress', 'Defined'],
aState = a.get('ScheduleState'),
aIndex = _.indexOf(scheduleStates, aState),
bState = b.get('ScheduleState'),
bIndex = _.indexOf(scheduleStates, bState);
return a - b;
}
}
]
The above function should sort them based on schedule state descending I think.
Related
I have a model that receives int bitmasks from the backend:
{"user": 7, "group":5, "other":1}
and I now want to show a form with checkboxes like this:
user: [X] read [X] write [X] execute
group: [X] read [ ] write [X] execute
other: [ ] read [ ] write [X] execute
where user can toggle on or off and then the updated bitmask is sent back to the server in a store.sync operation.
I know how to make and align the checkboxes, but ExtJS checkboxes in a form bind to boolean values through a correlation between the checkbox name and the model field name, and not to parts of bitmask.
So I have to convert back and forth between the bitmask int and a bunch of booleans. How would I implement that in a reusable manner?
I think the checkbox group component is a good candidate to render your checkboxes and also to implement the conversion logic.
Here is a reusable component to do the two-way conversion of bitmasks:
Ext.define('Fiddle.Bitmask', {
extend: 'Ext.form.CheckboxGroup',
xtype: 'fiddlebitmask',
isFormField: true,
columns: 3,
items: [{
boxLabel: 'Read',
name: 'read',
inputValue: 1,
excludeForm: true,
uncheckedValue: 0
}, {
boxLabel: 'Write',
name: 'write',
inputValue: 1,
excludeForm: true,
uncheckedValue: 0
}, {
boxLabel: 'Execute',
name: 'exec',
inputValue: 1,
excludeForm: true,
uncheckedValue: 0
}],
getModelData: function () {
let obj = {};
obj[this.name] = this.getValue();
return obj;
},
setValue: function (value) {
if (value) {
var binary = Ext.String.leftPad((value).toString(2), 3, '0');
value = {
read: Number(binary[0]),
write: Number(binary[1]),
exec: Number(binary[2])
};
}
this.callParent([value]);
},
getValue: function () {
var value = this.callParent();
var binary = `${value['read']||0}${value['write']||0}${value['exec']||0}`
return parseInt(binary, 2);
}
});
And the working fiddle: https://fiddle.sencha.com/#view/editor&fiddle/2clg
edit Component completed with getModelData implementation to support usage with form.getValues/form.updateRecord.
What version of Ext are you using? If your version supports ViewModels then I would do the conversion in the ViewModel and bind it to the view.
There is the convert and calculate config on fields as well but they are converting in one way only.
Say my event looks like:
purchase = {
items: ["pickle", "turtle", "lexicon"]
}
How do I count how many events have "pickle"?
Use the equal operator.
var count = new Keen.Query("count", {
event_collection: "purchases",
timeframe: "this_14_days",
filters: [
{
property_name: "items",
operator: "eq",
property_value: "pickle"
}
]
});
From the API reference for filters & their operators:
“Equal to” – Note that if your property’s value is an array, “eq” can be used to filter for values inside that array. For example, eq: 5 will match a value of [5, 6, 7].
I have a basic mongoose model with an attribute instruments which represents an array. Therefore it consists multiple items, which each have the following attributes: name, counter. The document itself has an autogenerated _id of type ObjectID.
Model
var ParticipationSchema = new Schema({
instruments: [{
name: String,
counter: Number
}],
// etc.
},
{
timestamps: true
});
I'd like now to change exactly 1 item within the instruments array, only if it matches the following requirements:
The document id has to equal 58f3c77d789330486ccadf40
The instruments-item's name which should be changed has to equal 'instrument-1'
The instrument-item's counter has to be lower than 3
Query
let someId = '58f3c77d789330486ccadf40';
let instrumentName = 'instrument-1'
let counter = 3;
Participation.update(
{
$and: [
{ _id: mongoose.Types.ObjectId(someId) },
{ 'instruments.name': instrumentName },
{ 'instruments.counter': { $lt: counter } }
]},
{
$set: {
'instruments.$.currentcounter' : counter
}
},
(err, raw) => {
// ERROR HANDLING
}
});
Let's assume I have 3 entries within the instruments-attribute:
"instruments": [
{
"name": "instrument-1",
"counter": 2
},
{
"name": "instrument-1",
"counter": 2
},
{
"name": "instrument-1",
"counter": 2
}
]
Desired behaviour: change the first element's counter attribute to 3, no matter, when running the update code 1 time, do no action when running it more times.
Actual behaviour:
it changes the 1st element's counter attribute to 3 when running it the 1st time
it changes the 2nds element's counter attribute to 3 when running it the 2nd time
it changes the 3rds element's counter attribute to 3 when running it the 3rd time
Although the queries are anded, they don't seem to run element-wise. To resolve the issue, $elemMatch can be used:
Participation.update({
_id: mongoose.Types.ObjectId(someId),
'instruments': {
$elemMatch: {
'name': instrumentName,
'counter': { $lt: counter } }}
},
// etc.
more info:
API reference on $elemMatch
Thanks to #VEERAM's for pointing out that this kind of behaviour is also documented the mongodb homepage.
I'm using angular 1.5.9 and I have a JSON object like this:
var data=[
{"id":1,"name":"object 1", childs:[
{"id":51,"name":"object 51", childs:[]},
]},
{"id":2,"name":"object 2", childs:[
{"id":11,"name":"object 11", childs:[]},
{"id":12,"name":"object 12", childs:[
{"id":13,"name":"object 100", childs:[]},
]},
]},
{"id":3,"name":"object 3", childs:[]},
{"id":1,"name":"object 1", childs:[]}
];
I need to filter this tree so that I get all the elements (branches or leaves whose name contains the filter string and all the parents.
i.e: filtering for "100" will result in
[
{"id":2,"name":"object 2", childs:[
{"id":12,"name":"object 12", childs:[
{"id":13,"name":"object 100", childs:[]},
]},
]},
]
This data will then be rendered in a customized tree directive using ng-repeat over the data itself
I'm wondering if someone can suggest a clean and efficent way to achieve this. All the code I've written seems to be too complex and end up traversing the tree so many times that a better way must exist.
actual metacode is somewhat like
* sequenially read ech JSON object in main array
* if name matches add a property (visible:true) and travel back to the beginning setting all the parents' visible:trre
* if childs array contain something, re-call the main filter function to scan all childrens
This could be somewhat acceptable for small datasets, but on large object will probably be very inefficient.
You can just write some recursive javascript for this, something like:
function findObjectAndParents(item, name) {
if (item.name.split(' ')[1] == name) {
return true;
}
for (var i = 0; i < item.childs.length; i++) {
if (findObjectAndParents(item.childs[i], name)) {
return true;
}
}
return false;
}
And use it like this:
var searchName = "100";
var filtered = data.filter(function(item) {
return findObjectAndParents(item, searchName);
});
Ref the answer:
A Javascript function to filter tree structured json with a search term. exclude any object which donot match the search term
function search(array, name) {
const s = (r, { childs, ...object }) => {
if (object.name.includes(name)) {
r.push({ object, childs: [] });
return r;
}
childs = childs.reduce(s, []);
if (childs.length) r.push({ ...object, childs });
return r;
};
return array.reduce(s, []);
}
console.log(JSON.stringify(search(data, '100'),0,2));
I'm looking for the best way to define own custom type in ExtJs.
I would like to use it in forms, grids, pivots, etc., it should has own rendering, calculating and sorting behaviour.
Consider a type 'pace'. Pace is defined as amount of time needed to move on unit of distance, for example pace 2:30 means you need two and half minute to do 1 mile or km.
Pace could be added (2:30 + 2:35 = 5:05) and be used in other calculations.
The smaller pace is faster, that means pace 2:00 is faster (higher) than 2:30.
So far as I know, this is a way to define own custom type and use it in data model as in coding below:
Ext.define('App.fields.Pace', {
extend: 'Ext.data.field.Field',
alias: 'data.field.pace'
});
Ext.define( 'Data', {
extend: 'Ext.data.Model',
fields: [
{name: 'id'},
{name: 'pace', type: 'pace'}
]
});
Such defined type is a dummy one, it doesn't render, sort or calculate correctly.
Is there a way to extend it, so it will work ok in forms, grids, pivots, etc.?
What should I do to archive it? Should I define or overwrite some methods?
Or perhaps I should take other similar type (for example date) and inherit it or use it as template?
I think as minimum the new custom type should provide a method to convert its value to internal type like int and a method to render this internal value as external format but I haven’t find such methods.
Is it possible to define own type which will work correctly in all scenarios where standard type could be used?
Regards,
Annie
I don't think that calculating with custom types is supported in ExtJS. You can extend any class with any function you wish, to implement custom calculation functions, but I am not sure you can override operators in JavaScript.
That said, let me give you an example of a custom type array:
Ext.define('Ext.data.field.Array',{
extend: 'Ext.data.field.Field',
getType: function() {
return "array"
},
compare: function(o1, o2) {
if (!o1 && !o2) {
return 0
}
if (!o1) {
return -1
}
if (!o2) {
return 1
}
if (o1.length != o2.length) {
return o1.length > o2.length ? -1 : 1
}
for (var i = 0; i < o1.length; i++) {
if (o2.indexOf(o1[i]) == -1) {
return 1;
}
}
return 0
},
convert: function(value) {
if (this.separator && Ext.isString(value)) {
if (value == "") {
value = []
} else {
value = value.split(this.separator)
}
}
if (Ext.isArray(value)) {
if (this.map) {
value = value.map(this.map)
}
return value
}
return []
},
serialize: function(value) {
if (Ext.isArray(value)) {
if (!this.allowBlank) {
value = Ext.Array.filter(value, function(a) {
return a !== ""
})
}
if(this.separator) return value.join(this.separator)
else return value
}
return value
}
});
A typical field definition in a model may then be the following:
{
name: "Names",
type: "array",
allowBlank: false,
separator: ","
}
which would be able to parse a JSON array or a comma-separated string, and serialize into a comma-separated string for submission, or
{
name: "Attachments",
type: "array",
}
which would be able to parse a JSON array and also serialize into an array, or
{
name: "Categories",
type: "array",
separator:',',
map:function(value) {
return Ext.create('MyApp.model.Category',{value:value});
}
}
which does take a comma-separated string and map every part of it into a model, so that it returns an array of models.
This value does sort, serialize and deserialize, but not render. Rendering isn't done by a type, but by the Ext.Component that uses the type, e.g. the gridcolumn or the form field. There's a reason they have a datecolumn and datepickerfield, a numbercolumn and a numberfield (AKA spinner), a checkcolumn and a checkboxfield, just to name a few.
For my array, a suitable field would be the combobox with multi:true, but I need a column with a custom renderer; so let's make an arraycolumn:
Ext.define('MyApp.ux.ArrayColumn',{
extend:'Ext.grid.column.Column',
lines: 6,
renderer:function(value) {
if (Ext.isString(value)) {
value = value.split("\n")
}
var len = value.length;
if (this.lines && len > this.lines) {
value = value.slice(0, lines - 1);
value.push('and {n} more...'.replace('{n}', len - lines + 1))
}
return value.join("<br>");
}
});
Everything without warranty, of course...
It feels like a bit of an anti-pattern as complex types (or those with rules) are generally modelled separately and such entities can be attached to records using one-to-one associations. With your question in mind however, this is the simplest implementation I could come up with.
» Fiddle
Ideally I'd of liked to normalise and store the values as integers; the lowest denomination of "pace" i.e. the total number of seconds. Unfortunately the ExtJS field types don't provide any formatting hooks that work automatically with other parts of the API so string manipulation it has to be.
The following field class includes a custom getValue function which reduces either a numeric or string input to an integer. The other functions override members of the base class and are called automatically by the framework.
convert is called when data is read into the model and is used here to implicitly validate the integrity of the input. sortType is called whenever you use the various sorters on collections and is used here to reduce the string to an unambiguous / easily comparable value.
Ext.define('App.data.field.Pace', {
extend: 'Ext.data.field.Field',
alias: 'data.field.pace',
isPace: true,
getValue: function(v){
var type = typeof v,
isNan = isNaN(v);
if(type === 'number' || !isNan)
return isNan ? 0 : (+v|0);
if(type === 'string'){
var match = v.match(/^(\d+):([0-5]?\d)$/);
if(match)
return (+match[1]) * 60 + (+match[2]);
}
return 0;
},
convert: function(v){
var proto = App.data.field.Pace.prototype,
value = proto.getValue(v),
mins = value / 60 | 0,
secs = value % 60;
return String(mins) +':'+ (secs<10?'0':'') + String(secs);
},
sortType: function(v){
var proto = App.data.field.Pace.prototype;
return proto.getValue(v);
}
});
Note that the all functions are deliberately accessed via the class prototype instead of from the this keyword - while playing around it looked as if the framework - at various points - likes to call field functions independently of any (if any) instantiated class, so it's advisable not to rely on the bound context.
In order to address the addition / math on field values you can utilise the calculate configuration - though as this is specific to each use-case it should reside within the model that's actually using the custom field type. For example:
Ext.define('App.data.Model', {
extend: 'Ext.data.Model',
fields: [
{
name: 'name',
type: 'string'
},
{
name: 'paceOne',
type: 'pace'
},
{
name: 'paceTwo',
type: 'pace'
},
{
name: 'paceTotal',
type: 'pace',
calculate: function(data){
var proto = App.data.field.Pace.prototype;
return proto.convert(
proto.getValue(data.paceOne) +
proto.getValue(data.paceTwo)
);
}
}
]
});