Problem/Motivation
Add interaction for adding paragraphs between other, already present paragraphs. Thus, soothe the pain of having to drag a newly created Paragraph across maybe dozens of others which possibly span the height of the screen, leading to a bad user experience for editors.
This is a Followup from #2944372: Introduce hidden field for addition position of paragraph
Proposed resolution
Add a Button labelled something like «add before» to the paragraph actions modal, allowing insertion before any present paragraph. Inserting after is not planned for now, as this can easily be achieved with the still present «add button» at the end of the containing entity reference revisions field.
This way, no new types interface elements are introduced. The button would be enabled via settings and could be positioned through the backend theme.
This approach was discussed and approved by /u/miro_dietiker and /u/lukas-von-blarer
Remaining tasks
As i'm just starting out i will provide a rough outline for now and refine this section as i go along.
Add the option to the paragraph actions modalGet the weight of the paragraph currently occupying the spaceInsert in the corresponding hidden fieldProgrammatically click the «add paragraph button»- Provide setting in widget which is disabled by default and explaing the feature only works with the modal add mode
- Create API to open the modal dialog at a ceratin position
- Clean up selectors used in JS
- Reset the hidden field to it's initial value after the add dialog has opened
- Write tests
| Comment | File | Size | Author |
|---|---|---|---|
| #56 | interdiff-54-56.txt | 1.39 KB | marcoscano |
| #56 | 2946514-56.patch | 21.07 KB | marcoscano |
| #54 | interdiff-2946514-52-54.txt | 1.43 KB | johnchque |
| #54 | add_paragraph_before-2946514-54.patch | 20.84 KB | johnchque |
| #52 | interdiff-2946514-50-52.txt | 1.9 KB | johnchque |
Comments
Comment #2
jenteralex commentedComment #3
jenteralex commentedComment #4
jenteralex commentedComment #5
jenteralex commentedComment #6
jenteralex commentedOk, the first draft of the mere functionality is done. Glad to get some feedback!
(Also on coding style and best practices if neccessary) :)
Comment #7
jenteralex commentedComment #8
miro_dietikerCool stuff, hopefully others can review as well. A first quick review without testing:
It will be postponed until this #2946441: Add setting to hide the duplicate button (hopefully done today) and then need a feature setting check if the function is enabled and force the „...“ button to appear as it could be missing completely if no other thing is enabled and conditionally attach the library.
And tests :-)
Comment #9
luksakYesterday I was wondering why we are adding the button client side... Does it make sense?
Comment #10
miro_dietikerWe have more and more form elements that clutter everything and slowing down the form API.
The next iteration in the Paragraphs UI will move certain actions into the direction of the client.
Comment #11
miro_dietikerI just committed the feature checkboxes in the UI. We can thus disable / enable new features like the duplicate or edit-all buttons.
This is a new feature that could be enabled by default, but needs an opt-out.
So let's add a new feature checkbox, check if it is enabled, and then make sure the ... button is always present.
The tests should disable all the other features and make sure that the button appears if the "add before" is the only action.
And yeah, sure the effective functionality of adding at the right position.
Hopefully we can get this in soon! :-)
Comment #12
mtodor commentedNice work.
I didn't go into implementation details, but I just want to mention here one thing that I have noticed when I started using "paragraph-type-add-modal-delta" hidden field and it could be relevant for this issue too.
When you click on "add before", hidden field will be filled with delta position and that value will stay for that field. When you click "Add paragraph" after that, because that's default adding of a new paragraph, delta will not be changed what will cause that a new paragraph will be added to the previous set position. And that's wrong.
For the improvements, that I'm working on it's not so relevant because I'm hiding "Add paragraph" and controlling delta always when a new paragraph is added by using new buttons. But in this case, you want to clear that state. Maybe the best way is to clear it with some behaviour attach or to change backend implementation to always set empty value (with the setting of empty value I had some side effects, but can't remember what was the problem).
Comment #13
miro_dietikerAbout the add position problem:
Let's cover this in the modal dialog itself. If the dialog is cancelled, it can always reset the delta. (This is completely independent of the add-before JS code).
Comment #14
mtodor commented@miro_dietiker I agree with you and your proposal for the solution is good. Can you create a new issue for that?
Comment #15
luksak@miro_dietiker How should we determine in JS whether the button should be added or not? A class on the actions? Or a attribute? This decision affects other client side actions in the future i guess.
I found it unintuitive to select the modal button so that the hidden inupt and therefore the add before buttons appear. How should we handle that?
Comment #16
berdir> @miro_dietiker How should we determine in JS whether the button should be added or not? A class on the actions? Or a attribute?
Simple. You only add the library if the setting is enabled.
> I found it unintuitive to select the modal button so that the hidden inupt and therefore the add before buttons appear. How should we handle that?
Yeah, not perfect, but I don't see how other add types should work (We don't want to add an add button for every paragraph type for example), so for now, that's how it is. We can include a description in the UI that it only works if the modal is being used.
This also means that it should be off by default as it is not compatible with the default add mode.
Comment #17
jenteralex commented@all Thanks for the Feedback!
So if i understand correctly the issue @mtodor points out about the delta value remaining in the hidden field, should be solved in the modal part of the code? I feel like it might make sense, if i would just clean up after myself in the
paragraphsAddBeforeButtonbehavior?The other more meta question i have is, at the moment my implementation is pretty much just barely working and not very readable or sensible for that matter. I would like to refactor and make some smarter selector choices.
@miro_dietiker: concerning both prior points, are you happy with what it does? Or is it to early for refactor?
Comment #18
berdirConsidering that there will be multiple variants of this, maybe there could be something like an API function that basically allows to open the add dialog with a certain position and makes sure to clean up after itself. We also need to make sure that it works with nested paragraph types. Maybe it already does, didn't really check the patch.
Issues that change the UI should always include screenshots, but I guess there's nothing too special about it, just another button inside the list .
Comment #19
miro_dietikerWe should still think about (how to maintain) button sequence.
@alex feel free to improbe the patch, i didn‘t check yet if it needs to improve for a commit, but the fact that it is opt in and isolated in its own library reduces my perfection stress a lot :-)
Keep in mind, beautification is optional. Configurability and minimal test coverage is a must. Thus my priorities are clear.
Comment #20
luksak@mtodor @jenteralex Yes, I also think this is not related to this issue. This patch uses the weight currently set and writes the current value to the hidden field. The must be somewhere else.
@alexjenter
agreed. Specifically I'd like to improve those:
var $tableRow = $target.closest('tr');var $submit = $widget.find('[type=submit]');var $actions = $(context).find('.draggable .paragraphs-dropdown-actions').once().append(button);Should we add classes to those elements to make them easier to select?
@berdir
This doesn't work with nested paragraphs or multiple fields using paragraphs that use different settings. We need to know on which widget it has been enabled.
Yeah, that's fine for me.
I totally agree that this only works with the modal, but it is kind of sad that the user has to switch the button to get that feature. Any chance we can switch the default add mode?
By cleaning up afterwards you mean resetting the hidden field to it's initial value?
Could you point us to the right direction for writing a test for this? Should we add them to
ParagraphsExperimentalAddWidgetTest? What exactly should we test?Here is a screenshot of the new button:

Comment #21
luksakUpdating remaining tasks
Comment #22
luksakComment #23
miro_dietikerTesting:
In a widget with 3 items i would check if the new total „add before/above) button count is 3 after enabling the feature.
And then i would click the second, choose some type, and then you just assert the Paragraph was added at position 2.
More extensive tests about positioning are not needed here as we have it already from the hidden field.
Comment #24
miro_dietikerI proposed a consistent button sequence in related issue.
That makes the "Add Paragraph before" the first item.
Let's consider that sequence in the patch already.
Comment #25
jenteralex commentedDear all,
am unassigning this, because i don't know how to write the test yet, and don't have the capacity to get into this right now. Will gladly be back when things cool down workload wise! Thanks for your feedback, help and support!
Comment #26
miro_dietikerI'm promoting this as i want to have this in before we release.
Comment #27
marcoscanoThe 'click' event didn't work for me for some reason, apparently 'mousedown' on its place does the trick.
Here a first version of the test, feedback welcome.
Not configurable yet, can work on this next.
Comment #28
stborchertI guess the parent element (the dropdown button) captures the click-event a little bit too greedy.
Wouldn't it be better to use "mouseup" instead of "mousedown"? With "mouseup" the user has a chance to cancel the click by simply moving the mouse away from the element.
Comment #29
miro_dietiker@stBorchert Agree, mousedown is a bad thing, but it's what core does in all places (and it annoys me as well when trying to debug an item ba right clicking, while the ajax already fires)... We should start an initiative to get rid of all these items. :-)
Comment #30
marcoscanoMaking it configurable (and disabled by default) was easier than expected :)
Some notes:
1) I feel that maybe the "Enable widget features" setting inside the widget configuration would benefit from a description for each feature. However this probably could be done in a follow-up for all existing features (if it doesn't exist already).
2) About the mousedown vs mouseup, a quick search in core returns 22 results for 'mousedown' and 4 for 'mouseup'. Should we go with the most used? :) Definitely no strong opinions here, and once I'm not a JS savvy, I defer to whatever you suggest.
Comment #31
miro_dietikerHm, queuing seems stuck, reuploading..
Comment #35
marcoscanoC'mon testbot
Comment #36
berdirdebug left-over?
strange empty lines?
this might need a bit more explanation/description?
Miro asked what happens if you have nested paragraphs and enable it on *one* level only? Is the JS intelligent enough to handle that?
(should be tested then if we don't do that yet..)
Comment #37
miro_dietikerI investigated button sequence proposal here: #2948553: [META] Decide about Paragraph action sequence
So the button should be first, not last.
Also if i click this button on a node with many open top level Paragraphs, i get multiple overlays opened at once - plus multiple entity browsers open. There seems to be some odd .each or so. Not for nested closed Paragraphs though.
About naming: We always write capital "Paragraph", but i think we should just state "Add above" as short as all other actions.
Comment #38
miro_dietikerAnd yes, if i enable this functionality on the host node field, it aggressively attaches itself to all nested child widgets. The configuration of the children is irrelevant. Instead we should only attach it where it's enabled.
Maybe we want to inject data attributes to a widget about the enabled features so we can offer a simple JS helper to check for enabled features?
Comment #39
marcoscanoThis seems to work as well, and should be much less aggressive.
Comment #40
miro_dietikerAlso i see that the "Add above" button not only is added to the field instance items, but also the field instance table head label.
The result is the same - i get many overlays open when clicking.
This matches all "..." buttons of the widget, for field instance items and the field instance tableheader, and all nested ones as well. And then the party begins...
The action item is still the last in the "..." button for me instead of first.
Comment #41
marcoscanoLet's try again.
I've also tried to improve the test coverage with the nested scenarios, but couldn't finish it yet (stil WIP). But if you want to start testing it in parallel, in manual testing this works for me.
Comment #42
marcoscanoAnd now with a passing test for the nested paragraph scenario as well.
Note: I still think the UX here could perhaps be improved by adding some description texts to the features checkboxes... For example, if the user selects anything different than "Modal" for the add mode, the "Add before" feature doesn't make sense, and may confuse users. But this is probably better solved in a slightly more generic new issue.
Comment #43
mtodor commentedNice progress here!
I have tested this functionality a bit and also checked code.
You should not use the value from this select list. Problem is that delta is actually index (or position) and when you reorder paragraphs all values for selects are set in minus -> what makes adding of next paragraph to the first position. You can take a look how delta is calculated in "paragraphs_features" module for "add in between" feature.
I would suggest using
inputhere since all other buttons are input "submit" elements and it would be good to keep it uniform. If you understand what I mean. That would be helpful for future styling, JS handling, etc.I would suggest using
oncehere, since I see "mousedown" registered multiple times.For this one problem is in
paragraphs.actions.jsThat one goes before click and dropdown is hidden -> that's why click is not triggered, I guess. "mousedown" works because it's before "focusout". It would be nice to document that here at least or to change behaviour in
paragraphs.actions.js. Maybe to add some debuance to handler function.Comment #44
miro_dietikerYeah we should definitively have a separate issue about un‘messing that click handler..
As we had issues with duplicate handler triggers, using .once is a must.
Comment #45
johnchqueGonna continue with this, found a problem and still investigating, when:
- We add a paragraph above.
- We remove the new paragraph.
- Attempt to add a new paragraph above again.
The button in the latest patch refresh the page and opens the first paragraph, making the changes suggested above the button doesn't trigger anything at all now, checking.
Comment #46
johnchqueI think this also fixes the problem in #45.
Need to work on tests now, if broken. :)
Comment #48
johnchqueOK, updating the title, this seems to fix the tests and the styling of the button.
Comment #50
johnchqueShould have updated this too, now adding tests for what I pointed in #45.
BTW, thank you so much @marcoscano for your patch, we are getting closer. :)
Comment #52
johnchquePlease, we can pass now right? :)
Comment #54
johnchqueT_T.
Comment #56
marcoscanoThese tests are annoying...
(but necessary! :D )
Comment #57
berdirNot too happy about using such a generic class here (.clearfix). But I see that this is added by field-multiple-value-form.html.twig, so there's not really a way to improve that. Except replacing that with our own template completely, which is obviously not a task for this issue.
Also, we first above get the clearfix sibling to go down to the add dialog. Then here we go back up to the clearfix to go back down to the delta again. Would it make sense to save that element in a variable, so we don't have to find it a second time? And access both the hidden form field and the add wrapper from it?
We did not yet solve the cancel problem.
To reproduce:
1. Click on add above on any element.
2. Cancel the dialog
3. Click on the add button below
4. Select a paragraph
5. The paragraph is added above the item where you clicked add before.
I think the easiest way to fix this is to adjust the code in paragraphs.modal.js, where we close/destroy the modal.
It actually took me a minute to remember that I have to switch it to the modal, I think it would be really useful to have at least that explained to the user. I'd say we can extend the generic description text of the features to mention that for now.
Comment #59
miro_dietikerI improved the description of the features and fixed 3.
The first two still need to be addressed. Still committed as it's our experimental widget and even opt-in only.
Comment #60
miro_dietikerAdded follow-ups.
Comment #61
miro_dietikerAlmost forgot this one.
Comment #62
mtodor commentedVery nice!
Just one note regarding
.clearfixIn this case it's possible to just say
var $add_more_wrapper = $button.closest('table').siblings().find('.paragraphs-add-dialog');That will search for
paragraphs-add-dialogin all siblings. So that looks a bit cleaner and not generic selectors.And you can use same
tablefor findingparagraph-type-add-modal-delta.Ah, deltas. :'(
This will not work nice with "add in between" feature, but it's fine for default paragraphs functionality.
Maybe we could think about adding in paragraphs module some delta resolver (single function in paragraphs related namespace) that would work for everyone. It would resolve and return delta for paragraph row element passed in.
Comment #63
miro_dietiker@mtodor can you transport that to the according follow-up please or open a new one? thx :-)
There is big potential to create some JS object that provides nice tools to interact with the widget.
I also proposed to inject other information into the DOM as data attributes and then we can offer JS getters.
This likely would simplify custom JS complexity and simplify future improvements like moving some (currently server) interaction (like deleting an item) into the client JS.
Comment #64
miro_dietikerI have created an issue for the JS wrapper.
Comment #65
miro_dietiker