Using RequireJS inject additional Bacbone Models in View - backbone.js

I'm learning Backbone with RequireJS and I have got a problem when trying to instantiate additional model in my view. I have couple of events which are calling different methods. Different methods are using more or less different models and subviews The example above drops on new model instance
TypeError: GridsModel is not a constructor
var gridModel = new GridsModel;
when fireing grid method
My code looks like
/*global define*/
define([
'jquery',
'underscore',
'backbone',
'templates',
'jqueryui',
'models/grids',
'views/grids',
'views/modal'
], function ($, _, Backbone, JST, GridsModel, GridsView, ModalView) {
'use strict';
var EditorView = Backbone.View.extend({
template: JST['app/scripts/templates/editor.ejs'],
tagName: 'div',
el: '.container',
id: '',
className: '',
events: {
"click button.expand" : "controlToggle",
"click .row-edit" : "edit",
"click .grid" : "grid",
"click .delete" : "delete",
"click .components" : "components",
},
initialize: function () {
var gridModel = new GridsModel;
var body = $('body')
var rows = body.find('.row')
console.log(this.model)
$.each(rows, function(e , v){
if(v.length > 0)
console.log(v)
//$(this).parent().addClass('editor-row')
else
//console.log($(this))
$(this).addClass('editor-row empty-row')
})
$('.ui-sortable').sortable({ handle: 'button.row-handle' })
this.listenTo(this.model, 'change', this.render);
this.listenTo(this.model, 'change', this.render);
},
render: function () {
this.$el.html(this.template(this.model.toJSON()));
},
controlToggle: function(e){
var controlls = $(e.currentTarget).closest('.editor-controls')
$(controlls).find('.active').removeClass('active')
$(e.currentTarget).parent().addClass('active')
},
edit: function(){
},
delete: function() {
confirm('Press OK to delete section, Cancel to leave')
},
grid: function() {
this.model = new GridsModel({
'title': 'Edit Grids'
})
var gridView = new GridsView({
model: this.model
})
var grids = new ModalView({
model : this.model,
subview: gridView
}).render()
},
components: function() {
this.model = new Fefe.Models.Components({
'title': 'Add Component'
})
var componentsView = new Fefe.Views.Components({
model: this.model
})
var components= new Fefe.Views.Modal({
model : this.model,
className: 'modal large',
subview: componentsView
}).render()
}
});
return EditorView;
});
What do I do wrong here

Related

reuse Bootsrap Modal view in BackboneJS

I just started to learn BackboneJS and getting deeper inside I face a problem. I have a bootstrap modal where I would like populate the modal-content in function of a called event fired in my main view and try to figure out how to inject a subview in my Modal view which is dynamically generated. So far my code looks like but not working
Main view
//here events are mapped
Fefe.Views = Fefe.Views || {};
(function () {
'use strict';
Fefe.Views.Editor = Backbone.View.extend({
template: JST['app/scripts/templates/editor.ejs'],
tagName: 'div',
el: '.container',
id: '',
className: '',
events: {
"click button.expand" : "controlToggle",
"click .grid" : "grid"
},
controlToggle: function(e){
var controlls = $(e.currentTarget).closest('.editor-controls')
$(controlls).find('.active').removeClass('active')
$(e.currentTarget).parent().addClass('active')
},
grid: function() {
this.model = new Fefe.Models.Grids({
'title': 'Edit Grids'
})
var gridView = new Fefe.Views.Grids({
model: this.model
})
var grids = new Fefe.Views.Modal({
model : this.model,
subview: gridView
}).render()
},
initialize: function () {
var body = $('body')
var rows = body.find('.row')
$.each(rows, function(e , v){
$(this).addClass('editor-row empty-row')
})
$('.sortable-rows').sortable({ handle: 'button.row-handle.btn.btn-default' })
this.listenTo(this.model, 'change', this.render);
},
render: function () {
return this;
}
});
})();
Modal view
//this one holds the modal markup
Fefe.Views = Fefe.Views || {};
(function () {
'use strict';
Fefe.Views.Modal = Backbone.Marionette.View.extend({
template: JST['app/scripts/templates/modal.ejs'],
subview: '',
className: "modal",
attributes: {
tabindex: "-1",
role: "dialog",
},
initialize: function() {
this.template = this.template;
console.log(this)
},
events: {
"click .save": "save",
"click .close": "close",
"change input": "modify",
},
render: function(e) {
this.$el.html(this.template(this.model.toJSON())).modal()
$(".modal-dialog").draggable({
handle: ".modal-header"
})
return this
},
show: function() {
$(document.body).append(this.render().el);
},
close: function() {
this.remove();
},
save: function() {
if(this.model.id == null) {
tasks.create(this.model);
}
else {
this.model.save();
}
this.remove();
},
edit: function(e) {
var attribute = {};
attribute[e.currentTarget.name] = e.currentTarget.value;
this.model.set(attribute);
},
});
})();
Maybe the approach is wrong and I'm on the wrong track
You should checkout the way with custom regions, described by Brian Mann at backbonerails.com
So the idea is following:
1) Define a region in your app with special class, lets call it DialogRegion
regions: {
dialogs: {
selector: '#dialogs',
regionClass: DialogRegion
}
}
2) Extend DialogRegion like following. I used Bootstrap modal API, please expect
var DialogRegion = Marionette.Region.extend({
onShow: function(view) {
view.$el.addClass('modal');
view.$el.modal();
// add handler to close popup via event
view.on('before:destroy', function() {
view.$el.modal('hide');
});
//destroy view on popup close
view.$el.on('hidden.bs.modal', function (e) {
view.destroy();
});
})
})
3) Later from any place of your app you can render Modal via rendering any view in dialogs App region:
App.dialogs.show( new SomeSuperView({
model: model
}))
I recommend you to checkout tutorial at Backbonerails to clarify this way. Hope you will find it usefull

How to trigger events in backbone and marionette?

Here is my widget module:
app.module("Widget.MyWidget", function(MyWidget, app, Backbone, Marionette, $, _) {
this.on("start", function(el){
var that = this;
});
this.on("click", function(el){
alert("click");
});
this.renderWidget = function (id) {
var that = this;
var el = '#widget_' + id;
widgetModel = new app.Widget.MyWidget.models.meteo();
widgetModel.getWidget(id)
.fetch({ type: "GET" })
.done(function(){
var widgetView = new that.views.widgetView({model:widgetModel, el : el});
widgetView.render();
})
});
};
});
and here is the Marionette view:
define(['hbs!modules/myWidget'],function (template) {
var meteoView = Marionette.ItemView.extend({
template: template,
events: {
"dblclick" : "open",
"click" : "select",
"contextmenu .icon.doc" : "showMenu",
"click .show_notes" : "toggleNotes",
"click .title .lock" : "editAccessLevel",
"mouseover" : "showTooltip"
},
onShow: function(){
var view = this;
alert("showing");
}
});
return meteoView;
});
Everything works perfectly: the model is loaded and populated with data, the view is rendered on screen with the data.
But, I cannot set any event on the view: I would like to attach a click event to the view: What do I do wrong ?
regards
You must define your callbacks in your view :
var meteoView = Marionette.ItemView.extend({
....
open: function() {},
select: function() {},
showMenu: function() {},
toggleNotes: function() {},
editAccessLevel: function() {},
showTooltip: function() {},
....
}

Backbone collection length always set to one with nested views

When the view is loaded, the collection length always return 1 while there is currently 3 members shows up in the parse function. I wondered if the problem was the nested item view, but it seems to behave the same without ! I don't understand why the self.push(member) does not add the model to the collection ! Bit stuck here, any help please ?
The model
define([
'backbone'
], function(Backbone) {
'use strict';
var MemberModel = Backbone.Model.extend({
id: "_id"
});
return MemberModel;
});
The collection
define([
'backbone',
'models/MemberModel'
], function(Backbone, MemberModel) {
'use strict';
var Members = Backbone.Collection.extend({
model: MemberModel,
url: '/api/v1/users',
parse: function(response, options) {
var self = this;
_.each(response.users, function(item){
var member = new self.model();
member.set('email', item.email);
member.set('firstname', item.firstname);
member.set('lastname', item.lastname);
member.set('group', item.group);
member.set('city', item.city);
// shows every member's emails
console.log('member.email='+member.get('email'));
self.push(member);
});
console.log('this.length='+this.length); // this is always equal to 1
return this.models;
}
});
return Members;
});
The view
define([
'collections/members',
'views/membersItem',
'text!templates/members.html'
], function(MembersCollection, MembersItem, membersTpl) {
'use strict';
var Members = Backbone.View.extend({
el: '#settings-content',
template: _.template(membersTpl),
events: {
'click #edit-member': 'editMember'
},
initialize: function() {
this.collection = new MembersCollection();
this.render();
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
var self = this;
this.collection.fetch({
success: function() {
self.renderMember();
}
});
return this;
},
renderMember: function() {
// same here collection length = 1
console.log('collection.length:'+this.collection.length);
_.each(this.collection.models, function (item) {
var memberView = new MembersItem({model: item});
$('.list-item', this.el).append(memberView.render().el);
}, this);
}
});
return Members;
});
The nested item view
define([
'text!templates/members_item.html'
], function(membersItemTpl) {
'use strict';
var MembersItem = Backbone.View.extend({
tagName: "tr",
className: '.item',
template: _.template(membersItemTpl),
initialize: function() {
this.model.bind("change", this.render, this);
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
return MembersItem;
});
Looks like the problem is with duplicated id's in the collection. It is supposed to be a unique Identifier.
Set the idAttribute inside the model and make sure, the id's for the 3 objects in question are different.
var MemberModel = Backbone.Model.extend({
idAttribute: "_id"
});
If your id is duplicated then the model will not be added to the model.
If that does not work try setting the id Attribute explicitly
_.each(response.users, function(item, index){
var member = new self.model();
member.set('_id', index);

Backbone Keypress not triggering after adding template

I've created my own version of what is basically: todomvc dependency-example, but I built it from looking at this Modular Backbone Example. I'm trying to move hardcoded html in the base template to it's own template file. I plan on creating multiple pages and want a minimal base template. However, when I load and insert the template in the view, the keypress event for createOnEnter stops working. Every other feature still works, which includes the other event listed in events (clearCompleted).
See: this.$el.append(notesTemplate);.
The browser never makes it to the function createOnEnter().
My app view:
define([
'jquery',
'underscore',
'backbone',
'models/notes/NoteModel',
'collections/notes/NoteCollection',
'views/notes/NotesListView',
'text!templates/notes/statsTemplate.html',
'text!templates/notes/notesTemplate.html'
], function($, _, Backbone, NoteModel, NoteCollection, NotesListView, statsTemplate, notesTemplate){
'use strict';
var NotesView = Backbone.View.extend({
el: $("#page"),
events: {
"keypress #new-note": "createOnEnter",
"click #clear-completed": "clearCompleted"
},
initialize: function() {
var onDataHandler = function(collection) {
this.render();
}
this.$el.append(notesTemplate);
this.model = new NoteCollection();
this.model.fetch({ success : onDataHandler, dataType: "jsonp"});
this.input = this.$("#new-note");
this.allCheckbox = 0;
this.listenTo(this.model, 'add', this.addOne);
this.listenTo(this.model, 'reset', this.addAll);
this.listenTo(this.model, 'all', this.render);
this.footer = this.$('footer');
this.main = $('#main');
this.model.fetch();
},
render: function() {
var done = this.model.done().length;
var remaining = this.model.remaining().length;
if (this.model.length) {
this.main.show();
this.footer.show();
this.$('footer').show();
this.footer.html(_.template(statsTemplate, {done: done, remaining: remaining}));
} else {
this.main.hide();
this.footer.hide();
}
this.allCheckbox.checked = !remaining;
},
addOne: function(note) {
var view = new NotesListView({model: note});
$("#notes-list").append(view.render().el);
},
addAll: function() {
this.model.each(this.addOne);
},
createOnEnter: function(e) {
if (e.keyCode != 13) return;
if (!this.input.val()) return;
this.model.create({title: this.input.val()});
this.input.val('');
},
clearCompleted: function() {
_.invoke(this.model.done(), 'destroy');
return false;
},
toggleAllComplete: function () {
var done = this.allCheckbox.checked;
this.model.each(function (note) { note.save({'done': done}); });
}
});
return NotesView;
});
Solved!
I didn't provide enough information for anyone to find the problem. It was a typo in the element with the ID #new-note. The above code works just fine.
I accomplished loading templates like this by setting the template option in the view like this:
template: _.template(notesTemplate),
and then in my render function calling:
this.$el.html(this.template());
to actually render it. This ensures the events get delegated properly.

uncaught typeerror: object function has no method 'tojson'

I have created a model like this
define(['backbone', 'text_dictionary'], function(Backbone, Text_Dict) {
var IndexPageModel = Backbone.Model.extend({
defaults:{
val_btn_gotohomepage : Text_Dict.val_btn_gotohomepage,
val_btn_gotologinpage : Text_Dict.val_btn_gotologinpage,
val_btn_gotolistpage : Text_Dict.val_btn_gotolistpage
}
});
return IndexPageModel;
});
and instantiated this model with 'new' in my page code like this
define([ 'page_layout',
'panel_itemview',
'header_itemview',
'content_itemview',
'footer_itemview',
'templates',
'text_dictionary',
'indexpage_model',
'indexpage_logic'],
function( Page,
Panel,
Header,
Content,
Footer,
Templates,
Text_Dict,
IndexPageModel,
IndexPage_BusnLogic) {
console.log("Success..Inside Index Page.");
var Page_Index = {};
Page_Index.page = (function(){
var _pageName = Text_Dict.indexpage_name;
var _pageModel = new IndexPageModel();
return _pageLayout = Page.pageLayout({
name:_pageName,
panelView: Panel.panelView({name:_pageName, pagetemplate: Templates.simple_panel}),
headerView: Header.headerView({name:_pageName, title: Text_Dict.indexpage_header, pagetemplate: Templates.header_with_buttons}),
contentView: Content.contentView({name:_pageName, page_model:_pageModel, pagetemplate:Templates.content_index, busn_logic:IndexPage_BusnLogic.HandleEvents}),
footerView: Footer.footerView({name:_pageName, title: Text_Dict.indexpage_footer, pagetemplate: Templates.simple_footer})
});
})();
return Page_Index;
});
my page gets created using the page layout
define([ 'underscore', 'marionette' ], function( _, Marionette ) {
console.log("Success..Inside Index View.");
var Page = {};
var _ReplaceWithRegion = Marionette.Region.extend({
open: function(view){
//Need this to keep Panel/Header/Content/Footer at the same level for panel to work properly
this.$el.replaceWith(view.el);
}
});
Page.pageLayout = function (opts) {
var _opts = _.extend ({ name: 'noname',
panelView: null,
headerView: null,
contentView: null,
footerView: null,
}, opts);
return new ( Marionette.Layout.extend({
tagName: 'section',
attributes: function() {
return {
'id': 'page_' + _opts.name,
'data-url': 'page_' + _opts.name,
'data-role': 'page',
'data-theme': 'a'
};
},
template: function () {
return "<div region_id='panel'/><div region_id='header'/><div region_id='content'/><div region_id='footer'/>";
},
regions: {
panel: {selector: "[region_id=panel]", regionType: _ReplaceWithRegion},
header: {selector: "[region_id=header]", regionType: _ReplaceWithRegion},
content: {selector: "[region_id=content]", regionType: _ReplaceWithRegion},
footer: {selector: "[region_id=footer]", regionType: _ReplaceWithRegion},
},
initialize: function(){
$('body').append(this.$el);
this.render();
},
onRender: function() {
if (this.options.panelView) {
this.panel.show (this.options.panelView);
};
if (this.options.headerView) {
this.header.show (this.options.headerView);
};
if (this.options.contentView) {
this.content.show(this.options.contentView);
};
if (this.options.footerView) {
this.footer.show (this.options.footerView);
};
},
}))(_opts);
};
return Page;
});
but in my itemview when i am passing model reference like this
define([ 'underscore', 'marionette', 'event_dictionary', 'app' ], function(_,
Marionette, Event_Dict, App) {
console.log("Success..Inside Content Index View.");
var Content = {};
Content.contentView = function(opts) {
return new (Marionette.ItemView.extend({
tagName : 'div',
attributes : function() {
console.log('options name==' + opts.name);
console.log("page model=="+opts.page_model);
return {
'region_id' : 'content',
'id' : 'content_' + opts.name,
'data-role' : 'content'
};
},
initialize : function() {
_.bindAll(this, "template");
},
template : function() {
return opts.pagetemplate;
},
model : function() {
return opts.page_model;
}
}))(opts);
};
return Content;
});
It's giving me error
Uncaught TypeError: Object function () {
return opts.page_model;
} has no method 'toJSON'
The model property of a view cannot be a function. Backbone allows this for some things like url (by way of the _.result helper function), but not in this case. Change your view code to not have a model function and just do this in initialize:
initialize: function (options) {
this.model = this.page_model = options.page_model;
}
UPDATE since you won't just take my word for it, here is the Marionette source that is almost certainly the top of your exception stack trace. Once again: view.model has to be a model object not a function. Fix that and the error will go away.
The accepted answer is correct, but it took a bit of messing about to find out why I had that error coming up, so I'm offering what the solution for my personal use-case was in case it helps anyone else stumbling upon this page in the future.
I had this:
app.module 'Widget.Meta', (Meta, app, Backbone, Marionette, $, _) ->
Meta.metaView = Backbone.Marionette.ItemView.extend
model: app.Entities.Models.meta
template: '#meta-template'
... when I should have had this:
app.module 'Widget.Meta', (Meta, app, Backbone, Marionette, $, _) ->
Meta.metaView = Backbone.Marionette.ItemView.extend
model: new app.Entities.Models.meta()
template: '#meta-template'
It's just a matter of instantiating the function definition.

Resources