Web Applications server-side public web pages in Adobe Campaign Classic, created via a drag and drop editor. They are built on top of the ACC JSSP framework and provide access to the workflow ctx
variable.
How web apps work
An ACC web app is just a visual tool to generate a JSSP page:
The JSSP code is generated via the XSL template web-webApp.xsl
which calls web-core.xsl
, which contains:
import core.xsl
<xsl:call-template name="serverScriptInit"/>
response.addHeader(Pragma, Cache-Control, Expires, Content-type)
<xsl:call-template name="initActivities"/>
if !g_bNoRendering
<html>
<head>
<meta http-equiv="MSThemeCompatible" content="Yes"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<xsl:call-template name="css"/>
[...]
core.xsl
contains XSL templates such as
<xsl:template name="serverScriptInit">
<xsl:template name="initActivities">
<xsl:template name="css">
Using the ctx
variable in the Javascript frontend
Create a dead simple web app with a query on Recipients (named queryRecipients
) and a page (without transition):
HTML code of the Page
, using the Bootstrap 4 starter template:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Starter Template Β· Bootstrap</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.3.1/dist/css/bootstrap.min.css">
</head>
<body>
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top"><a class="navbar-brand" href="#">Navbar</a> <button class="navbar-toggler" id="input155197075600849" aria-expanded="false" aria-controls="navbarsExampleDefault" aria-label="Toggle navigation" type="button" data-target="#navbarsExampleDefault" data-toggle="collapse"> <span class="navbar-toggler-icon"></span> </button>
<div class="collapse navbar-collapse" id="navbarsExampleDefault">
<ul class="navbar-nav mr-auto">
<li class="nav-item active"><a class="nav-link" href="#">Home</a></li>
<li class="nav-item nav-link">Bootstrap 4 starter template</li>
</ul>
</div>
</nav>
<main class="container mt-5 pt-3"></main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.3.1/dist/js/bootstrap.min.js" type="text/javascript"></script>
</body>
</html>
The ctx
variable is an XML created automatically by Adobe Campaign and injected into the HTML. With _debug
enabled, it is shown in a <pre>
block:
It is available as a Javascript DOMElement
in the frontend with document.controller.ctx
:
See w3c reference for DOMElement
Using the ctx
variable in the Javascript backend
This ctx
var can also be used in the backend Scripts
and in the Page
itself. Insert in <main></main>
the following table:
<table class="table table-bordered table-hover table-striped table-sm">
<thead>
<tr><th>@firstName</th><th>@lastName</th><th>@email</th></tr>
</thead>
<tbody>
<% for each(var recipient in ctx.queryRecipients.recipient){ %>
<tr>
<td><%= recipient.@firstName %></td>
<td><%= recipient.@lastName %></td>
<td><%= recipient.@email %></td>
</tr>
<% } %>
</tbody>
</table>
Output:
Recap:
Using ctx
for interaction between pages
Now that we have a list of recipients, letβs display the customer profile of one selected Recipient. First, create a Web app variable which will store the value of the selected recipient id:
To enable transitions and interactions between pages, the page must contain a <form id="page-form>">
. Insert it in the <main
:
<main>[...]</main>
becomes
<main><form method="post" name="page" id="page-form">[...]</form></main>
Add a transition named transition1
, connected to a query where @id = $([vars/recipientId])
and another page:
In the first page, add a new <td>
with a Button as follow:
<td>
<a href="#" class="btn btn-sm btn-primary"
onclick="
document.controller.setValue('/ctx/vars/recipientId', '<%= recipient.@id %>'); // make sure to wrap the recipient-id in quotes, otherwise the ACC editor will throw a JS error)
document.controller.submit('next', '_self', 'transition1');">
Show
</a>
</td>
This is made possible via the setValue(path, value)
and getValue(path)
functions of document.controller
which is a UIController
:
and submit(formAction, formTarget, transitionName)
:
/**
* Inject an <input name="ctx"> in the <form id="page-form">,
* Then submits the form
*
* @param strAction can be any of none|refresh|next|previous
* @param strTarget can be any of the HTML attribute target _blank|_self|_parent...
* @param strTransition name of the transition to call in case of accAction=next
* @param bForceNoFile file are already uploaded, don't reupload it
* @param bNoWait don't display waiting box
*/
UIController.prototype.submit = function(strAction, strTarget=null, strTransition=null, bForceNoFile=null, bNoWait=null){};
Save, refresh, click on Show and youβll get this:
For both pages, download the HTML codes on gist.github.com.
Update a recipient based on ctx.recipient.@id
var now = formatDate(new Date(), "%4Y-%2M-%2D %02H:%02N:%02S")
xtk.session.Write(
<recipient xtkschema="nms:recipient" _operation="update" _key="@id" id={ctx.recipient.@id}
lastModified={now}
firstName="New first name"
/>
);
// equivalent to:
var recipient = NLWS.nmsRecipient.load(ctx.recipient.@id);
recipient.firstName = "New first name";
recipient.save();
Use images and enum
<%= NL.route('nms:task.png', 'reverse_img') %> // '/nms/img/task.png'
/**
* Given a list of <enumValue>, return img using name
*
* @param enum XML, <node><enumValue img="nms:canceled.png" name="canceled"/><enumValue img="nms:task.png" name="todo"/></node>
* @param enumName string, 'canceled'
*
* @example getEnumFieldWithName(<node><enumValue img="nms:canceled.png" name="canceled"/><enumValue img="nms:task.png" name="todo"/></node>, 'canceled', 'img') // nms:canceled.png
*/
function getEnumFieldWithName(enum, enumName, enumField){
for each(var e in enum.enumValue){
if(e.@['name'] == enumName){
return e.@[enumField];
}
}
return '';
}
<%= getEnumFieldWithName(ctx.queryEnumClientelingStatus, task.@status2, 'img') %> // nms:canceled.png
<img class="nlui-widget" src="<%= NL.route(getEnumFieldWithName(ctx.queryEnum, aVariableHere, 'img'), 'reverse_img') %>"/>
Use NL.QueryDef
to execute client-side SOAP calls in Javascript from the browser
Can be used to create a Single Page App (SPA) for specific goals, such as a read-only view of recipients, with filters, orders, etc.
β οΈ the result is in XML format. For JSON format see next chapter.
var queryDef = new NL.QueryDef("nms:recipient", NL.QueryDef.prototype.OPERATION_SELECT);
queryDef.addSelectExpr("@id"); // add the column @id to the select clause
queryDef.addSelectExpr("@firstName", "@newAttr"); // set @firstName as an attr named "newAttr" in the result XML
queryDef.addSelectExpr("@lastName", "newNode"); // set @lastName as a node named "newNode" in the result XML
queryDef.setLineCount(2); // SQL LIMIT
queryDef.setStartLine(10); // SQL OFFSET
queryDef.setShowSQL(true); // create a <dataSQL>SELECT x,y from Z</dataSQL> node in <recipient-collection>
queryDef.addWhereConditionExpr("@email = '"+email+"'");
var callback = {
onXtkQueryCompleted: function(queryDef, res, error) {
console.log('Recipients found!', res); // see below for res output
}
};
queryDef.execute(NL.session.serverURL + "/nl/jsp/soaprouter.jsp", '', callback);
Output, res
content:
<recipient-collection>
<recipient id="1" newAttr="Jane"><newNode>Doe</newNode></recipient>
<recipient id="2" newAttr="John"><newNode>Doe</newNode></recipient>
<dataSQL>
SELECT R0.iRecipientId, R0.sFirstName, R0.sLastName FROM NmsRecipient R0 WHERE (R0.sEmail = E'x@y.z') LIMIT 2 OFFSET 10
</dataSQL>
</recipient-collection>
Above code taken from nl6/web/core/dce/contentEditor.js
loadFromTemplateId: function(templateId)
.
Doc for queryDef.execute()
@ nl6/web/code/queryDef.js
:
/** Do the soap call
* @strUrl : soap router url
* @sessionToken
* @asyncTarget : enable the asynchronious mode and define the objet to
* notify. That object must implement a onXtkQueryCompleted()
* method.
*/
NL.QueryDef.prototype.execute = function(strUrl, sessionToken, asyncTarget)
Use POST /xtk/queryList.jssp
to get objects in Javascript from the browser
Calling \datakit\xtk\fra\jssp\queryList.jssp
with a POST param queryDef
containing a JSON as an urlencoded string:
var postData = {
"operation":"select", "schema":"xtk:workflow",
"startLine":0, "lineCount":30,
"select":{
"node":[
{"expr":"[.]", "alias":"@cs"},
{"expr":"@label", "alias":"@label"},
{"expr":"[process/@startState]", "alias":"@startState", "enabledIf":"HasPackage('nms:campaign')"},
]
},
"where":{
"condition":[{"expr":"@isModel=0"}]
},
"orderBy":{
"node":[{"expr":"@lastModified", "sortDesc":true}]
},
};
$.post("/xtk/queryList.jssp", {queryDef:encodeURIComponent(JSON.stringify(postData))}, function(response) {
console.log(response); // {data: Array(30), needPagination: true}
console.log(response.data); // [{cs: " ()", label: "", startState: "0", _schema: "xtk:workflow"}]
});