How do I create a reusable partial for duplicate markup in ember.js?
Asked Answered
J

3

8

Given this chunk of HTML:

<div id="email_field" class="control-group">
  <label class="control-label" for="account.email">Email</label>
  <div id="email_input" class="controls">
    <input id="account.email" name="account.email" type="text" placeholder="[email protected]">
    <span class="help-block">We just need a valid email address.</span>
  </div>
</div>

How do I turn this into a re-usable partial for whatever attribute I want? IE: email, password, password confirmation, etc.

I would assume some sort of view hierarchy but I'm not quite sure.

EDIT: After further exploration I've knocked out {{view}} and {{render}} and figured out exactly what I need:

I want to: 1. Use a specific view (InputView) 2. Use a specific controller (Preferably similarly named: InputController) ({{view}} doesn't do this I think) 3. Be able to use this multiple times ({{render}} can't do this) 4. Be able to pass in values ({{render}} can't do this)

Example:

<!-- templates/application.hbs -->
{{foo "input" name="Email" id="account.email" placeholder="[email protected]"}}

// controllers/input.js
Application.InputController = Ember.ObjectController.extend({
  type: "text"
});

// views/input.js
Application.InputView = Ember.View.extend({
  templateName: "form/input"
});

<!-- templates/form/input.hbs -->
<input {{bindAttr id="id" name="name" type="type" placeholder="placeholder"}}>
Joist answered 17/2, 2013 at 20:35 Comment(0)
P
3

I would create a view that takes all the parameters that are variable. Such as:

{{view App.FormEntity
    name="email"
    placeholder="My placeholder..."
    help="We just need a valid email address."
    valueBinding="value"
}}

From there you could extract the label, the various class names, and then use Ember.TextField to bind the value to.

Once you have all of those arguments passed into the view, it should be nice and easy to create the markup using a mixture of bindAttrs, a couple of computed properties, and Ember helpers (such as the Ember.TextField).

Partin answered 17/2, 2013 at 22:12 Comment(6)
Great response, thanks, but it leaves me wondering what controller does the view look for when using bindAttr? I can't seem to gather that from the docs.Joist
Well, every view should have access to a controller. By default, if you're in the IndexRoute then the index view will have access to IndexController, and if you insert a view in that index view, then that view will inherit the IndexController. Maybe this will help: #14802723Partin
Interesting, but doesn't help me I guess? I want this helper to have it's own controller. Or more specifically I have computed values that I don't want to attach to IndexController.Joist
So I've unaccepted the answer because this really only gives one piece to the puzzle, it doesn't actually solve my problem: I want to take this reusable chunk of HTML, make it dynamic, and use it in other templates.Joist
Then use something like {{render "input" property="whatever"}} and it will attach InputController to your InputView.Partin
Works for a lot of reasons, but raises an exception when used multiple times :(Joist
G
1

I am new to Emberjs and looking for pretty much the same thing, but couldn't you also simply use http://emberjs.com/guides/templates/writing-helpers/ for that? I will try it myself, so can give more updates if that works out.

Update: Ok, I got it to work. I created a new Helpers folder with FormgeneratorHelper.js and the following code:

Ember.Handlebars.registerBoundHelper('control-group', function (options) {
    var name = options.hash.test.capitalize();
    console.log(name);
    return new Handlebars.SafeString('<div class="control-group"> \
            <label class="control-label" for="input' + name + '">' + name + '</label> \
            <div class="controls"> \
                <input type="text" id="input' + name + '" placeholder="' + name + '" /> \
            </div> \
        </div>');
});

An then, no matter in which template you can do:

{{control-group test="email"}}

I really like the idea of using helpers, but if you are using plain Javascript (as opposed to CoffeScript) and have more than one line of code, then it gets a bit ugly unfortunately. But will probably still use that method.

Gehrke answered 20/2, 2013 at 15:30 Comment(1)
While that certainly seems to do what I want, that's definitely not how I want to do it. It becomes unmaintainable that way.Joist
U
1

How do I turn this into a re-usable partial for whatever attribute I want? IE: email, password, password confirmation, etc.

What you want is the experimental {{control}} helper. The control helper is currently under development and is considered experimental. To enable it, set ENV.EXPERIMENTAL_CONTROL_HELPER = true before requiring Ember.

I want to: 1. Use a specific view (InputView) 2. Use a specific controller (Preferably similarly named: InputController) ({{view}} doesn't do this I think)

Out-of-box the control helper expects to be passed a template name. That template name is used to lookup a matching view and controller. So for example:

App.InputView = Ember.View.extend()
App.InputController = Ember.Controller.extend()
{{control input}}

See:

  1. Be able to use this multiple times ({{render}} can't do this)

A control can be used multiple times

  1. Be able to pass in values ({{render}} can't do this)

Like the {{view}} helper, {{control}} will accept arbitrary name/value pairs. So as in your example, one could manually pass options to the control helper. Like the {{view}} helper these options become properties on the view instance:

<!-- templates/form/input.hbs -->
<label class="control-label" {{bindAttr for="view.inputId"}}>
  {{view.label}}
</label>
<div class="controls">
  <input {{bindAttr id="view.inputId" name="view.name" type="type" placeholder="view.placeholder"}}>
  <span class="help-block">{{view.help}}</span>
</div>

// controllers/form_input.js
App.FormInputController = Ember.ObjectController.extend({
  type: "text"
});

// views/form_input.js
App.FormInputView = Ember.View.extend({
  classNames: ["control-group"]
});

<!-- templates/application.hbs -->
{{control "form/input" 
    inputId="account.email" 
    name="email"
    label="Email" 
    placeholder="[email protected]" 
    help="We just need a valid email address."
}}

See this jsbin for working example

Also keep in mind that A control can specify a model to use in its template - with this in place we can bind properties to model data. Also if a controller's model changes, its child controllers are destroyed so the control will reset as expected if the model is swapped out.

Unstep answered 24/2, 2013 at 5:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.