iron-meteor / iron-router

A client and server side router designed specifically for Meteor.
MIT License
1.98k stars 414 forks source link

dev: concept of child & parent states - template inheritance #205

Open geekyme opened 10 years ago

geekyme commented 10 years ago

Hi guys, I'm come from angularjs background and I've actually been using angular-ui-router for a while and I like their concept of child & parent states, with each state having layout and templates. Their sample is here - http://angular-ui.github.io/ui-router/sample/#/

In the sample above, the child auto inherits templates from the parent without the need to repeat code. It can optionally render additional templates into the parent's layout.

I've tried to apply the same concept in iron-router below, but unlike angular-ui-router, I would have to repeat myself if I want such template inheritance.

Here's what I'm doing, I might be doing it wrongly. If so please correct me: My templates -

<template name="master">
  <div class="container">
    <div id="main" class="row-fluid">
      {{yield}}
    </div>
  </div>
</template>

<template name="parent">
  <h1>Parent Layout</h1>
  <div style="border:1px solid red">
    Parent Space 1
    {{yield 'parent-space1'}}
  </div>
  <div style="border:1px solid red">
    Parent Space 2
    {{yield 'parent-space2'}}
  </div>
</template>

<template name="child">
  <h2>Child Layout</h1>
  <div style="border:1px solid blue">
    Child Space 1
    {{yield 'child-space1'}}
  </div>
  <div style="border:1px solid blue">
    Child Space 2
    {{yield 'child-space2'}}
  </div>
</template>

<template name="content1">
  <p>I'm content 1</p>
</template>

<template name="content2">
  <p>I'm content 2</p>
</template>

<template name="content3">
  <p>I'm content 3</p>
</template>

<template name="content4">
  <p>I'm content 4</p>
</template>

My Router code, annotated with comments of what i'm trying to accomplish:

Router.configure({
  layoutTemplate: 'master'
})

Router.map(function(){
  this.route('home', {template: 'newPosts', path:'/'});
  // parent - nesting template layout 'parent' into the 'master' unnamed yield
  // and render content blocks 1 & 2 into the yields of 'parent'.
  this.route('parent', {
    yieldTemplates: {
      // it works! 2 red boxes
      'content1': {to: 'parent-space1'},
      'content2': {to: 'parent-space2'}
    }
  });

  // child - explicitly state use of 'parent' layout using option 'template'
  // attempt to nest a layout named 'child' inside 'parent' yield
  this.route('child', {
    path: '/parent/child',
    template: 'parent',
    yieldTemplates: {
      // it still works! 2 red boxes with 1 of them nesting 2 blue boxes
      'child': {to: 'parent-space1'},
      // rendering something into the 'child' yield also works!
      'content3': {to: 'child-space2'}
    }
  });

  // Do the same as above, but add 'content4 into child-space1'
  // Problem: have to repeat myself again if I want the contents above to remain
  // Perhaps can follow Angular-ui-router's method of inheritance?
  this.route('child2', {
    path: '/parent/child2',
    template: 'parent',
    yieldTemplates: {
/*      'child': {to: 'parent-space1'},
      'content3': {to: 'child-space2'},
*/      'content4': {to: 'child-space1'}
    }
  });

Any chance such template inheritance of child-parent states could be implemented in iron-router?

geekyme commented 10 years ago

I provided a live-demo in my tutorial app to further explain the above code: http://superman.meteor.com/parent

Click around the links 'parent', 'child', 'child2'. You will notice child2 correctly inserts content 4 if you arrive from the /parent link. But if you refresh on the link http://superman.meteor.com/parent/child2 you will see the template did not inherit anything.

tmeasday commented 10 years ago

@geekyme -- can you achieve what you want using controller inheritance?


ChildController = RouteController.extend({
    template: 'parent',
    yieldTemplates: {
      'child': {to: 'parent-space1'},
      'content3': {to: 'child-space2'}
    }
);

Child2Controller = ChildController.extend({
  yieldTemplates: {
    'content4': {to: 'child-space1'}
  }
});

I'm probably missing something (and might have the syntax wrong), but it seems conceptually this should achieve what you are trying (unless I'm misunderstanding you).

geekyme commented 10 years ago

I just tried your suggestion. The child controller doesnt inherit the yieldTemplates from its parents. Rather, it overwrites them completely.

So in the above code, the parent...

yieldTemplates: {
      'child': {to: 'parent-space1'},
      'content3': {to: 'child-space2'}
    }

is replaced with ...

  yieldTemplates: {
    'content4': {to: 'child-space1'}
  }
tmeasday commented 10 years ago

Damn.

I'm not sure how common this use case is but if we want to support it, I'd suggest that the syntax should be:

Child2Controller = ChildController.extend({
  'yieldTemplates.content4': {to: 'child-space1'}
});

So it's clear when we are completely overriding and when we are just changing a single bit.

geekyme commented 10 years ago

yep that syntax looks great. hoping this can be supported. Is Iron-Router currently re-rendering all templates for every route handler? Meaning from "parent" to "child" above, does it re-render the "parent" template? Or does it just insert the child template direct into the parent?

edit: accidentally clicked the close button

tmeasday commented 10 years ago

My understanding is that it does not re-render the template if it doesn't have to.

This is to avoid flicker that shouldn't be a problem once the UI branch of Meteor is released so the behaviour may change.

Maybe keep this bug open until we have decision on the syntax I referenced above.

geekyme commented 10 years ago

Sure, in the mean time i'll just have to be more verbose in my code :)

cmather commented 10 years ago

@geekyme, Thanks for the suggestion! Will take a deeper look and mark this as design.

meggarr commented 10 years ago

May I ask will this feature be pursued ?