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

Add a widget mechanism to Hugo #3412

Closed
wants to merge 2 commits into from
Closed

Conversation

lebarde
Copy link
Contributor

@lebarde lebarde commented Apr 29, 2017

Here is a first attempt to get widgets into Hugo.

  1. Create a site
  2. Create a widget directory inside /widgets. It should look like this:
widgets/
└── text
    ├── layouts
    │   └── widget.html
    └── README.md

Note that the name widget.html is mandatory. Currently the context is the content of the config parameter widgets.[mywidgetarea].[mywidget].options. Variables are accessible with .content for a text widget like the following and as described in the config below.

{{- if isset . "content" -}}
  {{- .content | safeHTML -}}
{{- else -}}<pre>Here is a text widget, but there is nothing to print. Please define options.content inside every text widget in your config.</pre>
{{- end -}}
  1. Configure your site with a widgets variable describing widgets inside widget areas:
widgets:
  sidebar:
    - type: text
      options:
        content: "<h1>IT WORKS from config</h1>"
        parser: html
  showcase:
    - type: text
      options:
        content: "Here lies a showcase."
  footer:
    - type: text
      options:
        content: "Powered by Hugo with widgets."
        foo: bar
  1. Create a template using the widgets call. This can be done like this: {{ widgets "sidebar" . }}.
  2. Create content. You can also use the widget's shortcode: {{% widgets "showcase" %}}
  3. Build and enjoy.
  • Currently the widgets' context is only the content of the config variable. We should add a wider context (easy).
  • I have not studied the impact on performances.
  • Else?

Fixes #2683
See #2535

@lebarde
Copy link
Contributor Author

lebarde commented Apr 29, 2017

Please pull this project to see widgets in action.

@bep
Copy link
Member

bep commented Apr 30, 2017

The CI builds are failing.

@lebarde lebarde force-pushed the widgets_MERGE branch 3 times, most recently from f0ed2c7 to 7eb857e Compare May 1, 2017 18:22
@lebarde
Copy link
Contributor Author

lebarde commented May 1, 2017

Ok I changed the commit, now the tests should succeed.

Now I have not updated the code to fit the last modifications in tpl/ (see #3042). I don't have time tonight to do it.

@lebarde lebarde force-pushed the widgets_MERGE branch 3 times, most recently from f795a5a to 17533d0 Compare May 1, 2017 21:12
@lebarde
Copy link
Contributor Author

lebarde commented May 2, 2017

@bep Tests pass on my environment but I cannot manage to pass the CI and Appveyor tests. What would you advise me?

Thanks!

@bep
Copy link
Member

bep commented May 2, 2017

It does not compile. You should fix that.

Here is a first attempt to get widgets into Hugo. General tests are ok on my dev environment, but I have not already written tests for widgets.

1. Create a site
2. Create a widget directory inside `/widgets`. It should look like this:
```
widgets/
└── text
    ├── layouts
    │   └── widget.html
    └── README.md
```
Note that the name `widget.html` is mandatory. *Currently the context is the content of the config parameter `widgets.[mywidgetarea].[mywidget].options`*. Variables are accessible with `.content` for a text widget like the following and as described in the config below.
```
{{- if isset . "content" -}}
  {{- .content | safeHTML -}}
{{- else -}}<pre>Here is a text widget, but there is nothing to print. Please define options.content inside every text widget in your config.</pre>
{{- end -}}
```

3. Configure your site with a `widgets` variable describing widgets inside widget areas:
```
widgets:
  sidebar:
    - type: text
      options:
        content: "<h1>IT WORKS from config</h1>"
        parser: html
  showcase:
    - type: text
      options:
        content: "Here lies a showcase."
  footer:
    - type: text
      options:
        content: "Powered by Hugo with widgets."
        foo: bar
```

4. Create a template using the `widgets` call. This can be done like this: `{{ widgets "sidebar" . }}`.
5. Create content. You can also use the widget's shortcode: `{{% widgets "showcase" %}}`
6. Build and enjoy.

- Currently the widgets' context is only the content of the config variable. We should add a wider context (easy).
- I have not studied the impact on performances.
- Else?

Fixes gohugoio#2683
See gohugoio#2535
@lebarde
Copy link
Contributor Author

lebarde commented May 2, 2017

At last this works! It seemed capricious since it builded on my environment.

@Jos512
Copy link

Jos512 commented May 3, 2017

Interesting addition to Hugo! But would using .content not confuse people new to Hugo with .Content? It seems potentially confusing to me to use the same variable names as already exist.

@KevinGimbel
Copy link
Contributor

@Jos512 is right. Using {{ .content }} is kind of confusing given {{ .Content }} is used "normally".

@lebarde
Copy link
Contributor Author

lebarde commented May 3, 2017

Absolutely. Thank you for your comment.

What would you propose?
Note that this .content variable is only defined in the text widget. For now, the content of options is accessible in . inside the widget template. This is defined inside your config file as

widgets:
  yourwidgetarea:
    - type: yourwidgettype
      options:

You may define whatever you want and the widget may need an arbitrary options structure. What do you think of this behaviour? Should we engage widget developers to normalize the options structure? Should we change the behaviour?

@Jos512
Copy link

Jos512 commented May 3, 2017

What would you propose?

Perhaps .WidgetContent, or does that become too long?

Prefixing .content with .Widget might be possible too (so .Widget.Content), which is similar to how we access data files in Hugo (.Data. ...), custom page variables (.Params.myVariable) or general site variables (.Site.RegularPages, for instance).

Since we already have both a .Site.Pages and a .Data.Pages variable, I expect that Hugo users are already somewhat familiar with how prefixing the same variable (.Pages here) can give different results. (Perhaps working with a widget might be easily to explain too if it already "looks & feel" like we currently use other variables in Hugo.)

I do agree that "content" is the best word to describe the body of a widget, but am just thinking aloud here about how to make it as clear as possible that it's the content of the widget, and not the content of the page itself.

You may define whatever you want and the widget may need an arbitrary options structure. What do you think of this behaviour?

Is the options field necessarily, you think? From a user perspective, it might be more convenient to not add an additional level but define it directly below the widget type? For instance:

widgets:
  sidebar:
    - type: text
    - content: "<h1>IT WORKS from config</h1>"
    - parser: html

Instead of:

widgets:
  sidebar:
    - type: text
      options:
        content: "<h1>IT WORKS from config</h1>"
        parser: html

If this isn't the goal, I'm a bit confused why type: text is not considered an option. (It seems to be a configurable option?).

@lebarde
Copy link
Contributor Author

lebarde commented May 3, 2017

Thank you @Jos512! This is most interesting.

  1. In the configuration examples you show, you have to keep in mind that sidebar is a widget area, which will display a collection of widgets (as many as you want). They can be arbitrary text, menus, figures, forms, and so on. So I assume widgets.sidebar has to have only one child per widget instance we want. IMO this could also be done like this (in YAML):
widgets:
  sidebar:
    my-first-widget-arbitrary-id:
      type: text
      content: "<h1>IT WORKS from config</h1>"
      parser: html

In that way, you can give each widget an ID (which we can display inside the HTML), and we get all configuration options in that.

  1. Here the type field is mandatory, because Hugo will find the corresponding widget inside widgets/mywidgettype or inside themes/mytheme/widgets/mywidgettype.

  2. I like very much your idea to put all information inside a .Widget var!!

What do you think of this design? If it is OK I will modify the code to allow testing with these design choices.

@lebarde
Copy link
Contributor Author

lebarde commented May 3, 2017

So I modified the source to take your remarks into account. The context inside widget templates is now:

  • . can access all classical site variables (normally including .Content and so on). .Content still refers to the actual page content content.
  • .Widget is defined as follows:
type Widget struct {
	Type       string
	Params     map[string]interface{}
	Identifier string
	Weight     int
	Template   *template.Template
}

In particular, you can access .Widget.Type (name of widget type) and .Widget.Params (config parameters).

  • .WidgetArea contains the widget collection in the current widget area.
  • .Site.Widgets contains the collection of all widget areas.

Please note that widgets still have to be declared this way:

widgets:
  sidebar:
    - type: text
      options:
        content: "<h1>IT WORKS from config</h1>"
        parser: html

I will modify this behaviour later.

The context inside widget templates is now:

- `.` can access all classical site variables (normally including `.Content` and so on). `.Content` refers to the actual page content content.
- `.Widget` is defined as follows:
```
type Widget struct {
	Type       string
	Params     map[string]interface{}
	Identifier string
	Weight     int
	Template   *template.Template
}
```
In particular, you can access `.Widget.Type` (name of widget type) and `.Widget.Params` (config parameters).
- `.WidgetArea` contains the widget collection in the current widget area.
- `.Site.Widgets` contains the collection of all widget areas.
@bep
Copy link
Member

bep commented May 4, 2017

Here is a first attempt to get widgets into Hugo.

How is this different from #2687?

@lebarde
Copy link
Contributor Author

lebarde commented May 4, 2017

Hello @bep,

This proposal is not so different from that other pull request. I have corrected some things though.

Actually the 2 main points are:

  • The current PR is updated to be suitable on the current state of the project (because tpl/ and hugolib/hugo_sites.go have evolved a lot since then).
  • Issue Make Node a Page #2649 (nodes / pages) is done, so it could be high time now to see if widgets are still a need.

Indeed, what I have written is a little bit more than a proof of concept. I assume that as it is, that PR is usable for my own needs. But I have not written any test yet, nor written any documentation. But as soon as we decide that we really want this feature, I will work on these.

What do you think?

@bep
Copy link
Member

bep commented May 4, 2017

My take on this is this:

  • Hugo needs a more flexible way to compose content pages, and content that isn't necessarily 1:1 mapped to an output page on disk. This is discussed elsewhere.
  • In my head, this fits nicely into the model we already have (a Page can be a leaf node or have children -- it should also be composable ...).
  • We have a flexible template system and the composability is there already with the partials and the .Render func.

We need to expand on the above, of course, but I do not think we want some other widget thingy with its own syntax and configuration where it is .content and not .Content etc. I can appreciate the effort in this, but to me, this looks like a refurbished variant of #2687.

Note that this is my thoughts on this. Others may disagree.

@rdwatters
Copy link
Contributor

@lebarde Long time no chat! 😄

I know we spoke of this before on the forums, but I'm hoping you can expand on the extra utility of a widget vs partial and widget vs content view and widget vs shortcode. Is your vision that themes would ultimately use only widgets? I agree with @bep that we need to be very careful about naming, especially when it comes to assuming differences in casing, but it looks like you're already handling the difference between .content and .Content.

I appreciate I'm probably missing something obvious here, but to me it seems like improving at the function-level (e.g., #3297) is a more flexible approach without adding too much complexity, and configuration files are already getting to be too large, IMHO. (See #3090 for additional context.)

Also, thanks for all your hard work on this. It's appreciated!

@lebarde
Copy link
Contributor Author

lebarde commented May 5, 2017

@rdwatters: What @Jos512 and @KevinGimbel said about naming is totally right. And as you said we need to be very careful about naming. This is why I changed that in the second commit. So I am proposing that inside the widget template we have the following variables: .Widget and .WidgetArea.

As I said here:

But what to do if the theme has sidebars and footers, or other customizable place? I see two possibilities for that: 1) to put customizable content (as an arbitrary text field set by the end-user inside the config file -- this is what @digitalcraftsman does in the excellent Hugo Material Docs5), or 2) to set predefined empty partials places that can be manually set by the end-user (this is the use case that @spf13 chose for the main gohugo.io1 website: here2 you see that config file is very light, and here3 you see the partials included).

When I develop a website, my client often asked me to add blocks in the sidebars, headings, footers, sliding showcases on the home page, and so on.

This is where widgets come into play. If somebody has already made the corresponding widget, I install that one inside widgets/ and put it in the good place by modifying the config file. This is so easy! And if I have to add it inside the content, a simple shortcode will do.

For instance, with the proper widgets available, I am sure that a site like consequently.org would not require much work.

Now I am pretty convinced that:

  1. Partials and blocks enable to do whatever we want ;
  2. widgets are not a "solution to everything", because my vision is that they are only intended to be those pieces of code that are most easily reusable within the community. The first real added value is social. The second is that it saves time for webdesigners when they want to add blocks that already exist somewhere.

@lebarde
Copy link
Contributor Author

lebarde commented May 5, 2017

@bep wrote:

  • Hugo needs a more flexible way to compose content pages, and content that isn't necessarily 1:1 mapped to an output page on disk. This is discussed elsewhere.
  • In my head, this fits nicely into the model we already have (a Page can be a leaf node or have children -- it should also be composable ...).

I am not sure to understand. I am pretty convinced that Hugo is very good at content and structure, which is one of the reasons I love it. In anyway I am not talking about content, only aside blocks that will display on every page, eventually depending on the context (sidebars, footers).

We have a flexible template system and the composability is there already with the partials and the .Render func.

And it is good at it IMO. I have currently all I want to develop a website from scratch.

But in fact the lack is about reusable pieces of code. For now, if a site-maker wants to display a menu, a tag cloud, a list of the 5 last posts or a form to subscribe to a newsletter in the main sidebar, he/she has to write it from scratch in a specific layout. This is OK and feasible, but if there are some pieces of code already available he/she will be glad to not reinvent the wheel. And if the site-maker is a beginner, it could be very hard to display a simple menu entry. Widgets intend to go on that ground. Easy-to-use, reusable and fast to set up.

In my model, adding a menu to the sidebar would be as easy as:

  1. Installing a menu widget inside widgets/
  2. setting the following inside the config file:
widgets:
  sidebar:
    first-top-menu:
      type: menu
      menu: main

Where sidebar is defined inside the theme/template and included with {{ widgets sidebar . }}, first-top-menu is the widget's id in the HTML code, type:menu refers to the widget located at widgets/menu/ and menu: main points to the menu described in the config file.

@bep
Copy link
Member

bep commented May 5, 2017

In anyway I am not talking about content, only aside blocks that will display on every page, eventually depending on the context (sidebars, footers).

I assume you mean that the user should be able to put it on any page (which would be every page if he wanted), i.e. composable.

A widget = a template and some metadata (i.e. a content file with front matter) and a way to put include it.

There is a very small mental step from the current Hugo to this. I.e. a much smaller step than this PR.

@lebarde
Copy link
Contributor Author

lebarde commented May 5, 2017

For now the widget areas display each time the template calls it (by default, on every page). I have written a shortcode that enables putting it on one specific page inside the content. But for now you cannot enable a widget area on specific pages. In that way, maybe it could be useful to manually enable or disable a widget area in the front matter of one specific page.

There is a very small mental step from the current Hugo to this. I.e. a much smaller step than this PR.

What would you suggest to make it smaller or to have something reusable partials like this in Hugo?

@KevinGimbel
Copy link
Contributor

But for now you cannot enable a widget area on specific pages. In that way, maybe it could be useful to manually enable or disable a widget area in the front matter of one specific page.

This is a very good idea in my opinion if we go for widgets. When I worked with WordPress for clients there was always need to have different sidebars on different pages and WordPress did not have a mechanic to say where and when to display a widget. I always ended up using the Display Widgets plugin, which adds options to say when and to display a widget inside a sidebar or area. For Front-Matter this could work similar, e.g. by saying

---
title: "About me"
widgets:
  sidebar:
    recentpost:
      hide: true
---

sidebar would be the widget area ({{ .Widget sidebar . }}), recentpost would be the widget ID, and hide would be an option that defaults to false (e.g., show on all pages, except it is hidden via Front-Matter). Or something similar.

I do agree with @lebarde in that Widgets do add value compared to partials, especially with the re-usability and the "easy" plugin mechanism with the widgets folder. As far as I can see, with the generic Widgets shortcode I could call my widgets inside the content - e.g. a "Call to Action" Box - and use the same code for the sidebar or footer. Correct me, but with Partials I would need to create both a "Call to Action" Shortcode and a "Call to Action" partial in order to make it available for use in shortcodes and the sidebar/inside templates. With the widget mechanism I could define it like so:

widgets/
└── cta
    ├── layouts
    │   └── widget.html
    └── README.md

Inside widget.html

<div class="cta">
{{- if isset .WidgetContent -}}
  {{- .WidgetContent | safeHTML -}}
{{- else -}}
  <p>Want a performance audit? Request one <a href="/contact">today!</a></p>
{{- end -}}
</div>

I can then use it inside my content pages (e.g. Blog Post, About, etc) as {{% widgets "cta" %}} and re-use the same widget for the sidebar from the sites config. A default Call To Action could be used ("Want a performance audit...") and optional a different Content can be configured or maybe even set "in-line" with the Shortcode.


Just my two cents, but I personally find the idea of widgets appealing. I think they can provide great value to Hugo and especially to configurable themes.

@rdwatters
Copy link
Contributor

Partials I would need to create both a "Call to Action" Shortcode and a "Call to Action" partial in order to make it available for use in shortcodes and the sidebar/inside templates

I've always thought the use case for shortcodes as separate from a partial. What's been discussed already w/r/t including (or removing) a partial can very easily be taken care of via an arbitrarily named FM Boolean.You can also access page page params from within a shortcode. Instead, maybe there is some value in a convention for using such a front matter field, but it seems that would come up organically (like using app.js as an entry point, for example) or could be documented as a best practice? (This is spitballing.)

Shortcodes are different because you can easily pass in Params when needing to be directly embedded within body copy...so I don't see the connection with a sidebar. This is something I've never tried, but what about creating a shortcode that calls a partial and passes the params in as a dict? Again, never had a use for it, so not tested, so you get the picture...

One consideration is how much Hugo wants to pollute the exportability of content developed by editors and writers; i.e., by adding yet another mechanism (widgets) to content that doesn't conform to the commonmark spec. Widgets in body copy, for example, mean people creating content that could (in their eyes) have to live in Hugo forever...rather than other SSGs, which more or less take just markdown+YAML without issue. I love shortcodes, and I have no plans to ever stop using Hugo, but I see them [shortcodes] as a potential deterrent for writers who want to keep content as small, self-contained, well-composed, standalone files that aren't tethered to any system. A Hugo export (ie, that renders the shortcode to HTML inside markdown but keeps the md format) would be a cool feature, but that's for another thread...

@lebarde
Copy link
Contributor Author

lebarde commented May 5, 2017

You are totally right about shortcodes. Actually, I wrote some math articles which I intended to publish both as PDF (with pandoc) and as web articles. I managed to do it with LaTeX. But in case of shortcodes, it is right that they are specific of Hugo and thus not portable to other content systems.

Using widget areas as shortcodes is IMO a nice way to add value in very specific cases. Here we are talking about corporation websites where we want to add slideshows, call-to-action or post lists in the main content or in place of the main content.

Take for example my own site. The homepage is not intended to have textual content, but to be a place to listen to tunes, to click and see articles, and so on. I added widget areas to content-bottom ("Écouter" and the right column).

But what we are talking about is an adding. The very main purpose of widgets is still to easily manage sidebars, headers, footers, and asides.

On Hugo I am pretty sure that what I did on my website would need a very consequent work (same for consequently.org website, which has beautiful sidebars). But on other CMS it is very easy and done in minutes. Once again, and as @KevinGimbel said, when creating websites this is a most frequent use case. And in that situations, not having to modify raw HTML code just to switch two blocks in the sidebar is nice. Then, if I teach them to, my clients may be able to do it, which is always my target when I create a website. We don't all have only blogs with only pure content.

@lebarde
Copy link
Contributor Author

lebarde commented May 5, 2017

And thank you @KevinGimbel for your answer. I see we had a close experience of that kind of job.

@lebarde
Copy link
Contributor Author

lebarde commented May 8, 2017

As @KevinGimbel said, widgets really add value.

There is a very small mental step from the current Hugo to this. I.e. a much smaller step than this PR.

@bep: What do you have in mind exactly?

Widgets in body copy

@rdwatters: Actually widgets don't go inside the content. As I said the ability to add a widget area inside the content will be most occasional for specific purposes.

Within the pros and cons until now, I cannot see any con that would make the widget mechanism undesirable.

I am looking forward to see widgets in Hugo. But it may not be time for it, and I would understand!

@bep
Copy link
Member

bep commented May 26, 2017

I'm closing this. This PR is just a new iteration of the last PR with the same name. Widgets in itself may be interesting, but this PR introduces so much new in terminology etc., that even I have problems following it -- so I'm not willing to maintain/support this feature. Which currently is a winning argument.

And I don't see too much enthusiasm by others, either.

@bep bep closed this May 26, 2017
@github-actions
Copy link

This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 12, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Dynamic call to template: partial call with prefix
5 participants