Best Practice to add UI enhancements to multiple Backbone Marionette views - backbone.js

So I was what the best way for all views in an application to have actions performed on an element.
In a non single page application you would run say:
$(document).ready(function() {
$('.autosize').autosize();
});
to apply autosize function to all elements with the autosize class on every page.
Now in a Backbone Marionette app to do this you could perform that in each view with onDomRefresh or similar but for things that affect 90% of views you'd want this to run automatically somehow.
I don't think there's a way that an Application object can listen to all onDomRefresh events which would potentially solve it. I've consider overloading Marionette.MonitorDOMRefreshto add this in but it doesn't feel like a Backbone approach.
Other things I considered were sub-classing each of the marionette views to add mixins for loading different groups of UI elements.
I figured other people must have experienced this scenario so was interested what approaches have been used.

Just make a base View class and inherit from it every view class that needs the autosize enhancement.
var AutosizeBaseView = Backbone.Marionette.ItemView.extend({
onDomRefresh: function(){
this.$('.autosize').autosize();
}
});
then make your classes like this:
var SomeView = AutosizeBaseView.extend({
});

So I couldn't really find any solutions that really solved my problem, despite some helpful chats with #julio_menedez and #marionettejs on Twitter. With a really good idea being using Polymer but wasn't suitable as I need to support older IE's.
So instead I headed into the dangerous world of monkey patching to solve it (Bear in mind I might need to iron out some wrinkles with this still, just finished writing it and not fully tested it - I'll update accordingly)
In Coffeescript: (javascript version at the bottom)
# Monkey patching the Marionette View.. sorry!
# this is the only Marionette view which doesn't have it's own constructor
Marionette.ItemView = Marionette.ItemView.extend
constructor: ->
Marionette.View.prototype.constructor.apply #, Array.prototype.slice.call(arguments, 0)
original_view_constructor = Marionette.View.prototype.constructor
Marionette.View.EventAggregator = event_aggregator = _.extend {}, Backbone.Events
# all the other constructors call this so we can hijack it
Marionette.View.prototype.constructor = ->
event_aggregator.listenTo #, 'all', =>
args_array = Array.prototype.slice.call arguments, 0
event_aggregator.trigger.apply event_aggregator, [ 'view:' + args_array[0], # ].concat(args_array.slice(1))
event_aggregator.stopListening # if args_array[0] == 'close'
original_view_constructor.apply #, Array.prototype.slice.call(arguments, 0)
And then to use I just setup a listener in my application object to catch view events I need. e.g:
#listenTo Marionette.View.EventAggregator, 'view:dom:refresh', (view) ->
view.$('div').css('backgroundColor', 'red');
So in my view these are the pros and cons of this technique:
Pros:
Can listen to all view events without injecting all view classes or subclassing all view classes
Simple to use
Objects don't need to opt-in to using it at all
Cons
Uses monkey patching, dangerous to Marionette API Changes
Uses Marionette namespacing so vulnerable to a future Marionette namespace collision
Takes dealing with views out of view context
Having an event aggregator object isn't something seen elsewhere in Backbone/Marionette (afaiw) so breaks a pattern (update - something similar is seen with Backbone.history)
Anyway I'm welcome to feedback, alternatives, criticism :-) and hope maybe this helps someone else in the same situation
Javascript:
(function() {
var event_aggregator, original_view_constructor;
Marionette.ItemView = Marionette.ItemView.extend({
constructor: function() {
return Marionette.View.prototype.constructor.apply(this, Array.prototype.slice.call(arguments, 0));
}
});
original_view_constructor = Marionette.View.prototype.constructor;
Marionette.View.EventAggregator = event_aggregator = _.extend({}, Backbone.Events);
Marionette.View.prototype.constructor = function() {
var _this = this;
event_aggregator.listenTo(this, 'all', function() {
var args_array;
args_array = Array.prototype.slice.call(arguments, 0);
event_aggregator.trigger.apply(event_aggregator, ['view:' + args_array[0], _this].concat(args_array.slice(1)));
if (args_array[0] === 'close') {
return event_aggregator.stopListening(_this);
}
});
return original_view_constructor.apply(this, Array.prototype.slice.call(arguments, 0));
};
}).call(this);

In CoffeeScript I think you could also do:
extend = (obj, mixin) ->
obj[name] = method for name, method of mixin
obj
include = (klass, mixin) ->
extend klass.prototype, mixin
include Marionette.View,
onDomRefresh: () -> #$('.autosize').autosize()
Which should cover all the view types. Haven't tested this specifically, but just did something very similar to add functionality to Marionette's Layout view. Extend / include pattern at http://arcturo.github.io/library/coffeescript/03_classes.html. Of course this should all be doable in straight up JS too.
UPDATE:
Actually, since we have Underscore available to us we don't need to manually define the include and extend methods. We can just say:
_.extend Marionette.View.prototype,
onDomRefresh: () -> #$('.autosize').autosize()

Related

How use set method on model Backbone to change a data?

I'm new with Backbone and I think I misunderstand the use of get/set with the model. I made a really simple example with IPython widget :
class Automaton(widgets.DOMWidget):
from IPython.display import Javascript
_view_name = traitlets.Unicode('AutomatonView', sync=True)
nodes = traitlets.List(sync=True)
%%javascript
require(['widgets/js/widget'], function(WidgetManager){
var AutomatonView = IPython.DOMWidgetView.extend({
render: function(){
var n_nodes = this.model.get("nodes").slice();
n_nodes[0] += 1;
this.model.set("nodes", n_nodes);
return this;
},
});
WidgetManager.register_widget_view("AutomatonView", AutomatonView);
});
So now I can call the widget like that:
a = Automaton(nodes=[1])
What I understand here is: nodes is passed to the model and now in the model nodes = [1], then I create a new node [2] that I'm setting to the model, so I'm expected for the nodes value be [2] now, but If I ask
a.nodes
It's say:
[1]
Sorry if I don't understand something really basic, if you can explain whats is happening here it will be really useful for me. I checked if the n nodes is [2] with a console log and it's fine so it's really with "this.model.set" the problem.
Are you sure you're rendering the view? Try to put this code into an initialize method instead, and everything should work just fine. By the way, this should work even without a set method call:
initialize: function() {
var n_nodes = this.model.get("nodes");
n_nodes[0] += 1;
return this;
},
Maybe that can help someone but I fixed my error with adding
this.touch();
I found my answer on this example : http://nbviewer.ipython.org/github/ipython/ipython/blob/2.x/examples/Interactive%20Widgets/Custom%20Widgets.ipynb
" it is very important that we call this.touch() to let the widget machinery know which view changed the model "

Is it good practice to combine CREATE and EDIT controllers in AngularJS?

There are many duplicated code among CREATE and EDIT controllers.
These controllers could be combined into one for minimizing repetitive code.
The problem: I need to distinguish which method to use on form submitting - create() or edit() for example.
The solution: I could add $scope.mode for example and set $scope.mode='edit' if user clicked 'EDIT' button or set $scope.mode='add' if user clicked 'ADD' button.
I could use services for minimizing repetitive code, but there still will be duplicated code. For example in both controllers I have cancel() method which clears the form and hide it. I could store clearForm() and hideForm() in the service, but this code will be duplicated in both controllers:
$scope.cancel = function() {
Service.clearForm();
Service.hideForm();
};
Questions:
Is it good practice to combine CREATE and EDIT controllers in AngularJS?
Is there any good practices to minimize repetitive code?
Yes. Use 1 controller.
Here is the reason why use 1 controller
The job of the controller is to support the View. Your create view and the edit view is exactly same - just that one has data pre-populated (edit) and another does not (create).
Moreover the "purpose" of this View is to have the user change or enter new values in the form. Your only difference should be something like reset(). But even there you could start with an empty model object e.g. $scope.entity = {} in case of CREATE and you will start with $scope.entity = $http.get().
Repetition Problem with 2 Controllers
With 2 different controllers and services you are going to incur at least the following duplication:
$scope.cancel = function() {
Service.cancel();
};
$scope.validate = function() {
ValidtionSvc.validate();
}
.
.
.//other stuff similar
but the problem is why even this duplication like you stated.
(UDATED here onwards since above was the answer to the 1st question)
How to use 1 controller with repetition ?
Is there any good practices to minimize repetitive code?
Question redefined: Is there a good practice of eliminating repetitive code in CREATE and EDIT forms ?
No formal 'best practice' exist to my knowledge to avoid repetitive code in this specific situation. However I am advising against mode=edit/create. The reason being for controllers in this situation there should be almost no difference since their job is to purely to fetch/update the model as the user interacts.
Here are the difference you will encounter in this situation and how you can avoid if/then/else with mode=create/edit:
1) Populating the form with existing values vs. empty form for Create.
To fetch a existing entities you need some key/query data. If such key data is present you could do
var masterEntity = {};
if(keyData) {
masterEntity = MyEntityResourceFactory.getEntity(keyData);
}
$scope.entity = masterEntity;//for Create this would be {}
2) reset() form
should be simply
$scope.reset = function() {
$scope.entity = masterEntity;
}
3) Update/Create
$http.post()//should not be different in today's world since we are treating PUT as POST
4) Validation - this is a perfect reuse - there should be no differences.
5) Initial / Default Values
You can use masterEntity = Defaults instead of {}.
Is it good practice to combine CREATE and EDIT controllers in
AngularJS?
In my experience, yes it is a good idea for 99.9% of the time. I typically inject a formType variable into my controller via the $routeProvider resolve feature. So I would have something like the following:
$routeProvider
.when('/item/create', {
templateUrl: '/app/item/itemForm.html',
controller: 'itemFormController',
resolve: {
item: ['$route', 'itemRepository', function ($route, itemRepository) {
return itemRepository.getNew();
}],
formType: function () { return Enums.FormType.CREATE; }
},
})
.when('/item/edit/:itemId', {
templateUrl: '/app/item/itemForm.html',
controller: 'itemFormController',
resolve: {
item: ['$route', 'itemRepository', function ($route, itemRepository) {
return itemRepository.get($route.current.params.itemId);
}],
formType: function () { return Enums.FormType.EDIT; },
},
});
That way you get your entity and type of form action injected into the controller. I also share the same templates, so saving a form I can either rely on my repository/service to determine what REST endpoint to call, or I can do a simple check inside the controller depending on what formType was injected.
Is there any good practices to minimize repetitive code?
Some of the things I'm using to keep things DRY:
If you keep a common convention on your server API you can go a very long way with a base factory/repository/class (whatever you want to call it) for data access. For instance:
GET -> /{resource}?listQueryString // Return resource list
GET -> /{resource}/{id} // Return single resource
GET -> /{resource}/{id}/{resource}view // Return display representation of resource
PUT -> /{resource}/{id} // Update existing resource
POST -> /{resource}/ // Create new resource
etc.
We then use a AngularJs factory that returns a base repository class, lets call it abstractRepository. Then for each resource I create a concrete repository for that specific resource that prototypically inherits from abstractRepository, so I inherit all the shared/base features from abstractRepository and define any resource specific features to the concrete repository. This way the vast majority of data access code can be defined in the abstractRepository. Here's an example using Restangular:
abstractRepository
app.factory('abstractRepository', [function () {
function abstractRepository(restangular, route) {
this.restangular = restangular;
this.route = route;
}
abstractRepository.prototype = {
getList: function (params) {
return this.restangular.all(this.route).getList(params);
},
get: function (id) {
return this.restangular.one(this.route, id).get();
},
getView: function (id) {
return this.restangular.one(this.route, id).one(this.route + 'view').get();
},
update: function (updatedResource) {
return updatedResource.put();
},
create: function (newResource) {
return this.restangular.all(this.route).post(newResource);
}
// etc.
};
abstractRepository.extend = function (repository) {
repository.prototype = Object.create(abstractRepository.prototype);
repository.prototype.constructor = repository;
};
return abstractRepository;
}]);
Concrete repository, let's use customer as an example:
app.factory('customerRepository', ['Restangular', 'abstractRepository', function (restangular, abstractRepository) {
function customerRepository() {
abstractRepository.call(this, restangular, 'customers');
}
abstractRepository.extend(customerRepository);
return new customerRepository();
}]);
What you'll find if you use this base repository pattern is that most of your CRUD controllers will also share a lot of common code, so I typically create a base CRUD controller that my controllers inherit from. Some people dont like the idea of a base controller, but in our case it has served as well.
The answer to your first question probably depends on the specific circumstances.
If the two controllers share a substantial amount of operations, and the behavior of just one or two functions needs to be altered - why not! Maybe not the most elegant solution but hey, whatever works.
If the behavior of many or all controller operations is going to depend on '$scope.mode'...I'd say be careful. That seems like a dangerous path.
Angular services have always served me well when it comes to minimizing code replication between controllers. If there is a "good practice to minimizing repetitive code," I would say it would be services. They are global to your app and can be injected into multiple controllers without issue.
I hope that helps!

Multiple resources in one view

I want to display a list of images and their respective comments. Like:
Image url | Format | Comments
http://example.com/img.jpg | 1280x420 | [Comment 1], [Comment 2] ...show all ...show all
http://example.com/img2.jpg | 630x590 | [Comment 1], [Comment 2] ...show all
I have two resouces: /images and /comments/{image_id}
What is the recommended way to fetch the comments for each image to be able to display them on the same row? Does Marionette have a helper for this?
In my opinion, these look like a good place to use relational models. Backbone doesn't support these out of the box, so you'll need a plugin. Have a look at Backbone-Relational or supermodel.js. These projects provide better forms of model nesting than the default implementation. From there, use nested composite views to render the collections.
From what I know Marionette does not have such helper. I think you can use something simple like:
var ImageComments = Backbone.Collection.extend({
initialize: function(models, options) {
options || (options = {});
this.imageId = options.imageId;
Backbone.Collection.prototype.initialize.apply(this, arguments);
},
urlRoot: function() {
return 'comments/' + this.imageId;
}
});
var id = 1,
image = new Image({ id: id }),
comments = new ImageComments(null, { imageId: id });
$.when(image.fetch(), comments.fetch()).done(function() {
// .. do your things with image & comments
});
This describes simple case, if that's commonly used in your application you might want to implement your own fetch method (e.g. for image, that will also fetch comments) or use plugins like Backbone-relational or Backbone-associations
You can use nested composite views.
http://davidsulc.com/blog/2013/02/03/tutorial-nested-views-using-backbone-marionettes-compositeview/
You can also do old fashioned in template loops for the comments
http://www.headspring.com/an-underscore-templates-primer/

Show layout in Marionette.Application inside controller

I am trying to structure my code with MVC flow within my application. I am trying to show created layouts in my marionette app instance within my marionette.controller as below..
Can anyone please tell me is it a proper way to show or change layouts within controller is proper way or not? And if not then what's the proper approach for that.
My Controller
define([ 'marionette', 'app', 'index_view' ], function( Marionette, App, IndexView ) {
console.log("Inside...ViewFlow Controller.");
var ViewFlow_Controller = Marionette.Controller.extend({
loadIndex : function() {
console.log("Inside...Load Index Method.");
App.main.show( new IndexView() );
}
});
return new ViewFlow_Controller();
});
where my IndexView is like this
define(['app', 'helper', 'templates'],
function (App, Helper, templates){
console.log("Inside...Index View.");
App.Page_Index = (function(){
var Page_Index = {};
var _pageName = 'IndexPage';
var _pageLayout = Helper.newPageLayout({
name:_pageName,
panelView: Helper.newPanelView(),
headerView: Helper.newHeaderView({name:_pageName, title:'Welcome to the Index Page'}),
contentView: Helper.newContentView({name:_pageName, template: templates.content_index}),
footerView: Helper.newFooterView({name:_pageName, title:'IndexPage Footer'})
});
return Page_Index;
})();
return App.Page_Index;
});
My helper returns me App_Layout instance.
But it's not working, it's giving me an error
Uncaught TypeError:object is not a function viewflow_controller.js:12
Please help me out.
You can find the code here if you want to refer to the complete code or contribute.
Thanks in advance.
The code on GitHub seems to contain only empty files (aside from the libraries), so I'm going to assume Helper returns a layout instance (which you seem to have indicated, saying it returned an App_Layout instance).
It looks like you're using layouts wrong. The way to use layouts is basically:
Create a layout instance with regions (e.g.) panelRegion and contentRegion
Create view instances that will be displayed in the layout (e.g.) panelViewInstance and contentViewInstance
Write a handler to show your views when the layout itself is shown.
The handler should look like this:
myLayout.on("show", function(){
myLayout.panelRegion.show(panelViewInstance);
myLayout.contentRegionshow(contentViewInstance);
});
Then, show that layout in one of your app's regions:
MyApp.mainRegion.show(myLayout);
The documentation on layouts is here: https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.layout.md
You can learn more on using layouts and structuring your code in my book on Marionette.

advice on how to design an extendable backbone view

I have the following backbone application.
It's a generic crud view, with the following template:
<div id="<%= crudId %>">
<div class="header-view"></div>
<div class="table-view"></div>
<div class="form-view"></div>
</div>
You can see the crud live here: http://bbbootstrap.com.ar/index.html#Wine
The view itself has subviews, to be rendered in the table-view and the form-view.
The thing is I want it to be a base crud view, and to be easily entendable, adding new subviews, for example, adding a new panel to issue some bulk operations.
These are the possible solutions I came out with so far
1- inheritance: create a new CrudBulkView inheriting from CrudView, modify the template to have a bulk-view place holder.
pro: inheritance can provide quite an elegant and simple solution
cons: it's a bit limiting, I'd like to just be able to compose the BulkView and add it to the CrudView.
2- add a method to crudview like addView(view, place) with place being something like 'beforeForm', 'afterForm', 'beforeTable', etc... (it's much too hardcoded...
cons: too hardcoded
3- pass a function with each subview I want to add, that takes care of creating the dom and attaching to it, right after CrudView has rendered the container. the method could be called setEl and return the newly created el.
pro: really flexible
cons: adds some complexity to the process of attaching the subview to the dom
4-modify the crudView template and then attach to it, something like this:
<div id="<%= crudId %>">
<div class="header-view"></div>
<div class="table-view"></div>
<div class="form-view"></div>
<div class="bulk-view"></div
</div>
then bulkView.el would be '.bulk-view'
pro: simple approach
cons: have to mess around with strings, instead of dealing with the dom
I think it's not so strange what I'm trying to achieve. I just want to add a view to a container view, being as much decoupled as possible, and being able to establish where it should be rendered.
After reading your response to my previous answer I went through and modified my example to hopefully give you an idea of how you can implement a system with named views that allows you to control the ordering as you desire. Let me know if this helps or if you have any questions about how it works.
var viewCtor = Backbone.View.prototype.constructor;
// Assuming we have a reference to the subviews already
var BaseCrudView = Backbone.View.extend({
// This is null for an important reason, see comment in constructor
subViews: null,
// Override the constructor instead of initialize since this is meant to be a base object, so things that
// inherit don't have to remember to call the parent inialize every time.
constructor: function() {
viewCtor.apply(this, arguments);
// It is important this is initialized when instantiating the view rather than in the prototype.
// Backbone's extend() will "copy" the prototype properties of the parent when extending, which really
// just performs an assignment. If this were initialized above in the prototype then all children
// that inherit from that prototype would share the exact same instance of the array/object. If a child
// adds something to the array, it would be changed for all instances that inherit from the parent.
this.subViews = {
header: new HeaderView(),
table: new TableView
};
this.subViewOrder = [
'header',
'table'
];
},
addBefore: function(subView, name, beforeView) {
this.subViews[name] = subView;
var viewLoc = this.subViewOrder.indexOf(beforeView);
if(viewLoc == -1) {
viewLoc = 0;
}
this.subViewOrder.splice(viewLoc, 0, name);
},
addAfter: function(subView, name, afterView) {
this.subViews[name] = subView;
var viewLoc = this.subViewOrder.indexOf(afterView);
if(viewLoc == -1) {
viewLoc = this.subViewOrder.length - 1;
}
this.subViewOrder.splice(viewLoc + 1, 0, name);
},
moveBefore: function(name, beforeView) {
this.addBefore(this.subViews[name], name, this.subViewOrder.splice(this.subViewOrder.indexOf(name), 1));
},
moveAfter: function(name, afterView) {
this.addAfter(this.subViews[name], name, this.subViewOrder.splice(this.subViewOrder.indexOf(name), 1));
},
render: function() {
var that = this;
_.each(this.subViewOrder, function(viewName) {
// Assumes the render() call on any given view returns 'this' to get 'el'
that.$el.append(this.subViews[viewName].render().el);
});
return this;
}
});
var BulkCrudView = BaseCrudView.extend({
inialize: function() {
// Skipping the last parameter causes it to insert at the end
this.addAfter(new BulkView(), 'bulkView');
}
});
With this you could easily extend the BulkCrudView and modify its subViews array in initialize to add/insert whatever you want. Though, it'd work just as well to instantiate a BaseCrudView and work with the view methods. Just whatever feels cleaner and/or floats your boat.

Resources