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.
Related
Currently I am learning ext.js 6 and I have a quastion to ask.
I want to build a tree-like menu and from examples I know how to build all kinds of trees. But how can I change a view when user clicks on different leefs in a tree? I know I need controller(viewcontroller) and handlers to work with events (onClick and such) but how to render views from there?
Thank you.
You need to use add() function for that:
add( component ) : Ext.Component[]/Ext.Component
Adds Component(s) to its parent.
You need to pass the view to be rendered as parameter
Eg. Adding a formpanel & button directly to the view port:
Ext.Viewport.add([
{
xtype:'formpanel'
},
{
xtype:'button'
}
]);
Im my application (its use ExtJS 4 actually, but I guess idea is the same) I do something like this:
var viewport = Ext.create('Ext.container.Viewport', {
alias: 'widget.viewport',
layout: 'border',
items: [
// Its my main menu, displayed on all pages
portalToolbar,
{
xtype: 'panel',
itemId: 'mainPanel',
layout: 'fit',
region: 'center'
}
]
});
And on menu item click I remove any content from main panel and add new one, like this:
// remove previous page from main panel,
// think about `abort()`ing all ajax requests, clear intervals and so on along with this
mainPanel.removeAll();
// `currentInterface` is any component that is one of the pages of my application
mainPanel.add([currentInterface]);
Also you can take a look at Ext.util.History and on menu click add new token to history and on history change event open application page like described above.
I'm writing full stack e-commerce store with AngularJS and express.js, and I'm searching help of somebody more experienced with $stateProvider and ngAnimate.
Currently I'm having a trouble with $stateProvider and two columns layout.
I've got root view abstract state which is named 'pages'. This state includes base app template with view named 'main'. On state change event the 'main' view is transitioning to the next one with CSS transition by the ngAnimate module and it's all OK with regular one column page.
The trouble I'm experiencing is within the two-column layout included in the 'main' view. With the partial template of two columns view, ngAnimate was transitioning whole content, so column was unnecessary transitioned with the products view.
What I've done, I've created a sub-state and divided its template into two views. Right now the 'sidebar' view is including partial with categories of products, and the second one, the 'content' is a products list.
The products list view contents is depending on sub-state of products state, based on abstract state descibed above.
Right now the products list is changing correctly with transition attached to the 'content' view but the column 'sidebar' is unnecessary reloaded.
Is it possible to change state with changing only the content of particular view or are you having other ideas?
Live demo: http://rtbm.space:3000/#/products
Code: https://github.com/rtbm/angular-express-store/tree/master/public/assets/src/web/modules
The modules described above are pages and products.
You could use a more hierarchical route-structure:
.state('pages.products.category', {
abstract: true,
url: '/category',
templateUrl: 'assets/dist/web/modules/products/partials/products.category.html',
controller: 'ProductsCategoryController',
controllerAs: 'productsCategoryCtrl',
resolve: {
CategoriesData: function(CategoriesService) {
return CategoriesService.query();
}
}
})
.state('pages.products.category.content', {
url: '/:categoryId',
templateUrl: 'assets/dist/web/modules/products/partials/products.list.html',
controller: 'ProductsListController',
controllerAs: 'productsListCtrl',
resolve: {
ProductsData: function($stateParams, ProductsService) {
return ProductsService.query({
categoryId: $stateParams.categoryId
});
}
}
})
Now the products.category.html should contain only one <ui-view>. The result is that the abstract parent state pages.products.category (and therefore the category's side nav) isn't reloaded every time you click on a category link in the side nav.
I hope this helps!
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 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.
currently i'm having trouble with meteor sessions and how my code triggers it to render a template. Currently I have a session that sets its ._id to whatever is clicked.
Template.sidebar.events({
/* on click of current sidecat class change to sidecat selected */
'click .sidecat': function (event) {
Session.set("selected_project", this._id);
}
});
And I have it add a css class if selected_player equals the div.
Template.sidebar.sidebarselected = function () {
return Session.equals("selected_project", this._id) ? "sidebarselected" : '';
}
Now I render a template when there is sidebarselected and another class present. On click of the item it renders the template.
Template.sidebar.projectselected = function() {
var find = Session.get("selected_project");
var find2 = ($("#"+find).attr('class'));
/* if exists return true render the template */
if (find2 == 'project sidebarselected')
{
return true
}
/* else don't render it return null */
else {
return null
}
};
Everything up until now works great.
Now I have a Button that creates a new item in the list and makes it the selected item. This is when the trouble occurs. When I create a new Item the list item is rendered and given the class sidebarselected but it does not render the template which should be called. It does render the template when I click an item. But not when using the button to create a new list item. It becomes selected but does not render the template. Here is the code for that.
Template.sidebar.events({
/* add bar settings menu functions for clicks */
'click #newProject': function(event){
var create = NewProject();
Session.set("selected_project", create);
},
This is the NewProject Function
/* add a new project to the side bar */
function NewProject() {
id = Aprojects.insert({
name: "New Project",
type: "project"
});
doc = Aprojects.findOne({_id:id});
return doc._id;
}
Ok there is everything. Item is created when I click on button, class is added but template is not rendered. This is all of the javascript, if html is need let me know and I will provide it.
Let me add some details and html template stuff. But anyways what does work is
you can select a project in the list
when you select a project (by clicking on it), it's given a classname to indicate that it's selected
when you click "new project" you want to add an item to the list and immediately select it
That all works correctly. The problem is when clicking the new project button. It still selects the newly made project properly and gives it a class of selected. What doesn't happen is if it finds that the selectedbar class also has a class of project with it. It renders a separate template (projectpicked). Projectpicked shows up when items are clicked. Not when #newproject is clicked. Even through it ends up selected it does not show projectpicked template. Let me see if the code can do more of the talking.
<body>
{{> sidebar}}
</body>
<template name="sidebar">
{{#if aproject}}
{{#each aproject}}
<div class="sidecat {{sidebarselected}} project" id="{{divids}}">
{{name}}
</div>
{{/each}}
{{/if}}
{{#if projectselected}}
{{> projectpicked}}
{{/if}}
</template>
<template name="projectpicked">
project was picked from sidebar
</template>
The reasoning behind the application logic and DOM structure combo is that I have other things besides projects like categories. I figured using session gets the id and then we figure out what class it is like project or category and display a different template based off of what class it is. Don't know if its the best way but it's what I came up with. Open to suggestions. If somethings not clear not me know and I'll try to explain it. Thanks for the help.
I would rewrite the projectselected helper to not depend on a HTML classname to execute its logic, but to depend on a Session value. You're using Session correctly in most of your code, but it seems weird to then couple your application logic to the DOM structure.
The way your code works at the moment (without seeing your HTML template - you should post that too!), it looks like you want it to do the following:
you can select a project in the list
when you select a project (by clicking on it), it's given a classname to indicate that it's selected
when you click "new project" you want to add an item to the list and immediately select it
Here's how I'd solve that scenario:
Your template
<body>
{{> sidebar}}
</body>
<template name="sidebar">
<ul>
{{#each items}}
<li class="{{selected}} project" id="id_{{_id}}">{{_id}}</li>
{{/each}}
{{> newproject}}
</ul>
{{> projectpicked}}
</template>
<template name="newproject">
<li id="newproject">Create a new project</li>
</template>
<template name="projectpicked">
{{#if projectpicked}}
A project was picked!
{{/if}}
</template>
Your javascript
// When clicking an item in the list, remember which one we just clicked
Template.sidebar.events({
"click li": function() {
Session.set("selected_project", this._id);
}
});
// Add the selected class to the item that was remembered
// by the session when we clicked on it
Template.sidebar.selected = function() {
return Session.equals("selected_project", this._id) ? "selected" : "";
}
Template.sidebar.items = function() {
// or whatever code you have to return the list of items
return Projects.find();
}
// when clicking the new project button, create the new project
// and remember the id, then store that as the selected value in the session.
Template.newproject.events({
"click #newproject": function() {
var newprojectId = newProject(); // do your thing
Session.set("selected_project", newprojectId);
}
});
Template.projectpicked.projectpicked = function() {
return !Session.equals("selected_project", undefined);
}
The key here is to only set the selected_project Session value when you click on something. That happens twice; when you click an item to select it, and when you click the new project.
To get your template to draw the selected item, all you need to do in Meteor is describe when an item is selected: an item is selected when some session value matches its id. That's all.
I haven't tested this code and it's obviously incomplete, but hopefully this points you in the right direction for refactoring your code a bit.
Well, after getting rather frustrated and rewriting most of the code over again I found the problem. In my projectpicked template instead of using Session.get and then using it to find the class. In stead I just have a
if ($('.sidecat.project').hasClass('sidebarselected')) {
return true
}
While the original was:
if (find2 == 'project sidebarselected')
{
return true
}
So I used some jquery instead of directly comparing variable find2 to a string.