5

I am working on a set of Lightning Components - all in the same namespace - that allow rows to be interactively added to a page and cells within the rows to be selected from database values and that works fine. When all the work is done, logic in the root table component can walk down through all the rows and cells and save the result to the database.

But I now have a case where one of my components within a row needs to find out about the content from other rows. So, using HTML terminology, a td component needs to grab information from multiple other td components located in many tr components in the overall table component.

There is this post How to get parent lightning component's attribute in child component?. It says that getting hold of your parent component breaks encapsulation but suggests a work around of passing references to the parent component data to the child component - not sure why the "breaks encapsulation" argument doesn't apply for that case. Or it suggests using events to communicate.

Are there any alternatives that make sharing model data between a set of co-operating (not stand-alone) components convenient?

PS

It occurs to me that having a JavaScript model tree that parallels the structure with the appropriate part used as the aura:attribute on each child component is one way to go. If that model had parent references as well as child collections I don't think it would be serializable from/to the server (as there would be circular references), so those parent references will have to be added/removed in JavaScript. Then my td component can walk up and down the tree as it needs. Anyone used this approach?

PPS

I've just placed an order for Force.com Enterprise Architecture Second Edition that has a section "Lightning Architecture and Components" that may help.

6
  • 1
    I'll admit a look under the hood of some of my components attempting this will cause one to cringe. Bunch of duct tape, cups and string, and a few pigeons. So I am not worthy to answer but will be watching
    – Eric
    Commented Mar 31, 2017 at 19:35
  • Only way I could think this can be done is by using Events. Have an Component event(has info of field being edited,and index of the tr) which is fired from the <c:tableRow/> and handle it in the Parent <c:table>, which inturns tries to access the all <c:tableRow /> components. Once you get hold of the each row component, you can access td's value (specific td value based on the field or all fields values) using trInstance.find('row').get('v.myProp'). Will this approach work for you?
    – Praveen
    Commented Apr 2, 2017 at 14:27
  • 1
    @Praveen Yeah thanks. Maybe a component event to notify the root table that something has changed and then an application event to broadcast the current set of column entries back to the td cells.
    – Keith C
    Commented Apr 3, 2017 at 9:03
  • @KeithC Using Application Event to notify the td with relevant info would also work.
    – Praveen
    Commented Apr 3, 2017 at 9:12
  • @Praveen I've gone with the component event to bubble up and the application event to share the update back down the tree approach and it works fine. Just a lot of plumbing and also requires care to support all the create/update/delete scenarios. If you want to add this as a brief answer I'll accept it.
    – Keith C
    Commented Apr 3, 2017 at 10:56

2 Answers 2

3

You want to use component.set("v.attributeName", c.variableName); from a method inside either your component controller or a helper. Doing that will set the values in every component that shares the attribute among your common components when you update the values in a component's controller. Otherwise, you need to use an event handler as recommended by the documentation.

Edit:

Okay, here's some more specifics to your use case that may help point you in the right direction without writing all your code for you. At a high-level, this is the general approach that you're going to want to take.

You're going to want to define and handle nested sub-tags that come from your sub components which are output by a parent component. Doing this requires implementing a "base tag" that typically is of a type Object[]. You'll use this in a init handler to aggregate sub-tag attributes into an array of objects which can be output using an <aura:iteration>.

Getting a bit more specific and practical to your application, you're probably going to want to create components with the following attributes:

DataGridColumn

type: string - type of widget to display in the column. Valid options would be test, checkbox or button with a default of text.

label: string.

class: string. Optional style class applied to each column cell

hidden: Boolean. True to hide column. Default is false.

DataGridRow

data: string. List of delmited data items

delimiter: string. Defaults to ",".

pKey: String. Required. Primary key of the record

DataGrid

(this is where you loop through the attributes with the subtags)

cols: Private Object[]. Used by controller to store aggregated info about grid columns from <c:DataGridColumn> tags.

rows: Private Object[]. Used by controller to store aggregated info about grid rows from <c:DataGridRow> tags.

DataGridTable

(generates HTML markup with SLDS classes to render the Grid)

cols: Global Object[]. Used to output column headers into SLDS grid.

rows: Global Object[]. Used to output column rows into SLDS grid.

You'll use attributes like the following immediately under your tag to define cols and rows, etc.

<aura:attribute name="cols"
                type="Object[]"
                access="PRIVATE"
                description="The grid columns." />

You can now use the tags to initialize the body of the grid and gridtable. When you do that, you will use component.set("v.cols",obj.cols) (and the equivalent for rows) in populating the object you'll define to hold the results for display in the body of your table component. In your subcomponents, you can "push" items onto a colItems or rowData array. You don't need to get anything from the outer component since all manipulation occurs in the inner components after which it is "pushed" to the outer component for display.

I hope this helps get you moving in the right direction with your components. Recognize that you'll need to create local variables in your subcomponents and will want to use "thisTag" as appropriate in your code.

6
  • Thanks. Any insight into how that works where v.attribute is the root of a tree? Do changes only on that node get notified to the framework or do any changed non-root nodes get notified to the framework. Also appreciate you thoughts on the PS I just added.
    – Keith C
    Commented Apr 1, 2017 at 9:59
  • See me edits. I'm confident this is the approach you'll want to use. You want to create objects that are used to build other objects which can be updated by your subcomponents using JS push.
    – crmprogdev
    Commented Apr 1, 2017 at 19:30
  • I really appreciate the detail and would up-vote again of I could. But my sticking point is that in one of the editing operations of a td cell, I do need to find out about the content of the other tds in a column because the user gets to chose one of the values from that column - that is the business requirement. Hence the desire to be able to navigate both up and down the structure or find some other way to access the values.
    – Keith C
    Commented Apr 1, 2017 at 20:42
  • You'll have the row number and column numbers for the two different cells. It's a matrix. You'll need to create local lists of selected colItems that you can manipulate in controller or helper methods. You'll need to have a means of determining the primary (target) cell and the source cell (copy or link from cell) that you'll be using to push data into the colItems list you'll be refreshing onUpdate. You'll navigate by knowing the rowData# and the colItem# of each cell on the grid.
    – crmprogdev
    Commented Apr 2, 2017 at 13:20
  • Right so how does a td component get hold of its parent row then its parent table and navigate down through all the rows examining data from the other column?
    – Keith C
    Commented Apr 2, 2017 at 15:02
1

The core problem here was that a modal dialog displayed inside a td needed access to all the values of a different column in the overall table. So how to walk up the tree?

What I have done for now is to fire a COMPONENT event from the td elements of the other column that bubbles up to the root table component where the overall collection is kept and that overall collection is then immediately fired back as an APPLICATION event (a broadcast) so the modal dialogs can be updated. This works. Thanks Praveen for this idea.

A little late in the day it has occurred to me that when a child component is added to a parent component there is nothing to stop a reference to the parent component from being added to the child component. (I was wrongly thinking such references would need to be in the underlying data model.) Then controller/helper logic can walk both up and down the tree. (Yes this is strong coupling, but this is a co-operating set of components that work together not components that can be re-used in multiple places.) I may change to that approach.

PS

Broadcasting the APPLICATION event doesn't help for components added after the broadcast. So instead I have added the parent references to most components:

<aura:attribute name="parent" type="Aura.Component"/>

which for statically defined components can be set like this:

<c:child parent="{! this }"/>

or for dynamically created components passed in as one of the $A.createComponent attributes.

Then to walk up the tree in a controller or helper method stopping at the desired parent type:

methodName: function(component, event, helper) {
    var c;
    for (c = component; c !== null && !c.isInstanceOf("c:rootComponent"); c = c.get("v.parent"));
    if (c) {
        // Use c
    }
}

This avoids having to pass data references down through intermediate components and preserves flexibility for the future.

PPS

Just found a nasty side-effect of having this payment reference set when removing the component from its parent facet of this error being generated somewhere late in the rendering cycle:

This page has an error. You might just need to refresh it. Invalid component tried calling function [isInstanceOf] with arguments [aura:html], markup://aura:expression [58:0] Failing descriptor: {InvalidComponent markup://aura:expression {58:0}}

To work-around this error I'm now clearing the parent reference as part of the remove operation:

var rows = component.get("v.rows");
var cs = rows.splice(indexToRemove, 1);
for (var i = 0; i < cs.length; i++) cs[i].set("v.parent", null);
component.set("v.rows", rows);

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .