Requesting Help with Component design & implementation

It is at times like these I feel the burden of my novice-level JS skills. :frowning:

I have a UI requirement that goes something like this:

  • display a drop-down list with no value selected
  • display a red X next to the drop-down list when a value is selected
  • display a new drop-down list below the drop-down list with a value
  • if the number of “selected values” equals the maximum amount, stop displaying new drop-down lists
  • if a red X is clicked remove the drop-down from the page and move lower selected values up
  • if the number of “selected values” is less than the maximum, display a drop-down list at the end of the list

(hopefully I explained that clearly)

I’ve been wracking my brain on how to implement this and coming up with a complete blank.

How can I create a component that inserts itself into the DOM? Is this one component or more? What do I need to consider when implementing this behavior?

Any guidance/advice will be greatly appreciated. Links to online articles would be super cool, too. I want to learn how to implement this, which means understanding how it works.

Thanks!

P.S. I have additional requirements for similar “lists” that add additional fields (date fields, numeric fields) where filling out all the fields causes a new set of fields to get displayed.

So you can probably accomplish this with either an Ember Array or a hasMany relationship the same way, but let’s assume it’s the Array. Your template could look something like this:

{{#each dropdownValues as |dropdownValue index|}}
  {{my-dropdown value=dropdownValue onChange=(action 'dropdownChanged' index)}}
  {{#if dropdownValue}}{{clear-value-button onClick=(action 'dropdownCleared' index)}}{{/if}}
{{/each}}

Then in your actions you have to handle a few scenarios:

  • a dropdown value changes from null => a real value, push “null” to the end of the array (only if length < max values) and update the array item that changed (you have the index and the new value passed into the action)
  • a dropdown value changes from a real value to a different real value, just update it in the array (you have the index and the new value)
  • the dropdown cleared action is clicked, you have the index so just remove that value from the array

EDIT: to clarify this assumes you have a dropdown component that uses DDAU one-way binding, you’ll definitely want that

EDIT 2: also assumes your clear button is a separate component but it could just as easily (probably more easily) be part of the dropdown component, same basic deal, you could even handle all scenarios with one action

1 Like

It often helps to think about component design from the standpoint of data ownership.

In this case you have a dropdown component which is responsible for displaying a value, and utilizing actions that can update or clear the value.

Then you would want a “manager” component (or controller if you chose) that manages the array of values (and therefore what gets rendered in the DOM). The manager is responsible for holding the array, rendering the array of dropdowns, and responding to actions from each subordinate dropdown to update/clear values.

1 Like

Right now we’re using ember-power-select. I’m not sure how to tell if it uses DDAU. I assume it does… shrugs

But, that aside, Thank you very much for your response. And I think I understand it.

I do have a model that defines a hasMany relationship that will used with the list of drop-downs. So dropdownValues is probably that relationship: model.codes.

my-dropdown is the component responsible for displaying the list of values, and dropdownChanged is implemented on the component that is the manager (from your next reply).

clear-value-button is the component that displays the red X and its action, dropdownCleared is also implemented in the manager component.

I know I’ll get better over time at this. I’m feeling frustrated and inpatient. :slight_smile: Thank you for your words of wisdom.

I hadn’t thought of the “manager” component until you mentioned it. That makes sense to me because I was visualizing/wanting something like that from the beginning. I just couldn’t see how the pieces fit together.

Follow-up Question: how are the drop-downs added? removed?

I don’t have time to dig into the question details (and @dknutsen is doing a great job there anyway), but I just want to say: stick with it! We were all there once, and the patterns click into place over time. You’ll get the hang of it! :+1:

2 Likes

Yeah the whole data ownership thing sorta (at least in my understanding) evolved from the same set of ideas that DDAU did (heavily influenced by React when it was first introduced). Basically clarifying what thing “owns” the data (stores it and mutates it). DDAU stands for Data Down Actions Up, and basically means the ideal pattern for this sort of thing is figuring out who “owns” the data (generally the “highest up” place that it is needed) and then passing data down to subcomponents, and then having those subcomponents passing actions back up to the owner rather than mutating the data themselves. Back in the day the manager component/controller would pass a value down to the dropdown and the dropdown would change the value by itself. This caused lots of issues in complex hierarchies because it was hard to figure out what had mutated the value and at what level. It’s much clearer to establish data ownership and let the data owner manage the data. I haven’t used power select in a while but I’m sure you can use it in one-way binding mode even if it allows two way binding also. You basically just want to make sure that power select isn’t change any values itself, it’s just passing a change action out and letting whatever is rendering it actually do the data mutation.

Follow-up Question: how are the drop-downs added? removed?

the dropdowns are added and removed as a function of the “manager” adding or removing elements from the array. Since it is rendering each item in the array with a dropdown (via the template each helper) when you add a new “null” value to the array it would render an additional dropdown with no value selected. And when you remove the value that is “cleared” from the array it will no longer render that dropdown. So really the “each” helper, rendered by the “manager” component, is determining how many dropdowns to render. Does that make sense?

1 Like

Ah, yes. That does make sense. Thank you for the explanation.

Good luck! And as @chriskrycho said, stick with it! You’re doing great. IMO component design patterns are one of the hardest things to learn because they’re more an art than a science. The nice thing is this subject really transcends specific frameworks so a lot of the ideas discovered in React/etc components can be applied to Ember apps. If you’re looking for blog posts to read anything written fairly recently about DDAU, contextual components, higher order components, etc. are probably good places to start. Also I think EmberMap and the rock’n’roll with ember book both have pretty good component design examples.

1 Like

Sorry to bother you but I was wondering if you help me over a sticking point. I have everything implemented, and mostly everything works! :smiley:

I went with a component for my “list item” because each “item” will have multiple elements (i.e. a drop-down, a drop-down plus a numeric field, a drop-down plus a date field, etc.).

Is there a way, in my “manager” component, to force a refresh, to re-render the page?

I have the following code in my “manager” and it does not work as expected:

conditionCodeChanged(index, option) {

  debug(`${this.toString()}; conditionCodeChanged(${index}, ${option})`);
  // NOTE: This does not update the display
  const items = get(this, "items");
  const conditionCode = items.objectAt( index );
  const parts = option.split("-");
  set(conditionCode, "code", parts[0].trim());
  conditionCode.save();
  const amt = 1;
  items.replace(index, amt, [ conditionCode ]);
  items.enumerableContentDidChange();
  this.rerender();

},

I thought if I changed the items array this would trigger the refresh. It did not. I have a feeling ember-power-select might be thwarting my efforts too, but that’s just a hunch, and one that’s difficult to run down.

Second Question: How can I trigger my own events? I think I want to do this as my “items” become more complex.

I may be able to answer my own question with this page in the guides: https://guides.emberjs.com/v2.18.0/components/triggering-changes-with-actions/

Going this route may allow me to let ember-power-select do its thing and then call back into the “manager” component.

Here goes nothing… :smiley: