External object code examples

Principles

An external object is basically a custom HTML page/component that can be made available into the generic web UI's authenticated zone or in the public zone.

By extension it can also be used to generate other type of files such as a PDF document or an image. But this is not a typical use of external objects as there are other mechanisms for generating such contents (e.g. publication templates).

As of version 3.1 external objects can also be used to provide custom web services (typically JSON/REST web services) on the API endpoint. Please refer to this document for details.

The content of an external page is produced by the display method of its server side script.

Warning: the following examples are only suitable for standalone pages and/or for versions 3.x and 4.0 legacy UI (still available for backward compatibility) For the 4.0 responsive UI's single page custom components the implementation pattern is different, please refer to the dedicated section bellow Responsive UI pattern.

Basic example

Let's configure a basic external object called MyExtObj and set the following server side script:

MyExtObj.display = function(params) {
    return "<h1>Hello world !</h1>";
};

Note that the display method is created in the MyExtObj namespace that corresponds to the name of the configured external object.

Once granted and made available from a main menu entry or a shortcut for instance, the page will render like this:

You can see that, by default, the page uses the standard UI page framework (title, navigation bar, ...). To do so, the page includes all core JavaScript and CSS:

Note: jQuery is aliased as $ as of version 4.0 (in previous versions the alias was $j)

If you just want to generate only a plain HTML content (e.g. to be embedded inside another page) you need to set it explicitly as non decorated (meaning not using the default UI page framework):

MyExtObj.display = function(params) {
    this.setDecoration(false); // Just a plain page
    return "<h1>Hello world !</h1>";
};

Then the plain page will render like this:

No script or HTML header is included, only what is explicitly added in the display method is generated, so in that case the generated HTML is:

<h1>Hello world !</h1>

If you want to generate a full custom HTML page, one of the following web helper classes should be used:

E.g. of a simple jQuery® page:

MyExtObj.display = function(params) {
    this.setDecoration(false);
    var wp = new JQueryWebPage(params.getRoot(), this.getDisplay());
    wp.setReady("$('#hello').append('Hello world !');");
    wp.appendHTML("<h1 id=\"hello\"></h1>");
    return wp.toString();
};

The output is visually the same as above but the generated HTML is now:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>My external object</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<!--[if lt IE 9]>
<script type="text/javascript" src="/demo/scripts/ie/html5shiv.js"></script>
<script type="text/javascript" src="/demo/scripts/ie/css3-mediaqueries.js"></script>
<![endif]-->
<script type="text/javascript" src="/demo/scripts/jquery/jquery.js"></script>
<script type="text/javascript">
$(document).ready(function() { $('#hello').append('Hello world !'); });
</script>
</head>
<body>
<h1 id="hello"></h1>
</body>
</html>

In the rest of the document we will speak of standard page when web helper classes are not used and of non standard pages when used.

Using resources and custom HTML/JavaScript/CSS

If resources are associated to an external object, they are automatically processed as follows:

On standard pages several methods allows to add custom JavaScript/CSS includes or fragments when required:

MyExtObj.display = function(params) {
    this.appendCSSInclude("http://url/of/a/file.css");
    this.appendCSSIncludes([ "http://url/of/a/file1.css", "http://url/of/a/file2.css" ]);
    this.appendJSInclude("http://url/of/a/file.js");
    this.appendJSIncludes([ "http://url/of/a/file1.js", "http://url/of/a/file2.js" ]);
    return "<p>Hello world !</p>"
        + HTMLTool.jsBlock("console.log('Hello again !');")
        + HTMLTool.cssBlock("p { color: red; }");
};

The com.simplicite.util.tools.HTMLTool helper class provides methods that can be used to get the URLs of packaged third party components' JavaScript/CSS (for use in the append* methods described above):

Other methods can be used to get the URL of global resources (i.e. disposition-level resources):

On non standard pages, the web helper classes offers methods for similar inclusions, please refer to the JavaDoc of these classes. With wpbeing an instance of a sub classes of com.simplicite.webapp.web.WebPage the following snippets can be used:

wp.appendCSSInclude("http://url/of/a/file.css");
wp.appendCSSIncludes([ "http://url/of/a/file1.css", "http://url/of/a/file2.css" ]);
wp.appendJSInclude("http://url/of/a/file.js");
wp.appendJSIncludes([ "http://url/of/a/file1.js", "http://url/of/a/file2.js" ]);
wp.appendHTML("<p>Hello world !</p>");
wp.appendCSS("p { color: red; }");
wp.appendJS("function myfct() { ... }");

Using HTTP parameters

The display method takes a single params argument which is a populated instance of com.simplicite.webapp.util.ServletParameter.

This let you access all HTTP parameters (including file parameters), please refer to JavaDoc for more details.

You can get the page URL by calling params.getLocation(). This can be used, for instance, by self links and self form submissions.

For example you can write a traditional form posted to server (you should really consider using the Ajax APIs instead of doing this kind of things ;-):

MyExtObj.display = function(params) {
    var name = params.getParameter("myname", "");
    if (name != "") return "Hello " + name + " !";
    return "<form action=\"" + params.getLocation() + "\" method=\"post\">"
        + "Your name: <input type=\"text\" name=\"myname\"/>"
        + "<input type=\"submit\"/>"
        + "</form>"
};

When used inside the generic web UI authenticated zone with navigation (i.e. by adding the URL parameter nav=add to the page URL), you can get the previous page URL by calling params.getBackLocation(). This can be used, for instance, by a close button.

Using other features {#using others}

Ajax APIs

Most external pages are using the Ajax APIs to interact with Simplicité® services instead of doing full page request/responses to the server.

As indicated above, the Ajax APIs JavaScripts are included by default in the standard decorated pages. In other cases they must be explicitly included:

The usage of the Ajax APIs itself is described in details in another document. Here is just a simple standard page example:

MyExtObj.display = function(params) {
    return "<div id=\"hello\"></div>"
        + HTMLTool.jsBlock("new Simplicite.Ajax().getGrant(function(g) { $('#hello').append('Hello ' + g.login); });");
};

UI tools

Decorated standard pages includes the UI tools by default (non standard page need to include it explicitly, e.g. using wp.appendUITools() where wp is an instance of a sub class of com.simplicite.webapp.web.WebPage).

The usage of the UI tools APIs itself is described in details in another document. Here is just a simple standard page example:

MyExtObj.display = function(params) {
    return HTMLTool.jsBlock("Simplicite.UI.popup({ content: "<h1>Hello world !</h1>" });");
};

jQPlot® charts

Typical usage in a standard page is something like:

MyExtObj.display = function(params) {
    this.appendCSSIncludes(HTMLTool.jqplotCSS());
    this.appendJSIncludes(HTMLTool.jqplotJS());
    return "<div id=\"mychart\"></div>" + HTMLTool.jsBlock("onload_functions.push(function() { $.jqplot('mychart', ...); });");
};

Check the jQPlot® documentation for details on usage (in the above example, ... needs to be replaced by a correct jQplot chart definition).

Note that for simple charts you can also use the Simplicite.UI jQplot® wrappers APIs.

MyExtObj.display = function(params) {
    this.appendCSSIncludes(HTMLTool.jqplotCSS());
    this.appendJSIncludes(HTMLTool.jqplotJS());
    return "<div id=\"mypiechart\" style=\"width: 300px;\"></div>"
        + "<div id=\"mybarchart\" style=\"width: 300px;\"></div>"
        + HTMLTool.jsBlock("onload_functions.push(function() {"
            + "Simplicite.UI.pieChart('mypiechart', { values: [1, 2, 3], labels: ['Label 1', 'Label 2', 'Label 3'] });"
            + "Simplicite.UI.barChart('mybarchart', { values: [[1, 3, 5], [2, 4 ,6]], names: ['Serie 1', 'Serie 2'], labels: ['Label 1', 'Label 2', 'Label 3'] });"
        + "});");
};

Google Maps®

Typical usage in a standard page is something like:

MyExtObj.display = function(params) {
    this.appendJSIncludes(HTMLTool.gmapJS());
    return "<div id=\"mymap\" style=\"width: 400px; height: 400px;\"></div>"
        + HTMLTool.jsBlock("onload_functions.push(function() { Simplicite.Gmap.simpleDisplay('mymap', 8.8566667, 2.3509871); });");
};

The above example uses the Simplicite.Gmap wrapper but the Google Maps® API can also be directlyb used (check the Google Maps® API documentation for details on usage).

Google+®

Usage example displaying the Google+ me profile data

Note this requires that you are logged in with a Google account and minimum Simplicité® version 4.0

MyExtObj.display = function(params) {
    var g = this.getGrant();
    var h = "";
    try {
        var ps = GoogleAPITool.getPlusService(g);
        var me = GoogleAPITool.plusGetMe(ps);
        h += "<div>Hello " + me.getDisplayName() + " (" + me.getEmails().get(0).getValue() + ")" +
            "<br/><img src=\"" + me.getImage().getUrl() + "\"/>" +
            "</div>";
    } catch (e) {
        h+= "<div class=\"workerror\">" + g.T("ERROR") + "<div class=\"worktextblock mono\">" + e.message + "</div></div>";
    }
    return HTMLTool.border(h);
};

CLEditor® HTML editor

Typical usage in a standard page is something like:

MyExtObj.display = function(params) {
    this.appendCSSIncludes(HTMLTool.htmleditorCSS());
    this.appendJSIncludes(HTMLTool.htmleditorJS());
    return "<textarea id=\"myeditor\" style=\"width: 400px; height: 300px;\">Hello world !</textarea>" +
        HTMLTool.jsBlock("onload_functions.push(function() { $('#myeditor').cleditor(); });");
};

Check the CLEditor® documentation for details on usage.

Ace® code editor

Typical usage in a standard page is something like:

MyExtObj.display = function(params) {
    return "<div style=\"position: relative;\">" +
            "<div id=\"myeditor\" style=\"width: 400px; height: 300px;\"></div>" +
        "</div>" +
        HTMLTool.jsBlock("onload_functions.push(function() {" +
            "var e = ace.edit('myeditor');" +
            "e.getSession().setMode('ace/mode/html');" +
            "e.getSession().setValue('<p>Hello world !</p>');" +
        "});") +
        HTMLTool.jsIncludes(HTMLTool.aceJS(), "UTF-8"); // Must be done like this and here!
};

Check the Ace® documentation for details on usage.

Fullcalendar®

Typical usage as a standalone page in the 3.x web UI or 4.0 legacy web UI is something like:

MyExtObj.display = function(params) {
    this.appendCSSIncludes(HTMLTool.fullcalendarCSS());
    this.appendJSIncludes(HTMLTool.fullcalendarJS(this.getGrant().getLang()));
    return "<div id=\"mycalendar\"></div>" +
        HTMLTool.jsBlock(onload_functions.push(function() { $('#mycalendar').fullCalendar({ defaultView: 'agendaWeek', timezone: 'local', editable: false });});");
};

If you plan to use this external object also as a part of a view (in this case you get the embedded parameter) you should write the code like this:

MyExtObj.display = function(params) {
    var embedded = params.getBooleanParameter("embedded");
    if (!embedded) {
        this.appendCSSIncludes(HTMLTool.fullcalendarCSS());
        this.appendJSIncludes(HTMLTool.fullcalendarJS(this.getGrant().getLang()));
    }
    return "<div id=\"mycalendar\"></div>" +
        (embedded ? HTMLTool.cssIncludes(HTMLTool.fullcalendarCSS()) + HTMLTool.jsIncludes(HTMLTool.fullcalendarJS(this.getGrant().getLang())) : "") +
        HTMLTool.jsBlock(
            (embedded ? "" : "onload_functions.push(function() {") +
                "$('#mycalendar').fullCalendar({ defaultView: 'agendaWeek', timezone: 'local', editable: false });" +
            (embedded ? "" : "});")
        );
};

If you are only using the version 4.0 responsive web UI then the code can use only the client-side UI API like this:

MyExtObj.display = function(params) {
    this.setDecoration(false);
    this.setHTML('<div id="mycalendar" class="calendar"></div>');
    return this.javascript(
        "$ui.loadCalendar(function() {" +
            "$('#mycalendar').fullCalendar({ defaultView: 'agendaWeek', timezone: 'local', editable: false });" +
        "});"
    );
};

In the responsive UI you do't have to take care of the case where the external object is used as a part of a view.

Check the Fullcalendar® documentation for details on usage.

Field completion

The field completion API allows to add completion capabilities on a text input using a granted business object field.

Typical usage in a standard page is something like:

MyExtObj.display = function(params) {
    this.appendJSInclude(HTMLTool.completionJS()); // Single JavaScript include
    return "<input type=\"text\" id=\"myloginfield\"/>" +
        HTMLTool.jsBlock("onload_functions.push(function() { Simplicite.FieldCompletion.fcomp.register('myloginfield', 'usr_login', 'User'); });");
};

Public usage

The basic thing to do if you want your external object to be available in the public zone (i.e. without authentication) is to grant it to the public user (either by granting it to the PUBLIC group or, better, by giving a responsibility on a dedicated group to the public user).

The other thing to to is to call this.setPublic(true) in the display method.

Most of the time, this kind of public external object does not need to use the default UI layout. This can be achieved by calling this.setDecoration(false) in the display method as described above.

Redirects

Sometime an external object only redirects to another page after having applied some business logic.

MyExtObj.display = function(params) {
    // My business logic...
    return this.sendRedirect(HTMLTool.getHomeURL());
};

To guarantee backward compatibility you are strongly encouraged to use the methods provided by the com.simplicite.util.tools.HTMLTool helper class to get URLs (see JavaDoc) instead of hard coding the URLs that may change in the future.

Other types

Example of an external object generating a PDF document (using the iText® library wrapper 2.1.7 version):

For more information on itext 2.1.7 available on javadoc

MyExtObj.display = function(params) {
    this.setMIMEType(HTTPTool.MIME_TYPE_PDF); // Must force the MIME type
    importPackage(Packages.com.lowagie.text);
    var bos = new java.io.ByteArrayOutputStream();
    var pdf = PDFTool.open(bos);
    pdf.add(new Phrase("Hello world !"));
    PDFTool.close(pdf);
    return bos.toByteArray();
};

Example of an external object generating a Microsoft Excel® document (using the POI® library through its wrapper tool):

MyExtObj.display = function(params) {
    this.setMIMEType(HTTPTool.MIME_TYPE_XLS); // Must force the MIME type
    var xls = new ExcelPOITool();
    /* or as of version 4.0
    this.setMIMEType(HTTPTool.MIME_TYPE_XLSX); // Must force the MIME type
    var xls = new ExcelPOITool(true); // the true argument means using XLSX format
    */
    var s = xls.newSheet("MySheet");
    var r = xls.newRow(0);
    r.add(xls.newCell(0, "Hello"));
    r.add(xls.newCell(1, "world !"));
    s.add(r);
    xls.add(s);
    return xls.generateToByteArray();
};

Example of a web service external object:

MyExtObj.display = function(params) {
    this.setMIMEType(HTTPTool.getMimeTypeWithEncoding(HTTPTool.MIME_TYPE_JSON)); // or, as of version 4.0, this.setJSONMimeType();
    return new JSONObject().put("hello", "world");
};

which renders as:

{"hello":"world"}

Responsive UI pattern

The version 4.0 responsive UI is a single page UI, so the implementation of custom components is using a different pattern than the standalone page pattern of the above examples.

The use of client-side JavaScript is mandatory, a typical UI external objet will return a JavaScript statement and will rely on HTML/CSS/JavaScript resources.

Note: this JavaScript-focused implementation logic is also applicable with 4.0 legacy UI (but not with 3.x UI) as longs as you don't use the JavaScript UI API (but note that by testing wether you are on the responsive UI or the legacy UI in your client-side JavaScript using var responsive = !!window.ui; you can handle both responsive UI and legacy UI cases).

Basic example:

HTML resource

<div id="myextobject"></div>

CSS resource

#myextobject {
    p {
        color: red;
    }
    p > span {
        color: green;
    }
}

Client-side JavaScript resource

MyExtObject = (function($) {
    function render(data) {
        $("#myextobject").html($("<p/>").append(data.greetings).append(" ").append($("<span/>").append(data.name)));
    }
    return { render: render };
})(jQuery);

Server-side script

MyExtObject.display = function(params) {
    var data = new JSONObject()
        .put("greetings", "Hello")
        .put("name", "Bob");
    return this.javascript(this.getName() + ".render(" + data.toString() + ");");
};

Note: For a dynamic HTML part you can also use this.setHTML() instead of a static HTML resource, in the above case it would be this.setHTML('<div id="myextobject"></div>').

Mustache example:

This example is the same as above but using a Mustache® template.

HTML resource:

<template id="myextobject-template">
    <p>{{greetings}} <span>{{name}}</span></p>
</template>
<div id="myextobject"></div>

CSS resource

Same as above

Client-side JavaScript resource

Not needed in this case

Server-side script

MyExtObject.display = function(params) {
    this.addMustache();
    var data = new JSONObject()
        .put("greetings", "Hello")
        .put("name", "Bob");
    return this.javascript("$('#myextobject').html(Mustache.render($('#myextobject-template').html(), " + data.toString() + ");");
};

Submitting and returning dynamic data

HTML resource:

Your name: <input id="myextobject-name" type="text">Bob</input>
<button id="myextobject-ok">OK</button>
<div id="myextobject"></div>

CSS resource

Same as above

Client-side JavaScript resource

MyExtObject = (function($) {
    var url;
    function submit() {
        $.ajax({ url: url, data: { name: $("#myextobject").val() }, type: "post", dataType: "json" }).done(render);
    }
    function render(data) {
        $("#myextobject").html($("<p/>").append(data.greetings).append(" ").append($("<span/>").append(data.name)));
    }
    function init(params) {
        url = params.baselocation;
        $("#myextobject-ok").click(submit);
    }
    return { init: init };
})(jQuery);

Server-side script

MyExtObject.display = function(params) {
    if (params.isPost()) {
        this.setJSONMIMEType();
        return new JSONObject()
            .put("greetings", "Hello")
            .put("name", params.getParameter("name").trim());
    }
    return this.javascript(this.getName().".init(" + params.toJSON() + ");");
};