Solving some semi-related shortcomings with block attributes #49466
Replies: 3 comments 7 replies
-
@youknowriad @gziolo: Here's a big write-up of a few problems we've been attempting to solve using a new abstraction ("transient attributes", etc.) Check out the PRs I've linked to and let me know if there's anything there you really like or really hate. My 2¢: I don't think there's a silver bullet. I'm leaning towards either this set of changes if we feel that these problems are common enough to warrant an API solution:
Or this set of changes if we think these problems are pretty rare and that something minimal is best:
|
Beta Was this translation helpful? Give feedback.
-
It's also an alternative approach to #34750 where @stacimc proposed extending the "attributes": {
"_blobURL": {
"type": "string",
"__experimentalRole": "internal"
}
} In effect, this PR could potentially close #29693.
Here we also have a previous attempt #38643 to address the issue by @stacimc where he proposed a following syntax: "attributes": {
"myAttributeThatShouldNotBeCopied": {
"type": "string",
"default": "my-default-value",
"__experimentalSupports": {
"copy": false
}
}
} That would close #29693, too. |
Beta Was this translation helpful? Give feedback.
-
I am inclined to go with two API changes that address the problem for this set of use cases. I think it would also help to address "2. Associating Widget IDs with blocks", too. That's in fact a revised version of the proposal:
|
Beta Was this translation helpful? Give feedback.
-
Part of #41236.
We have occasionally ran into a few limitations of block attributes while developing the block editor. In this post I'll describe some common classes of problem and, for each one, link to some rough prototypes that demonstrate how it may be solved.
1) Constructing a block with data from a transform
Problem
When creating a block, sometimes we need to pass data to it that is used for initialisation but then should not be serialised into
post_content
.Examples:
When dragging-and-dropping a file into the block editor, a File block is created via a
file
transform. The file is stored in a temporary blob URL and passed to the File block via thehref
attribute. The file block, when mounted, uploads the temporary file. When the upload completes, the File block updates thehref
attribute with the actual file URL.This creates an extraneous undo level which can be seen by pressing Undo once the file is complete. The File block returns to an invalid state where
href
is set to the temporary blob URL.Furthermore, if a user saves the post while the file is uploading, then the temporary blob URL is saved to
post_content
which is invalid.This exact problem applies to the Image and Video blocks as well.
We wish to have a block transformation that can transform a Legacy Widget block containing a Menu widget into a Navigation block. See Widget Importer: ensure it works with importing menus #47285. To do this elegantly we could have the transform create a Navigation block and pass the
classicMenuId
to the Navigation block'sedit
function. There is currently no way to do this, however, without creating an extra attribute and therefore extraneous undo levels as above.Potential solutions
edit
function in a param.edit
function in an attribute markedinternal
.edit
function using a sharedMap
(Use Maps to pass "constructor args" to blocks from a transform #49456) and avoid any API changes.edit
function by setting an attribute that isn't defined inblock.json
. Because the attribute isn't in the block schema, it won't be serialised or copied. This is currently undocumented behaviour (used by the widgets editor) but we could document it.2) Associating Widget IDs with blocks
Problem
The widget editor works by adding a hidden
__internalWidgetId
attribute to each block in the editor. This allows the editor to tell which widget to update when the user presses Save. If a block doesn't have a__internalWidgetId
set then the editor knows to create a new widget.The implementation relies on the fact that attributes within
block.attributes
that do not have a corresponding definition inblockType.attributes
are removed bywp.blocks.serialize()
andwp.blocks.cloneBlock()
. This is undocumented behaviour—a more robust solution would be nice to have.Potential solutions
params
object i.e.block.params.widgetId = widgetId
. This ensures that it won't be serialised or copied in a documented way.widgetId
attribute to all blocks. This makes it explicit that the attribute will not be serialised or copied.blocks.getSaveElement
filter, we could add filters that removes thewidgetId
attribute from blocks before they are duplicated or serialised.3) Storing foreign keys in a block
Problem
From #29693:
Potential solutions
copy: false
that means that an attribute is not copied when the block is duplicated. The third party would then setcopy: false
on the attribute.copy
function on their block that removes the attribute.copy
filter on their block that removes the attribute.4) Derived attributes
Problem
Occasionally we have block attributes that are derived from other data that we wish to still be serialised into
post_content
and we do not wish for changes to the attribute to create undo levels, however.Examples:
packages/block-library/src/gallery/v1/gallery-image.js:89
- url and alt are derived from imagepackages/block-library/src/query/edit/query-content.js:77
- queryId is initialised to a unique numberpackages/block-library/src/cover/edit/index.js:142
- isDark derived from imagepackages/block-library/src/gallery/edit.js:182
- id and align are derived from imagepackages/block-library/src/file/edit.js:112
- fileId initialised automaticallyPotential solutions
This is much trickier as the cases are so varied.
In some cases, a proper way to do internationalisation within the output of
save()
would make the need for a derived attribute redundant.In some cases, the attribute always stored derived data. In this instances we could think about perhaps having an
undo: false
flag on the attribute similar tointernal: true
andcopy: false
above.In other cases, the attribute alternates between being user-sourced and derived. In these instances we more or less have to mark the call to
setAttributes()
as one that should not create an undo level. This is currently done by calling__unstableMarkNextChangeAsNonPersistent
. We could potentially make this a native part ofsetAttributes()
e.g.setAttributes( changes, { undo: false } )
.Side note: Exploring "virtual attributes"
I did some exploration into creating a kind of proxy class called
VirtualAttribute
which lets you customise whether an attribute is copied, serialised, creates undo levels, etc. at runtime.I don't think this solution is practical however. There are two problems:
productId
attribute to be serialised but not copied. We could useVirtualAttribute
to mark it as non-copyable but this will not persist after the page is reloaded and blocks are re-parsed frompost_content
.serialize: false
is very complicated. Consider the case where you callsetAttributes( { foo: 1 } )
and thensetAttributes( { foo: new VirtualAttribute( 2, { serialize: false } ) } )
.attributes.foo
would then be2
butserialize( block )
would result in{ "foo": 1 }
. To implement this, the block editor store would have to track both the last value of each attribute as well as the last serialisable value of each attribute. It makes more sense for this to be a property of the attribute at the schema level.Beta Was this translation helpful? Give feedback.
All reactions