This was originally requested by @Andrew_Mallis on #2155377-26: Only show one widget preview at a time when adding content in panels:

It would be really, really awesome if instead of a live preview, we could optionally load an image.

I could not find a hook for this.

Widgets out of context can look really strange sometimes. Many widgets require a context to render any data at all.

Proposal

I propose a multi-facted approach:

  1. Allow CTools content type plugins to provide a 'preview image' key in their info array which points at an image file. If Panoploy Magic finds this key filled in, it should just use that image as the preview.
  2. Allow CTools content type plugins to provide a 'preview callback' key in their info array which is a function that returns a render array or an HTML string. It should take at least $type, $subtype and $plugin. This will allow for several use cases:
    1. Content types that provide multiple subtypes - each will need it's own preview image
    2. If you want to provide an HTML preview, rather than an image. This will allow the preview to match the theme and respond to the available space, but it doesn't need to actually render the widget which may lack context. You can even have the HTML respond in simple ways to the changing settings if appropriate
  3. Allow CTools content type plugins to provide an 'preview settings' key in their info array which will include some settings to be merged into the pane configuration before rendering. This should be done by the default 'preview callback' so that we can have other preview callbacks that change this behavior. For example, an 'preview callback' for FPPs that uses those values to create a default entity.

This will also allow us to refactor our rendering code a little bit, making our current live preview the default fallback for 'preview callback' and more easily segregate the FPP rendering from the other previews.

Examples

An image for an FPP with the bundle "complex_widget":

/**
 * Implements hook_ctools_content_subtype_alter().
 */
function MYMODULE_ctools_content_subtype_alter(&$subtype, $plugin) {
  if ($plugin['name'] == 'fieldable_panels_pane' && $subtype['bundle'] == 'complex_widget') {
    $subtype['preview image'] = drupal_get_path('module', 'MYMODULE') . '/images/complex_widget.png';
  }
}

Default settings for an FPP with the bundle "something_cool" which has a Text field called "field_some_text":

/**
 * Implements hook_ctools_content_subtype_alter().
 */
function MYMODULE_ctools_content_subtype_alter(&$subtype, $plugin) {
  if ($plugin['name'] == 'fieldable_panels_pane' && $subtype['bundle'] == 'something_cool') {
    $subtype['preview settings'] = array(
      'title' => 'An awesome title',
      'field_some_text' => array(
        LANGUAGE_NONE => array(
          0 => array(
            'value' => 'Example text!',
          ),
        ),
      ),
    );
  }
}

A completely custom preview callback for an FPP with the bundle "dynamic widget":

/**
 * Implements hook_ctools_content_subtype_alter().
 */
function MYMODULE_ctools_content_subtype_alter(&$subtype, $plugin) {
  if ($plugin['name'] == 'fieldable_panels_pane' && $subtype['bundle'] == 'dynamic_widget') {
    $subtype['preview callback'] = 'MYMODULE_dynamic_widget_preview_callback';
  }
}

/**
 * Preview callback.
 */
function MYMODULE_dynamic_widget_preview_callback($type, $subtype, $plugin, $renderer) {
  // Return a string or render array!
  return "Here's the preview HTML";
}

Comments

dsnopek’s picture

Thinking about this again after so long!

One thing that the issue description doesn't seem to consider is that we show live previews in two situations: when selecting a widget on the "Add content" dialog, and when configuring a widget. The issue summary seems to be talking about using them for both situations, which I could see. But I could also see an argument for using a static image for just selecting the widget on the "Add content" dialog, but rendering a real live preview when configuring the widget.

Should this be configurable on a per widget basis (ie. two different keys on the CTools content type)? Or, a global setting that affects all widgets? Or should we just pick one way?

Also, for FPPs, it's probably most sustainable to created and render (but not save) an actually FPP in code than use an image. Should there be special support for that? I could see a special configuration just for FPPs that is the field values for the preview to be shown in the "Add content" dialog, which would be much simpler to manage than having to create a new static image every time a widget's appearance changes.

dsnopek’s picture

Title: Allow widgets to control how their preview is rendered (including using a static image rather than live preview) » Allow widgets to control how their "example" is rendered on the "Add content" dialog (including using a static image rather than rendering)
Issue summary: View changes

Updating the title and issue description to only address the previews in the "Add content" dialog. And, actually, those aren't really previews, let alone live previews (even though that's what they are called in code where it's all tangled up) but actually "examples" of the widget, so, I've switched the terminology to talk about examples.

Hopefully, this'll make things easier in the future too and we can talk about "previews" (where we show a preview of the values the user entered) and "examples" (where we show an example of the widget so users have an idea before they even pick it).

I also removed the idea about the hook, because that just creates too many branches. If a module wants to make an example for another modules widget, it can alter the other module's plugin definitions.

And, I added the idea of an 'example settings' so that widgets can simply give some example settings, which can overloaded for entity values in FPPs. This settles on the idea that we'll have two defaults for 'example callback': one for normal panes, and one for FPPs so they can handle their cases specially.

We can make a new issue later for allowing widgets to control their live preview, but that seems like a less common use case.

dsnopek’s picture

Issue summary: View changes

Gah, started trying to make this work in code but the Panopoly code has too many things connected with this called 'preview' that I don't want to change for BC reasons, for example: the constants (PANOPOLY_ADD_PREVIEW_SINGLE, etc) and the theme functions ('panopoly_magic_preview', 'panopoly_magic_preview_link', etc). We can't change those without breaking contrib, but I don't want a mix of things called 'example' and 'preview'. Blergh. No extra clarity for us on this issue :-(

I updated the issue summary to revert back to the 'preview' terminology

dsnopek’s picture

Issue summary: View changes
dsnopek’s picture

Issue summary: View changes

Add some code examples of how to actually use this

dsnopek’s picture

Issue summary: View changes

Added an example for a custom preview callback to the issue summary

dsnopek’s picture

Status: Active » Needs review
StatusFileSize
new8.17 KB

And here's a patch! This works in my limited testing, but it'll need to run through the tests and get a bunch more manual testing.

EDIT: Here's a Travis build: https://travis-ci.org/panopoly/panopoly/builds/312081483

dsnopek’s picture

StatusFileSize
new8.61 KB
new874 bytes

Here's a new patch based on a bug that @cboyden found! Before this patch, we show "No Preview" for new FPPs, and with this patch we were showing a rendered version of the FPP, but with no data in it, which led to some really, really weird results. This patch will simply return no preview if the FPP has no 'preview settings' and maintain the old behavior. If we want to come up with a way to allow previews of new FPPs (maybe fill in the fields with their default values, and "lorem ipsum" for required values?) we can do that on a separate issue.

EDIT: Here's a Travis build for the new patch https://travis-ci.org/panopoly/panopoly/builds/317094403

dsnopek’s picture

StatusFileSize
new8.78 KB
new547 bytes

Another cool idea that came out of @cboyden's testing: this patch sets a property on the FPP so you can tell it's being rendered in the preview. For example, if you want to check in the FPP template, you could do:

  <?php if (!empty($elements['#fieldable_panels_pane']->panopoly_magic_preview)): ?>
    <div>PREVIEW</div>
  <?php endif; ?>

This way you could render a little differently if necessary

EDIT: Here's the Travis build: https://travis-ci.org/panopoly/panopoly/builds/317105066

cboyden’s picture

This is looking great so far. I'm using the 'preview image' and 'preview settings' implementations; I haven't tested the 'preview callback' nor the new FPP property that allows it to know whether it's being rendered as a preview.

cboyden’s picture

Status: Needs review » Needs work

When a custom preview using image or settings is in place, reusable FPPs show the generic custom preview instead of what they actually look like. This will make it harder to distinguish different reusable widgets.

I don't see a way in the alter callback to pass in any information about whether a preview is of a reusable widget or not. Should this be done in the implementation of the hook, or in panopoly_magic?

dsnopek’s picture

Status: Needs work » Needs review
StatusFileSize
new9.01 KB
new1017 bytes

Here's a new patch that adds a special case for reusable FPPs. Basically, if a reusable FPP is being previewed, it'll ignore the 'preview callback' and 'preview image' and force the 'preview callback' to be the default FPP one. Because I could see a case where someone had a widget where they want to always use their custom preview, you can set a special key, 'preview always', to a truthy value to prevent this.

I agonized over how to integrate this into the logic in a way that didn't make it convoluted - I'm pretty happy with that part. This keeps the same sort of "cascading" behavior where we look at the callback, the image, and then one of two defaults - it just adds a special case before any of that happens.

I also struggled with what to name the new key to disable this special case. I'm not super happy with what I came up with ('preview always') but I wanted it to be short and similar to the other two keys ('preview callback' and 'preview image'). Other ideas I had just ended up being too long or too weird, ex: 'preview reusable fpp always', 'disable reusable fpp preview default', etc. If anyone has any suggestions, I'd love to hear them! However, in the end, that is probably a rare edge case, so odds are few people will ever need to care about it.

cboyden’s picture

Thanks, that's fixed it. I've tested the preview image, preview settings, and reusable widgets preview aspects of the latest patch, and they're all working great.

dsnopek’s picture

Status: Needs review » Reviewed & tested by the community

I think I'm going to commit this. It shouldn't cause any regressions - the default behavior shouldn't change. There could be more bugs, but those will only affect people using the new feature.

  • dsnopek committed 02c3533 on 7.x-1.x
    Update Panopoly Magic for Issue #2484843 by dsnopek, cboyden: Allow...
dsnopek’s picture

Status: Reviewed & tested by the community » Fixed

Commited!

cboyden’s picture

Status: Fixed » Needs work

We've begun testing on PHP 7 and ran into an error using this patch.

ArgumentCountError: Too few arguments to function fieldable_panels_pane_view(), 2 passed in modules/panopoly/panopoly_magic/panopoly_magic.module on line 1552 and exactly 3 expected in fieldable_panels_pane_view() (line 1149 of modules/contrib/fieldable_panels_panes/fieldable_panels_panes.module)

The function fieldable_panels_pane_view() expects a third argument, $langcode.

cboyden’s picture

Status: Needs work » Needs review
StatusFileSize
new9.03 KB
new560 bytes

It seems like the function in fieldable_panels_panes should allow that argument to be NULL, as it does for the function it's essentially duplicating:


/**
 * View a fieldable panel pane.
 *
 * @see node_view()
 */
function fieldable_panels_panes_view($entity, $view_mode = 'full', $langcode = NULL) {
  return entity_get_controller('fieldable_panels_pane')->view($entity, $view_mode, $langcode);
}

/**
 * Entity API callback to view a fieldable panel pane.
 *
 * This is essentially a duplicate of fieldable_panels_panes_view() but the
 * function name has to match the entity type with is singular.
 *
 * @see entity_view()
 */
function fieldable_panels_pane_view($entity, $view_mode = 'full', $langcode) {
  return entity_get_controller('fieldable_panels_pane')->view($entity, $view_mode, $langcode);
}

but in the meantime I've attached a patch that adds the langcode argument.

dsnopek’s picture

+++ b/panopoly_magic.module
@@ -1549,7 +1549,7 @@ function panopoly_magic_preview_callback_fpp($type, $subtype, $plugin, $renderer
-  $content = fieldable_panels_pane_view($fpp, 'full');
+  $content = fieldable_panels_pane_view($fpp, 'full', $fpp->language);

I don't know that this is right. The $langcode argument is the language to render in, and the $fpp->language is the language the FPP is saved in. It'd be better, I think, to explicitly pass NULL because that will use the current language of the request for rendering.

That said, I don't have a lot of experience with FPP's and multi-lingual, and haven't tested this in any way.

cboyden’s picture

StatusFileSize
new9.02 KB
new550 bytes

Thanks - updated patch and interdiff are attached.

  • dsnopek committed dce6bbc on 7.x-1.x
    Update Panopoly Magic for Issue #2484843 by dsnopek, cboyden: Allow...
dsnopek’s picture

Status: Needs review » Fixed

Thanks! Committed :)

cboyden’s picture

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.