Here is my behavior:
define(['marionette'], function (Marionette){
'use strict';
var Filter = Marionette.Behavior.extend({
events: {
'keyup #ui.filter': 'onFilter'
},
defaults: {
field: "name"
},
onFilter: function () {
console.log(this.options.field);
console.log(this.view);
}
});
return Filter;
});
Here is how I am using it
define(['underscore', 'jquery', 'marionette', 'text!components/slide-out-menu/template.html', 'eventer',
'behaviors/behavior.filter'],
function (_, $, Marionette, templateHTML, eventer, Filter) {
'use strict';
var SlideOutMenuView = Marionette.LayoutView.extend({
tagName: 'section',
className: 'menu open',
template: _.template(templateHTML),
ui: {
next: '.next',
prev: '.prev',
cancel: '.cancel',
submit: '.submit',
title: 'header h1',
filter: 'header input',
toggleGroups: '.toggle-groups',
toggleSegments: '.toggle-segments'
},
events: {
'click #ui.cancel': 'menuClose',
'click #ui.submit': 'updateSelection',
'click #ui.next': 'onNextClick',
'click #ui.prev': 'onPrevClick',
'click #ui.toggleGroups, #ui.toggleSegments': 'onToggleClick'
},
behaviors: {
Filter: {
behaviorClass: Filter
}
},
But I am needing the behavior to transcend into an ItemViewlike this:
var GroupItemView = Marionette.ItemView.extend({
tagName: 'article',
template: _.template(templateHTML),
ui: {
input: 'input'
},
events: {
'click': 'onSelect'
},
behaviors: {
Filter: {
behaviorClass: Filter,
field: "group_name"
}
},
The behavior triggers obviously from inside the LayoutView but I can't get it to fire inside the ItemView... the #ui.filter property is apart of the LayoutView, so maybe that's my problem, but how would that transcend down to the ItemView?
Not fully sure I'm following but can you try bubbling up an event from the ItemView and listening to it in the Layout?
So in the ItemView you can say:
triggers:
"click .elThatYouCareAbout": "el:clicked"
Then in your collection/composite view you can say (you could also listen to this in your controller if you initialize it there):
initialize: ->
#on "childview:el:clicked", (child, args) =>
#trigger "some:other:event", args
Then in your layout you can act on that event. You might not even really need the Behavior with this not that I kind of think about it - I've never actually used one with this model of pub/sub/messaging to different parts of the app.
(excuse the coffeescript) :)
Related
I can't figure out how to use a function to decide which child view to render in marionette. It seems like it should be pretty simple based on the documentation here: https://marionettejs.com/docs/master/marionette.collectionview.html#collectionviews-childview
I found that page from the composite view docs which inferred that using a function to define a childView should be the same for collection and composite views https://marionettejs.com/docs/master/marionette.compositeview.html#compositeviews-childview)
However, with the following code I am getting the error message "Uncaught TypeError: view.on is not a function." My code is below:
var Backbone = require('backbone');
var Marionette = require('backbone.marionette');
var ToDoModel = require('./models/todo');
var ToDo = Marionette.LayoutView.extend({
tagName: 'li',
template: require('./templates/todoitem.hbs')
});
var TodoList = Marionette.CompositeView.extend({
el: '#app-hook',
template: require('./templates/todolist.html'),
childView: function(item) {
return ToDo;
},
childViewContainer: 'ul',
ui: {
assignee: '#id_assignee',
form: 'form',
text: '#id_text'
},
triggers: {
'submit #ui.form': 'add:todo:item'
},
collectionEvents: {
add: 'itemAdded'
},
modelEvents: {
invalid: 'itemInvalid'
},
onAddTodoItem: function() {
this.model.set({
assignee: this.ui.assignee.val(),
text: this.ui.text.val()
});
if (this.model.isValid()) {
var items = this.model.pick('assignee', 'text');
this.collection.add(items);
}
},
itemAdded: function() {
this.model.set({
assignee: '',
text: ''
});
this.ui.assignee.val('');
this.ui.text.val('');
},
itemInvalid: function() {
console.log('this item is invalid!')
}
});
var todo = new TodoList({
collection: new Backbone.Collection([
{assignee: 'Scott', text: 'Write a book about Marionette'},
{assignee: 'Andrew', text: 'Do some coding'}
]),
model: new ToDoModel()
});
todo.render();
Why isn't the ToDo view being rendered?
It appears as if you are using an older version of Marionette (LayoutView for example was removed in version 3) and referencing the documentation for the newest version (currently 3.5.1).
In older versions of Marionette, childView as a function is not supported, instead you should use getChildView
So, the relevant portion of your code should look like:
var TodoList = Marionette.CompositeView.extend({
...
getChildView: function(item) {
return ToDo;
},
...
});
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
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
I have issue in rendering Shopping Bag Views using Backbone for my website.
I am using 1 collection for all Bag Views (“Quick_View” of items list & “Normal_View” of Items list). Also I created “Item_View”, which is being used to render each item in both the Views.
It is a SPA (Single Page Application) and “Quick_View” is initiated and rendered for all Backbone routes and hidden by default. Whenever user clicks on “Quick_View” link from any page it is showing. There is no route defined for it.
The “Normal_View”, can be accessed using Checkout button given in “Quick_View”. It is bind with “domain/checkout” route.
When I access “Normal_View” from “Quick_View” check button; it works fine and both (Quick and Normal) views are in Sync. Means, when we add, delete, update any item in any of the View, both Views are getting updated accordingly.
But when I access “domain/checkout” route directly in a new browser, both views are getting rendered fine, but they are not in sync. Means, change in 1 view does not update another view.
The reason, I tracked is, when I access “Normal_View” through “Quick_View”, model for each item in both the Views having same CID, so the both Views are in sync, if there is any change in a model from any of the View.
And, when I access “Normal_View” directly, model for each item in both the views are not having same CID, so they do not work as expected.
There are few more points to consider:
Collection is firing reset event twice for “Quick_View” and each item
in “Quick_View” is rendering twice.
When, I access “Normal_View” (in either way), “Quick_View” is again getting rendered but once “Normal_View” rendering is over.
// Main View
var mainView = Backbone.View.extend({
el: 'body',
template: {
header: Handlebars.compile(headerTemplate),
mainNav: Handlebars.compile(mainNavtemplate),
footer: Handlebars.compile(footerTemplate)
},
initialize: function() {
_.bindAll();
AW.collection.bag = new bagCollection();
//AW.collection.bag.fetch({reset:true});
},
render: function() {
this.$el.html(this.template());
this.loadSubView('bagQV');
},
loadSubView: function(subView) {
switch(subView) {
case 'home' :
if(!AW.view.home) AW.view.home = new homepageView();
AW.view.home.render();
break;
case 'bagQV' :
if(!AW.view.bagQV) AW.view.bagQV = new bagQuickView({collection: AW.collection.bag});
//AW.view.bagQV.render();
break;
case 'checkout' :
if(!AW.view.checkout) AW.view.checkout = new checkoutView({collection: AW.collection.bag});
AW.view.checkout.render();
break;
}
}
});
// Single Item View
var bagItemView = Backbone.View.extend({
tagName: 'tr',
template: Handlebars.compile(bagItemTemplate),
initialize: function() {
_.bindAll(this);
this.listenTo(this.model, 'change', this.render);
this.listenTo(this.model, 'remove', this.removeItem);
$(document).on('keyup', this.listenKeyboard);
},
events: {
'click .qtyInput .button' : 'updateItem',
'click .controls a.remove' : 'removeModel'
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
this.$el.attr('data-id',this.model.cid);
return this;
},
updateItem: function(e) {
e.preventDefault();
e.stopPropagation();
var newQty = this.$el.find('.qtyInput input').val();
var newAmt = newQty * parseFloat(this.model.get('prodRate').replace('$',''));
this.model.set({prodQty: newQty, amount: '$' + newAmt});
this.cancelEdit(e);
},
removeModel: function(e) {
e.preventDefault();
e.stopPropagation();
if(AW.collection.bag) AW.collection.bag.remove(this.model);
},
removeItem: function() {
this.$el.remove();
}
});
// Bag Quick View
var bagQuickView = Backbone.View.extend({
tagName: 'div',
id: 'myBagQV',
template: Handlebars.compile(bagQuickViewTemplate),
initialize: function() {
_.bindAll(this);
this.collection.fetch({reset:true});
//this.collection.bind("reset", _.bind(this.render, this));
this.listenTo(this.collection, 'add', this.addItem);
this.listenTo(this.collection, 'reset', this.render);
},
render: function() {
if($('#myBagQV').length == 0) {
this.$el.html(this.template());
$('body').append(this.el);
}
this.addAllItems();
return this;
},
addItem: function(item) {
var parent = this;
var itemView = new bagItemView({model: item});
$('#itemsInBag table tbody').append(itemView.render().el);
},
addAllItems: function() {
if(this.collection.length > 0) {
$('#itemsInBag table tbody').html('');
this.collection.each(this.addItem, this);
}
},
});
// Normal Bag View
var bagView = Backbone.View.extend({
tagName: 'div',
id: 'myBag',
template: Handlebars.compile(checkoutBagTemplate),
initialize: function() {
_.bindAll(this);
this.collection.fetch({reset:true});
//this.collection.bind("reset", _.bind(this.render, this));
this.listenTo(this.collection, 'add', this.addItem);
this.listenTo(this.collection, 'reset', this.render);
},
render: function() {
this.$el.html(this.template());
$('#checkoutContainer #details').append(this.el);
this.addAllItems();
return this;
},
addItem: function(item) {
var parent = this;
var itemView = new bagItemView({model: item});
this.$el.find('table tbody').append(itemView.render().el);
},
addAllItems: function() {
if(this.collection.length > 0) {
this.$el.find('table tbody').html('');
this.collection.each(this.addItem, this);
}
}
});
Looking for you help.
Thank you in advance
Cheers,
Vikram
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.