In Panelizer, we have a need to extend the IPE by having the save button give the user additional options: "Save as custom" and "Save as default" -- and to add a "Revert to default" button when the Panels display has been made custom.

Looking at the IPE's Javascript code, I'm not sure exactly how to do that, and I'm beginning to suspect that we'll need to change the code to make this possible.

For example, Drupal.behaviors.panels_ipe.attach() calls Drupal.panels_ipe.init() on the body element at startup. I have a feeling that we'll need other modules to be able to subscribe to an event or something, that allows them to take their own actions after Drupal.panels_ipe.init() or after Drupal.behaviors.panels_ipe.attach().

Also, I think most of the changes that I'd need to do for Panelizer would be adding new tabs to the tab_collection in Drupal.panels_ipe.init(). How can I do that? I'll also need to some of the data saved with the layout when we save, to tell it to "Save as custom" or "Save as default" - how can I replace or extend the layout model?

Maybe we need another object that's responsible for wiring up the whole Backbone app, and then an event to allow other modules to change that wiring, before actually kicking it off?

Another thing, is that we'll need for Panelizer to only mess with the IPE, when it knows that the IPE is editing a Panels display stored in Panelizer. It can figure this out via drupalSettings.panels_ipe.panels_display.storage_type, but ideally it'd be passed in to this magic event (rather than referenced globally) in case there is ever a future where the IPE can edit multiple Panels displays on the same page (which we couldn't do in D7, but it's very possible for multiple Panels displays to exist on the same page, so it'd be cool to support it one day).

Thoughts?

Comments

dsnopek created an issue. See original summary.

samuel.mortenson’s picture

I have two ideas around this, without delving deeply into the specific questions yet (but I will when I have more time to open issues/write up plans):

- Panelizer is going to push the limits of our Backbone implementation in IPE, and that's a good thing. For your examples/use cases, we should have globally accessible methods and events in the AppModel and AppView that allow for the most common contrib modifications - post render modifications, adding tabs, changing tabs views, etc.

- We should have an example module that implements this set of methods/events for a simple and practical use case. We can then use this example module in documentation and in future Javascript tests.

The code we're writing here is breaking a lot of new ground for Drupal, and while that's exciting we do need to make sure that we make it easy for new devs to jump in and start working with the IPE. In the end our API should be good enough that it feel better writing IPE integrations than it would be to write multiple preprocess hooks and chains of unstable jQuery selectors in a Drupal.behaviors implementation. I think we have the tools to make that happen.

samuel.mortenson’s picture

I have a feeling that we'll need other modules to be able to subscribe to an event or something, that allows them to take their own actions after Drupal.panels_ipe.init() or after Drupal.behaviors.panels_ipe.attach().

Yep, we should have a document-level event before the IPE is actually rendered, and then an event at the AppView level every time the IPE is re-rendered.

Also, I think most of the changes that I'd need to do for Panelizer would be adding new tabs to the tab_collection in Drupal.panels_ipe.init(). How can I do that?

Tabs have two components - the TabModel, which contains metadata about the tab, and then the related Backbone View which Drupal.panels_ipe.TabsView is aware of. You can add TabModels without any issues right now, but can't interact with our Views globally. We can change that and even add a helper method like:

Drupal.panels_ipe.app_view.addTab = function (tab_model, backbone_view)

To improve DX with a common task.

Your use case (modifying the Save tab) is a bit more complicated as the AppView is listening to the Save tab's TabModel change:active (click) event, but you could either remove the old tab and add a new one or we could rework that part of the App to support you. Save/Edit are the only tabs that have non-standard functionality.

Maybe we need another object that's responsible for wiring up the whole Backbone app, and then an event to allow other modules to change that wiring, before actually kicking it off?

I think a global (Drupal.panels_ipe.*) AppView and AppModel should serve this task just fine, users can directly interact with our events/methods without calling another container object.

Top two action items right now to unblock you are: 1) Make the AppView global 2) Trigger a global event when the IPE is rendered

dsnopek’s picture

Thanks for all the input!

Your use case (modifying the Save tab) is a bit more complicated as the AppView is listening to the Save tab's TabModel change:active (click) event, but you could either remove the old tab and add a new one or we could rework that part of the App to support you.

Yeah, I was thinking about removing and adding a new tab (or actually, on Saturday I would have been happy with just adding a new tab that did something close-ish so I could start getting my mind around Backbone. :-))

Getting this working is really the main remaining thing for getting the Panelizer release out.

samuel.mortenson’s picture

StatusFileSize
new3.92 KB

Here's a patch that should get you going on Panelizer this week. I'm not putting the issue into review as there's likely more I want to do this week, but assume that what's provided now will still be around by the time we get an alpha out.

The patch adds:

  1. A global Backbone event you can watch to make changes to the IPE before the initial render.
  2. The Drupal.panels_ipe.app_view variable, which lets you interact with the IPE's Backbone View from other modules.
  3. A new hook, hook_panels_ipe_panels_display_presave($panels_display, $layout_model), which should allow you to modify the Panels Display before save based on the data sent from the App. For your use case you would call layout.set('some_flag', true) before calling layout.save(), in your custom Backbone View.

Here's an example of how you might disable the normal Save Tab functionality and add your own Backbone View (I add the BlockPicker() here, but it's just an example):

Backbone.on('PanelsIPEInitialized', function() {
  // Disable the normal save event.
  Drupal.panels_ipe.app_view.stopListening(Drupal.panels_ipe.app.get('saveTab'), 'change:active');

  // Add a new view for the save button to the TabsView.
  Drupal.panels_ipe.app_view.tabsView.tabViews['save'] = new Drupal.panels_ipe.BlockPicker();
});

I plan to look at this more this week, as I said, and try to add some simple methods for adding/removing/editing tabs.

dsnopek’s picture

Awesome, thanks! :-)

dsnopek’s picture

Title: How to allow other modules to extend the IPE? » Allow other modules to extend the IPE
Status: Active » Needs review
StatusFileSize
new15.39 KB
new11.47 KB

Here's a patch which adds hook_panels_build_alter() to Panels, which we need to actually add the new Javascript to the page when the IPE is being used. It also cleans out panels.api.php which had all out-dated D7 hooks in it.

Status: Needs review » Needs work

The last submitted patch, 7: panels-ipe-extend-api-2659566-7.patch, failed testing.

dsnopek’s picture

Status: Needs work » Needs review
StatusFileSize
new17.7 KB
new2.3 KB

This should fix the tests!

phenaproxima’s picture

  1. +++ b/panels.api.php
    @@ -5,260 +5,19 @@
    -function hook_panels_cache_get($argument) {
    

    Why have so many hooks been removed? I don't think it presents a problem or anything (indeed, I didn't even know these hooks existed until I started reviewing this patch), but is this relevant to the patch or just random cleanup?

  2. +++ b/panels.api.php
    @@ -5,260 +5,19 @@
    + * Allow modules to alter the build Panels output.
    

    s/build/built

  3. +++ b/panels.api.php
    @@ -5,260 +5,19 @@
    + * @param array &$build
    + *   The fully built render array.
    

    It'd be nice to say a few more words about $build here. Is the layout divvied up by region? Any other relevant stuff that developers should know?

  4. +++ b/panels.api.php
    @@ -5,260 +5,19 @@
    +function hook_panels_build_alter(&$build, PanelsDisplayVariant $panels_display) {
    

    &$build should be type hinted.

  5. +++ b/panels_ipe/js/panels_ipe.js
    @@ -95,12 +95,11 @@
    -    var app_view = new Drupal.panels_ipe.AppView({
    +    Drupal.panels_ipe.app_view = new Drupal.panels_ipe.AppView({
    

    This is a bit of a nitpick, but it's preferable to keep a local reference to the app view (Drupal.panels_ipe.app_view = var app_view = new Drupal.panels_ipe.AppView), so that the namespace can be changed in one place later, if need be. The code will also be a little more readable that way.

  6. +++ b/panels_ipe/js/panels_ipe.js
    @@ -134,10 +133,16 @@
    +    // Trigger a global Backbone event informing other Views that we're done
    +    // initializing and ready to render.
    +    Backbone.trigger('PanelsIPEInitialized');
    

    It might be more future-proof to namespace these events. For example, panels_ipe.initialized.

  7. +++ b/panels_ipe/panels_ipe.api.php
    @@ -0,0 +1,26 @@
    +function hook_panels_ipe_panels_display_presave($panels_display, $layout_model) {
    

    Both parameters should be type hinted.

dsnopek’s picture

StatusFileSize
new17.79 KB
new1.56 KB

Thanks!

#10.1: None of the hooks in panels.api.php are current - they don't exist in D8. This patch adds a hook, so I it's relevant to this patch in that I need to put something in there, but the removal itself isn't really relevant. We could make a seperate issue that just to remove panels.api.php, but I took the easy way. :-) Whether or not this is acceptable will be up to the maintainers

#10.2: Fixed!

#10.3: Unfortunately, we can't say anything useful because it's essentially undefined. All the Panels layouts will put render arrays named like the region which contain the blocks, but the DS layouts (which we can use) work a totally different way and have the blocks in a flat structure and break them up into regions on rendering. And any custom layout could really do whatever they want. For our use here, that doesn't matter because we're just messing with $build['#attached']['library'].

#10.4: Fixed!

#10.5/6: I'm going to leave these to mortenson to respond to, since this part was his code.

#10.7: Fixed!

dsnopek’s picture

Alright! For allowing the two saving modes needed by Panelizer ("Save as custom" and "Save as default") this patch works perfectly! I've got both frontend code (modifying the UI and getting all the interactions right) and the backend code (making all the necessary changes via hooks). So, I think we're good there. :-)

The last thing that Panelizer needs to do, is add a new button for "Revert to default". Unlike the saving buttons, this isn't saving any of the current state that's in the Backbone models, instead we need to call back to the server and get a complete replacement for the current layout and blocks.

How should we implement this? I think Panelizer should probably provide a new route that does the backend stuff and returns the data. But how do we wire that into the Backbone app?

samuel.mortenson’s picture

I would copy the same logic that's used for the Cancel Tab and just add a new Tab to the tray. Luckily, this is one of the least complicated functions in the IPE as it does a full page refresh instead of AJAX'ing in the previous display configuration. When we get around to improving that, we can implement similar logic in Panelizer for the Revert Tab.

japerry’s picture

StatusFileSize
new18.65 KB
new3.18 KB

I've committed #2636516: Allow creating new content blocks from the IPE which made it not possible to apply this patch anymore. When merging it in this is what I got, but not convinced this re-roll is exactly right...

dsnopek’s picture

Branch tests are currently failing (a fix in #2663530: Update for move of page_manager UI's to page_manager_ui) which is why the tests for #14 aren't running. But, I tested your re-roll and everything is working for me!

Also, I just finished getting the "Revert to default" button working in Panelizer per @samuel.mortenson's suggestion in #13! No new changes were required to this patch.

So, as far as I'm concerned this patch is ready! Any final review? @japerry? @phenaproxima? Please RTBC at will. :-)

phenaproxima’s picture

Status: Needs review » Reviewed & tested by the community

#10.5 and #10.6 are still unaddressed, but if I'm honest, they are nitpicks and can be fixed at a later time. This looks fine to me otherwise.

dsnopek’s picture

I just pushed my Panelizer changes that work with this to the 8.x-3.x branch of Panelizer. So, if you want to experience how the Panelizer integration into Panels IPE works, then you can check that out! All that remains is committing this patch. :-)

  • japerry committed 5d420dc on authored by dsnopek
    Issue #2659566 by dsnopek, japerry, samuel.mortenson, phenaproxima:...
japerry’s picture

Status: Reviewed & tested by the community » Fixed

Yup, lets make the rest in a follow up issue probably... for now, this is in!

Status: Fixed » Needs work

The last submitted patch, 14: panels-ipe-extend-api-2659566-12.patch, failed testing.

dsnopek’s picture

Status: Needs work » Fixed

Status: Fixed » Closed (fixed)

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