I get data back from a url: /app/api/assetDetail/{id} where the id is a parameter passed to a presenter which sets up the new assetModel and assetView.
I'm struggling with where to build and call the above url with id and then set the model.
asset presenter
define([
'text!html/regions/tplAssetPage.html',
'views/assetView',
'collections/assets',
'models/asset'
],
function (template, AssetView, Assets, Asset) {
return {
load: function (params) {
$(mv.sections.mainContainer).html(template);
var view1 = 'assetView',
id = params || '';
this.model = new Asset({
wid: params, //sets id on model
url: function(){
var url = 'api/assetDetail/' + params;
return url;
}
});
mv.i.views[view1] = new AssetView({
'el': '#asset-container',
model: asset
});
mv.i.views[view1].setup();
},
};
});
asset model
define([], function () {
return Backbone.Model.extend({
defaults: {
id:''
},
initialize: function () {}
});
});
asset view
define([
'text!html/tplAsset.html',
'models/asset'
], function (template, Asset) {
return Backbone.View.extend({
el: '',
template: _.template(template),
initialize: function () {},
render: function () {
//var data = this.model.toJSON();
this.$el.html(this.template(data));
},
setup: function () {
var self = this;
$.when(self.model.fetch())
.done(function () {
//console.log(self.model.toJSON());
self.render();
})
.fail(function () {
console.log('request for data has failed');
});
},
events: {},
});
});
now getting these errors:
ERR: Routing error Error: A "url" property or function must be specified
at Backbone.View.extend.setup (/js/views/assetView.js:36:22)
$.when(self.model.fetch())
at Object.load (/js/presenters/asset.js:34:23)
mv.i.views[view1].setup();
To set the model url dynamically in your model instance:
var asset = new Asset({
wid: params, //sets id on model
url: function(){
var url = '/app/api/assetDetail/' + this.id;
return url;
}
});
Then, after you have set the url, do asset.fetch()
Note that this will now be the URL for any communication with the server (save and fetch) for that model instance. If you need greater flexibiliy, you'll need to do adjust the Bacbkone sync method for your model.
UPDATE:
Once you've fetched the data you want for the model, you then can call the render function:
render: function () {
this.$el.html( this.template(this.model.toJSON() ) );
return this;
}
This will then render model data in your templates. If you're using the underscore templates it will look like:
<p>some html<span> <%= data %> </span><p>
If you want to check what you have fetched, don't forget that fetch accepts success and error callbacks: http://backbonejs.org/#Model-fetch
Related
I have Person model and I am retrieving a person info inside a view. The success callback FetchSuccess executes when the response has an object. But when response is empty, the callback is not called. Any Guess?
Models.Basic = Backbone.Model.extend({
parse: function(response) {
return response;
}
});
Models.PersonModel = Backbone.Model.extend({
url: function() {
return '/person/' + this.data.id;
}
});
Backbone.View.extend({
template: Templates['template'],
initialize: function(options) {
this.id = options.id;
_.bindAll(this, 'FetchSuccess');
this.personModel = new Models.PersonModel();
this.model = new Models.Basic();
this.fetchData();
return this;
},
render: function() {
this.$el.append(this.template(this.model.toJSON()));
},
fetchData: function() {
this.personModel.data = {
id: this.id
};
this.personModel.fetch({
context: this,
success: this.FetchSuccess
});
},
FetchSuccess: function() {
this.model.set({
name: this.personModel.get('name');
});
this.render();
}
});
this.personModel = new Models.PersonModel();
This is a Backbone Model, not a collection.
this.personModel.fetch({
reset: true, // this doesn't exist on model
success: this.FetchSuccess
});
You can't fetch a model without an id. Also, the model, when fetching, expects an object to be returned.
If you want to fetch a specific person, give an id to the model, then fetch.
this.personModel = new Models.PersonModel({ id: "id_here" });
// ...
this.personModel.fetch({
context: this,
success: this.FetchSuccess
});
Here's the code with the corrections
// parse isn't needed if you're not going to parse something
Models.Basic = Backbone.Model.extend({});
Models.PersonModel = Backbone.Model.extend({
urlRoot: 'person/', // this handles putting the id automatically
});
Backbone.View.extend({
template: Templates['template'],
initialize: function(options) {
this.id = options.id;
// pass the id here
this.personModel = new Models.PersonModel({ id: this.id });
this.model = new Models.Basic();
this.fetchData();
// makes no sense in the initialize since it's never called
// manually and never used to chain calls.
// return this;
},
render: function() {
// render should be idempotent, so emptying before appending
// is a good pattern.
this.$el.html(this.template(this.model.toJSON()));
return this; // this is where chaining could happen
},
fetchData: function() {
// This makes no sense unless you've stripped the part that uses it.
// this.personModel.data...
this.personModel.fetch({
context: this, // pass the context, avoid `_.bindAll`
success: this.onFetchSuccess,
error: this.onFetchError
});
},
onFetchSuccess: function() {
this.model.set({
name: this.personModel.get('name')
});
this.render();
},
onFetchError: function() { this.render(); }
});
You could catch the error with the error callback, or just do nothing and render by default, and re-render on fetch.
You could also listen to the model events (inside the initialize):
this.listenTo(this.personModel, {
'sync': this.FetchSuccess,
'error': this.onFetchError
});
this.personModel.fetch();
I have a Backbone Marionette app with Router and a Controller. In my app you can view a collection of texts (index route with collection fetching from server), can view existing collection of texts (indexPage route without fetching from server) and can create a new text (form route). Views of list texts and create form are different from each other and changes in region.
I want to add a successully saved model to a collection and then redirect to indexPage route, but what is the best way to get a texts collection from _FormView success callback? Or how to restruct an app to do it simple?
I can send event to a controller with Backbone.Radio but want to deal without it.
Routes
router.processAppRoutes(controller, {
'': 'index',
'index': 'indexPage',
'create': 'form'
});
Controller
_Controller = Marionette.Controller.extend({
initialize: function () {
this.list = new _MainTexts();
},
index: function () {
if (!_.size(this.list)) {
var
self = this;
this.list.fetch({
success: function (collection, response, options) {
self.indexPage();
return;
}
});
}
this.indexPage();
},
indexPage: function () {
var
textsView = new _TextsView({
collection: this.list
});
application.getRegion('contentRegion').show(textsView);
},
form: function () {
var
formView = new _FormView({
model: new _MainText()
});
application.getRegion('contentRegion').show(formView);
}
});
Views
_TextView = Marionette.ItemView.extend({
className: 'item text',
template: function (serialized_model) {
return _.template('<p><%= texts[0].text %></p>')(serialized_model);
}
});
_TextsView = Marionette.CollectionView.extend({
className: 'clearfix',
childView: _TextView
});
Form view
_FormView = Marionette.ItemView.extend({
template: '#form-template',
ui: {
text: 'textarea[name="text"]',
submit: 'button[type="submit"]'
},
events: {
'click #ui.submit': 'submitForm'
},
submitForm: function (event) {
event.preventDefault();
this.model.set({
text: this.ui.text.val()
});
this.model.save({}, {
success: function (model, response, options) {
???
}
});
}
});
Ok, my problem solution is here. In controller action "form" I create event listener
var
formView = new _FormView({
model: model
});
formView.on('formSave', function (model) {
if (id == null) {
self.list.add(model);
}
...
});
Then in form view I trigger event
this.model.save({}, {
success: function (model, response, options) {
if (response.state.success) {
self.trigger('formSave', model);
}
}
});
That's all:)
For some reason, I am getting a TypeError in my JavaScript regarding a supposed Backbone model object for which I am trying to call "model.destroy()":
Here's my Backbone code:
var Team = Backbone.Model.extend({
idAttribute: "_id",
urlRoot: '/api/teams'
});
var TeamCollection = Backbone.Collection.extend({
model: Team
});
var teamCollection = new TeamCollection([]);
teamCollection.url = '/api/teams';
teamCollection.fetch(
{
success: function () {
console.log('teamCollection length:', teamCollection.length);
}
}
);
var UserHomeMainTableView = Backbone.View.extend({
tagName: "div",
collection: teamCollection,
events: {},
initialize: function () {
this.collection.on("reset", this.render, this);
},
render: function () {
var teams = {
teams:teamCollection.toJSON()
};
var template = Handlebars.compile( $("#user-home-main-table-template").html());
this.$el.html(template(teams));
return this;
},
addTeam: function (teamData) {
console.log('adding team:', team_id);
},
deleteTeam: function (team_id) {
console.log('deleting team:', team_id);
var team = teamCollection.where({_id: team_id}); //team IS defined here but I can't confirm the type even when logging "typeof"
console.log('team to delete', typeof team[0]);
console.log('another team to delete?',typeof team[1]);
team.destroy({ //THIS FUNCTION CALL IS THROWING A TYPEERROR
contentType : 'application/json',
success: function(model, response, options) {
this.collection.reset();
},
error: function(model, response, options) {
this.collection.reset();
}
});
}
});
So I am fetching the data from the node.js server, and the server is returning JSON. The JSON has cid's and all that jazz, so those objects were once Backbone models at some point.
I just don't know why the type of team would not be a Backbone model.
Any ideas?
.where returns an array. You need to use .findWhere instead.
Or call destroy for every model in the resulting array.
.where({...}).forEach(function(model){
model.destroy();
});
i have a problem with backbone.js. I'm creating a frontend for an existing api, for me unaccessable. The problem is that when I try to add a new model to a collection, i can see in my firebug that every time backbone tries to create the model it appends the attribute name to the url.
Example:
default url = /api/database
when i perform a GET = /api/database
when i perform a GET/POST with object {"name": "test"} =
/api/database/test is the result
Anyone knows how to avoid that behaviour?
Greetings Kern
My View:
window.databaseView = Backbone.View.extend({
el: '#content',
template: new EJS({url: 'js/templates/databaseView.ejs'}),
initialize: function() {
var self = this;
this.collection.fetch({
success: function() {
console.log(self.collection);
var test = self.collection.get("_system");
console.log(test);
self.collection.get("_system").destroy();
self.collection.create({name: "test"});
}
});
},
render: function(){
$(this.el).html(this.template.render({}));
return this;
}
});
Model:
window.Database = Backbone.Model.extend({
initialize: function () {
'use strict';
},
idAttribute: "name",
defaults: {
}
});
Collection:
window.ArangoDatabase = Backbone.Collection.extend({
model: window.Database,
url: function() {
return '../../_api/database/';
},
parse: function(response) {
return _.map(response.result, function(v) {
return {name:v};
});
},
initialize: function() {
this.fetch();
},
getDatabases: function() {
this.fetch();
return this.models;
},
dropDatabase: function() {
},
createDatabse: function() {
}
});
By default, Backbone create models URLs this way: {collection url}/{model id}.
It consider the collection URL to be a base URL in a RESTful way.
Here you only want to set the Model url property to the URL you whish to call. That'll overwrite the default behavior. http://backbonejs.org/#Model-url
I'm building small one page application with rails 3.1 mongodb and backbonejs.
I have two resources available through json api. I created two models and collections in backbone which look like this
https://gist.github.com/1522131
also I have two seprate routers
projects router - https://gist.github.com/1522134
notes router - https://gist.github.com/1522137
I generated them with backbonejs-rails gem from github so code inside is just template. I initialize my basic router inside index.haml file
#projects
:javascript
$(function() {
window.router = new JsonApi.Routers.ProjectsRouter({projects: #{#projects.to_json.html_safe}});
new JsonApi.Routers.NotesRouter();
Backbone.history.start();
});
I don't want fetch notes when application is starting, because there is big chance that user will never look inside notes. So there isn't good reason to fetch it on start. Inside NotesRouter in all action I rely on #notes variable but without .fetch() method this variable is empty. Also I should can reproduce notes view from url like
/1/notes/5
project_id = 1
note_id = 5
What is best practices in backbonejs to solve this kind of problem ?
Why don't you lazy load the notes when it's requested? Here's an example:
var State = Backbone.Model.extend({
defaults: {
ready: false,
error: null
}
});
var Note = Backbone.Model.extend({
initialize: function () {
this.state = new State();
}
});
var Notes = Backbone.Collection.extend({
model: Note,
initialize: function () {
this.state = new State();
}
});
var NoteCache = Backbone.Model.extend({
initialize: function () {
this._loading = false;
this._loaded = false;
this._list = new Notes();
},
_createDeferred: function (id) {
var note = new Note({ id: id });
this._list.add(note);
this._load();
return note;
},
getNote: function (id) {
return this._list.get(id) || this._createDeferred(id);
},
getNotes: function () {
if (!this._loaded)
this._load();
return this._list;
},
_load: function () {
var that = this;
if (!this._loading) {
this._list.state.set({ ready: false, error: null });
this._loading = true;
$.ajax({
url: '/api/notes',
dataType: 'json',
cache: false,
type: 'GET',
success: function (response, textStatus, jqXHR) {
_.each(response.notes, function (note) {
var n = that._list.get(note.id);
if (n) {
n.set(note);
} else {
that._list.add(note, { silent: true });
n = that._list.get(note.id);
}
n.state.set({ ready: true, error: null });
});
that._list.state.set({ ready: true, error: null });
that._list.trigger('reset', that._list);
that._loaded = true;
},
error: function (jqXHR, textStatus, errorThrown) {
that._list.state.set({ error: 'Error retrieving notes.' });
that._list.each(function (note) {
note.state.set({ error: 'Error retrieving note.' });
});
},
complete: function (jqXHR, textStatus) {
that._loading = false;
}
});
}
}
});
In this example, I'm defining a NoteCache object that manages the lazy loading. I also add a "state" property to the Note model and Notes collection.
You'll probably want to initialize NoteCache somewhere (probably inside your route) and whenever you want a note or notes, just do this:
var note = noteCache.getNote(5);
var notes = noteCache.getNotes();
Now inside your view, you'll want to listen for state changes in case the note/notes is not loaded yet:
var NoteView = Backbone.View.extend({
initialize: function(){
this.note.state.bind('change', this.render, this);
},
render: function(){
if (this.note.state.get('error') {
// todo: show error message
return this;
}
if (!this.note.state.get('ready') {
// todo: show loader animation
return this;
}
// todo: render view
return this;
}
});
I haven't tested this, so there may be some bugs, but I hope you get the idea.