Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Data binding parent-child relationships in Aurelia

The Code:

I have two classes:

export class Shipment {
    shipmentId: number;
    widget: Widget;

}

export class Widget {
    widgetId: number;
    name: string;
}

Then I have a ShipmentUi view-model that has an instance of shipment (this.shipment).

And in the ShipmentUi view I compose part of the UI show the WidgetUi that allows selection of the Widget:

<compose view-model="src/views/widgetUi" model.bind="shipment"></compose>

The WigetUi's view-model saves off the shipment. So WidgetUi has a this.shipment.

And then widgetUi's view shows a selector:

<select value.bind="shipment.widget" >
    <option class="dropdown-toggle" repeat.for="widget of widgets"
            model.two-way="widget">${widget.name}</option>
</select>

The Question Setup:

In my compose tag (on ShipmentUi's view), I would rather bind to shipment.widget.

This would have WidgetUi's view-model only get a this.widget. (The WidgetUi class does not need to see or know about the shipment. Its sole function is to allow selecting a Widget. It should not need to care if it is for a shipment or for something else.)

But, as I understand Javascript, this is not going to work.

Because if I just pass in the reference to shipment.widget, then WidgetUi will have just a reference to the widget part. At first WidgetUi's this.widget will have the same reference as ShipmentUi's this.shipment.widget.

But when the user selects a different widget, WidgetUi this.widget will get a different reference (to the newly selected widget in the dropdown). But ShipmentUi's this.shipment.widget will will still reference the original widget.

The Question:

When binding to child objects in Javascript, do you always have to pass in the containing object if you want to know about swap of the child object?

The reason for this question is that my tests are not 100% conclusive. So I am hoping someone can clear it up for me.

I am also hoping I am wrong somehow, as I really don't like the idea of having to expose all the data in the containing classes. (Giving access to shipment in the WigetUi class in this case.)


Re-asking (clarification):

Fabio Luz requested some clarification on what I am asking. So here is an attempt. This walks through the example above, but changing it to the way I would LIKE it to work.

I have two Widgets. Sharp Widget and Dull Widget.

ShipmentUi.js:
This class has the variable this.shipment.widget. I am going to say that its value is 'A3' (arbitrary memory value). 'A3' is a reference to a widget that has a name of 'Sharp Widget'.

I then pass the widget down to the WidgetUi class:

<compose view-model="src/views/widgetUi" model.bind="shipment.wiget"></compose>

WidgetUi.js:
The WidgetUi class has:

activate(widget: Widget) {
   this.widget = widget;
}

So now in WidgetUi this.widget also has a value of 'A3'. That value is a memory reference to the Widget that has a name of 'Sharp Widget'.

Now the user uses this select element to change the Widget:

<select value.bind="widget" >
    <option class="dropdown-toggle" repeat.for="widget of widgets"
            model.two-way="widget">${widget.name}</option>
</select>

This time I bind to widget (instead of this.shipment.widget like I did above).

Then user picks a widget with the name of 'Dull Widget' using the select. That widget has a value of 'B7'. And 'B7' is a reference to the widget named 'Dull Widget'.

As I understand JavaScript, WidgetUi's this.widget now has a value of 'B7' (which is a reference to 'Dull Widget'). (This is done via the Aurelia data binding system.)

But ShipmentUi's this.shipment.widget is still 'A3' (which is a reference to 'Sharp Widget').

This is not what I wanted when I bound this.shipment.widget to the compose element. I wanted the updates to the widget object to be reflected in the shipment. (Note, if I had just updated widget.name, then it would have been updated.)

So, from what I can see, I have to pass in the full parent to the compose element (this.shipment in this case), if I want an assignment to be captured.

I am hoping I am wrong (or there is a workaround), because passing the parent object makes me share details that the "child" class does not need to know about. (ie it breaks data encapsulation)

I guess I could make a "holder" between each layer of my classes. For Example: this.shipment.holder.widget and holder would just have the widget in it. But this is kinda ugly... I hope there is another way...

So, my question is: Am I right with my above statements? And if I am, is there another way that keeps my object model clean?

like image 704
Vaccano Avatar asked Sep 26 '22 07:09

Vaccano


1 Answers

If I understand the question correctly you're looking for a way to share the minimum amount of data with the widgetui component. Instead of giving it the whole shipment object so that it can manipulate the shipment.widget property, you'd rather give it a property accessor to the widget property.

Good news: this is exactly what @bindable is designed to do. All you'll need to do is stop using compose and craft a custom element with @bindable properties representing the minimum amount of data the custom element needs to do it's job. For example:

widget-picker.js

import {bindable, bindingMode} from 'aurelia-framework';

export class WidgetPicker {
  @bindable({ defaultBindingMode: bindingMode.twoWay }) widget;
  @bindable widgets;
}

widget-picker.html

<select value.bind="widget">
  <option repeat.for="widget of widgets" model.bind="widget">${widget.name}</option>
</select>

usage:

<widget-picker widget.bind="shipment.widget" widgets.bind="widgets"></widget-picker>

Example:

https://gist.run/?id=4c726da335aaecefd80b

like image 75
Jeremy Danyow Avatar answered Nov 10 '22 02:11

Jeremy Danyow