Create object from Backbone collection that maps model values to one another - javascript

We are using Backgrid which allows you to define grid columns with an array of Javascript objects which it converts to a collection. We are trying to take advantage of this to have configurable validation on a column by column basis, so we might have the following where we've added a "validator" function to a couple of the columns:
[
{
label: "Delete",
name: "delete",
cell: "boolean"
},
{
label: "Alias",
name: "alias",
cell: "string",
sortType: "toggle",
validator: function (value) {
return true;
}
},
{
label: "Assigned Server",
name: "assignedServer",
cell: "string",
sortType: "toggle",
validator: function (value) {
return new Error("Cannot Assign Server")
}
}
]
We are listening to edits to the grid in the following prescribed manner and for the purposes of this question we can ignore the model argument to the function but concentrate on the column (delete, alias or assignedServer from above) which is itself a model in a collection. So far I have a snippet of code leveraging underscore.js's _.filter that returns the validatableColumns but I want to take this further and end up with an object of the format {name: validator, etc...}. Bearing in mind my specific use case, what is a succinct way to create an object from a Backbone collection that maps model values to one another?
certificateGrid.listenTo(certificateCollection, "backgrid:edited", function (model, column) {
var validatableColumns = _.filter(column.collection.models, function (c) {
return c.get('validator');
});
//etc.

Using _.reduce seems to do the trick:
var validatorFns = _.reduce(column.collection.models, function (fns, model) {
var validator = model.get('validator');
if (model.get('validator')) {
fns[model.get('name')] = validator;
}
return fns;
}, {});

Related

Using ExpressionProperties to access different model templateOptions

Firstly I would like to say Angular Formly is a fantastic library for novices such as myself. I am not a web developer, however find this library to be intuitive and powerful.
However I do need assistance with use of Expression Properties.
I have a model library which contains library items, for example:
{
"itemId":"STX001",
"title":"Grey Wolf",
"category":"White", etc.
}
{
"itemId":"STX002",
"title":"Noble Black",
"category":"Black", etc.
}
etc.
I also have a formly form which uses ui-select in top field to lookup all values from Library, select one of these (I will call this Item), and then populate remaining fields in the form with Items properties, then submit form to Catalogue model.
The problem I am facing is I cannot reference the properties of Item from within other fields. I have tried using expressionProperties but can only extract the valueProp value (which is uniqueID), however I am after Item.title, Item.category, etc.
Code below:
{
//This is form fields for creating a new Catalogue entry
key: 'libraryId',
type: 'ui-select',
templateOptions: {
label: gettextCatalog.getString('Search Library'),
options: [],
valueProp: 'itemId',
itemTitle: 'title',
itemCategory: 'category',
labelProp: 'title',
focus: true,
placeholder: 'Start typing keywords..'
},
controller: function ($scope) {
getLibrary().then(function(data){
$scope.options.templateOptions.options = data;
return data;
});
}
}
{
key: 'title',
type: 'input',
templateOptions: {
label: gettextCatalog.getString('Name'),
required: true
},
expressionProperties : {
//This is what i'm trying to achieve but doesn't work
'templateOptions.placeholder' : 'model.libraryId.itemTitle'
}
},
Use the call back function provided
expressionPropertyObj = {
'templateOptions.required': (model, formState: any, field: FormlyFieldConfig) => {
console.log('model',model);
console.log('state',formState);
console.log('field',field);
},

Meteor Autoform - disable select options in an array of Object fields

I'm wanting to disable an option if it has already been selected in one of the object groups.
So, if I selected "2013" then added another sample, "2013" would not be available in that group, unless that option is changed in the original group.
Is there an easy way to do this that I'm missing? Do I need to reactively update the schema when a selection is made?
samples:{
type: Array,
optional: true,
maxCount: 5
},
"samples.$":{
type: Object,
optional: true
},
"samples.$.sample":{
type:[String],
autoform: {
type: "select",
options: function () {
return [
{
optgroup: "Group",
options: [
{label: "2013", value: 2013},
{label: "2014", value: 2014},
{label: "2015", value: 2015}
]
}
];
}
}
},
Proof of Concept
I know this post is about 3 years old. However, I came across the same issue and want to provide an answer for all those who also stumbled over this post.
This answer is only a proof of concept and does not provide a full generic and performant solution, that could be used on production apps.
A fully generic solution would require a deep change in the code of how select field options are generated and updated in AutoForm.
Some prior notes.
I am using Autoform >=6 which provides a good API to instantly obtain field and form values in your SimpleSchema without greater trouble. SimpleSchema is included as npm package and Tracker has to be passed to it in order to ensure Meteor reactivity.
Functions like AutoForm.getFieldValue are reactive, which is a real great improvement. However, reactively changing the select options based on a reactive value causes a lot of update cycles and slows the performance (as we will see later).
Using AutoForm.getFormValues is not working, when using it within options of an Object field. While working within Array field, it will not behave reactively in Object fields, thus not update the filtering on them.
Manipulating Options for Arrays of Select Inputs (failing)
You can't use it with array types of fields. It's because if you change the select options, it applies for all your select instances in the array. It will therefore also apply to your already selected values and strips them away, too. This makes your select looks like it is always "not selected"
You can test that yourself with the following example code:
new SimpleSchema({
samples:{
type: Array,
optional: true,
maxCount: 5
},
"samples.$":{
type: String,
autoform: {
type: "select",
options: function () {
const values = AutoForm.getFormValues('sampleSchemaForm') || {};
const samples = values && values.insertDoc && values.insertDoc.samples
? values.insertDoc.samples
: [];
const mappedSamples = samples.map(x => x.sample);
const filteredOpts = [
{label: "2013", value: "2013"},
{label: "2014", value: "2014"},
{label: "2015", value: "2015"}
].filter(y => mappedSamples.indexOf(y.value) === -1);
return [
{
optgroup: "Group",
options:filteredOpts,
}
];
}
}
},
}, {tracker: Tracker});
Using fixed values on an Object Field
when taking a closer look at the schema, I saw the maxCount property. This made me think, that if you anyway have a list of max options, you could solve this by having fixed properties on a samples object (by the way: maxCount: 5 makes no sense, when there are only three select options).
This causes each select to have it's own update, that does not interfere the others. It requires an external function, that keeps track of all selected values but that came out be very easy.
Consider the following code:
export const SampleSchema = new SimpleSchema({
samples:{
type: Object,
optional: true,
},
"samples.a":{
type: String,
optional:true,
autoform: {
type: "select",
options: function () {
const samples = AutoForm.getFieldValue("samples");
return getOptions(samples, 'a');
}
}
},
"samples.b":{
type: String,
optional:true,
autoform: {
type: "select",
options: function () {
const samples = AutoForm.getFieldValue("samples");
return getOptions(samples, 'b');
}
}
},
"samples.c":{
type: String,
optional:true,
autoform: {
type: "select",
options: function () {
const samples = AutoForm.getFieldValue("samples");
return getOptions(samples, 'c');
}
}
},
}, {tracker: Tracker});
The code above has three sample entries (a, b and c) which will let their options be computed by an external function.
This function needs to fulfill certain requirements:
filter no options if nothin is selected
filter not the option, that is selected by the current samples select
filter all other options, if they are selected by another select
The code for this function is the following:
function getOptions(samples={}, prop) {
// get keys of selections to
// determine, for which one
// we will filter options
const sampleKeys = Object.keys(samples);
// get sample values to
// determine which values
// to filter here
const sampleValues = Object.values(samples);
const filteredOptiond = [
// note that values are stored as strings anyway
// so instead of parsing let's make them strings
{label: "2013", value: "2013"},
{label: "2014", value: "2014"},
{label: "2015", value: "2015"}
].filter(option => {
// case 1: nothing is selected yet
if (sampleKeys.length === 0) return true;
// case2: this selection has a
// selected option and current option
// is the selected -> keep this option
if (sampleKeys.indexOf(prop) > -1 && option.value === samples[prop])
return true;
// case 3: this selection has no value
// but others may have selected this option
return sampleValues.indexOf(option.value) === -1;
});
return [
{
optgroup: "Group",
options: filteredOptiond,
}
]
};
Some Notes on this Concept
Good:
-it works
-you can basically extend and scale it to your desired complexity (optgroups, more fields on samples, checking against other fields with other fields etc.)
Bad:
- performance
- bound to a given (or the nearest) form context (see here)
- much more code to write, than for an array.

Using jQuery removeData() with nested data

I'm using the jQuery data() function to store data on a series of divs in a format similar to:
{
options: {
example: {
option_1: {
value: "example 1"
},
option_2: {
value: "example 2"
}
}
}
}
I can add new keys and update the data, e.g.
$("#mydiv").data('options',{'example':{} }); // the object is already created in the live version
$("#mydiv").data('options')['example']['option_3'] = { value: "example 3" };
But when I come to use removeData(), FireBug tells me that the key is undefined, e.g.
$("#mydiv").removeData('options')['example']['option_2'];
Any help appreciated!
.removeData(name) removes the previously stored data with the given name, and returns a jQuery object. In your scenario, you don't want the remove the entire options object, just a specific property of it, so you should be using delete instead:
delete $("#mydiv").data('options')['example']['option_2'];

how to save a SlickGrid column order (js)?

I have two grids in my application.
var columns1 = [
{
name: "Address",
field: "address"
id: "address",
sortable: true
}
]
var columns2 = [
{
{
name: "Rating, in %",
field: "rating"
id: "rating_percent",
resizable: false
}
]
They are absolutely independent from each other. Also, I have some grid events descriptions in another js file.
grid.onColumnsReordered.subscribe(function (e, args) {
_this.updateHeaderRow();
// here
});
When user changes the columns order, then I want to save this order. Should I change (overwrite) the DOM elements, I mean column1 and column2?
So question: how can I save the columns order?
njr101's answer (using store.js) is great, but note: store.js cannot store functions (i.e. for editors, formatters), so once you store.get() the columns, you'll need to add the functions back, using your original stock "columns" array:
if (store.get('gridColumns')) {
grid.setColumns(store.get('gridColumns')); // Restore settings if available
grid.getColumns().forEach(function(ch) { // Re-create editor and formatter functions
var result = $.grep(columns, function(e){ return e.id == ch.id; });
if (result[0]) {
ch.editor = result[0].editor;
ch.formatter = result[0].formatter;
}
});
}
I have done this before and the easiest way I found was to store the columns in local storage. I use the store.js library which makes this pretty simple.
grid.onColumnsReordered.subscribe(function (e, args) {
store.set('gridColumns', grid.getColumns());
});
When you want to restore the columns (e.g. when the user returns to the page) you can just call:
grid.setColumns(store.get('gridColumns'));

How I'll create a model from json file? (ExtJS)

This the model that I want to create using json file
Ext.define('Users', {
extend: 'Ext.data.Model',
fields: [{name: 'user_id', type: 'int'},
{name: 'user_name', type: 'string'}]
});
What do I have to do in order to automatically create this model, based on the content of a json response from the server?
In order to have the model created automatically, you need to include the metaData field with your Json data. metaData can be used to describe all of the fields for the Model.
In the ExtJS 4.1 documentation - Ext.data.reader.Json has a section called Response MetaData which describes basic use of this feature.
You should be able to pull down some json with fields and or some format that can be transformed into that format pretty easily.
Make call to service to get model's fields. Might need to define some chain that first calls model service and performs subsequent steps after.
Build model's field array w/ fields results from #1. May need to transform data based on response in #1.
var fields = response.fields;
Define model based on fields in Store's constructor
var store = Ext.create('Ext.data.Store', {
constructor: function () {
var model = Ext.define("Users", {
extend: "Ext.data.Model",
fields: fields
});
this.model = model.$className;
this.callParent(arguments);
}
});
I only use the jsonp, which loads an json file and parses it automatically, don't know if Ext.Ajax does this, too.
But you would do something like this:
definition.json:
{
"name": "User",
"fields": [
{ "name": "user_id" , "type": "int" },
{ "name": "user_name", "type": "string" }
]
}
load it:
Ext.Ajax.request({
url : "..../definition.json"
success: function( res ) {
Ext.define( res.name, {
extend: 'Ext.data.Model',
fields: res.fields
}, function() {
Ext.create( 'somestore', { model: res.name });
});
}
});

Categories