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.
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"
}
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!
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 want to start with a base JSON, ie [], and provide a tree like structure with options to edit node (change or extend existing keys/values), add sibling (create new entry), and add child (extend the json to become a map, ie add { "field1" : "value1", "field2" : "value2"} to "data".
Seems like the best way to do this is to bind a json scope value to a tree structure. I am about to build one myself, but thought I would check to see if it's been done already....
This sort of feature would allow someone closer to the business to define and refine the data model, as well as make simple edits. Think the php myadmin interface, or the django admin page.
This fiddle will give you a headstart. It was actually referenced from this question. It does not deal with object parameters, just nodes in the tree, but adding these should be pretty straightforward from a controller standpoint. The real challenge is in developing a good-looking view. Here is the controller part (just to make SO happy):
angular.module("myApp", []).controller("TreeController", ['$scope', function($scope) {
$scope.delete = function(data) {
data.nodes = [];
};
$scope.add = function(data) {
var post = data.nodes.length + 1;
var newName = data.name + '-' + post;
data.nodes.push({name: newName,nodes: []});
};
$scope.tree = [{name: "Node", nodes: []}];
}]);
Check it out json-tree angularjs directive.
This looks like a good implementation of what you're looking for.
I know this error has come up a few times, but I'm still not sure how to make this work appropriately..
My magic begins here :
var list_edit_member_view = new app.views.ListMemberEdit({
el: $("#enterprise_member_list_edit_container"),
list_ids: list_ids
});
list_edit_member_view.render();
And this loads this View (ListMemberEdit.js) which has this in the render() :
this.list_edit_member_view = new app.views.CollectionView({
el: $("#enterprise_member_list_edit_container"),
collection: app.peers,
list_item: app.views.ListMemberEditSelection,
list_item_options: {list_ids: this.options.list_ids}
});
Which loads a CollectionView view that renders its list_item_options as model views.. It is within this file (ListMemberEditSelection.js), that when I perform this.destroy, it will return :
Uncaught Error: A "url" property or function must be specified
So this makes me think that the Model or the Model URL is not being defined.. I'm just not sure where to put this since it works very similar to my other partials that are doing roughly the same thing..
Any thoughts? My apologies for the vagueness. Let me know if there's anything else you would like to look at!
I'm curious if its possible to see where this URL attribute would be written within the Object Model or Collection itself.
This is because destroy() function will call Backbone.sync to update the server too, not only your models in the frontend. http://backbonejs.org/#Model-destroy
So, if you're using REST to sync your data, you'll need to set a url property in your model so Backbone know where to send request:
Backbone.Model.extend({
url: "http://myapi.com/"
})
To allow more flexibility, you can also set a urlRoot: http://backbonejs.org/#Model-urlRoot
I had a similar problem, I removed the "id":"" from my models default values and the problem was solved.
I did receive similar error
Try this: I am just making an assumption what your model might look like
window.MyModel = Backbone.Model.extend({
url: function(){
return this.instanceUrl;
},
initialize: function(props){
this.instanceUrl = props.url;
}
}
Please look at this question that I had posted myself for more details: https://stackoverflow.com/a/11700275/405117
I am providing this reference as the answers here helped me better understand
Hope this helps!