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

Ractive Layout Views #422

Closed
erikringsmuth opened this issue Feb 1, 2014 · 4 comments
Closed

Ractive Layout Views #422

erikringsmuth opened this issue Feb 1, 2014 · 4 comments
Milestone

Comments

@erikringsmuth
Copy link

Ractive Layout Views

I'd like to propose a new Ractive feature. The goal is to use a JavaScript router to load a view without an intermediate step to render the layout (header, footer, etc.). In most frameworks you have to start the "App" then start the router. The "App" is the layout and the router loads and populates the main content. I prefer to have the router run the app without an intermediate step to render the layout. The router should load a view, let's start with the HomeView, and the HomeView should specify a LayoutView. Creating the home view should also create and render the layout. When you attach the home view to the document you're also attaching it's layout.

Here's an example:

// layout/layoutView.js
define([
  'ractive',
  'text!layout/layoutTemplate.html'
], function(Ractive, layoutTemplate) {
  'use strict';

  // a simple layout
  return Ractive.extend({
    template: layoutTemplate
  });
});
// home/homeView.js
define([
  'ractive',
  'text!home/homeTemplate.html',
  'layout/layoutView'
], function(Ractive, homeTemplate, LayoutView) {
  'use strict';

  // the home view extends the layout
  return LayoutView.extend({
    components: {
      contentplaceholder: Ractive.extend({ // and set's itself in the contentplaceholder component
        template: homeTemplate
      })
    }
  });
});

This works but isn't ideal. The home view has to know what component it's attaching to. It would be cleaner if the home view could simply specify a layout property and have the layout specify what component to attach to. The layout could specify a contentPlaceholder for the child to attach to. This way it would look like this.

// layout/layoutView.js
define([
  'ractive',
  'text!layout/layoutTemplate.html'
], function(Ractive, layoutTemplate) {
  'use strict';

  // a simple layout
  return Ractive.extend({
    template: layoutTemplate,
    contentPlaceholder: contentplaceholder // This time the layout specifies where the child goes (an alias to a component)
  });
});
// home/homeView.js
define([
  'ractive',
  'text!home/homeTemplate.html',
  'layout/layoutView'
], function(Ractive, homeTemplate, LayoutView) {
  'use strict';

  // the 'HomeView' has a layout property this time instead of extending the layout
  return Ractive.extend({
    template: homeTemplate,
    layout: LayoutView
  });
});

This is a good start but it would be even better if the HomeView had an outerEl property in addition to it's el property. The outerEl would refer to the layout's el. You could attach it to the document using homeView.outerEl.

Now you can use a router to load homeView.js, render it, and attach it to the document. When the route changes you do the same thing for the next view. The RequireJS Router is the example I've been working on. This completely decouples the layout from the router.

I think this could be implemented using Ractive components. The layout and contentPlaceholder properties would simply tell the Ractive view's how to attach the child view to the layout.

@Rich-Harris
Copy link
Member

I have to confess it took me a couple of goes to understand this proposal, which is one of the reasons it's taken me so long to respond! Looking at the RequireJS Router readme I think I get it though - you want to be able to lazily require() views representing different routes, and replace the current view with the new one once it's loaded. These route views should append themselves to a master layout view, which is created when the app launches and which stays put throughout. Have I understood correctly?

Lazily loading views is a great idea, it's one of the reasons I love AMD. RequireJS Router looks brilliant, I love the concept.

My first instinct is always to see if there's a sane way to achieve the goal with the current tools. This is the kind of approach I'd initially go with:

app.js

define([
  'router'
], function ( router ) {
  'use strict';

  var app = {}, currentView;

  router.registerRoutes({
    home: { path: '/home', moduleId: 'home/controller' },
    customer: { path: '/customer/*', moduleId: 'customer/controller' },
    order: { path: '/orders/:id', moduleId: 'order/controller' }
  }).on( 'routeload', function ( module, routeArguments ) {
    var renderView = function () {
      currentView = module.createView( app, routeArguments );
    };

    if ( currentView ) {
      currentView.teardown( renderView ); // callback style, allow async teardowns
    } else {
      renderView();
    }
  });
});

home/controller

define([
  'rvc!./HomeView' // using https://github.com/RactiveJS/requirejs-ractive
], function ( HomeView ) {
  'use strict';

  return {
    createView: function ( app, routeArguments ) {
      // controller has access to global app state plus any
      // route arguments...
      return new HomeView({
        el: 'body', // or `app.el`, or `app.layoutView.find('.placeholder')`, etc
        ...
      });
    }
  };
});

I feel like I may have misunderstood the problem though. I think the thing I'm not clear on is what the layout view's job is?

@erikringsmuth
Copy link
Author

I created a demo of Ractive using the RequireJS Router in the RequireJS Router gh-pages branch. The demo is run here http://erikringsmuth.github.io/requirejs-router/examples/ractive/.

The layout contains the header, footer, etc. The page specifies what layout to use. This way the site can have multiple layouts depending on what page your on.

To make it work in Ractive the page's module returns a layout (Ractive) that contains the page (Ractive) as a component. This is actually pretty clean compared to other frameworks.

So this all works. The idea I was trying to get at before would be a way to make it even simpler. This is the way a page uses a layout in the demo.

var HomePage = Ractive.extend({...});

return Layout.extend({
  components: { 'content-placeholder': HomePage }
});

It could be even simpler if it worked like this.

// HomePage
return Ractive.extend({
  layout: Layout
});

The layout property would tell the Layout to include the HomePage as a component. It's all syntactical sugar in the end. It works awesome using components. It just has some extra boilerplate on every page.

@erikringsmuth
Copy link
Author

After looking around I've seen some even simpler examples of layouts in handlebars and mustache syntax. These show different ways of implementing them.

https://www.npmjs.org/package/express3-handlebars#basic-usage
{{{body}}}

http://emberjs.com/guides/views/adding-layouts-to-views/
{{yeild}}

https://www.npmjs.org/package/handlebars-layouts
{{#extend}}, {{#append}}, {{#replace}}

The view extends or includes the layout and get's inserted in the {{body}} section.

@Rich-Harris
Copy link
Member

@erikringsmuth Thanks for finding those examples, that's useful stuff. The more I think about it the more I think this use case needs to be properly supported. I had a go at answering a similar question on Stack Overflow recently by proposing a <route/> component that's responsible for handling route changes, but you could argue that it gets the relationship between layout and view backwards, especially from the point of view of RequireJS Router.

I'm going to leave this issue with a post-0.4.0 milestone so we can discuss it here and on the mailing list after the upcoming release.

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

No branches or pull requests

2 participants