Iam developing a cross platform application and iam using backbone framework.
i have many views in them and i render the views as follows
For eg:
sampleFunction: function() {
var sampleObject = new window.sampleView();
sampleObject.Render();
}
Then one of my friend happen to see my code and he said, every variable created inside a function should be deleted,
sampleFunction: function() {
var sampleObject = new window.sampleView();
sampleObject.Render();
delete sampleObject;
}
I searched the whole web and couldn't find anything relevant to his theory. In web it says a variable is never deleted.
so i showed him a sample like below,
<script>
function onBodyLoad() {
var test = "i'am alive";
delete test;
alert(test);
}
</script>
<body onLoad="onBodyLoad()">
So my question is what delete operator really do? just used for deleting an object property?
What happens to the object and variables that brought up to the DOM when using an MV framework like backbone?
Does the object and variables will be cleared when a new view is loaded?
Do we need to unbind or destory view each time just before loading another view?
Regarding the delete operator, it will delete the property from the object. It wont do anything related to Freeing memory.
In backbone, whenever model is attached with some views it'll be in DOM. view.remove(), will remove the view and the attached model from DOM.
Creating a new view wont delete existing views. You can set a listener to remove the existing views when you create new views.
See this question: Destroy or remove a view in Backbone.js
Related
I need to run some client-side javascript from a button in a form view in Odoo 8. This button runs a python method which returns this dictionary:
{"type": "ir.actions.client",
"tag": "my_module.do_something",}
do_something is defined in a .js file as follows:
openerp.my_module = function (instance) {
instance.web.client_actions.add("my_module.do_something", "instance.my_module.Action");
instance.my_module.Action = instance.web.Widget.extend({
init: function() {
// Do a lot of nice things here
}
});
};
Now, the javascript is loaded and executed properly, but even before launching the init function, Odoo loads a brand new, blank view, and once the javascript is over I can't get browse any other menu entry. In fact, wherever I click I get this error:
Uncaught TypeError: Cannot read property 'callbackList' of undefined
What I need instead is to run the javascript from the form view where the button belongs, without loading a new view, so both getting the javascript stuff done and leaving all callbacks and the whole environment in a good state. My gut feeling is that I shouldn't override the init funcion (or maybe the whole thing is broken, I'm quite new to Odoo client-side js) , but I couldn't find docs neither a good example to call js the way I want. Any idea to get that?
Sorry, I don't work on v8 since a lot of time and I don't remember how to add that, but this might help you: https://github.com/odoo/odoo/blob/8.0/doc/howtos/web.rst
Plus, if you search into v8 code base you can find some occurence of client actions in web module docs https://github.com/odoo/odoo/search?utf8=%E2%9C%93&q=instance.web.client_actions.add
Thanks to the pointers simahawk posted in another answer, I have been able to fix my js, which is now doing exactly what I needed. For your reference, the code is as follows:
openerp.my_module = function (instance) {
instance.web.client_actions.add("my_module.do_something", "instance.my_module.action");
instance.my_module.action = function (parent, action) {
// Do a lot of nice things here
}
};
I've got some problems with my Backbone, RequireJS & jQuery mobile application.
When I use a form view 2 times the form submit event is fired twice.
Each new view is added to the body and the previous view will be removed. For that I use this code in my app.js file:
$(document).on("mobileinit", function () {
$.mobile.linkBindingEnabled = false;
$.mobile.hashListeningEnabled = false;
$(document).on('pagehide', 'div[data-role="page"]', function (event, ui) {
$(event.currentTarget).remove();
});
})
Router.js
define([
'jquery',
'backbone',
'views/projects/ProjectsView',
'views/projects/AddProjectView'
], function($, Backbone, ProjectsView, AddProjectView) {
return Backbone.Router.extend({
routes: {
'addProject': 'addProject',
'editProject/:projectId': 'editProject',
'*actions': 'showProjects' // Default
},
addProject: function() {
new AddProjectView().render();
},
editProject: function(projectId) {
require([
"views/projects/EditProjectView",
"collections/ProjectsCollection",
"models/ProjectModel"
], function (EditProjectView, ProjectsCollection, ProjectModel) {
var projectsCollection = new ProjectsCollection();
projectsCollection.fetch();
var project = projectsCollection.get(projectId);
if (project) {
var view = new EditProjectView({model: project, projectsCollection: projectsCollection});
view.render();
}
});
},
showProjects: function() {
new ProjectsView().navigate();
}
});
});
I've uploaded my code to a directory on my website: http://rickdoorakkers.nl/np2
If you go through the following steps, you'll see the problem:
Add a project
Add a second project with a different name
Open a project by clicking on it and change the values and save it
As you can see the event of adding a project is launched and there's a project added instead of changed.
This same problem also occurs when you try to change 2 projects after each other. The first project is edited then.
Is there somebody who can tell me what I'm doing wrong?
Thanks!
Rido, your code is really hard to read for me because of the way it's mixing together a few things and not following any of the usual conventions for Backbone.
For your specific problem, I have a feeling the problem is that you binding both the Edit view and the New view to body (el: body), both respond to the event submit, and you are never clearly cleaning up the views, so I think that whenever you add a project and then edit it, the add view is still in memory, still bound to the submit event and still answering the call = new project with the new name, instead of editing.
It's 'easy' to fix in a dirty way, by adding a call to stopListening after adding, but the real problem is that you are binding to body, and mixing togethers the Backbone Router and manual hash control + other weird patterns, such as fetching the collection every 5 lines (you could just create one at the start of the App and always use it! here it's localstorage so it doesn't matter but if you ever move to a remote storage, you'll regret it... fetch() reset the collection and do a full reload!). May I suggest you maybe try to rewrite this without caring about jQuery mobile and just try to make it work with Backbone.Router + a single collection + not binding to body but instead, create the views on the fly and append them to body / remove when you are done? You'll see the bugs will be less weird and easier to track.
I'm trying to pick up backbone and would like to do a simple image gallery app but am having problems.
I'd like to instantiate my view with a collection of items (named itemsArray) and then load the first model of that collection into a view. This view will provide a previous and next button and I've set up the events.
I'm confused about how to pass this collection into a Backbone view and tell it to load the first element. Also, manipulating the collection with prev / next doesn't seem to be working correctly. I asked this question before (backbone - trying to rerender a view with the next or previous model in a collection) but think it's better if I post the whole fragment here. I have seen sample code similar to this in tutorials but I think the loading of the single model with knowledge of the collection has proven problematic. I have provided the full source code below:
<div id='item-container'></div>
<script type='text/template' id='tmp'>
<%=header %>
<img src='<%=assets[0].url %>' />
<div class='next-btn'>next</div> <div class='prev-btn'>prev</div>
</script>
<script>
$(document).ready(function(){
var arc={};
// 2. items to be made into an Items collection
var itemsArray=[{'id':4, 'header':'my header','detail':'my detail', 'price':'$12.25', assets:[{'url':'/tmp-images/surf.jpg'}]},
{'id':7, 'header':'my header 2','detail':'my detail 2', 'price':'$14.25', assets:[{'url':'/tmp-images/jamaica.jpg'}]},
{'id':11, 'header':'my header 3','detail':'my detail 3', 'price':'$21.00',assets:[{'url':'/tmp-images/jamaica-2.jpg'}]}
];
// 3. item class
arc.Item = Backbone.Model.extend({});
// 4. items collection made up of item
arc.Items = Backbone.Collection.extend({
model:arc.Item
});
var items = new arc.Items(itemsArray);
//console.log(menu_items);
arc.ItemsGalleryView = Backbone.View.extend({
el: $('#item-container'),
events: {'click .next-btn' : 'loadNext', 'click .prev-btn':'loadPrevious' },
template:_.template($('#tmp').text()),
initialize: function() {
_.bindAll( this, 'render' );
// 5. this is definitely wrong, trying to render template with single instance of model
// seems like I should be doing something like this.collection.at(0) or something but that isn't working
//this.render(this.model);
/* 6. works but not an array
this.render({header:'my name',assets:[
{url:'/image/some.jpg'}
]});
*/
// 7. not working header is not defined
this.render(this.collection.at(0)); // error 'header is not defined'
console.log(this.collection.at(0)); // in console this kinda looks like a model but do I need to call another method on it?
},
render: function(xModel) {
var compiled=this.template(xModel);
this.$el.html(compiled);
return this;
},
loadNext: function(){
// 8. not sure what to do here
},
loadPrevious: function(){
console.log('i want to load Previous');
// 9. not working
this.render(this.collection.prev());
}
});
var itemView=new arc.ItemsGalleryView({ collection: items });
});
</script>
Any help is greatly appreciated. How would I pass a collection? and then manipulate where I am in the collection via events?
thx
When passing a model to a template, note that every attribute is held in a special hash. For you, it'd be model.attributes.header. (so <%= attributes.header %>)
It is usually suggested you pass your model attributes directly to your templates though: this.render(this.collection.at(0).toJSON()); (and that'll work with your current template)
As already mentioned, you usually do not pass arguments to render(). You're also not actually appending your #tmpl template to your #item-container.
I have created a working jsfiddle with your code. I've demonstrated creating a separate view for your collection and model, as I feel this was confusing things.
I'm a big fan of ICanHaz, and I'm trying to directly intregrate it into a new Marionette application I'm building. However, going off this post, I have written this that reaches into the render method and changes it in Marionette:
// Set up Initalizer
APP.addInitializer(function() {
//Reach into Marionette and switch out templating system to ICH
Backbone.Marionette.Renderer.render = function(template, data){
return ich[template](data);
}
//Create Router
new APP.Routers.GlobalRouter();
//Start Backbone History
Backbone.history.start();
});
If I walk through this function, all the data seems to work fine. However, when put into use and trying to use it for layouts and Item Views, nothing gets appended or inserted. This is from my GlobalRouter:
//Grab the main Layout
var layout = new APP.Views.LayoutView();
//Render that layout
layout.render();
//Make the model
var userModel = new APP.Models.UserModel({
"user_name" : "nweingartner#awesome.com",
"tenant" : "Ginger Ale is Great"
});
//Make the Header Region
var headerRegion = new APP.Views.HeaderView({model: userModel});
layout.header.show(headerRegion);
This all happens in a method that gets called when index is hit. There are no JS errors so I have nothing to go on. However, it in the render function I append the data to the body, it will add (however ruining my layout and region structure).
I am storing my templates in index.html.
Can anyone help with this?
Okay, I couldn't find an easy way to do this using ICH. However, due to another SO I found, very similar functionality can be found just using Mustache.
Using this code:
Backbone.Marionette.TemplateCache.prototype.compileTemplate = function(rawTemplate) {
return Mustache.compile(rawTemplate);
}
Lets you change the renderer so you can pull mustache templates from index.html using Marionette's template call. A mustache template looks like this:
<script id="headerTemplate" type="text/template">
<p>{{user_name}}</p>
<p>{{tenant}}</p>
</script>
The difference is that the type is 'text/template' as opposed to 'text/html'. Otherwise it acts very similar.
Hope this helps someone else.
These days I find myself putting a lot of my code in the $(document).ready() which does not seem clean to me. For example, if I am creating something that will query my database via ajax and return it and append it to my list i would do something like this:
$(function(){
//Initialize my DOM elements
$MyList = $("#MyList"),
$MyButton = $("#MyButton");
//Add my Click event
$MyButton.click(function(){
$.ajax({
type: 'POST',
url: "/lists/getmylist",
contentType: "application/json",
success: function(results){
//Parse my results and append them using my favorite templating helper
var jsonResults = $.parseJSON(result);
$MyList.mustache("my-template", jsonResults);
}
});
})
});
Now I know this is a small example but it starts to get really big and messy when I have multiple click events, ajax requests etc. It all ends up going in my document ready. I know that I can possibly put all my ajax requests in an external javascript file to help make it cleaner, but is this architecture in general ok? just seems like its really messy. I have seen others use plugin architectures or init functions. I usually have this document ready at the bottom of all my pages and just throw in whatever is necessary to make my page work correctly. Is this a good way to structure my js?
I think the addition of some Model objects and general object oriented programming principals might go a long way here. If you break your your data fetching and storing out into model classes it should help a lot.
Here are some links that should get you started thinking about OO with Javascript.
Writing Object-Oriented JavaScript
Javascript Design Patterns
Javascript: prototypal inheritance
Javascript: prototypal inheritance 2
Another thing that might help out would be to break the Javascript into multiple files. One for global scripts that might be included via a header that attaches to all your pages and a script for each of your pages that requires it.
Perhaps Backbone.js ( or one of the other frameworks ) could be part of the rescue you are looking for.
I found Backbone immensely helpful organising some inherited spaghetti. Your starting point might be to transition your massive document ready into a backbone view (or multiples of)
Organise your scripts by separating out the views, collections, models into individual files then bundle and minify them together into a single file so the browser only needs to make one request instead of many.
ASP.NET MVC4 can do the bundling for you, it also works similarly on MVC3
This is just a example of simple starting point, there are more advanced techniques (eg. AMD, require.js) to reduce the script size per page, but with caching and gzip I find that the single everything script bundle is fine for a lot of cases.
As for your example, here's a possible backbone implementation
Remember to namespace out your code...
var app = app || {};
$(function ($) {
// depending on your server setup you might be able to just override the url
// and get away with what you want. Otherwise you might look into overriding
// the save/fetch or sync methods of the collection
app.MyListCollection = Backbone.Collection.extend({
url: '/lists/getmylist'
});
app.MyListView = Backbone.View.extend({
//bind the view to the existing element in the HTML.
el: '#MyList',
// your mustache template
template:$('#list-template').html(),
// hook up your event handlers to page components
//(within the context of your el)
events: {
'click #MyButton': 'onMyButtonClick'
},
//called on object creation.
initialize: function () {
//you could create a collection here or pass it into the init function
this.collection = new app.MyListCollection();
//when the collection is changes, call the render method
this.listenTo(this.collection, 'reset', this.render);
},
// render is named by convention, but its where you would "redraw" your view and apply your template
render: function () {
this.$el.html(
Mustache.render(
this.template(this.collection.toJSON())));
return this;
},
//your click handler
onMyButtonClick: function(e){
this.collection.fetch();
}
});
});
use your doc ready to spin up whatever backbone functionality you need
and use it bootstrap your javascript with any server side data that you may have.
$(function () {
// create an instance of your view
new app.MyListView();
//example bootstrap using razor
app.title = #Model.Title;
});