Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

layouts: user-defined default path #83

Closed
jonschlinkert opened this Issue · 19 comments

3 participants

@jonschlinkert

low priority

As a user, I want to be able to define:

  • a default layout and/or
  • a default layoutPath

So that any pages I add to my project will automatically use the default layout.

And if I need to override the default layout for a page, I do not need to specify the path to the layout i wish to use. (it would be nice to exclude extension as well, but that's probably another request?)

---
layout: carousel.hbs
---

Docs

layoutPath

Type: String
Default: undefined

Optional, specifies a default path to a directory of layouts, enabling you to specify only the name of the layout you want to use. (this is the best I can do at 1 AM lol)

assemble: {
  options: {
    layoutPath: 'src/layouts'
  },
 ...
}

layout

Type: String
Default: undefined

Default layout to be used. Layouts are optional. If a layoutPath is defined, you may specify only the file name of the layout to be used, or you may specify a full path if the layout you wish to use is outside of the layoutPath.

assemble: {
  options: {
    layoutPath: 'my/default/layouts'
    layout: 'default.hbs'
  },
 ...
}

or:

assemble: {
  options: {
    layoutPath: 'my/default/layouts'
    layout: 'my/custom/layouts/default.hbs'
  },
 ...
}

Example

---
title: Blog Post
layout: post.hbs
---
@Arkkimaagi

+1

Altho I'd keep the layout variable always relative to the layoutPath. You may want to have subfolders at some point within your layoutPath and your suggestion (as I understand it) would not work well with that system.

Currently I have solved this issue by having a separate Data paths.json that contains the layoutPath and others. I just reference it in my YAML frontmatter when needed. This suggestion would be more elegant.

@jonschlinkert

I'd keep the layout variable always relative to the layoutsPath

Not necessarily. I would rather keep this flexible (templates abbreviated for example):

options: {
    layout: '<%= layoutspath %>/default.hbs',
    layoutspath: 'src/templates/layouts`
}

This accomplishes the relativity you mentioned, but it also allows you to not associate the two variables if they shouldn't be. Perhaps you will want to define a default layout for the target which does not reside within the layoutPath?

You may want to have subfolders at some point within your layoutsPath

How would that work? Sounds like you have an interesting setup, I love seeing different ways that people setup projects - would you mind showing some examples for the subfolders you mentioned.

In case this helps, I'm looking at this feature as a convenience that would allow:

  1. Each target to have a default layout defined (this has been implemented for a while)
  2. You can override the default by defining a new layout in the YAML front matter of any page. The catch is that the entire path to the layout is required (relative to the Gruntfile).
  3. This feature request would simply eliminate the need to add the full path to a layout in the YAML front-matter.
@Arkkimaagi

Well, it's true that sites do not generally have plenty of layouts (biggest I've built have had about 8 different main layouts, and the cms at that time had some stupid limitations)

What I was imagining was a possible rebranding of a site. In this situation the site has been built with a flat "layouts" folder (where layoutsPath variable points to). Later the customer wants a new branding element across the site, and he wants it done gradually while the site is live and on the live server. This way the rebranding could be started and kept neatly in its own folder, while keeping the old structure intact.

#The original layouts:
layouts/default.hbs
layouts/barebones.hbs
layouts/wide.hbs
layouts/map.hbs
layouts/searchpage.hbs
# These are added after the site launch:
layouts/rebranding2013/default.hbs
layouts/rebranding2013/map.hbs
layouts/rebranding2013/searchpage.hbs

The original frontmatter:

---
layout:barebones.hbs
---

frontmatter on a rebranded page with my suggested approach:

---
layout:rebranding2013/barebones.hbs
---

frontmatter on a rebranded page with your suggested approach:

---
layout:<%=layoutPath%>rebranding2013/barebones.hbs
---

In this situation with your suggested approach, one should have to put the current full path in all the rebranding pages instead of just adding the "rebranding2013/" string to frontmatter. This issue could also be solved by making the folder flat and renaming the rebranded layouts like "rebranding2013-default.hbs". But that's not as clean solution. :)

I do not feel too strongly about this matter, as it's quite easy to go around with YAML (I keep forgetting it, and had not considered it originally) Also, I do appreciate the backward compatibility of your approach. That's always a plus. I just wanted to bring up another option to consider and show the use-case about layouts with subfolders that you wondered. I'll probably be happy either way. :)

@jonschlinkert

I see your point, but technically in all scenarios you also have the option of rearranging your targets around layouts, so that you can just do this:

---
layout: barebones.hbs
---

And while you would be adding another target to the assemble task, this might offer a more durable approach than customizing the YFM for each page - specifically because of the point you're making: layouts might change.

IMO, an idea approach would give you all or most of the control from the build process. You are pointing out however that we can never predict what might change, and there is no foolproof approach. So why not keep the YFM clean and only put the file name in each page?

---
layout: barebones.hbs
---

And then in assemble:

assemble: {
  options: {
    // this layout is overridden by layouts defined in YAML front matter
    layout: '<%= layouts %>/default.hbs'
  },
  pages: {
    options: {
      // "layout path" is used to eliminate need for path in YFM 
      layoutPath: '<%= layouts %>'
    },
    files: {
      'dest/': ['src/**/*.hbs']
    }
  },
  // And pages built with this target use the layoutPath from this target
  refactor: {
    options: {
      // New layoutPath for the "rebranded" pages
      layoutPath: '<%= layouts %>/rebranding2013'
    },
    files: {
      'dest/': ['src/rebrand/*.hbs']
    }
  }
}

Hope this helps...

Btw, another thing you could do that would give you additional flexibility it to use a lodash (underscore) template for the entire layout variable:

---
layout: <%= layout.barebones %>
---
@Arkkimaagi

Okay, you are right. Your approach is probably the best course to take. It amazes me how flexible this whole system is, as we can dance around all the issues quite easily.

Thank you for explaining all these ways to do it. :)

@jonschlinkert

It amazes me how flexible this whole system is, as we can dance around all the issues quite easily.

:100: Thanks! I'm really glad you're getting value out of it. Can I use your comment on the readme?

@doowb
Owner

@Arkkimaagi and @jonschlinkert ... I like the default layout path idea and I really like Jon's solution of using the build target options to control the layout path. I think it can still be overridden at the page level, but not just by giving a full path like this...

---
layout: src/templates/layouts/another.hbs
---

But instead giving the path like this...

---
layout: /src/templates/layouts/another.hbs
---

By using the leading slash / it would tell assemble to not use the layoutPath but start from the root. This is similar to how most systems work, the difference is that we wouldn't go to the root of the file system, but the root of our build.

The question that I have is, what is the root? Is it where the grunt file was run from or would we take into account the cwd if it's applied?

@jonschlinkert

the leading slash / it would tell assemble to not use the layoutPath but start from the root.

Nice! So that's the best of all worlds.

what is the root?

Good thinking... for now though I would consider where the Gruntfile is run to be the root without taking cwd into consideration. Using cwd might be necessary, but it also might be a solution to a problem that doesn't exist, so until someone expresses a need for it let's just do it from the Gruntfile.

@jonschlinkert

In my earlier example above, #83 (comment), I was saying that the layout option would not calculate a path from the layoutspath option, meaning that the layout would be completely on its own. but I think it should calculate off of layoutspath based on what you were saying, @doowb:

options: {
    layout: 'default.hbs',
    layoutspath: 'src/templates/layouts`
}

Then I assume this would work as well, not just in the YFM?

options: {
    layout: '/override/layout/paths/new.hbs',
    layoutspath: 'src/templates/layouts`
}
@Arkkimaagi

It amazes me how flexible this whole system is, as we can dance around all the issues quite easily.

Thanks! I'm really glad you're getting value out of it. Can I use your comment on the readme?

Sure. :)

@jonschlinkert

thanks!

@Arkkimaagi

I think what @doowb suggested is the best course. On question with what is the root, I'd go with gruntfile and cross the cwd bridge once we come to that point just like @jonschlinkert said. For now the root === Gruntfile.js location should be ok.

So, what's stopping us from making the layout work like suggested?

options: {
    layout: 'default.hbs',
    layoutsPath: 'src/templates/layouts`
}

Search for the file in {{layoutsPath}}/

---
layout: alternative.hbs
---

Search for the file in {{layoutsPath}}/subfolder/

---
layout: subfolder/alternative.hbs
---

Search for the file in {{gruntfolder}}/totallydifferent/

---
layout: /totallydifferent/alternative.hbs
---
@jonschlinkert
@Arkkimaagi

Seems promising, what's the bug?

@jonschlinkert

Given the following targets:

assemble: {
  options: {
    layoutdir: 'test/templates/layouts'
  },
  first: {
    options: {
      layout: 'default.hbs'
    },
    files: {
      'posts/': ['src/posts/**/*.hbs']
    } 
  },
  second: {
    files: {
      'docs/': ['src/pages/*.hbs']
    }
  }
}

The second target currently throws an error unless a layout is defined. It should just use the "built-in" layout.

I tried checking for null and false here https://github.com/assemble/assemble/blob/layoutdir/tasks/assemble.js#L568-L571 and it didn't work. But I made some changes and it might work now I just didn't have a chance to get back to it. As always, you're free to play around with it if you want.

Btw, I at one point I had also implemented layoutext so that layouts could be defined without extensions as well. And it worked just fine... however, I found that what initially appeared like it would be advantageous ended up causing more work than it saved. the abstraction actually made it more challenging to maintain layouts on multiple targets, especially when different extensions were used on different files and on different targets (.hbs, .md.hbs etc)

@Arkkimaagi

Okay, so it's more of an missing feature than a bug, as the current implementation does not support pages without layout either. That's good, so it seems that this feature is close to being implemented in master. Good good, can't wait. :)

Yep, too much abstraction can bite you in the wrong place.

@jonschlinkert
@Arkkimaagi

To me it seems that this issue has been solved, as we currently have an user defined layoutdir. Isn't that what this issue was about?

@jonschlinkert

Thanks for the reminder!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.