Add customized publisher form for a custom RXT

In the previous post I have discussed how to add a custom asset type (Registry Extension) to wso2 governance registry. There can be situations, you need to customize these publisher forms in a way that your organizations requires. Probably adding more labels, changing colours and adding a captcha. Thanks to the maximum extendibility of wso2 governance center, you can do whatever the change your organizations requires. This article will elaborate on changing a particular area of a text field of Governance Publisher related to your custom RXT.

First you need to add your own custom RXT in order to add your own asset type. Read how to add a custome rxt here. For the consistency, you can use following RXT template as the extension.

<?xml version="1.0"?>
<artifactType type="application/vnd.wso2-widget+xml" shortName="widget" singularLabel="widget" pluralLabel="widgets"
hasNamespace="false" iconSet="10">
<storagePath>/widgets/@{overview_provider}/@{overview_name}/@{overview_version}</storagePath>
<nameAttribute>overview_name</nameAttribute>
<ui>
<list>
<column name="Provider">
<data type="path" value="overview_provider" href="@{storagePath}"/>
</column>
<column name="Name">
<data type="path" value="overview_name" href="@{storagePath}"/>
</column>
<column name="Version">
<data type="path" value="overview_version" href="@{storagePath}"/>
</column>
</list>
</ui>
<content>
<table name="Overview">
<field type="text" required="true">
<name>Provider</name>
</field>
<field type="text" required="true" readonly="true">
<name>Name</name>
</field>
<field type="text" tooltip="Age" validate="^\d+$">
<name>Age</name>
</field>
<field type="text" required="true" readonly="true">
<name>Version</name>
</field>
<field type="text">
<name>Createdtime</name>
</field>
<field type="options">
<name label="Category">Category</name>
<values>
<value>Google</value>
<value>WSO2</value>
<value>Templates</value>
</values>
</field>
<field type="text" url="true">
<name>URL</name>
</field>
<field type="text-area">
<name>Description</name>
</field>
</table>
<table name="Images">
<field type="text">
<name>Thumbnail</name>
</field>
<field type="text">
<name>Banner</name>
</field>
</table>
</content>
</artifactType>

view raw
gistfile1.xml
hosted with ❤ by GitHub

After adding the rxt, move to the publisher (https://localhost:9443/publisher) and you will see the asset type “Widget” there. Once you click on “add new widget”, you will be redirected to a form like following.

You can do the customization to the form in two versa.

  • Add a custom.css file for modifying sizes and colours of existing fields.
  • Write a new template for the form with its own fields.

I will first go through the easy first way and then will jump into the more useful second scenario.

Adding a custom CSS

<custom_rxt_type> should be replaced with the short name of the asset type. (Here it should be “widget”)

  • Create a new css file such as custom-rxt.css in wso2greg-5.1.0/repository/deployment/server/jaggeryapps/publisher/extensions/assets/<custom_rxt_type>/themes/default/css
  • Every text-area in create page has its own id. Therefore it can be used to bind a new css rule to the element. Find the id of the text area that needs to be customized. (i.e.: overview_name). Then add your new style for the particular element using css id selector.
    e.g.:

     #overview_name {
        background-color: green;
        height: 340px;
    }
  • Create the following directory structure in wso2greg-5.1.0/repository/deployment/server/jaggeryapps/publisher/extensions/assets/<custom_rxt_type>/themes/default/helpers
  • Create a new helper file named create_asset.js and add the following content. Add the name of the custom css file to be pushed.
    e.g.: o.css.push(‘custom-rxt.css’);
var name;
var hps = require('/extensions/app/greg-publisher-defaults/themes/default/helpers/view-asset.js');
var that = this;
/*
In order to inherit all variables in the default helper
*/
for (name in hps) {
if (hps.hasOwnProperty(name)) {
that[name] = hps[name];
}
}
var fn = that.resources||function() { return {} };
var resources = function(page, meta) {
var o = fn(page, meta);
if (!o.css) {
o.css = [];
}
if(!o.js){
o.js = [];
}
if(!o.code){
o.code = [];
}
o.css.push('custom-rxt.css');
return o;
};

view raw
create_asset.js
hosted with ❤ by GitHub

  • Reload the page. Next time you move to “Add widget” form on publisher you will see the changes have taken place.

 Write a new template for the form

Suppose, there is a scenario where you want your organization wants to fill out some details on the form on behalf of the registrants. Or else, you want to check the inputs for a bot using a captcha. The extensible governance center platform provides you the maximum extensibility by adding your own .hbs (handlebars) template to be populated.

Assume that compared to the large palette of input fields in the “Add widget” page, you only need to show the required fields to the end user. The carbon-store platform allows you to override the default form with your own HTML/CSS/JS files.

  • Create the wso2greg-5.1.0/repository/deployment/server/jaggeryapps/publisher/extensions/assets/widget/themes/default directory structure. This is where we are going to place all the files needs to be used by the particular asset type.
  • Add 3 directories namely, “css”, “helpers”, “partials” in the path.
  • Create a file named “create_form.hbs” inside partials folder and enter following snippet there.
<form method='post' class="form-horizontal" action="/publisher/apis/assets?type=widget" id="form-asset-create" name ="form-asset-create" data-redirect-url='{{url ""}}/assets/{{rxt.shortName}}/list' >
<div class="form-group">
<label class="custom-form-label col-lg-2 col-md-2 col-sm-12 col-xs-12" for="overview_provider">{{t "Provider"}}<sup class="text-danger"> *</sup></label>
<div class="custom-form-right col-lg-5 col-md-8 col-sm-8 col-xs-12">
<input type='text' name='overview_provider' id='overview_provider' class="form-control validate-required" placeholder="Enter your Age here!" readonly value={{this.cuser.username}}>
</div>
</div>
<div class="form-group">
<p style="margin-left:18%;"><i class="fa fa-info">&nbsp;This is a tip for you! You have to enter your name here, not the age!</i></p>
<label class="custom-form-label col-lg-2 col-md-2 col-sm-12 col-xs-12" for="overview_name">{{t "Name"}}<sup class="text-danger"> *</sup></label>
<div class="custom-form-right col-lg-5 col-md-8 col-sm-8 col-xs-12">
<input type='text' name='overview_name' id='overview_name' class="form-control" placeholder="Enter your name here!"/>
</div>
</div>
<div class="form-group">
<label class="custom-form-label col-lg-2 col-md-2 col-sm-12 col-xs-12" for="overview_version">{{t "Version"}}<sup class="text-danger"> *</sup></label>
<div class="custom-form-right col-lg-5 col-md-8 col-sm-8 col-xs-12">
<input type='text' name='overview_version' id='overview_version' class="form-control validate-required" placeholder="Enter your version here!"/>
</div>
</div>
<div class="form-group">
<label class="custom-form-label col-lg-2 col-md-2 col-sm-12 col-xs-12" for="overview_age">{{t "Age"}}<sup class="text-danger"> *</sup></label>
<div class="custom-form-right col-lg-5 col-md-8 col-sm-8 col-xs-12">
<input type='text' name='overview_age' id='overview_age' class="form-control validate-required" placeholder="Enter your Age here!"/>
</div>
</div>
<div class="form-group" id="saveButtons">
<div class="col-sm-offset-2 col-sm-10">
<input type='submit' id="btn-create-asset" class="btn btn-primary" name="addNewAssetButton" value='{{t "Create"}}'>
<button class="btn btn-default" type="reset">{{t "Reset"}}</button>
<input type="hidden" value="{{rxt.shortName}}" name="{{rxt.shortName}}" id="meta-asset-type">
</div>
</div>
</form>

view raw
form.html
hosted with ❤ by GitHub

If you refer to the source of existing “Add Widget” form, you will notice that elements have been wrapped in such a hierarchy. Input fields for each entry contains the prefix of “overview_” in it’s id and you need to preserve the hierarchy, as we need these input fields to be serialized and sent to the server. ./css and ./js can be used to add more complicated verifications. Also note the form action as well.

On the .hbs context, input fields needs to be identified and mapped to the rxt entries. Therefore, use “overview_<fieldname>” pattern for input names and identifiers.
E.g.:

<input type='text' name='overview_version' id='overview_version' class="form-control validate-required" placeholder="Enter your version here!"/>

Once, you refresh the page you will see your new form in action instead of the default form.

Cheers!

[js-disqus]

Understanding asynchronous JavaScript – Callbacks

Recall Jquery click() function.

$("#btn").click(function() {
alert("Hello World");
});

view raw
jqueryHello.js
hosted with ❤ by GitHub

Have you ever wondered, why would you want to write the block of the code that needs to be executed after the clicking on the button inside another function?

Welcome to the world of asynchronous programming, callbacks!

Callback is a programming convention rather than a special feature of a language. They are useful when you want your program not to be blocked by a time consuming operation. In this case, finding the exact html element by the id might consume time. Once it is found, the function we pass to the click() function is called back.

See the following code.

find(function() {
alert("Found the element");
});
alert("Meanwhile I show up");
function find(callback) {
//The time consuming operation
setTimeout(function() {
//calling the passed callback function after 2 seconds
callback();
} ,2000);
}

view raw
file.js
hosted with ❤ by GitHub

Here, the find function takes 2 seconds to finish its operation.  It accepts a function as its parameter to be executed once the time consuming operation is done. (Don’t worry about the setTimeout() function. Yes, this too is a calling back function, but for the time being lets assume, all what it does is calling the passed function after 2 seconds). “callback” is just a variable that is used to pass the callback function back and forth. You can change it to whatever the name you want to call it. Even though it’s a time consuming operation, the alert in the main routine “meanwhile I show up” is displayed, regardless the synchronization.

Find function could be something that was defined in the API or somewhere in the program that performs IO operations or network calls. It promises to the main program routine that “I will call you back, once my time consuming operation is done”.

Callbacks with parameters

JS is too awesome to be around that we don’t explicitly need to mention what is being passed to the find function is another function. Be it another function with or without arguments and variables, it treats them the way they like to be treated. We can pass any function alongside any type of variable and a function signature. Inside the caller function (find() function) all we have to do is passing the arguments to the callback properly.

find([1,2], function(results) {
alert("Found other elements "+results);
});
alert("Meanwhile I show up");
function find(elements, callback) {
//The time consuming operation
setTimeout(function() {
elements.push(3,4,5);
callback(elements);
} ,2000);
}

view raw
callback1.js
hosted with ❤ by GitHub

Here, we pass an array of 2 elements to find() function. It takes 2 seconds to find other elements and push them to the elements array. Then the resulting array is passed to the callback function as another parameter and from the major program routine it is caught and displayed.

Rolling in the deep

Here we pass the callback function by reference to the 2nd caller function, process().

firstFunctionCall : find([1,2], function(results) {
process(results, onMethodsDone);
});
callBackFunctionForProcess : function onMethodsDone(results1) {
alert("Results : "+results1);
}
alert("meanwhile I show up");
function process(elements, callback1) {
//The time consuming operation
setTimeout(function() {
elements.push(6,7,8);
callback1(elements);
}, 2000)
}
function find(elements, callback) {
//The time consuming operation
setTimeout(function() {
elements.push(3,4,5);
callback(elements);
} ,2000);
}

view raw
callback2.js
hosted with ❤ by GitHub

Once the results are returned to the firstFunctionCall it sends the results to the process() function to add more elements to the array. The callback function for process() is passed by the reference onMethodsDone.

How to make sure passed function is actually a function?

Once your genius code is behind an API you need to throw proper messages to the user that he has not quit got the idea of callbacks or he has mixed up the function signature. You can use Js typeof operator to make sure the retrieved parameter is a reference to an actual function within the caller function. Try the following code passing a simple integer instead of a function at the firstFunctionCall.

firstFunctionCall : find([1,2], function(elements) {
alert("Results : "+elements);
});
alert("meanwhile I show up");
function find(elements, callback) {
//The time consuming operation
setTimeout(function() {
elements.push(3,4,5);
if(typeof callback === "function") {
callback(elements);
}
else {
alert("Retrieved function is not a callback");
}
} ,2000);
}

view raw
callback3.js
hosted with ❤ by GitHub

Dealing with states

Your caller function behind the API may be operating a network call and might have gotten a null result. For the sake of modularity you need to treat the failure in a different way so then the user can handle the failure situation inside another function separately. Try following code by passing null instead of [1,2] to the find() function.

find([1,2], response);
function response(err, result) {
if(err) {
alert(err);
}
else {
alert("operation successful : "+result);
}
}
alert("meanwhile I show up");
function find(elements, callback) {
//The time consuming operation
setTimeout(function() {
if(elements) {
elements.push(3,4,5);
callback(null, elements);
}
else {
callback("Elements array should not be null", null);
}
} ,2000);
}

view raw
callback4.js
hosted with ❤ by GitHub

Will call you back! 🙂

Cheers!

[js-disqus]