Proposal: Introduce emptyIf for Conditional Output

@ben @dan

Further to my idea to simplify control group creation with states. After giving it a lot of thought here is my proposal.

I would like to propose a small but powerful enhancement to the control schema: the addition of a new conditional key named emptyIf.

This feature would significantly simplify component development, reduce duplication, and make the system more scalable for state-based control groups.

What is emptyIf?

I propose adding a new optional schema key:

“emptyIf”: “condition”

It functions similarly to your existing:

  • visible

  • enabled

…using the same expression system and evaluation context.

However, instead of affecting UI visibility or interactivity, emptyIf affects the value a control returns.

Behavior

When emptyIf evaluates to true, the control should return an empty string ("") during output generation.

This does not hide the control in the UI
and does not disable it.

It simply prevents the control from contributing to any final output in rw.props.

Why this matters for developers like me

Groups of controls can exist in multiple states.
Different states naturally require different output patterns.

Today, any component that needs to assemble output from a group of controls must also:

  • replicate portions of the group’s internal state logic

  • understand which controls are applicable in which situations

  • handle conditions that already exist in the schema

  • manually skip controls based on state-dependent logic

This results in:

  • logic duplication

  • hard-to-maintain code

  • greater likelihood of mismatches

  • increased fragility when the platform evolves

By allowing controls to self-declare when they should “fall silent,” the system becomes far easier for developers to reason about, especially for complex, multi-state control groups.

How emptyIf improves extensibility

:check_mark: Keeps logic in one source of truth

Currently, the schema already knows all the conditions.
But hooks must re-express some of those conditions.

emptyIf lets the schema express the final piece:
“In this state, this control should not produce output.”

Components then don’t need duplicated state machines.

:check_mark: Drastically simplifies javascript

With emptyIf in place, a hooks js can generate macros / compiled attributes by simply:

result = allControlOutputs.filter(Boolean).join(" ");

No conditionals.
No branching logic.
No state replication.

:check_mark: Safe, backward-compatible enhancement

  • Existing controls remain unchanged

  • Existing hooks logic continues to work

  • Developers who don’t need this feature can ignore it

  • Component authors who want cleaner output logic can adopt it

Example (generic)

{
  "id": "exampleControl",
  "format": "example-{{value}}",
  "visible": "mode != 'off'",
  "emptyIf": "mode == 'off'"
}

This pattern is extremely useful when a group has multiple states where some controls become invisible and should not influence output.

Summary of Benefits

  • Removes state-duplication from hooks

  • Reduces inconsistencies between schema logic and hooks logic

  • Produces cleaner, more maintainable components

  • Increases flexibility for future control types

  • Keeps the system declarative and predictable

  • Requires minimal change to your existing schema system

  • Delivers a large benefit for a small implementation cost

3 Likes

And a real world example of a much simplified hooks.js if emptyIf were implemented.

for two complex groups with multiple complex possible states, the js can be much simplified.

// List all id's that I know will possibly return a useful value or "" for each group

const BG_GROUP_IDS = [
  "globalBgGradientFromColor",
  "globalBgGradientToColor",
  "globalTextColor",
  "globalTextColorOpacity",
  // etc...
];


const BORDER_GROUP_IDS = [
  "globalBorderWidth",
  "globalBorderColor",
  // etc...
];


function buildMacro(props, ids) {
  return ids
	.map((id) => props[id])
	.filter((v) => typeof v === "string" && v.trim() !== "")
	.join(" ")
	.trim();
}


const transformHook = (rw) => {
  const p = rw.props;

  // Expose the macro to the template
  rw.setProps({
	node: rw.node,
	globalBgMacro: buildMacro(p, BG_GROUP_IDS),
	globalBorderMacro: buildMacro(p, BORDER_GROUP_IDS),
  });
};

exports.transformHook = transformHook;

Yes, please!

Bug in current “enabled” key as well.

Currently does nothing. Does not hide the control when evaluated, and does not disable the control from user input. Does not stop value getting to rw.props if thats what this was meant to do, like I proposed for emptyIf?

{
  "groups": [
    {
      "title": "Tester",
      "properties": [
        {
          "title": "Mode",
          "id": "mode",
          "segmented": {
            "items": [
              {
                "value": "enabled",
                "title": "Enabled",
                "default": true
              },
              {
                "value": "disabled",
                "title": "Disabled"
              }
            ]
          }
        },
        {
          "title": "Number",
          "id": "myNumber",
          "enabled": "mode == 'enabled'",
          "number": {
            "default": 100,
            "subtitle": "a simple number input"
          }
        }
      ]
    }
  ]
}

Hi @Doobox

Thanks for taking the time to propose this, it’s great to see developers asking for improvements to the Elements APIs :smiley:

All of us have read your proposal and we are going to consider it internally before we make any snap decisions on adding to the property control API.

Honestly, the chatgpt-ified proposal has made the proposal harder for me to understand as there’s a lot of information, especially for what I think is a simple request.

In short, and if I am understanding you correctly, you want to stop having to do a conditional check in the hooks.js file before returning (or not) other control values.

So, currently you’d have to do this:

const transformHook = (rw) => {
    const {
        // colorType is the control that sets the type of color the user wants,
        // in this example it would be either 'solid' or 'gradient'
        // colorType would be used to show/hide other controls
        // via the `visible` prop in properties.json
        colorType,
        
        // These three props are conditional, and should/shouldn't
        // be used based on the value of colorType
        colorBgSolid,
        colorBgGradientFrom,
        colorBgGradientTo
    } = rw.props;

    // Check colorType, return the appropriate classes
    const colorClasses = colorType === 'solid'
      ? colorBgSolid
      : `${colorBgGradientFrom} ${colorBgGradientTo}`

    rw.setProps({ colorClasses });
};

exports.transformHook = transformHook;

and instead, you’d like to do this:

const transformHook = (rw) => {

    // all three of these controls have the new `emptyIf` property
    const { colorBgSolid, colorBgGradientFrom, colorBgGradientTo } = rw.props;

    // no need to check colorType each prop will
    // either have a value, or be empty, based on the new `emptyIf` property
    const colorClasses = [
        colorBgSolid,
        colorBgGradientFrom,
        colorBgGradientTo
    ].join(" ");

    rw.setProps({ colorClasses });
};

exports.transformHook = transformHook;

Is that basically what you’re asking for? If not, can you give us a concrete code example of what you’re currently having to do, and what you’d like to do instead.

Thanks :slight_smile:

1 Like

Exactly, yes.

So yes

// each prop will either have a value, or be empty, based on the new `emptyIf` property.

We can then do with that what we will.

Re broken “enabled” key. Might be with considering just using that key for this purpose, as its currently broken, so no one can be using it :man_shrugging: Unless you really intended and want enabled to simply disable user input on any given control.

1 Like

Great, now I fully understand what we’re aiming for :smiley:

That might be an option, and it’s why we need to consider what/how/if we proceed with this emptyIf proposal before making any snap decisions.

I’ll add a ticket for us to look in to this further :slight_smile:

2 Likes

also, just a thought off the top of my head (this might not work, or be doable)…

what if we had disabledValue instead?

You could set the value of a disabled control to an empty string, false, or perhaps a tailwind class… that might make this more flexible for all of us :smiley:

1 Like

That would be way better and more flexible. Cant think off the top of my head what else I’d like to return for “disabledValue” other than ““ but I’d bet it will be a godsend one day.

It was going to be my proposal, but didn’t want to push my luck, so went for the lesser option :joy:

2 Likes

Thanx for contributing - didn’t know you could specify commands - new to me - thought there were basic established computer commands and that was it…

Those aren’t ‘commands’, they’re just keys you can invent for a JSON structure. JSON is only a data format, so you can create any keys you want as long as the software that reads it knows what they mean. Elements in this case.

Now I’m pushing my luck :joy:
But would be very neat to extent the current “format” key.

"format": {
    "value": "opacity-[{{value}}%]",
    // Optional
    "disabled": {
        "condition": "someControl == 'theValueToCheck'",
        "value": ""
    }
}

Maybe a bit late in the day for that for backward compatibility.

Keys commands potato tomato :slight_smile: lol :joy:

I thought it was ‘all figured out’ in 2025

That there couldn’t be new keys…

Very interesting thread…

Did you get a degree in computer science?

They say many musicians would be good coders and good at video editing… similar brain types….

Carpentry (city & guilds) :grinning_face_with_smiling_eyes:

The day job:

excellent work/quality from photos, nice

Been off work a few weeks with a knackered knee, hence my bugging ben and dan daily :joy:

Got to ask, where did you learn programming?

How long have been doing this?

Just sort of picked up web dev about 15 years ago , much the same as a lot of guys are doing here. Always wanted to get into app dev, but Obj-C language at the time just wouldn’t click for me.
Then Swift followed by SwiftUI came along and it just felt intuitive.

Want to do my trumpet app lol :laughing:

I started version 1 got the graphics done :white_check_mark: then hired the same person to program version 1 - 2 and 3

I’m debating trying chat gpt - I tried to redo version 3 rewriting one section would break another… did this for 3-4 days

Deleted the results.

Chat GPT is telling me now that starting from scratch- new libraries etc… would be a breeze and take a few hours..

I doubt it - but that’s what it is saying…