Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block Bindings: How should bindings handle placeholder/fallback values? #63442

Open
SantosGuillamot opened this issue Jul 11, 2024 · 10 comments
Open
Labels
[Feature] Block bindings Needs Design Feedback Needs general design feedback. [Type] Discussion For issues that are high-level and not yet ready to implement.

Comments

@SantosGuillamot
Copy link
Contributor

Context

With block bindings, we connect a block attribute with a dynamic source. However, the value of the dynamic source is not always available, so we should show a fallback value. For example, if I connect a paragraph with a custom field, that custom field might not exist, or I don't have permissions to see the value. Or when I am in a template and there isn't a specific post ID.

In those cases, we need to fallback to another value to not break the user experience. Simplified, the workflow could be understood this way:

  1. Try to get the value from the source.
  2. If the value is undefined, show the fallback.
  3. If the fallback is undefined, show the original value of the block attribute.

Right now, there are two use cases in core:

  • Custom fields: They show the key as the fallback.

Screenshot 2024-07-11 at 16 43 49

  • Pattern overrides: They show the original value of the block attribute.

Screenshot 2024-07-11 at 16 48 30

I assume pattern overrides work as expected, but the fallback for custom fields feels a bit techy, so it'd be great to hear opinions if this is right or it should change.

The important part here is that the fallback might defer depending on the source. Right now, we added a getPlaceholder API to let sources decide what to show. This is how it is done for post meta, where we just return the key: link.

However, it was done that way because it was simple and we knew the API was private. Now that we are considering opening the APIs, it's time to discuss the best approach here.

Potential approaches

These are the different approaches I was considering:

Option 1: Keep using getPlaceholder (or getFallback) callback

We could keep the callback we are using right now. It provides flexibility because sources could do whatever they want but at the same time it makes the logic a bit more complex.

Option 2: Change the getPlaceholder property to be a string instead of a callback

This would mean that the fallback would be always the same for each source. Imagine I define it as "Connected to Post Meta". It would show that message independently of which field the block attribute is connected to.

One potential benefit of this is that it could be defined from the server registration directly, and reused in the client.

Option 3: Use the bindings object in the block attributes to receive the fallback value

Right now, bindings are defined inside the metadata attribute. I suggest adding a new property in the bindings attribute that receives the fallback. Something like this:

  "metadata": {
    "bindings": {
      "content": {
        "source": "core/post-meta",
        "args": { "key": "my_custom_field" },
        "fallback": "My custom field"
      }
    }
  }

In this case, the UI would be in charge of adding that property. For example, when a user connects a block attribute with a custom field in the upcoming UI, it could automatically insert the fallback the same way it's adding the rest of the object.

Option 4: Don't use a placeholder at all

This would mean skipping the placeholder step and always fallback to the original value of the block attribute when the value returned by the source is undefined. This is how pattern overrides work right now.

If I understood it correctly, this is what @richtabor was suggesting in this comment. Please correct me if that's not the case.


I'd love to know any thoughts on this before making a decision 🙂

@SantosGuillamot SantosGuillamot added [Type] Discussion For issues that are high-level and not yet ready to implement. [Feature] Block bindings labels Jul 11, 2024
@SantosGuillamot SantosGuillamot changed the title Block Bindings: How should sources handle placeholder/fallback values? Jul 11, 2024
@huubl
Copy link
Contributor

huubl commented Jul 14, 2024

How about adding a fifth option to prevent the block from rendering when its bound value is undefined?

This could be implemented by adding a "whenUndefined" property to the bindings object. This property would specify how the block should behave when it encounters an undefined value. The structure could be as follows:

"metadata": {
  "bindings": {
    "content": {
      "source": "core/post-meta",
      "args": { "key": "my_custom_field" },
      "whenUndefined": "omitBlock" | "useOriginalContent"
    }
  }
}
@artemiomorales artemiomorales added the Needs Design Feedback Needs general design feedback. label Jul 14, 2024
@artemiomorales
Copy link
Contributor

artemiomorales commented Jul 15, 2024

I think there are various possibilities to consider here. For example:

  • Whether the source is registered on the server only
  • Whether the source is registered on the client only
  • Whether the source is registered on both the server and the client
  • Whether a bound value is editable or not, in conjunction with the above scenarios

We should likely outline all of these possibilities to get a better handle on what the user experience should be like in those cases — like how it might look for the Post Meta, and how that might compare and contrast with the approach for custom sources.

One idea: If a value is bound to the Post Meta source and editable, maybe the placeholder should be a message like, “Customize {attributeName}“. I’m thinking about the use case Carlos mentioned in an unrelated issue about having Block Bindings serve as a means of creating a template full of editable fields for users to override.

I'll aim to to think through those possibilities and follow up this week if someone doesn't get to it before me. I added the "Needs Design Feedback" label as well since design may want to share feedback on this part of a potentially key user experience flow, though feel free to remove it if we feel that's not needed.

@SantosGuillamot
Copy link
Contributor Author

How about adding a fifth option to prevent the block from rendering when its bound value is undefined?

I think this is an interesting and totally valid use case, although I wouldn't consider it exactly as another alternative but as another functionality to keep in mind.

While I think it is something that should be supported at some point, it seems a bit more complex than it sounds to me:

  • In this discussion, we are talking only about the client. It could be the case where the value is undefined in the client but available during the server processing. For example, if I connect a block to a custom field in a template, it won't be available because we don't have a postId, but when I render a specific post with that template, the postId will be available so it can render the proper value.
  • Conditionally loading blocks is a bigger topic not only related to block bindings and needs to be handled carefully. There is a Block Visibility plugin from Nick Diego that could serve as inspiration.

We should likely outline all of these possibilities to get a better handle on what the user experience should be like in those cases

I agree that we have to keep all the possibilities in mind, and I briefly touch on that in the options suggested. Basically, the main question is how sources define the placeholder when getValue function returns undefined. When a source is only registered in the server, it will always return undefined. If we want to allow sources only registered in the server to define the placeholder, we must go with an option that both the server and client can understand. That's probably a string as suggested in option 2. It has the counterpart that it provides less flexibility.

One idea: If a value is bound to the Post Meta source and editable, maybe the placeholder should be a message like, “Customize {attributeName}“.

I would say this is a different discussion. If a value is bound to Post Meta and editable, it means that it was able to "connect" to the source and there is no "undefined" value, so it would show the value of the custom field. Something like that could make sense when the value is empty (not undefined), but I see it as a different use case.

@artemiomorales
Copy link
Contributor

I agree that we have to keep all the possibilities in mind, and I briefly touch on that in the options suggested. Basically, the main question is how sources define the placeholder when getValue function returns undefined. When a source is only registered in the server, it will always return undefined. If we want to allow sources only registered in the server to define the placeholder, we must go with an option that both the server and client can understand. That's probably a string as suggested in option 2. It has the counterpart that it provides less flexibility.

@SantosGuillamot Ah ok! I misunderstood this a bit then, thanks.

In that case, how about we keep the getPlaceholder() method, but also introduce a default for it on both the server and the client? For sources registered only on the server, we could do it via the bootrapping mechanism. In either case, we could return a string like, "Binding preview unavailable." This way, we have a good default in place but developers could introduce a more specific placeholder if they prefer.

In our case for Post Meta, maybe we could do something like, "Binding unsuccessful. Please make sure {key} has been registered and is available to the REST API." That may be too verbose, but I think you get the idea 😄

What do you think?

@SantosGuillamot
Copy link
Contributor Author

In that case, how about we keep the getPlaceholder() method, but also introduce a default for it on both the server and the client?

Do you mean adding a default for all sources or let each source define the default in the server? If it's the latter, I'm not convinced about having two different public APIs for handling the placeholder, to be honest.

Lately, I'm slightly inclined to option 3, having a fallback property in the bindings object, and delegate the responsibility to the UI. For example, when the initial UI gets merged, it could add the fallback property when a field is selected the same way it is adding the binding.

But I'm not 100% convinced either.

@gziolo
Copy link
Member

gziolo commented Jul 18, 2024

Putting aside all the technical details, what is the user experience we are trying to build here? I see the following scenarios for the attribute bound to the source:

  • user can edit the value (e.g. when editing post content )
  • user can't edit the value but they can preview it (e.g. in Query loop when editing post content)
  • user can't edit nor preview the value (e.g. when editing the template)

Pattern Override is an edge case because we always present the value provided in the designed pattern or the one that the user provided locally, so unless there would be some permission model introduced, we can keep it outside of the exercise. It's either the default value, or the edited value in the UI.

Now the question to confirm is what happens if the value resolved on the server is an empty string. Does it stil replace the fallback value that might be set for the attribute in the saved HTML? I assume that it still does.

Now, for any core block if the value for the attribute presented in the block canvas is empty, we provide some sort of placeholder, like for an Image block it's a media placeholder, for Paragraph or Heading block, the RichText provides the placeholder text. In that sense, the editor covers for edge case to ensure that the user can interact with these blocks even when they have no values set.

What are the scenarios outlined above where Block Bindings should handle these attributes differently?

@artemiomorales
Copy link
Contributor

artemiomorales commented Jul 18, 2024

Do you mean adding a default for all sources or let each source define the default in the server? If it's the latter, I'm not convinced about having two different public APIs for handling the placeholder, to be honest.

I meant the former! Pardon if the wording was unclear.

Now the question to confirm is what happens if the value resolved on the server is an empty string. Does it stil replace the fallback value that might be set for the attribute in the saved HTML? I assume that it still does.

This sounds right to me.

Putting aside all the technical details, what is the user experience we are trying to build here? I see the following scenarios for the attribute bound to the source:

  • user can edit the value (e.g. when editing post content )
  • user can't edit the value but they can preview it (e.g. in Query loop when editing post content)
  • user can't edit nor preview the value (e.g. when editing the template)

What are the scenarios outlined above where Block Bindings should handle these attributes differently?

Thanks for outlining these cases. To Mario's point above, the primary scenario in which Block Bindings should handle this differently, at least as far as this issue is concerned, is the one in which a block is bound but the preview is unavailable for whatever reason. At the moment, those reasons can include the following when one creates a block binding (these are the scenarios I can identify, though there may be more):

  • A source was defined on the server, but not the client, so there is no getValue() specifying what to show in the editor
  • A source was defined in the client with a getValue(), but it fires an error or is otherwise unable to return a proper value
  • The source specified in the binding markup is not present on the server or the client, so again there is no getValue(). This could happen if a user installs a plugin that allows them to create the binding, then later uninstalls the plugin.

Speaking of that last scenario, once the bootstrapping mechanism is merged, we should also be able to detect whether a source is valid on the client or not. That means we could potentially introduce an error state for the attributes panel if the specified source doesn't exist, an important part of the user experience which I think is also relevant to this discussion:

attributes-panel-error-state
@SantosGuillamot
Copy link
Contributor Author

SantosGuillamot commented Jul 19, 2024

I think we are having many related discussions at the same time. I'll try to gather the different scenarios to have the global context and I can try to split it into different conversations after that.

Let's use custom fields as a real example to take a look at the different scenarios and the different possibilities. I'm sharing screenshots of how it works right now, but everything still requires a deeper discussion and changes may be needed.

Uses cases

User can edit and the value of the custom field IS NOT empty

This just works as expected. Nothing to decide here related to this discussion.

Screenshot 2024-07-19 at 09 21 58

User can edit and the value of the custom field IS empty

Right now, it shows the prompt text defined by each block. Some examples:

  • "Type / to choose a block" for the paragraph.
  • "Heading" for the heading.

Screenshot 2024-07-19 at 09 25 51

This doesn't seem right to me. Additionally, it seems that if I refresh the page, when a custom field has an empty string as a value, the editing gets locked. I think this is a bug.

User can't edit the custom field

When the user can't edit the custom field value, there are different use cases.

The user can access the custom field value and it IS NOT empty
In this case, it just shows the value of the custom field with the editing locked.

Screenshot 2024-07-19 at 09 38 21

The user can access the custom field value and it IS empty
Right now, it stills shows the prompt text from the blocks even if they are not editable. This doesn't seem right to me.

Screenshot 2024-07-19 at 09 40 22

The user can't access the custom field value
This could happen when they don't have the permissions, for example.

Screenshot 2024-07-19 at 09 50 53

Right now, the editing gets locked and we show the value defined by the source as a fallback. In this case, it is the custom field key, but that needs to be revisited. Two questions come from this:

  • Should we let the sources decide what to show as a fallback, or should we use the default value or the block placeholder? (See the possibilities section for more info about this)
  • Which fallback should we show in this use case for the custom field? Is the key too technical?

Custom field doesn't exist

This is the same as if the user can't access the custom field value. It gets locked and shows the fallback we decide.

Users editing a template

Editing a template is a different use case because there is no postId, so we can't show real values and we need to show placeholders/fallbacks. This is how it looks right now a template connecting blocks to custom fields.

This is how it looks a movie template in the editor and in the frontend:

Editor Frontend
Screenshot 2024-07-19 at 10 21 10 Screenshot 2024-07-19 at 10 22 21

Again, this needs to be discussed and decided. Should it use the same fallback as the use cases where the custom field doesn't exist or the user can't access it?


Aside from custom fields, we need to take into account that other sources will deal with similar issues. There are a couple of uses cases related to that.

A block is connected to a source NOT defined.

As the source is undefined, we can't show any value. What we are doing right now, is showing the original attribute value as a fallback and showing the source name in the attributes panel.

Screenshot 2024-07-19 at 10 06 38

Screenshot 2024-07-19 at 10 05 47

A block is connected to a source defined ONLY in the server.

In this case, right now we can't get the value dynamically in the client because the source hasn't defined what to do that. That means that we need to show some fallback. (See the possibilities section for the different alternatives).

In this case, we could read the default content and the block attributes or let the source provide a fallback from the server.

At this moment, we are showing the original value as a fallback like when the source is undefined, with the different that we will be able to access the label defined in the server after this pull request.

Screenshot 2024-07-19 at 10 08 34

Possibilities for fallback

As mentioned in the previous use cases, there are scenarios where we can't show the value of a custom field (or other source), and we need to provide a fallback. These are some of the possibilities we have for that fallback.

At this point, it is important to note that they are not exclusive. For example, we could use the fallback defined by the source and if it doesn't exist, fallback to the original attribute value.

Use the original attribute value as a fallback

This is what we are doing for undefined sources, for example.

There are a couple of challenges with this approach:

  • The sources can't modify it to adapt it to their needs. For example, we can't show the custom field key.
  • The UX is still weird when the original value is empty.
    Screenshot 2024-07-19 at 10 35 01

Use the block placeholder as a fallback

There is a block attribute named "placeholder" that is used in some blocks, we could try to reuse that somehow. I am not sure how this could work because the placeholder might change depending on the block attribute.

Let sources define the fallback

Right now, we are using a getPlaceholder API that lets sources decide what to show as a fallback in the mentioned use cases. If this is not defined, it fallbacks to the original attribute value. I guess there are two questions here:

  • Is this necessary or should we always fallback to the original attribute value.
  • If this is considered necessary, how should that API look (the original purpose of this issue).

Discussions

These are the different discussions I get from the conversation so far:

  • Should the editing be locked when the value of a custom field is empty, even though the user can edit it?
  • Should we modify the prompt text when a user can edit the custom field and its value is empty? How?
  • Should we modify the prompt text when a user cannot edit the custom field and its value is empty? How?
  • Should we let the sources decide what to show as a fallback, or should we use the default value or the block placeholder? (See the possibilities section for more info about this)
  • Which fallback should we show in this use case for the custom field? Is the key too technical?
  • Should we use the same fallback in templates as in other uses cases like when the custom field doesn't exist? How it should look like?
  • Should we throw an error when a source doesn't exist or just show the key as we are doing right now?
  • Which mechanisms should we use as fallback?
  • If we decide to provide an API to let sources define the fallback, how it should look like? (this specific issue)

More use cases not contemplated here?

@gziolo
Copy link
Member

gziolo commented Jul 19, 2024

That's an excellent analysis of all existing use cases that users can experience when dealing with custom block-binding sources. You did a great job outlining all the talking points.

I understand all the concerns, but I think we should standardize around the existing behavior for blocks without connected attributes. There are some rough edges to polish with the current handling around the proper values, like showing proper placeholder prompts that reflect that the value is empty but connected. In the case when the source is not recognized, we should present a proper error message and offer a way to remove or update the connection. In the cases where the values aren’t editable for the user we should make strong indicator that it’s a token, if possible show the previewed value, even when it’s an empty one then if the placeholder is present, we should remove it. Otherwise, automatically generate the token placeholder and sync that with the details we show in the sidebar. Here are my current thoughts regarding the questions you shared:

Should the editing be locked when the value of a custom field is empty, even though the user can edit it?

What would be the rationale for locking it? As explained above, we should update the placeholder value in the RichText to reflect that it's a connected value.

Should we modify the prompt text when a user can edit the custom field and its value is empty? How?

Yes, the same way we update the RichText to show readonly state when there are no permissions, we should override the placeholder prop.

Should we modify the prompt text when a user cannot edit the custom field and its value is empty? How?

We need to have some default state so for example Paragraph or Heading can be selected in the UI. So either we can edit the message of the placeholder so there is something to show, or we make it a token UI element.

Should we let the sources decide what to show as a fallback, or should we use the default value or the block placeholder? (See the possibilities section for more info about this)
If we decide to provide an API to let sources define the fallback, how it should look like? (this specific issue)

I don't think we should let sources decide the handling before we learn about the modifications they would like to have. Having good defaults is key for a better and more consistent user experience.

Which fallback should we show in this use case for the custom field? Is the key too technical?

Can we have a hybrid where we show the source label which is going to be always available on the server and/or client and only show the additional params (it might be more settings for some sources) when hovering over the token, or when using some interaction that shows details?

Should we use the same fallback in templates as in other uses cases like when the custom field doesn't exist? How it should look like?
Should we throw an error when a source doesn't exist or just show the key as we are doing right now?

In general, when the source is not recognized, once we have all the details about registered sources, then we should present the proper info that the handling is missing. This isn't that different from missing blocks:

Screenshot 2024-07-19 at 11 11 16
@artemiomorales
Copy link
Contributor

Thanks for outlining all of these use cases @SantosGuillamot! Overall, I think we need to acknowledge that many of these are distinct scenarios, and we should consider providing helpful information dependent on the situation so users can diagnose the state of the editor.

I also agree with the general thrust of what @gziolo is saying. I’ll go through his points while also adding a few of my own suggestions that I think could help make this all more concrete.

Should the editing be locked when the value of a custom field is empty, even though the user can edit it?

What would be the rationale for locking it? As explained above, we should update the placeholder value in the RichText to reflect that it's a connected value.

I agree — we shouldn’t lock the field.

Should we modify the prompt text when a user can edit the custom field and its value is empty? How?

Yes, the same way we update the RichText to show readonly state when there are no permissions, we should override the placeholder prop.

Yes, we should modify the prompt text. In terms of a placeholder, could we do something along the lines of, “Type here to modify {key}”?

Should we modify the prompt text when a user cannot edit the custom field and its value is empty? How?

We need to have some default state so for example Paragraph or Heading can be selected in the UI. So either we can edit the message of the placeholder so there is something to show, or we make it a token UI element.

Yes, we should indicate to the user what’s going on with the empty field. How about something like, “({Key} value is empty)” in gray text?

Should we let the sources decide what to show as a fallback, or should we use the default value or the block placeholder? (See the possibilities section for more info about this)
If we decide to provide an API to let sources define the fallback, how it should look like? (this specific issue)

I don't think we should let sources decide the handling before we learn about the modifications they would like to have. Having good defaults is key for a better and more consistent user experience.

By default for all sources, I think we should say exactly what the situation is regarding the fallback, something along the lines of, “({Label} binding preview unavailable.)” I believe the label is the only user-friendly information that we’d be guaranteed to have from any source. Showing the default value doesn’t make sense — presumably that will get overridden and it’d be confusing to see one value in the editor and a different one on the frontend. The placeholder also doesn’t make sense, as it doesn’t indicate what’s actually happening.

As far as letting sources decide the handling, what scenarios can we anticipate wherein the above message, “({Label} binding preview unavailable.)” would be insufficient? Either the preview is available or it isn’t. And if it isn’t, I imagine developers will have the best idea of what needs to be done in order to fix that (see my thoughts on the custom fields below as an example). So I lean towards allowing developers to define the fallback, since it seems an integral part of opening the editor API to them.

Which fallback should we show in this use case for the custom field? Is the key too technical?

Can we have a hybrid where we show the source label which is going to be always available on the server and/or client and only show the additional params (it might be more settings for some sources) when hovering over the token, or when using some interaction that shows details?

Yes, in my opinion the key on its own is too technical. If we’re in the post editor, I believe the custom field fallback would only be displayed after a block has successfully connected to the post meta source but is unable to retrieve the value using the specified key, essentially an error state. So we could say something like, “Post Meta binding unsuccessful. Please make sure {key} has been registered and is available to the REST API.” That’s a helpful message that could help developers and end users fix the issue.

As far as other sources go, as mentioned in the point above, I'm not sure that we'd have enough information to display anything helpful to end users, besides that the preview is unavailable. If desired, developers could modify that message to be more suited for their specific circumstance.

Should we use the same fallback in templates as in other uses cases like when the custom field doesn't exist? How it should look like?
Should we throw an error when a source doesn't exist or just show the key as we are doing right now?

In general, when the source is not recognized, once we have all the details about registered sources, then we should present the proper info that the handling is missing. This isn't that different from missing blocks:

I agree with Greg, let’s have an error state when the block is unable to connect to the source.

Something I’ll add though is that in templates, when a block is successfully able to connect to a custom field via the post meta source, we should provide an indicator for the state, maybe just some curly braces around the key: “{key}”.

I’m unsure how we should handle this scenario for custom sources — maybe creating another part of the API where developers can decide the treatment inside templates? Or perhaps that’s overkill. Maybe in that scenario, we just show empty curly braces or the connections icon.

The user can't access the custom field value
This could happen when they don't have the permissions, for example.

This seems like a unique scenario worth handling differently. Can we say something like, “Unauthorized to see {label} binding preview”?

I think there's already a lot to unpack here, so I can circle back to these following points as discussion continues:

  • Which mechanisms should we use as fallback?
  • If we decide to provide an API to let sources define the fallback, how it should look like? (this specific issue)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Block bindings Needs Design Feedback Needs general design feedback. [Type] Discussion For issues that are high-level and not yet ready to implement.
4 participants