I´m looking a sample of a backbone / require.js app.
I have the following code:
define([
'jquery',
'underscore',
'backbone',
// Pull in the Collection module from above,
'models/project/ProjectModel',
'collections/projects/ProjectsCollection',
'text!templates/projects/projectsListTemplate.html'
], function($, _, Backbone, ProjectModel, ProjectsCollection, projectsListTemplate){
var ProjectListView = Backbone.View.extend({
el: $("#projects-list"),
render: function(){
var data = {
projects: this.collection.models,
_: _
};
var compiledTemplate = _.template( projectsListTemplate, data );
$("#projects-list").html( compiledTemplate );
}
});
return ProjectListView;
});
Someone could explain me the meaning of the code bellow?
_: _
Short answer: They're assigning _ a value of _ in the data object. Just like projects is assigned this.collection.models.
Longer answer: By doing this you're creating a local reference to _ so when the program is executed it doesn't have to look as far up the scope tree to find _. However, this really isn't needed as it most likely won't have any visible impact on performance.
The thing is there are a million other much larger bottlenecks so you'll most likely never notice the difference. For example, your example uses the require.js test.js plugin which loads external files on demand. However, that means before that text template can be used by your application it has to successfully complete a GET request. Which means the application probably has to wait anywhere from 10ms to 200ms before it can do anything with the template. So, any performance gains by using _:_ are blown out of the water by that alone.
Related
First of all, i'm discovering AngularJS. I read many courses about it but i'm far from being familiar with it.
I have a project, were i cannot modify the previous declarations.
I want to add wysiwyg into the project.
I have to create an other controller using the existant module.
I know that if i redefine the module, previous will be lost.
I thought this would be good :
angular.module('demo')
.controller('WysiwygCtrl', ['colorpicker.module', 'wysiwyg.module', function($scope) {
$scope.data = {
text: "hello"
}
}]);
But it doesn't work.
In fact, the easiest way would be :
angular.module('demo', ['colorpicker.module', 'wysiwyg.module'])
.controller('WysiwygCtrl', function($scope) {
$scope.data = {
text: "hello"
}
});
But it creates a new module and i loose previous one ...
How can i do to make it works ? If you need more code i can edit my question just ask but i think the module/controller is the most important part.
Thanks for you help, i'm facing this problem since this morning.
EDIT1 : The wysiwyg library is hosted on github here https://github.com/TerryMooreII/angular-wysiwyg
EDIT2 : Right now, nothing is displayed because i have the following error :
Error: [$injector:unpr] http://errors.angularjs.org/1.2.16/$injector/unpr?p0=colorpicker.moduleProvider%20%3C-%20colorpicker.module
angular.module('moduleName', ['dep1', 'dep2']) - creates a module, that has dependencies listed in a second parameter, this signature also returns newly created module, you HAVE to specify list of dependencies, even if it's just an empty array []. This also overwrites any existing modules by the same name.
angular.module('moduleName') - returns a module created earlier in your code, hence the absence of dependency list in the signature - this also returns a module.
both signatures allow you to add controllers, services, etc..
Plus I think you need to be passing in references to them modules in the function
.controller('WysiwygCtrl', ['$scope', 'colorpicker.module', 'wysiwyg.module', function($scope, colorpickermodule, wysiwygmodule) {
$scope.data = {
text: "hello"
}
}
If you use the array notation when creating components as controllers or directives, then the parameters on the main function or module should match. As i see that you use
.controller('WysiwygCtrl', ['colorpicker.module', 'wysiwyg.module', function($scope) {
$scope.data = {
text: "hello"
}
you maybe want to say
.controller('WysiwygCtrl', ['$scope', 'colorpicker.module', 'wysiwyg.module', function($scope) {
$scope.data = {
text: "hello"
}
I have an Angular app, MyApp, that depends on external modules (two different map solutions), and I need them both but in different controllers (different modules within MyApp even).
The problem is the two modules both have directives that bind to the same argument ('center' in this case), which causes them both do manipulate a single element. What I want is for one directive to be active inside one controller and the other directive to be active inside another controller - so not have them inpact my elements at the same time.
I don't want to change the code of the external modules to achive this.
I found this to be a very interesting question. The answer below is incomplete, and, frankly, a bit hackish, but it demonstrates a way to rename a directive in another module without modifying the source of the module itself. There is a lot of work to do to make this anywhere near production ready and it absolutely can be improved.
The caveats to the solution are that once a directive is renamed, the "old" name will no longer work. It also depends on some angular conventions that might be changed with future versions, etc, so it's not future proof. It also might fail for complex directives, and I haven't really done any testing on it.
However, it demonstrates that it can be done, and the concept might lead to a feature angular needs (the ability to namespace external modules in order to prevent conflicts such as the one your are experiencing).
I think that if your use case is fairly simple, this will solve your problem, but I wouldn't recommend using it in the general case yet.
(function () {
var util = angular.module('util', [], function ($compileProvider) {
util.$compileProvider = $compileProvider
})
.factory('$directiveRename', function () {
var noop = function () { return function () { }; };
return function (module, directive, newDirective) {
var injector = angular.injector(['ng', module]);
var directive = injector.get(directive + 'Directive');
if(directive)
{
//there can be multiple directives with the same name but different priority, etc. This is an area where this could definitely be improved. Will only work with simple directives.
var renamedDirective = angular.copy(directive[0]);
delete renamedDirective['name'];
util.$compileProvider.directive(newDirective, function () {
return renamedDirective;
});
}
//noop the old directive
//http: //stackoverflow.com/questions/16734506/remove-a-directive-from-module-in-angular
angular.module(module).factory(directive + 'Directive', noop);
};
});
})();
Example usage:
angular.module('app', ['module1', 'module2', 'util'])
.run(function ($directiveRename) {
$directiveRename('module1', 'test', 'testa');
$directiveRename('module2', 'test', 'testb');
});
An alternative, slightly less hackish answer.
Add the following immediately after the script tag that includes angular (before any other modules are loaded)
<script type="text/javascript">
var angularModule = angular.bind(angular, angular.module);
angular.module = function (moduleName, requires, configFn) {
var instance = angularModule(moduleName, requires, configFn);
var directive = instance.directive;
instance.directive = function (directiveName, directiveFactory) {
//You can rename your directive here based on the module and directive name. I don't know the module and directive names for your particular problem. This obviously could be generalized.
if (instance.name == 'module1' && directiveName == 'test') {
directiveName = 'testa';
}
if (instance.name == 'module2' && directiveName == 'test') {
directiveName = 'testb';
}
return directive(directiveName, directiveFactory);
}
return instance;
};
</script>
This works by intercepting calls to module.directive and allowing you the opportunity to rename the directive before it is created.
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()
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.
I've run into a problem that may have to do with my lack of understanding of the use of exports / RequireJS for circular dependencies.
I'm getting the error relatedModel does not inherit from Backbone.RelationalModel.
On to the code (in CoffeeScript; I hope that's alright)...
I have two Backbone Models / RequireJS modules, FooModel and BarModel:
FooModel:
define (require) ->
Backbone = require 'backbone'
BarModel = require 'models/bar'
FooModel = Backbone.RelationalModel.extend
relations: [
type: Backbone.HasMany
key: 'theBars'
relatedModel: BarModel # <-- this is where the BB Relational error is coming from
]
return FooModel
BarModel:
define (require, exports) ->
Backbone = require 'backbone'
FooCollection = require 'collections/foos'
BarModel = Backbone.RelationalModel.extend
someFunction: ->
# uses FooCollection
# I've tried moving the require in here and getting rid of exports
exports.BarModel = BarModel
return BarModel # I've tried with and without this line, but CS just returns the last line anyway so removing it is functionally the same
I have also tried:
Extending FooModel from Backbone.Model instead of Backbone.RelationalModel and creating the theBars collection myself (in parse and in custom function). (BarModel has a HasOne relation of a another model, so I need it to still be a RelationalModel.
Is this possibly a problem with the way exports works? As far as I understand, exports just provides an object to hang module objects on so the modules are accessible elsewhere. Is the error occurring because the BarModel isn't actually a Backbone Model at the point in the FooModel code where I define relations?
Update
I seem to have solved my issue, although I'm unsure how. Can't say I'm pleased about not understanding why it's working, but I sure am pleased that it is working. Also see my comment about _.memoize below in the BarModel code.
(Before I got the code below to work, I created a workaround whereby I created the associated collection in FooModel's parse function and exported BarModel. However, the response of require 'collections/foos' returned an object like so: {FooCollection: <Backbone.Collection Object>}, i.e. it was unexpectedly wrapped in another object.)
Here's the updated code:
FooModel:
define (require) ->
Backbone = require 'backbone'
BarModel = require 'models/bar'
BarCollection = require 'collections/bars'
FooModel = Backbone.RelationalModel.extend
relations: [
type: Backbone.HasMany
key: 'theBars'
relatedModel: BarModel
collectionType: BarCollection
]
return FooModel
BarModel:
define (require, exports) ->
Backbone = require 'backbone'
BarModel = Backbone.RelationalModel.extend
someFunction: -> #this actually used to use _.memoize (sorry for the incomplete code), so maybe it would have tried to run the function argument immediately?
# uses FooCollection
FooCollection = require 'collections/foos'
return AttributeModel
Your BarModel requires 'collections/foos', correct? And I'm guessing (since there's no code for FooCollection) that the collection requires 'models/foo', because a collection needs to define it's model right? Finally, I can see from the code above that your foo model requires 'models/bar'.
In other words foos needs foo needs bar needs foos needs foo needs bar needs ...
No matter how Require decides to order that, one of those three has to be loaded before the others, which will give you problems like the one you are having.
The solution is to not load one of those three until after all three modules are loaded. For instance, what if you change:
define (require, exports) ->
Backbone = require 'backbone'
FooCollection = require 'collections/foos'
BarModel = Backbone.RelationalModel.extend
someFunction: ->
# uses FooCollection
to:
define (require, exports) ->
Backbone = require 'backbone'
BarModel = Backbone.RelationalModel.extend
someFunction: ->
FooCollection = require 'collections/foos'
# uses FooCollection
Now BarModel can load, and while someFunction is defined, it is not actually run yet, so it won't require foos and create a circular dependency. Later on, after everything is loaded and some code invokes someFunction, foos will already have had a chance to load, and the require should work.
Now I say should work because of your comment:
# I've tried moving the require in here and getting rid of exports
Again, I have to guess since I can't see your code, but I'd imagine that what happened is that nothing else depended on foos, so it never got loaded. In order for the require of foos to work synchronously inside someFunction, the foos module has to have previously been loaded.
To fix this you just need to add a dependency on foos ... only this time not in any module that requires foos (or any that require a module that requires foos, or ...).
Hope that helps.