From Rails & Backbone to ember - javascript

I'm trying out ember js on a personal project. I'm struggling with understanding what part plays the different components of Ember.
I'm quite familiar with Rails, and I've used backbone to some extent (never used everything related to routing), and get how the two of them work, and what kind of objects plays which part of MVC.
With Ember, things are not that clear and seems a bit less easy. I've read the guides and a few posts, but I'm still struggling to get going. I get the model part, which is fairly similar to its rails and backbone counterpart. I get templates, handlebars, that stuff too. The router is similar to rails router.
Then there is Controllers, Routes, and Views. From what I understood, Views represent a portion of the ui, and handles user interaction. It's the controller/routes roles that I don't get very much.
To be more concrete: I want my app to have a nav bar (with links to page sections), and a "user connexion widget", displaying user data if he's connected, allow him to do so if not, log in via facebook etc.
The navbar seems appropriate for a View, however I need to keep the state somewhere (to highlight the current page), which seems to be the controller role. And the user widget seems appropriate for a controller, but there's only one controller per route, so how to do this ?
Thanks a lot for your time, I hope I was clear enough!
:)

Controller stores the state of your application. Router manages different states of your application. You're correct, there's only one controller per route, but you can access other controllers as well.
Assuming this your html:
<script type="text/x-handlebars" data-template-name="application">
<div class="main-content">
{{outlet}}
</div>
</script>
<script type="text/x-handlebars" data-template-name="index">
<div class="nav-bar">
<div class="user">
{{#if isLoggedIn}}
<div class="user-name">{{userName}}</div>
{{else}}
<button {{action "login" target="App.UserController"}}>
Login
</button>
{{/if}}
</div>
</div>
<div class="content">some other amazing content here</div>
</script>
and JS:
var App = Ember.Application.create({
LOG_TRANSITIONS: true,
rootElement: '#ember-app'
});
App.Router.map(function () {
this.route('user');
});
App.IndexRoute = Ember.Route.extend({
setupController: function(controller, model) {
this.controller.set('isLoggedIn', this.controllerFor('user').get('isLoggedIn'));
}
});
App.UserController = Ember.ObjectController.extend({
isLoggedIn: false,
userName: 'emberjs',
login: function () {
this.set('isLoggedIn', true);
}
});
App.NavbarView = Ember.View.create({
loggedInBinding: Ember.Binding.oneWay('App.UserController.isLoggedIn')
});
If you need to access another controller from your route, you can do this:
App.IndexRoute = Ember.Route.extend(function () {
setupController: function () {
var userController = this.controllerFor('user');
console.log('this is a user controller instance', userController);
}
});

Related

Ember.js Render multiple views inside static HTML

I'm new to Ember and I'm stuck trying to render views.
The application in context is not a single page application but so far Ember has been playing nice until I started dealing with views.
I'm trying to render more than one view on a single page (which is like a dashboard and has tons of static HTML and a few containers, one for each view).
Sample HTML:
Let's say I would like to render two lists, one inside the "left-container" div and the other inside the right container.
<div class="static-header></div>
<!-- more static content goes here -->
<div id="left-container"></div>
<!-- more static content goes here -->
<div id="right-container"></div>
...
I've tried creating different views and inserting them using the appendTo method (which is described in the Defining a View section of Ember guides) but it throws the error:
Container was not found when looking up a views template. This is most likely due to manually instantiating an Ember.View. See: http://git.io/EKPpnA
and I couldn't find my way using the link that it points to.
Ember code:
var App = Ember.Application.create({});
App.HomeViewLeft = Ember.View.extend({
templateName: 'home-left',
});
var view = App.HomeViewLeft.create();
view.appendTo('#left-container');
I have also tried using a ContainerView as described in Ember api docs:
App.HomeViewLeft = Ember.View.extend({
templateName: 'home-left',
});
var containerView = Ember.ContainerView.create({
classNames: ['container-view'],
});
containerView.pushObject(App.HomeViewLeft.create());
containerView.appendTo('left-container');
But I get the same error.
How should I render each view inside the #left-container and #right-container respectively?
Thanks in advance.
Template:
<div id="here"></div>
<script type="text/x-handlebars" data-template-name="components/my-foo">
{{text}}
</script>
JS:
App = Ember.Application.create({});
Ember.Application.initializer({
name: 'stand-alone-components',
initialize: function(container, application) {
App.MyFooComponent.create({text: 'Hello World', container: container}).appendTo('#here');
}
});
App.MyFooComponent = Ember.Component.extend({
init: function() {
this._super();
this.set('layout', Ember.TEMPLATES['components/my-foo']);
}
});
http://emberjs.jsbin.com/nekowidera/1/edit?html,js,output
It looks like you are trying to make views behave like partials
Move anything static into a partial and include it in your main template like so:
{{partial "header"}}
The lists you want to render can be turned into a component (if the only thing that changes in them is the data).
So you end up with a single view that contains partials and components:
<div class="static-header>{{partial "header"}}</div>
{{partial "static-content"}}
<div id="left-container">{{list-component data=firstList}}</div>
{{partial "static-content"}}
<div id="right-container">{{list-component data=secondList}}</div>
...

Ember replacing html content

I've just started learning ember am trying to make a link that replaces other html, currently I have written this:
<script type="text/x-handlebars" id="about">
{{#each}}
<h2>{{#link-to 'quarter' this}}{{title}}{{/link-to}}</h2>
{{/each}}
</script>
Now I want to make the quarter content load in the same about template replacing that each loop, how should I go about it?
Again, I'm totally new to ember and it's own guides section is a bit too convoluted for me to find the answer.
Since About is not a resource route, you have to use custom outlet in your about template:
<div class="quarter">{{outlet quarter}}</div>
Then in your QuarterRoute:
App.QuarterRoute = Ember.Route.extend({
renderTemplate: function() {
this.render({ outlet: 'quarter' });
}
});
If you have a Quarters resource routes, you could just nest your routes and just a have simple outlet in your quarters template.
App.Router.map(function() {
this.resource('quarters', function() {
this.resource('quarter');
});
});

Rendering views in Ember globally

I’m quite new in Ember world (came from Backbone/Marionette powered apps), and some things are quite unclear for me.
I’d like to render 3 views in my application: Sidebar, Navigation, and view with actual content (based on my path).
In order to do that, I added to my application tempalate 3 outlets:
<nav id="main_navigation">{{outlet navigation}}</nav>
<!-- some html goes here... -->
<nav id="sidebar_navigation">{{outlet sidebar}}</nav>
<section id="content">{{outlet}}</nav>
Because I want to display them on every page, I created ApplicationRoute with following code:
App.ApplicationRoute = Ember.Route.extend
activate: ->
#render "sidebar",
outlet: "sidebar"
# into "application"
Of course I’ve got sidebar template, but I don’t think its code is relevant here (I can attach it if it’ll be relevant). Unfortunately, this leads to following error:
Error while loading route: Error: Assertion Failed: An outlet (sidebar) was specified but was not found.
If I try to comment out into "application", then I get this: Error while loading route: TypeError: Cannot read property 'connectOutlet' of undefined
I’d be really greatful, if someone tell me how to render this templates globally (on all pages).
The activate hook is executed when the router enters the route. The template must already be in scope in order to render an item into it. That being said, you can just render the application template first, then render the navbar/sidebar using the renderTemplate hook.
App.ApplicationRoute = Ember.Route.extend({
renderTemplate: function(){
this.render(); // render the application template
this.render('nav',{
into: 'application',
outlet: 'navigation'});
this.render('side',{
into: 'application',
outlet: 'sidebar'});
}
});
http://emberjs.jsbin.com/lemutisa/1/edit
Additionally you could do this all even easier using render in the template
<script type="text/x-handlebars">
Appplication
{{render 'nav'}}
{{render 'side'}}
<section id="content">{{outlet}}</nav>
</script>
http://emberjs.jsbin.com/lemutisa/2/edit

How to make something happen after a template has rendered in emberjs

So the concept is simple: You come to the app, you have a default template, with a default navigation element. You click a link in that navigation element it renders a new template: #/rails.
From here the default navigation needs to be hidden, and your new nav needs to be rendered.
The way i attempted to approach this, seems a bit silly: what I did was,
SG.Router.map(function(){
this.resource('rails');
});
$(document).ready(function() {
if($('#rails-nav').length !== 0){
$('#main-nav').hide();
}
});
Now the issue with this is that if you go from the default application template, to the rails template via the link - you get two navs unless you refresh that page. My friend stated that I should use something like:
{{outlet nav}} and then render a navigation based on template. The issue is I don't know how to set this up and I have been looking all over the ember site.
Could some one help me out?
If i understood correctly, when you say default template you mean the application template, where all other templates are rendered within its {{outlet}} helper.
There are several approaches to achieve what you want, but a simple one i think would be to use the index template as your default template. In this case evertyhting will be much simpler and work as you require, since you can specify whether the navigation element is shown by placing it inside a template or not.
http://emberjs.jsbin.com/sume/1/edit
hbs
<script type="text/x-handlebars">
<h2> Welcome to Ember.js</h2>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
This is the default template using <i>index</i>
<br/>
<br/>
<span style="background-color:grey">this is the nav part {{#link-to 'rails'}}go to rails{{/link-to}}</span>
</script>
<script type="text/x-handlebars" data-template-name="rails">
this is the rails app
</script>
js
App = Ember.Application.create();
App.Router.map(function() {
this.resource("rails");
});
Also to make this answer a bit more relevant to the title of the question, in order to do something when a template has rendered one way would be to use the didInsertElement callback of the View class.
App.RailsView = Ember.View.extend({
didInsertElement:function(){
if($('#rails-nav').length !== 0){
$('#main-nav').hide();
}
}
});
http://emberjs.jsbin.com/dido/1/edit

Angular.js - Two Concurrent But Exclusive Routes ($routeProvider)

<div id="header">
Header
</div>
<div id="wrapper">
<div id="slider" ng-view>
Slider
</div>
<div id="mainWindow" ng-view>
Main window
</div>
</div>
http://jsfiddle.net/abehnaz/k9Y4f/
I am working with a webapp that has two major views. The first is a main window (standard stuff), and the second is a "slider". The slider is more than a slide out menu (sometimes called a "shelf"). It has routing and secondary information associated with it.
In what seems to be the normal Angular paradigm, I would use a single ng-view in the main window, and that would populate the main window with the results of the $routeProvider's view template.
However, here I want to be able to ALSO have the slider have its own route. Angular does not seem to support this. I was thinking that I could potentially hack the functionality in by making a client side URL of the form:
www.app.com/MAINWINDOW/someView1/someView2/SLIDER/someViewa
Am I on the right track with this, or is there another functionality of Angular that I can use?
I would not recommend two routeParameters. Make your sidebar a service and put a controller on your wrapper, slider, and mainWindow. By having the ng-controller on #wrapper as well as the children, you can share data between the controllers (and in turn, inject the service into those controllers. I have done this and it was very successful. See an example below. In my code, my ng-view and routing returned everything within the <div ng-app="myApp">. You could easily put the ng-view in the MainCtrl and trigger routes from the sidebar.
In your template:
<div ng-app="myApp">
<div ng-view>
//Produced by routing
<div id="wrapper" ng-controller="RootCtrl">
<div id="Sidebar" ng-controller="SidebarCtrl">
...
<div ng-click="setUnit(mod.number, unit.number)">Unit 1</div>
...
</div>
<div id="mainWindow" ng-controller="MainCtrl">
</div>
</div>
</div>
</div>
In your javascript:
A service:
myApp.service('questionsSvc', function(Modules) {
var QuestionsSrc = {};
QuestionsSrc.questions = [];
var getQuestionsForModAndUnitFn = function(mod, unit) {
...bunch of code to have it populate QuestionSrc with questions
};
return {
getQuestionsForModAndUnit: getQuestionsForModAndUnitFn,
Questions: QuestionsSrc
};
});
For Controllers:
function RootCtrl($scope) {
$scope.data = {};
}
function SidebarCtrl($scope, Modules, questionsSvc) {
$scope.setUnit = function (mod, unit) {
questionsSvc.getQuestionsForModAndUnit(mod, unit);
$scope.data.questions = questionsSvc.Questions.questions;
//$scope.data.questions was used in the MainCtrl window to do an ng-repeat on the json array of questions bound to this model.
}
};
function MainCtrl($scope){
...whatever you need to manipulate the code...
}
With this example, I am sharing information across Controllers. The magic is with the $scope.data = {}; from the RootCtrl. It allows me to attach questions to the $scope based on an action in the Sidebar (clicking a label) and use that same $scope in the MainCtrl to display the questions in a pretty format. With what I just showed, I did not have to use $routeParameters to pass variables to go to another page (such as module and quiz) but I could have done so as you asked but would have had the sidebar ng-click change routes.
Hope this helps.
One solution is to use Angular UI router:
AngularUI Router is a routing framework for AngularJS, which allows
you to organize the parts of your interface into a state machine.
Unlike the $route service in Angular core, which is organized around
URL routes, UI-Router is organized around states, which may optionally
have routes, as well as other behavior, attached.
States are bound to named, nested and parallel views, allowing you to
powerfully manage your application's interface.
Just to give a flavor for how you could use AngularUI Router- It supports multiple named views (which you can read more about under "Multiple & Named Views" in their docs). So for instance you can use ui-view with names:
<div id="wrapper">
<div id="slider" ui-view="slider">
Slider
</div>
<div id="mainWindow" ui-view="main">
Main window
</div>
</div>
Then within config you can attach states to various routes and specify what each view should display for that state.
myapp.config(function($stateProvider, $urlRouterProvider){
$stateProvider
.state('index', {
url: "/index",
views: {
"Slider": {
templateUrl:"route1.viewA.html"
},
"Main": {
templateUrl:"main1.viewA.html"
}
}
})
.state('slider2', {
url: "/slider2",
views: {
"Slider": {
templateUrl:"route2.viewA.html"
},
"Main": {
templateUrl:"main1.viewA.html"
}
}
})
There's a variety of other ways you could use AngularUI Router though. For instance, you may be able to get away with just using nested routes- which they recommend trying first.
Here's a couple more good reference material you might check out:
http://joelhooks.com/blog/2013/07/22/the-basics-of-using-ui-router-with-angularjs/
http://www.ng-newsletter.com/posts/angular-ui-router.html

Categories

Resources