How to get model data on view in Ember JS - javascript

How can i access model data in my view template in Ember JS ?
Is it saved in a global variable?
Here's my template:
<script type="text/x-handlebars" data-template-name="todo-list">
{{#if length}}
<section id="main">
{{#if canToggle}}
{{input type="checkbox" id="toggle-all" checked=allTodos.allAreDone}}
{{/if}}
<ul id="todo-list">
{{#each}}
<li {{bind-attr class="isCompleted:completed isEditing:editing"}}>
{{#if isEditing}}
{{todo-input type="text" class="edit" value=bufferedTitle focus-out="doneEditing" insert-newline="doneEditing" escape-press="cancelEditing"}}
{{else}}
{{input type="checkbox" class="toggle" checked=isCompleted}}
<label {{action "editTodo" on="doubleClick"}}>{{title}}</label>
<button {{action "removeTodo"}} class="destroy"></button>
{{/if}}
</li>
{{/each}}
</ul>
</section>
{{/if}}
</script>
<script type="text/x-handlebars" data-template-name="todos">
<section id="todoapp">
<header id="header">
<h1>todos</h1>
{{todo-input id="new-todo" type="text" value=newTitle action="createTodo" placeholder="What needs to be done?"}}
</header>
{{outlet}}
{{#if length}}
<footer id="footer">
<span id="todo-count"><strong>{{remaining.length}}</strong> {{pluralize 'item' remaining.length}} left</span>
<ul id="filters">
<li>
{{#link-to "todos.index" activeClass="selected"}}All{{/link-to}}
</li>
<li>
{{#link-to "todos.active" activeClass="selected"}}Active{{/link-to}}
</li>
<li>
{{#link-to "todos.completed" activeClass="selected"}}Completed{{/link-to}}
</li>
</ul>
{{#if completed.length}}
<button id="clear-completed" {{action "clearCompleted"}}>Clear completed</button>
{{/if}}
</footer>
{{/if}}
</section>
<footer id="info">
<p>Double-click to edit a todo</p>
</footer>
</script>
Thank you!

The Route sets up a "model" variable that is accessible by your template via:
{{model.myProperty}} or
{{myProperty}} (Before Ember2 this way would look up into your Controller Computed Properties. If none exists, then it would proxy through model.myProperty. See this deprecation)
In order to make the model data accessible "outisde of Ember" your can:
Create a Component
From the template, call it and pass the data down to it
Use onDidInsertElement and read the property. From now on, you can make it accessible to "the outisde world"
OBS: I'm not sure that's a good practice though.
JS BIN: http://emberjs.jsbin.com/dapidu/3/edit?html,js,output

If you want to use it in a different template (which is linked to a different controller I guess?), you can create an alias in the other controller like this :
//Include the first controller
needs: ['todoController'],
length: Ember.computed.alias('controllers.todoController.length'),
I don't have much precisions on how you built your application, but if you can't access it from the other template, it means your changing the application context (firstController -> secondController).
Do you have 2 controllers?

Related

handlebars, access to parent each loop variable

I have code like here. I would like to set input name to #index from previous each loop.
Can I access #previousIndex somehow? Or can I assign inputs to some kind of group created before second each loop that will set all input names inside?
Data I receive:
questions:[
{
question:"How old are you?",
answers:[
'16',
'18',
'20',
'25',
'other'
]
},
{
question:"How many kids do you have?",
answers:[
'0',
'1',
'2',
'other'
]
}
]
hbs code:
{{#each questions}}
<li>
<h3 class='question'>{{this.question}}</h2>
<div class='flexbox'>
{{#each this.answers}}
<label class="container">
<input type='radio' name='{{#previousIndex}}' value='{{#index}}'>
<span class='checkmark'>{{this}}</span>
</label>
{{/each}}
</div>
</li>
{{/each}}
When you are using #each then you get the index in #index and if you want previousIndex why not just do parseInt(#index) - 1
Here's how you do it:
var Handlebars = require('handlebars');
Handlebars.registerHelper("previous", function(value, options)
{
return parseInt(value) - 1;
});
Now update your hbs code as:
{{#each questions}}
<li>
<h3 class='question'>{{this.question}}</h2>
<div class='flexbox'>
{{#each this.answers}}
<label class="container">
<input type='radio' name='{{previous #index}}' value='{{#index}}'>
<span class='checkmark'>{{this}}</span>
</label>
{{/each}}
</div>
</li>
{{/each}}
Hope this works for you!
I can't give you a 100% working code (environment is not currently setup) but from my previous experience with handlebars I will try to guide you to a possible solution.
{{assign "previousIndex" 0}}
{{#each questions}}
<li>
<h3 class='question'>{{this.question}}</h2>
<div class='flexbox'>
{{#each this.answers}}
<label class="container">
<input type='radio' name='{{../previousIndex}}' value='{{#index}}'>
<span class='checkmark'>{{this}}</span>
</label>
{{/each}}
</div>
</li>
{{assign "../previousIndex" "{{#index}}"}}
{{/each}}
I had to create two helper functions like here. Get is here because I wasn`t able to access variable directly from .hbs file by simply writing {{myVariableName}}.
Hbs file looks now like here.
<div class='flexbox'>
{{assign "parentIndex" #index }}
{{#each this.answers}}
<label class="container">
<input type='radio' name='{{get "parentIndex"}}' value='{{#index}}'>
<span class='checkmark'>{{this}}</span>
</label>
{{/each}}
</div>
helpers
assign: function(varName, varValue, options) {
options.data.root[varName] = varValue;
}
get: function(varName, options) {
return options.data.root[varName]
}

Bind-attr "for" attribute in Ember.js

I'm trying to include a unique id for an and tag to get a custom checkbox. The {{input}} tag outputs it correctly but the <label {{bind-attr for=binding}} does not. I'm a frontend guy new to Ember so I'm sure this should be trivial.
<ul class="col-menu dropdown-menu">
{{#each columns}}
<li>
{{input type="checkbox" checked=checked id=binding}}
<label {{bind-attr for=binding}}><span></span></label>
<span>{{heading}}</span>
{{columns}}
</li>
{{/each}}
</ul>
Here is the output ...
<li>
<input id="activityTotal" class="ember-view ember-checkbox" type="checkbox" checked="checked">
<label data-bindattr-2689="2689">
<span></span>
</label>
<span>
<script id="metamorph-53-start" type="text/x-placeholder"></script>
Events
<script id="metamorph-53-end" type="text/x-placeholder"></script>
</span>
<script id="metamorph-54-start" type="text/x-placeholder"></script>
<script id="metamorph-54-end" type="text/x-placeholder"></script>
First of all you should wrap all this in a component.
You really should not be manually binding the id like this as ember uses it internally but I think it is acceptable in this case as I can't think of a better way but you need to maintain the uniqueness.
I would so something like this to ensure the id is unique and uses
checkBoxId: Ember.computed(function(){
return "checker-" + this.elementId;
}),
Here is the component's javascript file that will be executed when the component is ran:
App.XCheckboxComponent = Ember.Component.extend({
setup: Ember.on('init', function() {
this.on('change', this, this._updateElementValue);
}),
checkBoxId: Ember.computed(function(){
return "checker-" + this.elementId;
}),
_updateElementValue: function() {
this.sendAction();
return this.set('checked', this.$('input').prop('checked'));
}
});
Here is the component's template, I use unbound to bind the unique checkBoxId with the label's for:
<label for="{{unbound checkBoxId}}">
{{label}}
<input id="{{unbound checkBoxId}}" type="checkbox" {{bind-attr checked="checked"}}>
</label>
Here is how you might use it:
<ul>
{{#each contact in model}}
<li>{{x-checkbox label=contact.name enabled=contact.enabled}}</li>
{{/each}}
</ul>
And here is a working jsbin for ember 1.7.1 and here is a working jsbin for ember 1.11.1.
As of version 1.11, you don't need bind-attr. You should be able to bind the attribute like this:
<label for={{binding}}></label>

Sending data into a nested Controller/View in EmberJS

I'm messing around with JSON structures in EmberJS starting from the top level Application level and trying to pass data to a general MenuController/MenuView child so I can reuse it.
The JSON I've come up with to play around with how data gets passed through nested ember structures is this:
JSON
{
appName: "CheesyPuffs"
menuItems: [
{name:"Gouda"}
{name:"Crackers", menuItems: [
{name: "Cheezeits"}
]
}
]
}
Handlebars
<script type="text/x-handlebars">
<div class='app'>
<div class='header'>
{{#if menuItems}}
// Embed template 'menu'
{{/if}}
</div>
</div>
</script>
<script type="text/x-handlebars" data-template-name="menu">
<ul>
{{#each menuItems}}
<li>{{name}}</li>
{{#if menuItems}}
// Embed menu template
{{/if}}
{{/each}}
</ul>
</script>
JS
App.MenuController = Ember.ArrayController.extend({
actions: {
click: function(){
// Signal Event to App
}
}
});
App.MenuView = Ember.View.extend({
click: function(){
console.log("I clicked a menu");
}
});
All the examples I've seen concerning nested stuff involves routes with the classic _id subroute. I'm not sure how I would go about telling the App Router that it needs to pass the menuItems data to a child controller that's embedded in the same view. I have a feeling you can do this both via the template and programmically? If so, how would you do it both ways?
using the template you should use render. And that's the way you should do it, programmatically doesn't make sense.
<script type="text/x-handlebars">
<div class='app'>
<div class='header'>
{{#if menuItems}}
{{render 'menu' menuItems}}
{{/if}}
</div>
</div>
</script>
<script type="text/x-handlebars" data-template-name="menu">
<ul>
{{#each menuItems}}
<li>{{name}}</li>
{{#if menuItems}}
{{render 'menu' menuItems}}
{{/if}}
{{/each}}
</ul>
</script>

Ember navbar UI condition based on currentPath

I must not be doing something right. I have the following:
application.hbs
{{#view App.NavbarView}}{{/view}}
{{outlet}}
with the following template for Navbar
_navbar.hbs
<div class="toolbar">
<div class="row">
<div class="absolute top-left">
<button {{action "back"}} class="btn passive back"><i class="fa fa-play"></i></button>
</div>
{{#if hasTabs}}
<div class="small-centered columns">
<div class="tabs">
<ul>
{{#link-to 'stories' tagName="li" class="tab"}}<i class="fa fa-book"></i> Stories{{/link-to}}
{{#link-to 'mylevels' tagName="li" class="tab"}}<i class="fa fa-user"></i> My Levels{{/link-to}}
{{#link-to 'arcade.index' tagName="li" class="tab"}}<i class="fa fa-gamepad"></i> Arcade{{/link-to}}
</ul>
</div>
</div>
{{else}}
<div class="small-6 small-offset-3 columns">
<h2 class="title">{{ pageTitle App.currentPath }}</h2>
</div>
{{/if}}
{{#if currentUser.userName}}
<div class="absolute top-right">
<span class="user-hello">Welcome Back, <strong>{{ currentUser.userName }}</strong></span>
<button {{action "transitionAccount" currentUser._id}} class="square logged-in"><i class="fa fa-user"></i></button>
</div>
{{ else }}
<div class="absolute top-right">
<button {{action "transitionLogin"}} class="square logged-out"><i class="fa fa-user"></i></button>
</div>
{{/if}}
</div>
</div>
So all it is is a typical fixed navbar and in the middle of it I display what page you are on, if you happen to be on a page that has tabbed content, I show a tab container instead.
So I'm just using this.get('currentPath') in my App controller and comparing it against a group of route names to trigger true/false (I need an observer so it looks at the route change since the Navbar is in inline view at the Application level).
app.js
App.ApplicationController = Ember.ObjectController.extend({
updateCurrentPath: function() {
App.set('currentPath', this.get('currentPath'));
}.observes('currentPath'),
tabs: function() {
var route = this.get('currentPath'),
group = ['arcade.index', 'mylevels', 'stories', 'arcade', 'arcade.loading'];
console.log("ROUTE: ", route);
var tabs = group.indexOf(route) > -1 ? true : false;
return tabs;
}.observes('currentPath'),
// no idea what to do here
hasTabs: function() {
this.tabs();
}.property('tabs')
});
So, basically, no matter what, the tab UI is showing up, but I only want it to show up if that tabs observer is true. With some debugging I'm getting all the console output I would expect but I tried just doing {{#if tabs}} (just using the observer directly) and that always fires true (always shows the tabs UI). I assumed that's because it was an observer and not an actual controller property I could use in my template, so I tried just setting the hasTabs property and referencing the observer, but that doesn't seem to work. I realize I am fundamentally not understanding how this should work. Any thoughts?
If I understand your question correctly you should be able to just change your code to this (renamed tabs to hasTabs, removed previous hasTabs function. Changed from observes currentPath to be property of current path, removed the tabs variable assignment and replaced with the return, reduced the boolean conditional to the simple comparison operator). This is what I'd do, anyway. :) H2H
hasTabs: function() {
var route = this.get('currentPath'),
group = ['arcade.index', 'mylevels', 'stories', 'arcade', 'arcade.loading'];
return group.indexOf(route) > -1;
}.property('currentPath')

Why can't I change this emberjs model without an error?

I'm writing an ember app that has this settings model that I'm using to bypass routing because I want a single url. However, I get this error:
Error: Cannot perform operations on a Metamorph that is not in the DOM.
when I change any of the data in the model. I'm relatively new to ember, but from what I read of what's out there, you should be able to change a model just fine. Here's my html file:
<script type="text/x-handlebars" data-template-name="index">
<div class="navbar navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<a class="brand" href="#"><img src="./img/MySpending.png"></a>
<div class="nav-collapse">
<ul id="navWrapper" class="nav">
{{#if model.isIndex}}
{{partial "indexNav"}}
{{/if}}
{{#if model.isMonthly}}
{{partial "monthlyNav"}}
{{/if}}
{{#if model.isYearly}}
{{partial "yearlyNav"}}
{{/if}}
</ul>
</div>
<div class='navbar_form pull-right'></div>
</div>
</div>
</div>
<div id="bodyWrapper" class="container" style="padding-top:60px;">
{{#if model.isIndex}}
{{partial "indexPage"}}
{{/if}}
{{#if model.isMonthly}}
{{partial "monthlyPage"}}
{{/if}}
{{#if model.isYearly}}
{{partial "yearlyPage"}}
{{/if}}
</div>
{{outlet}}
And each partial works fine initially.
It's when I change it in the three regular functions in my app.js that I have a problem. Here's my app.js:
App = Ember.Application.create();
App.Router.map(function() {
// put your routes here
});
App.settings=Ember.Object.extend({
isIndex: true,
isMonthly: false,
isYearly: false
});
Settings=App.settings.create();
//App.SettingsController = Ember.ObjectController.extend(Settings); Not sure whether to put this in or not
App.IndexRoute = Ember.Route.extend({
model: function() {
return Settings;
}
});
function goToMonthly(){
Settings.set('isIndex',false);
Settings.set('isMonthly',true);
Settings.set('isYearly',false);
}
function goToYearly(){
Settings.set('isIndex',false);
Settings.set('isMonthly',false);
Settings.set('isYearly',true);
}
function goToIndex(){
Settings.set('isIndex',true);
Settings.set('isMonthly',false);
Settings.set('isYearly',false);
}
So I'm not sure where the error is but if anyone can help,it'd be extremely appreciated.
There's a fundamental error in your approach here. Your Route/Controller are meant to handle the model for you, so by declaring your model outside of these objects, you're making more work for yourself and leading yourself to potential trouble.
I'm not seeing where your "goTo" functions are being called (I assume they're inside the partials, but if they're in that HTML and I missed it, that's my bad). But here's how they should be referenced:
App.SettingsController = Ember.ObjectController.extend({
actions:{
goToMonthly:function(){
this.set('isIndex', false);
this.set('isMonthly', true);
this.set('isYearly', false);
},
//Other functions go here
}
});
Then within your html, call these actions using handlebars:
<a {{action 'goToMonthly'}}> Click this to run the action </a>
Which ember version are you using? I tried it with ember 1.2 and 1.3 and your example is working fine.

Categories

Resources