Combining Component Logic and Template together in single file?


#1

This may ultimately be a build tool question.

But in many cases I think there is really great conceptual advantage to visualize a component as a standalone, self contained thing. To further promote this concept it might be really handy to have a way to group component logic and template code into a single file, especially if the logic and template are not that complex.

I was wondering if anyone have thoughts on how this could actually be done.

Here is a concrete example:

Imagine all this code in single file called

my-widget.component.js

App.MyWidgetComponent = Ember.Component.extend({
    someproperty: null,

    actions: {
        doSomething: function(input) {
            // Send action to external scope
            this.sendAction('action', input);
        }       
    },

    setupDefaults: function(){
        // implement setup bootstrapping when added to DOM
    }.on("didInsertElement")

});

// Handlebars Template Code

 <script type="text/x-handlebars" data-template-name="components/my-widget">
    <h2>My Widget</h2>
    {{someproperty}}
    <button {{action "doSomething"}}>Do Something</button>
 </script>  


/* 
* Handlebars Usage
  {{my-widget action="externalFunction" someproperty="externalValue"}}
*/

The problem is that I would like to do the above but express the template code as pure Javascript and not have to resort to some hacky use of eval.

I would even be fine if there was a asset-pipeline type convention where we pre process the file and split apart the template and logic. Using some block notation for the Handlebars segment.

Perhaps use a special file name suffix for the pre-processor.

my-component.js.embercomponent

Would be awesome if we could get the same pattern to be supported in both ember rails and grunt based ember app kit.

Thoughts?


#2

This would be much easier to do with CoffeeScript and by extension EmberScript.


#3

Wouldn’t it be better to keep the source files split but have a build task that took the .hbs file, precompiled it, and inserted the contents into the template property in the output component js file? There’s a few instances in ember where we precompile templates, e.g. for Ember.Select, only difference being that the whole template is hardwired into a single line of the file, whereas your build tools would pull the hbs file from another file.

I’m definitely curious to figure out the best way to solve this… that and possibly bundling component-specific CSS into some easily shareable/redistributable format. Lemme know how it goes.


#4

Instead of investing a file format that goes against best practices, we could consider creating a packaging mechanism that would make it easy for us to distribute and reuse components,

For example, if we consider Bower as the package manager, then we could add “ember-component” configuration information in bower.json.

Here is an example of a bower.json that would be included in the component’s repository.

{
  "name": "clock-widget",
  "version": "0.0.1",
  "authors": [
    "Taras Mankovski <tarasm@gmail.com>"
  ],
  "description": "Allows to show a stylized close on a page",
  "main": "index.js",
  "keywords": [
    "ember-component"
  ],
  "license": "MIT",
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "app/bower_components",
    "test",
    "tests"
  ],
  "ember-component": {
    "name": "clock",
    "css": "index.css",
    "less": "index.less",
    "template": "index.hbs"
  }
}

Then we could use bower to distribute the plugin and we could have a Grunt task that would include the component appropriately. What do you think something like this?


#5

I guess my thought about the use case here is that there is some convenience in having it bundled as a single conceptual thing. Easy to see the entire thing as a whole, easy to edit all together, easy to share informally as a gist, text editor snippet, forum post, cut and paste, modify, extend, customize.

Bower does seem like a good way to package this. But of course not everyone uses bower. Some of us are still in ruby/rails work flow. :smile:

I feel components are really a great idea, but there is fair amount of conceptual overhead that might be a stumbling block for some users or inhibit the share ability of these things.

  • a component name must be hyphenated
  • a component template must be in “components/” name space
  • a component must end with “Component” as its name
  • etc

The build tools can deal with the process of parsing, naming, and separating (or munging together) the constituent parts. That is what computers are good for. Why we are forcing ourselves to jump through a lot of hoops to make life easier for the compiler and the framework I don’t understand.

Don’t get me wrong, I am not saying that it is not a best practice to have these as separate files, and certainly there is legitimate reasons for the naming conventions and namespace requirements.

But my point is that it would be very convenient for a lot of users to be able to have everything together that defines what a component is.

  • Easy to visualize as a whole
  • Can be edited all in one place
  • Easy to copy and paste and share

Imagine if something like below was all someone had to paste into a forum or gist and share their component. Obviously discounting for the moment that this is a lot of pseudo code. Some syntax and delimiters would need to be worked out. And I am obviously abusing the yuidoc syntax.

Perhaps you could call this an “implementation” file. Almost analogous to the .m files you find in Objective C. But my example is overloaded with a few more things (spec/test, style, template, etc).

NOTE: This is not real functioning code, just an example

/* Do Something Button a simple Ember Component Example */

/* @spec */

    /**
      A simple component example, a button that is bound to a doSomething method

      @method doSomething
      @param {String} input A value to modify.

      @property label {string} a label for your button
      @property item {object} an object to manipulate

      @handlebars-usage
      {{ dosomething-button  
         actionDoSomething="externalFunction" 
         label="my button" 
         item="someObject"
      }}

    */

    // Perhaps insert some unit testing code as part of the spec



/* @style */

    .dosomething-btn {
        background-color: red;
        color: white;
    }

/* @template */
    <button class="dosomething-btn" {{action 'doSomething' item }}>{{label}}</button>

/* @logic */
var DosomethingButton = Ember.Component.extend({
    label: null,
    item: null,

    actions: {
        doSomething: function(input) {
            // create side effect and send to outer scope
            input.didsomething = true;
            // Send action to external scope
            this.sendAction('actionDoSomething', input);
        }       
    }       
});

/* @module-export */
export default DosomethingButtonComponent;

#6

This kind of things is problematic because no parser will be able to highlight this code. It might take some time for parsers to start supporting it but by that point progress might make this unnecessary. It might be easier to create a tool that will documentation page from a package and show code in this way. We might even be able to allow people to edit these and save them on GitHub.

I’m not familiar with Ruby/Rails, how do you manage JavaScript packages there now?


#7

Right now I am using the ember-rails gem which packages and minifies everything together. It uses the rails asset pipeline and compiles down handlebars templates. As far as external javascript packages I include those in the vendor folder and manually include them. In some cases there is a gem file that includes everything for me.

Everything is separate files by convention.

In reality a lot of the details about the build tool are abstracted away and I don’t really think about them.

But I do sometimes find it a bit annoying to have to jump between files especially when I am just prototyping something.

Good point about the editor/parser highlighting issue. Yeah that is a legitimate concern. Probably could be solved with a custom file extension and lexical parsing spec. But yeah, that is some work.

I think the ideal is to work within the native conventions of JS, HTML, and CSS as much as possible. But obviously we need some overlay to work with the needs of the framework (handlebars code for example). Not to mention there are meta languages to consider: coffescript, emblem, EmberScript, etc…

I do like the idea of a separate tool that would let you work on a component in isolation of everything. And then click a button or run a command and BOOM you have the source code prepared for you. And potentially tweaked for different targets

  • native HTML/Handlebars Script Tags/CSS/Javascript
  • EAK transpiler modules stuff
  • JSBin Snippet / Github Gist
  • Ruby Gem
  • Bower Dependency
  • etc

Bonus points if this could commit and push to a github repo.

Given the explicit isolation of components it probably makes sense to develop them insolation too.

This might be an interesting candidate for an Ember App.


#8

@machty this make some sense. I guess I was thinking the opposite direction. Writing the template code inline

But looking at it closer it seems it might be possible using the defaultTemplate property.

defaultTemplate: precompileTemplate('SOME HANDLEBARS STRING'),

which is ultimately an alias to this

Ember.Handlebars.compile = function(string) {
// implementation ...snipped...
}

However, can you explain why these don’t work?

http://jsbin.com/utEfIwi/3

http://jsbin.com/utEfIwi/4

It seems I still have to explicitly define the script tag.

http://jsbin.com/utEfIwi/5/

Even putting in an init function and calling super does not do what I would expect.

http://jsbin.com/utEfIwi/6/

even using template doesn’t do what I want.

http://jsbin.com/utEfIwi/7/

I guess I still don’t yet full understand the way the containerization works.


#9

It looks like this discussion has been idle for a while but I think that this is an important area that needs improvement in Ember. I want to get to a world where I can drop in Ember components using bower and have them ‘just work’ like a lot of npm modules do. The problem with this is that it requires me to copy/link files into the appropriate location for the Ember resolver to find the templates.

I can see two simple solutions to this problem:

  1. Allow Components to inform the resolver that its templates can be found at a given path
  2. Or, if a component’s templates isn’t be found in components/ then it is looked for in the same directory as the component itself

Option 2, obviously presents some challenges because the resolver would need to track the path (URI or module path) that each component was loaded from. However it also provides a nice extension point for modifying the presentation of components: if you want to make changes to the way a component is presented then you can simply copy its template into components/ and make your changes.

We could also provide this using Option 1, because users could use the same API to tell the resolver to get the template from some path that they specify.

Funnily enough, I’m only raising this because I’m about to release a component (ember-spin.js) which doesn’t require a template at all, but I need to instruct users to create an empty components/spin.js Handlebars template or the component won’t work.

Anybody got thoughts on how to proceed with this?


#10

Another challenge here is the App. prefix: the component requires that App = Ember.Application.create() has been called before the component is loaded. This suggests to me that we need a registerComponent() method, maybe something like:

var EmFooComponent = Em.Component.extend();
Ember.Application.initializer({
  name: "em-foo-component",
  initialize: function(container, application) {
    container.registerComponent('em-foo', EmFooComponent);
  }
});

I suggest that this would add component:em-foo to the resolver, and {{em-foo}} would work in the same way as App.EmFooComponent.


#11

Hi @aexmachina good points all around.

@krisselden gave a talk at the last Seattle Ember Meetup and showed an example of doing components without templates. Basically the component by default is a div and you can override the style attributes and other properties of the div from within your component code.

Unfortunately the code is not very readable in the video.

Kris, do you think you could post your demo somewhere?

Also, Jake Bixby recreated some of this functionality in a JS Bin

http://twitter.com/jakebixby/status/3­92436172527386624

http://emberjs.jsbin.com/UrorAhI/2


#12

It is currently possible to have templateless components, but you have to create a handlebars helper manually:

Ember.Handlebars.helper('my-component-name', App.MyComponentName);

Example JSBin.


#13

If you’re using master (or canary/beta, I’m not sure where it’s at in the pipeline) with the container-renderables feature enabled then you don’t need the helper as any component is automagically looked up in the container from handlebars.

IE {{foo-bar}} will use App.FooBarComponent if one exists, and so you don’t need to have a template defined.

See FEATURES and the commit for more detail.


#14

I thought this as well, but it appears that the component is looked up in the container, so you still need to register it with the container. Simply declaring it didn’t seem to do the trick when I played around with it.

I would love to be wrong about this though.


#15

It’s definitely working for me, I’m using it lots in my app!

Here’s a fiddle of a templateless component using the canary build (which has container-renderables enabled by default)

http://jsfiddle.net/rlivsey/5TGwD/


#16

Hmm, here is the JSBin that I used for testing: http://emberjs.jsbin.com/eSavUcu/4/edit (it uses the latest canary build), but doesn’t work without registering with the container (via initializer) or adding a helper.

I will dig in further, as your example shows it can be done.


#17

Rename App.FocusedTextField to App.FocusedTextFieldComponent and you’re good to go.


#18

Naming conventions FTW. Apparently, you have to name your component properly (must end in Component) for it to work without a template/initialzer/helper: http://emberjs.jsbin.com/eSavUcu/6/edit

@rlivsey - Thanks for pointing this out!


#19

No probs, I’ve done that a bunch of times myself and wondered why it didn’t work!


#20

I’ll post it at least as a gist when I get a chance, also, in the demo I used the container-renderables feature @rlivsey talks about.