Using react components in smartUI environments

Optional Module

With the react-dom package this can be easier then expected. The idea is, add the react component directly to the DOM

Build Environment

When you have a project directory made by yo from the sdk, you’ll need a couple more node.js module:

npm install -save react
npm install -save react-dom

Then make the two files

react.js
react-dom.js

ready for use with require, either by directly requiring them or use the require in the src path of your development. Dont forget, you cant use the IMPORT statement, because its ES6. The exact procedure depends on your project build modifications .

First steps

The React component

define([
'basec/widgets/hello/impl/lib/react'
], function (React) {
'use strict';
class UserInfo extends React.Component {
render() {
var img = this.props.img, name = this.props.name, mail = this.props.title; return ( 
React.createElement("div", null, 
      React.createElement("img", {width: "100", src: img}),
      React.createElement("div", {className: "name"}, name),
     React.createElement("div", {className: "mail"}, mail)));
}
);

This component is a simple div containing an image link and two other divs with content (name and mail)

The sdk demo widget

Then use the sdk demo widget, which will be generated, if you tell yo to do so:

yo csui-extensions:widget

This will add a widget/hello tree in your source tree (if your name for the widget was hello), The first couple of lines in the hello.view.js are:

define([
'csui/lib/underscore', // Cross-browser utility belt
'csui/lib/jquery',
'csui/lib/marionette', // MVC application support
'basec/widgets/hello/impl/hello.model.factory', // Factory for the data model
'basec/widgets/hello/impl/lib/react',
'basec/widgets/hello/impl/lib/react-dom',
'basec/widgets/hello/impl/UserInfo',
'i18n!basec/widgets/hello/impl/nls/lang', // Use localizable texts
'hbs!basec/widgets/hello/impl/hello', // Template to render the HTML
'css!basec/widgets/hello/impl/hello' // Stylesheet needed for this view
], function (_, $, Marionette, HelloModelFactory, React, ReactDOM, UserInfo, lang, template) {
'use strict';
// An application widget is a view, because it should render a HTML fragment var HelloView = Marionette.ItemView.extend({

First we need react and react-dom as modules. They are added here via define. I put it in some weird place, but this is up to you where to put this two modules. There are also a couple of different possibilities to get react and react-dom inside a module.

Next, we need a place where we can add our react component. So lets add this in the hello.hbs file:

...
<div id="userreact">
And this is the react component
</div>

this is simply a div going to held out react component.

The react-dom plays its role

The react-dom can render the component to a designated element, something like the $el element. We added a div with the id “userreact” in the hbs file above.

Now we can render the handlebars template. In Marionette, there is an onRender event routine, which can be used to spice up any rendings of Marionette.

onRender: function() {
var name = this.model.get("last_name");
var mail = this.model.get("mail");
var img = this.model.get("photo_url");
ReactDOM.render(
React.createElement(UserInfo, { name: name, mail: mail, img: img }),
this.$("#userreact").get(0));
},

In the onRender function, all is rendered and we now have a DOM. Now we can render our component.

  1. We need to provide your component with some data. So we extract these from our userdata model we got from the content server. This is more or less the equivalent to our old friend template_helper
  2. The ReactDOM is supposed to render the element UserInfo with the parameters in our div with the id “userreact”

Housekeeping

When the Backbone view’s remove method is called (from the csui app from the content server), we need to remove also our react component.

So lets add our Remove in the Backbone remove()

  remove: function() {
    ReactDOM.unmountComponentAtNode(this.el);
    Marionette.View.prototype.remove.call(this);
  },

Btw: If you want to use jsx for react ui definitions, you should amend your build process with the babel transpiler. This is an example of jsx and quite different to javascipt.

const element = ( <h1 className="greeting"> Hello, world! </h1> );

The transpiler basically builds this structure

// Note: this structure is simplified 
const element = { 
type: 'h1', 
props: { className: 'greeting', 
        children: 'Hello, world!' } 
};

Summary

  • Install react and react-dom and make them requirable.
  • Build or get the react components. Make them requireable.
  • Require this components from your view.
  • Add an anchor to your hbs file.
  • Let Backbone-Marionette render.
  • In the OnRender callback, gather all data for your react component and let react-dom render the component to the anchon.
  • Use standard JQuery to get data from the react-component.

Some further infos (on pure Backbone/React things without smartUI reference)

Integrating React with Backbone

Using BackboneJS successfully with React (using MobX)

Thats it.

But nevertheless not often used in our OpenText smartUI practical live. 🙂

smart UI in practice: SMART TOOLS(2) – Multilingual Metadata

smart tools

This is the second part of a multipart post of the new SMART TOOLS – a product supporting mlm and renditions in smartui.

You can get the first part smart UI in practice: SMART TOOLS(1) – the beginning

Today lets look on the functionality of the multilingual metadata part of smartUI.

Its directly integrated in the properties manager of the nodelist widget.

The multilingual metadata panel

Use the pulldown menu in the properties manager.

If you go to the “Multilingual Metadata” entry, you’ll see a list of the two attributes, which make up the multilingual metadata in all supported languages.

Here, just for demo purposes, metadata languages are English, German, French, Italien, Japan, Spanish and simplified Chinese.

Automatic translation with Microsoft Azure services

At right, there is a globe icon visible. If there is a globe icon, then this language is configured to do an automatic translate using Microsofts Translate service. Its to translate from the users default metadata language (here English, also displayed with a different background) to this target language. Simplified chinese is not configured to use automatic translation, therefore there is no globe set at chinese. To configure that is the job the Content Server administrator.

Clicking on the globe will replace any text with the translation of the attribute in the default metadata language (here “a special cover letter”).

Although this translating machines are quite good, its always a good idea to have the opportunity to manual correct the automatic entries. Its done simply by clicking on an entry.

By pressing <RETURN> in a inpput field or by clicking on any language name, the edit mode is switched off and the green button “save mlm data” is activated. By pressing this button, mlm data is saved on the server.

Print a list of actual mlm data

If you have a lot of these language entries, its unlikely for one person to check all language values. It would be nice to have a list (printed of pdf) with the actual values. This can be done by simply clicking on the blue Print-Button.

This will open the browsers print window and give you a preview of the list.

Ok, thats all for today. Next week we’ll take a look at the SMART TOOLS Rendition support.

smart UI in practice: SMART TOOLS(1) – the beginning

smart tools

Using OpenText Content Server as as developer, you have to face it sooner or later: Write an application in smartUI. This will give you a lot of experience for smartUI projects, especially on the Cost/Times/Materials base.

I did chose to build a product called SMART TOOLS with Renditions and Multilingual Metadata Support. Product means, it can be sold by other OT partners as well.

This is a multipart post. Today we talk about the basics of the application running in Content Server 20.2. Additional posts will explain the usage and some technical aspects

Features

SMART TOOLS includes these features:

  • Adds support for Multilingual Metadata to smartUI
    • Automatic Translations for the multilingual data using Microsoft Azure REST service (as nobody speaks all languages on this planet)
    • Add print lists (to get a list of the data entered for editing)
    • Add/edit mlm data directly
    • Integrated in the standard nodelist widget using the property manager
  • Adds support for Renditions
    • List all Versions and Renditions
    • Use “view as webpage” as a base viewer
    • Download renditions
    • Delete renditions
    • Replace renditions
    • Add Renditon as command
    • A cell icon marks all documents in the document list having renditions at the newest version. Clicking on the icon will open a window to show all renditions. Download or delete a rendition.
  • Adds configuration support for the javascript client software from the Admin pages. Critical things like MS Rest API key can be protected by sending this things at the page creation time to the client.

The SMART Tools are build for Content Server 20.2. A a considerable amount of new REST services also to be implemented in the Content Server.

The Integration

Integration is simply direct in the nodelist widget using the property manager.

Open the property manager

By clicking in the first icon in the list, the property manager opens.

Renditions and Multilingual Metadata

The panels adressed by this menu are the multilingual metadata panel (with the two fields Name and Description) and the rendition panel. Here you see the mlm panel with the autotranslate Icon (the globe at the right).

mlm main panel

This is the rendition panel which shows all renditions attached to all versions.

Renditions main panel

Language support

The SMART TOOLS support a couple of UI languages like

  • English
  • German
  • French
  • Spanish
  • Italian

This is one example on the multilingual metadata dialog using German, French and English.

The nls feature of smartUI is nice, isn’t it? I even tried it (experimental) for Arabic, and it worked!

Lets go more in detail in the next posts, as this here is intended to be a simple overview.

Next week I will describe the multilingual metadata support of smartUI

Conditional Configuration with require.js in OpenText smartUI

Optional loading of modules

When you write a widget or something else in smartUI, you encounter (from time to time) the problem to provide your widget with different configurations.

Like one widget provided as DashBoard for Music Events and lateron provide the same Widget as Dashboard for Race Events with different Logos Graphics etc. Require.js conditional loading of a module will help.

The idea is, if a conditional configuration module exists, it will be loaded and override the standard configuration. If its not present at the defined place, it wont load this and use the existing configuration.

Lets see an example:

in a view, we define a config.js containing all configuration stuff of this module

The main View
The main View with the config.js declaration

This will load configration values like

Top of a configuration module
End of the configuration module
Mid of the configuration Module with the value "backgroundcolor" which we want to override
We want to override the value “backgroundcolor”

So far so good. When we want to override for example the value “backgroundcolor”, we can do that by editing the file, but we would have to redo that for all diffenent configurations. Its much smarter to define an optional require.js module, that will be loaded if present.

Our alternative config.file is called targetconfig.js.

Here are the values to replace the original ones.

For simplicity it contains only the value “backgroundcolor”.

The logic is:

If targetconfig.js exists, it will be loaded with require.js and replace the specific contents of the original config.js file with the contents in targetconfig. If its not existing, its simply doing nothing and the original config will be used.

How is this done?

First, we have to declare the additional targetconfig.js in the define area as optional.

Optional declaration

Second, we must add a require.js module called optional doing the job

The require.js optional module

Now, we can use this in our wonderful widget providing as much configuratíons as we want – based on the existence of this config files.

Welcome to the Wonderful world of OpenText smartUI!!!

New: We are offering custom widget development. Interested? Send an e-mail to merz at ebit-company.de stating the purpose of the widget and requesting a qoute

Routers in OpenText SmartUI

greetall screenshot

Inside a Single Page Application, a router will take care of that what a URI used to do for you in a classic Client/Server application, it will intercept the URIs from beeing send to server and decide based on the URI whats to do with it. All within Javascript (we are not talking about html5 push state, thats another story)

Normally, the router will change the perspective (the display page) depending on the URI. For example, if an URI is http://<yourserver>/app/greetings, the Router will change the perspective to some new widgets and display the contents.

There are 4 routers build in the sdk (all in csui\pages\start\impl)

  • node.perspective router
    switches to a different node
  • landing.perspective.router
    used to address landing pages of the users
  • search.perspective.router
    switches to the search perspective requested
  • Perspective.router is the parent object of all routers in the sdk

A router changes the perspective, for example a node perspective

var nextNode = context.getModel(NextNodeModelFactory);

nextNode.set(‘id’, 2000);

Normaly these routers shoudl be enough for standard usages, but if you need your own router, the next example can bring some light into a routers anatomy.

Scenario:
there are two routes in an URI, “greetings” and “greetings/:id”.

“greetings” clears a greeting subject, which was initialized from the model factories) and sets the id and “greetings” in the application scope

“greetings/:id sets the id in the greetings subject

The greetings router

_updateURL constructs the URL and moves to this URL.

Next, we neet plugin with two tasks:

  • –Get/create the model and switch perspective depending on the subject
    • In _fetchHelloPerspective
  • –Set the greetingSubject in the constructor
Thed greetings router plugin
Process local perspectives with a plugin

We defined two routes, so we must provide two perspectives. We want to load them not from the server, so we need to define them locally als json arrays.

the greetings/:id perspective
The perspective for the greeting/:id simply displays the greeting widget
the greetings perspective
The perspective for the greeting displays the hello widget
the extension points
The extension points for the router

And this thing works (see these screenshots)

example of a greetings route
URL localhost/cs162/cs.exe/app/greetings called
example of a greetings/reiner route
URL localhost/cs162/cs.exe/app/reiner called

Imagine the possibilities. You can add a bunch of functionalities to the client all with an URI, which can be behind a link or a button.

Quite powerful, the smartUI sdk! You will love your routers.

The Connection Object in smartUI

Setup of the Connection Object

smartUI does have a couple of interesting objects, but the actual documentation about them is not very clear. So lets take a look on the objects.

First, we’ll examine the connection object a little bit more in detail. The page context will follow in another post.

The connection object provides information about the content server to connect with. Normally, this object comes from the content server but has to be constructed manually when you use the index.html testing facility.

Lets take a look at a typical index.html with the connection object setup

Setup of the connection object

Remember, the widget will get everything beeing perfectly setup from the content server, but using the test faciliy, you have to mock up this.

Parameters:

  • url – the url which is the content server url in your mockup data
  • supportPath – where is your content server support directory
  • credentials – in case you want to log in, use this clause
  • session- when you want to ignore the ticketing etc, use ticket:”dummy”

Lets see what this object is doing:

(here the page is running and this is a screenshot from the debugger)

A debugging session with the connection object

We see, there is a connector object beeing the parent for the connection object. The connection object contains all parameter setups from the index.html, mainly the url and the session infos.

There is also an Authenticator in this connector object, which contains another copy of this connection object but also with the information, how the automatic re-login for a valid session ticked should be done. And of course, the type of Authenticator (here InteractiveCredentialsAuthenticator)

(Remember: The security token timeout will cancel your session (within typically 300 sec) if you do not a relogin).

This is the mechanism to avoid permanent logins. Take a look at the available authenticators to see the possibilities.

You can always refer the Connection Object to get the url of the server. This url os merged with the parameters in the Model URL function to build the final REST call.

A useful object. Normally we get it automatically from the server, only while testing with Mock-Up Data we need to setup this object manually.

Config Settings send to a smartUI Module

There are several possibilities to set the configs from the base OScript Module. The most interesting is the method of setting the configs via Oscript. The complete list of the possibilities is_

  • When you visit a mode or get a children
    • Implement “fields” and/or “expand” in the appropriate CSNode.
    • Use the date from data.(fields.name)
  • When the Widget is created
    • Implement an REST service which will give you the data
    • Load the data on the first widget creation and cache it with a permanent model factory in the context
  • When the page is loaded
    • The CSUI::Extension::GetDynamicConfiguration method in OScript has normally this entry
function Assoc GetDynamicConfiguration( Object prgCtx, Record request )      
return Undefined
  end

Overwrite it with your desired configuration (here only the enableAppleSupport is displayed for clarity):

 function Assoc GetDynamicConfiguration(

        Object      prgCtx,

        Record      request )      
Assoc       basicAppData
Assoc       config 
(snip)

        Boolean     enableAppleSupport

    (snip)

        // changes in admin page should be reflected immediately by every thread

        settings = $WebAdmin.AdminUtils.GetSmartUISettings( prgCtx )
     (snip)
    if ( IsFeature( settings, "enableAppleSupport" ) && IsDefined( settings.enableAppleSupport ) )

        enableAppleSupport = settings.enableAppleSupport

    end

    

    config = Assoc{ 
    (snip)
     "csui/utils/commands/email.link": Assoc{ "enableAppleSupport": enableAppleSupport },
                  

    return config

end 

The returned assoc consists of a requirejs module name containing an assoc with the config values.

In your js module (here csui/utils/commands/email.link) add this line

var config = _.extend({
rewriteApplicationURL: false,
enableAppleSupport: false,
appleNodeLinkBase: 'x-otm-as-cs16://?launchUrl=nodes/'
}, module.config());

Then the config is avaliable in your js module and can be used like

var iOSEnabled = config.enableAppleSupport

Nice, isn’t it?

Dealing with Distributed Agents

Distributed Agent Dashboard

From time to time you’ll have to deal with distributed agents inside content server. This can be a little confusing, so maybe this post is helpful there

In the Admin Pages, you’ll find a dashboard to control the default setup. Either add a func=distributedAgent.AgentStatus to your servers URL or search inside the admin pages for distributed Agent things like this

Distribute Agent Entry

Click on Distributed Agent Dashboard.

Then you’ll see the distributed Agent dasshboard

Distributed Agent Dashboard

Here you’ll find all information about the current state of the system and the configuration of the distributed agent system

Per default a content server has one distributed agent with three workers. You can change the number of workers used by the agent by changing the number of workers in the opentext.ini file in the [distributedagent] section.

You can configure the whole distributed Agent system by clicking on the link indicated with the red arrow above. Then the configuration page opens:

Here you can set up values for the the treatment of low priority tasks (Enable Fairness). The agent can spent between 5% to 50% on low priority tasks, which will be processed aftera minimal age set by “Task Age”.

When you have more than 1 agent in the system, you can switch the priority Agent here also. OpenText recommends that you select the Distributed Agent with the greatest capacity to be your Primary Distributed Agent. Typically, the Distributed Agent with the greatest capacity resides on your most powerful or your least busy computer.

If you want a defined outage to be set, you can do this for the Agent by clicking on “Add new Outage” at the upper right. You can do the same for specific workers, we’ll discuss this later.

Agent System Outage

Back on the DashBoard, you cal also modify single workers. SImply click on the name of a worker, then the “Configure Worker” page opens

Worker Configuration

If you want to set a more specific name as that automatically generated, fill the Description field.

Much more important are the three columns “Never Run”, “Run” and “Run First”. The Run column contains all tasks this worker is supposed to do.

You can exclude tasks from this worker, then this worker will never execute this task. You can also prioritize a task, which means this worker will do this task at first.

To configure this, select either all of the tasklist under “Run” or one or more tasks from the tasklist and move the entries either to “Never Run” on the left (worker will never execute the tasks) or to “Run First” on the right (worker will execute this tasks at first).

This allows you an effective task prioritizing for a single worker.

You can also define an outage for this worker by clicking on the Button “Add new Outage” on the lower right.

Worker Outage

Easy, isn’t it?

Find require.js module dependencies in smartUI

Have you ever noticed that the number of modules in a require Javascript enwironment can be huge? Difficult for an overview?

In this case, a graphical overview of modules and their dependencies can save you.

There are two solutions.

Non-Webstorm Solution

If you are using any text editor as a main Javascript Programming Tool, the npm dependo is a nice tool. Used against the MyAssignments Widget, it produces his output (click on the image to see a larger version)

dependo output

Installation:

$ npm install dependo

Usage:

$ dependo -f amd /path/src > example/report.html

will produce the graph in the file report.html

There is also a grunt task for dependo here

https://github.com/auchenberg/grunt-dependo

Webstorm Solution

If you are using the Webstorm IDE for your projects, there is a build in solution.

It produces against the MyAssignments Widget this chart (click on the image so see a high resolution version):

To get this diagram, click on the root of a widget and select

“Show Diagram Popup” shows the Drawing as a popup window, while “Show Diagram” shows the drawing in a new tab.

In Detail Mode you’ll see a lot more details, here are the external modules required by the main js module.

This gives you a lot of information and this can be also used to provide a technical system documentation.

Happy documenting.

Switch the smartUI SDK to Javascript ES6

When you start GRUNT, you will get errors on ES6 keywords, each LET will be flagged as error,

Have you ever wondered, why the SDK is not accepting JavaScript ES6? The wonderful new level of Javascript avoids for example the Variables Hoisting Effect by using LET instead of VAR. (Reduces the scope of the variables)

On the other hand, not all Browsers support ES6. So be aware that you may have to switch it back, if your browsers provide no support for ES6.

To change it:

  • open the node-modules folder in your project dir
  • scoll down until you find the module eslint-config-default . Edit the file .eslintrc in a tect editor (screenshots are from WebStorm IDE)
eslint config default in node_modules
eslint.rc

in this file, you’ll see that the file off.js is used for configuration. Open this file in a text editor.

The file off.js

Here you see, that es6 is set to false (Yellow Arrow). Change this to true and save the file (maybe under another name. In this case, correct the require command to reflect the new name).

So now the GRUNT-file is producing no problems when it encouters a LET.

But be aware: If your bropwsers lack some support for es6, you must switch back to ES5.