Ember Dynamically Generated HTML - ember.js

I have a requirement where I need to add html after the DOM has been rendered.
I was wondering if it is possible to manipulate the DOM after creation and dynamically add html and also specifying an associated ember action.
E.g. The intension of what I want to achieve:
$('.add').on("click", function(e){
e.preventDefault();
i += 1;
var content = "<div class=\"item dodgerBlue\"><h1>"+i+"</h1></div>";
var content + "{{action "owlItemClicked" titleModel.id titleModel.index titleModel on="click" }}"
owl.data('owlCarousel').addItem(content);
});
Specifically I want to add another Item to my carousel:
http://owlgraphic.com/owlcarousel/demos/manipulations.html

I'm not sure that the triple-stash, which just includes content without escaping it, will work with an {{action}}. In any case, it looks to me that you'd be better off simply defining the html within an each block and letting Ember handle the content addition.
{{#each model as |titleModel index|}}
<div class=\"item dodgerBlue\">
<h1>{{index}}</h1>
</div>
{{action "owlItemClicked" titleModel.id titleModel.index titleModel on="click" }}
{{/each}}
I noticed you have a titleModel.index property, so maybe you don't need the index in each block and can use the model's property instead.
That would be the Ember way to do it. However, it looks like this OwlCarousel widget wants to have the html passed directly. But there's also a reinit method, so maybe that would be sufficient to tell it that new content has been added through Ember. Something like the following:
carouselOptions: {
// ...
},
didInsertElement: function() {
Ember.run.scheduleOnce('afterRender', this, function() {
var $c = Ember.$('#the-carousel');
if ($c.length) {
$c.owlCarousel( this.get('carouselOptions') );
}
});
},
actions: {
addCarouselItem: function(e){
e.preventDefault();
// Add a new item to your array.
// Not shown because i have no idea of your code.
// Ember will handle DOM insert.
// Then reinit carousel.
$("#the-carousel").data('owlCarousel').reinit( this.get('carouselOptions') );
},
owlItemClicked: function(e) {
// ...
}
}

You can insert dynamically created HTML using the triple-stache in your template.
// controller.js
dynamicHtml: Ember.computed({
get() {
return `<div>Hello World!</div>`;
}
})
...
{{! template.hbs }}
{{{dynamicHtml}}}

Related

How do I make a conditional helper with ember-cli and handlebars 2.0.0?

I am trying to achieve something similar to the form or function of:
{{#if has-permission "my_permission"}}
// do some stuff here
{{else}}
// fallback
{{/if}}
OR
{{#hasPermission session.user.permissions "my_permission"}}
I cannot figure this out. I've read this but there isn't much explaining on anything other than helpers that render content or alter it e.g. {{render "something"}}
When I try to make it a conditional I get this:
registerBoundHelper-generated helpers do not support use with Handlebars blocks.
Any help greatly appreciated, thanks!
EDIT:
My User object is serialized as such:
{"User": {
"id": 3,
"name": "john doe",
"permissions": [
"view_projects",
"edit_milestones",
"change_widgets"
]}
}
I want to be able to check a certain permission in the template, not bind a specific one to a computed property.
PS Im using ember 1.9.1, latest data and handlebars 2.0
According to the docs, this is not possible with a bound helper
Bound helpers do not support use with Handlebars blocks or the addition of child views of any kind.
What you can do is push that logic to the controller as follows:
App.IndexController = Ember.ArrayController.extend({
permission: "my_permission",
hasPermission: function(){
var permission = this.get('permission');
return permission === 'my_permission';
}.property('permission')
});
Then, in your template you can do:
<script type="text/x-handlebars" data-template-name="index">
{{#if hasPermission }}
<ul>
{{#each item in model}}
<li>{{item}}</li>
{{/each}}
</ul>
{{ else }}
// fallback
{{/if}}
</script>
Working solution here
You can make use of Ember's boundIf/unboundIf default helpers to create a nice and powerful helper to manage client side's user permissions and end up with something like this:
{{#can 'createPost'}}
<button {{action newBlogPost}}>New Post</button>
{{else}}
You don't have permission to post
{{/can}}
{{#each post in controller}}
<a {{action viewPost post href=true}}>{{post.title}}</a>
{{#can 'editPost' post}}
<button {{action editPost post}}>Edit</button>
{{/can}}
{{/each}}
If you take a look at the Ember's source code and see how if works:
Ember.Handlebars.registerHelper('if', function(context, options) {
Ember.assert("You must pass exactly one argument to the if helper", arguments.length === 2);
Ember.assert("You must pass a block to the if helper", options.fn && options.fn !== Handlebars.VM.noop);
return helpers.boundIf.call(options.contexts[0], context, options);
});
You can see that it only does some sanity checking and hands off to boundIf:
Ember.Handlebars.registerHelper('boundIf', function(property, fn) {
var context = (fn.contexts && fn.contexts[0]) || this;
var func = function(result) {
if (Ember.typeOf(result) === 'array') {
return get(result, 'length') !== 0;
} else {
return !!result;
}
};
return bind.call(context, property, fn, true, func, func);
});
This in turn calls bind which handles setting up all the observers and re-rendering when properties change. The result of the func it builds determines whether to display the content or not.
So if you create a helper which calls boundIf with some property to observe on an object, it will take care of the rest for us.
Handlebars.registerHelper('can', function(permissionName, property, options){
// do magic here
Ember.Handlebars.helpers.boundIf.call(someObject, "someProperty", options)
});
Lets fake out the magic and see what happens:
Handlebars.registerHelper('can', function(permissionName, property, options){
var permission = Ember.Object.create({
can: function(){
return true;
}.property()
});
Ember.Handlebars.helpers.boundIf.call(permission, "can", options)
});
Hmm, that leaves the content as hidden. It seems that it’s not calling the can on our permission.
If we look back at boundIf then we can see that it’s looking up the context on the options and only falls back to this if there’s not one set:
var context = (fn.contexts && fn.contexts[0]) || this;
We can get around this by nuking the contexts on the options we pass through to boundIf. (I’m not sure if this will cause issues, but it worked for me… YMMV and all that).
Handlebars.registerHelper('can', function(permissionName, property, options){
var permission = Ember.Object.create({
can: function(){
return true;
}.property()
});
// wipe out contexts so boundIf uses `this` (the permission) as the context
options.contexts = null;
Ember.Handlebars.helpers.boundIf.call(permission, "can", options)
});
If you twiddle the result of can from true to false then we see our content disappear and re-appear, success!
This example goes into more detail in this excellent post by Richard Livsey

Creating a map property and enumerating its properties in a template?

I have poured through a ton of documentation and I can't seem to find an answer to a very basic question. I have a component that needs to store a map (key/value) as a property:
App.SimpleTestComponent = Ember.Component.extend({
data: Ember.A(),
actions: {
add: function() {
this.get('data').set('test', 'value');
}
}
});
The template for the component looks like this:
<script type="text/x-handlebars" data-template-name="components/simple-test">
{{#each item in data}}
<p>
<strong>{{item.key}}:</strong>
{{ item.value}}
</p>
{{/each}}
<button {{action 'add'}}>Add</button>
</script>
However, this doesn't work. No items are displayed after clicking the button and the problem seems to be with the {{#each}} block. How do I correctly enumerate over the data property?
Ember.A() is shorthand for Ember.NativeArray. This is why your
code isn't working, you're also calling .set which is a method
inherited from Ember.Observable. So what you're really doing is
just setting an object property on the array rather than its
content.
What you probably want is Ember.Map which is an internal class
but many developers use it anyways. You will still need to return an
array of objects as the following example:
App.SimpleTestComponent = Ember.Component.extend({
// Shared map among all SimpleTestComponents
map: Ember.Map.create(),
// Or per component map
init: function() {
this.set('map', Ember.Map.create());
},
data: function() {
// this doesn't have to be locally scoped...
var arr = Ember.A();
this.get('map').forEach(function(key, value) {
arr.addObject({key: key, value: value});
});
return arr;
}.property('map'),
actions: {
add: function() {
this.get('map').set('test', 'value');
}
}
});
This doesn't really work when you have keys that have multiples
values simply becauses Ember.Map always overwrites on .set
If performance is of concern and you would like to have multiple
values per key then you will need to implement your own
map class with and a handlebars helper to display it.

Ember valueBinding to Redactor WYSIWYG

I'm using the Redactor WYSIWYG editor and it allows you to use minimal markup before initializing its code like this:
<textarea id="redactor" name="content">
…
</textarea>
However during initialization Redactor will wrap this textarea with the following content:
<div class="redactor_box">
<div class="redactor_ redactor_editor" contenteditable="true" dir="ltr">
…
</div>
<textarea id="redactor" name="content" style="display: none;">
…
</textarea>
</div>
I currently have done this in Ember
Template:
{{ view App.RedactorView valueBinding='contentAttributes.headerContent' class='header-redactor' name='headerContent' }}
View extending Ember.TextArea:
App.RedactorView = Ember.TextArea.extend({
didInsertElement: function() {
$("#"+this.elementId).redactor();
}
});
This still holds a binding to the textarea (now hidden), but I now need to bind the redactor_editor class instead. How can I do this?
After a bit of digging in the Redactor code I found out that if your element destined to be an editor is not a textarea element, Redactor will do the reverse thing and add the textarea if your a using a div instead for example.
Updated my view and tweaked it based on code from Ember.TextArea and Ember.TextSupport so it would get the correct value, this will probably work fine if you're using a contenteditable enabled element as well.
App.RedactorView = Ember.View.extend({
tagName: 'div',
init: function() {
this._super();
this.on("focusOut", this, this._elementValueDidChange);
this.on("change", this, this._elementValueDidChange);
this.on("paste", this, this._elementValueDidChange);
this.on("cut", this, this._elementValueDidChange);
this.on("input", this, this._elementValueDidChange);
},
_updateElementValue: Ember.observer(function() {
var $el, value;
value = Ember.get(this, "value");
$el = this.$().context;
if ($el && value !== $el.innerHTML) {
return $el.innerHTML = value;
}
}, "value"),
_elementValueDidChange: function() {
var $el;
$el = this.$().context;
return Ember.set(this, "value", $el.innerHTML);
},
didInsertElement: function() {
this.$().redactor();
this._updateElementValue();
}
});
Here's a JSBin demonstrating it: http://emberjs.jsbin.com/cefebepa/1/edit
I'm using Ember + Foundation, and kroofy's solution worked like a charm for me.
The height of the contentEditable were loading without paddings (were missing a paragraph), so ive changed to redactor's insertHtml method to update the value.
from:
return $el.innerHTML = value;
to:
return this.$().redactor('insertHtml', value);
thanks kroofy.
First, when you want to access the Ember View's DOM element, you should use this:
this.$()
instead of
$("#"+this.elementId)
About the Redactor issue... I am not sure how wise it is to tie up the Ember code with the WYSIWYG editor's functionality, but if you are determined about it, you can do the following:
App.RedactorView = Ember.TextArea.extend({
didInsertElement: function() {
var box = this.$().closest('.' + this.elementId + '_box');
box.find('.' + this.elementId + '_redactor_editor').redactor();
}
});

What's the best idiom for creating an EmberJS view that can show all its child-views, or just one?

Say I have a model App.Page, an ArrayController of App.Page, and an App.PageView to render each App.Page.
I'm trying to figure out how to best implement App.MyPagesView so it works like so:
if App.showAllPages is true: I want MyPagesView to contain an App.PageView(s) for displaying each of the App.Page in App.pages
Else: I want MyPagesView only show one App.PageView, bound to App.pages.currentPage.
The most straightforward implementation that occurs to me is using a template like so:
// MyPagesViewApproach1
{{#unless App.showAllPages}}
{{view App.PageView pageBinding="pages.currentPage"}}
{{else}}
{{#each pages}}
{{view App.PageView pageBinding="this"}}
{{/each}}
{{/unless}}
But won't this create new views for the existing models every time the user toggles showAllPages on and off? Also, I get emberJS warnings about performance issues when I try to use this template.
The PageView(s) could be quite complex and expensive to render. I'd really like to create a PageView once for each Page, and just remove/hide the irrelevant PageViews from the DOM when they're not in use.
App = Ember.Application.create({
showAllPages: false,
pages: Ember.ArrayController.create({
content: []
currentPage: null
}),
ready: function () {
this.pages.pushObject(App.Page.create({title: 'Page One'});
this.pages.pushObject(App.Page.create({title: 'Some Other Page'});
this.pages.pushObject(App.Page.create({title: 'Grrreatest Page Evar'});
this.pagesController.set('currentPage',
this.pagesController.get('firstObject'));
}
});
App.Page = Ember.Object.extend({
title: null
// etc, etc...
});
App.PageView = Ember.View.extend({
templateName: 'page',
page: null // should be bound to an App.Page
});
App.MyPagesView_Approach1 = Ember.View.extend({
pagesBinding: 'Elicitation.pages'
// ???
});
App.MyPagesView_Approach2 = Ember.ContainerView.extend({
// ???
});
And my HTML:
<script type="text/x-handlebars" data-template-name="page">
The title of this page is {{ page.title }}
</script>
{{view App.MyPagesView }}
To recap, what's the proper EmberJS-y way to implement MyPagesView so it responds to App.showAllPages without re-creating all the views each time its toggled?
Should it be some sort of ContainerView? Or should I use the unless/else template shown at the top of the question? Or something entirely different? I feel like a really simple solution exists in EmberJS, but its elluding me.
Here's the best I've come up with, encapsulated as a re-usable View class called "CurrentCollectionView". I'm using CollectionView, and using view.set('isVisible') to hide/show appropriate child views. Basically use it like a CollectionView, but you can set currentContent to hide all but one element of content, or use showAllContent to override currentContent.
App.CurrentCollectionView = Ember.CollectionView.extend({
showAllContent: false,
currentContent: null,
currentContentChanged: function () {
console.log("Elicitation.PagesView.currentContentChanged()");
var showAllContent = this.get('showAllContent');
if (Ember.none(showAllContent) || !showAllContent) {
var contents = this.get('content');
var currentContent = this.get('currentContent');
this.get('childViews').forEach(function (view, i) {
var isVisible = contents.objectAt(i) == currentContent;
view.set('isVisible', isVisible);
});
} else {
this.get('childViews').forEach(function (view) {
view.set('isVisible', true);
});
}
}.observes('currentContent', 'showAllContent', 'childViews')
});
An example of using CurrentCollectionView to implement MyPagesView:
App.MyPagesView = App.CurrentCollectionView.extend({
itemViewClass: App.PageView,
contentBinding: 'App.pages',
currentContentBinding: 'App.pages.currentPage',
showAllContentBinding: 'App.showAllPages',
});
or as using it inline as a template:
{{view App.CurrentCollectionView itemViewClass="App.PageView" contentBinding="App.pages" currentContentBinding="App.pages.currentPage" showAllContentBinding="App.showAllPages"}}
Hope somebody else finds this useful and/or can improve on it (please!)

Create Ember View from a jQuery object

I'm looking into integrating Ember with an existing Rails application, to take advantage of Ember's bindings, events (didInsertElement, etc.) ...
Now I don't want to transfer my erb views to handlebars, but instead I want to create Ember View objects and attach them to various elements already in the DOM. For example, I might have
<html>
<body>
<div class="header">
</div>
<div class="content">
</div>
<div class="footer">
</div>
</body>
</html>
and (on DOM ready) create a View for each element:
App.HeaderView = Ember.View.create({
// capture $('.header') to this
// console.log(this.$().attr('class')) should then output `header`
});
use appendTo() on a view: App.HeaderView.appendTo('.header') see http://jsfiddle.net/yFke9/
UPDATE
I think this is currently not possible. Please proof me wrong! You could create a workaround for this, although this is a hack, see http://jsfiddle.net/jFTk5/. The workaround basically adds the view via append() and inside the didInsertElement callback it replaces the specific element via jQuery's replaceWith.
App.HeaderView = Ember.View.create({
template: Ember.Handlebars.compile('hello from HeaderView'),
classNames: ['header'],
didInsertElement: function() {
Ember.$('.header').replaceWith(this.$());
}
}).append();
If you're going with this solution you could write a Mixin which handles this for you, see http://jsfiddle.net/KFcgA/.
App.ReplaceWith = Ember.Mixin.create({
didInsertElement: function(){
var el = this.get('elementToReplace');
Ember.$(el).replaceWith(this.$());
}
});
App.HeaderView = Ember.View.create(App.ReplaceWith, {
template: Ember.Handlebars.compile('Hello from HeaderView'),
classNames: ['header'],
elementToReplace: '.header'
}).append();
Ok the following works but I haven't fully tested it.
Inspired by #pangratz's pull request I extend Ember.View with the following method for
Ember.View = Ember.Object.extend(
/** #scope Ember.View.prototype */ {
// ........
wrap: function(target) {
this._insertElementLater(function() {
// Set all attributes name/values from target
var target_attrs = {};
var $this = this.$();
for (var attr, i=0, attrs=$(target)[0].attributes, l=attrs.length; i<l; i++){
attr = attrs.item(i)
var attrName = attr.nodeName;
var attrValue = attr.nodeValue;
if(attrName === 'id') continue;
$this.attr(attrName, attrValue);
}
// Set HTML from target
$this.html($(target).html());
Ember.$(target).replaceWith($this);
});
return this;
},
// ........
});
Basically it copies the html content of the target element as well as its attributes. Then by just doing
App.HeaderView = Ember.View.create().wrap('.header');
the .header element (that is already in the DOM) is now in App.HeaderView.
See http://jsfiddle.net/KFcgA/4/