DDAU is giving me fits


#1

I’m working on an ember app, and running into a situation where what needs to happen feels to be at odds with the DDAU philosophy.

My back end is rails, and I’m using JSONAPI between my ember front-end and rails back-end. My ember app has resource routes for different tables. I have the various action handlers for creating, updating, and deleting records to my resource routes via a mixin, and I’m using the route-actions plugin to send my actions up to the resource route handler. This feels “right”, as the route for each type of data knows how to create, update, or delete it, but I can replace a handler specific for a data-type if it needs to do something unique. Below the resource route are routes for the specific types of actions; create, update, delete. For create and update, I have a form-field component which handles a record, and I pass the model down to the form component. Here’s where DDAU starts breaking down. The “save” action is triggered by the form’s submission, in order to preserve typical form behavior, but the form tag and controls are one level higher than the form-fields component, because I want to re-use that component for more than one action (create, update). It currently works because I’m not using one-way bindings, so when the save action gets triggered, the updated model is available to be sent up to the resources save handler.

I could “fix” the current arrangement by moving the form tags and controls down to the form-field component, and adding conditional logic to it for different uses, but that feels like breaking the notion of keeping knowledge where it belongs.

Now we get to the really challenging issue for me. I have some tables which have one-to-many relations, and I want to add a two-panel selection editor for those relations. It makes sense for this to be another component, so it can be re-used between different tables. It’s simple to pass in the lists of available and currently selected items, but when it comes time to save, I need to take the current “selected” list, construct a set of relations and attach them to the record being sent to the back end. That logic feels like it belongs to the “save” action for the resource, rather than the selection component- and further complicates the arrangement of having the trigger for “save” being two levels above the selection component.

It feels like this would best be solved by allowing parents to query a property of a child component; I would have no problem with being required to declare a query-able property, but, this isn’t how ember works, or seem to be the direction Ember is going.

What would be a clean approach to what I’m doing that conforms to data-down, actions up? Is there a means of passing some state from the selection component back out to the save action?

It feels like DDAU is at odds with clear, limited responsibilities.

Current arrangement:

Resource-route (has create, update, delete action handlers) -> action routes (create, update, has HTML form and save/cancel buttons in template, uses route-actions to trigger save/cancel) -> form-field component (receives model, displays and allows editing of model) -> selection component (receives lists, allows selection editing)


#2

Sample code would be helpful


#3

@lightblade - well, there’s a whole bunch of related code, which is why I tried to summarize what’s being done. Which pieces of the code would you suggest I share?

My current solution which is a horrible hack and not The Ember Way at all is to set a (previously nonexistent) property on the model within the group-selection component, and then simply customize the serializer for the model to use that group list to serialize a relationship before sending the record to the back end. It’s working, but it’s a Bad Hack.

Within my group editor component:

sortedSelected: Ember.computed('_options.@each.selected',function() {
        let sortedSelected = this.get('_options').filterBy('selected',true).sortBy(this.get('sortProperty'));
     // TODO: this is a bad hack - fix it!
    let model = this.get('model');
    model.set('groupList',sortedSelected);
    return sortedSelected;
    }),

Then in the class serializer:

serialize(snapshot, options) {
    let json = this._super(...arguments);
    let groupLinks = snapshot.record.get('groupList');
    if ( groupLinks !== null ) {
      let serializedGroupLinks = groupLinks.map(function(item) { return {'id': item.get('id'), 'type': 'access_group_links'}; });
      json['data']['relationships']['access_group_link'] = { 'data': serializedGroupLinks };
    }
    return json;
  },

Within my form-field component, I include the selection panels like this: {{component 'multiselect-panels' label="Access Cards" options=cards selected=model.accessGroupLink displayKey='name' optionKey='id' selectedKey='accessCard.id' sortProperty='name' model=model}}

Then on the back-end I create a database transaction, and delete/create the related records as needed, and attempt to save the updated master record before closing the transaction. This way the whole update either works or fails.

I’m reasonably happy with the serialization and back-end portions, but I’m struggling to find something clean and conformant with DDAU to get the users choices from the group selection component up to the save/serialization portions.


#4

Could you do something like this in your main parent template?

{{#form-fields}}
  {{group-selection}}
{{/form-fields}}

Using your form-fields component in block form allows external callers to provide additional information to be displayed inside while keeping the logic (including DDAU logic) in the caller where it belongs. More details on it here: https://guides.emberjs.com/v2.9.0/components/wrapping-content-in-a-component/