cancel
Showing results for 
Search instead for 
Did you mean: 
cancel
Showing results for 
Search instead for 
Did you mean: 

Community Tip - Learn all about PTC Community Badges. Engage with PTC and see how many you can earn! X

How can I reference my js libraries in custom extension widget

tcoufal
12-Amethyst

How can I reference my js libraries in custom extension widget

Hi Guys,

I have one question.

How can one add a js library for a custom widget extension without copying it directly into the extension.runtime.js?

We have made a custom extension which uses d3.js and d3-tip.js (newest version, not that depricated and modified version). We had to copy its contents directly into the extension.runtime.js.

I have looked at CustomChartWidgets (which they messed up FontLabel widget btw..) and there is a subfolder in ui folder:

ui/barChart/include/d3/d3.js

in barChart.runtime.js is following function (jquery):

(function () {

    // loader for extension libs

    $('head').append('<link rel="stylesheet" href="../Common/extensions/chartWidget_ExtensionPackage/ui/barChart/include/nvd3/nv.d3.css" type="text/css">');

    $('head').append('<script type="text/javascript" src="../Common/extensions/chartWidget_ExtensionPackage/ui/barChart/include/d3/d3.js"></script>');

}());

so I made the same, but you know referencing my extension and my libraries.

(function(){

  $("body").append('<script type="text/javascript” src="../Common/extensions/NetworkSchema/ui/include/d3/d3.js"></script>');

  $("body").append('<script type="text/javascript” src="../Common/extensions/NetworkSchema/ui/include/d3/d3_tip.js"></script>');

  }());

I also made a pure js version of it:

document.getElementsByTagName('body')[0].appendChild('<script type="text/javascript” src="../Common/extensions/NetworkSchema/ui/include/d3/d3.js"></script>');

document.getElementsByTagName('body')[0].appendChild('<script type="text/javascript” src="../Common/extensions/NetworkSchema/ui/include/d3/d3_tip.js"></script>');

But neither of those seem to do the trick.

Any advice?

One followup - how does the concat function (which makes that combined.extension.js) handles duplicate libraries?

If I add one extension which contains let say myLibrary.js and then I add another which has the same library (directly and/or as reference/link), what could happen?

Is that being taken care off somehow?

Is there any way how to reference from my extension js libraries that I need?

Let say that I have access to target machine, so I can copy libraries directly into the Thingworx/Common directory. Is that wise even?

Why I need to do it?

We had append d3.js directly into our extension.runtime.js, but there must be a bug in TW 6.5.2 extension import parsing function.

There are special characters inside our extension (d3.js contains special characters like euler numbers, pi etc...) and after extension has been added combine.extension.js

contained jibberish like this:


var ε = 1e-6, ε2 = ε * ε, π = Math.PI, τ = 2 * π, τε = τ - ε, halfπ = π / 2, d3_radians = π / 180, d3_degrees = 180 / π;


instead of this:


var ε = 1e-6, ε2 = ε * ε, π = Math.PI, τ = 2 * π, τε = τ - ε, halfπ = π / 2, d3_radians = π / 180, d3_degrees = 180 / π;

so we had to manually repair entire combined.etension.js, which is pain

further more, if we add another extension now, it makes that CAT process again and we end up at the same place.

We have not noticed this behaveuior in TW 6.6 and 7.1

If I make my libraries being referenced indirectly, this might help us avoid that issue.

Thanks

Tomas

17 REPLIES 17
ttielebein
12-Amethyst
(To:tcoufal)

Hello,

Have you seen the small blurb about this in the Extension Development guide found here: http://support.ptc.com/WCMS/files/170215/en/thingworx_extension_development_user_guide.pdf​ ?

"Third-Party JavaScript Libraries If the custom widget needs to use third-party JavaScript libraries, the best practice is to create a subfolder in the widget folder (for example, /ui/<widgetname>/<jslibrary>/) and put the third-party library files there. These files can be referenced from the ide.js and runtime.js files using the following relative path: ../Common/extensions/<extensionname>/ui/<widgetname>/<jslibrary>/"

I hope this helps!

Tori

tcoufal
12-Amethyst
(To:ttielebein)

Yes I've seen it,

but Javascript has no include, import or require statement, so how can I reference it?

Only way I know how is to do it via DOM manipulation, e.g.

document.getElementesByTagName(myTag)[0] or via document.getElementById(myTage) have not tried that one though.

But again, it does not seem to work. I cannot see my library code anywhere. :\

Tomas

tcoufal
12-Amethyst
(To:ttielebein)

One question,

is necessary to remove the extension prior importing new one?

Or simple version change in metadata.xml will suffice?

ttielebein
12-Amethyst
(To:tcoufal)

I believe it is necessary to remove the old extension. I have always had to remove it AND update the version in the metadata.xml. Otherwise, if I leave the version the same, I usually need to restart Tomcat to see the changed I made. I am not sure about Widget development specifically, but this has always been the case for me using the Extension SDK

For UI Extension development a Tomcat restart it's not needed, just a Browser Window refresh.

tcoufal
12-Amethyst
(To:ttielebein)

Sorry to bother you, but I am still stacked on referencing external javascript libraries.

It says:

files using the following relative path: ../Common/extensions/<extensionname>/ui/<widgetname>/<jslibrary>/",

but How.

This a basic runtime.js structure

TW.Runtime.Widgets.networktopology= function () {

  var valueElem;

  this.renderHtml = function () {

  // return any HTML you want rendered for your widget

  // If you want it to change depending on properties that the user

  // has set, you can use this.getProperty(propertyName). In

  // this example,we'll just return static HTML

  return  '<div class="widget-content widget-networktopology">' +

  '<span class="networktopology-property">' + this.getProperty('NetworkTopology Property') + '</span>' +

  '</div>' +

  '<script src="../Common/extensions/PNnetwork/ui/networktopology/jslibrary/test.js" type="text/javascript"></script>';

  };

  this.afterRender = function () {

  // NOTE: this.jqElement is the jquery reference to your html dom element

  //  that was returned in renderHtml()

  // get a reference to the value element

  valueElem = this.jqElement.find('.networktopology-property');

  // update that DOM element based on the property value that the user set

  // in the mashup builder

  valueElem.text(this.getProperty('NetworkTopology Property'));

  };

// this is called on your widget anytime bound data changes

  this.updateProperty = function (updatePropertyInfo) {

  // TargetProperty tells you which of your bound properties changed

  if (updatePropertyInfo.TargetProperty === 'NetworkTopology Property') {

  valueElem.text(updatePropertyInfo.SinglePropertyValue);

  this.setProperty('NetworkTopology Property', updatePropertyInfo.SinglePropertyValue);

  }

  };

};

I have placed my reference as HTML tag, but that does not work...

Any suggestions?

Thanks

TOmas

Hi Tomas,

When I want to reference a library, I just write it as a resource, here a sample where I have a common.js file shared between different widgets:

<Widget name="wup_common" description="Common Shared Resources">
<UIResources>
<!-- Studio/Runtime -->
<FileResource type="JS"file="wup_common.js"description="" isDevelopment="true" isRuntime="true" />
<!-- Runtime -->
<FileResource type="CSS"file="wup_common.runtime.css"description="" isDevelopment="false" isRuntime="true" />
<FileResource type="JS"file="wup_common.runtime.js"description="" isDevelopment="false" isRuntime="true" />

</UIResources>
</Widget>
tcoufal
12-Amethyst
(To:CarlesColl)

Thanks Carles,

I will try that.

Turned out that referencing via return '<script>' actually worked, but in my case (TW 7.1.0) Tomcat needs to be restarted. I'll get errors (Service invoke failed on remove extension) otherwise. It removes the content of of .../extension/myExtension but not the folder it self. Than import of an extension fail.

If I change the version number in XML it work, although some other problems have come up.

Thanks.

jgabriel
12-Amethyst
(To:tcoufal)

Basically, there are few options with various advantages and shortcomings:

1)  You can copy your code into the this.afterRender method and hook ti to main scope. Your html widget node already exists in this.jqElement​ a you can work it out from there. But that means you have will probably have to maintain runtime and ide versions in sync.

2) You can add it to metadata.xml as resource.

          <UIResources>

                <FileResource description="" file="widget.ide.js" isDevelopment="true" isRuntime="false" type="JS"/>

                <FileResource description="" file="widget.ide.css" isDevelopment="true" isRuntime="false" type="CSS"/>

                <FileResource description="" file="widget.runtime.js" isDevelopment="false" isRuntime="true" type="JS"/>

                <FileResource description="" file="widget.runtime.css" isDevelopment="false" isRuntime="true" type="CSS"/>

                <FileResource description="" file="my.js" isDevelopment="false" isRuntime="true" type="JS"/>

                <FileResource description="" file="my.js" isDevelopment="true" isRuntime="true" type="JS"/>

            </UIResources>

Thingworx takes those referenced files and bundles them into one huge JS file for Composer and second JS file for Runtime. That means you drag you extension code every time mashup loads, even when widget is not present. That is not very practical! Your widget.ide.js​ and widget.runtime.js​ should be only lightweight wrappers for custom code so methods 1 and 2 are out of the question for any serious case.

3) During build proces TW takes whole UI folder and everything in it. Long story short, you can reference your file directly like this '<script src="../Common/extensions/MyWidgetExtension/ui/widget/widget.js" type="text/javascript"></script>​ this is what I add as tag in renderHtml method. This makes my bundled widget.ide.js​ and widget.runtime.js files very light and when the widget is present in mashup, browser puls down custom code. Same can be done with multiple files. But you have to take in account that you can have several instances of widget that render the same code. That is probably Ok because it goes from browser cache. But since my code is jQuery extension I test if (jQuery().widget)​ (Note that this is only example and jQuery().widget is part of included jQueryUI library and you can cause lot of troubles messing with global scope, try to prefix your widget name.) and it is already loaded I just skip rendering script tag. So only first widget initializes my custom code. This is very simple and lot of custom dynamic loaders such as jQuery.getScript() can be implemented.


Minify you production code is also good idea.

Method 3 is best option I could come up with, there might be other (better) options.

You can also explore my code here:

tw-semaphorewidget-ext/semaphore.runtime.js at master · Foxoncz/tw-semaphorewidget-ext · GitHub

Hi Jan,

3) it's not always the best option, it will depend on your solution, usually it's best to load all the "crap" with one shot, instead of dynamic JS loading.

Carles

You are right, in case there are not lot of changes in UI widgets extension (Combined Extension JS is not rebuild often) and you use only that specific widget and not library with lots of widgets. Best option would be number 2, where your clients get combined file once and then it is served from cache. But one little change and they are pulling megs of data. But is is save to assume that once it is deployed, there not a lot of changes.

Wrap it up:

  • Option number 2 when single widget per extension and deployed to production with minimal changes.
  • Option number 3 several widgets per extension, not all are always used. Slim combined js file prepared for widget changes.

Either way files are cached, one way only single big file, other way several small files.

Would you agree?

Thanks.

That's it, once deployed changes on js extensions should not be common.

arosse
4-Participant
(To:jgabriel)

Hi Jan,

I know it has been a while, but maybe you can help me.

I am trying to include the JavaScript library print.js (downloaded from here) in a custom widget called printbutton. The library contains multiple javascript and CSS files. The src folder contains the following:

The goal is to be able to print a JSON object when clicking on this custom widget. I am trying to include it in the renderHtml runtime function, like this:

TW.Runtime.Widgets.printbutton = function () {

     ...

     this.renderHtml = function () {

     var formatResult = TW.getStyleFromStyleDefinition(thisWidget.getProperty('Style', 'DefaultButtonStyle'));

        v     ar formatResult2 = TW.getStyleFromStyleDefinition(thisWidget.getProperty('HoverStyle', 'DefaultButtonHoverStyle'));

             var formatResult3 = TW.getStyleFromStyleDefinition(thisWidget.getProperty('ActiveStyle', 'DefaultButtonActiveStyle'));

             var textSizeClass = 'textsize-normal';

             if (this.getProperty('Style') !== undefined) {

                 textSizeClass = TW.getTextSizeClassName(formatResult.textSize);

             }

             // The Disabled property is used for programmatic control of the button state. There is

             // also a class, widget-button-disabled, which is used internally to debounce button

             // click events.

             var buttonState = thisWidget.getProperty('Disabled', false) ? ' disabled' : '';

             var html =

             '<script src="../Common/extensions/ButtonPrint_ExtensionPackage/ui/printbutton/print/js/print.js"></script>'

             + '<div class="widget-content widget-button">'

                     + '<button class="button-element ' + textSizeClass + '" tabindex="' + thisWidget.getProperty('TabSequence') + '"' + buttonState + '>'

                         + '<span class="widget-button-icon">'

                             + ((formatResult.image !== undefined && formatResult.image.length > 0) ? '<img class="default" src="' + formatResult.image + '"/>' : '')

                             + ((formatResult2.image !== undefined && formatResult2.image.length > 0) ? '<img class="hover" src="' + formatResult2.image + '"/>' : '')

                             + ((formatResult3.image !== undefined && formatResult3.image.length > 0) ? '<img class="active" src="' + formatResult3.image + '"/>' : '')

                         + '</span>'

                         + '<span class="widget-button-text">' + (thisWidget.getProperty('Label') === undefined ? 'Button' : Encoder.htmlEncode(thisWidget.getProperty('Label'))) + '</span>'

                     + '</button>'

                 + '</div>';

       

             TW.log.info(html);

       

             return html;

     };

     ...

};

Without the <script> tag, the widget does not generate any error (though, of course, the print function does not work). However, when I open a mashup with my widget including print.js in it, I get the following error (from the log in the runtime mashup):

Did I make a mistake in the syntax? By the way, my project architecture is shown in the image attached. I don't really understand why I have to specify the Common/extensions folders, as I do not see them in the project architecture.

I also tried to reference the library in the metadata.xml (metadata.xml.png attached), but the widget does not appear in the widgets list. I assume there is an error in the building.

Does someboday know how I could include print.js in my extension project?

Thanks,

Antoine

jgabriel
12-Amethyst
(To:arosse)

Hard to tell, seems ok. Please consult my example here:

https://github.com/Foxoncz/tw-semaphorewidget-ext/blob/master/widgets/semaphore/semaphore.runtime.js

Maybe you are missing type="text/javascript" in script tag, but that should not have mattered.

Did I make a mistake in the syntax? By the way, my project architecture is shown in the image attached. I don't really understand why I have to specify the Common/extensions folders, as I do not see them in the project architecture.

That is the folder structure in which you extension folder gets copied on server.

I also tried to reference the library in the metadata.xml (metadata.xml.png attached), but the widget does not appear in the widgets list. I assume there is an error in the building.

If referenced in metadata, Thingworx grabs it and bundles it into Combined.js or .css

arosse
4-Participant
(To:jgabriel)

Thank you for your answer, I am starting to understand.

I managed to include the libraries via metadata.xml. However, I do not think it is the best way, as it is not very safe. For example, I made the mistake of including jQuery in metadata.xml, which created a double reference to the library. After that, the Composer was not accessible anymore. Moreover, the monitoring interface was not accessible either. I was therefore not able to remove the extension. Finally, I had to restore a backup of the server in order to solve the issue.

I would like to avoid making the same mistake in the future, so is it possible that such an issue could occur when including "standard" libraries (not jQuery itself) in metadata.xml?

To avoid copying libraries in the Combined.js, which may be unsafe as I noticed, I would like to include them with an HTML script. I am though still struggling to manage it. In addition to the try made on my previous comment, I made a few others:

1. Appending a dynamically created script to the body:

2. Appending a string script to the HTML of the widget:

3. Appending a string script to the body:

I noticed that the libraries are not written on the server. I tested it by querying the file on the browser console:

> window.location = 'http://143.3.3.150:8080/Thingworx/Common/extensions/ButtonPrint_ExtensionPackage/ui/printbutton/printbutton.ide.js'

Do you know if there is anything to configure to write all the content of the extension on the server?

Thanks,
Antoine

arosse
4-Participant
(To:jgabriel)

I have identified my mistake: the "name" of the ExtensionPackage was "Print Button", so by changing it to "ButtonPrint_ExtensionPackage" it works and I can reference the JavaScript libraries from *.ide.js and *.runtime.js.

However, I could not manage to make the jQuery().mywidget (in my case, jQuery().printbutton) work, it returns undefined. Therefore each time I add a widget PrintButton, the libraries are loaded, which is not the behavior I would like to have. I had a look at your semaphore widget extension source code, and I tried to add this code in the afterRender function:

this.afterRender = function () {

     this.printbutton=this.jqElement.printbutton();

     // ...

}

This generated an error because this.jqElement.printbutton is not a function:

Any idea what I have to change to make it work?

Thanks,

Antoine

jgabriel
12-Amethyst
(To:arosse)

When I was strugling, it helped me a lot to dig deeper to jQuery plugin developement. Try this repo: https://github.com/jquery-boilerplate/jquery-boilerplate/wiki/Extending-jQuery-Boilerplate

And about error I can´t really tell without full code. Try to post whole codebase, but I am not promising anything... =)

Top Tags