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.
Related
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();
});
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)
I'm walking into a large Backbone.js project so I'm still getting my bearings. My template, my-group-item.jhbs has:
{{#if isComplete}}
.row-fluid
.span2
img.entity-image(src="/pictures/{{entityId}}.png")
.span10
.row-fluid
.span12
h3 {{entityName}}
p My first variable {{totalFirst}} and my second variable {{totalValue}}
{{/if}}
My View is:
module.exports = class MyItemView extends View
className: ->
templateData = #getTemplateData()
primaryData = #model.get('primaryData')
tagName: 'li'
template: require 'views/my-group-item'
initialize: () ->
super
primaryData = #model.get('primaryData')
In my template, the totalFirst and totalValue variables show nothing.
I'm calling my view with:
#groupView = new MyItemView
collection: groups
el: '.group-list'
How can I get these to show in the template?
You could pass more than 1 variable to your template, the first being your model and the second being the attributes of primaryData.
I'm following Brian Mann's tutorial on building an application in Backbone + Marionette (~35min into Ep08), and I'm having problems with a controller inserting content into the main region rather than one of its sub-regions.
The page layout is structured like so:
<div id="header-region"></div>
<div id="main-region"></div>
<div id="footer-region"></div>
And I'm trying to insert content into an #admin-navs-region, which is to be nested under the #admin-list region inside of the #main-region:
<!-- admin/list/templates/list_layout; inserted into #main-region -->
<div id="admin-list">
<div id="banner-region"></div>
<div id="admin-navs-region"></div>
<div id="article-region"></div>
</div>
The controller in question is as follows:
#PE.module "AdminApp.List", (List, App, Backbone, Marionette, $, _) ->
class List.Controller extends App.Controllers.Application
initialize: ->
adminNavs = App.request "admin:nav:entities"
#layout = #getLayoutView()
#listenTo #layout, "show", => ## LINES IN QUESTION
#bannerRegion() ## LINE 11
#listRegion adminNavs ## LINE 12
#show #layout
bannerRegion: ->
bannerView = #getBannerView()
#show bannerView, region: #layout.bannerRegion
listRegion: (adminNavs) ->
listView = #getListView adminNavs
#show listView, region: #layout.adminNavsRegion
getListView: (adminNavs) ->
new List.Navs
collection: adminNavs
getBannerView: ->
new List.Banner
getLayoutView: ->
new List.Layout
And the corresponding view:
#PE.module "AdminApp.List", (List, App, Backbone, Marionette, $, _) ->
class List.Layout extends App.Views.Layout
template: "admin/list/list_layout"
regions:
bannerRegion: "#banner-region"
articleRegion: "#article-region"
adminNavsRegion: "#admin-navs-region"
class List.Banner extends App.Views.ItemView
template: "admin/list/_banner"
class List.Nav extends App.Views.ItemView
template: "admin/list/_nav"
tagName: "li"
class List.Navs extends App.Views.CollectionView
tagName: "ul"
className: "side-nav"
itemView: List.Nav
When I comment out the LINES IN QUESTION (see 3rd code snippet), the list_layout is rendered properly within the #main-region. However, if I uncomment LINE 11 or LINE 12, #admin-list is being entirely replaced with the List.Banner or List.Nav templates, rather than inserting into the specified region.
Everything looks like it should be working to me, but instead of insertion into the correct region, the code is simply replacing everything inside of #main-region.
Does anyone know how to start debugging this?
Solved after realizing this is from some peculiar coffeescript / Backbone / Marionette interactions...
I'm using tabs (not spaces) to indent my coffeescript, and I had:
<tab> class List.Layout extends App.Views.Layout
<space><space><tab> template: "admin/list/list_layout"
For whatever reason, this doesn't break things but rather causes Marionette to render over the parent section. Fixed this by replacing the <space><space> with a <tab>:
<tab> class List.Layout extends App.Views.Layout
<tab><tab> template: "admin/list/list_layout"
Just one of those Mondays...
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