Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Embedding ractive code in html pages #299

Closed
davidmoshal opened this issue Dec 1, 2013 · 11 comments
Closed

Embedding ractive code in html pages #299

davidmoshal opened this issue Dec 1, 2013 · 11 comments

Comments

@davidmoshal
Copy link

Hi, am wondering if it's possible to embed Ractive code inside an html template.
e.g:

 <script>
    function get (el, items) {
       new Ractive:({
          el:el,
          template: this,
          data: items
       })
</script>

<div>
   {{#items}}
      <div>{{ name }}</div>
   {{/items}}
</div>

ie: the ractive code is embedded in an html page, which contains the template, so that all of the code can be in one place?

@Rich-Harris
Copy link
Member

You can, but with a caveat. The template option can be a string or the ID selector of an element, so...

<div id='myTemplate'>
  {{#items}}
    <div>{{name}}</div>
  {{/items}}
</div>
// These two are equivalent
ractive = new Ractive({
  el: 'body',
  template: document.getElementById( 'myTemplate' ).innerHTML
});

ractive = new Ractive({
  el: 'body',
  template: '#myTemplate'
});

Here comes the caveat: when you put template code inside an element, it has to be valid HTML. Certain things that are perfectly normal Mustache templates (e.g. a repeating section of rows inside a table) aren't valid HTML, and the browser will 'helpfully' try and 'correct' it before you get innerHTML.

For that reason, it's recommended to put your templates inside a <script type='text/ractive'> element - that way, the browser won't try and parse it beforehand:

<script id='myTemplate' type='text/ractive'>
  {{#items}}
    <div>{{name}}</div>
  {{/items}}
</script>

Let me know if this answers the question

@davidmoshal
Copy link
Author

You are mis-understanding my question.
I want to do essentially the opposite of what you propose.
I want to put Ractive JAVASCRIPT in an HTML page,
so as to describe the entire Ractive javascript object in a single html (not js) page.

Do you follow?

Here is the code again, all of which is in a SINGLE html file called widget.html :

---------- start of widget.html ------------
 <script>
    function get (el, items) {
       new Ractive:({
          el:el,
          template: this,
          data: items
     })
  </script>

  <div>
     {{#items}}
        <div>{{ name }}</div>
     {{/items}}
  </div>
  ------------ end of widget.html -----------

@Rich-Harris
Copy link
Member

A-ha! I understand perfectly. This is an excellent idea - I've been wondering about how best to package components up for distribution, and this is much better than anything I'd thought of so far.

So the short answer is no, this isn't supported right now. But once the 0.3.8 release is out the door, this will be high up the priority list.

I've spend the last couple of hours hacking around with this idea and it's coming together pretty nicely. For my future reference as much as anything, I'll describe the state I've got to.

I've got a series of <link> tags which get fetched (much of these will seem vaguely familiar to anyone following the development of the HTML imports spec)...

<link rel='ractive' href='widget.html'>

...containing this sort of thing:

<div class='widget'>
  <h2>This is an imported widget!</h2>
  <p>Message: {{message}}</p>
</div>

<style>
  .widget {
    color: blue;
  }
</style>

<script>
  module.exports = function ( Ractive, template ) {
    return Ractive.extend({
      template: template,
      init: function () {
        // custom component logic...
      },
      data: {
        message: 'This is the default message'
      }
    });
  };
</script>

Once they get fetched, the contents are parsed with Ractive's parser. Any top-level style and script tags are removed from the parsed template. The styles are added to the document (no scoping, unfortunately - if styles are included with a component they will have to rely on namespacing).

The scripts are treated similarly, except that before executing the code (which is wrapped inside a IIFE to prevent global namespace pollution), we do window.module = {}, so that afterwards we can do...

factory = window.module.exports;
Component = factory( Ractive, parsedTemplate );

Then, the component is attached to Ractive.components using the link element's name attribute (if not specified, we derive it from the href) - once that is done, we can use it as we'd use any other component.

There are a few things still to work out. In particular, doing things this way means that components are fetched asynchronously, so we need to figure out whether to delay execution of the application until all components are ready (or jimmy them into a previously rendered instance once they become ready, or whatever). There's also the question of optimising apps built this way to minimise network requests in production (though the good news is that this current system would work beautifully with an AMD module loader or Browserify transform).

Many thanks for making this suggestion.

@davidmoshal
Copy link
Author

Many thanks for making this suggestion.

Thanks Rich, and thanks to you for building Ractive, it's my preferred reactive framework, and I've evaluated most of them over the past 12+ years that I've been building reactive applications.

In response to your solution, let's identify 2 different, though related, requirements.

The first requirement is to have the entire widget, or component, described in a single file, including html, js, and css (nice inclusion btw). ie: a single-file component.

In my view, embedding the javascript inside the html has some subtle advantages, such as decreasing the explicit binding required to bind the model to the view (let's table that discussion for now).

The second requirement is to find the best way to include that component, html or other template, in the javascript application, preferably with scope isolation. I view this as a separate problem, and have chosen to go in a different direction, namely: to preprocess the html file into a javascript template file, see option 5 below:

Here are some of the alternatives I've seen for getting an html template to the server:

  1. Per-template ajax calls, as you describe in your tutorials.
    This is a good fallback option, at the cost of numerous http calls and some performance hit. A server-side cache would be of help in that regard.
  2. Use RequireJS's text! mechanism to import the templates.
    I'm actually not a RequireJS fan myself, I prefer browserify, so I'm not sure if it would be possible to decouple that functionality.
  3. Manually embed the html in a huge string inside a javascript file.
    Note: if you're using coffeescript, with triple quotes, then some Coffeescript editors (eg Webstorm) can correctly highlight the html tags inside the string, but not the javascript inside the string.
  4. Use a different templating technology, such as Jade, which has access to the underlying javascript runtime from inside the template (non logic-less I guess they'd be called). It should be possible to build a single-file component using javascript inside a jade file.
  5. Pre-process the html file into a javascript template on the server!
    This seems to be quite common with other templating tools. In fact it's built into underscore and Lodash.
    You create a javascript file which holds the html, either in a huge string (ie: an automated version of option 3 above), or, ideally, a parsed and tokenized representation of the template, ready to be parameterized and rendered. It's not clear to me, btw, whether this is something Ractive supports. It has separated parse() and renderHTML() functions, so presumably it does, but I've never got it working.
    Here's what this looks like in the handlebars world:
    http://handlebarsjs.com/precompilation.html
    The templates are precompiled and then concatenated into a 'templates.js' file, or even into the browserify bundle.js file (the later should be easy once you have the templates in a CommonJS compatible js file).
    Additionally, in our case, we'd want to eval the script tags at runtime I think.
  6. Import the html via link or script tags, as you suggest. I have to admit that I've not yet examined it carefully. Certainly it looks interesting.

I'll have a working version of option 5 working soon.

Dave

@Rich-Harris
Copy link
Member

We agree that preprocessing is a must for serious applications - AJAXing in a load of template files is far from ideal. The approach I take in my own apps is to use a custom AMD module loader, which loads templates with AJAX and processes them on the client during development, but precompiles and bundles them up with the rest of the application JS at build time, e.g.

views/templates/widget.html

<div class='widget'>
  <h2>I'm a widget</h2>
  <button on-click='activate'>Activate me</button>
</div>

views/Widget.js

define([ 'Ractive', 'rv!./templates/widget' ], function ( Ractive, template ) {
  return Ractive.extend({
    template: template,
    init: function () {
      this.on( 'activate', function () {
        alert( 'activating' );
      });
    }
  });
});

app.js

define([ 'views/Widget' ], function ( Widget ) {
  var app = new Widget({
    el: 'body'
  });
});

The CSS lives separately, of course. Now, we're in a position to have a (say) rvc! (RactiVe Component) loader plugin alongside the rv! plugin, which keeps them in a single file. There's already a browserify transform equivalent of rv! - https://npmjs.org/package/ractify - I imagine it would be fairly straightforward to do the same for single-file components.

I'll address the suggestions in turn:

Per-template ajax calls, as you describe in your tutorials.

Yep, not a fan, though some people will always prefer the familiar...

Use RequireJS's text! mechanism to import the templates.

A step up from bog-standard ajax, but I prefer rv! since it precompiles templates during optimisation

Manually embed the html in a huge string inside a javascript file.

It would be great if JS had decent multi-line string support, and even better if editors could handle the mixed syntax. Sadly I don't think this is realistic, especially for non-CoffeeScript people

Use a different templating technology, such as Jade

I've been thinking about this option recently (see #238), but at present Ractive's innards are fairly tightly coupled to the variant of Mustache that it implements. Basically, templating engines like Underscore's have a much easier job - they're just creating a function that produces a string from some data. Ractive's parser has to create an abstract syntax tree and turn it into a JSON representation that is 'hydrated' at runtime to become the parallel DOM that manages the data-binding etc. For that reason, allowing full-blown logic to dictate the structure of the template, as opposed to the content of it (i.e. expressions), may not be possible. It would be good to support other languages in future though.

Pre-process the html file into a javascript template on the server!

Bingo. To my mind this is what we're talking about with the rvc! plugin or Browserify equivalent - template file goes in, module .js file comes out (either a factory that returns the component when called with Ractive, or a module that has a dependency on Ractive). Alternatively a build tool could create a .js file that assumes Ractive is a global, and attaches the component to the Ractive.components registry.

The Ractive.parse() method takes in the HTML-ish template and spits out the JSON representation described above - it's certainly possible to precompile templates on the server today...

fs.readFile( 'template.html', function ( err, result ) {
  var parsed = Ractive.parse( result.toString() );
  fs.writeFile( 'template.json', JSON.stringify( parsed ) );
});

...just not with the CSS and custom component logic baked in. (Incidentally the ractive.renderHTML() method is designed for situations where you want to serve a rendered page from the same template+data that's going to be used on the client, in the name of progressive enhancement/SEO/speed.)

Import the html via link or script tags

This doesn't actually do anything special, I just like the semantics as using <link> tags resembles HTML imports (it's subject to the same cross-origin restrictions as imports as well). In my test app I'm just doing document.querySelectorAll( 'link[rel="ractive"]' ) and iterating over the result, loading components for each one. Coupled with a helper function (e.g. Ractive.fetchLinks( callback )) this could be a pretty nice way to prototype stuff, or for developers who aren't yet comfortable with build steps/module loaders.

Look forward to seeing your option 5 implementation - between us we should be able to nail this.

@Rich-Harris
Copy link
Member

Meanwhile, @jrburke is looking at how to solve a similar problem with Web Components. Well worth learning from his research.

@davidmoshal
Copy link
Author

Thanks, my initial impression is that his solution is way more complex than
it needs to be for our purposes, presumably due to other requirements and
constraints his system has.

In the meantime, I do have the browserification of 'plain' html templates
(ie: without embedded script tags) working with pre-processing,

I'll try tackle the embedded script tags issue next week.

Best

Dave

On Sat, Dec 14, 2013 at 8:22 AM, Rich Harris notifications@github.comwrote:

Meanwhile, @jrburke https://github.com/jrburke is looking athttps://github.com/jrburke/elementhow to solve a similar problem with Web Components. Well worth learning
from his research.


Reply to this email directly or view it on GitHubhttps://github.com//issues/299#issuecomment-30578810
.

@Rich-Harris
Copy link
Member

Hi @davidmoshal - I was doing some work yesterday that would really benefit from component imports, so after a bit of hacking I've added the feature to the dev branch. Would love to hear your thoughts, things you'd do differently etc - I've started a fresh thread (#366) so that newcomers can get straight to the examples. Thanks!

Will close this issue in favour of that one.

@davidmoshal
Copy link
Author

Hi @Rich-Harris I've been playing with Vue.js recently, seem really similar to this idea we bounced around in 2014.

In fact one of the examples above looks just like a .vue file !
Seems to work pretty nicely.

Dave Moshal

@Rich-Harris
Copy link
Member

Yep! A lot of Vue's ideas are heavily influenced by Ractive, including this one – we've been using single-file components since long before .vue files existed. We just never did a good job of advertising it 😁

@davidmoshal
Copy link
Author

We should have!

Vue.js is the second most starred front-end framework on Github (after AngularJS).

Dave

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants