I have a model which defines a property with either markdown or html content.
I am wondering whether using a markdown JS library to output the info or use handlebars to generate the html output inside the view.
Any recommendations, examples will be appreciated.
Using a Markdown converter worked for me.
Here is my view code:
App.ActivityDetailsView = Em.View.extend(
templateName : 'activity-details',
classNames : ['details rounded shadow'],
rawDescriptionBinding: 'App.activityDetailsController.description',
description: (->
converter = new Markdown.Converter().makeHtml
return converter(#rawDescription)
).property('rawDescription')
)
Here is the template code (note the triple handlebars {{{}}} for raw html):
<script type="text/x-handlebars" data-template-name="activity-details">
{{{description}}}
</script>
Here is a link to more details and the showdown.js script
Ember recommends that you use your controller to decorate your model. Given this model, we want to render each of these blog posts using the appropriate rendering engine:
[
{ id: 1, isMD: false, md_or_html: "<p>This is HTML.</p>" },
{ id: 2, isMD: true, md_or_html: "*This is MD.*" }
]
You'll start by creating a route which returns that model:
App.IndexRoute = Ember.Route.extend({
model: function() {
return [
{ id: 1, isMD: false, md_or_html: "<p>This is HTML.</p>" },
{ id: 2, isMD: true, md_or_html: "*This is MD.*" }
];
}
});
Just having the model returned doesn't mean that things get rendered. You also need to make sure the template for the index route attempts to put something on the page:
<script type="text/x-handlebars" data-template-name="index">
<ul>
{{#each}}
<li>{{output}}</li>
{{/each}}
</ul>
</script>
You'll note that we haven't yet created an output property, though we've included it in our template. We need to decorate our model to add the processed HTML or Markdown output:
App.IndexController = Ember.ArrayController.extend({
itemController: 'post'
});
App.PostController = Ember.ObjectController.extend({
output: function() {
var result;
if (this.get('isMD')) {
var converter = new Markdown.Converter();
result = converter.makeHtml(this.get('md_or_html'));
} else {
result = this.get('md_or_html');
}
/*
IMPORTANT!!! Ember automatically escapes HTML upon insertion.
To actually embed the result as HTML you will need tell Ember
that the value is safe to embed as HTML.
DO NOT RETURN SafeStrings UNLESS THE VALUE IS TRUSTED AND SANITIZED!
*/
return new Handlebars.SafeString(result);
}.property('isMD', 'md_or_html')
});
We can't just add the output property to PostController and have everything work without telling IndexController to use PostController for each item in the model. This is accomplished by setting itemController on IndexController (think: "what controller to use for each item"). This allows us to decorate each blog post individually with the output property. We use a computed property to tell Ember that the value of output is dependent upon whether or not the post isMD and the body of the post. If either changes we want Ember to re-render the output.
The complete example includes additional comments and details about how to extend the pattern for introspection into the post body to determine if it is HTML or MD.
I encountered a similar case that I handled with a dynamically inserted handlebars template: I have a field containing a template which may have content bound to application values.
Ember.View.create({
tagName: 'span',
classNames: ['dynamic-content'],
template: Ember.Handlebars.compile(App.preCompileTemplate(template)),
context: someContextObject
});
The App.preCompileTemplate function replaces bindings with valid handlebars expressions, but you could also imagine using Markdown here:
App.preCompileTemplate = function(template) {
return template.replace /{(.*?)}/g, '{{context.$1}}'
}
Using the context object scopes the values that you bind into the template.
Related
I have two directives, each displaying a list of documents, but in a slightly different way : One displays user-favorited documents, and the other one displays user-pinned documents. These two properties depend on two object members specified for each document, i.e. :
document = {
pinned: true,
favorite: false
};
Each directive displays a frame with its own title, according to the type of documents we want to display. For refactoring purposes, I use the same template for both, and specify varying template strings and objects in two controllers, each one dedicated to a directive. (i.e. the service member to call to get the documents we want is specified in a string, since the handling of these is exactly the same)
…Until I realized the two controllers were almost identical, the only thing that changed being… template strings.
So what I came up with is using the exact same controller and template (DocumentsPanel), but still with two directives, the only difference in them being link() :
function documentsPanelFavorites(templateService, resources) {
return {
restrict: 'A',
templateUrl: templateService.getUrl('Documents/Panel/documentsPanel.html'),
controller: 'DocumentsPanel',
scope: true,
link: link
};
function link(scope) {
//Used to show a favorite/pinned checkmark for each document entry
scope.documentOptions = {
showFavoriteCheckmark: true,
showPinnedCherkmark: false
};
scope.panelName = resources.text.MODULE_TITLE_FAVORITE_DOCUMENTS;
scope.className = 'favorites';
scope.noDocumentText = 'No favorite for this user';
// Used by the controller to know which method of the
// document dataService to call to get the listed documents
scope.documentList = 'favoriteDocuments'
// etc.
}
};
The documentsPanel.html template then uses these strings defined in the controller's scope via link().
Note: Another directive used to represent a document in a list is included in documentsPanel.html, that's why I set both showPinned and showFavorite options in each directive : It's the same directive that displays each document, and it is used elsewhere with all settings to true.
Would that be considered good practice? If not, what would be a better solution?
Thanks in advance!
I would expect documents="document | filter:{pinned:true}" and documents="document | filter:{favorite:true}"... Considering title, no document text, etc. I would first create config object and pass it to directive: config.title = '...', config.nodoctext = ... But if number of this strings params is too big, just create 2 templates.
I have a template with some nested templates in Meteor:
<template name="collectingTmpl">
{{> firstTmpl}}
{{> secondTmpl}}
</template>
If I set a reactive var/dict in firstTmpl with
Template.firstTmpl.events({
'click .class-name': function(event, template) {
template.state = new ReactiveDict;
template.state.set('someName', 'someValue');
}
});
I can get this value within the same template with
Template.firstTmpl.helpers({
myValue: function() {
Template.instance().state.get('someName');
}
});
but can I also retrieve the value being set in firstTmpl from secondTmpl?
I mean something like
Template.secondTmpl.helpers({
myValueFromAnotherTmpl: function() {
Template.firstTmpl.state.get('someName');
}
});
You could alternatively set the ReactiveDict on the parent template which is collectingTmpl.
// always initialize your template instance properties
// in an onCreated lifecycle event
Template.collectingTmpl.onCreated(function(){
this.firstTmplState = new ReactiveDict();
});
Then you can obtain references to this template instance property in child templates using this code :
Template.firstTmpl.onCreated(function(){
// warning, this line is depending on how many parent templates
// the current template has before reaching collectingTmpl
var collectingTmplInstance = this.view.parentView.templateInstance();
this.firstTmplState = collectingTmplInstance.firstTmplState;
});
Template.secondTmpl.onCreated(function(){
var collectingTmplInstance = this.view.parentView.templateInstance();
this.firstTmplState = collectingTmplInstance.firstTmplState;
});
Then you can use the standard Template.instance().firstTmplState syntax in any of your 3 templates and they'll always point to the same ReactiveVar instance defined as a property of collectingTmpl.
If you take care on a currently issue of blaze (missing data context), you are able to access other templates vars by Template.parentData().
See this MeteorPad as a demo, the background color will be changed onn each player when pressing the button. the color is defined by their parent template:
http://meteorpad.com/pad/zoiAvwuT3XXE5ruCf/Leaderboard_Template_parentData_Bug
You may also read at GitHub PullRequest about some updates I suggest
https://github.com/meteor/meteor/pull/4797
Cheers
Tom
I have a simple action that can be attached to list items in an {{#each}} loop, and when that action is triggered, it will link to that instance of the model.
This is what it looks like now
VpcYeoman.SuperTableController = Ember.ArrayController.extend({
actions: {
goTo: function(input) {
this.transitionToRoute('someModel', input);
}
}
});
The action is called on an HTML element like this
{{action 'goTo' this bubbles=false}}
You can see the problem with this in that 'goTo' cannot be reused on other models because it is specifically looking at the 'someModel' model.
Please help me make this action work for whatever the current model is
I tried replacing 'someModel' with a generic 'model' & even 'this.model' but they didn't work.
Do not reply with 'use {{#link-to}}' please. I am aware that this exists and
Before you read this, you should know that I do recommend you use the link-to helper. I normally pass a computed property to the helper when I need it to change based on the model...
I am not sure where you have that action in your code, but you could just compute that path as needed. For example, take this item controller:
App.ItemController = Ember.ObjectController.extend({
getTransitionPath: function () {
return this.get('foo') + '_bar';
},
transitionPath: function () {
return this.get('foo') + '_bar';
}.property('foo'),
actions: {
goTo: function(input) {
//this.transitionToRoute(this.getTransitionPath(), input); // Regular method
this.transitionToRoute(this.get('transitionPath'), input); // Computed property
}
}
});
I also don't know what kind of logic you are looking for inside of those methods, but this pattern should work on a per model basis.
Good luck!
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
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).