I have a json object in the following format:
{
properties:{
url:"http://..."
}
}
And I want to display the url in a Backgrid grid. However, I can't figure out how to change the name attribute of the column such that it accesses the nested url. I have tried the following examples to no avail:
{
name: "properties.url",
label: "URL",
cell: "uri"
}
And
{
name: "properties[url]",
label: "URL",
cell: "uri"
}
It seems like a simple enough thing to do but I can't find an answer.
Take a look at Backbone's Wiki.
There are at least 4 choices:
backbone-deep-model
backbone-nested
backbone-nestify
backbone-dotattr
This is the entireity of "backbone-dotattr"
(function(_, Backbone) {
_.extend(Backbone.Model.prototype, {
get: function(key) {
return _.reduce(key.split('.'), function(attr, key) {
if (attr instanceof Backbone.Model)
return attr.attributes[key];
return attr[key];
}, this.attributes);
}
});
})(window._, window.Backbone);
with this, i can specify
name: "child.childAttribute"
works perfectly in the "columns" part for Backgrid. hope it helps.
Related
Using React with TypeScript
Please could somebody provide an example of how I might be able to use a Kendo DataSource to read from a method which internally uses Axios to prod an external API for JSON data..? I must have flown through 20 different versions of this code trying different approaches, nothing seems to fit...
All I'm trying to do currently is supply a Kendo ComboBox with an array of {id: number, name: string}
Very basic stuff at the moment, but I do have to use a similar approach to this later on with a Kendo Grid which handles server side sorting and pagination so I'd like to get this working now then that should be somewhat easier later on...
The reason I want to use Axios is because I've written an api.ts file that appends appropriate headers on the gets and posts etc and also handles the errors nicely (i.e. when the auth is declined etc...)
A basic example of what I'm trying, which isn't working is this: -
public dataSource: any;
constructor(props: {}) {
super(props);
this.dataSource = new kendo.data.DataSource({
type: "odata",
transport: {
read: function() {
return [{ id: 1, name: "Blah" }, { id: 2, name: "Thing" }];
}.bind(this)
},
schema: {
model: {
fields: {
id: { type: "number" },
name: { type: "string" }
}
}
}
});
}
<ComboBox
name="test"
dataSource={this.dataSource}
placeholder={this.placeholder}
dataValueField="id"
dataTextField="name"
/>
Anybody got any thoughts on this please? :)
Easy fix in the end...
this.dataSource = new kendo.data.DataSource({
transport: {
read: function(options: any) {
options.success([{ id: 1, name: "Blah" }, { id: 2, name: "Thing" }]);
}.bind(this)
},
schema: {
model: {
fields: {
id: { type: "number" },
name: { type: "string" }
}
}
}
});
2 things were wrong..
Removed the type: "odata",
and
Added the usage of options in
All working fine now with the async await function also, just passing the data into the options.success in the .then on the promise. Job done :-)
I defined the store and a filter. The ViewModel contains test object I need to filter store items by this object.
Ext.define('XXX.view.XXX.ViewXXXXModel', {
extend: 'Ext.app.ViewModel',
...
stores: {
agreements: {
source: 'XXX',
filters: {
filterFn: function(item) {
return item.some_field !== this.get('test').somevalue;
}
}
}
}
I cannot access the test object of View Model from filter function?
Way too late now, but I just had the same issue, and the cleaner method to do this is by returning filterFn as a formula bind:
For your original example:
stores: {
agreements: {
source: 'XXX',
filters: [{
filterFn: '{storeFilter}'
}]
}
}
},
formulas: {
storeFilter: function(get) {
var somevalue = get('test').somevalue;
return function(item) {
return item.some_field !== this.get('test').somevalue;
};
}
}
Edit:
When I originally wrote this I wasn't aware that Ext continually added extra filters when using setFilters rather than just replacing them all. To get around this, you need to name the filter using an id. In the above example something like this:
filters: [{
id: 'myVMFilterFunction',
filterFn: '{storeFilter}'
}]
Then it replaces the filter as expected
Ideally you would use the declarative filter format in most cases - the granularity ensures that bindings are more specific, triggering appropriate / expected updates when data changes. For example:
stores: {
agreements: {
source: 'XXX',
filters: {
property: 'some_field',
value: '{test.somevalue}',
operator: '!='
}
}
}
If you really want to use imperative code you can inject the view-model scope via a formula:
formulas: {
_this: function(){
return this;
}
}
Then bind it like so:
stores: {
agreements: {
source: 'XXX',
filters: {
scope: '{_this}',
filterFn: function(item){
return item.some_field !== this.get('test.somevalue'));
}
}
}
}
This is a bit of a kludge though and changes to test likely won't be reflected in the store and any visual component tied to it. In this case you'd end up having to manually reload the store or reapply the filters - which kind of defeats the point of MVVM.
When using Strongloop Loopback, we can make a data request (with relations) to the database these ways:
(1) Using lb-service (at front-end)
Model.find({
filter: {
where: {id: 1},
include: {
relation: 'relationship',
scope: {where: {id: 2}}
}
}
}, function (instances) {
}, function (err) {
});
(2) Using node.js (at server-side)
Model.find({
where: {id: 1},
include: {
relation: 'relationship',
scope: {where: {id: 2}}
}
}, function (err, instances) {
});
What I need: Exclude items from first filter whether another filter fails.
There is one obvious solution: filtering the response, this way:
instances = instances.filter(function(instance){
return typeof(instance.relationship) !== "undefined";
});
But... Using filter() to eliminate is not a good scalable solution, because it will always iterate over the array. Using this solution at the front-end is not good, because the size of the array will slow down the performance. Bringing it to the server-side could be a solution. But... each model will have a particular set of relations... and it is not scalable again!
Main question: Is there some way to overcome this situation, excluding items from the first filter whether second (third, or more) fails simultaneously (or not)?
Something like, defining it on filter object:
var filter = {
where: {id: 1},
include: {
relation: {name: 'relationship', required: true}, // required means this filter *needs* to be satisfied
scope: {where: {id: 2}}
}
};
Requirements:
(1) SQL query is not an option ;)
(2) I am using MySQL as database. So things like
{ where: { id: 1, relationship.id: 2 } }
will not work as desired.
I don't know of a way to do this within the filter syntax itself. I think you would have to write a custom remote method to do the filtering yourself after the initial query was complete. Here's what that might look like:
// in /common/models/model.js
Model.filterResults = function filterResults(filter, next) {
Model.find(filter, function doFilter(err, data) {
if (err) { return next(err); }
var filteredData = data.filter(function(model) {
return model.otherThings && model.otherThings().length;
});
next(null, filteredData);
});
};
Model.remoteMethod(
'filterResults',
{
accepts: { arg: 'filter', type: 'object', http: { source: 'query' } },
returns: { arg: 'results', type: 'array' },
http: { verb: 'get', path: '/no-empties' }
}
);
Now you can hit: .../api/Models/no-empies?filter={"include":"otherThings"} and you will only get back Models that have a related OtherThing. Note that this is for a one-to-many relationship, but hopefully you can see how to change it to fit your needs.
I'm using the new Angular UI Grid (that is planned to replace ng-grid).
My data needs some formatting before it's displayed in the table. For instance, my server returns an attribute named "status" as a number, but I want to display it as a nice name.
If status=1 display "Todo", if status=2 display "Doing" etc.
How can this be done in UI Grid?
The preferred method now is to use a cellFilter, rather than a custom template. Custom templates are OK, but they impose more workload on upgrade - you have to check whether new features require modifications to your template.
There is a reasonable example of filters in the tutorials: http://ui-grid.info/docs/#/tutorial/201_editable
Note the cellFilter: 'mapGender' on the gender column, and the filter itself defined further below in the tutorial:
.filter('mapGender', function() {
var genderHash = {
1: 'male',
2: 'female'
};
return function(input) {
if (!input){
return '';
} else {
return genderHash[input];
}
};
})
First step, add a cellTemplate to the column:
$scope.gridOptions.columnDefs = [
{field:'status', displayName: 'Status',cellTemplate: 'statusTemplate.html'}
];
The Template-File should look like this (COL_FIELD is the actual field):
<div style="text-align: center">{{COL_FIELD==1 ? 'Todo' : 'Doing'"}}</div>
Hope, you got the idea! :)
The shortest way is use CellTemplate with appScopeProvider:
vm.gridOptions = {
columnDefs: [
{
field: 'status',
cellTemplate: '<div>{{grid.appScope.formatStatus(row)}</div>'
}
],
appScopeProvider: {
formatStatus: function (row) {
return row.entity.status === 1 ? 'Todo' : 'Doing';
},
}
}
I have a question about how to just get a certain element of an array using MongoDB and MeteorJS. I have the following schema for the user document:
bankList:[
{
id: "34567890987654345678",
name: "xfgchjbkn",
type: "credit"
},
{
id: "09876543456789098767"
name: "65789876t8",
type: "debit"
}
]
I first subscribe to only part of the fields in the array, specifically I gather a list of all the ids. Then I have an edit screen that should subscribe to all of the fields for a specific element in the array with a matching id. I do not want to expose the rest of the array just the single element. Currently, I use the following to first gather a list of just the ids:
Meteor.users.find({_id: this.userId},
{fields:{'bankList.id': 1}});
And the following publication-subscription method to get just a specific element's information:
Publication:
Meteor.publish("userBankAdvanced", function(bankId){
check(bankId,String);
if(this.userId){
return Meteor.users.find({_id:this.userId,"bankList.id": bankId}, {'bankList.$': 1});
}else{
this.ready();
}
});
Subscription:
this.route('edit_account', {
path: '/edit/account/',
waitOn: function(){
if(Session.get("bankId")){
return Meteor.subscribe('userBankAdvanced',Session.get("bankId"));
}
return null;
},
data: function(){
if(Session.get("bankId")){
return Meteor.users.findOne();
}
return null;
},
onBeforeAction: function(){
beforeHooks.isRevise(Session.get("bankId"));
}
});
The subscription method returns all of the elements of the array with all of the information.
I want, for example, just this (not the entire list with all of the information):
bankList:[
{
id: "34567890987654345678",
name: "xfgchjbkn",
type: "credit"
}]
It looks like you're just missing the "fields" specifier in your "userBankAdvanced" publish function. I wrote a test in meteorpad using your example and it seems to work fine. The bank id is hardcoded for simplicity there.
So instead of
return Meteor.users.find({_id:this.userId,"bankList.id": bankId}, {'bankList.$': 1});
try using
return Meteor.users.find({_id:this.userId,"bankList.id": bankId}, {fields: {'bankList.$': 1}});
No luck, in meteor the "fields" option works only one level deep. In other words there's no builtin way to include/exclude subdocument fields.
But not all is lost. You can always do it manually
Meteor.publish("userBankAdvanced", function (bankId) {
var self = this;
var handle = Meteor.users.find({
_id: self.userId, "bankList.id": bankId
}).observeChanges({
added: function (id, fields) {
self.added("users", id, filter(fields, bankId));
},
changed: function (id, fields) {
self.changed("users", id, filter(fields, bankId));
},
removed: function (id) {
self.removed("users", id);
},
});
self.ready();
self.onStop(function () {
handle.stop();
});
});
function filter(fields, bankId) {
if (_.has(fields, 'bankList') {
fields.bankList = _.filter(fields.bankList, function (bank) {
return bank.id === bankId;
});
}
return fields;
}
EDIT I updated the above code to match the question requirements. It turns out though that the Carlos answer is correct as well and it's of course much more simple, so I recommend using that one.