%META:TOPICINFO{author="TWikiContributor" date="1495793747" format="1.1" version="$Rev$"}%
---+!! !EmbeddedJSPlugin
<!--
One line description, required for extensions repository catalog.
   * Set SHORTDESCRIPTION = EJS (Embedded !JavaScript) plugin

Make sure to disable EJS for documentation purpose.
   * Set EJS = off
-->
<sticky>
<div class="twikiTocFloat">
%TOC{title="Page contents"}%
</div>
</sticky>
%SHORTDESCRIPTION%

%I% *See [[EmbeddedJSPluginAPI]] for the full list of available APIs.*

---++ Introduction

This plugin enables [[https://www.google.com/search?q=EJS+Embedded+JavaScript][EJS (Embedded JavaScript) template]] to embed !JavaScript code as part of TWiki topic contents.
!JavaScript is executed on the server side to interact with various TWiki contents (Webs, Topics, Attachments, etc.) and generate TWiki markups, naturally using loops, if-statements, and function calls.

The example below finds and lists all the topics whose names start with "IssueItem".

<verbatim>
<%
var issues = findTopics('IssueItem*');
for (var i = 0; i < issues.length; i++) {
  println('---+++ ' + issues[i]);
});
%>
</verbatim>

In this code, [[EmbeddedJSPluginAPI#findTopics][findTopics()]] and [[EmbeddedJSPluginAPI#println][println()]] are [[EmbeddedJSPluginAPI][API functions]] that are made available by this plugin.

The same thing can be achieved with TWiki variables such as [[VarSEARCH][%<nop>SEARCH{...}%]], but !JavaScript allows you to express the logic in a more flexible way.

While !JavaScript is a full-fledged programming language, it is also safe because arbitrary file system access and command executions are not allowed, while only certain safe interfaces are provided (such as reading/writing TWiki topics with permission restrictions in place).

Once the =EJS= preference variable is set to =on=, the content of a TWiki topic will be processed by the [[https://metacpan.org/pod/EJS::Template][EJS::Template]] Perl module.

<pre>
&nbsp;&nbsp;&nbsp;* Set EJS = on
</pre>

In other words, EJS is _not_ automatically turned on.
However, the administrator can choose to configure <code>$TWiki::cfg{Plugins}{EmbeddedJSPlugin}{DefaultExecute}</code> to =on=, if EJS should be executed by default in all the topics.

It is also possible to use =%<nop>EJS_INCLUDE{...}%= to process EJS, where the code is executed regardless of the =EJS= preference variable.

<verbatim>
%EJS_INCLUDE{"ComponentTopicWithEJS"}%
</verbatim>

It is useful when a library or application component is implemented in EJS, so that it can be used anywhere without having to set the =EJS= preference variable.

---++ Synopsis

Within the EJS tag =&lt;% ... %&gt;=, the code is executed as !JavaScript.
The [[EmbeddedJSPluginAPI#print][print()]] function appends the text to the output content.
The short-hand notation =&lt;%&#61; ... %&gt;= can also be used to print the value of the expression.
Any texts outside of these tags are simply appended to the output, just like the print function.

<verbatim>
<%
function hello() {
  return 'Hello, World!';
}
%>

<%= hello() %>

<% print(hello()) %>

The above two lines are equivalent.

Texts outside of the EJS tags are simply printed.
</verbatim>

---++ Execution Model

When a topic is viewed (via the TWiki =view= script) where the =EJS= preference variable is set to =on=, EJS is executed as a __preprocessor__, prior to expanding the TWiki variables (e.g. =&#37;TOPIC&#37;=) and finally rendering HTML.

An exception is the =%<nop>EJS_INCLUDE{...}%= tag, which is processed as part of all the TWiki variables.

Roughly speaking, the rendering phases will take place in the following order.

   1. Preference variables (=* Set NAME &#61; VALUE=) are extracted.
   1. EJS tags (=<% ... %>=) are processed with a !JavaScript engine.
   1. TWiki variables (=%<nop>VARIABLE%=) are expanded, where =%<nop>EJS_INCLUDE{...}%= tags are also expanded.
   1. TWiki markups (=---++ Heading=) are converted to HTML.

This means TWiki variables that appear within !JavaScript code will look as they are, rather than the expanded texts.

<verbatim>
var topic = '%TOPIC%';
// During JavaScript execution, the value is literally '%TOPIC%' rather than something like 'WebHome'.
</verbatim>

On the other hand, some API functions _do_ expand TWiki variables that are used as arguments.

<verbatim>
var topics = findTopics('%SYSTEMWEB%.Web*');
// The findTopics() will expand the argument, so it is treated as something like 'TWiki.Web*'.
</verbatim>

If necessary, you can call [[EmbeddedJSPluginAPI#expandVariables][expandVariables()]] or [[EmbeddedJSPluginAPI#getVariable][getVariable()]] to expand TWiki variables.

<verbatim>
var topic = expandVariables('%TOPIC%');
// or
var topic = getVariable('TOPIC');
</verbatim>

It is possible to modify preference variables by calling [[EmbeddedJSPluginAPI#setVariable][setVariable()]].

<pre>
&lt;!--
&nbsp;&nbsp;&nbsp;* Set FOO = Value 1
--&gt;
&lt;%
setVariable('FOO', 'Value 2');
%&gt;

%<nop>FOO% &lt;!-- "Value 2" is printed here --&gt;
</pre>

For the =%<nop>EJS_INCLUDE{...}%= tag, a completely new !JavaScript environment is created with an initial namespace each time =%<nop>EJS_INCLUDE{...}%= is expanded.
Thus, functions and variables defined in !JavaScript code cannot be shared between the including and included topics.

The three examples below demonstrate the namespace separation:

<verbatim>
<!-- Topic name: TopicA -->
<%
function funcA() {...} // Define funcA()
%>
</verbatim>

<verbatim>
<!-- Topic name: TopicB -->
%EJS_INCLUDE{"TopicA"}%
<%
// funcA() is not defined here
%>
<%
function funcB() {...} // Define funcB()
%>
%EJS_INCLUDE{"TopicC"}%
</verbatim>

<verbatim>
<!-- Topic name: TopicC -->
<%
// funcB() is not defined here
%>
</verbatim>

---++ Developing Libraries

Re-usable libraries can be developed in a TWiki topic, which can be referenced via the [[EmbeddedJSPluginAPI#requireTopic][requireTopic()]].

<verbatim>
<!-- Topic name: ExampleLibrary -->
<%
function exampleFunction1() {
  ...
}
function exampleFunction2() {
  ...
}
%>

<!-- Topic name: UsingLibrary -->
<%
requireTopic('ExampleLibrary'); // Load library
exampleFunction1();
exampleFunction2();
%>
</verbatim>

The =requireTopic()= will load the specified topic _only once_. The expected usage of =requireTopic()= is to write !JavaScript functions (or class-like implementation), which can be utilized in other topics. It should not be used like [[VarINCLUDE][%<nop>INCLUDUE{...}%]] to print texts just by calling =requireTopic()=.

It is a good practice to put a collection of library code in a designated Web, so that the library components can be utilized across many Webs.
If the =requireTopic()= is used within a library to load another library (often sub-components), the topic name is referenced relative to where the =requireTopic()= is invoked.

<verbatim>
<%
requireTopic('LibraryWeb.ExampleLibrary');
%>

<!-- LibraryWeb.ExampleLibrary -->
<%
requireTopic('SubComponent1');
requireTopic('SubComponent2');
// These sub-components are assumed to be in the same "LibraryWeb"
%>
</verbatim>

---++ Function Arguments

Most EJS API functions parse the given arguments in two ways: _positional_ and _keyed_.

For example, [[EmbeddedJSPluginAPI#findTopics][findTopics()]] can accept the arguments in either of the following ways:

<verbatim>
findTopics('Web.Topic*');
findTopics({topic: 'Web.Topic*'});
findTopics({web: 'Web', topic: 'Topic*'});
</verbatim>

Keyed parameters can span across multiple arguments:

<verbatim>
findTopics({web: 'Web'}, {topic: 'Topic*'});
</verbatim>

If the same key appears multiple times, the last one has the precedence.

A callback function can be given as a positional function object or a =callback= key:

<verbatim>
findTopics('Web.Topic*', function () {...});
findTopics('Web.Topic*', {callback: function () {...}});
</verbatim>

TWiki variables (=%VARIABLE%=) included in the arguments are expanded if the arguments are of the following types:
   * =web=, =toWeb=, =baseWeb=
   * =topic=, =toTopic=
   * =file=, =toFile=
   * =user=
   * =url=

In other cases, TWiki variables are *not* expanded, but used literally.

For example, [[#saveTopic][saveTopic()]] takes two arguments =topic= and =text=, where only =topic= argument will expand TWiki variables.

<verbatim>
saveTopic('%TOPIC%_Data', 'Content contains %VARIABLE%');
</verbatim>

In the above example, the first argument is expanded (since it is the =topic= argument), while the second argument is *not* expanded (since it is the =text= argument).

---++ Callback

Some EJS API functions accept a callback function, where the callback invocation is usually a loop iteration.

[[EmbeddedJSPluginAPI#findTopics][findTopics()]] is an example:

<verbatim>
<%
findTopics('IssueItem*', function (topic, loop) {
  // "topic" is a string like "IssueItem1234"
  // "loop" is an object - See below
});
%>
</verbatim>

The first argument is each value in the loop, and the second argument is a loop object that has information about the current loop iteration.

| *Property* | *Value* | *Description* |
| =loop.first= | =0= or =1= | The value is =1= if this is the first iteration in the loop; =0= otherwise |
| =loop.last=  | =0= or =1= | The value is =1= if this is the last iteration in the loop; =0= otherwise |
| =loop.index= | <code>0</code>-based index | The index starts at =0= for the first iteration, and increments as the loop goes on |
| =loop.value= | Current value | The value of the current loop while iterating through multiple values |

The return value of the callback function will affect the result array as the final return value of the API function.
If the callback returns nothing (=undefined= or =null=), the value in the iteration will be excluded from the result. All other return values from the callback will be the values of the final array.

<verbatim>
<%
var topics = findTopics('IssueItem*', function (topic, loop) {
  if (!loop.first) {
    return '<b>' + topic + '</b>';
  }
});
/*
Returns something like:
['<b>IssueItem2</b>', '<b>IssueItem3</b>', '<b>IssueItem4</b>', ...]
*/
%>
</verbatim>

---++ Save Action Policies

When TWiki contents are modified via EJS (e.g. [[EmbeddedJSPluginAPI#saveTopic][saveTopic()]] and [[EmbeddedJSPluginAPI#moveTopic][moveTopic()]]), there are some restrictions in order to prevent unintended data changes.

For example, when the EJS script page is displayed right after saving the script, the modification would take place immediately even if you just wanted to save the script temporarily.
In addition, a search engine crawler might access your page while you are developing something, where [[EmbeddedJSPluginAPI#saveTopic][saveTopic()]] call might corrupt some data if it were not for any protection.

---+++ POST Method Policy

By default, the HTTP POST method is required to invoke save actions. In order to run EJS with any save actions, the =POST= method should be sent to the TWiki =view= script (rather than the =save= script etc.).

<verbatim>
<form method="POST">
<input type="submit" value="Save">
</form>
<%
if (isPost()) {
  var topic = "...";
  var text = "...";
  saveTopic(topic, text);
  println("Saved!");
}
%>
</verbatim>

If you are very confident, set =EJS_POST_METHOD_POLICY= preference variable to =off= to disable this policy. See also [[#Configurations]].

---+++ Same-Web Policy

By default, the save actions can modify only the contents within the same Web or its !SubWebs (and all the way downwards recursively). This is to prevent the EJS script to modify contents of unintended Webs outside the current scope. Note the save actions can be invoked from within libraries (loaded by [[EmbeddedJSPluginAPI#requireTopic][requireTopic()]]) to modify contents within the _currently visited_ Web, rather than where the library topic resides.

If you are very confident, set =EJS_SAME_WEB_POLICY= preference variable to =off= to disable this policy. See also [[#Configurations]].

---+++ Crypt Token Policy

If =$TWiki::cfg{CryptToken}{Enable}= is turned on, any save actions listed by =$TWiki::cfg{CryptToken}{SecureActions}= will require the CRYPTTOKEN as the POST parameter.

<verbatim>
<form method="POST">
<input type="hidden" name="crypttoken" value="%CRYPTTOKEN%">
<input type="submit" value="Save">
</form>
<%
if (isPost()) {
  ...
}
%>
</verbatim>

---++ Namespace

All the built-in API functions are defined in the global scope, which may not be ideal in some cases.

If the =EJS_NAMESPACE= preference variable is set, the API functions will be defined in the specified object name.

<pre>
&lt;!--
&nbsp;&nbsp;&nbsp;* Set EJS_NAMESPACE = TWiki
--&gt;
&lt;%
TWiki.findTopics(...);
%&gt;
</pre>

The namespace can be specified as chained object path by the "." notation (e.g. "Foo.Bar.Baz") but it does not allow arbitrary !JavaScript expression.

The following functions are always defined in the global scope (that is, they cannot be in the specified namespace):
   * [[EmbeddedJSPluginAPI#print][print()]]
   * [[EmbeddedJSPluginAPI#println][println()]]

The namespace for =%<nop>EJS_INCLUDE{...}%= also inherits this setting, but it is also possible to specify the namespace in the tag, if the included component is implemented with an assumed namespace.

<verbatim>
%EJS_INCLUDE{"ComponentTopic" namespace="SomeNamespace"}%
</verbatim>

---++ Data Types

TWiki source code is written in Perl, and the EJS API functions attempt to convert values between !JavaScript and Perl as much as possible.

However, due to the difference between the two languages, there are some data types that cannot be converted straightforwardly.

---+++ Boolean Values

Although !JavaScript has the notion of Boolean values (=true= and =false=), some EJS API functions return =1= or =0= instead, due to the limitation with Perl.

While the value can be used as a conditional expression (such as =if= statement), it should not be assumed that the values are integer or boolean for potential future compatibility.

---+++ Null Values

EJS API functions will not distintuish =undefined= and =null= if they are passed as arguments. EJS API will always return =undefined= when there are no values to return.

Somewhat confusingly, if values are serialized by [[EmbeddedJSPluginAPI#JSON][JSON.stringify()]], then the corresponding value is encoded as =null=.

---+++ Date/Time Values

Some EJS API functions return a Unix timestamp (such as [[EmbeddedJSPluginAPI#getEditLock][getEditLock()]]), which is an integer value of seconds that have elapsed since 1 January 1970.

The integer value can be converted to a !JavaScript =Date= object like this:

<verbatim>
var expires = getEditLock('SomeTopic').expires; // Unix timestamp in seconds
var dateObject = new Date(expires * 1000); // Convert timestamp to milliseconds and pass it as the argument
</verbatim>

Similarly, some EJS API functions accept a Unix timestamp as an argument ([[EmbeddedJSPluginAPI#getRevisionAtTime][getRevisionAtTime()]]), which can be converted like this:

<verbatim>
var dateObject = new Date();
dateObject.setMonth(dateObject.getMonth() - 3); // Three months ago
getRevisionAtTime('SomeTopic', dateObject.getTime() / 1000); // Convert milliseconds to timestamp
</verbatim>

---+++ Custom Object Types

If a custom object (instanciated by the =new= operator) is passed as an argument to an EJS API function, it is converted to a plain object.

<verbatim>
function CustomParam(web, topic) {
    this.web = web;
    this.topic = topic;
}
var param = new CustomParam('WebName', 'TopicName');
readTopic(param); // same as readTopic({web: 'WebName', topic: 'TopicName'})
</verbatim>

---++ Exceptions

Some API functions throw an exception that your !JavaScript code can =catch=.

Due to the limitation with conversion between Perl and !JavaScript, API functions can only throw a standard =Error= object.
The =message= property of the object contains the string error message.

<verbatim>
try {
    readTopic('NonExistingTopic');
} catch (e) {
    println(e.message);
}
</verbatim>

---++ Raw View

If EJS is turned on, it is executed when the raw view is accessed with =?raw=expandvariables=.

---++ Dynamic Template

If EJS dynamic template is enabled, EJS is executed when a new topic is created with a template topic.

A template topic is copied into the edit page's text box for topic creation, where the new content is dynamically generated by executing EJS in the template topic.

For example, consider a topic named =ExampleTemplate= contains some EJS script:

<verbatim>
---+ %TOPICTITLE%

Copyright (c) <%=new Date().getFullYear()%>
</verbatim>

When a new topic named =NewTopic= is being created where =ExampleTemplate= is specified as the topic template, the edit page will start with the generated content:

<verbatim>
---+ %TOPICTITLE%

Copyright (c) 2017
</verbatim>

The =WebTopicEditTemplate= topic can also be used as an EJS dynamic template.

This feature can be enabled by either =EJS_DYNAMIC_TEMPLATE= preference variable (which should be placed in the =WebPreferences= topic) or =$TWiki::cfg{Plugins}{EmbeddedJSPlugin}{DefaultDynamicTemplate}= set by the administrator.

---++ Configurations

| *Preference Variable*    | *Configuration*                                                                    | *Default* | *Description* |
| =EJS=                    | <code><nobr>$TWiki::cfg{Plugins}{EmbeddedJSPlugin}{DefaultExecute}</nobr></code>          | off       | Enable EJS execution (=view= script). |
| =EJS_DYNAMIC_TEMPLATE=   | <code><nobr>$TWiki::cfg{Plugins}{EmbeddedJSPlugin}{DefaultDynamicTemplate}</nobr></code>  | off       | Enable EJS dynamic template (=edit= script). |
| =EJS_NAMESPACE=          | <code><nobr>$TWiki::cfg{Plugins}{EmbeddedJSPlugin}{DefaultNamespace}</nobr></code>        |           | Specify the namespace for API functions. |
| =EJS_POST_METHOD_POLICY= | <code><nobr>$TWiki::cfg{Plugins}{EmbeddedJSPlugin}{DefaultPostMethodPolicy}</nobr></code> | on        | Apply POST method policy for save actions. |
| =EJS_SAME_WEB_POLICY=    | <code><nobr>$TWiki::cfg{Plugins}{EmbeddedJSPlugin}{DefaultSameWebPolicy}</nobr></code>    | on        | Apply same-web policy for save actions. |
|                          | <code><nobr>$TWiki::cfg{Plugins}{EmbeddedJSPlugin}{JavaScriptEngine}</nobr></code>        |           | Set !JavaScript engine, which is automatically determined by [[CPAN:EJS::Template]] by default. |
| =EJS_TIMEOUT=            | <code><nobr>$TWiki::cfg{Plugins}{EmbeddedJSPlugin}{DefaultTimeout}</nobr></code>          | 5         | Set !JavaScript execution timeout in seconds. |
|                          | <code><nobr>$TWiki::cfg{Plugins}{EmbeddedJSPlugin}{MaxTimeout}</nobr></code>              | 180       | Set the maximum limit configurable by the =EJS_TIMEOUT= preference variable. |
| =EJS_DEFAULT_BASE_WEB=   | <code><nobr>$TWiki::cfg{Plugins}{EmbeddedJSPlugin}{DefaultBaseWeb}</nobr></code>          | _default  | Set the template web name used for [[EmbeddedJSPluginAPI#createWeb][createWeb()]] API. |

---++ EJS_INCLUDE

Below is a list of parameters that can be specified for the =%<nop>EJS_INCLUDE{...}%= tag.

| *Parameter*        | *Description* |
| =_DEFAULT=         | Topic name of the EJS component |
| =namespace=        | Namespace for EmbeddedJSPlugin API functions, if required by the EJS component |
| =postMethodPolicy= | True/false to enable/disable the POST method policy |
| =sameWebPolicy=    | True/false to enable/disable the same-web policy |
| =timeout=          | Timeout in seconds |
| =defaultBaseWeb=   | Template web name for [[EmbeddedJSPluginAPI#createWeb][createWeb()]] API |

By default, these parameters are inherited from the current preference variables or TWiki configurations.

---++ Installation Instructions

__Note:__ You do not need to install anything on the browser to use this plugin. The following instructions are for the administrator who installs the plugin on the TWiki server.

%TWISTY{
 mode="div"
 showlink="Show details %ICONURL{toggleopen}% "
 hidelink="Hide details %ICONURL{toggleclose}% "
}%

   * For an __automated installation__, run the [[%SCRIPTURL{configure}%][configure]] script and follow "Find More Extensions" in the in the __Extensions__ section. 
      * See the [[http://twiki.org/cgi-bin/view/Plugins/BuildContribInstallationSupplement][installation supplement]] on TWiki.org for more information.

   * Or, follow these __manual installation__ steps: 
      * Download the ZIP file from the Plugins home (see below).
      * Unzip ==%TOPIC%.zip== in your twiki installation directory. Content:
        | *File:* | *Description:* |
        | ==data/TWiki/EmbeddedJSPlugin*.txt== | Plugin topics |
        | ==lib/TWiki/Plugins/EmbeddedJSPlugin.pm== | Plugin Perl module |
        | ==lib/TWiki/Plugins/EmbeddedJSPlugin/*.pm== | Component modules |
      * Set the ownership of the extracted directories and files to the webserver user.
      * Install the dependencies.

   * Plugin __configuration and testing__: 
      * Run the [[%SCRIPTURL{configure}%][configure]] script and enable the plugin in the __Plugins__ section.
      * Configure additional plugin settings in the __Extensions__ section if needed.
      * Test if the installation was successful using the example above.

%ENDTWISTY%

---++ Plugin Info

Many thanks to the following sponsors for supporting this work:
   * Acknowledge any sponsors here

%TABLE{ tablewidth="100%" columnwidths="170," }%
|  Plugin Author(s): | TWiki:Main.MahiroAndo |
|  Copyright: | &copy; 2017 TWiki:Main.MahiroAndo %BR% &copy; 2017 TWiki:TWiki.TWikiContributor |
|  License: | [[http://www.gnu.org/licenses/gpl.html][GPL (Gnu General Public License)]] |
|  Plugin Version: | 2017-08-24 |
%TWISTY{
 mode="div"
 showlink="Show Change History %ICONURL{toggleopen}%"
 hidelink="Hide Change History %ICONURL{toggleclose}% "
}%
%TABLE{ tablewidth="100%" columnwidths="170," }%
|  Change History: | <!-- versions below in reverse order -->&nbsp; |
|  2017-08-24: | TWikibug:Item7817: Rename plugin to WikiName - TWiki:Main.MahiroAndo |
|  2017-08-05: | TWikibug:Item7817: Add some API functions - TWiki:Main.MahiroAndo |
|  2017-07-21: | TWikibug:Item7817: Initial release - TWiki:Main.MahiroAndo |
%ENDTWISTY%
%TABLE{ tablewidth="100%" columnwidths="170," }%
|  Dependencies: | CPAN:EJS::Template, !JavaScript engine (either CPAN:JE or CPAN:JavaScript::V8), CPAN:HTML::Entities, CPAN:IO::String, CPAN:JSON, CPAN:Scalar::Util, CPAN:Text::CSV |
|  Plugin Home: | http://twiki.org/cgi-bin/view/Plugins/EmbeddedJSPlugin |
|  Feedback: | http://twiki.org/cgi-bin/view/Plugins/EmbeddedJSPluginDev |
|  Appraisal: | http://twiki.org/cgi-bin/view/Plugins/EmbeddedJSPluginAppraisal |

__Related Topics:__ %TWIKIWEB%.TWikiPlugins, %TWIKIWEB%.DeveloperDocumentationCategory, %TWIKIWEB%.AdminDocumentationCategory, %TWIKIWEB%.TWikiPreferences

<!-- Do _not_ attempt to edit this topic; it is auto-generated. Please add comments/questions/remarks to the feedback topic on twiki.org instead. -->
