Appending Ember Component to a DOM element not managed by Ember - ember.js

I would like to append an Ember Component ComponentB to a DOM element which is generated by some non-Ember UI library on didInsertElement of ComponentA, resulting in something like
<div class='ember-view component-a'>
<div class='i-know-nothing-of-ember'>
<div class='ember-view component-b'></div>
</div>
</div>
I am aware of appendTo(element) method, however it fails with assertion
You cannot append to an existing Ember.View. Consider using Ember.ContainerView instead.
I also tried calling createElement on component B and then appending it to DOM via jQuery - which kind of works, however in the end it fails with error
Cannot set property '_elementInserted' of null
See http://emberjs.jsbin.com/cofebo/2/
What is the proper way to achieve the above; if possible actions and other behaviours should be as if i-know-nothing-of-ember would be generated by Component A template.

I suggest using the container to lookup the component and append it anywhere whenever you need to.
Approach 1 - retrieve container within route
http://emberjs.jsbin.com/libipazavu/1/edit?html,js,output
js
App = Ember.Application.create();
App.IndexRoute = Em.Route.extend({
setupController:function(controller,model){
controller.set("container",this.container);
}
});
App.IndexView = Em.View.extend({
appendNonEmberUILibrary:function(){
callNonEmberUILibrary();
var componentB = this.get("controller.container").lookup("component:component-b");
componentB.appendTo(".non-ember-ui");
}.on("didInsertElement")
});
App.ComponentBComponent = Em.Component.extend({
layoutName:"components/component-b",
prop1:"test-option-1"
});
function callNonEmberUILibrary(){
$("body").append("<div class='non-ember-ui' style='border:1px solid;'>element from non-ember ui lib</div>");
}
hbs
<script type="text/x-handlebars">
<h2>Welcome to Ember.js</h2>
<h3>Adding Ember Component to non-Ember DOM Element using <u><i>container</i></u></h3>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="components/component-b">
<br/>
<div style="border:1px dashed #F5664D">
This is componentB -> {{prop1}}
</div>
<br/>
</script>
Approach 2 - retrieve container within an initializer
http://emberjs.jsbin.com/hijedowacu/1/edit?html,js,output
js
App = Ember.Application.create();
App.initializer({
name:"main",
initialize:function(container,application){
application.register('main:compB', container.lookup("component:component-b"), { instantiate: false });
application.inject("controller:index","theComponentB","main:compB");
}
});
App.IndexView = Em.View.extend({
appendNonEmberUILibrary:function(){
callNonEmberUILibrary();
var componentB = this.get("controller.theComponentB");
componentB.appendTo(".non-ember-ui");
}.on("didInsertElement")
});
App.ComponentBComponent = Em.Component.extend({
layoutName:"components/component-b",
prop1:"test-option-1"
});
function callNonEmberUILibrary(){
$("body").append("<div class='non-ember-ui' style='border:1px solid;'>element from non-ember ui lib</div>");
}
hbs
<script type="text/x-handlebars">
<h2>Welcome to Ember.js</h2>
<h3>Adding Ember Component to non-Ember DOM Element using <u><i>container</i></u></h3>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="components/component-b">
<br/>
<div style="border:1px dashed #F5664D">
This is componentB -> {{prop1}}
</div>
<br/>
</script>
Approach3 - reallocate added component with jquery (update from comments)
Alternatively you could just add the component-b in the template as non visible and within didInsertElement you could reallocate and display it wherever required using jquery.
example http://jsbin.com/maginovexa/1/edit?html,js,output
js
App.IndexView = Em.View.extend({
prop2:"prop-from-index-view",
appendNonEmberUILibrary:function(){
callNonEmberUILibrary();
//var componentB = this.get("controller.container").lookup("component:component-b");
//componentB.appendTo(".non-ember-ui");
var componentBDOM = this.get("componentB").$().detach();
$(".non-ember-ui").append(componentBDOM);
}.on("didInsertElement"),
click:function(){this.set("prop2",Date.now());}
});
App.ComponentBComponent = Em.Component.extend({
layoutName:"components/component-b",
prop1:"test-option-1"
});
function callNonEmberUILibrary(){
$(".inner").append("<div class='non-ember-ui' style='border:1px solid;'>element from non-ember ui lib</div>");
}
hbs
<script type="text/x-handlebars">
<h2>Welcome to Ember.js</h2>
<h3>Adding Ember Component to non-Ember DOM Element using <u><i>container</i></u></h3>
<div class='inner'></div>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
this is index (click here to change prop2 of component)
<div style="display:none">
{{component-b prop2=view.prop2 viewName="componentB"}}
</div>
</script>
....
This is a fully working solution for reallocating an ember controlled element to a non-ember element that is within an ember view, as requested by Simon Jesenko (the op).

Have you tried the run loop? Like this,
//after comp.createElement();
parent = this;
Ember.run.schedule('afterRender', this, function() {
parent.$('.i-know-nothing-of-ember').append($(comp.element));
});

Related

Why is itemController not set in the child view?

I want App.IndexRowController to be the controller for the three row views. Instead Ember sets them to plain Objects. I believe I'm properly setting itemController in the DataIndexController. I a version of this code without the nested route working as expected. Do I need to do something special when working with nested routes/needs?
JSBin: http://jsbin.com/sazafi/edit?html,css,js,output
To see the behavior go to #/data/index. Notice there are three li elements but no corresponding text (from getName). The getName controller property isn't accessible from the row template. Ember docs say that setting the itemController in the ArrayController should make that controller available to the template specified in itemViewClass. Take a look at the Ember Inspector and see that the controller for the three views is an Ember.Object, not App.IndexRowController.
JavaScript:
App = Ember.Application.create();
App.Router.map(function() {
this.resource("data", function() {
this.route("index")
});
});
App.DataIndexRoute = Ember.Route.extend({
model: function() {
return(
[
Ember.Object.create({name: 'row 1'}),
Ember.Object.create({name: 'row 2'}),
Ember.Object.create({name: 'row 3'})
]);
}
});
App.DataController = Ember.ArrayController.extend({
filter: ''
});
App.DataIndexController = Ember.ArrayController.extend({
needs: ['data'],
itemController: 'indexRow',
filter: Ember.computed.alias("controllers.data.filter"),
filteredContent: function(){
var filter = this.get('filter');
var list = this.get('arrangedContent');
return list.filter(function(item) {
return item.get('name').match(filter);
});
}.property('content', 'filter')
});
App.IndexRowController = Ember.ObjectController.extend({
// This method isn't accessible from the row template
getName: function() {
return(this.get('content').get('name'));
}.property()
});
App.DataIndexView = Ember.CollectionView.extend({
tagName: 'ul',
content: function(){
return this.get('controller.filteredContent')
}.property('controller.filteredContent'),
itemViewClass: Ember.View.extend({
controllerBinding: 'content',
templateName: 'row'
})
});
HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>Collection View</title>
<script src="http://code.jquery.com/jquery-2.0.2.js"></script>
<script src="http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars-v1.1.2.js"> </script>
<script src="http://builds.emberjs.com/release/ember.js"></script>
<script src="app.js"></script>
</head>
<body>
<script type="text/x-handlebars" data-template-name="application">
<h1>CollectionView With Item View</h1>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="data">
{{input type="text" placeholder='row 1' value=filter}}
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="row">
{{getName}}
</script>
</body>
</html>
EDIT: I have a working example of how to set the controller in a child view of a Ember.ContainerView and how to filter the contents here: https://github.com/mkolenda/ember-listview-with-filtering. ListView is a descendent of ContainerView.
Simple solution is to use an {{each}} instead of a CollectionView.
This is a well-known "feature", aka design bug, in the Ember design for array controllers, item controllers, and collection views. It shouldn't be too hard to find references on the web about the problem and some suggested hacks/workarounds. You might start with https://github.com/emberjs/ember.js/issues/4137 or https://github.com/emberjs/ember.js/issues/5267.

Accessing a single record from a simple model

I'm starting simple, trying to display a single value from a simple model.
This answer to "accessing the model from the template" suggests that it's necessary to extend ObjectController. At this point, there's have no application logic, so it doesn't seem like a controller (or a view) is really needed yet.
Likewise, there are no routes yet, so it doesn't seem like anything should be needed beyond App.IndexRoute.
The single object in the dictionary fixture has a title property with the value Hello Ember. I'm expecting to see that text displayed between two hard-coded arrows. Instead, all I get is the arrows.
The Index.html is:
<!DOCTYPE html>
<html>
<head>
<title>Dictionary</title>
</head>
<body>
<!-- Main body of the application -->
<script type="text/x-handlebars">
<p>Title: -->{{title}}<--</p>
</script>
<!-- ... Ember.js and other JavaScript dependencies ... -->
<script src="js/libs/jquery-1.10.2.min.js"></script>
<script src="js/libs/handlebars-1.0.0.js"></script>
<script src="js/libs/ember.js"></script>
<script src="js/libs/ember-data.js"></script>
<script src="js/app/application.js"></script>
<script src="js/routers/router.js"></script>
<script src="js/models/dictionary_model.js"></script>
<script src="js/controllers/dictionary_controller.js"></script>
</body>
</html>
And then the JavaScript:
// application.js
window.App = Ember.Application.create();
App.ApplicationAdapter = DS.FixtureAdapter.extend();
// router.js
App.Router.map(function() {
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.find('dictionary', 0);
}
});
// dictionary_model.js
App.Dictionary = DS.Model.extend({
title: DS.attr("string")
});
App.Dictionary.FIXTURES = [
{
id: 0,
title: "Hello Ember"
}];
// dictionary_controller.js
App.DictionaryController = Ember.ObjectController.extend({
});
I'm not sure where you're reading in the documentation that's contradicting, please update your question with the contradicting statements so they can be fixed.
The controller really only need be defined if you need to add additional computed properties, actions, or other methods. In your case you are correct in that it needn't be defined.
That being said, the application template (or unnamed template as in your case) is the root of your ember app. Any child routes/resources will be rendered in the {{outlet}} located in the application template(examples below).
The index route is a route underneath the application route. Resources are considered routes that can have children and generally associated with a model.
All this comes up to the main problem you're seeing. You've returned your model from the index route, but you are attempting to use it in the application route's template.
Here's a simplified version of your code
Code
App = Ember.Application.create();
App.ApplicationAdapter= DS.FixtureAdapter;
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.find('dictionary', 0);
}
});
App.Dictionary = DS.Model.extend({
title: DS.attr("string")
});
App.Dictionary.FIXTURES = [
{
id: 0,
title: "Hello Ember"
}];
Templates
<script type="text/x-handlebars">
<h2>Application Template</h2>
Here we Are in the Application Template
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
<h2>Index Template</h2>
{{title}}
</script>
Example in action
http://emberjs.jsbin.com/OxIDiVU/443/edit

Ember: controller properties not available in partial views - what am I doing wrong?

See JSFiddle: http://jsfiddle.net/cyclomarc/aYmuJ/3/
I set a property in the application controller and want to display this property in a partial view. This does not seem to work. I can access the property in the template itself, but not in the partial view rendered within the template ..
index.html
<script type="text/x-handlebars">
<h3>Ember access to controller properties</h3>
{{#linkTo 'about'}}About{{/linkTo}} <br><br>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="about">
Access to property in index template: <br>
<b>{{controllers.application.applicationVersion}}</b>
<br><br>
{{render "_footer"}}
</script>
<script type="text/x-handlebars" data-template-name="_footer">
Footer text (partial view) with a controller property:<br>
<b>{{controllers.application.applicationVersion}} MISSING</b>
</script>
app.js
var App = Ember.Application.create({});
App.Router.map(function () {
this.resource('about', { path: "/about" });
});
App.IndexRoute = Ember.Route.extend({
redirect: function () {
this.transitionTo('about');
}
});
App.ApplicationController = Ember.Controller.extend({
//Set some properties
applicationVersion: "1.0.0"
});
App.AboutController = Ember.Controller.extend({
needs: "application"
});
render view helper have your own context.
To use the current context in a other template use the partial view helper.
{{partial "footer"}}
When you use render a new controller is created, in that case named generated _footer controller.
Using partial will preserve the controller bound to the template that called the partial template
And since you used needs in about controller, you don't have it in the new generated controller.
Here is a sample

Emberjs 1.0.0-RC3: Can't use send to call controller method from view

Normally, we can access a method declared in the controller from the views using: this.get('controller').send('method');. In this simple jsfiddle, that is failing with: Uncaught TypeError: Cannot read property 'send' of undefined.
Here is the entire code in the jsfiddle.
App = Ember.Application.create();
App.YesController = Ember.ArrayController.extend({
cow: function(){
console.log('say mooooo') ;
}
});
App.YesView = Ember.View.extend({
templateName: 'yes',
um: function(){
this.get('controller').send('cow');
}
});
The entire view:
<script type='text/x-handlebars' data-template-name='application'>
{{render yes}}
{{outlet}}
</script>
<script type='text/x-handlebars' data-template-name='yes'>
<h1> Can't use send to call controller method from view</h1>
<button {{action 'um' target='view.content'}}> can't call controller from view</button>
<button {{action 'cow'}}> controller action works</button>
</script>
As far as I know it's possible to call the cow() method in your controller like this:
App.YesView = Ember.View.extend({
templateName: 'yes',
um: function() {
this.get('controller').cow();
//this.controller.cow() // <-- this should work also
}
})
Try target=view rather that target=view.content, otherwise you're trying to call send on the model, which is undefined.

Using the new Ember RouterV2, how do you immediately redirect to another state from the index state?

What I have so far:
App = Ember.Application.create({
LOG_TRANSITIONS: true
});
App.Router.map(function(match){
match('/').to('application');
match('/edit').to('edit');
});
App.ApplicationRoute = Ember.Route.extend({
redirect: function() {
this.transitionTo('edit');
},
events: {
startEdit: function( context ){
this.transitionTo( 'edit' );
}
}
})
App.EditRoute = Ember.Route.extend({
init: function(){
this._super()
console.log('EditRoute')
},
});
Handlebars:
<script type="text/x-handlebars" data-template-name = 'application'>
Hello World
{{ outlet main }}
</script>
<script type="text/x-handlebars" data-template-name = 'edit'>
<div class = 'edit-background'> Edit State: {{ title }} </div>
</script>
I have four questions:
When I open the application it just remains in the home page, is the redirectTo hook suppose to immediately redirect you to another state?
In addition, I have this events hash in AplicationRoute per suggestion from here: How to programmatically transition between routes using Ember.js' new Router. but I read through the answers and still am not sure how you are supposed to use it.
How do I test the router on the console? before you could navigate between the states by calling transitionTo commands, what do I do now?
For some odd reason, my application template seem to rendered twice, as in there are two 'Hello World' up there, and when try to add something like: <li>{{#linkTo edit}}edit{{/linkTo}}</li>
I get this error:
'Uncaught TypeError: Cannot read property 'container' of undefined -- ember.js:2223'
This is how you would initially load the editView/route/template on application start up:
Router
App.Router.map(function(match){
match('/').to('application',function(match){
match('/').to('edit')
})
})
ApplicationTemplate
<script type="text/x-handlebars" data-template-name="application">
{{outlet}}
</script>
EditTemplate
<script type="text/x-handlebars" data-template-name="edit">
I am embedded!
</script>
EditRoute
EditRoute = Ember.Route.extend({
renderTemplates:function () {
this.render('edit', {
into:'application'
});
})