Routing in Ember.js is troubling me, and I can't seem to find the "correct" way of doing what I want to do.
I have a route, let's call it #/map, which contains a series of top-level and containers of child views.
Hierarchically, I have a top map_view, which contains 4 additional views: A topbar (which has topbar menu item triggers within it), a sidebar (which has sidebar menu item triggers in it), and two containerViews (a sidebar menu containerView and a topbar menu containerView), which will contain one or more nested views that are programatically inserted on clicking a menu item trigger.
My issue is that while this works, and I can embed all of these views into their various templates, none of them are linking with controllers, and the controller they are picking up is the map_controller (which makes sense as that is the linked outlet controller for the top level view). Currently I am using a method described on Ember's github here, but it seems a little...hacky?
Here is a JSFiddle showing the problem. Notice that the controller for level-one-entry and level-two-entry is the index_controller: http://jsfiddle.net/fishbowl/Z94ZY/3/
Here are some code snippets for what I am doing to get around it:
map.hbs:
<section id='map'>
{{view App.SidebarView}}
{{view App.TopbarView}}
<div id='map-canvas'></div>
</section>
topbar_view.js:
var TopbarView = Em.View.extend({
templateName: 'topbar',
classNames: ['topbar-container'],
init: function() {
var content = this.get('content'),
controller = App.TopbarController.create({
view: this
});
this.set('controller', controller);
this._super();
}
});
module.exports = TopbarView;
topbar_controller.js
var TopbarController = App.ApplicationController.extend({
content: Ember.computed.alias('view.content'),
trigger: null,
start_date: null,
end_date: null,
travelling: null,
word: 'topbar'
});
module.exports = TopbarController;
I'm not doing anything special in the router other than declaring this.route('map'). A further problem i'm having is that whenever I declare needs: ['some_other_controller'], I get an error
<App.TopbarController:ember413> specifies 'needs', but does not have a container. Please ensure this controller was instantiated with a container.
Am I missing something blindingly obvious about how to go about linking these together. I'm guessing that i'm using routing incorrectly. I don't want to change what the URL is, as i'm technically not moving pages, just opening and closing menus on the page, but I don't really understand how else i'm supposed to use the router to achieve this.
EDIT 2: i've mocked up another jsfiddle of what I could do with outlets and link-to's, but i'm not sure that I want the URL changing (as you'd probably be able to do odd things with the back button etc): jsfiddle - The alternative to this is to set location: 'none' in the Router, but I don't really like that option either...
Related
A question regarding transclude within an angular 1.5.8 component, and it's uses.
Here is an example of some code;
var app = angular.module('app', [])
function AccordionController () {
var self = this;
// add panel
self.addPanel = function(panel) {
// code to add panel
}
self.selectPanel = function() {
//code to select panel
}
}
// register the accordion component
app.component('accordion', {
template: '<!---accordion-template-->',
controller: AccordionController
}
function AccordionPanelController () {
// use parents methods here
var self = this;
// add panel
self.parent.addPanel(self);
// select panel
self.parent.selectPanel(self);
}
// register the accordion-panel component
app.component('accordionPanel', {
// require the parent component
// In this case parent is an instance of accordion component
require: {
'parent': '^accordion',
template: '<!---accrodion-panel-template-->',
controller: AccordionController
}
My question is would it be better to nest all the according panels within the parent using transclude or alternatively pass in a data array to the parent which this loops out the required number of panels based on the array passed inside using a binding.
Thanks
// added
Many thanks for your reply, an example I have of transclude possibly being necessary is in the following bit of code
<modal modal-id="editCompany" title="Edit Company"> <company-form company="$ctrl.company"></company-form> </modal>
Here we have a modal component which may have a variety of other components used within it, on the example above I am adding the company form, but this could we be an contact form. is there an alternative way?
I've worked with angular pretty extensively. Two enterprise tools managing and displaying large amounts of data, dozens of interactive widget modules, all that.
Never, once, have I had anything to do with transclude. At work we are explicitly told not to use it (link functions too). I thought this was a good thing, and the way Angular 2 turned out it seemed that thinking wasn't totally without reason.
I would go with the iteration to lay out the required number of items. At work this wouldn't be a choice because transclude wouldn't be an option.
The thing with using transclude in a component architecture is that it visually breaks the idea of single responsibility and messes with the architecture.
<html>
<navigation></navigation>
<accordion>
<accordion-panel></accordion-panel>
</accordion>
<footer></footer>
</html>
In this example you know your page has a navigation menu, an accordion and a footer. But at the index level (or root component) you don't want to know / see what the accordion contains.
So the accordion-panel component should only appear in its direct parent component.
As for your other question, through the use of require or attributes you pass an array of panels that you iterate using ng-repeat inside the accordion component.
app.component('accordion', {
template: `<accordion-panel ng-repeat="..."></accordion-panel>`,
controller: AccordionController
}
My goal:
I want to make a compact, reuseable modal. It should take a template name, and render in a modal,
What i got so far:
http://emberjs.jsbin.com/mehabivu/6/edit
How it works:
App.ApplicationRoute has an action, which takes a template name.
It renders it into {{ outlet modal }}
I can use this action
application wide, and reuse templates
My problem:
I dont know how to wrap the outlet into a div or something (to add
css), and show/hide it.
Maybe there is a better practice (component?)
to do this
Sounds like your on the right path. Pretty much exactly what Ember says to do in their cookbook: http://emberjs.com/guides/cookbook/user_interface_and_interaction/using_modal_dialogs/
Wrapping/styling the modal can be done via the view you render into the outlet. Right now your menu2 template doesn't have a view.
App.Menu2View = Ember.View.extend({
tagName: 'section',
classNames: ['modal']
});
The above makes the containing tag a section and adds the modal css class to that container section.
I've updated your JSBin: http://emberjs.jsbin.com/mehabivu/7/edit
I have the below route structure.
App.Router.map(function() {
this.resource('projects', function() {
this.resource('listings', {path: '/:project_id/listings'}, function() {
this.route('listing', {path: '/:property_code'});
});
});
});
I replicated this structure and created a fiddle.
Fiddle: http://jsfiddle.net/aqHnt/6/
I hope the router is self-explanatory. I have a bunch of projects which each have a bunch of listings and each listing will have some additional details. Because these are nested resources, each child resource renders in to the {{outlet}} of it's parent template.
What I need is to entirely overwrite the parent template and as per a suggestion in a different post, I'm using the resources index route to achieve this.
So If you click on a project, the entire projects template will be replaced with the listings template. It's all good up to this point but I can't seem to achieve the same with listings. When I click on a listing, I want the entire listings template to be replaced by listing details. Can someone point out what am I doing wrong here.
Here is a possible solution: jsfiddle.
You were missing the listings template besides the listings/index one, as well as the route ListingsIndex. This way you replicate the same pattern you use at application level.
You could also consider using renderTemplate to specify in which outlet you need to render a given template.
Hope it helps!
I try to develop a simple Ember.js app.
I think that these screens describe the desired scenario good enough.
Buttons on the top are #link-tos. Note that I want the first button to be highlighted on the 3rd screen.
It's easy to find examples where the 3rd template lives in the outlet in the 2nd one, but I need some kind of template replacement in the main outlet.
Please help me to achieve this behavior. Hope that my description is clear enough.
You can make list and detail routes same level, so they both are rendered to the same outlet, one at time. Like that:
App.Router.map(function() {
this.resource("movies", function() {
this.route("list");
this.route("view", {path:"view/:movie_id"});
});
});
App.MoviesIndexRoute = Em.Route.extend({
redirect : function() {
this.transitionTo('movies.list');
}
});
All the rest is done as allways
In my backbone app I create a popover in parent view and add it to the DOM like this
afterRender: function() {
this.$el.append(
new Popover.Views.Default({
stick:'right',
offsetTop: 3,
offsetRight: 5,
content: "Foo",
reference: this.$el
}).render().$el
);
},
toggle: function(){
app.vent.trigger('popover34:toggle');
}
However the popover is appended to its parent view. The parent view is a link and this causes some css issues in the popover.
My question is, is it safe to do something like:
afterRender: function() {
$('body').append(
new Popover.Views.Default({
stick:'right',
offsetTop: 3,
offsetRight: 5,
content: "Foo",
reference: this.$el
}).render().$el
);
}
Or is there a way to set it just beside the parent view? The problem is that when I toggle the popover it will be added to the DOM multiple times.
To second (and hopefully clarify) Isaac's answer here, your 'parent' view should be in one region, the Popover view should be in another region.
Brian Mann has done an excellent set of videos on Marionette.js and good application design, one specifically that demonstrates your exact scenario.
Good luck,
Aaron
It looks like you're using Marionette. Using Marionette, one way to handle this is with regions. From the docs:
Regions provide a consistent way to manage your views and show / close them in your application. They use a jQuery selector to show your views in the correct place.
You can put your region anywhere you like and trigger it using the Event Aggregator. In addition, it automatically closes old views so you don't end up with zombies.