I've got my models saving and updating using the regular save with a success callback.
But I'm trying to figure out how to define a separate add view depending on if the model is new, or an update to an already existing model.
This is not server side. I have the server create/update working fine, I'm trying to figure out what is the 'right' way to update the view.
My code is pretty straight forward
Myapp.FormInput = Backbone.Views.extend({
initialize: function(){...
},
submit_form: function(){
if(this.id===undefined){
// this is a new model, so create it
model.set(new Myapp.Model.set(Myapp.Models.Helpers.serialize_objects(form)));
model.set({parent_id:parent.id});
} else {
// this is an update to an existing model, so just update the model
model.set(Myapp.Model.set(Myapp.Models.Helpers.serialize_objects(form)));
model.url+='/'+this.id;
}
model.save(model,
{success: function(model){
Myapp.Collection.add(model);
}, error: function(){
alert('error creating or updating');
}}
});
});
What I like to do is initialize the 'View' from the router with a 'Model', if I am editing, and without a 'Model', if I am creating a new 'Model' from scratch.
The idea would be :
var Workspace = Backbone.Router.extend({
routes: { '/collection/:id' : 'edit_model',
'/collection/newObject' : 'new_model
},
new_model : function(){
myView = new A_View();
},
edit_model : function(){
myView = new A_View({model:aModel});
}
});
And then, in the 'render' method, check for a model in this view instance.
But.. this only works if your application use different routes for editing and creating models. Maybe you are doing everything in the same route in a most elaborate way (or you have other reasons).
You could try something like this:
Define your model.save 'success' function in the View (that is, in the same way that you define your submit_form in your example) and add a code line where you associate the model to this view:
successSaving : function( model ){
Myapp.Collection.add( model );
this.model = model; //here you save the model in your view.
}
In order this to work, you have to bind the view to your new success function (this is the reason you define it outside).
You could make it in the view initialize function like this (more information about '_bind' here):
_.bindAll(this, 'successSaving');
Now, in your 'render' method you can render the form and, after that, check if 'this.model' is 'undefined', if it is not, you know now that you have to fill the content.
Disclaimer: I have not checked the code, so probably a copy paste is not going to work but hopefully you can get the idea.
Related
ng-grid is not updating my list.
I am trying to create a object in one view and on creating I redirect my page to list view where i want to see all my added objects. I am able to create a new object through api call and on success I call the view which should ideally show list of all objects, however I dont see the latest added object (On refresh I do see the same object)
Save function call in my create view:
$scope.save = function() {
SERVICE.$save();
$location.path('listview');
}
ListViewCtrl has and init() function which sets the ng grid option
SERVICE.query().
$promise.then(function (data) {
$scope.ngGridOptionVariable = data;
});
I dont see the new data that is being added
Any clue how to handle this issue?
I'm trying to use Backbone with REST API:
Here the code
My model:
var PagesModel = Backbone.Model.extend({
idAttribute: 'Guid',
initialize: function () {
this.on('remove', this.destroy);
},
urlRoot: '/api/pages'
});
Collection:
var PagesCollection = Backbone.Collection.extend({
model: PagesModel,
url: '/api/pages'
});
View:
var PagesView = Backbone.View.extend({
el: '#pages',
events: {
'click .removePage': 'remove',
},
initialize: function (collection) {
this.collection = collection;
this.collection.fetch();
this.template = $('#pages-template').html();
this.collection.bind('change reset', this.render, this);
},
render: function () {
var template = _.template(this.template);
$(this.el).html(template({ pages: this.collection.toJSON() }));
return this;
},
remove: function (e) {
e.preventDefault();
var id = $(e.currentTarget).closest('ul').data("id");
var item = this.collection.get(id);
this.collection.remove(item);
$(e.currentTarget).closest('ul').fadeOut(300, function () {
$(this).remove();
});
}
});
And here I'm starting up application:
$(function () {
var pagesCollection = new PagesCollection();
var pagesView = new PagesView(pagesCollection);
});
I'm clicking or Remove and in Network inspector see this link
http://localhost:54286/backbone/function%20()%20%7B%20%20%20%20%20%20var%20base%20=%20getValue(this,%20'urlRoot')%20%7C%7C%20getValue(this.collection,%20'url')%20%7C%7C%20urlError();%20%20%20%20%20%20if%20(this.isNew())%20return%20base;%20%20%20%20%20%20return%20base%20+%20(base.charAt(base.length%20-%201)%20==%20'/'%20?%20''%20:%20'/')%20+%20encodeURIComponent(this.id);%20%20%20%20}
instead of /api/pages/{guid}.
What I'm doing wrong?
I still haven't figured fully why, but you can make it work by destroying your model after the end of its removal (Backbone does one last thing after triggering the remove event: destroy the collection's reference in the model).
But what's even better, is using directly the destroy function on the model, it will remove it from the collection automatically (use {wait: true} if needed).
Edit:
Finally managed to locate the source of the problem. It's rather simple in fact. To override the model's url (calculated with urlRoot but that doesn't matter), you can pass Model#destroy a url option when calling Backbone.sync (or something that'll call it).
Now you're thinking "but I don't!". But you do. The listener (Model#destroy in your case) is given 3 arguments. Model#destroy will take the first one (the model itself) as options.
And here's the fail (I think Backbone needs a patch to this): giving an url option to Backbone.sync is the only time _.result in not used to calculate the url. So you find yourself having as url the url property of your model, which is the function you see in your call.
Now, for a quickfix:
this.on('remove', this.destroy.bind(this, {}));
This will ensure the first argument of your Model#destroy call is {} (as well as binding the context).
Bear with me a little longer.
Now, if you're still willing to call Collection#remove before destroying your model, here's a little hack: because (as I stated above) the remove event is triggered before Backbone makes sure to remove the collection's reference in your model, you don't need the urlRoot property in your model. Indeed, the model won't be in the collection anymore, but Backbone will still take the collection's url into account to get the model's url (as the reference is still there).
Not a definitive answer, but just going by the code in your question and the backbone.js documentation, the problem may be that you named your method remove and this is getting in the way of the remove method in Backbone.View.
http://backbonejs.org/#View-remove
Update:
It also looks like the output you see in the network inspector is that the definition of the Backbone.Model.url function is being appended. Meaning url is not being properly called (Maybe the () is missing by the caller?). Are you overriding Backbone.sync anywhere in your application?
Using Backbone i'm starting to build an App where i have everything cleanly separated. But now i have the following question. Where should i put the App main logic, in the views or in the model.
For example i have a view and a model, which are binded to a button and when i click that button i have to make
$.ajax(params)
do i put that in the view or the view calls a method with :
this.model.doAction(params)
which do you think is the best approach?
You can define an events property in the view which is of the format {"event selector": "callback"} for eg. {"click .collapse": "collapse"} where collapse would be a function defined as a property of the view. Then write your ajax request code in this callback function.
Also, unless I am missing something, "binding a view and model to a button" doesn't sound correct Backbone way to me. Instead you should think of one instance of model associated with one instance of the view. Whenever an attribute of the the model instance changes, a model change event will be triggered. You can bind a view function to this event so that change in the model is reflected in the view. Here is a quick example
var Book = Backbone.Model.extend({
// ...
});
var BookView = Backbone.View.extend({
initialize: function () {
this.model.bind('change', this.render, this);
},
render: function () {
// here, make changes to the dom as per changes in model
}
});
To associate a model with a view instance, you can pass the it while instantiating a new
view object..
var book = new Book({
title: "A great book"
});
var view = new BookView({model: book});
view.model.set('author', 'AGreatAuthor');
The set function call will fire change event and will result in render function of view
to be called.
Refer to the annotated source of Todos app example for a complete example.
There's a addPost function in my router. I don't want to re-create the postAddView every time the function is invoked:
addPost: function () {
var that = this;
if (!this.postAddView) {
this.postAddView = new PostAddView({
model: new Post()
});
this.postAddView.on('back', function () {
that.navigate('#/post/list', { trigger: true });
});
}
this.elms['page-content'].html(this.postAddView.render().el);
}
Here's the PostAddView:
PostAddView = backbone.View.extend({
events: {
'click #post-add-back': 'back'
}
, back: function (e) {
e.preventDefault();
this.trigger('back');
}
});
The first time the postAddView is rendered, the event trigger works well. However, after rendering other views to page-content and render postAddView back, the event trigger won't be trigger anymore. The following version of addPost works well, though.
addPost: function () {
var that = this, view;
view = new PostAddView({
model: new Post()
});
this.elms['page-content'].html(view.render().el);
view.on('back', function () {
delete view;
that.navigate('#/post/list', { trigger: true });
});
}
Somewhere you are calling jQuery's remove and that
In addition to the elements themselves, all bound events and jQuery data associated with the elements are removed.
so the delegate call that Backbone uses to bind events to your postAddView.el will be lost. Then, when you re-add your postAddView.el, there are is no delegate attached anymore and no events are triggered. Note that Backbone.View's standard remove method calls jQuery's remove; a few other things in jQuery, just as empty will do similar things to event handlers. So the actual function call that is killing your delegate could be hidden deep inside something else.
You could try calling delegateEvents manually:
this.elms['page-content'].html(this.postAddView.render().el);
this.postAddView.delegateEvents();
or better, just throw the view away and create a new one every time you need it. Your view objects should be pretty light weight so creating new ones should be cheap and a lot less hassle than trying to keep track of the existing views by hand.
If you really want to reuse the current DOM and View you do not need to set again and again the element as you are doing, everything that you call .html() you are destroying the DOM of the View and generating again and losing events. Also I prefer always to add the "el" in the DOM before render the View. I will have your function in this way:
addPost: function () {
if (!this.postAddView) {
this.postAddView = new PostAddView({
model: new Post()
});
this.postAddView.on('back', this.onBack);
this.elms['page-content'].html(this.postAddView.el);
}
this.postAddView.render();
},
onBack : function () {
this.navigate('#/post/list', { trigger: true });
}
I'm not fan of the use of local variables to refer to "this". If all of your Views uses _.bindAll(this) in the initialize method you could bind your events to your view and could use this(check how I transformed onBack).
With my code there is not a need to manually call this.delegateEvents()
I have looked over lots of examples of ExtJS 4 MVC, and they all pretty much show the same thing: The application creates a viewport, loads in a view, and has a 'controllers' defined, which init's the controller:
Ext.application({
name: 'AM',
controllers: [
'Users'
],
launch: function() {
Ext.create('Ext.container.Viewport', {
layout: 'fit',
items: [
{
xtype: 'userlist'
}
]
});
}
});
Thats great, but now let's say in my application I want a button contained within my view to open a whole new controller/view, how do you do that?
I think what I am looking for is a way to say something like:
- Create Controller (run it's init code)
- in the controller init code, create the view and display it
Is that correct, and how do you do this?
I want to clarify that in my case I would need TWO individual instances of the SAME controller/view combination. For example, I might have a view with a tab panel and two tabs. I then want to put TWO separate instances of a 'Users' controller and 'user.List' view inside each tab.
I think what I am looking for is a way to say something like: - Create Controller (run it's init code) - in the controller init code, create the view and display it
In extjs, all controllers get instantiated when the application is loaded. You can use the launch method in the Application class to start off a view. And Have a controller listen to events of that view. In a controller, you can always access the other controller using the application object:
this.application.getController('ControllerName1').displayListPanel(options);
In the above code, I am calling a method displayListPanel that is available in ControllerName1 controller. This method holds the code to display a view (a grid panel) onto the screen. Similarly, I can have methods that create views like a new form for data entry. Here is another example:
this.application.getController('ControllerName1').newDateForm();
and In my method:
newDataForm : function() {
var view = Ext.widget('form',{title: 'New Data'});
view.show();
},
Just checked the documentation of new controller and view classes.
It seems to me, that you could always find needed view when you need it.
For example you can:
//somewhere in controller
this.getView('Viewport').create(); // or .show()
check this and view class methods:
http://docs.sencha.com/ext-js/4-0/#!/api/Ext.app.Controller-method-getView