I am trying to get a json data render a dojo tree.
You can see what I am doing at http://jsfiddle.net/F53Ge/38/
require(["dojo/parser","dojo/json","dojo/store/Memory","dijit/tree/ObjectStoreModel","dijit/Tree", "dojo/window"], function (parser,json,Memory,ObjectStoreModel,Tree,win) {
var data = [{"id":"root","team":[{"teamId":1,"teamname":"JMK_TEST_1","parentteamId":0,"associatedList":[{"type":"9117","number":"1011D1P"}]},{"teamId":174,"teamName":"JJG_PARENT_3","parentteamId":0,"associatedList":[{"type":"8205","number":"062072T"}]},{"teamId":172,"teamName":"JJG_PARENT_1","parentteamId":0,"subteamsList":[{"teamId":175,"teamName":"JJG_Subteam_1","parentteamId":172,"associatedList":[{"type":"8720","number":"12345"}]},{"teamId":176,"teamName":"JJG_Subteam_2","parentteamId":172,"associatedList":[{"type":"8720","number":"12345"}]}],"associatedList":[{"type":"7945","number":"KQZGTNC"}]},{"teamId":221,"teamName":"JJG_Parent_4","parentteamId":0,"subteamsList":[{"teamId":222,"teamName":"JJG_Subteam_4_1","parentteamId":221,"associatedList":[{"type":"9117","number":"10E7683"},{"type":"9119","number":"514DDB2"},{"type":"8233","number":"102FE9P"},{"type":"7978","number":"KDGYKLL"},{"type":"7978","number":"99A9880"}]}]},{"teamId":106,"teamName":"JMK_TEST","parentteamId":0,"subteamsList":[{"teamId":107,"teamName":"JMK_TEST1","parentteamId":106,"subteamsList":[{"teamId":173,"teamName":"JJG_PARENT_2","parentteamId":107,"subteamsList":[{"teamId":178,"teamName":"JJG_Subteam_2_1","parentteamId":173,"associatedList":[{"type":"9117","number":"10E7683"}]}],"associatedList":[{"type":"7945","number":"KQZGTNC"}]}]}]}]}];
var store = new Memory({
data: data,
getChildren: function(object){
return object.team || [];
}
});
var model = new ObjectStoreModel({
store: store,
query: { id:'root' },
mayHaveChildren: function (item) {
return "subteamsList" in item;
}
});
var tree = new Tree({
model: model,
showRoot: false,
autoExpand: true,
persist: false,
onClick: function (item, treeNode, e) {
selectednodeid = this.selectedNode;
win.scrollIntoView(selectednodeid);
alert(selectednodeid);
}
},"oopt_list");
tree.startup();
});
First I am not see children nodes and also do not know how to pass the label as it shows undefined for the list.
Any help is appreciated.
Also let me know if I should use the ForestModel instead.
Basically I am trying to show the json data in a tree hireachy and want to know which node the user clicked so I can do some action based on that.
Regards
BumbleBee
To define which property should be used as label, you need to define 'labelAttr' on model.
The reason, why you do not see the child nodes is, that you store is missing idProperty on Store.
(or you have different id property on root item 'id' and different on other items 'teamId').
Using ObjectStoreModel instead of ForestModel is correct. You are using Memory store, which is implementaion of new dojo/store API. (ForestModel should be used only with older dojo/data API)
See your sample updated here
http://jsfiddle.net/F53Ge/42/
require(["dojo/parser","dojo/json","dojo/store/Memory","dijit/tree/ObjectStoreModel","dijit/ Tree", "dojo/window"], function (parser,json,Memory,ObjectStoreModel,Tree,win) {
var data = [{"teamId":"root","subteamsList":[{"teamId":1,"teamName":"JMK_TEST_1","parentteamId":0,"associatedList":[{"type":"9117","number":"1011D1P"}]},{"teamId":174,"teamName":"JJG_PARENT_3","parentteamId":0,"associatedList":[{"type":"8205","number":"062072T"}]},{"teamId":172,"teamName":"JJG_PARENT_1","parentteamId":0,"subteamsList":[{"teamId":175,"teamName":"JJG_Subteam_1","parentteamId":172,"associatedList":[{"type":"8720","number":"12345"}]},{"teamId":176,"teamName":"JJG_Subteam_2","parentteamId":172,"associatedList":[{"type":"8720","number":"12345"}]}],"associatedList":[{"type":"7945","number":"KQZGTNC"}]},{"teamId":221,"teamName":"JJG_Parent_4","parentteamId":0,"subteamsList":[{"teamId":222,"teamName":"JJG_Subteam_4_1","parentteamId":221,"associatedList":[{"type":"9117","number":"10E7683"},{"type":"9119","number":"514DDB2"},{"type":"8233","number":"102FE9P"},{"type":"7978","number":"KDGYKLL"},{"type":"7978","number":"99A9880"}]}]},{"teamId":106,"teamName":"JMK_TEST","parentteamId":0,"subteamsList":[{"teamId":107,"teamName":"JMK_TEST1","parentteamId":106,"subteamsList":[{"teamId":173,"teamName":"JJG_PARENT_2","parentteamId":107,"subteamsList":[{"teamId":178,"teamName":"JJG_Subteam_2_1","parentteamId":173,"associatedList":[{"type":"9117","number":"10E7683"}]}],"associatedList": [{"type":"7945","number":"KQZGTNC"}]}]}]}]}];
var store = new Memory({
data: data,
idProperty:"teamId",
getChildren: function(object){
return object.subteamsList || [];
}
});
var model = new ObjectStoreModel({
store: store,
query: { teamId:'root' },
mayHaveChildren: function (item) {
console.log("may",item)
return "subteamsList" in item;
},labelAttr :"teamName"
});
var tree = new Tree({
model: model,
showRoot: false,
autoExpand: true,
persist: false,
onClick: function (item, treeNode, e) {
selectednodeid = this.selectedNode;
win.scrollIntoView(selectednodeid);
alert(selectednodeid);
}
},"oopt_list");
tree.startup();
});
Related
I'm trying to deal with a server response, and am a little confused how to turn the json response into Backbone Models.
My Backbone model looks like so:
Entities.Recipe = Backbone.Model.extend({
defaults: {
id: '',
name: '',
introduction: ''
},
parse: function (response)
{
if(._isObject(response.results)){
return response.results
else {
return response
}
})
Entities.RecipeCollection = Backbone.Collection.extend({
url: 'recipes',
model: Entities.Recipe
)}
var API = {
getRecipeEntities: function (){
var recipes = new Entities.RecipeCollection()
var defer = $.Deferred()
recipes.fetch({
url: 'http://3rdpartyApilocation.com/recipes'
success: function (data) {
defer.resolve(data)
}
})
var promise = defer.promise()
$.when(promise).done(function (fetchedData)
{})
return promise
}
RecipeManager.reqres.setHandler('recipe:entities', function()
{
return API.getRecipeEntities()
}
And the response.results is an Array of objects - with each object having an id key, a name key and an introduction key. But because I am so inexperienced with Backbone I have no idea how to map those results to the model?
I have installed Chromes Marionette inspector and when I look at the entire array of results seems to be passed to the model, rather than each individual object within each response.result being set to each individual model. Sorry if I can't be more clear - I'm very new to Backbone...
Perhaps your confusion is because you're in fact able to use parse on a model or on a collection. And from your explanation it looks like the response.results object returns a list of objects that you want to become models in your application. But because you're catching that object in a model, the model doesn't know what to do with that array.
Let's say you have a response like this:
{
"status": "ok",
"results": [
{
"id": 1,
"name": "Pie"
}, {
"id": 2,
"name": "Rice"
}, {
"id": 3,
"name": "Meatballs"
}
]
}
Then you would just use parse on your Collection to let it know the response isn't array itself, and help it find it in the results property.
Here's a working example:
var Recipe = Backbone.Model.extend();
var Recipes = Backbone.Collection.extend({
model: Recipe,
url: 'http://www.mocky.io/v2/56390090120000fa08a61a57',
parse: function(response){
return response.results;
}
});
var recipes = new Recipes();
recipes.fetch().done(function(){
recipes.each(function(recipe){
/** Just demo of the fetched data */
$(document.body).append('<p>'+ recipe.get('name') +'</p>');
});
});
<script src='http://code.jquery.com/jquery.js'></script>
<script src='http://underscorejs.org/underscore.js'></script>
<script src='http://backbonejs.org/backbone.js'></script>
I have three stores in my application EmployeesTree, Empolyees, History.
First one is source for treegrid, second for combobox that is used to pass additional parameters while loading third stode.
My first approach was to create second store using standard proxy and load employees from server using request.
In my history grid I have:
me.c = Ext.create("MyApp.Store.Employees");
me.c.load();
me.tbar=
[
{
xtype: 'combo',
fieldLabel: 'Employee',
width: 300,
labelWidth: 65,
typeAhead: false,
forceSelection: true,
store: me.c,
queryMode: 'local',
displayField: 'Name',
valueField: 'Id',
listeners: {
select: function (combo, records) {
console.log(records[0].get('Id'));
me.store.proxy.extraParams.uid = records[0].get('Id');
me.store.load();
},
scope: this
}
}
];
But this cause additional request that I would like to avoid.
My second idea was to create empty store and add every leaf from first store to it (because it will contains the same list of employees but without hierarchy)
I've created method that should copy all leafs to new store:
cloneStore: function(source) {
var target = Ext.create('Sch.data.ResourceStore', {
model: 'MyApp.Model.Employee'
});
var node = source.getRootNode();
node.eachChild(function (myNode) {
if (myNode.isLeaf()) {
var newData = Ext.clone(myNode.copy().data);
var model = new source.model(newData, newData.id);
target.add(model);
}
});
Right now this copies first level of my store, I need to add recursive to this method so it will add leafs from subnodes.
Also when I do var newData = Ext.clone(myNode.copy().data); my new record will have all fields of that treestore, how can I copy only two fields from my source store to target. I need only 2 fields (id, Name).
How should I modify cloneStore method to recursively copy employees from treeStore to Store and how can I copy only needed fields? Do I must specify them in my method.
EDIT
I've modified my function so it can be called recursively.
cloneStore: function(source) {
var target = Ext.create('MyApp.Store.EmployeesForComboBox');
var node = source.getRootNode();
this._addLeafsToStore(target, node);
return target;
},
_addLeafsToStore: function(store, node) {
node.eachChild(function (myNode) {
if (myNode.isLeaf()) {
store.add({
Id: myNode.get('Id'),
Name: myNode.get('Name')
});
} else {
this._addLeafsToStore(store, myNode);
}
},this);
},
This works quite well, but I would like to create more universal function, because right now I have hardcoded Store and fields I want to copy.
Additional question:
What option is better: to add new record to store using Ext.clone (first solution) or adding only necessary fields (second solution)?
To create a more generic function, you'll need to change hardcoded stuff as function parameters:
cloneStore: function(source, target, fields) {
var node = source.getRootNode();
this._addLeafsToStore(target, node, fields);
return target;
},
_addLeafsToStore: function(store, node, fields) {
node.eachChild(function (myNode) {
if (myNode.isLeaf()) {
var config = {};
for (var i = 0; i < fields.length; i++) {
config[fields[i]] = myNode.get(fields[i]);
}
store.add(config);
} else {
this._addLeafsToStore(store, myNode, fields);
}
},this);
},
and call it like this:
var cloned = someObj.cloneStore(source, Ext.create('MyApp.Store.EmployeesForComboBox'), ['Id', 'Name']);
For the additional question, I think the second solution will perform better if your data set is large.
I try to remove the duplicate value before storing it into a store. I want to store the same value in the store 1 time only. But it seems the following Ext.Array.unique line does not working. Could anyone please help me to correct this.
Thank you
var input1store = new Ext.data.Store({
fields: [{name: 'name'}],
proxy: {
type: 'ajax',
url: 'www.requesturl.com?format=json&source1',
reader: {
type: 'json',
root: 'xml.result'
}
},
autoLoad: false,
sorters: [{property: 'name', direction: 'asc'}],
listeners:{
load: function(rec){
uniqueArray = Ext.Array.unique(rec.getRange());
}
}
});
You can use the filterBy method to filter out records that you don't want to appear in the store after loading.
Note that the store will keep a copy of the filtered out records, that it would restore if clearFilter is called (that could be by you or the component using the store). If you want to get rid definitively of these records, you'll have to delete that copy.
Ext.create('Ext.data.Store', {
fields: ['name'],
// example data
proxy: {
type: 'memory',
data: [{name: 'Foo'},{name:'Bar'},{a:'Baz'},{a:'Foo'},{a:'Bar'}],
},
listeners: {
load: function(store) {
// using a map of already used names
const hits = {}
store.filterBy(record => {
const name = record.get('name')
if (hits[name]) {
return false
} else {
hits[name] = true
return true
}
})
// delete the filtered out records
delete store.snapshot
},
},
})
You can specify the idProperty value (idProperty == 'name') in your store
http://docs.sencha.com/ext-js/4-1/#!/api/Ext.data.Model-cfg-idProperty.
I am working with a Playlist object which has some properties defining itself as well as a PlaylistItem collection.
When I receive data from my server, I get its JSON response in my client-side success method:
success: function (data) {
console.log("JSON data:", data);
playlists = _.map(data, function (playlistConfig) {
return new Playlist(playlistConfig);
});
...
}
Here, I convert my JSON data into Playlist objects. Each Playlist object is a Backbone.Model.
Here's how my data looks:
And here's what the Playlist constructor looks like:
return function(config) {
var playlist = new Playlist(config);
return playlist;
};
var Playlist = Backbone.Model.extend({
defaults: function() {
return {
id: null,
userId: null,
title: 'New Playlist',
selected: false,
position: 0,
shuffledItems: [],
history: [],
items: Backbone.Collection.extend({
model: PlaylistItem
})
};
},
...
}
My problem:
If I create a Playlist object with defaults, it initializes with an empty Backbone.Collection for PlaylistItem. However, if I create a Playlist object with an already-defined collection, I get a basic array and not a Backbone.Collection. This is because I am working with JSON data from the server which has not been converted to Backbone entities yet. That data is extended over the Playlist's defaults and overwrites the Backbone.Collection entity.
What is a proper way to initialize with a populated Backbone.Collection? I could write code in Initializes which checks the type of my items array and if it is not a Backbone.Collection I could create a new Backbone.Collection and add the items to it and then replace the old array with the new one, but that seems really hoakey.
Don't define your PlalistItems Collection inside defaults, but beforehand.
Then, create an initialize method on your Playlist Model like so:
var PlaylistItems = Backbone.Collection.extend({
...
});
var Playlist = Backbone.Model.extend({
initialize: function() {
this.set('items', new PlaylistItems(this.items));
},
defaults: function() {
return {
id: null,
userId: null,
title: 'New Playlist',
selected: false,
position: 0,
shuffledItems: [],
history: [],
items: [] // don't define your PlaylistItems Collection here
};
}
});
Check out the fiddle here: http://jsfiddle.net/georgedyer/r2XKb/
(you'll need to open the console to see the collection)
Another issue I ran into was after you save your model to the server you will get back a response that will change your embedded collection into a regular javascript array. To remedy this I had to override the parse function on my model class like so:
var model = backbone.Model.extend({
urlRoot : "/rest/model",
initialize: function(){
this.set("myCollection", new MyCollection(this.myArray));
},
defaults: {
myArray: []
},
parse: function(response){
this.set(response);
this.set("myArray", new MyCollection(response.myArray));
}
});
I have 2 models and one collection. JobSummary is a model, JobSummaryList is a collection of JobSummary items, and then I have a JobSummarySnapshot model that contains a JobSummaryList:
JobSummary = Backbone.Model.extend({});
JobSummaryList = Backbone.Collection.extend({
model: JobSummary
});
JobSummarySnapshot = Backbone.Model.extend({
url: '/JobSummaryList',
defaults: {
pageNumber: 1,
summaryList: new JobSummaryList()
}
});
When I call fetch on the JobSummarySnapshot object, it gets everything... Except when I move through the summaryList collection they are all of type object and not JobSummary.
I suppose this makes sense since other than the defaults object, it doesn't know that the summaryList should be of type JobSummaryList. I can go through each item and convert it to a JobSummary object, but I was hoping there was a way to do it without having to do it manually.
Here's my test code (working jsfiddle here):
var returnData = {
pageNumber: 3,
summaryList: [
{
id: 5,
name: 'name1'},
{
id: 6,
name: 'name2'}
]
};
var fakeserver = sinon.fakeServer.create();
fakeserver.respondWith('GET', '/JobSummaryList', [200,
{
'Content-Type': 'application/json'},
JSON.stringify(returnData)]);
var callback = sinon.spy();
var summarySnapshot = new JobSummarySnapshot();
summarySnapshot.bind('change', callback);
summarySnapshot.fetch();
fakeserver.respond();
var theReturnedList = callback.getCall(0).args[0].attributes.summaryList;
_.each(theReturnedList, function(item) {
console.log('Original Item: ');
console.log(item instanceof JobSummary); // IS FALSE
var convertedItem = new JobSummary(item);
console.log('converted item: ');
console.log(convertedItem instanceof JobSummary); // IS TRUE
});
UPDATE:
It occurred to me that I could override the parse function and set it that way... I have this now:
JobSummarySnapshot = Backbone.Model.extend({
url: '/JobSummaryList',
defaults: {
pageNumber: 1,
summaryList: new JobSummaryList()
},
parse: function(response) {
this.set({pageNumber: response.pageNumber});
var summaryList = new JobSummaryList();
summaryList.add(response.summaryList);
this.set({summaryList: summaryList});
}
});
This works so far. Leaving the question open in case someone has comment on it....
Your parse() function shouldn't set() anything, its a better practice to just return the attributes, Backbone will take care of setting it. e.g.
parse: function(response) {
response.summaryList = new JobSummaryList(response.summaryList);
return response;
}
Whatever you return from parse() is passed to set().
Not returning anything (which is like returning undefined) is the same as calling set(undefined), which could cause it not to pass validation, or some other unexpected results if your custom validate()/set() methods expects to get an object. If your validation or set() method fails because of that, the options.success callback passed to Backbone.Model#fetch() won't be called.
Also, to make this more generic, so that set()ing to a plain object from other places (and not only from the server response) also effects it, you might want to override set() instead:
set: function(attributes, options) {
if (attributes.summaryList !== undefined && !(attributes.summaryList instanceof JobSummaryList)) {
attributes.summaryList = new JobSummaryList(attributes.summaryList);
}
return Backbone.Model.prototype.set.call(this, attributes, options);
}
You might also find Backbone-relational interesting - it makes it much easier to deal with collections/models nested inside models.
edit I forgot to return from the set() method, the code is now updated