What differences modelEvents and this.model.on - marionette

var ContactManager = new Marionette.Application();
ContactManager.addRegions({
mainRegion: "#main-region",
child:"#child2"
});
Ar = Backbone.Model.extend({});
Se = Backbone.Model.extend({});
Articlescollection = new Ar({ product_id: "104", title: "Test title"});
SelectedsCollection = new Se({ product_id: "71", title: "Test title"});
ContactManager.StaticView = Marionette.ItemView.extend({
template: tpl2,
tagName: "div",
model:Articlescollection,
modelEvents: {
'change': 'fieldsChanged'
},
fieldsChanged:function(){
console.log('dddd')
},
initialize: function () {
this.model.on('change', this.render);
}
});
ContactManager.StaticView2 = Marionette.ItemView.extend({
template: tpl2,
tagName: "div",
model:SelectedsCollection
});
var MyLayout = Backbone.Marionette.LayoutView.extend({
template: tpl3,
regions: {
menu: "#menu",
content: "#content"
}
});
ContactManager.on("start", function() {
// ContactManager.mainRegion.show( new MyLayout )
var layout = new MyLayout
ContactManager.mainRegion.show( layout )
layout.menu.show(new ContactManager.StaticView());
layout.content.show(new ContactManager.StaticView2())
Articlescollection.set("product_id", 24)
//init fieldsChanged trigger for change model
})
ContactManager.start();
What differences between modelEvents and this.model.on ?
they both initizlized when model was change but
modelEvents: {
'change': this.render
},
throw exception Uncaught TypeError: Cannot read property 'split' of undefined

modelEvents is the same as this.listenTo(this.model, { 'change': 'fieldsChanged' }); It is just sugar so you don't have to add that to initialize. You should probably never use this.model.on inside a view. That would not get cleaned up automatically like this.listenTo would. Other than this.on I don't think on should be used in general as listenTo is much safer.
The other major difference here is that:
var model = this.model;
var view = this;
this.model.on('change', function() {
this === model; // true
this === view; //false
});
The only reason this would work with render is because render is forcibly bound to the view by marionette. Any other function would have a different scope. You can change the scope by passing it as the 3rd variable of on, but again then you need to this.model.off in onBeforeDestroy
If you want to call render from modelEvents you have a few options:
modelEvents: {
'change': 'render'
}
//or
modelEvents: function() {
return {
'change': this.render
};
}
// or
modelEvents: {
'change': function() { this.render(); }
}

Related

Backbone-validation.js on Subview

I've been following an online example of backbone validation online:
http://jsfiddle.net/thedersen/c3kK2/
So far so good, but now I'm getting into validating subviews and they're not working.
My code looks like this:
var ScheduleModel = Backbone.Model.extend({
validation: {
StartDate: {
required: true,
fn: "isDate"
},
StartTime: [{
required: true
},
{
pattern: /^([0-2]\d):([0-5]\d)$/,
msg: "Please provide a valid time in 24 hour format. ex: 23:45, 02:45"
}],
EndDate: {
required: true,
fn: "isDate"
}
},
isDate: function (value, attr, computed) {
if (isNaN(Date.parse(value))) {
return "Is not a valid Date";
}
}
});
var ScheduleView = Backbone.View.extend({
tagName: "div",
template: _.template($("#scheduleAddTemplate").html()),
render: function () {
// append the template to the element
this.$el.append(this.template(this.model.toJSON()));
// set the schedule type
var renderedInterval = SetScheduleType(this.model.attributes.ScheduleType.toLowerCase());
// append to the interval
$("#Interval", this.$el).append(renderedInterval.el);
this.stickit();
return this;
},
events: {
"submit #NewScheduleForm": function (e) {
e.preventDefault();
if (this.model.isValid(true)) {
this.model.save(null,
{
success: function (schedule) {
//do stuff
}
},
{ wait: true });
}
}
},
bindings: {
"[name=ScheduleType]": {
observe: "ScheduleType",
setOptions: {
validate: true
}
},
"[name=StartDate]": {
observe: "StartDate",
setOptions: {
validate: true
}
},
"[name=StartTime]": {
observe: "StartTime",
setOptions: {
validate: true
}
},
"[name=EndDate]": {
observe: "EndDate",
setOptions: {
validate: true
}
}
},
initialize: function () {
Backbone.Validation.bind(this);
},
remove: function () {
Backbone.Validation.unbind(this);
}
});
The possible interval I'm currently working with is the following:
var MonthModel = Backbone.Model.extend({
defaults: {
DayOfMonth: 1,
MonthsToSkip: 1
},
MonthsToSkip: {
required: true,
min: 1,
msg: "Number must be greater than 1"
},
DayOfMonth: {
required: function (val, attr, computed) {
console.log(computed.ScheduleType);
if (computed.ScheduleType === "monthly") {
return true;
}
return false;
}
}
});
var MonthlyView = Backbone.View.extend({
tagName: "div",
attributes: function () {
return { id: "Monthly", class: "inline co-xs-4" };
},
template: _.template($("#monthEditTemplate").html()),
render: function () {
// append the template to the element
this.$el.append(this.template(this.model.toJSON()));
this.stickit();
return this;
},
bindings: {
"[name=DayOfMonth]": {
observe: "DayOfMonth",
setOptions: {
validate: true
}
},
"[name=MonthsToSkip]": {
observe: "MonthsToSkip",
setOptions: {
validate: true
}
}
},
initialize: function () {
Backbone.Validation.bind(this);
},
remove: function () {
Backbone.Validation.unbind(this);
}
});
Does anyone have any idea why the subview isn't validating?
Found the way to do it. Posting how it's done in case anyone else ever finds this a problem. I'm only going to show the relevant bits of code without all the bindings, initializing ect.
var ScheduleView = Backbone.View.extend({
render: function () {
// this.Interval
this.Interval = SetScheduleType(this.model.attributes.ScheduleType.toLowerCase(), this.model);
// set the changed interval view
$("#Interval", this.$el).append(this.Interval.render().el);
this.stickit();
return this;
},
events: {
"change #NewScheduleForm": function (e) {
// validate the subview when changes are made
this.Interval.model.validate();
},
"change #ScheduleType": function (e) {
e.preventDefault();
var model = this.model;
var newSchedType = e.target.value;
this.model.attributes.ScheduleType = e.target.value;
this.Interval = SetScheduleType(newSchedType, model);
$("#Interval").html(this.Interval.render().el);
},
"submit #NewScheduleForm": function (e) {
e.preventDefault();
if ((this.model.isValid(true)) && (this.Interval.model.isValid(true))) {
console.log("Success");
this.model.save(null,
{
success: function (schedule) {
//do stuff
}
},
{ wait: true });
}
}
}
});
Essentially I turned the subview into an attribute on the master view. I manually call the validation for the subview on any changes to the master view and on submitting the form.

Using Inheritance Patterns to Organize Large jQuery Applications - how to extend the plugin?

I found this working example of Inheritance Patterns that separates business logic and framework code. I'm tempted to use it as a boilerplate, but since it is an inheritance Pattern, then how can I extend the business logic (the methods in var Speaker)?
For instance, how can I extend a walk: method into it?
/**
* Object Speaker
* An object representing a person who speaks.
*/
var Speaker = {
init: function(options, elem) {
// Mix in the passed in options with the default options
this.options = $.extend({},this.options,options);
// Save the element reference, both as a jQuery
// reference and a normal reference
this.elem = elem;
this.$elem = $(elem);
// Build the dom initial structure
this._build();
// return this so we can chain/use the bridge with less code.
return this;
},
options: {
name: "No name"
},
_build: function(){
this.$elem.html('<h1>'+this.options.name+'</h1>');
},
speak: function(msg){
// You have direct access to the associated and cached jQuery element
this.$elem.append('<p>'+msg+'</p>');
}
};
// Make sure Object.create is available in the browser (for our prototypal inheritance)
// Courtesy of Papa Crockford
// Note this is not entirely equal to native Object.create, but compatible with our use-case
if (typeof Object.create !== 'function') {
Object.create = function (o) {
function F() {} // optionally move this outside the declaration and into a closure if you need more speed.
F.prototype = o;
return new F();
};
}
$.plugin = function(name, object) {
$.fn[name] = function(options) {
// optionally, you could test if options was a string
// and use it to call a method name on the plugin instance.
return this.each(function() {
if ( ! $.data(this, name) ) {
$.data(this, name, Object.create(object).init(options, this));
}
});
};
};
// With the Speaker object, we could essentially do this:
$.plugin('speaker', Speaker);
Any ideas?
How about simply using JavaScript's regular prototype inheritance?
Consider this:
function Speaker(options, elem) {
this.elem = $(elem)[0];
this.options = $.extend(this.defaults, options);
this.build();
}
Speaker.prototype = {
defaults: {
name: "No name"
},
build: function () {
$('<h1>', {text: this.options.name}).appendTo(this.elem);
return this;
},
speak: function(message) {
$('<p>', {text: message}).appendTo(this.elem);
return this;
}
};
Now you can do:
var pp = new Speaker({name: "Porky Pig"}, $("<div>").appendTo("body"));
pp.speak("That's all folks!");
Speaker.prototype.walk = function (destination) {
$('<p>', {
text: this.options.name + " walks " + destination + ".",
css: { color: "red" }
}).appendTo(this.elem);
return this;
}
pp.walk("off the stage");
Runnable version:
function Speaker(options, elem) {
this.elem = $(elem)[0];
this.options = $.extend(this.defaults, options);
this.build();
}
Speaker.prototype = {
defaults: {
name: "No name"
},
build: function () {
$('<h1>', {text: this.options.name}).appendTo(this.elem);
return this;
},
speak: function(message) {
$('<p>', {text: message}).appendTo(this.elem);
return this;
}
};
var pp = new Speaker({name: "Porky Pig"}, $("<div>").appendTo("body"));
pp.speak("That's all folks!");
Speaker.prototype.walk = function (destination) {
$('<p>', {
text: this.options.name + " walks " + destination + ".",
css: { color: "red" }
}).appendTo(this.elem);
return this;
}
pp.walk("off the stage");
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>

Marionette - throws error on `removeRegions` how to solve it

In my app, i have the regions as header,content,footer - in which on the login page, I don't want to use the header, and footer. for that, on onRender i remove the regions what i don't want to be.
But I am getting an error saying: Cannot read property 'empty' of undefined.
here is my template : (i use jade )
div#wrapper
script(type='text/template', id="appTemplate")
div#header
div#content
div#footer
script(type='text/template', id="loginTemplate")
div this is login template
here is my layout.js:
socialApp.AppLayout = Backbone.Marionette.LayoutView.extend({
el:'#wrapper',
template:'#appTemplate',
regions: {
header : '#header',
content : '#content',
footer : '#footer'
},
onRender : function () {
this.removeRegion("header", "#header"); //i am removing header alone here.
}
});
here is my controller.js
socialApp.loginController = Marionette.Controller.extend({
_initialize:function(){
this.loginView = new loginView({model:new loginModel});
this.layout.onRender(); //calling onRender from here...
this.layout.content.show(this.loginView);
}
});
But it's all not working. any one help me the correct way please?
You should never call methods that are prefixed with on manually. Those are there for your code to react to given events, in this case that the view’s render method was invoked.
I would suggest that you instead of trying to remove and then later re-add regions, you create two different layouts. Then when your router hits the login route, you render LoginLayout into your App’s root region, and for other routes, the ‘normal’ layout. Here’s how I solved something similar:
app.js:
var App = new Marionette.Application;
App.addRegions({ root: '#acme' });
// Instantiate User model
App.addInitializer(function()
{
this.user = new UserModel;
});
// Render App layout
App.addInitializer(function()
{
this.layout = this.user.get('id') ? new ContentLayoutView({ identifier: 'content' }) : new UserLayoutView({ identifier: 'user' });
this.root.show(this.layout);
// And let the routers decide what goes in the content region of each layout
this.router = {
content: new ContentRouter,
user: new UserRouter
};
});
layout/content.js
var ContentLayout = Marionette.LayoutView.extend(
{
identifier: 'content',
template: ContentLayoutTemplate,
regions: {
content: '[data-region="content"]',
panelLeft: '[data-region="panel-left"]',
panelRight: '[data-region="panel-right"]'
},
initialize: function()
{
this.content.once('show', function(view)
{
this.panelLeft.show(new PanelLeftView);
this.panelRight.show(new PanelRightView);
}.bind(this));
}
});
layout/user.js
var UserLayout = Marionette.LayoutView.extend(
{
identifier: 'user',
template: UserLayoutTemplate,
regions: {
content: '[data-region="content"]'
}
});
router/content.js
var ContentRouter = Marionette.AppRouter.extend(
{
routes: {
'(/)': '...'
},
createLayout: function(callback)
{
if(App.root.currentView.options.identifier != 'content')
{
var layout = new ContentLayoutView({ identifier: 'content' });
this.region = layout.content;
this.listenTo(layout, 'show', callback);
App.root.show(layout);
}
else
{
this.region = App.root.currentView.content;
callback();
}
},
execute: function(callback, args)
{
if(App.user.get('id'))
{
this.createLayout(function()
{
callback.apply(this, args);
}.bind(this));
}
else
App.router.user.navigate('login', true);
}
});
router/user.js
var UserRouter = Marionette.AppRouter.extend(
{
routes: {
'login(/)': 'showLogin',
'logout(/)': 'showLogout'
},
createLayout: function(callback)
{
if(App.root.currentView.options.identifier != 'user')
{
var layout = new UserLayoutView({ identifier: 'user' });
this.region = layout.content;
this.listenTo(layout, 'show', callback);
App.root.show(layout);
}
else
{
this.region = App.root.currentView.content;
callback();
}
},
execute: function(callback, args)
{
this.createLayout(function()
{
callback.apply(this, args);
}.bind(this));
},
showLogin: function()
{
var LoginView = require('view/detail/login');
this.region.show(new LoginView);
},
showLogout: function()
{
var LogoutView = require('view/detail/logout');
this.region.show(new LogoutView);
}
});

marionette CompositeView access from templatehelpers

MyModule.CompositeView = Marionette.CompositeView.extend({
tagName: "div",
className: 'Liste',
id: idView,
itemView: MonModule.itemView,
itemViewContainer: "ul",
title: 'my title',
template: _.template('<%= header() %><ul></ul>'),
templateHelpers: {
header: function() {
var entete = _.template(collectionHeader, {nom: title});
return entete;
}
}
Hello,
I don't manage to get 'title' in templateHelpers/header from the composite view definition. The 'this' only gives me access to the templateHelpers itself while with an itemView i can access to the items.
Is possible to get this data in templateHelper or do I give up them in this case ?
thankyou
Sure. You can get access to this by assigning a function to templatesHelpers, rather than an object.
e.g:
templateHelpers: function() {
var that = this;
return {
header: function() {
// code goes here
// that refers to the view object
}
}
}

After closing the modal dialog refresh the base view

suggestion and code sample
I am new to Backbone marionette, I have a view ("JoinCommunityNonmemberWidgetview.js") which opens a modal dialog ("JoinCommunityDetailWidgetview.js").On closing of the dialog ( I want the view JoinCommunityNonmemberWidgetview.js") to be refreshed again by calling a specific function "submitsuccess" of the view JoinCommunityNonmemberWidgetview.js.
How can I achieve it.
The code for the modal is as below:
define(
[
"grads",
"views/base/forms/BaseFormLayout",
"models/MembershipRequestModel",
"require.text!templates/communitypagewidget/JoinCommunityWidgetDetailTemplate.htm",
],
function (grads, BaseFormLayout, MembershipRequestModel, JoinCommunityWidgetDetailTemplate) {
// Create custom bindings for edit form
var MemberDetailbindings = {
'[name="firstname"]': 'FirstName',
'[name="lastname"]': 'LastName',
'[name="organization"]': 'InstitutionName',
'[name="email"]': 'Email'
};
var Detailview = BaseFormLayout.extend({
formViewOptions: {
template: JoinCommunityWidgetDetailTemplate,
bindings: MemberDetailbindings,
labels: {
'InstitutionName': "Organization"
},
validation: {
'Email': function (value) {
var emailconf = this.attributes.conf;
if (value != emailconf) {
return 'Email message and Confirm email meassage should match';
}
}
}
},
editViewOptions: {
viewEvents: {
"after:render": function () {
var self = this;
var btn = this.$el.find('#buttonSubmit');
$j(btn).button();
}
}
},
showToolbar: false,
editMode: true,
events: {
"click [data-name='buttonSubmit']": "handleSubmitButton"
},
beforeInitialize: function (options) {
this.model = new MembershipRequestModel({ CommunityId: this.options.communityId, MembershipRequestStatusTypeId: 1, RequestDate: new Date() });
},
onRender: function () {
BaseFormLayout.prototype.onRender.call(this)
},
handleSubmitButton: function (event) {
this.hideErrors();
// this.model.set({ conf: 'conf' });
this.model.set({ conf: this.$el.find('#confirmemail-textbox').val() });
//this.form.currentView.save();
//console.log(this.form);
this.model.save({}, {
success: this.saveSuccess.bind(this),
error: this.saveError.bind(this),
wait: true
});
},
saveSuccess: function (model, response) {
var mesg = 'You have submitted a request to join this community.';
$j('<div>').html(mesg).dialog({
title: 'Success',
buttons: {
OK: function () {
$j(this).dialog('close');
}
}
});
grads.modal.close();
},
saveError: function (model, response) {
var msg = 'There was a problem. The request could not be processed.Please try again.';
$j('<div>').html(msg).dialog({
title: 'Error',
buttons: {
OK: function () {
$j(this).dialog('close');
}
}
});
}
});
return Detailview;
}
);
I would use Marionette's event framework.
Take a look at: https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.commands.md
Specifically, you need to:
1) Create a marionette application :
App = new Marionette.Application();
2) Use the application to set up event handlers
//Should be somewhere you can perform the logic you are after
App.commands.setHandler('refresh');
3) Fire a 'command' and let marionette route the event
App.execute('refresh');

Resources