How do you render a view from within a handlebars helper that takes dynamic content?


#1

We are working on a ember-bootstrap demo project at

All we want to be able to do is to have a helper call a view with the content.

{{ bslabel label}}

Bootstrap.Label = Ember.View.extend
  tagName:        "span"
  classNames:     "label"
  template:     Ember.Handlebars.compile("{{view.content}}")

Something as simple as the above. How do we get them to play nice together!?

We have looked at:

And https://github.com/dockyard/ember-easyForm/blob/master/packages/ember-easyForm/lib/helpers/inputField.js

Which was last edited 3 days ago.

We have been trying all kinds of different approaches, but no matter what, we get the error:

Uncaught You can’t use appendChild outside of the rendering process.

Looks really simple :stuck_out_tongue:

Ember.Handlebars.registerHelper('errorField', function(property, options) {
  if (this.get('errors')) {
    options.hash.property = property;
    return Ember.Handlebars.helpers.view.call(this, Ember.EasyForm.Error, options);
  }
});

This is how you it could be done 2 months ago?

Ember.Handlebars.registerBoundHelper('formFor', function(object, options) {
  return Ember.Handlebars.helpers.view.call(object, Ember.EasyForm.Form, options);
});

Passing the incoming object as the first argument to view !? Does this still work?

Where is there any updated documentation on how to create helpers that call views!?

We have been using this one, but looks outdated? http://techblog.fundinggates.com/blog/2012/08/ember-handlebars-helpers-bound-and-unbound/

Here is a sample of our attempts:

Bootstrap.Label = Ember.View.extend Bootstrap.TypeSupport,
  tagName:        "span"
  classNames:     "label"
  baseClassName:  "label"
  template:       Ember.Handlebars.compile("{{view.content}}")

Bootstrap.Label.helper = (view, options) ->
    childView = Bootstrap.Label
    currentView = options.data.view
    currentView.appendChild childView, options.hash

Ember.Handlebars.registerBoundHelper 'bslabel', (options) ->
  viewContext = options.data.view
  Bootstrap.Label.helper(this, options)
  # Ember.Handlebars.helpers.view.apply(viewContext, [Bootstrap.Label, options])


Ember.Handlebars.registerBoundHelper 'ourbslabel', (object, options) ->
  # Ember.Handlebars.helpers.view.call(object, Bootstrap.Label, options)
  Ember.Handlebars.helpers.view.call(this, Bootstrap.Label, options)

Note that the view says template: Ember.Handlebars.compile("{{view.content}}") Maybe this is the problem!?

Calling from a template (using latest Chrome)

{{bslabel content="Default"}}
{{bslabel contentBinding="controller.label" typeBinding="controller.type"}}

Note: Our problem is only the dynamic case, specifically the contentBinding for the label text.

App.LabelsController = Ember.ObjectController.extend
  label: 'Success'
  type: 'success'
  types: ['success', 'warning', 'important', 'info', 'inverse']
  select: (type) ->
    @set 'type',  type
    @set 'label', type.camelize()

We also tried this approach, setting contentBinding of the options hash explicitly before calling view:

Ember.Handlebars.registerBoundHelper 'bslabel', (property, options) ->
  console.log arguments
  options.hash.contentBinding = property
  # or even setting content!?
  # options.hash.content = property
  Ember.Handlebars.helpers.view.call(this, Bootstrap.Label, options)

{{ bslabel "Default"}}
{{ bslabel label typeBinding="type"}}

Uncaught You can’t use appendChild outside of the rendering process

What can we do to make content or contentBinding work inside the view when calling it from a helper!?

Any advice would be helpful. Thanks!


#2

It seems like the contentBinding is the problem. That content is somehow treated in a special way. Now I’m trying using a simple text binding instead, inspired by EasyForm.Hint (and EasyForm.Label):

Bootstrap.Label = Ember.View.extend Bootstrap.TypeSupport,
  tagName:        "span"
  classNames:     "label"
  baseClassName:  "label"
  init: () ->
    @_super()
    @set 'template', @renderText()
  renderText: () ->
    Ember.Handlebars.compile @.get('text')

Ember.Handlebars.registerBoundHelper 'bslabel', (property, options) ->
  console.log arguments
  options = property unless options
  Ember.Handlebars.helpers.view.call(this, Bootstrap.Label, options)

Now it looks pretty ugly:

{{bslabel textBinding="label" typeBinding="type"}}

Can I set the text from the property?

  options.hash.textBinding = property
  Ember.Handlebars.helpers.view.call(this, Bootstrap.Label, options)

So that I can do this:

{{bslabel label typeBinding="type"}}

Still nothing works. Now I get:

Uncaught TypeError: Cannot read property ‘constructor’ of undefined

It is all a big mystery how this is supposed to work!! :confused: Needs much better documentation than the simple examples I have seen so far. Hope someone out there can help out. Have spent 4 hrs this morning trying all that I can think of.


#3

This will work:

App.Label = Ember.View.extend({
  tagName: "span",
  classNames: "label",
  template: Ember.Handlebars.compile("{{view.content}}")
});

Ember.Handlebars.registerHelper('bslabel', function(content, options) {
  options.hash.content = content;
  return Ember.Handlebars.helpers.view.call(this, App.Label, options);
});

registerHelper takes each argument given to the view helper and a final options object. If you use Ember.Handlebars.helpers.view.call like a lot of the internal views in Ember, options.hash will be the properties given to that view. So in this case we take the content given as the second argument to our {{bslabel}} helper, and set it as the content property of the view.

You need to put the content in quotes for this implementation to work:

{{bslabel "Label content goes here"}}

Use custom helpers in Handlebars which are loaded from an api
#4

Awesome! Thanks Sebastian! You made my day :slight_smile: Now I can sleep “counting sheep”… and not code :stuck_out_tongue:

I think I tried the contentBinding approach before, but I couldn’t make it work.

Ember.Handlebars.registerHelper('bslabel', function(content, options) {
  options.hash.contentBinding = content;
  return Ember.Handlebars.helpers.view.call(this, App.Label, options);
});

How can I make it flexible so it works for both static content and contentBinding then? or is there some other syntax or approach that would provide “dynamic content” (bound content) for content?


#5

The way I did it in the above example is not really a binding. Take a look at how Ember.Handlebars.registerBoundHelper works, if you want to use bound content.

What’s wrong with:

<span class="label">{{dynamicContent}}</label>
<span class="label">Static content</label>

? :slight_smile:


#6

I’m wondering however if you have to add custom logic in order to make any handlebars helpers to work for both static and dynamic content binding. It looks to me like you have to do sth like:

# psudo-code!!
function myhelper (content, options)
  if incoming content is static (a simple String)
    options.hash.content = incoming content
  if incoming content is dynamic (a binding)
    options.hash.contentBinding = incoming content (binding)

Is this really the case? I would think this is a very common scenario, hence there should be an easier way to apply this common pattern (or am I missing sth?)