(This question is related to this one)
I have a web2py application which I want to extend with some ember.js code. The delimiters of the templating systems in web2py and ember.js conflict (both are {{ and }}). Since my application has no ember.js legacy, I would like to write the ember.js code using a different delimiter. Is this possible?
The template engine use by ember.js is Handlebars.js, and I don't think you can change the delimiter.
I've seen the other question, and perhaps an other answer could be found here : Handlebars.js in Django templates
In web2py: response.delimiters = ('[[',']]')
If you don't want to change any delimiters (on web2py or in handlebars) you can do it by saving the handlebars template in an external file like people.hbs in the
web2py /static/ folder for example
{{#each people}}
<div class="person">
<h2>{{first_name}} {{last_name}}</h2>
</div>
{{/each}}
And in the view import that file using jQuery load() function.
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
<script src="https://raw.github.com/wycats/handlebars.js/master/dist/handlebars.js"></script>
<div id="list"></div>
<script id="people-template" type="text/x-handlebars-template"></script>
<script type="text/javascript">
$('#people-template').load('/static/people.hbs', function() {
var template = Handlebars.compile($("#people-template").html());
var data = {
people: [
{ first_name: "Alan", last_name: "Johnson" },
{ first_name: "Allison", last_name: "House" },
]
};
$('#list').html(template(data));
});
</script>
Related
We have a theoretical need to run an ember widget inside of another ember widget. Is this possible to do? Would scoping the nested ember widget to a different dom element than its parent work?
The idea is that we might need to embed our ember widgets on customer websites that also run ember.
Thanks!
I'm not sure to understand you correctly but If your need is to have separate components that you can distribute and reuse across application, you sure can have them. Although I'm not sure I got you right as you seem to speak of 2 different problems (nesting widgets and distributing ember widgets as third parties for other ember applications...)
Here is a fiddle on how to make an external component and reuse it if you want more details. Let me know if this helps.
The component :
var GravatarImageComponent = Ember.Component.extend({
size: 200,
email: '',
gravatarUrl: function () {
var email = this.get('email'),
size = this.get('size');
return 'http://www.gravatar.com/avatar/' + CryptoJS.MD5(email) + '?s=' + size;
}.property('email', 'size')
});
Ember.Application.initializer({
name: "gravatar-image-component",
initialize: function(container, application) {
container.register('component:gravatar-image', GravatarImageComponent);
}
});
The HTML to bring it to life :
<script type="text/x-handlebars">
<ul class="example-gravatar">
<li>{{gravatar-image email="tomster#emberjs.com" size="200"}}</li>
<li>{{gravatar-image size="200"}}</li>
</ul>
</script>
<script type="text/x-handlebars" id="components/gravatar-image">
<img {{bind-attr src=gravatarUrl}}>
<div class="email-input">
{{input type="email" value=email placeholder="tomster#emberjs.com for instance"}}
</div>
</script>
I have defined a View subclass MenuitemView to which I'm referring from a template (see below). But Ember doesn't find it. I keep getting this error:
Error: assertion failed: Unable to find view at path 'App.MenuitemView'. What am I missing?
Initialization code:
window.require.register("initialize", function(exports, require, module) {
var App;
App = require('app');
App.Router.map(function(){});
require('routes/IndexRoute');
require('templates/menuitem');
require('templates/application');
require('templates/index');
App.initData();
});
View definition:
window.require.register("views/MenuItemView", function(exports, require, module) {
var App;
App = require('app');
module.exports = App.MenuitemView = Em.View.extend({
templateName: 'menuitem',
visibility: function(){
if (App.selectedDishType && !App.selectedDishType === this.get('dish').get('type')) {
return 'invisible';
} else {
return '';
}
}
});
});
Template referring to view (templates/index.hbs):
{{#each item in content}}
{{view App.MenuitemView itemBinding=item}}
{{/each}}
View template (templates/menuitem.hbs)
<div class="dishitem">
<div class="dishimage">
{{thumbnail item.dish.identifier}}
</div>
<div class="dishdetails">
<p class="dishname">{{uppercase item.dish.name}}</p>
<p class="dishdescription">{{item.dish.description}}</p>
<ul class="packages">
{{#each package in item.packages}}
<li>
<span class="packageprice">€ {{package.price}}</span>
<span class="packagespec">
{{#if package.description}}({{package.description}}){{/if}}
</span>
</li>
{{/each}}
</ul>
</div>
</div>
The problem was caused by the fact that I was using Brunch for building the application and I have all the javascript components and templates in separate files. Brunch then compiles the templates to separate common.js javascript modules. The code in the template's compiled module does not have access to the view defined in the view module. The way to normally handle such dependencies is to add "require('my-other-module')" to the dependent module's javascript. But, as my template source is not javascript but handlebars, I cannot add this to the source.
The solution is to ensure your application namespace is globally available. You do this by not putting your application initialization code in a module, e.g. directly in your html, inside a script tag:
<script>
App = require('app');
</script>
What I do notice is that the 'item' part of menuitemview is sometimes upper case (views/MenuItemView), sometimes lower case. Could that be the source of the error message?
I encountered a similar problem with ember-tools, and the accepted answer set me on the right path. I'll leave my solution here for posterity.
I had my app set up like this:
var FooRoute = Ember.Route.extend({
...
renderTemplate: function() {
this.render();
this.render('Bar', {into: 'foo', outlet: 'bar', controller: 'foo' });
},
...
});
and in the template foo.hbs:
{{outlet bar}}
This was causing problems for the same sorts of reasons #RudiAngela mentions in the accepted answer. Instead of including a <script> tag, though, I just changed the {{outlet}} to a {{view}},
{{view App.Bar}}
and this solved the problem for me.
In the documentation the first view example looks like:
HTML:
<html>
<head>
<script type="text/x-handlebars" data-template-name="say-hello">
Hello, <b>{{view.name}}</b>
</script>
</head>
</html>
JS:
var view = Ember.View.create({
templateName: 'say-hello',
name: "Bob"
});
and maybe I am being a muppet but I really don't understand what is going on. Could someone help.
I kind of understand the cases of {{view}}{{view}} around some html/handlebars where the actions and events will apply from the view definition in javascript. Also I appreciate that you can have a blockless single {{view MyApp.thingView}} which will render the template specified in the view into the the place the view helper is used (as well as making available properties in the view definition).
Is the {{view.x}} instantiating a view and if so why does the example use create rather than extend. Or is the view referring to the global var view (I'm assuming not since this is handlebars.) Could extend be used. Is this form just trying to say that you can access a view's properties inside a template where the view definition has templateName set to the template?
Thanks for any clarification
Update:
After looking at the example again it looks like the var is used for the programmatic append in the other snippets. So we can assume that this is like having the template within two {{view App.aView}}{{/view}} elements and the view. form allows you to get at properties inside App.aView.
<Update>
In response to the update in the question:
You should use {{#view App.SomeView}} ... {{/view}} if that view does not have any template associated to it.
On the other hand, you should use {{view App.SomeView}} if a template has been created for this view via naming conventions or templateName property. Example:
{{view Ember.TextField valueBinding="view.userName"}}
</Update>
When you see {{view.propertyName}} in a Handlebars template, that means you're consuming/rendering a property from the View you are in, so your initial assumption is kind of right. For example:
App = Em.Application.create();
App.HelloView = Em.View.extend({
welcome: 'Willkommen',
userName: 'DHH',
greeting: function() {
return this.get('welcome') + ' ' + this.get('userName');
}.property('welcome', 'userName')
});
Then in your application template:
<script type="text/handlebars">
<h1>App</h1>
{{#view App.HelloView}}
{{view.greeting}}
{{/view}}
</script>
In this case, the {{view.greeting}} part will look into the scope of that View (HelloView) for a property named greeting (it would be the same for any of those properties), and not in the parent view (ApplicationView which is implied). You have to use {{view.propertyName}} whenever calling properties defined in the View.
Properties defined in the controller can be accessed directly without a prefix.
One of the reasons for this, is to make sure you're calling the correct property. Consider the following:
App = Em.Application.create();
App.ApplicationView = Em.View.extend({
userName: 'David'
});
App.HelloView = Em.View.extend({
welcome: 'Willkommen',
userName: 'DHH',
greeting: function() {
return this.get('welcome') + ' ' + this.get('userName');
}.property('welcome', 'userName')
});
Now, both the application view and the inner view have been defined with a property named userName to represent slightly different things. In order to separate which one is which, you can use the view and parentView keywords to access the properties:
<script type="text/handlebars">
<h1>App</h1>
<!-- this comes from the ApplicationView -->
<h3>{{view.userName}}'s Profile</h3>
{{#view App.HelloView}}
<!-- this comes from the HelloView -->
{{view.welcome}} {{view.userName}}
{{/view}}
</script>
And if you want/need to use the real name and nickname in this example, you'd have to:
<script type="text/handlebars">
<h1>App</h1>
{{#view App.HelloView}}
<!-- this comes from the ApplicationView -->
<h3>{{parentView.userName}}'s Profile</h3>
<!-- this comes from the HelloView -->
{{view.welcome}} {{view.userName}}
{{/view}}
</script>
Relevant reference:
http://emberjs.com/api/classes/Ember.View.html
http://emberjs.com/api/classes/Ember.TextField.html
If anyone can put me out of my misery with this I would greatly appreciate it, drving me mad and I know it's gonna be something stupidly simple.
I have an array:
Data
var test = [{"name":"Kober Ltd","town":"Bradford","type":"Z1CA","number":3650645629},
{"name":"Branston Ltd.","town":"Lincoln","type":"Z1CA","number":3650645630}]
and I want to render this info as child elements inside a collectionView:
collectionView
App.ThreeView = Ember.CollectionView.extend({
itemViewClass: Ember.View.extend({
click: function(){
alert('hello')
},
classNames: ['element','foobar'],
templateName: 'foo'
})
})
and here is my controller:
controller
App.ThreeController = Ember.ArrayController.extend({
content: [],
init: function(){
var me = this;
$.each(test,function(k,v){
var t = App.ThreeModel.create({
name : v.name,
town: v.town,
type: v.type,
number: v.number
})
me.addObject( t )
})
console.log( me.content )
}
})
Templates:
<script type="text/x-handlebars" data-template-name="application">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="three">
</script>
<script type="text/x-handlebars" data-template-name="foo">
<div class="symbol"> {{ view.content.type }} </div>
<div class="number"> {{ view.content.number }} </div>
<div class="name"> {{ view.content.name }} </div>
<div class="town"> {{ view.content.town }} </div>
</script>
I am using the latest Ember so...V2 router that syncs up all the parts with the 'Three' name. Every will work if I put the array directly into the view:
App.ThreeView = Ember.CollectionView.extend({
content: test, // manually added a pure array into view content
itemViewClass: Ember.View.extend({
click: function(){
alert('hello')
},
classNames: ['element','foobar'],
templateName: 'foo'
})
})
But when I try and do this 'properly', using Ember.js Objects, I get no rendered views ( aside from an empty application view ).
I have tried work arounds, like adding a 'contentBinding' from the view to the controller just to see if I can force a connection but still nothing. It is important that I render the view through the container as I am using Three.js to pick up on the rendered content and manipulate further.
So, to summarise: I can render pure arrays in view, but nothing passed from controller. Incidentally, the controller is definitely being instituted as I can console log its contents on init. If i change the view name, the controller is not instantiated so I know the namespacing is working.
thanks in advance!
I'm not sure to embrace the whole problem, but for now, when you define your controller, in the init() function, first don't forget to call this._super() (it will go through the class hierarchy and call the constructors). Maybe that's just the missing thing.
Edit: it seems like with the new router, defining a view as a CollectionView does not work.
so I replaced it with a normal Ember.View, and use an {each} helper in the template.
<script type="text/x-handlebars" data-template-name="three">
{{each controller itemViewClass="App.FooView" tagName="ul"}}
</script>
here is a minimal working fiddle: http://jsfiddle.net/Sly7/qCdAY/14/
EDIT 2:
By re-reading the question, and seeing you try to bind the CollectionView content's property to controller, I tried it, because it just work fine :)
http://jsfiddle.net/Sly7/qCdAY/15/
I have a collection of objects (MyApp.instrsController, an ArrayController) and each object has a property called 'action' (a float number), how can I display it using the handlebars template?
Below is the code I've tried:
<ul>
{{#each MyApp.instrsController}}
<li>{{action}}</li>
{{/each}}
</ul>
But since 'action' is a 'keyword', it throws javascript runtime error 'options is undefined'.
You can manually specify that you're looking for a property in the current context by preceding the property name with this.:
{{this.action}}
If you can't change the property name you can use a computed property in your model object, see http://jsfiddle.net/pangratz666/4ZQM8/:
Handlebars:
<script type="text/x-handlebars" >
<ul>
{{#each App.controller}}
<li>{{actionProp}}</li>
{{/each}}
</ul>
</script>
JavaScript:
App.Object = Ember.Object.extend({
actionProp: function() {
return this.get('action');
}.property('action')
});
App.controller = Ember.ArrayController.create({
content: [],
addObj: function(number) {
this.pushObject(App.Object.create({
action: number
}));
}
});
If you don't have a custom model object, you can use a computed property on a CollectionView, see http://jsfiddle.net/pangratz666/r6XAc/:
Handlebars:
<script type="text/x-handlebars" data-template-name="item" >
{{actionProp}}
</script>
JavaScript:
Ember.CollectionView.create({
contentBinding: 'App.controller',
itemViewClass: Ember.View.extend({
templateName: 'item',
actionProp: function(){
return this.getPath('content.action');
}.property()
})
}).append();
I seriously suggest that you just change the name of the property.
You are spending time working on a problem that is not core to your domain.
YAGNI. KISS.
The biggest lesson to learn in programming is how to get things made and done rather than how to manipulate javascript into not throwing a snit over your use of a reserved keyword. You will be happier later if you don't have a fragile solution that looks tricky to understanc
Since this is a float, can I suggest you use "actionLevel"?
just use model.property,such as:
{{input class="input-xlarge" value=model.content }}