Follow-up to: #1751606: Move published status checkbox next to "Save"

Problem/Motivation

  • Modules that are adding or altering form validation or submit handlers to the node form are no longer able to add to the 'submit' button, since NodeFormController conditionally replaces that with two separate 'publish' and 'unpublish' buttons.
  • Modules and themes that want to adjust the form buttons on the node form require complex code to figure out which buttons exist, which are accessible, and what their purpose is, since the single 'submit' (Save) button no longer exists.

Proposed resolution

  • Restore sanity for contributed and custom modules and themes.

Remaining tasks

Contributor tasks needed
Task Novice task? Contributor instructions Complete?
Update the issue summary Instructions
Update the issue summary noting if allowed during the beta Instructions

User interface changes

API changes

Comments

sun’s picture

I think what we need to do here is this:

  1. Detach the form handlers from the actual buttons in $form.
  2. Introduce custom form handler stacks in EntityFormController itself, which are attached to an entity operation.

To clarify what I mean, before/after code from a DX/module perspective probably works best:

Before

function mymodule_form_node_form_alter(&$form, &$form_state) {

  // Act upon preparation of submitted form values.
  // (AFTER #entity_builders have run, BEFORE preview/saving)
  // Note: This weirdness only exists for node_form() currently.
  $form['#submit'][] = 'mymodule_node_form_prepare_submit';

  // Act BEFORE the node is saved.
  array_unshift($form['actions']['submit']['#submit'], 'mymodule_node_form_post_submit');

  // Act AFTER the node has been saved.
  $form['actions']['submit']['#submit'][] = 'mymodule_node_form_post_submit';
}

After

function mymodule_form_node_form_alter(&$form, &$form_state) {
  $controller = $form_state['controller'];

  // Act upon preparation of submitted form values.
  $controller->appendSubmit('mymodule_node_form_prepare_submit');

  // Act BEFORE the node is saved.
  $controller->prependSave('mymodule_node_form_pre_save');

  // Act AFTER the node has been saved.
  $controller->appendSave('mymodule_node_form_post_save');

  // Essentially, this concept works for all available entity form actions;
  // i.e., for all dedicated action methods on the EntityFormController...:

  $controller->appendPreview('mymodule_node_form_post_preview');
  $controller->appendPublish('mymodule_node_form_publish');
  $controller->appendUnpublish('mymodule_node_form_unpublish');
  // etc...
}
sun’s picture

Component: node.module » entity system
swentel’s picture

That makes much sense and 100 times more readable than what we had todo in D7 or earlier.

sun’s picture

Sleeping over this, I realized that we probably can't have dedicated methods for each operation though, since alas, modules should also be able to add custom operations.

Therefore, I think the signatures would change into generic:

$controller->prepend('preview', 'mymodule_node_form_pre_preview');

$controller->append('save', 'mymodule_node_form_save');

However, the main aspect stays the same: We effectively detach #submit from the buttons in the $form, and instead move the stack management and their invocation into the EntityFormController.

plach’s picture

I didn't have the time to study sun's proposal carefully yet, but from a first look it makes a lot of sense to me.

fago’s picture

However, the main aspect stays the same: We effectively detach #submit from the buttons in the $form, and instead move the stack management and their invocation into the EntityFormController.

Totally! But if we attach it to stacks named like the previous buttons it won't help us much, right? You'd still have to know the name of the button...

Also consider a module inventing a new button - it should be able to add all the default entity-processing logic to the button or not, depending on its use-case.

So what about just having 1 main stack of validation and submit handlers? (-> Submit) Then modules can add their handlers to that, while buttons like node-preview, save or publish would use them. A file-upload button that just partially submits the form wouldn't use them. Preview-ing would use them + add a custom handler for previewing. Save would use them + add a custom handler to actually save afterwards.
It's basically taking the entity-build-submit phase further to cover validation+submit and to provide a proper API for enhancing it (ala entity builders). If you need to do anything button specific, well you'll have to know which button anyway?

sun’s picture

re: #6:

Err, nope — the stacks on the controller are not "buttons", but "actions"/"operations". Therefore, your code can prepend or append its handler to the "save" action/operation, and that action/operation may be used by button X, Y, Z, doesn't matter.

Multiple buttons can call into the same action/operation. This is essentially the case with the new publish/unpublish buttons already; the corresponding publish/unpublish actions are adjusting the $entity->status property only. Afterwards, the save action is invoked.

pwolanin’s picture

How would the controller invoke one of these sets of operations in response to a form button press?

sun’s picture

re: #8:

Essentially no change to now:

$form['submit'] = array(
  '#value' => t('Save'),
  '#validate' => array(
    array($this, 'validate'), // Triggers the 'validate' stack.
  ),
  '#submit' => array(
    array($this, 'submit'),   // Triggers the 'submit' stack.
    array($this, 'save'),     // Triggers the 'save' stack.
  ),
);
$form['delete'] => array(
  '#value' => t('Delete'),
  // No need to validate the form when deleting the entity.
  '#submit' => array(
    array($this, 'delete'),
  ),
);

// NodeFormController:

$form['publish'] = array(
  '#value' => t('Publish'),
  '#validate' => array(
    array($this, 'validate'),
  ),
  '#submit' => array(
    array($this, 'submit'),
    array($this, 'publish'),
    array($this, 'save'),
  ),
);
$form['unpublish'] = array(
  '#value' => t('Unpublish'),
  '#validate' => array(
    array($this, 'validate'),
  ),
  '#submit' => array(
    array($this, 'submit'),
    array($this, 'unpublish'),
    array($this, 'save'),
  ),
);

In short:

  1. Buttons declare which actions/operations they invoke.
  2. Modules no longer integrate with the buttons, but instead, with the actions/operations.
fago’s picture

Multiple buttons can call into the same action/operation.

I see - this is the important puzzle I was missing previously.

Buttons declare which actions/operations they invoke.
Modules no longer integrate with the buttons, but instead, with the actions/operations.

That makes sense. But I wonder whether we really need to factor out the action/operation per button. What would be the use case to hook into e.g. publish or unpublish vs. just submit?

sun’s picture

What would be the use case to hook into e.g. publish or unpublish vs. just submit?

Well, by hooking into the publish action, you do not have to care for checking whether the entity will be published. It will.

And, your code will be executed whenever the publish action is executed, regardless of button.

As things currently stand, the 'submit' action is only used for #entity_builders-alike stuff; i.e., munging/transforming submitted form values into values that are suitable for X. We most likely want to clean this up, too, since especially for the node form, a rather crazy amount of builders and handlers are declared and invoked from different spots, and I don't think I'm able to precisely describe how the exact flow looks like without looking at the code...

Let's also bear in mind that this is almost exclusively about modules that try to integrate with an entity form.

fago’s picture

[updated comment]

And, your code will be executed whenever the publish action is executed, regardless of button.

ok, so tight to the form but not tight to the button. But still, I'm wondering what's the use-case for hooking into publish or unpublish?

sun’s picture

Honestly, I don't know yet. :) But I could imagine use-cases in workflow/revisioning modules as well as spam/moderation modules and so on. If the UI invokes a particular action processing stack, then there should be ways to integrate with that particular action stack. Otherwise, we're back to futzing with #properties on form buttons, but only on some; i.e., eek, inconsistency. ;)

Speaking of, this overall change on its own will introduce a relatively major inconsistency with regard to entity forms vs. non-entity forms. However, I think that's a bullet we need to bite.

yoroy’s picture

Issue summary: View changes

Curious to hear how your thinking has evolved around this :)

sun’s picture

The proposal still makes sense to me. The buttons in the node form are still problematic in HEAD. (stumbled over them again just recently)

Meanwhile, D8 introduced entity operations. But not sure whether operations are mapping to the concept of "buttons" or "actions" here. I assume neither of both, because e.g. there is no 'save' operation, and e.g. a 'publish' operation would implicitly save. Unless I'm mistaken, operations are rather meant as entry points (as opposed to exit points).

FWIW, I've the impression that the actions proposed here would be fairly similar to the concept of the Actions in core; i.e., each action has an identifier and performs a default operation, whereas modules can add further operations before/after the default, and the action can be triggered from any code. — …whereas this summary actually sounds like a description of the Event handler architecture. (?)

Hm. What if a form button would trigger one or more named events? (1. submit, 2. publish, 3. save) And form handlers and/or entity actions/operations would listen/subscribe to events?

sun’s picture

The question of how to alter the #submit handlers on the node form came up in IRC today… $form['submit'] vs. $form['publish'], ugh.

The idea of (1) having action/operation stacks, (2) have a button trigger one of those stacks, and (3) possibly even converting "list of callbacks/handlers" into (prioritized) event listeners sounds sane to me.

heddn’s picture

opdavies’s picture

Watching as this affects override_node_options.

kevinquillen’s picture

I arrived here after looking at the changes in Override Node Options (Maintainer of Custom Publish Options).

Both CPO and ONO need to modify the node form to do two things:

- Offer new statuses/state of the Node (custom publish options) which an admin can add as many as they need
- Override those statuses as they need (as done by ONO)

In order to work, ONO changed the NodeForm default and edit class to it's own override, which means that I won't be able to implement CPO without breaking ONO and vice versa.

What is the best way to work in harmony with modules that will offer this kind of functionality?

swentel’s picture

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

xjm’s picture

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

seanb’s picture

This came up while working on moving media_entity to core. Linking the issue to make sure the final solution will work for media as well.

berdir’s picture

Status: Active » Closed (duplicate)

I think at this point is is safe to close this as a duplicate of #2068063: Change "Save and keep un-/published" buttons to a "Published" checkbox and an included "Save" button, where we now have an agreement to do it and know how to do it.

Discussed with @seanB, we will create a follow-up for the media patch to make the same change as that issue.

seanb’s picture