1

I've got a JS module which I'm trying to use in lightning. I've decided to wrap the module up in a component of it's own which I could then reuse where needed. One of the parameters for the module is a rendering function which gets called as it works through some data and uses the function to generate some DOM elements which are then appended to an element on the page. I've tried making this module parameter available as an attribute to my wrapper component, but if I try and pass a function to it from a parent component I get the following error:

Error in $A.getCallback() [Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'.]

Here's an example of how I've structured the code so far, I've left out the controllers as you can imagine they simply call the helper methods.

ThirdPartyLibWrapper.cmp

<aura:component>
    <aura:attribute name="data" type="Object" access="global" />
    <aura:attribute name="renderData" type="Object" access="global" />
    <aura:attribute name="onReady" type="Aura.Action" default="{!c.init}" access="global" />

    <aura:method name="init" action="{!c.init}" access="global" />

    <ltng:require scripts="{!$Resource.thirdpartylib + '/thirdpartylib.js'}" afterScriptsLoaded="{!v.onReady}" />

    <div id="thirdparty"></div>    
</aura:component>

ThirdPartyLibWrapperHelper.js

({
    init: function(component)
    {
        var params = {
            data: component.get('v.data'),
            renderData: component.get('v.renderData')
        };

        var thirdPartyLib = new ThirdPartyLib(params);
        thirdPartyLib.init();
    }
})

ParentComponent.cmp

<aura:component>
    <aura:attribute name="data" type="Object" />
    <c:ThirdPartyLibWrapper aura:id="lib" data="{!v.data}" onReady="{!c.doInit}" />
</aura:component>

ParentComponentHelper.js

({
    doInit: function(component)
    {
        var lib = component.find('lib');
        lib.set('v.renderData', helper.renderData);
        lib.init();
    },

    renderData: function(item)
    {
        var div = document.createElement('div');
        div.innerHTML = item.name;
        return div;
    }
)}

I understand why I'm getting this message, the locker service is converting the element generated in renderData() in to a SecureElement which isn't a valid element as far as appendChild() is concerned. If I move renderData() to the ThirdPartyLibWrapperHelper.js it works fine. The problem is I want it to be configurable. Any ideas other than turning the Locker Service off?

2 Answers 2

1

So after refactoring my code to strip out the wrapper component and move everything in to the parent component I realised why this was happening. The API version of the child wrapper component was 39.0 while the parent was 40.0. This meant the function creating DOM elements in the parent was subject to the Locker Service which was converting those elements to SecureElement. When the child component tries to use the SecureElement, it doesn't recognise it because it's not using the Locker Service. After changing the version on the child component to 40.0 everything magically started working, which is rare for the Locker Service!

The important lesson here is when trying to pass things such as DOM elements (or perhaps anything the Locker Service converts to a secure object) between components, both components must be API version 40.0 or higher i.e. Locker Service enabled.

Additionally, I found this post very helpful in making sure my JS module works with the Locker Service, specifically the part about attaching globals to the window:

0

You have two options:

Option 1 (old way):

If you are modifying the DOM you should be doing it in a renderer function - afterRender, reRender, render or using the valueRender event.

Option 2

Depending on what you are trying to do, you can listen to the value render event on the child and despatch an event to the parent to tell it that the child is rendered, eg child component:

<aura:handler name="render" value="{!this}" action="{!c.tellParent}"/>

Or you could attach it to the parent. Or you could do both and don't do anything with your third-party component until you are happy that you have got your custom childDoneRendering event and also the parents doneRendering event.

Basically as long as you stick to using these events, your DOM manipulations should work fine.

Ref here

1
  • the renderData() function I posted an example of is something that would be called multiple times as part of a loop by the third party library as it loops through a data array and essentially defines how the data is rendered. This is something I ideally wanted to be configurable outside of my library wrapper component, but it seems perhaps that simply isn't possible in lightning.
    – marktuk
    Commented Aug 19, 2017 at 19:32

You must log in to answer this question.

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