Custom column filter on a formatted column - slickgrid

I need help using a custom column filter for handling JS objects.
I have a slickgrid table where the values in one column are JS object:
[
{ id: "1234", text: "Batman"},
{ id: "2345", text: "Robin"}
]
I use a custom formatter to smash the object into a string:
// convert [{id:string, text:string}...] to string
const optionFormatter: Formatter = (row, cell, value, columnDef, dataContext: any) =>
value ? value.map(o => o.text).join(', ') : '';
Which displays in slickgrid as
Batman, Robin
my slickgrid options use gridmenu and enables column filtering:
this.gridOptions = {
enableGridMenu: true,
enableFiltering: true,
enableAutoResize: true,
enableColumnReorder: true
};
My columnDef enables filtering for this column:
{
id: 'owners',
name: 'Owners',
field: 'owners',
sortable: false,
formatter: optionFormatter,
filterable: true
}
Everything works if the value in the cell is a string, but the filter doesn't work if the cell is an object. I assume the filter is searching the pre-formatted value.
Is there a way to provide the column with a custom filter function that knows how to search the JS object for the query string? For example if I could just search the JSON.stringify(value), that would be good enough.
Alternatively, this answer describes how I could use the formatter to store the formatted text as a different string property in dataContext. If I do that, how do I specify which property to filter, seeing as it is a different property than the column field?

I found a workaround.
preprocess my data, calling JSON.stringify on all values that are objects:
flattenFeature(f: Feature): any{
var res = {};
for (var prop in f) {
res[prop] = (typeof f[prop] === 'object') ? JSON.stringify(f[prop]) : f[prop];
}
return res;
}
Then in my formatter, I parse the json, before formatting:
// convert [{id:string, text:string}...] to string
const optionFormatter: Formatter = (row, cell, value, columnDef, dataContext) =>
value ? JSON.parse(value).map(o => o.text).join(', ') : '';
This allows the standard string filter to search the stringify'd JSON

Related

Alter the text displayed for a token in ace-editor

I have this example where I am locating a matching string via regex and changing the styles using highlight rules.
this.$rules = {
start: [{
token: 'variableRef',
regex: /\$variable\..+\$/
}]
};
and alter the color using a css class:
.ace_variableRef {
color: red;
}
But what I would really like to do is change the text that is being displayed from $variable.1.name$ to the "resolved value". I have access to:
var variables = {
1: 'timeout'
};
so I can use the reference path to get the value, but is it even possible to do this with ace-editor?
Ideally I would display the string in the user friendly way, but keep the original reference value handy (in metadata or something) since that is what is actually stored in the db.
You can accomplish this by defining a custom onMatch for your rule, like so
this.$rules = {
start: [{
onMatch: function(value, state, stack) {
var values = this.splitRegex.exec(value);
return [{
type: 'variableRef',
value: variables[values[1]]
}]
},
regex: /\$variable\.(\d+).+\$/
}]
};
but the actual text will remain unaltered (thus causing oddities with text selection/cursor), so you'll need to pad/clip the resulting value for it to match length of values[0]

JqGrid custom formatter with custom parameter

I have a question about custom formatters.
What I try to achieve is a currencyFormatter just for the amount with Locale sent by the server, when locale is not define or supported fall back to British English.
Something like this:
function currencyFmatter(cellvalue, options, rowdata) {
return new Intl.NumberFormat([locale, "en-GB"], {minimumFractionDigits: 2, maximumFractionDigits: 2}).format(cellvalue);
}
My problem is how to pass my variable locale to the formatter, I’m pretty sure it has to be a way to do it but right now I don’t see it.
Thanks
It's an interesting question! There are many ways to implement your requirements.
1) you can extend your input data returned from the server with additional information which specify the locale of data. For example you can returns "de-DE:10.000,04" instead of "10.000,04" which represent 1000.04 formatted in German locale (where , will be used as the decimal separator and . used as the thousands separator). It allows you to use cellvalue.split(":") to get array ["de-DE", "10.000,04"] with the locale of the number and the number itself
function currencyFmatter(cellvalue, options, rowdata) {
var data;
if (typeof cellvalue === "string") {
data = cellvalue.cellvalue.split(":");
if (data.length === 2) {
return new Intl.NumberFormat([data[0], "en-GB"], {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(data[1]);
}
}
return cellvalue;
}
Alternatively you can place the information about locale of the number in separate field (for example numLocale) of the input data and use something like rowdata.numLocale (or rowdata[12] depend on the input format of the JSON data) to access the locale.
2) It could be that all the data returned from the server will be in the same format. In the case it would be not the best way to prepend all numbers with the same prefix "de-DE:". What you can do for example is to extend the data returned from the server with additional field. For example you can use
{
"total": "12",
"page": "1",
"records": "12345",
"localOfNumbers": "de-DE",
"rows" : [
...
]
}
You can access the custom localOfNumbers field inside of beforeProcessing callback. It's very practical callback. It allows you to pre-process the data returned from the server before the data will be processed by jqGrid. I recommend you to read the answer and this one for more code example. What you can do for example is to save localOfNumbers value in some new option of jqGrid (see the answer for more details). Let us you want to have an option gridLocale for the goal. Then you can do something like the following:
beforeProcessing: function (data) {
if (typeof data.localOfNumbers === "string") {
$(this).jqGrid("setGridParam", {gridLocale: data.localOfNumbers});
}
}
To access the new gridLocale option you can use
function currencyFmatter(cellvalue, options, rowdata) {
var locale = $(this).jqGrid("getGridParam", "gridLocale"); // this.p.gridLocale
}
3) You can consider to save the information about the locale as column property instead of usage one common gridLocale grid option. To do this you can define the column in colModel like below
{ name: 'col2', width: 200, formatoptions: { colLocale: "en-IN" },
formatter: function (cellvalue, options, rowdata) {
// options.colModel.formatoptions.colLocale get you the value
}
One can set the property of formatoptions.colLocale inside of beforeProcessing too. You can use
{
"total": "12",
"page": "1",
"records": "12345",
"localOfColumns": {
"col2": "de-DE",
"col5": "en-IN"
},
"rows" : [
...
]
}
and
beforeProcessing: function (data) {
if ($.isPlainObject(data.localOfColumns)) {
if (typeof data.localOfColumns.col2 === "string") {
$(this).jqGrid("setColProp", "col2", {
formatoptions: { colLocale: data.localOfColumns.col2 }
});
}
if (typeof data.localOfColumns.col5 === "string") {
$(this).jqGrid("setColProp", "col5", {
formatoptions: { colLocale: data.localOfColumns.col5 }
});
}
}
}
I'm sure that one can suggest even more ways to implement your requirements.

CKEditor - Use Advanced Content Filter rule to specify values

It seems that some plugins of CKEditor specify values of properties. For example, the left-to-right plugin has the following rule:
{
"styles":null,
"requiredStyles":null,
"classes":null,
"requiredClasses":null,
"attributes":{
"dir":"ltr"
},
"requiredAttributes":{
"dir":true
},
"elements":{
"span":true
},
"featureName":"styles",
"propertiesOnly":false,
"match":null
},
How can I specify values with string format rules?
Something like span[!dir=ltr].
You can't. String format doesn't allow such definition. You can specify span[!dir] so all spans require dir attribute and nothing else. With object definition you can do more, e.g. use functions:
...
'ul, li: true,
'$0': {
match: function( el ) {
return el.name == 'b';
},
propertiesOnly: true,
attributes: 'dir'
}
'$1': {
...
Why do you persist to use string format? You can use objects and store it as JSON.

jqGrid text Sort Order

I have a jqGrid that I set up like this
gridAltMpn.jqGrid({
autowidth: true,
shrinkToFit: true,
datatype : 'local',
data : input,
height : '100',
scrollrows: true,
scrollOffset : '0',
hidegrid : false,
colNames : [ 'P', 'MPN' ],
colModel : [
{ name : 'Col1', width : 30, align:'center' },
{ name : 'Col2', width : 250, sorttype: 'integer'}
],
pager : '#altmpn_pager',
pagerpos : 'left',
scroll: 50,
gridview : true,
caption : 'A useful table title',
emptyRecordText : '<div id="no_data_msg" style="text-align:center"> No Results Found</div>',
hoverrows : true,
onSelectRow: function(id) {
var gsr = gridAltMpn.jqGrid('getGridParam', 'selrow');
if (gsr) {
var rowData = gridAltMpn.jqGrid('getRowData', gsr);
if ($("input[name='optInvInqType']:checked").val() == 'MPN') {
getInvInq("MPN", rowData.MPN);
}
}
},
loadComplete: function() {
gridAltMpn.setSelection(gridAltMpn.getDataIDs()[0], true);
}
});
The data in this grid looks like this
XX 774860A6
774860A8
774860A4
774860A3
774860A10
STARTER, PNEUM,PW4000
When the grid is first loaded that it fine but if the user wants to sort by the second column it ends up like this
774860A10
774860A3
774860A4
XX 774860A6
774860A8
STARTER, PNEUM,PW4000
The 774860A10 should go after the 774860A8 just like in an integer sort. I cannot use an integer sort because these are not integers as there is some alpha characters in there. In other words I want a text entry to sort like an integer. Do I need to use a custom sort routine and then have my Javascript to do a integer like sort? I also don't need this sorted on the first time because my server sorts it by the first column. The user might want it sorted by the second column
You should use a custom function for this type of sorting.
For this, set sort type property of jqgrid as your custom function. As stated in this link, sort type can have the following values.
sorttype:
int/integer - for sorting integer
float/number/currency - for sorting decimal numbers
date - for sorting date
text - for text sorting
function - defines a custom function for sorting. To this function we pass the value to be sorted and it should return a value too.
And the custom function can be something like this: (From this link found in the answer by Oleg for this question.)
colModel: [
{name:'Posn', index:'Posn', width:100, sorttype:
function(cell)
{
//Here you have to apply your own logic
if (cell=='GK') return '0';//returns the sort order
if (cell=='DEF') return '1';
if (cell=='MID') return '2';
if (cell=='STR') return '3';
}
},
By the way, you can set the sortname property of jqgrid to set a column for initial loadtime sorting.

Translating JSON into custom dijit objects

I am looking for an example where JSON constructed from the server side is used to represent objects that are then translated into customized widgets in dojo. The JSON would have to be very specific in its structure, so it would not be a very general solution. Could someone point me to an example of this. It would essentially be the reverse of this
http://docs.dojocampus.org/dojo/formToJson
First of all let me point out that JSON produced by dojo.formToJson() is not enough to recreate the original widgets:
{"field1": "value1", "field2": "value2"}
field1 can be literally anything: a checkbox, a radio button, a select, a text area, a text box, or anything else. You have to be more specific what widgets to use to represent fields. And I am not even touching the whole UI presentation layer: placement, styling, and so on.
But it is possible to a certain degree.
If we want to use Dojo widgets (Dijits), we can leverage the fact that they all are created uniformly:
var myDijit = new dijit.form.DijitName(props, node);
In this line:
dijit.form.DijitName is a dijit's class.
props is a dijit-specific properties.
node is an anchor node where to place this dijit. It is optional, and you don't need to specify it, but at some point you have to insert your dijit manually.
So let's encode this information as a JSON string taking this dijit snippet as an example:
var myDijit = new dijit.form.DropDownSelect({
options: [
{ label: 'foo', value: 'foo', selected: true },
{ label: 'bar', value: 'bar' }
]
}, "myNode");
The corresponding JSON can be something like that:
{
type: "DropDownSelect",
props: {
options: [
{ label: 'foo', value: 'foo', selected: true },
{ label: 'bar', value: 'bar' }
]
},
node: "myNode"
}
And the code to parse it:
function createDijit(json){
if(!json.type){
throw new Error("type is missing!");
}
var cls = dojo.getObject(json.type, false, dijit.form);
if(!cls){
// we couldn't find the type in dijit.form
// dojox widget? custom widget? let's try the global scope
cls = dojo.getObject(json.type, false);
}
if(!cls){
throw new Error("cannot find your widget type!");
}
var myDijit = new cls(json.props, json.node);
return myDijit;
}
That's it. This snippet correctly handles the dot notation in types, and it is smart enough to check the global scope too, so you can use JSON like that for your custom dijits:
{
type: "my.form.Box",
props: {
label: "The answer is:",
value: 42
},
node: "answer"
}
You can treat DOM elements the same way by wrapping dojo.create() function, which unifies the creation of DOM elements:
var myWidget = dojo.create("input", {
type: "text",
value: "42"
}, "myNode", "replace");
Obviously you can specify any placement option, or no placement at all.
Now let's repeat the familiar procedure and create our JSON sample:
{
tag: "input",
props: {
type: "text",
value: 42
},
node: "myNode",
pos: "replace"
}
And the code to parse it is straightforward:
function createNode(json){
if(!json.tag){
throw new Error("tag is missing!");
}
var myNode = dojo.create(json.tag, json.props, json.node, json.pos);
return myNode;
}
You can even categorize JSON items dynamically:
function create(json){
if("tag" in json){
// this is a node definition
return createNode(json);
}
// otherwise it is a dijit definition
return createDijit(json);
}
You can represent your form as an array of JSON snippets we defined earlier and go over it creating your widgets:
function createForm(array){
dojo.forEach(array, create);
}
All functions are trivial and essentially one-liners — just how I like it ;-)
I hope it'll give you something to build on your own custom solution.

Resources