Im studying require and modular programming with backbone.
My question specifically is how can I access the models I have created in the View module as shown below from the main page, (example from the console), as is allways telling me undefined.
I understand that as its encapsulated in the view module, but is being hard to me to understand where I should create the instances of the model and collection as if I do it in init.js I get them to be undefined in the view module when i define collections or model.
If I instance them from model or collections modules I get a bunch of undefined errors
I have this init.js;
requirejs.config({
enforceDefine: true,
baseUrl: 'js',
urlArgs: "bust=" + (new Date()).getTime(),
shim: {
underscore: {
exports: '_'
},
backbone: {
deps: ['underscore', 'jquery'],
exports: 'Backbone'
}
},
paths: {
jquery: 'lib/jquery-1.10.2.min',
backbone: 'lib/backbone.min',
underscore: 'lib/underscore.min',
text: 'lib/require/text',
Plato: 'app/models/plato',
Carta: 'app/collections/carta',
MainView: 'app/views/mainView',
mainTemplate: 'app/templates/mainTemplate.html'
}
});
define(['jquery', 'underscore', 'backbone', 'MainView'], function($, _, Backbone, MainView) {
console.log(typeof $);
console.log(typeof _);
console.log(typeof Backbone);
var mainView = new MainView;
});
Then I have mainView.js as:
define(['backbone', 'text!mainTemplate', 'Plato', 'Carta'], function(Backbone, mainTemplate, Plato, Carta) {
var pizza = new Plato({precio:120, nombre:'pizza', foto:'n/a', ingredientes: 'harina, tomate, oregano', diabeticos:false}),
empanada = new Plato({precio:40, nombre:'empanada', foto:'n/a', ingredientes: 'harina, carne picada', diabeticos:false}),
lasagna = new Plato({precio:200, nombre:'lasagna', foto:'n/a', ingredientes: 'pasta, queso', diabeticos:false}),
carta = new Carta([pizza, empanada, lasagna]),
MainView = Backbone.View.extend({
tagName: 'div',
id: 'mainView',
events: {'click td': 'clickAction'},
collection: carta,
template: _.template(mainTemplate, this.collection),
initialize: function() {
this.render();
},
render: function() {
this.collection.each(function(item) {
console.log(item.toJSON() + item.get('nombre'));
this.$el.append( this.template( item.toJSON() ));
}, this);
$('#content').html(this.$el);
return this;
},
clickAction: function() {
alert(this);
}
});
return MainView;
});
I also have the model and collection modules if that helps you out to help me.
My main purpose would be to be able to access them and then put a listener or an on in the elements to be able to play with the data of those models.
Sorry If I am confused and mixing concepts with the variable scope of the modules using require.js and backbone, but I have read whatever I was able to find in the internet and Im still confused.
EDIT
Should I create and entire module just to instantiate them and then export the values as an object??
Should I create and entire module just to instantiate them and then
export the values as an object??
Yes. That's one way to accomplish what you are looking to do:
define(['backbone', 'text!mainTemplate', 'Plato', 'Carta', 'carta'],
function(Backbone, mainTemplate, Plato, Carta, carta) {
...
});
Where Carta is the collection module and carta is the object that contains the data.
Related
Here is how my Backbone Router looks like
define([
"jquery",
"underscore",
"backbone"
], function ($, _, Backbone) {
return Backbone.Router.extend({
routes: {
"overview": "overview"
},
overview: function () {
require([
"views/overview",
"models/user-collection",
"grid",
"spreadsheet"
], function (OverviewView, TestCollection, GridView, SpreadSheetView) {
// Data
var collection = new TestCollection();
// Main view
var view = new OverviewView({
el: "#page",
collection: collection
});
// Sub view #1
var gridView = new GridView({
el: "#backgridWrapper"
});
// Sub View #2
var spreadsheetView = new SpreadSheetView({
el: "#handsontableWrapper"
});
// Flow
collection.fetch({
success: function () {
view.render();
gridView.render();
spreadsheetView.render();
}
});
});
}
});
});
As you can see there are several views:
Main view
Sub view #1
Sub view #2
I've did a lot of searching on how to organize the views and sub-views in Backbone, however all of them supposed to create a new sub-view instance directly within a view definition, so that router only knows about Main view...
So the question is - is it a good idea to handle sub-views at a router, instead of directly at view constructor?
The router should be just handling routes and initializing stuff.
Things like fetching data should go in the view that uses it - The view displays the data or error messages (in case of a failure), so I think it's wise to let the view fetch the data rather than some router who's only interested in the routes and have no interest in the data.
and I prefer initializing the sub views, inside their parent view, rather than somewhere else. The parent - child relationship itself justifies that, you better keep the children with their parents than a stranger so they will be under better control and you can easily find them later as well :)
Mostly it's a matter of opinion, but the thing is if you don't, all your code will soon get cluttered in the router rather than being well organized.
Below is how I'll structure the same thing.
Note that I'm initializing child views as part of parent views render method. It could be done when the parent view is initialized, but I see no point in doing so unless the parent view successfully fetches data and is proceeding to render itself.
define([
"jquery",
"underscore",
"backbone"
], function($, _, Backbone) {
return Backbone.Router.extend({
routes: {
"overview": "overview"
},
overview: function() {
require(["views/overview"], function(OverviewView) {
// initialize Main view
var view = new OverviewView({
el: "#page"
});
});
}
});
});
define([
"jquery",
"underscore",
"backbone",
"models/user-collection",
"grid",
"spreadsheet"
], function($, _, Backbone, TestCollection, GridView, SpreadSheetView) {
return Backbone.View.extend({
initialize: function(options) {
this.collection = new TestCollection();
this.fetchData();
},
events: {},
render: function() {
// rendering subviews is part of rendering their parent view.
//I prefer to do that here
// Sub view #1
this.gridView = new GridView({
el: "#backgridWrapper"
});
// Sub View #2
this.spreadsheetView = new SpreadSheetView({
el: "#handsontableWrapper"
});
//Below lines can be handled while initializing the respective view
// (In their initialize() method, or after fetching some data etc
// or can be chained with the above initialization if their render() method returns a reference to itself (`return this`)
this.gridView.render();
this.spreadsheetView.render();
},
fetchData: function() {
var view = this;
this.collection.fetch({
success: function() {
view.render();
}
});
}
});
});
side note : I strongly suggest not to put a collection under models folder.
I'm using require with backbone + backbone-forms. I'm currently using RequireJS to seperate code into multiple files. I have models stored in separate files and want to keep form validators separately.
However, I am unable to access variables defined in one files, in another file that depends on this one. What I get is Uncaught ReferenceError: isEmptyName is not defined. isEmptyName is defined in validators and used in model. Any feedback about RequireJS config is also appreciated.
My config:
requirejs.config({
//By default load any module IDs from js/lib
baseUrl: 'js',
paths: {
jquery: 'lib/jquery',
app: 'lib/app',
wizard: 'lib/jquery.bootstrap.wizard.min',
bootstrap: 'lib/bootstrap.min',
underscore: 'lib/underscore-min',
backbone: 'lib/backbone-min',
backboneForms: 'lib/backbone-forms.min',
langSwitcher: 'lib/lang',
cookie: 'lib/cookie',
datepicker: 'lib/bootstrap-datepicker',
mask: 'lib/jquery.maskedinput.min',
validators: 'modules/validators',
// models
personalData: 'models/personal-data',
addressData: 'models/address-data',
workData: 'models/work-data',
productsData: 'models/products-data',
statmentData: 'models/statment-data',
model: 'models/form',
collection: 'collections/form',
view: 'views/form',
setup: 'setup',
send: 'send',
},
shim: {
'underscore': {
deps: ['jquery'],
exports: '_'
},
'backbone': {
deps: ['underscore', 'jquery'],
exports: 'backbone'
},
// all model needs to go within one collection
'bootstrap' : ['jquery'],
'wizard': ['jquery'],
'backboneForms': ['backbone'],
'validators': ['backbone','mask'],
'personalData' : ['backbone','backboneForms','validators'],
'addressData': ['backbone','backboneForms'],
'workData': ['backbone','backboneForms'],
'statmentData': ['backbone','backboneForms'],
//'collection': ['backbone','backboneForms','personalData'],
//'view': ['backbone','backboneForms','personalData']
}
});
Beginning of validators.js
require(['backbone','backboneForms'], function(){
var lettersOnly = /^[A-Za-zęóąśłżźćńĘÓĄŚŁŻŹĆŃ]+$/;
var lettersOnlyDash = /^[A-Za-zęóąśłżźćńĘÓĄŚŁŻŹĆŃ\-]+$/;
var err = {};
var errCh = {};
var errFormat = {};
var isEmptyName = function(value){
err = { message: 'Wpisz imię.'};
if (value.length === 0) return err;
};
Beginning of model.js that needs the validators in validators.js
require(['backbone','backboneForms','mask','validators'], function(backbone,backboneForms,mask,validators){
var PersonalData = Backbone.Model.extend({
schema: {
first_name:{
title: 'Imię',
validators: [isEmptyName, isLetter, minCharCount] //Accessing validators.js members here...
}, ...
I think you're using require when what you really need is define. From When should I use require() and when to use define()?,
With define you register a module in require.js that you than can
depend on in other module definitions or require statements. With
require you "just" load/use a module or javascript file that can be
loaded by require.js.
So here, you have some variables that are defined in one file, but are required to be accessed in another file. Seems like a 'Module', doesn't it? So now, you have two ways of using this file as a module:
Conform to AMD-ness
Conform to chaotic javascript global variable-ness
Using the AMD Approach
validators.js is now a module. Anybody wishing to use 'validator functions' can depend on this module to provide it for them. That is,
define(['backbone','backboneForms'], function(){
var lettersOnly = /^[A-Za-zęóąśłżźćńĘÓĄŚŁŻŹĆŃ]+$/;
var isEmptyName = function(value){
err = { message: 'Wpisz imię.'};
if (value.length === 0) return err;
return {
someVariable: lettersOnly,
someFunction: isEmptyName
}
};
You'll notice that the require has been replaced with define. Now, when somebody (model) depends on validator.js, they can access their dependencies as follows
require(['backbone','backboneForms','mask','validators'],
function(backbone, backboneForms, mask, validators) {
var isEmptyNameReference = validators.someFunction;
...
Using shim
Check Requirejs why and when to use shim config, which references this link which says,
if we were to just add the backbone.js file to our project and list
Backbone as a dependency from one of our modules, it wouldn’t work.
RequireJS will load backbone.js, but nothing in backbone.js registers
itself as a module with RequireJS. RequireJS will throw up its hands
and say something like, “Well, I loaded the file, but I didn’t find
any module in there.”
So, you could have your validator.js populate a global Validator namespace, and still use it the way we used it in the example above.
function(){
var lettersOnly = /^[A-Za-zęóąśłżźćńĘÓĄŚŁŻŹĆŃ]+$/;
var isEmptyName = function(value){
err = { message: 'Wpisz imię.'};
if (value.length === 0) return err;
Globals.Validator = {
someVariable: lettersOnly,
someFunction: isEmptyName
}
}();
Your config.js would then be,
shim: {
'validator': {
deps: ['backbone','backboneForms'],
exports: 'Globals.Validator'
},
...
Note that you can alias the namespace as you wish, but the alias is just a reference to the existing global object/namespace. This is helpful if you have, say, Foo.Bar.Foobar as your namespace, but want to refer to it as FB. Shimming, hence, is a way for non-AMD libraries to adapt to AMD usage. In this case, option 1 should be sufficient.
I'm putting together a simple todo list app based on some examples to learn about Backbone. I'm running the code on Django for database and using Tastypie to do the API, and am trying to use Backbone (with RequireJS for AMD) but am having trouble with the following error in my console:
Uncaught TypeError: Object function (){ return parent.apply(this, arguments); } has no method 'on'
I think my app is having trouble communicating via the backbone collection,because it seems to get this error at about here in my app.js (main app view):
// Bind relavent events to the todos.
this.listenTo(Todos, "add", this.addOne);
this.listenTo(Todos, "reset", this.addAll);
I've seen some similar issues on here but I'm just breaking into this stuff so an explanation of what's happening would be much appreciated.
collections.js
define([
'underscore',
'backbone',
'app/app',
'app/models/model'
], function(_, Backbone, app, Todo){
// The collection of our todo models.
TodoList = Backbone.Collection.extend({
model: Todo,
// A catcher for the meta object TastyPie will return.
meta: {},
// Set the (relative) url to the API for the item resource.
url: "/api/item",
// Our API will return an object with meta, then objects list.
parse: function(response) {
this.meta = response.meta;
return response.objects;
}
});
var Todos = new TodoList();
return Todos;
});
app.js:
define([
'jquery',
'underscore',
'backbone',
'app/models/model',
'app/collections/collection',
'app/views/view'
], function($, _, Backbone, Todos, Todo, TodoList, TodoView){
// The view for the entire app.
var AppView = Backbone.View.extend({
el: "#todo-app",
// Bind our events.
events: {
"keypress #new-todo": "createOnEnter",
},
initialize: function() {
// TastyPie requires us to use a ?format=json param, so we'll
// set that as a default.
$.ajaxPrefilter(function(options) {
_.extend(options, {format: "json"});
});
// Bind relavent events to the todos.
this.listenTo(Todos, "add", this.addOne);
this.listenTo(Todos, "reset", this.addAll);
// Cache some of our elements.
this.$input = this.$("#new-todo");
// Get our todos from the API!
Todos.fetch();
},
// Crate a new todo when the input has focus and enter key is hit.
createOnEnter: function(event) {
var keyCode = event.keyCode || event.which,
title = this.$input.val().trim();
// If we haven't hit enter, then continue.
if (keyCode != 13 || !title) return;
// Create a new todo.
Todos.create({title: title, complete: false});
// Reset the input.
this.$input.val("");
},
// Add a single todo to the list.
addOne: function(todo) {
var view = new TodoView({model: todo});
$("#todo-list").append(view.render().el);
},
// Clear the list and add each todo one by one.
addAll: function() {
this.$("todo-list").html("");
Todos.each(this.addOne, this);
}
});
return AppView;
});
The order of definitions and parameters in your app.js file is incorrect, you can try like this:
define([
'jquery',
'underscore',
'backbone',
'app/models/model',
'app/collections/collection',
'app/views/view'
], function($, _, Backbone, Todo, Todos, TodoView){
I'm using Controller to Fetch URL. I need a way to put Parameter in this POST. These Parameters are selected by users on View & Not Stored yet(I dont know how to store)
Currently I managed to
1) Display & Route The View with search result coming from API
2) Display and refresh the page when someone selects a Filter Option
Problem
1) I got no idea how to record what the users clicked
2) How do i "re-post" so i can get the new set of results
3) I read and say people saying POST Fetch should be done in Model , Conllection is for Store Multiple Models which i don't know in this scenario?
Collections
Jobs.js
define([
'jquery',
'underscore',
'backbone',
'models/filter'
], function($, _, Backbone,JobListFilterModel){
var Jobs = Backbone.Collection.extend({
url: function () {
return 'http://punchgag.com/api/jobs?page='+this.page+''
},
page: 1,
model: JobListFilterModel
});
return Jobs;
});
Collections Filter.JS
define([
'jquery',
'underscore',
'backbone',
'models/filter'
], function($, _, Backbone,JobListFilterModel){
console.log("Loaded");
var Jobs = Backbone.Collection.extend({
url: function () {
return 'http://punchgag.com/api/jobs?page='+this.page+''
},
page: 1,
model: JobListFilterModel
});
// var donuts = new JobListFilterModel;
// console.log(donuts.get("E"));
return Jobs;
});
Models
Filter.js
define([
'underscore',
'backbone'
], function(_, Backbone){
var JobFilterModel = Backbone.Model.extend({
defaults: {
T: '1', //Task / Event-based
PT: '1', //Part-time
C: '1', //Contract
I: '1' //Internship
}
});
// Return the model for the module
return JobFilterModel;
});
Models
Job.js
define([
'underscore',
'backbone'
], function(_, Backbone){
var JobModel = Backbone.Model.extend({
defaults: {
name: "Harry Potter"
}
});
// Return the model for the module
return JobModel;
});
Router.js
define([
'jquery',
'underscore',
'backbone',
'views/jobs/list',
'views/jobs/filter'
], function($, _, Backbone, JobListView, JobListFilterView){
var AppRouter = Backbone.Router.extend({
routes: {
// Define some URL routes
'seeker/jobs': 'showJobs',
'*actions': 'defaultAction'
},
initialize: function(attr)
{
Backbone.history.start({pushState: true, root: "/"})
},
showJobs: function()
{
var view = new JobListView();
view.$el.appendTo('#bbJobList');
view.render();
console.log(view);
var jobListFilterView = new JobListFilterView();
jobListFilterView.render()
},
defaultAction: function(actions)
{
console.info('defaultAction Route');
console.log('No route:', actions);
}
});
var initialize = function(){
console.log('Router Initialized');// <- To e sure yout initialize method is called
var app_router = new AppRouter();
};
return {
initialize: initialize
};
})
;
Capturing what a user has done should be completed within a view. For example:
filterView.js
var View = Backbone.View.extend({
initialize: function() {
_.extend(this, Backbone.Events);
},
render: function() {
// Your normal render routine
return this;
},
events: {
"submit form": "submit"
},
submit: function() {
var query = $("input[type=search]", this.el).val();
this.trigger("submit", query);
}
});
return View;
...whatever implements filterView and jobCollection...
// Implement our collections, views, etc
var jobCollection = new JobCollection();
var jobsView = new JobsView();
var filterView = new FilterView().render();
// Add listeners on our view
filterView.on("submit", updateJobs);
// Implement the functions for our view (this can be done in a number of ways)
function updateJobs(query) {
jobCollection.fetch({
data: {
query: query
}
}).done(function(response) {
// Technically you could just provide "jobsView.render" to the done() method, but I wanted to be a little more verbose here regarding parameters
jobsView.render(response);
});
}
Your view is only aware of itself (input structure, etc). The thing that implements the view and collection is the broker between these two modules.
You specify parameters within the fetch statement by providing an object which implements a data parameter. In this case, we're going to pass the query using the query key. fetch returns a promise, so once that's done, we return that response to the jobsView and pass it to the render function, which should be set up to accept the collection.
updateJobs is unaware of the view implementation, all it knows is it expects a query to be included.
This code is not meant to be directly implemented or copy/pasted, but it's meant to provide an outline for how you might accomplish what you're looking to do.
I am attempting to make my Backbone.js components, models and views modular through Backbone.js. However whenever I attempt to require() one it returns:
function (){return i.apply(this,arguments)}
instead of the Backbone wizardry I require.
I have set up my Require.js like so:
// Configure require.js
require.config(
{
paths: {
"data": "config"
, "jquery": "libs/jquery.min"
, "underscore": "libs/underscore-min"
, "backbone": "libs/backbone-min"
, "events": "collections/events"
, "eventModel": "models/eventModel"
, "eventView": "views/eventView"
}
, shim: {
underscore: {
exports: '_'
}
, jquery: {
exports: '$'
}
, backbone: {
deps: ["underscore"]
, exports: "Backbone"
}
}
}
);
// Initiate app
require(
[
"data"
, "jquery"
, "underscore"
, "backbone"
, "events"
, "eventModel"
, "eventView"
], function(data, $, _, Backbone, Events, EventModel, EventView) {
console.log(Events);
_.each(["data/general.xml"], function(URI) {
var general = new Events({url: URI});
});
}
);
And here is my collections/events.js:
define(
[
"backbone"
, "eventModel"
]
, function(Backbone, EventModel) {
"use strict";
var Events = Backbone.Collection.extend({
model: EventModel
, parse: function(data) {
var parsed = [];
$(data).find('Event').each(function(index) {
parsed.push({
title: $(this).find('title').text(),
date: $(this).find('date').text(),
content: $(this).find('content').text()
});
});
return parsed;
}
, fetch: function(options) {
options = options || {};
options.dataType = "xml";
Backbone.Collection.prototype.fetch.call(this, options);
}
});
return Events;
}
);
I would expect that to return the Events collection, however it clearly isn't. Can anyone see what I am doing wrong?
Everything seems fine. What you see:
function (){return i.apply(this,arguments)}
is the constructor of your class. Pay no attention to it. Instead, try to log new myClass.
Edit:
You don't see any of the methods because they're stored in the prototype of your class. The i function that is called is the "real" constructor (named i because it's been minified).