I want to load a tree structure in dojo dynamically with every layer loading(fetching data from server) only after a click to the next layer is made. This would help me to not have the entire tree loaded in memory all together but as someone clicks a level of a tree, only then all the elements of the next level are fetched from the server using ajax requests in dojo.
Can someone help me on how to go about this?
The answer for this is here as follows: using a dijit tree, it has just 3 steps to create a dynamic tree that loads lazily: 1. Create a store, 2. Create a model, 3. Create the tree. Here is the code snippet that I used which worked perfectly:
function createLazyTreeStore()
{
var store = new JsonRest({
target: "location where your file is",
getChildren: function(object) {
// object may just be stub object, so get the full object first and then return it's
// list of children
return this.get(object.id).then(function(fullObject){
console.log(fullObject.children);
return fullObject.children;
});
}
});
return store;
}
function createModel(store)
{
var model = new ObjectStoreModel({
store: store,
getRoot: function(onItem) {
this.store.get("the .php file").then(function(item)
{
onItem(item[0]);
});
},
getChildren: function(object, onComplete, onError)
{
this.store.get("your url to fetch the data with parent child relation").then(function(fullObject) {
object.children = fullObject;
onComplete(object.children);
}, function(error)
{
console.error(error);
onComplete([]);
});
},
mayHaveChildren: function(object) {
return true;
}
});
return model;
}
function createTree(model)
{
var tree = new Tree({
model: model,
style: "your preference",
id: "tree",
});
return tree;
}
Then call these functions in the following order:
var store = createLazyTreeStore();
var model = createModel(store);
var tree = createTree(model);
accordionPane.addChild(tree);
Related
i have problem rendering my view...the view return always the last in the json object: This is the code:
Router.js:
var list = new clientCollection();
var cards = new cardsView({model:list})
list.fetch({success: function (collection, response, options) {
cards.render();
}
});
Cards.js view:
....
tagName: 'section',
className: 'list',
template: Handlebars.compile(cardsTemplate),
render: function () {
var list = this.model.toJSON(),
self = this,
wrapperHtml = $("#board"),
fragment = document.createDocumentFragment();
$(list).each(function (index, item) {
$(self.el).html(self.template({card: item}));
$.each(item.cards, function (i, c) {
var card = new cardView({model : c});
$(self.el).find('.list-cards').append(card.render().el);
});
fragment.appendChild(self.el);
});
wrapperHtml.append(fragment.cloneNode(true));
},
...
This is my json data:
[
{"id":"9","name_client":"XXXXXXX","cards":[]},
{"id":"8","name_client":"XXXXXXX","cards":[{"id":"8","title":"xxxxx.it","description":"some desc","due_date":"2016-01-23","sort":"0"}]}
]
Can u help me to render the view?
It's hard to know for sure without seeing how the view(s) are attached to the DOM, but your problem appears to be this line ...
$(self.el).html(self.template({card: item}));
That is essentially rendering each element in the collection as the full contents of this view, then replacing it on each iteration. Try instead appending the contents of each template to the view's element.
Also, since you tagged this with backbone.js and collections, note that the easier, more Backbone-y way to iterate through a collection would be:
this.model.each(function(item) {
// 'item' is now an instance of the Backbone.Model type
// contained within the collection. Also, note the use
// of 'this' within the iterator function, as well as
// this.$el within a View is automatically the same as
// $(self.el)
this.$el.append(this.template({ card: item });
// ... and so on ...
// By providing 'this' as the second argument to 'each(...)',
// the context of the iterator function is set for you.
}, this);
There's a lot packed in there, so ...
Backbone.Collection Underscore Methods
Backbone.View this.$el
I'm learning how to use backbone.js. I got 4 JSON APIs, and I need to collect all the responses and fetch the total response to single view
How can I achieve this? Do I need to use collection/model to achieve this?
I implemented a 2 call main view with sub views. It gets a lot more complex then that. But that should get you started.
These are the main 2 collections that get the data. If you want to break it down further (depends on needs) you can create a Model and then assign it to the collection using model: CityModel (in this case).
var Cities = Backbone.Collection.extend({
url: 'http://api.geonames.org/citiesJSON?north=44.1&south=-9.9&east=-22.4&west=55.2&lang=de&username=me',
parse: function(response) {
return response.geonames;
}
});
var Earthquakes = Backbone.Collection.extend({
url: 'http://api.geonames.org/earthquakesJSON?north=44.1&south=-9.9&east=-22.4&west=55.2&username=me',
parse: function(response) {
return response.earthquakes;
}
});
then the sub views, standard: here you can create events for each.
var EarthquakeView = Backbone.View.extend({
tagName: 'li',
template: _.template('<strong><%=eqid%></strong> <%=lng%> <%=lat%> magnitude: <%=magnitude%>'),
render: function() {
this.$el.html(this.template(this.model.toJSON()))
return this;
}
});
Notice here you can extend a view so you dont have to repeat yourself.
var CityView = EarthquakeView.extend({
template: _.template('<strong><%=toponymName%></strong> <%=lng%> <%=lat%>')
});
The main view..
var View = Backbone.View.extend({
initialize: function(opt) {
// attach to the main body
$(opt['main']).append(this.el)
this.earthquakesEl = $('<ol>');
this.earthquakes = new Earthquakes()
this.earthquakes.on('sync', _.partial(this.renderList, EarthquakeView, this.earthquakesEl), this);
this.earthquakes.fetch()
this.citiesEl = $('<ol>');
this.cities = new Cities()
this.cities.on('sync', _.partial(this.renderList, CityView, this.citiesEl), this);
this.cities.fetch()
// initialize the element
this.render();
},
renderList: function(view, el, collection) {
collection.forEach(function(model) {
el.append(new view({model: model}).render().el)
});
},
render: function() {
this.$el.append(this.earthquakesEl);
this.$el.append(this.citiesEl);
return this;
}
});
new View({ main: $('body') });
http://jsfiddle.net/u9y574y8/2/
There are 2 collection calls that are made independently, and there are 2 listeners that wait for them to finish and then place it on the main view. If you have any issue let me know. I think its pretty straight forward.
I'm trying to save the data using $resource and for REST API using Dreamfactory. I do have seperate mysql table for multiple addresses as user might have multiple addresses.
I'm looping through multiple addresses and querying to the API but some how it doesn't store in sequence or split some of the data to store
Here is my code:
Members.save($scope.member,
function () {
//console.log('POST',arguments );
console.log('POST', arguments[0].id);
$scope.mid = arguments[0].id;
if($scope.addresses)
{
$.each($scope.addresses,function(k,v){
//alert(k+"=>"+v);
Members_address.save({"member_id":$scope.mid,"address":v},
function () {
alert(k+"=>"+v);
console.log('POST',arguments );
}, function () {
console.error('POST', arguments);
}
);
});
window.location = "#/members";
}else
{
window.location = "#/members";
}
}, function () {
console.error('POST', arguments);
}
);
"Members" is factory to store data into members table and "Members_address" is a factory to store data in separate members_address table with member id.
It stores addresses but not in sequence and sometime it missed one of the address.
Here are the factories:
App.factory('Members',['$resource',function ($resource) {
return $resource('API_URL', null, {'update': { method:'PUT' }
});
}])
App.factory('Members_address',['$resource',function ($resource) {
return $resource('API_URL', null, {'update': { method:'PUT' }
});
}])
Check this example at JSFiddle using recursion, I think this is what you want.
Ok. So, one fundamental thing is the thought process when you working with data like this (especially with DreamFactory). You want to create a 'payload' of data to send to the server rather that trying to iteratively save records. Make an array of the records of a specific type that you want to save and then push them up to the server. In this case...you want to save a member(single object) and then addresses associated with that member(array of objects). The process is 'send member record to server then on save success, if I have addresses, create address objects with member id and store them in an array and send to the server. Then on address save success redirect.'
So here's the code I came up with. Hope it helps you out. Also, checkout $location for redirecting as opposed to window.location. And if your in need of the window object checkout $window. And...try only to use jQuery inside of directives to do DOM manipulation. AngularJS provides a lot of functionality for manipulating data.
// I'm assuming that your 'API_URL' contains the following information shown
// in this factory definition.
App.factory('Members', ['$resource', function ($resource) {
return $resource('YOUR_DSP_URL/TABLE_NAME', null, {'update': { method:'PUT' } });
}]);
App.factory('Members_address', ['$resource', function ($resource) {
return $resource('YOUR_DSP_URL/TABLE_NAME', null, {'update': { method:'PUT' } });
}]);
App.controller('SomeCtrl', ['Members', 'Members_address', '$scope', '$location', function(Members, Members_address, $scope, $location) {
$scope.member = {}; // Member data object
$scope.addresses = []; // Array of address strings
// attach to UI button to trigger the save
$scope.saveMember = function() {
// Save the member
Member.save($scope.member).then(
// Handle Member save success
function(result) {
// Check for addresses
if ($scope.addresses) {
// Create a temporary var to hold our payload
// to the server
var payload = [];
// Assemble address objects for insertion in db
angular.forEach($scope.addresses, function (_string) {
// create a temporary var to hold our address
var tempAddressObject = {};
// add member id
tempAddressObject['member_id'] = $scope.member.id;
// add address
tempAddressObject['address'] = _string;
// store on temporary payload array;
payload.push(tempAddressObject);
});
// Check that we have some records in our payload
if (payload.length > 0) {
// Send to the server
Members.address.save(payload).then(
// Handle Success
function(result) {
console.log(result);
// redirect
$location.url('/members');
},
// handle Error
function (reject) {
console.log(error);
// redirect
$location.url('/members');
}
)
}
}
},
// Handle Member save error
function(reject) {
console.log(reject);
}
);
}
}]);
I have just started working with Backbone.js by creating a simple portfolio site for myself. There is not much functionality at the moment but I hope to add more & more as I become familiar with the library. I am using json to populate the data for the site which either lists all my portfolio items or displays one of them depending on the route. I have the list of portfolio items working/showing but am struggling to just show one item for a specific route (/#projects/2 for example), this is where I need help.
I have tried to keep everything as simple as possible while I start learning Backbone & have seen the various Boilerplates & tutorials, none of which helped me hugely.
At these routes, I would like this to happen:
/ - list of portfolio items
/#projects - list of portfolio items
/#projects/3 - one portfolio item
I have put my attempt into JSbin but it is not working because I don't know how to load the json properly...
http://jsbin.com/asezul/6/edit to edit
http://jsbin.com/asezul/6/ to view
I am certain my problem arises because I am loading all items into the collection rather than the model. Is that right?
How can I make this work?
How can I improve this?
[EDIT --------------------]
After Peter's very kind help below, I am still in need of assistance. He said:
A view displays 1 model, a collection view displays a list of views,
one per model.
So, how do I create a View to display one item from the json data & then create the collection view to display all of them? The reason I ask is because I can return all them without much of a problem but returning a single item is proving quite tricky..
I updated my code example too: http://jsbin.com/asezul/8/edit
[---------------------EDIT]
Please let me know if you need more information, I am new to Backbone & may have missed an important part that would help to answer my questions.
Here's some code excerpts but I suggest viewing the code in JSbin:
The model:
SITE.Project = Backbone.Model.extend({
initialize: function() {
console.log(this, this.attributes.name);
}
});
The collection:
SITE.Projects = Backbone.Collection.extend({
model: SITE.Project,
url: 'content.json',
parse: function(response) {
console.log(response);
return response.portfolio;
}
});
A view:
SITE.ProjectsView = Backbone.View.extend({
el: '#main',
initialize: function() {
_.bindAll(this, 'render');
// create a collection
this.collection = new SITE.Projects();
// Fetch the collection and call render() method
var that = this;
this.collection.fetch({
success: function () {
that.render();
}
});
},
template: SITE.helpers.template('project-list'),
render: function() {
$(this.el).html(this.template({ projects: this.collection.toJSON() }));
}
});
The router:
SITE.Router = Backbone.Router.extend({
initialize: function() {
this.el = $('#main');
},
routes: {
"": "index",
"projects": "projects",
"projects/:id": "project"
},
// Homepage / List of projects
index: function() {
var view = new SITE.ProjectsView();
},
// List of projects
projects: function() {
var view = new SITE.ProjectsView();
},
// Individual Project
project: function(id) {
var view = new SITE.ProjectView();
console.log("You are trying to reach project " + id);
}
});
So your data isn't really dynamic, so you can cheat a little bit compared to a dynamic application where the projects collection would be under constant change. I would just load the projects collection once at app startup and from there use the loaded collection and models within for the rest of your pages.
SITE.Router = Backbone.Router.extend({
initialize: function() {
//Router's don't have a this.el
//this.el = $('#main');
this.projects = new SITE.Projects();
},
routes: {
"": "index",
"projects": "projects",
"projects/:id": "project"
},
// Homepage / List of projects
index: function() {
var view = new SITE.ProjectsView({collection: this.projects});
if (this.projects.isEmpty()) {
this.projects.fetch({success: function () {
view.render();
}});
} else {
view.render();
}
},
// List of projects
projects: function() {
var view = new SITE.ProjectsView({collection: this.projects}).render();
},
// Individual Project
project: function(id) {
var view = new SITE.ProjectView({model: this.projects.get(id)}).render();
}
});
Then keep your views simpler/dumber. Views should accept their models/collections in the options argument of the constructor/initialize. Give that a try and see if you can make it work. Your code otherwise looks like you should be able to make the corresponding view code changes on your own, so I'll leave that as an exercise for the reader.
how do i use a for each loop to create accordian containers for each data returned from json file using ajax ?
i have tried this ! is it the way to it ?
dojo.xhrGet({
url:"json/"file_Name".json",
handleAs: "json",
timeout: 10000,
load: function(response,details){
container(response)},
error: function(error_msg,details){
container(error_msg, details);
}
});
//how do i use the json file to add data to the array arrFruit and then create dijit accordian container for every data in the array//
container = function(array, domConstruct) {
var arrFruit = ["apples", "kiwis", "pineapples"];
array.forEach(arrFruit, function(item, i){
domConstruct.create("li", {innerHTML: i+1+". "+item}, "properties");
});
};
//the response data from my json file is:-
[
{
"itemId": 1234,
"Name": "Name",
}]
I would suggest you re-write it into leveraging the ItemFileReadStore. The Store is a data container in which you can pull out items by their id. This means that your json needs to be changed slightly with description of what is identifier and - if any - which is the children attribute keys.
JSON:
{
identifier: 'itemId',
// you dont seem to have child references any but these are defaults
childrenAttrs: ['items', 'children'],
items: [
{
itemId: 1234,
name: 'Name'
}, {
...
}
]
}
then in your code, instead of using .xhr use .fetch in a store like so:
// 1.7+ syntax, pulling in dependencies.
// We want accordion and some other display widget like contentpane.
// Also we await domReady before calling our function
require(["dojo/data/ItemFileReadStore", "dijit/layout/AccordionContainer", "dijit/layout/ContentPane", "dojo/domReady!"], function(itemStore, accordion, contenpane) {
// this section is called when loading of itemstore dependencies are done
// create store
var store = new itemStore({
url:"json/"file_Name".json"
});
// create container
var acc = new accordion({style:"height: 300px"}, 'someDomNodeId');
// call XHR via .fetch and assign onComplete (opposed to 'load') callback
store.fetch({ onComplete: function(items) {
// items is an array of all the objects kept in jsonObject.items array
items.forEach(function(item) {
acc.addChild(new contentpane({
title: "Name for id: " + store.getValue(item, 'itemId'),
content: store.getValue(item, 'name')
}));
});
console.log(acc.getChildren()); // << would print out an array of contentpane widgets
});
});
This is howto :)
At any given time you could use the store and fetch some items, lets say you want to filter out some specific ones, call .query like so: store.fetch({query: { name: '*foo'/*all item.name ending with foo*/ }, onComplete: function(items) { /*cb func*/});
See
http://livedocs.dojotoolkit.org/dijit/layout/AccordionContainer#programmatic-example
and
http://livedocs.dojotoolkit.org/dojo/data/ItemFileReadStore