assemble-core
assemble-core copied to clipboard
migration .0.4.42 to 0.7.0 each pages
My '{{#each pages }}' no longer works. Guessing from the fact collections became first class citizens i tried creating a view collection:
app.create("blog")
app.blogs('./src/templates/blog-pages/**');
return app.toStream("blogs")
.pipe(app.renderFile('*'))
.pipe(extname())
.pipe(app.dest('./build/public/blog'));
I have tried many variations like {{#each blog }} etc. Also my {{#each categories}} is not working.
This is the page i am currently migrating http://www.edc4it.com/blog/index.html.
In Assemble 0.4.42 the following works (out of the box)
For the category buttons (each blog has categories in the front matter)
{{#each categories}}
<label class="btn btn-{{category}} ">
<input class="chkbx-filter" type="checkbox" autocomplete="off" value=".cat-{{category}}">{{category}}
</label>
{{/each}}
To render to the blog tiles
{{#each pages }}
{{#is data.published true}}
<a href="{{relative ../../page.dest this.dest}}" >
<div class="grid-item {{#each data.categories}} cat-{{this}} {{/each}}"
data-date="{{formatDate data.date '%Y%m%d'}}" data-categories="{categories}}">
<span class="title">{{data.title}}</span>
</div>
</a>
{{/is}}
{{/each}}
I found some related issues
- https://github.com/assemble/assemble/issues/743
- https://github.com/assemble/assemble/issues/729
- https://github.com/assemble/assemble/issues/634
- https://github.com/assemble/assemble/issues/629
Guessing from the fact collections became first class citizens
kind of, but primarily it's just because the properties on the context object are no longer the same. the following might cast some perspective on all of the linked issues.
(TLDR: depending on the type of "list" you want to generate with the each helper, you might want to use lists or collections. Assemble has "lists" (arrays) as well as "colllections" (objects). A list might work better for what you need. See these unit tests for examples of how to work with lists, and do things like pagination. Once these lists are generated, it should be fairly trivial to use helpers to render them.)
How Assemble 0.6.0 is different than 0.4.x
In grunt-assemble (assemble 0.4.x), we were adding both the context for the current page, AND the pages array to the context.
The implication being that we needed to:
- read in all files first,
- then process the files
- expose each file's context, "global" context, and
pagescontext (for pagination etc)
There were advantages to this, like making it easier to do simple things with pagination, use the each helper to build pages lists, etc. But the downside was that memory management is difficult or impossible. The more pages, the slower it got. (we now of some users that build sites with 25k pages or more, and it gets very slow)
In Assemble 0.6.0, we don't assume that this is always what the user wants or need, but it's still possible (and maybe easier in some ways).
Regardless of how we approach the solution, to generate a list of pages (or posts, or widgets, etc), the entire "list" must be loaded first. Once that's done, we can easily render the list using helpers. If you're using app.src() to load views, then (by default) you won't see the entire list when you render, since they haven't all been loaded yet.
However, this is easily solved by building up the list in the flush function of a plugin, or by not using app.src() to load pages. Instead, we can use the .toStream() method.
Example
Try something like the following in your assemblefile.js
/**
* Helper for showing the context in the console
*/
app.helper('log', console.log.bind(console));
/**
* Middleware
*
* Add the `pages` collection to `view.data`,
* which exposes it to the context for rendering
*/
app.preRender(/./, function(view, next) {
view.data.pages = app.views.pages;
next();
});
/**
* Task for rendering "site"
*/
app.task('site', function() {
app.pages('src/pages/**/*.hbs');
app.partials('src/partials/*.hbs');
app.layouts('src/layouts/*.hbs');
// use the `toStream` method instead of `src`
// so that all pages are available at render time
return app.toStream('pages')
.pipe(app.renderFile())
.pipe(extname())
.pipe(app.dest('_build'));
});
Layout: default.hbs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
</head>
<body>
{% body %}
{{> list }}
</body>
</html>
Partial: list.hbs (or you could add this inline in the layout)
{{#each pages}}
{{log .}}
{{@key}}
{{/each}}
Inspecting the context
Since views are vinyl files, you'll need to inspect them to see what's available to use. To make this easier, you might also try adding a helper to see what's on the context:
Example
Create a helper, arbitrarily named ctx (for context) or whatever you want, and add it to your assemblefile.js:
app.helper('ctx', function(context) {
console.log(arguments);
console.log(context); // the object passed to the helper
console.log(context.hash); // hash arguments, like `foo="bar"`
console.log(this); // handlebars context
console.log(this.options); // assemble `options`
console.log(this.context); // context of the current "view"
console.log(this.app); // assemble instance
});
Then use the ctx helper inside the {{#each}} loop:
{{#each pages}}
{{ctx .}}
{{/each}}
And try it outside the loop:
{{ctx .}}
Thanks for the excellent explanation. I was indeed fairly easy to push in collection into the view's data. The difficulty was mostly in creating the relative link to the page. The destination for the outer page has not yet been set when rendering the page (this.context.view.path still points to the template file of the outer page, whereas context.path already has the current's destination path during the iteration). I wrote a helper function absurl where i can pass in the base for the build:
// hack for now
module.exports = function(basedir){
return function(context){
return "/"+path.relative(basedir,context.path)
}
};
It works, but now have to enable permalinks and see if still works. I forgot i also have that obstacle: permalinks. (that then need to create and push the category collection into the page as well)
Thanks again for your thorough explanation.
The difficulty was mostly in creating the relative link to the page
try this (ironically I just created this, literally right before I read your message! lol):
// somewhere on the assemble options, define a `dest`
app.option('dest', '_build');
// relative path helper
app.helper('relative', function(item) {
var view = this.context.view; // this is the "current" view being rendered
var from = rename(this.options.dest)(view);
var dest = rename(this.options.dest)(item);
return relative(from, dest);
});
// "rename" function
function rename(dest) {
return function(file) {
// return if dest is defined, so we don't calculate the
// dest path for a file more than once
if (file.dest) return file.dest;
var fp = path.join(dest || file.base, file.relative);
file.dest = fp.replace(path.extname(fp), '.html');
return file.dest;
};
}
Also, add hbs to the renderFile() method in the task, to force the hbs engine to be used on .html files:
.pipe(app.renderFile('hbs'))
Then define the following in your template:
{{#each pages}}
<a href="{{relative .}}">{{data.title}}</a>
{{/each}}
Thanks..
Could you not just use context.path as that already points to the destination of the current "each" view? Also would this not conflict with using gulp's extname and perhaps the assemble permalinks?
Meaning item.path from the helper arguments (e.g. the context passed to the helper)?
That won't work for two reasons:
- the files haven't all come through yet
assemble.dest()has not renamed the files yet
This is why I'm using a custom rename function and intentionally avoiding updating any vinyl properties.
You would see the following if you use item.path (with an added line break to make it easier to see what's happening):
/Users/jonschlinkert/dev/assemble-collections/src/pages/context-from-inline.hbs
/Users/jonschlinkert/dev/assemble-collections/src/pages/context-from-page.hbs
/Users/jonschlinkert/dev/assemble-collections/src/pages/context-from-partial.hbs
/Users/jonschlinkert/dev/assemble-collections/src/pages/index.hbs
/Users/jonschlinkert/dev/assemble-collections/src/pages/sub-folder/index.hbs
/Users/jonschlinkert/dev/assemble-collections/_build/context-from-inline.html
/Users/jonschlinkert/dev/assemble-collections/src/pages/context-from-page.hbs
/Users/jonschlinkert/dev/assemble-collections/src/pages/context-from-partial.hbs
/Users/jonschlinkert/dev/assemble-collections/src/pages/index.hbs
/Users/jonschlinkert/dev/assemble-collections/src/pages/sub-folder/index.hbs
/Users/jonschlinkert/dev/assemble-collections/_build/context-from-inline.html
/Users/jonschlinkert/dev/assemble-collections/_build/context-from-page.html
/Users/jonschlinkert/dev/assemble-collections/src/pages/context-from-partial.hbs
/Users/jonschlinkert/dev/assemble-collections/src/pages/index.hbs
/Users/jonschlinkert/dev/assemble-collections/src/pages/sub-folder/index.hbs
/Users/jonschlinkert/dev/assemble-collections/_build/context-from-inline.html
/Users/jonschlinkert/dev/assemble-collections/_build/context-from-page.html
/Users/jonschlinkert/dev/assemble-collections/_build/context-from-partial.html
/Users/jonschlinkert/dev/assemble-collections/src/pages/index.hbs
/Users/jonschlinkert/dev/assemble-collections/src/pages/sub-folder/index.hbs
/Users/jonschlinkert/dev/assemble-collections/_build/context-from-inline.html
/Users/jonschlinkert/dev/assemble-collections/_build/context-from-page.html
/Users/jonschlinkert/dev/assemble-collections/_build/context-from-partial.html
/Users/jonschlinkert/dev/assemble-collections/_build/index.html
/Users/jonschlinkert/dev/assemble-collections/src/pages/sub-folder/index.hbs
That explains why the "index" page was still referring to the template.
But would this solution not conflict with gulp extname / permalinks / gulp rename?
As long as the same destination path is generated in both places it should work. I don't use gulp-rename, but out of curiosity what permalinks solution are you using?
Nothing yet i am trying to figure out assemble-permalinks. I was expecting i could just use that on my view collection. Working my way through the tests and example on that repo. (BTW one tests fails, i'll report it over there)
sounds good, thx
@doowb put that posts helper example up here! lol it's a good one
@rparree @jonschlinkert is referring to this...
app.helper('posts', function(options) {
var list = this.app.list(this.app.views.posts);
return list.items.map(function(post) {
return options.fn(post.data);
}).join('\n');
});
I haven't completely tested it yet, but I think that will get you close to what you're looking for on your tiled posts page...
<ul>
{{#posts}}
<li>{{title}}<li>
{{/posts}}
</ul>
much better solution! but now you see multiple ways to do it :)
That looks very clean, i'll give it a try tomorrow...
Thanks guys!
I've tried this approach but it does not work for me.
The hbs
{{#posts}}
{{title}}
{{/posts}}
The helper:
app.helper('posts', function(options) {
var list = this.app.list(this.app.views.blogs); // shows list {"options":{"/home/rparree/projec....
console.log("list", JSON.stringify(list.items)) // shows []
return list.items.map(function(post) {
return options.fn(post.data);;
}).join('\n');
});
The list has values, it's items not.
BTW for reference...the list of catagories i've solved like this:
{{#categories}}
{{category}}
{{/categories}}
The helper
"categories" : function(options) {
var cats = _.chain(this.app.views.blogs)
.values()
.map(function(v){return v.data.categories})
.flatten()
.uniq()
.value()
return cats.map(function(cat){
return options.fn({category : cat})
}).join('\n')
I guess i can do something similar for an improved page list
that's great! I'd love to see what you come up with for the page list too