Backbone.Paginator : cannot read property 'requestPager' - backbone.js

Similar to question 21560374.
I am trying to implement Backbone.Paginator version 0.8 and on page load I am meet with the error: Uncaught TypeError: Cannot read property 'requestPager' of undefined.
First I referred to the CDNJS file and then I downloaded the raw code and placed it in app/assets/javascripts. I also required it in application.js '//= require backbone.paginator.min.js'.
Here is the my section code located in application.html.erb:
<head>
<title>D2jive</title>
<link href='http://fonts.googleapis.com/css?family=Audiowide' rel='stylesheet' type='text/css'>
<link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
<%= stylesheet_link_tag "application", media: "all" %>
<%= javascript_include_tag "application" %>
<script type="text/javascript" src="assets/backbone.paginator.min.js"></script>
<%= csrf_meta_tags %>
</head>
On window I instantiate my router:
window.D2Jive = {
Views: {},
Routers: {},
Events: {},
Models: {},
Collections: {},
initialize: function() {
D2Jive.router = new this.Router();
Backbone.history.start({pushState: true});
},
};
Located in my router I refer to my PaginatedCollection:
this.collection = new D2Jive.Collections.PaginatedCollections( [], { location: location });
Then where the actual code for the Backbone.Paginator.requestPager appears the error pops up.
D2Jive.Collections.PaginatedCollection = Backbone.Paginator.requestPager.extend({
initialize: function(attributes, options){
this.city = options.location;
},
I am still getting used to JavaScript, but it seems the Backbone.Paginator code is loading after the window initialize function creates a new router and checks the Backbone.Paginator collection I created. I am just not sure how to avoid that.
After the error pops up, in the console I am able to load the Backbone.Paginator.requestPager.
Backbone.Paginator.requestPager.extend
function (protoProps, staticProps) {
var parent = this;
var child;
// The constructor function for the new subclass is either defined by you
// (the "constructor" property in your `extend` definition), or defaulted
// by us to simply call the parent's constructor.
if (protoProps && _.has(protoProps, 'constructor')) {
child = protoProps.constructor;
} else {
child = function(){ return parent.apply(this, arguments); };
}
// Add static properties to the constructor function, if supplied.
_.extend(child, parent, staticProps);
// Set the prototype chain to inherit from `parent`, without calling
// `parent`'s constructor function.
var Surrogate = function(){ this.constructor = child; };
Surrogate.prototype = parent.prototype;
child.prototype = new Surrogate;
// Add prototype properties (instance properties) to the subclass,
// if supplied.
if (protoProps) _.extend(child.prototype, protoProps);
// Set a convenience property in case the parent's prototype is needed
// later.
child.__super__ = parent.prototype;
return child;
}
Any help will be much appreciated!

Make sure you include the backbone paginator library before starting your app. The reason you can access requestPager after the error pops up is likely because at that stage, the lib has been loaded.
To make sure this isn't the issue with your code, try including the backbone paginator code in the page's <head> section.
Edit based on comment:
You need to change your "app/assets/javascripts/application.js" so it includes the files in the proper order. Something like:
//= require jquery
//= require jquery_ujs
//= require underscore
//= require backbone
//= require bacbkone.paginator.min
//= require_tree .
If you just rely on require_tree to include the files, they won't necessarily be included in the right order, which is what you're experiencing.
Also, since Backbone paginator gets included in the application file, it should no longer be in .

Related

backbonejs demo case can't work well with localstorage added in stack snippet

I am trying to use the stack snippet tool to embed live demo case into my post. But I find when I add localstorage function into the demo case, it can't work well.
So I simplified my question to the basic backbone case, to emphasis the localstorage issue as above.
And if I remove the localstorage flow, the demo can run through very well, but if localstorage added, then it can't work well. The error message from console said
Failed to read the 'localStorage' property from 'Window': The document is sandboxed and lacks the 'allow-same-origin' flag.
Any idea?
// A simple case
var Daymodel = Backbone.Model.extend({
defaults : {
day: 1,
}
});
var DayCollection = Backbone.Collection.extend({
model: Daymodel,
localStorage: new Backbone.LocalStorage("test-simple")
});
// The view for each day panel
var DayView = Backbone.View.extend({
tagName:"div",
template: _.template( $('#eachday-template').html() ),
initialize: function() {
this.listenTo(this.model, "change", this.render);
},
render: function(){
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
// The view for the entire application
var AppView = Backbone.View.extend({
el: $('#todoapp'),
events: {
"click #add-firebase":"addToLocalhost"
},
initialize: function() {
this.daylist = this.$("#container"); // the daylist to append to
this.listenTo(this.collection, 'add', this.addOne);
this.collection.fetch();
},
addOne: function(todo) {
var view = new DayView({model:todo});
this.daylist.append(view.render().el);
},
addToLocalhost: function(){
this.collection.create({
day : this.collection.length + 1,
});
}
});
// Create a function to kick off our BackboneFire app
function init() {
// The data we are syncing from our remote Firebase database
var collection = new DayCollection();
var app = new AppView({ collection: collection });
}
// When the document is ready, call the init function
$(function() {
init();
});
<div id="todoapp">
<div id="container"></div>
<button id="add-firebase">Add to Localstorage</button>
</div>
<script type="text/template" id="eachday-template">
<h3 class="which-day"> day <%= day %></h3>
<ul id="todo-list"></ul>
</script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone-localstorage.js/1.1.16/backbone.localStorage-min.js">
</script>
The answer is in the error message you're getting: "The document is sandboxed". You can't mess with the localStorage as it's a restricted feature for all sandboxed iframe documents unless the restriction is specifically lifted.
If you look at the page source, you'll see the iframe and the sandbox attribute options.
<iframe name="d62428c9-4eba-3156-6ef7-8815d959a281"
sandbox="allow-modals allow-scripts"
class="snippet-box-edit" frameborder="0">
See Play safely in sandboxed IFrames for more details.
The framed document is loaded into a unique origin, which means that
all same-origin checks will fail; unique origins match no other
origins ever, not even themselves. Among other impacts, this means
that the document has no access to data stored in any origin's cookies
or any other storage mechanisms (DOM storage, Indexed DB, etc.).
[...]
With the exception of plugins, each of these restrictions can be
lifted by adding a flag to the sandbox attribute’s value. Sandboxed
documents can never run plugins, as plugins are unsandboxed native
code, but everything else is fair game:
allow-forms allows form submission.
allow-popups allows popups (window.open(), showModalDialog(), target="_blank", etc.).
allow-pointer-lock allows (surprise!) pointer lock.
allow-same-origin allows the document to maintain its origin; pages loaded from https://example.com/ will retain access to that
origin's data.
allow-scripts allows JavaScript execution, and also allows features to trigger automatically (as they'd be trivial to implement
via JavaScript).
allow-top-navigation allows the document to break out of the frame by navigating the top-level window.
For allow-modals, Add allow-modals to the sandbox of Stack Snippets gives more details:
Chrome blocks modal dialogs such as alert, confirm and prompt in
sandboxed iframes unless allow-modals is set. This behavior became the
default as of Chrome 46 and Opera 34.

Nothing displayed on-screen and no errors, but JavaScript objects are populated

This is my first trial on backbone + marionette + require + handlebars. I will provide the full explanation on what I did, and I have no clue on why it doesn't work. I removed all possible JavaScript errors, and everything gets properly loaded. So, no errors in the console, but the page stays entirely blank.
What it represents is a simple header menu with buttons (an unordered list of buttons to be displayed in the header).
Index.php
<head>
<meta charset="UTF-8">
<title>Zwoop</title>
<link rel="stylesheet" type="text/css" href="http://www.zwoop.be/dev/css/layout.css">
</head>
<body>
<script id='zwoop_interface' type='text/template'>
<div id="headerRegion">
</div>
<div id="mainRegion"></div>
</script>
<script src="http://www.zwoop.be/dev/js/libs/require/require.js" data-main="js/main"></script>
</body>
main.js
Notes: I don't receive any JavaScript errors and the JS files are properly loaded (I checked this in the browser).
//Require.js
require.config({
baseUrl: 'js',
paths : {
jQuery : '//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min',
jQueryUI : '//ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min',
lodash : 'libs/lodash/lodash',
backbone : 'libs/backbone/backbone',
marionette : 'libs/marionette/marionette',
handlebars : 'libs/handlebars/handlebars-v1.1.2',
text : 'libs/require/text',
localstorage : 'libs/backbone/localstorage'
},
shim : {
backbone: {
deps : ['jQuery', 'lodash'],
exports : 'Backbone'
},
marionette: {
deps : ['backbone'],
exports : 'Marionette'
},
handlebars: {
exports: 'Handlebars'
}
}
});
require(["backbone","marionette", "views/main_menuView"], function (Backbone, Marionette, Main_menuView) {
var Zwoop = new Marionette.Application();
//Pass options if required
var options = {
};
//Initialize functions
Zwoop.on("initialize:before", function(options){
console.log("test");
});
Zwoop.addInitializer(function(options){
var Main_Layout = Marionette.Layout.extend({
template: "#zwoop_interface",
regions: {
headerRegion: "#headerRegion",
bodyRegion: "#bodyRegion"
}
});
var main_layout = new Main_Layout();
//Rendering the layout is required before you can show anything within the regions
main_layout.render();
main_layout.headerRegion.show(Main_menuView);
console.log("rendered"); //This console log works
});
Zwoop.vent.on("main_layout:rendered", function(){
//Initialize router
new ZwoopRouter();
Backbone.history.start();
console.log("router started"); //This one is not called; I don't know why
});
//Start the application
Zwoop.start(options);
return Zwoop;
});
3_ main_menuView.js
Notes: I console logged 'Main_MenuCollection.toJSON()' and the object is properly set.
define([
'jQuery',
'marionette',
'handlebars',
'text',
'text!templates/main_menu.html',
'models/main_menuModel'
], function ($, Marionette, Handlebars, Text, Main_menu_tpl, Main_MenuCollection) {
'use strict';
var Main_MenuView = Marionette.ItemView.extend({
initialize: function () {
_.bindAll(this, 'render');
this.render();
},
el: '#headerRegion',
template: Handlebars.compile(Main_menu_tpl),
events: {
'click .main_menu_item':'select_menu'
},
select_menu: function(){
console.log("clicked");
},
render: function () {
this.$el.html(this.template({
models: Main_MenuCollection.toJSON()
}));
return this;
}
});
var main_menuView = new Main_MenuView();
return main_menuView;
});
4_ main_menu.html
This is the template that I used:
<ul id="main-menu">
{{#each models}}
<li><a id="{{models.id}}" href="{{models.href}}" class='main_menu_item'">{{models.label}}</a></li>
{{/each}}
</ul>
4_ main_menuModel.js model + collection
Note: Also here, I console logged the collection before returning it, and it is properly set.
define([
'backbone'
], function(Backbone){
var Menu_ItemModel = Backbone.Model.extend({
initialize: function(){
},
//These are data that are related to the main menu
defaults: {
id: 'undefined',
href: 'undefined',
label: 'undefined'
}
});
var btn_bars = new Menu_ItemModel({id:'btn_bars', href: 'bars', label:'Bars'});
var btn_eat = new Menu_ItemModel({id:'btn_eat', href: 'places_to_eat', label:'Places to eat'});
var btn_events = new Menu_ItemModel({id:'btn_events', href: 'events', label:'Bars'});
var btn_touristic = new Menu_ItemModel({id:'btn_touristic', href: 'touristic', label:'Touristic places'});
var btn_hotels = new Menu_ItemModel({id:'btn_hotels', href: 'hotels', label:'Hotels'});
var btn_shops = new Menu_ItemModel({id:'btn_shops', href: 'shops', label:'Shops'});
var btn_companies = new Menu_ItemModel({id:'btn_companies', href: 'companies', label:'Companies'});
var Main_MenuCollection = Backbone.Collection.extend({
initialize: function(){
},
model: Menu_ItemModel
});
var main_menuCollection = new Main_MenuCollection();
main_menuCollection.add([
btn_bars,
btn_eat,
btn_events,
btn_touristic,
btn_hotels,
btn_shops,
btn_companies
]);
return main_menuCollection;
});
The first attempt, and I'm not quite experienced yet so I really don't see where to find the problem. Do you have any suggestions?
Your main_layout gets rendered, but never shown. This seems to be the main issue.
In addition, console.log("router started") might not get called, because although you define a listened for the "main_layout:rendered" event, I don't see it getting triggered anywhere.
Also, it seems you might be confused about layouts VS regions : regions remain "static" with the application, whereas layouts are removed and redisplayed as the user navigates through the app. So for example, you'd use a region to display the app's header menu, but you'd use a layout to display (e.g.) the user's homepage (so you can organize the various sub-views). In other words, you create application regions to segment areas in your application that will always be displayed (e.g. header, main content, footer), and then you can also use layouts (with declared regions) to organize views that require sub-views (e.g. a "user profile" page with a "last comments" region, a "contact info" region, etc.). they have the same name, but think of application regions as "areas" in the application, and layout regions as "parts of a big, ciomplex view".
Last but not least, you might want to consider using layouts like this https://github.com/davidsulc/marionette-gentle-introduction/blob/master/assets/js/apps/contacts/list/list_controller.js#L43 (from my Marionette book) : note that we use the lyout's "show" event listener to then display the sub-views. This means we don't need to call render manually, which is more in line with Marionette's conventions.

Require.js JST files

This is my sample JST file
(function() {
var _ref;
if ((_ref = window.JST) == null) {
window.JST = {};
}
window.JST['test'] = function(context) {
return (function() {
var $o;
$o = [];
$o.push("<h1>yayyyyyyaa</h1>");
return $o.join("\n");
}).call(context);
};
}).call(this);
I use require.js in a backbone app, like
define ['backbone', 'marionette', 'text!javascripts/backbone/templates/test.jst'],
(Backbone, Marionette, template) ->
class Test extends Backbone.Marionette.ItemView
template: JST[template]
And when i load the app, i get:
ReferenceError: JST is not defined
Why oh why!
Thanks!
The problem with your code is that you are getting the text of the function in your "template" variable. You still need to eval that text to create an actual JST instance on the window.
The problem as a whole is that you are abusing the text! plugin, what you really need to do is use the modules of requireJs instead of hanging your variables on the window.

Understanding Backbone architecture base concepts

I'm trying to working with backbone but I'm missing it's base concepts because this is the first JavaScript MVVM Framework I try.
I've taken a look to some guide but I think I still missing how it should be used.
I'll show my app to get some direction:
// Search.js
var Search = {
Models: {},
Collections: {},
Views: {},
Templates:{}
};
Search.Models.Product = Backbone.Model.extend({
defaults: search.product.defaults || {},
toUrl:function (url) {
// an example method
return url.replace(" ", "-").toLowerCase();
},
initialize:function () {
console.log("initialize Search.Models.Product");
}
});
Search.Views.Product = Backbone.View.extend({
initialize:function () {
console.log("initialize Search.Views.Product");
},
render:function (response) {
console.log("render Search.Views.Product");
console.log(this.model.toJSON());
// do default behavior here
}
});
Search.Models.Manufacturer = Backbone.Model.etc...
Search.Views.Manufacturer = Backbone.View.etc...
then in my web application view:
<head>
<script src="js/jquery.min.js"></script>
<script src="js/underscore.min.js"></script>
<script src="js/backbone/backbone.min.js"></script>
<script src="js/backbone/Search.js"></script>
</head>
<body>
<script>
var search = {};
search.product = {};
search.product.defaults = {
id:0,
container:"#search-results",
type:"product",
text:"<?php echo __('No result');?>",
image:"<?php echo $this->webroot;?>files/product/default.png"
};
$(function(){
var ProductModel = new Search.Models.Product();
var ProductView = new Search.Views.Product({
model:ProductModel,
template:$("#results-product-template"),
render:function (response) {
// do specific view behavior here if needed
console.log('render ProductView override Search.Views.Product');
}
});
function onServerResponse (ajax_data) {
// let's assume there is some callback set for onServerResponse method
ProductView.render(ajax_data);
}
});
</script>
</body>
I think I missing how Backbone new instances are intended to be used for, I thought with Backbone Search.js I should build the base app like Search.Views.Product and extend it in the view due to the situation with ProductView.
So in my example, with render method, use it with a default behavior in the Search.js and with specific behavior in my html view.
After some try, it seems ProductModel and ProductView are just instances and you have to do all the code in the Search.js without creating specific behaviors.
I understand doing it in this way make everything easiest to be kept up to date, but what if I use this app in different views and relative places?
I'm sure I'm missing the way it should be used.
In this guides there is no code used inside the html view, so should I write all the code in the app without insert specific situations?
If not, how I should write the code for specific situations of the html view?
Is it permitted to override methods of my Backbone application?
Basically, you should think of the different parts like this:
templates indicate what should be displayed and where. They are writtent in HTML
views dictate how the display should react to changes in the environment (user clicks, data changing). They are written in javascript
models and collections hold the data and make it easier to work with. For example, if a model is displayed in a view, you can tell the view to refresh when the model's data changes
then, you have javascript code that will create new instances of views with the proper model/collection and display them in the browser
I'm writing a book on Marionette.js, which is a framework to make working with Backbone easier. The first chapters are available in a free sample, and explain the above points in more detail: http://samples.leanpub.com/marionette-gentle-introduction-sample.pdf

Handlebar compiled html not recognizing template function in backbone.js

The project I am on is currently using Backbone.js to create a website and is using Handlebars (http://handlebarsjs.com/) as the templating system. I am attempting to create a sub-view that gets values from a json document into a corresponding template and then return that to a parent view.
The problem I am running into is that when I use
Handlebars.Compile(referenceViewTemplate)
it then doesn't recognize the template function when I try to replace the tokens using
this.template({ identifier: value })
The template code is:
<div id="reference-template">
<div class="id">{{id}}</div>
<div class="reference">{{content}}</div>
</div>
The backbone model is:
define(['underscore','backbone'],
function(_, Backbone){
var reference = Backbone.Model.extend({
initialize: function(){}
});
return reference;
});
The backbone collection code is:
define(['underscore','backbone','models/reference'],
function(_, Backbone, Reference){
var References = Backbone.Collection.extend({
model: Reference,
parse:function(response){ return response; }
});
return new References;
});
The code in the parent view which calls the reference view is:
this.ref = new ReferenceView();
this.ref.model = this.model.page_refs; //page_refs is the section in the json which has the relevant content
this.ref.render(section); //section is the specific part of the json which should be rendered in the view
And the code in the ReferenceView is:
define([
// These are path alias that we configured in our bootstrap
'jquery','underscore','backbone','handlebars',
'models/reference','collections/references','text!templates/reference.html'],
function($, _, Backbone, Handlebars, Reference, References, referenceViewTemplate) {
var ReferenceView = Backbone.View.extend({
//Define the default template
template: Handlebars.Compiler(referenceViewTemplate),
el: ".overlay-references",
model: new Reference,
events:{},
initialize : function() {
this.model.bind('change', this.render, this);
return this;
},
// Render function
render : function(section) {
//this is where it says "TypeError: this.template is not a function"
$(this.el).append(this.template(References.get(section).get("content")));
return this;
}
});
I know this is a lot to read through and I appreciate anyone taking the time to do so, please let me know if there is anything else I can provide to clarify.
The answer is that apparently I was using the wrong function to compile the html. For some reason I typed in Handlebars.Compiler instead of Handlebars.compile
This hasn't solved all the problems in my project (template is being passed back now, but without the values entered), but at least it's a step forward.

Resources