View is not showing in backbone.marionette - backbone.js

I was trying to build an example to demonstrate the region and layout thing of Backbone.Marionette . But i stuck in the layout , though i am calling the layout.region.show() , it's not showing anywhere in the DOM.
Full example can be found in this JsFiddle.
This is the layout part :
AppLayout = Backbone.Marionette.Layout.extend({
template: "#layout-template",
el : "layout-containr",
regions: {
menu: "#menu",
content: "#content"
}
});
Layout template:
<script id="layout-template" type="text/template">
<section>
<div id="menu"></div>
<div id="content"></div>
</section>
Here is how i am showing the layout :
var layout = new AppLayout();
layout.render();
layout.menu.show(gridView);
GridView's definition can be found here:
var GridView = Backbone.Marionette.CollectionView.extend({
itemView: GridRow,
el:'#menu'
});
Full example can be found in this JsFiddle.
And i have a complimentary question :
How layout will know where it should be attached ???
I did not find it anywhere in the net which makes me sure that i am missing some concepts here.

You need a bigger region to show the layout, in application level.
Usually when you initialize a Marionette application, you will have some top-leve regions to show the layout you want to render later.
App.addInitializer =>
App.addRegions
menuRegion: '#header'
contentRegion: '#stage' # These DOM come from your application layout
And then in your controller, you show these layout in your top-leve region.
indexShow: ->
layout = new App.Layouts.SomeLayout()
App.contentRegion.show(layout)
someView = new App.Views.SomeView()
anotherView = new App.Views.AnotherView()
layout.someSubRegion.show(someView)
layout.anotherSubRegion.show(anotherView)
And, you usually don't need an el either in your view or layout

Related

Unable to have an ol3 map in each angular module

I'm using openlayers3 (ol3) and angular 1.5.6 on IE Edge.
I have two modules. Each has their own controller and component. Each controller wants to have a map in the view. One view is for interactively querying data off its map. The other view is for displaying interactive query results.
Under the hood, I provide a MapFactory which returns an instance of a object, containing the said openlayers map.
PROBLEM: The one displays while the other does not.
Here's a sample of my code (some details are left out for simplicity. For example the dependency injection checks. All of this code is being called as expected.):
Module A definition
angular.module('ModuleA').controller('ModuleAController',ModuleAController);
ModuleAController.$inject = ['MapFactory'];
function ModuleAController(MapFactory){
var vm = this;
var vm.map = MapFactory.getMapInstance({
id:'module-A-map',
otherOption:true
});
}
In ModuleA's view:
<div id='module-A-map' class="map-classes"></div>
Module B definition
angular.module('ModuleB').controller('ModuleBController',ModuleBController);
ModuleBController.$inject = ['MapFactory'];
function ModuleBController(MapFactory){
var vm = this;
var vm.map = MapFactory.getMapInstance({
id:'module-B-map',
otherOption:true
});
}
In ModuleB's view:
<div id='module-B-map' class="map-classes"></div>
MapFactory's definition:
angular.module('common').factory('MapFactory',MapFactory);
MapFactory.$inject = [];
function MapFactory(){
var factory = {
getMapInstance : getMapInstance
};
return factory;
function getMapInstance(options){
return new _MapConstructor(options);
}
function _MapConstructor(options){
var _map = new ol.Map({
target : options.id,
logo : false,
view : new ol.View({...}),
layers : [some,layers,here]
});
return {
publicMethod : publicMethod
};
function publicMethod(){...}
function privateMethod(){...}
... other stuff ...
}
}
Please, let me know if any clarification is needed to answer the question.
MORE:
This issue: https://github.com/openlayers/ol3/issues/4601 might be part of the problem. I am using collapsable DIVs with bootstrap. The ModuleA is in the default displayed one, while ModuleB is hidden at first. More to come.
I wrote this up as an OL3 issue as well: https://github.com/openlayers/ol3/issues/5789
ABSTRACT ANSWER:
http://getbootstrap.com/javascript/#collapse-events
I need to add a _map.updateSize() on a show.bs.collapse or shown.bs.collapse event. Now, I need to figure out how to do that in Angular, and post it (unless somebody gets to it first).
Ah, this is in Bootstrap's collapse class. So, let's back up to the Module-B view. Each of my Module's is a panel within a Bootstrap panel accordian. The ModuleA map that displays is the default open panel (the one that has the in class). The ModuleB map is not open by default, and thus, OL3 gives the canvas a display:none in the map's div's style.
<div id="module-B-collapse" class="panel-collapse collapse" >
<div id='module-B-map' class="map-classes"></div>
....
</div>
In my ModuleBController, I simply added:
angular.element('#module-B-collapse').on('shown.bs.collapse',function(){
_map.updateSize();
});

Backbone Marionette Slow Composite Views (200+ collections)

When showing a dropdown composite view collection of around 200 countries my application gets far too slow.
What is the best way to increase performance when dealing with large collections in marionette composite views?
Here is the function in the controller that is very slow to load. It is fast with only the following lines removed:
#layout.shippingCountryRegion.show shippingCountryView
#layout.billingCountryRegion.show billingCountryView
So it appears to be a very slow rendering issue.
Show.Controller =
showProfile: ->
#layout = #getLayoutView()
#layout.on "show", =>
headerView = #getHeaderView()
#layout.headerRegion.show headerView
accessView = #getAccessView()
#layout.accessRegion.show accessView
billingReadmeView = #getBillingReadmeView()
#layout.billingReadmeRegion.show billingReadmeView
billingFieldsView = #getBillingFieldsView()
#layout.billingFieldRegion.show billingFieldsView
shippingReadmeView = #getShippingReadmeView()
#layout.shippingReadmeRegion.show shippingReadmeView
shippingFieldsView = #getShippingFieldsView()
#layout.shippingFieldRegion.show shippingFieldsView
MyApp.request "location:get_countries", (countries) =>
billingCountryView = #getBillingCountryView(countries)
#layout.billingCountryRegion.show billingCountryView
MyApp.request "location:get_states", MyApp.activeCustomer.get('billing_country_id'), (states) =>
billingStateView = #getBillingStatesView(states)
#layout.billingStateRegion.show billingStateView
MyApp.request "location:get_countries", (countries) =>
shippingCountryView = #getShippingCountryView(countries)
#layout.shippingCountryRegion.show shippingCountryView
MyApp.request "location:get_states", MyApp.activeCustomer.get('shipping_country_id'), (states) =>
shippingStateView = #getShippingStatesView(states)
#layout.shippingStateRegion.show shippingStateView
MyApp.mainRegion.show #layout
The billing country view:
class View.BillingCountryDropdownItem extends MyApp.Views.ItemView
template: billingCountryItemTpl
tagName: "option"
onRender: ->
this.$el.attr('value', this.model.get('id'));
if MyApp.activeCustomer.get('billing_country_id') == this.model.get('id')
this.$el.attr('selected', 'selected');
class View.BillingCountryDropdown extends MyApp.Views.CompositeView
template: billingCountryTpl
itemView: View.BillingCountryDropdownItem
itemViewContainer: "select"
The template, simply:
<label>Country
<select id="billing_country_id" name="billing_country_id">
<%- name %>
</select>
</label>
Your code can be optimized. Just move content of onRender method to the ItemView attributes.
class View.BillingCountryDropdownItem extends MyApp.Views.ItemView
template: billingCountryItemTpl
tagName: "option"
attributes: ->
var id = this.model.get('id');
var attributes = { 'value': id };
if MyApp.activeCustomer.get('billing_country_id') == this.model.get('id')
attributes['selected'] = 'selected';
return attributes
The difference between this method and onRender case is that, on render will execute when collection already rendered and 200+ operations will be done with DOM nodes, which will bring performance issues.
In case of attributes method, it executes upon view creation.
There are few advices you can follow:
1) Ask your self do you really need to render all items at once? Maybe you can render part of collection and render other items on scroll or use pagination or use 'virtual scrioll' with SlickGrid or Webix for example.
2) Checkout how often you re-render your view. Try to minify num of events cause re-render
3) Try to minify num of event listeners of ItemView. Its good practice to delegate context events to CollectionView
4) You can use setTimeout to render collection by parts. For example you divide you coll in 4 parts by 50 items and raise 4 timeouts to render it.
5) You can optimize underscore templating and get rid of with {} operator. http://underscorejs.org/#template
What's in your 'billingCountryItemTpl' varible? If it's just string with template ID then you could precompile your template using Marionette.TemplateCache.
So you'll have:
template: Marionette.TemplateCache.get(billingCountryItemTpl)

How to display an item view with no tag

The problem is I want to render option elements with the value attribute set as well as the text node.
So setting tagName to option in my ItemView does not just do this. The solution I have at the moment is to set it to option and then use the following code in the ItemView constructor to set the value attibute:
onRender: function () {
this.$el.attr('value', this.model.get('name'));
}
This works.
But is there any other way?
What I'd really like to do is just tell Marionette not to output an element at all and then in my ItemView template have this:
<option value="<%= name %>"> <%= name %> </option>
Is this possible?
It's possible but a bit fiddly, by default Backbone always uses a wrapper element (definable by using tagName) but you'd have to expressly populate the attributes (as you are doing above).
It is however possible to circumvent the wrapper element using a slightly convoluted setElement approach and this will enable you keep all markup in a template with attributes on the root node populated from the model. I like this approach as well since I personally think it keeps a clearer separation of concerns.
Take a look here for an example - Backbone, not "this.el" wrapping
Not sure if Marionette has a build in mechanism for doing this.
I am not sure you should from all different kind of reasons.
But I understand that in the use case of a single tag view, the way Marionette/Backbone work you either create another tag and have a view, or you use onRender and query to update the single tag that was already generated for you.
You could have this Object that will extend ItemView, lets call it SingleTagView. Basicly I am extending ItemView and using overrunning it's render function with one change.
Instead of doing:
this.$el.html(html);
I am doing:
var $html = $(html);
this.$el.replaceWith($html);
this.setElement($html);
Here is the code:
var SingleTagView = Marionette.ItemView.extend({
render: function() {
this.isClosed = false;
this.triggerMethod("before:render", this);
this.triggerMethod("item:before:render", this);
var data = this.serializeData();
data = this.mixinTemplateHelpers(data);
var template = this.getTemplate();
var html = Marionette.Renderer.render(template, data);
// above is standart ItemView code
var $html = $(html);
this.$el.replaceWith($html);
this.setElement($html);
// this code above replaced this.$el.html(html);
// below is standart ItemView code
this.bindUIElements();
this.triggerMethod("render", this);
this.triggerMethod("item:rendered", this);
return this;
}
});
If you are going to use this kind of angle, make sure you test the behavior properly (event handling, Dom leaks, different flows with before:render events).
I would try:
var optionTag = Marionette.ItemView.extend({
tagName: "option",
initialize: function(){
this.attributes = {
value: this.model.get('name');
}
}
}
Backbone should then do the rest (i.e. putting the content of attributes into the HTML tag)...

Getting "NoTemplateError: Could not find template" with backbone.marionette

In this coffeescript+backbone.marionette application I am trying to write, I am getting a "NoTemplateError: Could not find template: '#second-template' when I try to display a different view in my content region.
Here are the two pieces of code, which are based on David Sulc's Backbone Books tutorial. The WelcomeApp displays fine but when I click the menu item that then calls MyApp.SecondApp.display(), I get the NoTemplateError.
window.MyApp = MyApp = new Backbone.Marionette.Application()
MyApp.addRegions
menu: '#menu'
content: '#content'
class MyApp.MenuView extends Backbone.Marionette.View
el: '#menu'
events:
'click #get-second': 'showSecond'
showSecond: ->
MyApp.SecondApp.display()
MyApp.vent.on 'welcome:rendered', ->
menu = new MyApp.MenuView()
MyApp.menu.attachView(menu)
MyApp.WelcomeApp = do ->
WelcomeApp = {}
class WelcomeLayout extends Backbone.Marionette.Layout
template: '#content_welcome-template'
WelcomeApp.display = ->
WelcomeApp.layout = new WelcomeLayout()
WelcomeApp.layout.on 'show', ->
MyApp.vent.trigger 'welcome:rendered'
MyApp.content.show MyApp.WelcomeApp.layout
return WelcomeApp
MyApp.SecondApp = {}
class MyApp.SecondApp.WelcomeView extends Backbone.Marionette.ItemView
template: '#second-template'
MyApp.SecondApp.display = ->
welcomeView = new MyApp.SecondApp.WelcomeView()
MyApp.content.show welcomeView
MyApp.addInitializer ->
MyApp.WelcomeApp.display()
My templates are simply script blocks in the index.html. I actually swapped the template used by the WelcomeApp with that used by the SecondApp and the WelcomeApp can find '#second-template' fine when I do this.
I tried this with both backbone.marionette 0.9.10 and 0.9.11.
Any help here would be greatly appreciated!
Thanks to both commenters on my question. While working to strip down the HTML and coffeescript code, I noticed a div in my HTML that was incorrectly closed using <div>. Once I fixed that to a </div> everything worked. Stupid error but I was just not seeing it until I strip down the HTML enough that it was right in my face. I need better syntax checking.

Managing views with routers in Backbone.js

I am working in a single page app that has the following layout:
I am using a Backbone.js router to manage the elements that load on the screen:
var AppRouter = Backbone.Router.extend({
routes: {
"" : "list",
"content1" : "content1",
"content1/cont3": "cont3"
},
list: function() {
var list = new List().render().$el; //view
$("#List").html(list);
},
content1: function(){
var cont1 = new Content1().render().$el; //view
$("#Content1").html(cont1);
},
content3: function(){
var cont3 = new Content3().render().$el; //view
$("#Cont3").html(cont3);
}
});
Everytime I click on a list item in #List, #Content1 gets generated, and when I do it on the blocks on #Content1, #Cont3 appears.
The problem I am facing is that if, for some reason, I refresh the page when the adress is localhost/content1, for example; the elements in #List disappear.
I want the content in #List to be always present when loaded, independent on what the url may be, as well as the content in #Content1. Is there a way to achieve this using backbone routers?
Thanks
You don't need to match 1 route <-> 1 View.
I would change the routes to something like this:
"list": "displayContent",
"list/:c1": "displayContent",
"list/:c1/:c3": "displayContent",
So it's only one callback the one who organize what Views are rendered or what not.
displayContent: function(c1,c3) {
Store the content1 variable, and check if has been rendered already or not, etc.
Have a look to this question: How to handle initializing and rendering subviews in Backbone.js?
Or for more complex apps, maybe a layout framework in top of Backbone could help, although I would recommend do your own stuff until you understand how Backbone works.
https://github.com/tbranyen/backbone.layoutmanager
https://github.com/derickbailey/backbone.marionette
I think you can try this:
.....
list: function() {
var list = new List().render().$el; //view
$("#List").html(list);
},
content1: function(){
var cont1 = new Content1().render().$el; //view
$("#Content1").html(cont1);
//Pseudo code
if (listisempty){
list();//If the list is empty, then the initialization list.
};
},
.....

Resources