I'm having issues loading a collection of Backbone Views with RequireJS - as they aren't loading in the correct order.
Below is a simple example of what I am trying to achieve - a page loops through a collection of widgets, and, using its 'template' attribute, get its Backbone View. Its crucial that these are displayed in order, and they are currently being displayed in random order.
page.js
collection.each(function(widget) {
require(['order!views/widgets/' + widget.get('template')], function(WidgetView) {
WidgetView.render();
})
}
widgets/widgetView.js (generic view)
define(['underscore','backbone'], function(_, Backbone) {
var WidgetView = Backbone.View.extend({
render: function() {
// .. show view
}
});
return WidgetView;
});
I'm aware of the order! plugin for RequireJS, but it doesn't seem to be doing its job. Is there something that I'm doing wrong?
As far as I can tell, issuing multiple require calls will fetch the dependencies in asynchronous mode. You probably need to build an array of the views and only then require them. For example,
var widgets=[];
collection.each(function(widget) {
widgets.push('order!views/widgets/' + widget.get('template'));
});
require(widgets, function() {
var viewclass, view;
for (var i=0, l=arguments.length; i<l; i++) {
viewclass=arguments[i];
view=new viewclass();
view.render();
}
});
Related
I'm evaluating using Casper.js to do functional/acceptance testing for my app. The biggest problem I've seen so far is that my app is an SPA that uses handlebars templates (which are compiled to JS) The pages of my app are nothing more than a shell with an empty div where the markup will be injected via JS.
I've messed around with Casper a little and tried using its waitFor functions. All I can seem to get from it are my main empty page before any of the markup is injected. I've tried waitForSelector but it just times out after 5 seconds. Should I try increasing the timeout? The page typically loads in a browser very quickly, so it seems like there may be another issue.
I'm using Yadda along with Casper for step definitions:
module.exports.init = function() {
var dictionary = new Dictionary()
.define('LOCALE', /(fr|es|ie)/)
.define('NUM', /(\d+)/);
var tiles;
function getTiles() {
return document.querySelectorAll('.m-product-tile');
}
function getFirstTile(collection) {
return Array.prototype.slice.call(collection)[0];
}
var library = English.library(dictionary)
.given('product tiles', function() {
casper.open('http://www.example.com/#/search?keywords=ipods&resultIndex=1&resultsPerPage=24');
casper.then(function() {
// casper.capture('test.png');
casper.echo(casper.getHTML());
casper.waitForSelector('.m-product-tile', function() {
tiles = getTiles();
});
});
})
.when('I tap a tile', function() {
casper.then(function() {
casper.echo(tiles); //nodelist
var tile = Array.prototype.slice.call(tiles)[0];
casper.echo(tile); //undefined!
var pid = tile.getAttribute('data-pid');
})
})
.then('I am taken to a product page', function() {
});
return library;
};
Any Angular, Backbone, Ember folks running into issues like this?
On my page, I will have a slider which will have html inside, that will be populated using data fetched from the server.
I would like for the slider to be built and populated using jQuery, leaving Backbone to handle the viewing and events.
For this, I would need the data from the Backbone model, which leads to my question:
Is it fine to pass the model into jQuery OR would it be better to leave everything to the jQuery function and do a $.ajax fetch in the function instead.
Backbone
define(['backbone', 'models/model','slider'], function(Backbone, Model, Slider) {
var View = Backbone.View.extend({
render: function() {
$('#slider').Slider( this.model );
var template = _.template();
this.$el.html(template);
return this;
}
});
return View;
});
jQuery
(function($, window, document, undefined) {
"use strict";
$.fn.Slider = function(model) {
// do stuff with model
}
})(jQuery, window, document);
I'd implement the event handling and logic directly in the backbone view, where you already have the model and all other backbone objects you might possibly want to use. If you need the model data in the slider initialization you should not pass the model itself, but the json representation of the model data using
$('#slider').Slider( this.model.toJSON() );
I have a Backbone multipage app written with the use of RequireJS. Since it's multipage I decided not to use a router as it got too messy. I've tried multiple ways of creating a singleton object to be used throughout the app
var singletonModel= Backbone.Model.extend({
}),
return new singletonModel;
For the above I'm just referencing the singletonModel model in my class using the define method and then calling it as is
this.singleton = singletonModel;
this.singleton.set({'test': 'test'});
On a module on my next page when I then call something similar to
this.singleton = singletonModel;
var test = this.singleton.get('test');
The singleton object seems to get re-initialized and the test object is null
var singletonModel= Backbone.Model.extend({
}, {
singleton: null,
getSingletonModelInst: function () {
singletonModel.singleton =
singletonModel.singleton || new singletonModel;
return singletonModel.singleton;
}
});
return singletonModel;
For the above I'm just referencing the singletonModel model in my class using the define method and then calling it as is
this.singleton = singletonModel.getSingletonModelInst();
this.singleton.set({'test': 'test'});
On a module on my next page when I then call something similar to
this.singleton = singletonModel.getSingletonModelInst();
var test = this.singleton.get('test');
Again it looks like the singleton object is getting re-initialized and the test object is null.
I'm wondering if the issue is because I'm using a multi-page app with no router so state is not been preserved? Has anyone tried using a singleton object in a multi-page app before? If so did you do anything different to how it's implemented on a single-page app?
Thanks,
Derm
Bart's answer is very good, but what it's not saying is how to create a singleton using require.js. The answer is short, simply return an object already instanciated :
define([], function() {
var singleton = function() {
// will be called only once
}
return new singleton()
})
Here we don't have a singleton anymore :
define([], function() {
var klass = function() {
// will be called every time the module is required
}
return klass
})
It's may sound a little ... but, you doing a multi-page application, so when you move to next page, a whole new document was loaded into the browser, and every javascript on it will be loaded too, include require.js and your model. so the require.js was reloaded, and it create your model again, so you got a different model than you thought.
If above was true, my opinion is your model will "live" on a single page, when you jump to then next page, that model was "kill"ed by browser. so If you want see it again, store it on somewhere else, maybe server or localstroe, on the former page. and in the next page load it back from server or localstore, and wrap it into a Backbone model, make it "live" again.
Here is how I implemented a singleton in a recent Backbone/Require application. State is remembered across any number of views.
instances/repoModel.js
define(['models/Repo'],
function(RepoModel){
var repoModel = new RepoModel();
return repoModel;
}
);
models/Repo.js
define(['backbone'],
function(Backbone){
return Backbone.Model.extend({
idAttribute: 'repo_id'
});
}
);
views/SomePage.js
define(['backbone', 'instances/repoModel'],
function(Backbone, repoModel) {
return Backbone.View.extend({
initialize: function() {
repoModel.set('name', 'New Name');
}
});
}
);
I have two different backbone js file for 2 different view. I need to call the render method of the second js file from the first one. How can i do that
I have one backbone.js file which as a view called DocumentsPageView. In my second backbone js file when i click button on the first js file i have to call the render method of DocumentsPageview
first js file
first.backbonejs = (function($) {
case myapp
sectionView = new second.mysecondbackbone.DocumentsPageView();
sectionView.render();
break;
}
}(jQuery)
second js file
second.mysecondbackbone = (function($) {
var DocumentsPageView= Backbone.View.extend({
render: function(){
//render the page
}
});
}(jQuery)
I am getting object undefined in the declaration section
Thanks & Regards
Ashik
My advice is, don't.
Use a mediator object that sits between the two, and controls the process of working with both views.
It can be as simple as this:
myProcess = {
show: function(){
var view1 = new View1();
view1.on("foo", this.doMoreStuff, this);
this.showView(view1);
},
doMoreStuff: function(){
var view2 = new View2();
this.showView(view2);
},
showView: function(view){
// code to stuff view.$el in to the DOM
}
}
The advantage here is that you have a high level workflow that can be managed and maintained on it's own, separate from the implementation details of the individual views. You don't have to trace down in to the individual views to see how they work together.
I wrote more about this, here: http://lostechies.com/derickbailey/2012/05/10/modeling-explicit-workflow-with-code-in-javascript-and-backbone-apps/
I've been using Marionette for a week now and it really made my life easier!
Right now I need to be able to notify a user when a collection or model is being fetched, because some views take a significant amount of time to render/fetch. To give an example I've made a small mockup:
When a user clicks on a category, a collection of all items within that category need to be loaded. Before The collection is fetched I want to display a loading view as seen in the image (view 1). What would be an elegant solution to implement this. I've found the following post where a user enables a fetch trigger: http://tbranyen.com/post/how-to-indicate-backbone-fetch-progress . This seems to work but not really like I wanted to. This is something I came up with:
var ItemThumbCollectionView = Backbone.Marionette.CollectionView.extend({
collection: new ItemsCollection(userId),
itemView: ItemThumbView,
initialize: function(){
this.collection.on("fetch", function() {
//show loading view
}, this);
this.collection.on("reset", function() {
//show final view
}, this);
this.collection.fetch();
Backbone.history.navigate('user/'+identifier);
this.bindTo(this.collection, "reset", this.render, this)
}
});
It would be nice though If I could have an optional attribute called 'LoadItemView' for example. Which would override the itemView during a fetch. Would this be a good practice in your opinion?
A few days ago, Derick Bailey posted a possible solution in the Marionette Wiki: https://github.com/marionettejs/backbone.marionette/wiki/Displaying-A-%22loading-...%22-Message-For-A-Collection-Or-Composite-View
var myCollection = Backbone.Marionette.CollectionView.extend({
initialize: function(){
this.collection.on('request', function() {
//show loading here
})
this.collection.on('reset', function() {
//hide loading here
})
this.collection.fetch({reset: true})
}
})
It's better now, I think.
Use Backbone sync method
/* over riding of sync application every request come hear except direct ajax */
Backbone._sync = Backbone.sync;
Backbone.sync = function(method, model, options) {
// Clone the all options
var params = _.clone(options);
params.success = function(model) {
// Write code to hide the loading symbol
//$("#loading").hide();
if (options.success)
options.success(model);
};
params.failure = function(model) {
// Write code to hide the loading symbol
//$("#loading").hide();
if (options.failure)
options.failure(model);
};
params.error = function(xhr, errText) {
// Write code to hide the loading symbol
//$("#loading").hide();
if (options.error)
options.error(xhr, errText);
};
// Write code to show the loading symbol
//$("#loading").show();
Backbone._sync(method, model, params);
};
In general, I'd suggest loading a preloader while fetching data, then showing the collection. Something like:
region.show(myPreloader);
collection.fetch().done(function() {
region.show(new MyCollectionView({ collection: collection });
});