AnsweredAssumed Answered

Html & embedded JS

Question asked by anweber on Jul 1, 2006
Hi all,

I should like to share with you a useful trick.  I have no questions.  I have no bugs to submit (in this post :) ).  I have nothing to complain about.

Sorry for my poor English (very poor indeed).  I hope my writing is clear enough.



Initial context :

For a proof of concept with Alfresco, I have proposed an alternative solution to create a simple workflow.  I will describe it in an other post.  This solution use the command servlet to call a script written in JavaScript.  This script should, of course, create the content of the HTML page to return to the caller.  To do not create this HTML in hard in my code,  I have imagined to use HML templates.  Obviously, we have to add some dynamic information into the content coming from such templates before sending it to our user.  A convenient way to do that is to replace some parts of the HTML template content with the value of some variables of the script that process the request.



There is my first attempt :





function replaceHostedJavaScript(content){

            var posBeg;

            var posEnd;

            var jsExpr;

            while(true){

                        posBeg = content.indexOf("<js>");

                        if(posBeg>0){

                                    posEnd = content.indexOf("</js>",posBeg);

                                    if (posEnd>0){

                                                jsExpr = content.substr(posBeg+4, posEnd-posBeg-4);

                                                content = content.substr(0,posBeg) +  eval(jsExpr + ";") + content.substr(posEnd+5);

                                    }

                        }

                        else return(content);

            }

            return(content);

}




If you do this test :





            var x = "hello";

            var htmlTemplateContent = "<html><js>x</js></html>";

            var html = replaceHostedJavaScript(htmlTemplateContent);


You will obtain html = ?<html>hello</html?.

Of course, ?htmlTemplateContent? should come from the reading of a stored node in Alfresco.

Remark that you could also call a JS function from the HTML template :







function sayHello(){

            return("hello world");

}



function test2(){

            x = "hello";

            var htmlTemplateContent = "<html><js>sayHello();</js></html>";

            var html = replaceHostedJavaScript(htmlTemplateContent);

            return(html);

}

var x;

var html = test2();






An evolution of this technic is to transform the HTML with embedded JS into JS code, and to eval the result.  Then it will be possible to use JavaScript tests, loops, ? directly in the code of the HTML template to obtain a more dynamic content.



There is the function to do the evaluation:





function convertHtmlCodeInText(htmlCode){

            result = htmlCode.replace(/</g,"&lt;");

            result = result.replace(/>/g,"&gt;");

            return(result);

}

 

function evalHtmlWithEmbeddedJs(content){

            var posOpenJsTag;

            var posCloseJsTag;

            var contentBeforeJsExpr;

            var jsExpr;

            var contentAfterJsExpr;

            var jsOpenTag = "<js>";

            var jsCloseTag = "</js>";

            var jsOpenTagLen = 4; // "<js>".length

            var jsCloseTagLen = jsOpenTagLen+1;

            var toEval = "html=\"\";\n";

            while(true){

                        posOpenJsTag = content.indexOf(jsOpenTag);

                        if(posOpenJsTag>0){

                                    posCloseJsTag = content.indexOf(jsCloseTag,posOpenJsTag + jsOpenTagLen);

                                    if (posCloseJsTag>0){

                                                contentBeforeJsExpr = content.substr(0,posOpenJsTag);

                                                jsExpr = content.substr(posOpenJsTag+jsOpenTagLen, posCloseJsTag-posOpenJsTag-jsOpenTagLen);

                                                contentAfterJsExpr = content.substr(posCloseJsTag + jsCloseTagLen);

                                                toEval += "html+= \"" + contentBeforeJsExpr.replace(/\r?\n/g, "\\n").replace(/\"/g, "\\\"") + "\";\n";

                                                toEval += jsExpr + "\n";

                                                content = contentAfterJsExpr;

                                    } else break;

                        }

                        else break;

            }

            if (content != "") toEval += "html+= \"" + content.replace(/\r?\n/g, "\\n").replace(/"/g, "\\\"") + "\";\n";

            var result;

            try{

                        result = eval(toEval);

            }

            catch(exception){

                        result = "<html>error \"" +  exception + "\" in the evaluation of <br>" + convertHtmlCodeInText(toEval) + "</html";

            }

            return(result);

}




There is a little test:







function print(content){

            html += content;

}





var x = "hello"; // global var referred in  ?htmlTemplateContent?

var html; // global var used by the functions ?print? & ?evalHtmlWithEmbeddedJs?

var htmlTemplateContent = "<html><ul><js>var c; for(c=0;c<3;c++){ </js><li>count : <js> print(c + \" - \" + x);</js></li><js>}</js></ul></html>";

var result = result = evalHtmlWithEmbeddedJs(htmlTemplateContent);




You should obtain the value ?<html><ul><li>count : 0 - hello</li><li>count : 1 - hello</li><li>count : 2 - hello</li></ul></html>? into the variable ?result?.

I am not completely satisfied by this code because it uses some global variables.  It?s just a first draft.  I will search a best solution later.



Now, I will show you a more realistic example:

    * I will develop a simple script that returns the content of the HTML template, evaluates it and returns the result.  The name of the HTML template is given in the URL.
    * I also create an HTML template showing the content of a given folder.  .  The path of this folder is given in the URL.



There is the code of my script:





/*

            Script to execute by the command servlet

            called by : http://localhost:8080/alfresco/command/script/execute/workspace/SpacesStore/<nodeId_of_this_script>?file=<name_of_html_file>

            Something like : http://localhost:8080/alfresco/command/script/execute/workspace/SpacesStore/afc7cfd7-08fc-11db-8464-435ab751fd63?file=browseFolder.html



*/



var debugMode=true;



// for debugging purpose

function log(msg){

            var logFileName= script.name + ".log";

            var logFile;

          

            if (!debugMode) return;

            logFile = companyhome.childByNamePath(logFileName);

            if (logFile == null)

            {

               logFile = companyhome.createFile(logFileName);

            }

            logFile.content += msg + "\r\n";

}





function getFileContent(folder, fileName){

            return("" + folder.childByNamePath(fileName).content);

}





function convertHtmlCodeInText(htmlCode){

            result = htmlCode.replace(/</g,"&lt;");

            result = result.replace(/>/g,"&gt;");

            return(result);

}



function evalHtmlWithEmbeddedJs(content){

            var posOpenJsTag;

            var posCloseJsTag;

            var contentBeforeJsExpr;

            var jsExpr;

            var contentAfterJsExpr;

            var jsOpenTag = "<js>";

            var jsCloseTag = "</js>";

            var jsOpenTagLen = 4; // "<js>".length

            var jsCloseTagLen = jsOpenTagLen+1;

            var toEval = "html=\"\";\n";

            while(true){

                        posOpenJsTag = content.indexOf(jsOpenTag);

                        if(posOpenJsTag>0){

                                    posCloseJsTag = content.indexOf(jsCloseTag,posOpenJsTag + jsOpenTagLen);

                                    if (posCloseJsTag>0){

                                                contentBeforeJsExpr = content.substr(0,posOpenJsTag);

                                                jsExpr = content.substr(posOpenJsTag+jsOpenTagLen, posCloseJsTag-posOpenJsTag-jsOpenTagLen);

                                                contentAfterJsExpr = content.substr(posCloseJsTag + jsCloseTagLen);

                                                toEval += "html+= \"" + contentBeforeJsExpr.replace(/\r?\n/g, "\\n").replace(/\"/g, "\\\"") + "\";\n";

                                                toEval += jsExpr + "\n";

                                                content = contentAfterJsExpr;

                                    } else break;

                        }

                        else break;

            }

            if (content != "") toEval += "html+= \"" + content.replace(/\r?\n/g, "\\n").replace(/"/g, "\\\"") + "\";\n";

            var result;

            try{

                        result = eval(toEval);

            }

            catch(exception){

                        result = "<html>error \"" +  exception + "\" in the evaluation of <br>" + convertHtmlCodeInText(toEval) + "</html";

            }

            return(result);

}







function getUrlForScriptCall(){

            var url = "";

            url += "/alfresco/command/script/execute/" + script.nodeRef;

            var expr = /\:\/\//;

            url = url.replace(expr,"/");

            return(url);

}





function print(content){

            html += content;

}





function main(){

            log("====begin\n");

                        var templateContent = getFileContent(companyhome.childByNamePath(HTML_TEMPLATES_FOLDER_PATH),args["file"]);

                        var htmlToReturn = evalHtmlWithEmbeddedJs(templateContent);

                        return(htmlToReturn);

            log("\n=====end");

}





// global constants

var HTML_TEMPLATES_FOLDER_PATH = "htmlTemplates"; // relative path to the companyhome



// global vars

var html = ""; // used by the template evaluator







main();




There is the content of my HTML template called ?browseFolder.html? and stored in the space ?companyhome /htmlTemplates? (this path is stored directly in the code of my script, if you want to change, change the code).





<html>

            browse folder <js>print(args.folderName);</js> <br>

            <js>

                        var folderName = args.folderName;

                        if (folderName  != null)  {

                                    var folder = companyhome.childByNamePath(folderName);

                                    if (folder != null) {

                                                var children = folder.children;

                                                var i;

                                                var child;

            </js>

            <ul>

            <js>

                                                for (i in children){

                                                            child = children[i];

                                                            if (child.children != null && child.children.length>0 ){

            </js>

                                    <li><a href="<js>print(getUrlForScriptCall());</js>?file=<js>print(args.file);</js>&folderName=<js>print(args.folderName + "/" + child.name);</js>"><js>print(child.name);</js></a></li>

            <js>                                        

                                                            }

                                                            else{

            </js>

                                    <li><a href="/alfresco<js>print(child.url);</js>"> <js>print(child.name);</js> </a></li>

            <js>                                        



                                                            }

                                                }

            </js>

            </ul>

            <js>



                                    }

                        }

            </js>

</html>




The HTML produced by this template includes call-backs to the same script (to show subfolders) and direct calls Alfresco to see the content of files.



In a real case, you should have more HTML tags and direct content than JavaScript code in your HTML template otherwise this solution is not really smart.  It?s possible to reduce the number of JavaScript lines in your HTML template, if you prepare some specific functions that you include in your script that will to the eval (you template will then call those specific functions).  All the business logic should be included in your script not in your template.  In my example there is no business logic (well I admit, it?s not a very good example).



The script will be called by the command servlet via a URL like :

http://localhost:8080/alfresco/command/script/execute/workspace/SpacesStore/<the_scriptID>?file=browseFolder.html&folderName=<folderPath>



For example, in my context it is :

http://localhost:8080/alfresco/command/script/execute/workspace/SpacesStore/afc7cfd7-08fc-11db-8464-435ab751fd63?file=browseFolder.html&folderName=aweTests



It?s not convenient to create ?by hand? such an URL.  I have used this solution in a simple workflow (done only by scripts and not using the ?simple workflow? technic of Alfresco); in this context the initialization (triggered by a rule) of the workflow creates in first HTML message (based on an HTML template) and stores it in the inbox of the first actor.  This is a message like ?You have to do task xxxx, press here if you have finished, press there if you refuse to do it?.  This HTML message includes callbacks to the script.  Then the script is called the first time by the execution of a rule and next times by URL included in the messages.  Then the user never have to key-in such an URL.



In a future post, I will present the ?alternative simple workflow? solution.



I hope I have been clear.  If  you are interested by this solution but you are lost in my explanations,  contact me (I am in holiday until the 15/7).





            Regards,



                        Andr

Outcomes