Custom App Connect components

Easy, just two labels required…

Team Extension
Pleb Extension

2 Likes

That clearly is the perfect distinction. At a glance you already know in which extensions you can trust for your production ready projects.

1 Like

We will be getting to extension packaging later on. There we will have indeed “official/core” and “other” extensions. This is similar to how Discourse for example is organizing their plugins.

Initially we will allow app connect extensions per project - like the way server connect extensions work for now.

3 Likes

That would definitely be a huge time saver. No more messing around with dmx.parse and stuff.

But I am still a bit skeptical about the performance. I have seen AppConnect die too many times as I add more and more dynamic stuff. :sweat_smile:

1 Like

@george I believe it’s possible but I just want to confirm with you. Would it be possible to create custom components that extend a core one?

I have seen core components extending other core components so I am guessing it would be possible.

But I don’t know if this would be possible from the UI. I am guessing we wouldn’t be able to add hjson to the core component rules file.

But would we be able to add a new hjson altogether and a new component that just extends it?

An example. Take the calendar component. Let’s say a new version comes out with interesting features and you guys are not able to squeeze upgrading the core component in the roadmap for a long time.

Could I write a dmx-jon-calendar component that extends dmx-calendar and just create a hjson for the new attributes and data instead of forking the dmx-calendar completely?

That way someone could

<dmx-calendar id=“myCalendar” attributes…></dmx-calendar>
<dmx-jon-calendar id=“myJonCalendar” extends=“myCalendar” attributes…></dmx-jon-calendar>

And add the core attributes in the App Connect panel for both components separately.

So when you eventually add the missing features to the core component it will be easier to deprecate the custom component and users will be able to migrate easier.

1 Like

We do have indeed an extend possibility for both the components and the UI. Many components already extend each other like a bare app connect form controls with bootstrap form controls.

But let’s get the base first shall we :slight_smile:

2 Likes

Great news! Just wanted to make sure to avoid having to backtrack but it seems you got your bases covered.

I am just happy with having the base for now.

@patrick what do you think about using ES6 array difference to update?

I think I saw long time ago in some old components that you used looping and stringify to compare but I may be wrong.

What are you using lately?

I thought about using a difference due to the simple code but I might be missing something. Do you know a simpler and reliable way?

if (this.props.data.filter(x => !props.data.includes(x)).length) {
//update data
}

Edit: I actually was missing something. I probably need to add symetric difference to the above.

In old code we used the JSON.stringify like:

if (JSON.stringify(this.props.data) != JSON.stringify(props.data)) {
  // update
}

We are removing these since they are slow with large datasets, we now have our own compare function that is more optimized. We now use:

if (dmx.equal(props.data, this.props.data)) {
  // update
}

We only use the above method for complex objects and arrays, for native types we compare directly.

Your function is not reliable. You check if the element from this.props.data is missing in props.data, this fails when this.props.data is missing a property.

When this.props.data = ['a', 'b', 'c'] and props.data = ['a', 'b'] then your check will see it as changed. When this.props.data = ['a', 'b'] and props.data = ['a', 'b', 'c'] then your check will think the data is the same.

Also our equal function also checks the order of the array items, maybe the arrays have the same items but 2 items are swapped, you probably want to update the DOM to represent the swapped items.

1 Like

Thanks for the insights @patrick dmx.equal() works a charm!

There is a small typo in the code though. I’m assuming you wanted to use the NOT operator:

if (!dmx.equal(props.data, this.props.data)) {
  // update
}

yes, it should be not equal :slight_smile:

1 Like

Hi @patrick, sorry to nagger you with questions regarding this.
I’m doing quite some progress.

Still there is something elusive to me.

How do you make sure the props equal condition doesn’t interfere with the fact that the component is waiting for SC data to be available?

Right now I have a chart that in 80% percent of the cases will render the data correctly, but the other 20% it will render empty because most probably SC data was not available at the time of update, but when it is available old and new props are now equal so it will not run the update.

Maybe it’s being handled around here:

image

But I’m having a hard time understanding the minified variables :smiley:

Any tips? Right now I’m working around this by forcing an update on the SC success event but I would rather understand what is the strategy you follow for components.

The update of each component is called after the state of any component is changed. So initially your data property which is bound with the server connect is null, at some moment you will get the data in an update call, not each update call will have a change in the props. After a server connect has loaded its data it will trigger the update of all components on the page, so at some point you should see the data being changed.

How do you detect the data change in your code?

So when a SC loads data it will trigger a dmx.requestUpdate() and this will call the update() method of all components present on the page?

I was just querying the props.

update: function (props) {
    if (!dmx.equal(props.xaxis, this.props.xaxis)) {
      chart.updateOptions({
        xaxis: {
          categories: this.props.xaxis,
        },
      });
    }
    if (!dmx.equal(props.data, this.props.data)) {
      chart.updateSeries(
        [
          {
            data: this.props.data,
          },
        ],
        true
      );
    }
  },

Or do you mean I should write a helper to detect that data is available and trigger an update? I was under the impression that this was already handled reactively by AC. But maybe what I’m describing is just an edge case that needs to be handled.

Exactly, the dmx.requestUpdate() is called whenever the data of an component is updated.

As you have it is fine, it is possible that the data is already available on render, for data that is set later you have to detect that in the update function.

Any idea in which situation it is not updating in your case? Try to add some debugging in the update function to see if it was called, what properties are set and if it comes in your condition.

1 Like

Not really. But I will debug thoroughly as per your advice.

So I haven’t find anything weird in the implementation, but I’ve noticed that everytime it fails to load the data it’s because some other js library didn’t initialize before(i18next).

I’m loading and initializing i18next via vanilla js so I’m taking the opportunity to migrate it to a dmx component to have further control as to how it interacts with other AC custom components. When I had apexcharts initialized via vanilla js there were not conflicts with i18next so I think this could be the reason.

I will keep updating here.

I’ve narrowed it down to a problem in the actual library when responsive mode for the chart is activated. I will report it in the library repository if not done already.

Edit: It was already reported some months ago.

The problem was that as the initial data on render was null so it was difficult to pinpoint.

It wasn’t until I deactivated the animation for the library that I saw for a microsecond that the data was actually being rendered to disappear immediately after.

So because of the animation and the reset to initial value it seemed that it was actually not rendering at all.

@patrick editing this as the forum won’t let me add another reply.

Anyway, now that this is solved I’ve bumped into other issues.
I hope all this nagging helps you guys when you have to create the docs. Hopefully I’m doing some common questions regarding custom components development.

So I’ve migrated my i18next integration to a custom component. It works perfectly. I’ve created the dmx component to setup and init the library and I also threw in a custom formatter there to translate the strings.

I was wondering if I could create a dmx-i18n attribute similar to the dmx-text to complement the custom formatter.

I created a simple attribute and performed some tests and I found some interesting behaviour.

dmx.Attribute("i18n", "mounted", function (e, t) {
    this.$addBinding(t.value, function (t) {
        e.innerText = i18next.isInitialized ? i18next.t(t) : t
    });
})

I have the following html:

  <div>Language: {{i18next.getLanguage()}}</div>
  <div>Formatter: {{'perks'.t()}}</div>
  <div>dmx-i18n: <span dmx-i18n="'perks'"></span></div>

And it will render this:

image image

This is expected as the library is not initialized when the attribute is evaluated.

If I run a dmx.requestUpdate() on the console it will not reevaluate which is kind of expected also as the content of the attribute dmx-i18n is static and not dynamic.

Question: Is there a workaround or a programatically way to make (specific)custom attributes to reevaluate? i.e from the update() method of my custom component.

You could try something like:

dmx.Attribute("i18n", "mounted", function (node, attr) {
  this.$addBinding(attr.value, function (value) {
    if (i18next.isInitialized) {
      node.innerText = i18next.t(value);
    } else {
      node.innerText = value;
      i18next.on('initialized', function update() {
        node.innerText = i18next.t(value);
        i18next.off('initialized', update);
      });
    }
  });
});

Haven’t tested it, but when not initialized then wait for the initialized event and update the value then.

1 Like

@patrick adding listeners did the trick! Thanks for the insights.

I changed the onInit to onLoaded as that is fired when the resources(translation files) are loaded.These have to be available before calling the translation function. I also added a listener for the language change. As this custom attribute only makes sense if certain events are fired i simplified it by just adding the two listeners.

All in all, we now have a dynamic attribute that reads a static value.

I am pretty sure I will have to circle back to this at some point to handle the onInitialized event to prevent some edgy case

dmx.Attribute("i18n", "mounted", function (node, attr) {
    this.$addBinding(attr.value, function (value) {
        i18next.on('languageChanged', function update() {
            node.innerText = i18next.t(value);
        });
        i18next.on('loaded', function update() {
            node.innerText = i18next.t(value);
        });
    });
});

Kapture 2021-06-22 at 17.03.49