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

extending the context from templates (that may be nested) #141

Open
jonschlinkert opened this issue Mar 8, 2015 · 14 comments
Open

extending the context from templates (that may be nested) #141

jonschlinkert opened this issue Mar 8, 2015 · 14 comments

Comments

@jonschlinkert
Copy link
Member

I started this issue to continue the discussion from #74 (comment)

i have some hacks using front matter, but it does feel like i am missing something.

@waynedpj what version are you using? with v0.4.x this is not easy to accomplish.

@waynedpj
Copy link

waynedpj commented Mar 8, 2015

i am testing a move of my gulp work flow to assemble 0.6. i had originally been using (handlebars-layouts)[https://github.com/shannonmoeller/handlebars-layouts] for layouts but have been having problems with corrupted contexts that seems to be caused by the handlebars-layouts helpers (still trying to debug to know for sure and will report to that project if i can get a test).

in the meantime i am trying to use the nested layouts of layouts instead of the inheritance-based layouts of handlebars-layouts and have been stuck trying to find a way to have nested/inner layouts add stylesheets to the top-most layout. basically each layout can add styles (and scripts, etc.) to the final rendered layout that need to appended to the outer layouts. here is a simplified example using the block features of handlebars-layout:
so the top-most layout
layouts/document.html

<html>
  <head>
    {{#block "documentStyles"}}
      <link href="assets/styles/document.css" rel="stylesheet"/>
    {{/block}}
  </head>
</html

and the inner/nested layout
layouts/page.html

{{#extend "layouts/document.html"}}
  {{content "documentStyles" mode="append"}}
    <link href="assets/styles/page.css" rel="stylesheet"/>
  {{/content}}
{{/extend}}

would give us

<html>
  <head>
      <link href="assets/styles/document.css" rel="stylesheet"/>
      <link href="assets/styles/page.css" rel="stylesheet"/>
  </head>
</html

so far, my initial and simple solution to reproduce this with non-inheritance nested layouts (i.e. w/o block, content, and extend helpers) is to use a context array variable styles which the layouts set. then i use a custom context merge which merges arrays instead of overwriting them:
layouts/document.html

---
styles:
  - assets/styles/document.css
---
<html>
  <head>
    {{#styles}}
      <link href="{{.}}" rel="stylesheet"/>
    {{/styles}}
  </head>
</html

and the inner/nested layout
layouts/page.html

---
layout: layouts/document.html
styles:
  - assets/styles/page.css
---

it works, but perhaps there is a better/preferred way? as noted this method requires modifying the context merge to merge array property values (which actually has some nice side effects, but that is for another issue).

PS. i leave it up to you, but perhaps the title of this thread should be more along the lines of "inheritance layout block functionality in non-inheritance nested layouts", or "modifying content in outer layouts from inner/nested layouts", or more specifically "appending stylesheets to head from nested layouts".

@jonschlinkert jonschlinkert changed the title conditionally adding scripts/stylesheets extending the context from templates (that may be nested) Mar 8, 2015
@jonschlinkert
Copy link
Member Author

I changed the title to something that seems closer to what this is about. I think we need to show how to take any arbitrary data from templates, even nested, and use it to extend the context. (on second thought, this might be about "exposing the layout stack on the context", but it depends...).

what if we exposed the layout stack of each template as an array of objects? The order of the array would be significant and would describe the order in which the stack would be "flattened" for any given template, and the objects in the array would be the data object from each template in the stack. After layouts are applied, helpers would be able to use this stack and its objects. etc. etc.

we will need to add something to layouts to make this work. @doowb and I have already planned to do this, but haven't yet because there are different ways to do it and we haven't decided which approach will work out best.

@jonschlinkert
Copy link
Member Author

I'm such an airhead sometimes. I didn't even realize I created this issue on assemble.io, "Oooh the pretty green button for issue, I click it now". <= me That's about it.

Anyway, @waynedpj I made some changes to the layouts lib that I haven't pushed up yet.

I'll can go over this tomorrow, but in a nutshell:

  • it will return an object instead of a string.
  • The object will have on it:
    • options: any options that were passed with the original string.
    • orig: a copy of the original string
    • layout: the actual template object of the layout being applied at that point in the stack. On the layout object, I also added a key, which is the name of the layout.
    • before: the string before the layout is applied
    • after: the string after the layout is applied
    • depth: the depth of the layout, where (ground) 0 is the innermost layout.

Last, a callback can now be passed to extend or use the object described above in some way, while the stack is being built - so we will only need to loop over the stack for a template a single time.

For example, to build that array of scripts you mentioned:

function getScripts(obj, stack, i) {
  stack.scripts = _.union([], stack.scripts, obj.layout.data.scripts || []);
};

here is what the resulting data object looks like after building the stack, in case anyone has suggestions or ideas for doing this differently.

{ options: {},
  stack:
   [ { layout:
        { content: 'aaa above\n{% body %}\naaa below',
          data: { title: 'Foo', scripts: [ 'aaa.js' ] },
          layout: 'bbb',
          key: 'aaa' },
       before: 'This is content',
       depth: 0,
       after: 'aaa above\nThis is content\naaa below' },
     { layout:
        { content: 'bbb above\n{% body %}\nbbb below',
          data: { title: 'Bar', scripts: [ 'bbb.js' ] },
          layout: 'ccc',
          key: 'bbb' },
       before: 'aaa above\nThis is content\naaa below',
       depth: 1,
       after: 'bbb above\naaa above\nThis is content\naaa below\nbbb below' },
     { layout:
        { content: 'ccc above\n{% body %}\nccc below',
          data: { title: 'Baz', scripts: [ 'ccc.js' ] },
          layout: 'default',
          key: 'ccc' },
       before: 'bbb above\naaa above\nThis is content\naaa below\nbbb below',
       depth: 2,
       after: 'ccc above\nbbb above\naaa above\nThis is content\naaa below\nbbb below\nccc below' },
     { layout:
        { content: 'default above\n{% body %}\ndefault below',
          data: { title: 'Quux', scripts: [ 'index.js' ] },
          key: 'default' },
       before: 'ccc above\nbbb above\naaa above\nThis is content\naaa below\nbbb below\nccc below',
       depth: 3,
       after: 'default above\nccc above\nbbb above\naaa above\nThis is content\naaa below\nbbb below\nccc below\ndefault below' } ],
  scripts: [ 'aaa.js', 'bbb.js', 'ccc.js', 'index.js' ],
  result: 'default above\nccc above\nbbb above\naaa above\nThis is content\naaa below\nbbb below\nccc below\ndefault below' }

What I'm thinking is that we should expose this as a param or a property on the file object, so that it's exposed to middleware and plugins. @doowb can you think of a reason we shouldn't do that?

@jonschlinkert
Copy link
Member Author

Just pushed this to a branch. https://github.com/doowb/layouts/tree/stack-object

@doowb
Copy link
Member

doowb commented Mar 9, 2015

I like this if we want to keep all that logic inside layouts. We had originally split out the stack creation out into layout-stack so it could be passed into layouts to just do the content building. And the same stack could be used outside of layouts to extend any data that was needed in any way that you wanted. This was useful when wanting to extend the data in the preRender middleware, but it did require looping over the generated stack twice (and potentially building the stack twice).

However, I like the callback function since it gives you the control over building the stack object. I think this will allow more flexibility and keep it clean when looking for where to do things with layout data.

@jonschlinkert
Copy link
Member Author

had originally split out the stack creation out into layout-stack so it could be passed into layouts

true, but that causes the stack to be looped over twice. this way we only loop once. If we need an array of template names to expose the stack so it can be used in other ways, we can just push the template names into an array and add that to the returned object.

@waynedpj
Copy link

@jonschlinkert thanks for this. it obviously handles my scripts/styles case.

2 little observations:

  • is the new key property possibly overwriting an existing key property on the actual layout object here?
  • is the depth property of the each result object redundant since its index in the stack array indicates the same information?

regardless, it definitely seems like it would be plenty flexible for other things. thanks again.

@jonschlinkert
Copy link
Member Author

overwriting an existing key property on the actual layout object

Hmm, I don't think so but it's possible. The only place I can think of that we're using key on templates is in the render plugin. are you having an issue related to that? I can look into it

Is the depth property of the each result object redundant since its index in the stack array indicates the same information?

Now that you mention it, yes lol. I added that since it seemed difficult to visualize the index while you're inspecting the object. It's not really necessary though so I should probably remove it

@waynedpj
Copy link

Hmm, I don't think so but it's possible. The only place I can think of that we're using key on templates is in the render plugin. are you having an issue related to that? I can look into it

so far i have only been using the new stack-object branch with some simple tests, so no overwrites there, though in my project i have been using properties w/ the name key so it caught my eye. but maybe it is really not a big deal since i mistakenly assumed that the data for the layout object was in the root of the layout object (because the layout property is there), when in fact it is in the data property (how shocking! 😉). so any key data property will be safe in the data property instead of the root where you are writing key. thus unless you think there could be an issue with adding the key property to the layout objects, it looks like you can ignore this. sorry for the false alarm.

and thanks again!

@jonschlinkert
Copy link
Member Author

Sometimes I forget about entire features we have (and no comments from you about that @Melindrea lol), so no worries lol. I'm not cloning the object (I don't think) so I thought maybe I forgot about a reference to it somewhere.

@Melindrea
Copy link
Collaborator

@jonschlinkert I say nothing, then I have nothing said ;)

@jonschlinkert
Copy link
Member Author

lol

@jonschlinkert
Copy link
Member Author

@doowb did we ever expose the object from the layout stack?

@doowb
Copy link
Member

doowb commented Mar 31, 2015

Don't think we merged in that branch.

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

4 participants