ContainerView.pushObject() does not automatically wire-up dynamically added views with a Container object.
The lack of an auto-wired container causes a render failure when a view renders a template that contains a handlebars render helper.
SIMPLE CASE THAT WORKS (KIND OF)
View:
App.DynamicView = Em.View.extend({
templateName: 'dynamic',
didInsertElement: function() {
var control = this.get('controller');
control.send( 'view_inserted', this.templateName, control._debugContainerKey);
control.send('callDynamicController');
}
});
Template:
<script type="text/x-handlebars" data-template-name="dynamic">
dynamic
</script>
Controller (only used when manually assigned):
App.DynamicController = Em.ObjectController.extend({
className: 'App.DynamicWithRenderController',
callDynamicController: function() {
console.log('DynamicController.callDynamicController()');
}
});
Index Controller:
App.IndexController = Em.ObjectController.extend({
view_inserted: function(aview, acontroller) {
console.log('view inserted!', aview, acontroller);
}
})
Instantiation code:
var acontainer = App.DynamicController.create({});
var aview = App.DynamicView.create({ controller: acontroller })
acontainerView.pushObject(aview);
These classes render & behave as expected, but if you interogate them, lack some of the Ember-wiring (e.g. no _debugContainerKey & container properties IIRC):
MORE ADVANCED CASE THAT BREAKS
If we introduce a handlebars template that uses rendering helpers, it breaks rendering. The dynamically added view currently lacks some properties the rendering helper assumes
<script type="text/x-handlebars" data-template-name="dynamic-with-render">
dynamic w/render:
{{render knob}}
</script>
and make knob look like this:
<script type="text/x-handlebars" data-template-name="knob">
{{render knob}}
</script>
The (failing) dynamic view instantiation code:
var acontainer = App.DynamicController.create({});
var aview = App.DynamicView.create({
controller: acontroller,
template:'dynamic-with-render' })
acontainerView.pushObject(aview);
CODE EXAMPLE
A fuller example with some notes can be seen here:
http://jsfiddle.net/AshCoolman/KyJ2U/6/embedded/result/
NOTE: My tests include a custom handlebars helper based of the control helper called controlWithVars
THE PROBLEM
It looks like I need to write something that does the Ember-wiring, in either:
the more native ContainerView (getting into the Ember guts), OR
a more de-coupled new render helper possibly inelegant)
I'm not sure how to proceed. It would be great if someone has already come up with an elegant solution, or at least could give me some helpful tips.
EDIT: So it looks like creating and assigning a container, which includes the views dependencies might be a solution. Thoughts anyone?
HELPFUL READING
https://github.com/emberjs/ember.js/issues/2108
What is the purpose of the Ember.Container
http://mcdowall.info/posts/ember-application-initializers/
The raison d'être of Ember.ContainerView is for dynamically adding and removing views, so I'm pretty confident you can do all the things you want to with it.
One thing I noticed in your examples is that you are creating your child views with View.create(attrs). It is important to use containerView.createChildView(viewClassName, attrs) to create views that get the container, parent view hierarchy, and more. See the implementation more details:
https://github.com/emberjs/ember.js/blob/master/packages/ember-views/lib/views/view.js#L2072
Related
I've started work on an e-learning delivery platform and we've chosen Angularjs (1.2.29 because we still have users on IE8). Our team is all relatively new to Angular and we're not sure what is best practise to deliver the system to brief.
The aim is to have a very component-based structure, where designers can simply edit a json file, adding named components as they require them. Behind the scenes, each component should have its own html template, js functionality file and css.
We have a working system which so far includes 'paragraph' and 'image' components. The next step was to add a 'popup' component that has some interactive functionality.
The problem I can already see forming is that we're adding the component functionality into the 'pageController' in our app.js file, which I suspect is a very bad idea, not least because if we keep adding each component's functionality there, the file will become huge and unwieldy. Here's the pageController in app.js, so far:
app.controller('pageCtrl', ['$scope', '$routeParams', '$http', 'content', function($scope, $routeParams, $http, content) {
$http.get('json/page' + $routeParams.pageId + '.json')
.success(function(data) {
$scope.page = data;
});
$scope.getStyle = function(singleCase, device) {
if (singleCase == undefined)
return '';
return assignBootstrap(singleCase, device);
}
// function for new, interactive 'popup' component
$scope.showPopup = function (showOnClick) {
// presentation logic located here. This is a bad idea, right?
if ($('#'+showOnClick).hasClass('overlay')) {
$('#page_container').append($('#'+showOnClick));
}
$( '#' + $( '#' + showOnClick ).attr('data-replaces') ).remove();
$('.popup').addClass("hidden");
$('#'+showOnClick).removeClass("hidden");
}
$scope.pageId = $routeParams.pageId;
}]);
I have read and watched a lot of tutorials, and pages on the Angular site, but comprehending how to get the specific requirements of our project working with Angular is proving difficult.
This page...
https://code.angularjs.org/1.2.29/docs/guide/controller
...tells me that DOM manipulation code should be encapsulated in directives (either custom or built in, I assume).
Given that we want to end up with small .js files associated with each required component, should we instead refactor the design to use custom element (restrict: "E") directives to encapsulate the functionality?
The information I've encountered is so concept-based and abstract, it is difficult to know how the concepts should be best applied to a working project.
Is it a good use of 'element restricted' directives (effectively custom html tags) to encapsulate our individual components' code? I can imagine ending up with a list of custom html tags that define the components we need. Is that even what element directives are for?
Thanks.
The answer to your question is yes, that's the purpose of directives: inject in your HTML some reusable components in an intelligent way.
Think if you ever need to bind a variable to your "components": you'll be able to do it easily and with no pain at all, by using directives/components.
This way of using your views goes against the angular way of things:
$scope.showPopup = function (showOnClick) {
// presentation logic located here. This is a bad idea, right?
if ($('#'+showOnClick).hasClass('overlay')) {
$('#page_container').append($('#'+showOnClick));
}
$( '#' + $( '#' + showOnClick ).attr('data-replaces') ).remove();
$('.popup').addClass("hidden");
$('#'+showOnClick).removeClass("hidden");
}
because you'll end up replicate this code all over your controllers.
.
Alternatively, if you don't need any form of logic inside your "containers", you could use ng-include with templates to inject html in your pages, like this:
<div ng-include"myContainer.html"></div>
and somewhere in your html pages, include a script
<script type="text/ng-template" id="myContainer.html">
<!-- content -->
</script>
I'm trying to build a basic web application with Backbone.JS and already encounter understanding issues in the very beginning.
I was thinking about the following HTML structure:
<script type="text/template" class="t_show">FOO</script>
static foobar
<script type="text/template" class="t_show">BAR</script>
where static foobar always gets rendered - .t_show however only, when the router matches #show.
That's my current backbone code:
var v_show = Backbone.View.extend({
el: $(".client"),
template: _.template( $( '.t_show' ).html() ),
render: function() {
this.$el.html(this.template(this.model.attributes));
return this;
},
that kind of already works, however only renders the very first template-partial (FOO) and the static code (static code), but no (BAR).
Ling story short: How to realise template snippets belonging to the same view in Backbone?
jquery's .html() function will only return the html of the first matched element, so calling _.template($('.t_show').html()) will only pass FOO to the template function.
There's a couple ways you could resolve this:
If you want FOO and BAR to render right next to each other (styling aside), you can combine them into a single template. I'm guessing this doesn't work for your use case or you wouldn't have asked about supporting multiple templates, but included it for completeness.
Iterate through each .t_show and add the html() bits together, and pass the result to _.template(). This also will show FOO and BAR next to each other.
Assign each template to its own variable and then execute and render the templates separately.
That last option has the most flexibility as you can place the resulting html in different parts of your view if you wish, or you could re-render them individually which is handy if they're fairly isolated or either is kind of heavy to render. That might look something like this:
fooTemplate: _.template($('.foo.t_show').html()),
barTemplate: _.template($('.bar.t_show').html()),
render: function() {
this.$el.html(this.fooTemplate(this.model.attributes));
this.$el.append(this.barTemplate(this.model.attributes));
return this;
},
...
I'm trying to display a string, pulled from my model, that contains ember custom components. They don't seem to get compiled though -- see (1) and (2) in the output. If I replace the custom components with standard html elements and use the {{{-}}} syntax for binding, things look right (see (3) and (4) in the output), but this is not sufficient for the application I have in mind, though. How can I get ember to compile the custom components before displaying them?
app.js:
App = Ember.Application.create();
var g1 = "{{#my-bold}}Yo{{/my-bold}}, {{#my-italic}}dude{{/my-italic}}!";
var g2 = "<b>Yo</b>, <i>dude</i>!";
App.IndexRoute = Ember.Route.extend({
model: function() {
return {greeting1: g1, greeting2: g2}
}
});
App.MyBoldComponent = Ember.Component.extend({tagName: "span"});
App.MyItalicComponent = Ember.Component.extend({tagName: "span"});
index.html
<script type="text/x-handlebars">
{{outlet}}
</script>
<script type="text/x-handlebars" id="components/my-bold"><b>{{yield}}</b></script>
<script type="text/x-handlebars" id="components/my-italic"><i>{{yield}}</i></script>
<script type="text/x-handlebars" id="index">
<ol>
<li>{{model.greeting1}}</li>
<li>{{{model.greeting1}}}</li>
<li>{{{model.greeting2}}}</li>
<li>{{#my-bold}}Yo,{{/my-bold}} {{#my-italic}}dude!{{/my-italic}}</li>
</ol>
</script>
output:
{{#my-bold}}Yo{{/my-bold}}, {{#my-italic}}dude{{/my-italic}}!
{{#my-bold}}Yo{{/my-bold}}, {{#my-italic}}dude{{/my-italic}}!
Yo, dude!
Yo, dude!
From the Ember.js issue tracker https://github.com/emberjs/ember.js/issues/11649 on dynamically inserting components:
This isn't something we support, I also suspect we won't as it would require all of the htmlbars compiler client side and would likely be pretty slow. If you wish to add components dynamically. The component helper may be your best bet.
On that issue they are proposing to use the {{component}} helper, but that doesn't work with your code since you actually want to produce two component.
They are also talking about the RFC for contextual components: https://github.com/emberjs/rfcs/pull/64#issuecomment-111761176 which depending on their implementation would work similar to what you're doing. So what can you do?
As far as I can tell it's simply not possible to do with 1.13 (I tried quite a number of things) because rerender doesn't work, but they are going to fix it. If you downgrade to 1.12 you can do:
App.RenderTemplateComponent = Ember.Component.extend({
layout: function(){
return Ember.Handlebars.compile(this.get('templateString'));
}.property('templateString')
});
Then in your template something like:
{{render-template templateString="test {{x-foo}} {{x-foo}}"}}
Take a look at this JSFiddle:
http://emberjs.jsbin.com/jazayiyufi/1/edit?html,css,js,output
this is my first post in stackoverflow.
I'm new to ember.js and i've been studying ember.js.
I'm building a small app to learn ember.js now,but binding doesn't work somehow.
Please give me a help!!
#------------------------Controller------------------------
App.ApplicationController = Ember.Controller.extend();
App.monstersController = Ember.ArrayProxy.create({
content:[],
//some code to add model instances to content...
counter: function(){
var content = this.get('content');
return content.get('length');
}.property('length')
});
#------------------------View------------------------
App.StatsView = Ember.View.extend({
counterBinding : 'App.monstersController.counter',
#------------------------HTML------------------------
<script type="text/x-handlebars" data-template-name="application">
//some code here
{{#view App.StatsView}}Counter: {{counter}}{{/view}}
//I'm expecting the length of content array in App.monstersController above.
//some code here
</script>
If you want to access a value off of a view you need to access it via {{view.counter}}.
However, in your case you should probably be setting the controller on the view like:
App.StatsView = Ember.View.extend({
controllerBinding : 'App.monstersController'
})
Once you get the hang of that, I'd look into setting up a router to manage binding controllers and views together.
As you'll be able to tell from my question, I'm slowly learning EmberJS. I've read the great guide on routes and I felt ready to take on the world but then...
In my example, I thought the {{somethingView}} would be rendered and not the controller property {{somethingCtrl}}. Is this the correct behaviour? If so how would you render a property from the Ember.View?
The JS
window.App = Ember.Application.create({
ready: function() {
this.initialize();
}
});
window.App.Router = Ember.Router.extend({
root: Ember.Route.extend({
index: Ember.Route.extend({
route: '/',
connectOutlets: function(router) {
var controller = router.get('applicationController');
controller.connectOutlet('garments');
}
})
})
})
window.App.ApplicationView = Ember.View.extend({
templateName: 'application',
});
window.App.ApplicationController = Ember.Controller.extend();
window.App.GarmentsController = Ember.Controller.extend({
somethingCtrl: "Something in the controller"
});
window.App.GarmentsView = Ember.View.extend({
templateName: 'garments',
somethingView: "Something in the view"
});
The DOM stuff
<script type="text/x-handlebars" data-template-name="application">
<h1>Hi Ember</h1>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="garments">
<h1>Garments</h1>
{{somethingView}}<br>
{{somethingCtrl}}
</script>
The Fiddle
This behaviour is correct. These are my understandings of these ember core concepts:
Model: These objects represent the date that is handled by your application. These are the business objects that form the domain model of your Application.
Controller: A Controller is responsible for providing access to your models. Controllers have the property content, where models should be injected (a single Object for Ember.Controller and an array of objects for Ember.ArrayController). The Controller passes this content to your View. The Controller is the default context for your view. Therefore the behaviour you describe is expected.
View: The View is just intended for displaying issues. I personally use it mainly to do jQuery animations.
But nonetheless it is possible to access the view instance in the template. You just have to use the variable with the name 'view' in your template. I updated your fiddle with a working example: http://jsfiddle.net/jPK8A/5/
<script type="text/x-handlebars" data-template-name="garments">
<h1>Garments</h1>
{{view.somethingView}}<br>
{{somethingCtrl}}
</script>
But to be clear: The most common case should be to access contents from your controller. It should be not often that you access variables of your view. You want to display date in your App and this date resides in models and should therefore be accessed through controllers. The most likely case might be, that you want to store labels in your view or something like that (labels that have to be computed).