Pass calling ui component reference to actions that are executed via trigger/event callback (dynamic/data/functional support in general)

It would be great to know the execution context for actions that are executed as a result of a trigger/event callback. There are a couple of things like this that are missing that would help make working in UI Bakery more functional and data driven.

Other examples of this type of functionality:

  1. To be able to identify the uib component name for custom components when operating in the context of the custom component code itself, since the exposed surface for ui components on the uib editor side is via their name (in ui..) Hopefully that makes sense. I currently have to do some hacky feeling things to get similar functionality when these props could just be passed along instead.

  2. A way to reference the current component when setting properties inside itself in the uib editor. For example, lets say you’re setting up a trigger on click and want to pass params to the action, being able to do something like this.name instead of ui.componentNameWeWant.name would be great.

  3. A way to programmatically interact with and configure uib component data. It would be great to be able to create tooling inside the uib editor for interacting with uib components themselves. For example, to wire trigger events from buttons to specific actions automatically according to whatever logic I want to use. I can conceive of ways to accomplish parts of this with what we have now, but it always feels brittle and error prone to depend on work arounds for fundamental parts of the dev workflow.

Thank you for your consideration!

Not sure when this was implemented, but I noticed today that, at least on Date/Time/DateTime picker elements’ On Changed triggers, we now have name, event, value, and a few other properties available to pass along in the params object for event callbacks. This has already proven very useful, so I wanted to raise awareness with an example and say thank you for implementing it!

Quick rundown of my own use case.

Separate Date picker and Time picker with a shared js Date state object for the value where the pickers update their respective half of the shared Date according to the scope defined.

Quick aside - this would be an argument in favor for also providing a property that conveys the ā€˜type’ of the calling element context from the definitionId or component name in the ui, so: ā€œdateEditā€/ā€œtimeEditā€/ā€œdateTimeEditā€ or ā€œDate pickerā€/ā€œTime pickerā€/ā€œDate & Time pickerā€ so that the changeScope can be inferred instead of hardcoded like below.

In any case, this allows for a structure that’s very close to what I had in mind when I made the feature request:

Component: timePicker

Trigger: On Change
Action: setSharedDateTime
Action Arguments:

{
  // small wrapper so we don't have to pass a string or hardcode the action to use a specific state var
  targetState: {
    get: () => state.selectedDateTime,
  	set: val => {
    	state.selectedDateTime = val;
    }
  },
  // small wrapper utilizing the new name property passed into the trigger context
  // we could also forward name, value, event, valid, etc directly if that is useful
  uiState: {
    get: () => ui[name].value,
  	set: val => {
    	ui[name].value = val;
    }
  },
  // defines what half of the shared js Date object should be updated by this event: Date, Time, or both
  changeScope: 'time'
}
Action: setSharedDateTime

JS Code Step

const VALID_SCOPES = ['date', 'time', 'datetime'];

function isValidDate(value) {
  return value instanceof Date && !Number.isNaN(value.getTime());
}

function isValidScope(value) {
  return VALID_SCOPES.includes(value);
}

console.log('params:', params);

const targetState = params?.targetState;
const uiState = params?.uiState;
const scope = isValidScope(params?.changeScope) ? params.changeScope : null;

const incomingDate = uiState?.get?.();
const currentDate = targetState?.get?.();

if (!targetState?.get || !targetState?.set || !scope || !incomingDate) {
  return isValidDate(currentDate) ? currentDate : null;
}

const current = isValidDate(currentDate) ? moment(currentDate) : moment();
const incoming = moment(incomingDate);
const next = current.clone();

if (scope === 'date' || scope === 'datetime') {
  next.year(incoming.year())
    .month(incoming.month())
    .date(incoming.date());
}

if (scope === 'time' || scope === 'datetime') {
  next.hour(incoming.hour())
    .minute(incoming.minute())
    .second(incoming.second())
    .millisecond(incoming.millisecond());
}

const nextDate = next.toDate();

if (isValidDate(currentDate) && nextDate.getTime() === currentDate.getTime()) {
  targetState.set(currentDate);
  return currentDate;
}

targetState.set(nextDate);

return nextDate;

So now any number of date/time/datetime pickers can reference and update a shared js date object and the only thing that I need to change is what scope should be used for the update. The next step will potentially be to pull the shared parts of the Action Arguments params object out (the targetState and uiState bits) into a common space like a state var, js file, or something like that so I can update the params in one location and know that all components using that configuration will stay in sync.

Hopefully this helps illustrate what I’m referring to when I say ā€˜functional’ and ā€˜data driven’ in requests and how it might be used. I’m certain my approach here will continue to evolve, so there’s no claim that the above example is the best approach etc, but I hope it’s useful to someone regardless.

1 Like

Interesting implementation and use case.

I’m not sure since when, but I think referencing properties directly on the current UI element has been a thing for some time. Maybe I remember it wrong.

Furthermore, is there a specific reason why you used separate date and time pickers and not just the Date & Time picker component?