I'm working to create a master view cordova/javascript application where a user a presented with a list of products, then depending on what they select, got to a tabbed based detail page about the product. Each tab will be generated depending on which product they pick. Along with the product specific information being loaded, I still want to keep a list a all the products in the view so that a user could switch products when ever they want. Here's an image to help better understand what I'm talking about. .
I tried to use ember.js to get this up and running but ran into a few issues. I can get the initial list of products generated and switch to the product specific details, but once I try to load the master list of products in my second template, everything breaks. I know about including two templates with an {{outlet}} in the parent template but I cant get this to have the child inherit the parent. Is this possible to do in ember or should I start looking at other frameworks like Anuglar? Any help is appreciated.
Displaying nested templates is actually where Ember has an advantage over other frameworks, in my opinion.
This should be really simple using nested resources. In your router, you can do something like
App.Router.map(function() {
this.resource('products', function() {
this.route('product', { path: '/product_id' });
});
});
Obviously, you'll have to fetch your data in each corresponding route. Something like
App.ProductsRoute = Ember.Route.extend({
model: function() {
this.store.find('product');
}
});
App.ProductsProductRoute = Ember.Route.extend({
model: function(params) {
this.store.find('product', params.product_id);
}
});
In your product template you'll need to include an {{outlet}} for all child routes to render into (ie, products.product).
For example
product.handlebars
{{#each}}
{{name}}
{{/each}}
{{outlet}}
products/product.handlebars
Product: {{id}}
Check out the resources section in the guides.
EDIT
If you want the master list to display differently between the products template and the products.product template, remove the master list from the products template and put it in the products.index and the products.product template.
Then specify that both the ProductsIndexController and the ProductsProductController needs its parent model. This will give both templates access to the products via controllers.products.
App.ProductsIndexController = Ember.ObjectController.extend({
needs: 'products',
products: Ember.computed.alias('controllers.products')
});
App.ProductsProductController = Ember.ObjectController.extend({
needs: 'products',
products: Ember.computed.alias('controllers.products')
});
See this jsbin and the associated guides.
Related
I have a web application (using ember 2.0) with 2 parts. Left menu and right content, both independent from each other.
Navigating to route name "PageRoute", application load the configuration JSON from server, process it and the checks, where should be content rendered:
{
...
position: "left",
...
}
or
{
...
position: "right",
...
}
Then I use "this.transitionTo" and redirect it to RightRoute or LeftRoute.
RightRoute is ok, LeftRoute is the problem.
At the beginning - Left menu is empty. When LeftRoute is loaded, it should append (not replace) some menu (rendered template or component).
When the LeftRoute is loaded again, new menu should be rendered from model and append it to existing left menus so it would be possible to swap between rendered menus and the last menu will be visible.
At the same time, when the current menu is swapped, it should be removed from the "left menu list" and the previous menu will be visible.
I've found some some solutions appending View, but the view is deprecated in Ember 2.0, I've tried to do it like here http://emberjs.jsbin.com/defapo/3/edit?html,js,output but Router can't access actions in components (with pushObject) and I've tried countless other methods like using data stores or dynamically modifying the model etc.
I would make it this way:
/controllers/left.js
import Ember from 'ember';
export default Ember.Controller.extend({
menus: [],
addMenu (menuData) {
this.get('menus').addObject(menuData);
},
removeMenu () {
this.get('menus').popObject();
}
});
/routes/left.js
import Ember from 'ember';
export default Ember.Route.extend({
setupController (controller, model) {
controller.addMenu(model);
}
});
/templates/left.hbs
{{#each menus as |menu| }}
<!-- render menu -->
{{log menu}}
{{/each}}
In this case when you transitionTo('left', menuData) it will append the menu to the list. When you need to remove last menu, you can do this.controllerFor('left').removeMenu() in any other route (e.g. in your PageRoute).
Hope this helps.
Prior to ember 1.13 i was using
var containerView = Em.View.views[view_id];
to get the ContainerView instance with the view id and manually adding childViews to this container view, which used to work just fine.
As of ember 1.13 this view lookup is not supported. What is the recommended way of doing this?
The problem is there are multiple instances of the same containerView with different id's. So i need to get the right instance of containerView from lookup.
Thanks
Update:
I have a parent (widget) component:
parentComponent.hbs
.....
{{input value=someBinding1 click='showCalendar'}}
{{calendar-component month=month1 disableBefore=disableBefore1}}
{{input value=someBinding2 click='showCalendar'}}
{{calendar-component month=month2 disableBefore=disableBefore2}}
{{input value=someBinding3 click='showCalendar'}}
{{calendar-component month=month3 disableBefore=disableBefore3}}
.....
So essentially i can have multiple date(input) fields which when clicked should show up a calendar component. If i write the code as above it would create 3 (or as many date fields) instances of the calendar component. These number of date fields are dynamic. I did not wanted to create so many calendar components as i see sluggish performance on mobile devices.
So what i did was:
.....
{{input value=someBinding1 click='showCalendar'}}
{{calendar-container-view id='calendarContainerView1'}}
{{input value=someBinding2 click='showCalendar'}}
{{calendar-container-view id='calendarContainerView2'}}
{{input value=someBinding3 click='showCalendar'}}
{{calendar-container-view id='calendarContainerView2'}}
.....
And calendar-container-view had an empty childViews array to start.
When user clicks on input field i get the corresponding containerView instance using Em.View.views[containerViewId] and append the calendar instance to it.
When another input is clicked i remove it from old parent containerView and add it to the new container view.
So there is only one calendar instance created by parentComponent.js and removed and added to container view.
Hope it makes sense. I can create a jsbin if need be.
Thanks a lot!
I think you could operate your logic by model instances instead of views (I mean model concept here, it could be some array of objects or records array).
It's clear you have model (as some array), since you mentioned multiple instances.
You might do:
{{!-- list of input fields --}}
{{#each model as |item|}}
{{item-input-field item=item value=item.someBinding1 clickInput="setCurrentItemWhenInstanceClick"}}
{{/each}}
{{!-- and one calendar-component --}}
{{#if currentItem}}
{{calendar-component item=currentItem month=currentMonth disableBefore=currentDisableBefore}}
{{/if}}
where item-input-field is a component, that extends input and pass it's item up to parent component or controller through action clickInput (action up).
Then setCurrentItemWhenInstanceClick action in parent component (or controller) should set currentItem, currentMonth and currentDisableBefore data (data down to calendar-component), so your calendar-component will be shown.
I'm just getting started with emberjs and I have some problems understanding how to dynamically change views within a view.
I want to have a page with a calendar.
This page has some buttons on the top to switch between different calendar views (day, week, month) and some filter options for the appointments within this view.
See this mockup:
Currently I have created a calendar route and a CalendarIndexView and template.
This template will contain the basic filter and view toggle buttons.
Within the index view I can call another view to display the grid.
<div class="calendar-container">
{{view monthView}}
</div>
The collection/context that is attached to these different views should not change because the filter is also applied on this.
The problem I have is that I don't know how to change the monthView to for example dayView after the "day" button is clicked.
Should I handle this in a router, controller or in the main calendar view?
If not view the router, how would make this view switching dynamic?
One of the core strengths of Ember is the router - everything is driven by the URL. I don't think you should part from that.
Use different routes for the different views within a calendar resource (in the router):
this.resource('calendar', { path: '/calendar' }, function() {
this.route('day');
this.route('week');
this.route('month');
});
Set the model on the calendar resource
In the nested routes (day, week (you could make month the default by using the calendar index route for it)), use the same model as in the calendar route, just filter it down to what you want. e.g.:
export default Ember.Route.extend({
model: function() {
return this.modelFor('calendar').filter(function(event) { ... });
}
});
For the people filter create a computed property in the controller that filters the events on the model and use that in the templates instead of the actual model. In that, if a person is selected, you can filter out any events without that person.
selectedPerson: null, // bind that e.g. to a select with all the people or change that with an action
filteredEvents: function() {
if (! this.get('selectedPerson')) {
return this.get('events');
}
return this.get('events').filterBy('attendee', this.get('selectedPerson'));
}.property('events.#each.attendee', 'selectedPerson')
Better than (4.): Do the filtering via query parameters. That way, you could be very flexible and even build powerful text search pretty easily...
Hope that helps and happy to see different approaches...
I'm having an issue with the template not rendering when I click the back button. From what I've read this is an issue because the parent template never goes away and the child routes should be rendered into the parent route. However, I want the users to have a clean page when they add records or edit records. I'm currently rendering the templates into named outlets on the main application template.
In this case, newrecord and editrecord should be rendered into form. However, I want the URL to be localhost/form/formID/newrecord so the user knows they're still in the form.
Question: What is the best way to achieve this? Do I have to change the routes as child resources or move them as higher level resources? Is there a better way to achieve what I want?
App.Router.map(function(){
this.resource('home', { path: '/' });
this.resource('form', { path: '/forms/:form_id' }, function() {
this.route('newrecord', { path: '/newrecord' });
this.route('editrecord', {path :'/editrecord' });
});
});
Template
<script type="text/x-handlebars" data-template-name="application">
{{ outlet }}
{{ outlet newrecord }}
{{ outlet editrecord }}
</script>
I found this was best answered here by Teddy Zenny.
Ember.js: Back button doesn't refresh index view when using nested routes
There is a Bckabone view Product:
Product = Backbone.View.extend({
templateBasic: _.template($("#pcard-basic").html()),
templateFull: _.template($("#pcard-full").html()),
initialize: function() {
this.render(this.templateBasic);
},
// ...
Here's my draft: http://jsfiddle.net/challenger/xQkeP/73
How do I hide/show other views when one of them gets chosen/unchosen to view its full template so it could expand to a full container width.
Should I use a view for an entire collection? How do I deal with event handling?
Thanks!
EDIT
That's my final draft: http://jsfiddle.net/challenger/xQkeP/
But still I'm not sure whether I could achieve the same result in more elegant manner? I just think that hiding siblings is not the best way of resolving it:
viewBasic: function(e) {
e.preventDefault();
this.render(this.templateBasic);
if(this.switchedToFull) {
this.$el.siblings().show();
this.switchedToFull = false;
}
},
viewFull: function(e) {
e.preventDefault();
this.render(this.templateFull);
this.$el.siblings().hide();
this.switchedToFull = true;
}
If I understand correctly you want to display all your models inside your collections in two different ways, leaving the choice of how to present them to the user, right?
One way you can do this is to create a main view where the user choice is made. When the user decides you should trigger a method from that view that renders every model from the collection using a different template. On your main view you should have a container (table, div, ul, etc) where you'll append each of the model view.
So, in the end, you have to views. One acting as a container for the collection that takes care of handling the users choice. Then you have another view to render a single model from the collection. This view has to templates that can be used. On the main view you iterate over the collection creating a new view instance for each model to append in the container using a different template depending on the user decision.