Domino 9 und frühere Versionen > ND6: Entwicklung

Domino und Prototype/Scriptaculous

(1/2) > >>

flaite:
Hi,

genervt über zunehmend schwer zu maintainende Domino Web Anwendungen bin ich jetzt dabei, für ein wenig Ordnung zu sorgen.

Die beliebten JavaScript-Libraries Prototype und Scriptaculous sollen in Domino Webanwendungen auf eine konsistente Weise genutzt werden. Ich verspreche mir davon ein mehr an Ordnung, schnellere Entwicklungszyklen und weniger Bug-Hunting.

Prototype ist mehr low level und mischt sich in JavaScript ein, um diese Programmiersprache besser händelbar zu machen. Scriptaculous ist mehr high level. Fokussiert auf visuelle Effekte und ein paar widgets.

Wichtig für einfacheres Ajax erscheint mir zunächst einmal typische Domino Idioms wie (hier heisst es) Transferfelder, in Prototype, JavaScript zu transkribieren.
Was sind Transferfelder?
Nun das ist, wenn man per DBLookup oder verwandtes auf ein anderes Dokument zugreift. Dann versucht man ja gerne aus Gründen geringerer Netzbelastung (Performance) mit EINEM DBLookup MEHRERE Daten zu holen. Die Daten werden dann in eine selbstgebastelten Struktur mit kreativen Trennzeichen gebracht. In Formelsprache läßt sich diese Struktur dann mit Listenoperationen oder den For-Schleifen und weiteren Erweiterungen in der Formelsprachen-Engine seit Release 6 parsen. 

Im Browser kann man den DBLookup des Transferfeldes in ein benanntes div-Tag mit eindeutiger ID packen. Dies läßt sich dann auslesen und in ein JavaScript Objekt packen.

Hier ist es var data. Das Beispiel ist noch nicht optimal, aber ein Schritt in die richtige Richtung.

code s. u.

flaite:
Nun das ist viel klarer:
HINWEIS: UNTEN IST DAS WEITERGEMACHT.

--- Code: ---<html>
<head>
<script type="text/javascript" src="prototype.js"></script>
<script type="text/javascript">
function processTransferin() {
var valTransferin = $("transferin").childNodes[0].nodeValue;
arrFieldNameValues = valTransferin.split("~~~");
var data = new Object();
var iterator = function(value, index) {
//alert("element " + index + " is " + value);
arrNameValue = value.split("°=°");
if (arrNameValue[0] && arrNameValue.length > 1) {
if (arrNameValue[1].indexOf("µ")) {
arrNameValue[1] = arrNameValue[1].split("µ");
}
data[arrNameValue[0]] = arrNameValue[1];

}
}
arrFieldNameValues.each(iterator);

    for (name in data) {
var value = data[name];
if ($(name)) {
var txtNode = document.createTextNode(value);
$(name).appendChild(txtNode);
//$(name).innerHTML = value;
}
}

}
</script>

</head>

<body onload="processTransferin();">
<!-- <body> -->
<div id="transferin">Category°=°Muppets Show~~~RequestAuthorsMailRecipients°=°CN=Kermit der Frosch/O=ozzµCN=/OU=Miss Piggy/O=ozz~~~RequestAuthors°=°CN=Waldorf/O=ozz~~~RequestReaders°=°CN=Statler/O=OzzµCN=Fozzy Bär/O=ozzµCN=Gonzo/O=ozz</div>


<hr />
<div>category:<span id="Category"/></div>
<div>requestauthormailrecipients:<span id="RequestAuthorsMailRecipients"/></div>
<div>requestauthors:<span id="RequestAuthors"></span></div>
<div>requestreaders:<span id="RequestReaders"></span></div>


</body>
</html>

--- Ende Code ---

Nur noch transferin wird als berechneter Text vom Server geholt. Die restlichen "Felder" werden auf dem Client und nicht mehr von Domino errechnet. Der Wert der Felder steht in den im Html leeren Span-Tags. Sie werden im onload event der Seite errechnet.

In der realen Anwendung steht der Inhalt von transferin in einem Feld von Category-Dokumenten. Will ich nun über AJAX die Kategorie ändern, muß ich nicht mehr die ganze Seite berechnen. Ich muß mir über ajax nur noch den neuen Wert von transferin besorgen und die JavaScript-Funktion processTransferin() aufrufen. Die Felder werden dann automatisch gesetzt.

Zur Zeit ist das weniger Prototype/Scriptaculous und mehr POJS (Plain Old JavaScript). Die Funktion $ (in $("transferin") ist Prototype. Ausserdem noch dieses var iterator Konstrukt. Man kann den Code
- so kopieren
- in eine html Datei pasten
- prototype.js aus dem Internet runterladen (try google).
- prototype.js in das selbe Verzeichnis wie die html Datei packen.
- html Datei mit dem browser öffnen.

flaite:
Und jetzt mit Ajax. Zugegeben ist das erstmal JSP/Servlet, weil das mit der Debug Unterstützung in Netbeans einfacher ist als in Domino.
Muß also die Ajax Komponente noch in LotusScript transkribieren und die Businesslogik da reinbringen. Kommt morgen  ;)

Hier also die JSP und das Servlet. Das Servlet wird per Ajax aus dem von der JSP generierten html aufgerufen. Und da beginnt Prototype zu glänzen. Wobei ichs noch nicht richtig beherrsche. Z.B. trau ich mich noch nicht richtig an die closures ran.
Die im Ajax ausgetauschten Daten sind in JSON. JSON ist gut.

JSP:

--- Code: ---<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
<%--
The taglib directive below imports the JSTL library. If you uncomment it,
you must also add the JSTL library to the project. The Add Library... action
on Libraries node in Projects view can be used to add the JSTL 1.1 library.
--%>
<%--
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
--%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
   "http://www.w3.org/TR/html4/loose.dtd">

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP Page</title>
    <script type="text/javascript" src="prototype.js"></script>
<script type="text/javascript">
                var ui = {}; // context stuff.
function processTransferin() {
                        ui.transferin = $("transferin");
//var valTransferin = $("transferin").childNodes[0].nodeValue;
                        var valTransferin = $("transferin").innerHTML;
arrFieldNameValues = valTransferin.split("~~~");
var data = new Object();
var iterator = function(value, index) {
//alert("element " + index + " is " + value);
arrNameValue = value.split("°=°");
if (arrNameValue[0] && arrNameValue.length > 1) {
if (arrNameValue[1].indexOf("µ")) {
arrNameValue[1] = arrNameValue[1].split("µ");
}
data[arrNameValue[0]] = arrNameValue[1];

}
}
arrFieldNameValues.each(iterator);

    for (name in data) {
var value = data[name];
if ($(name)) {
//var txtNode = document.createTextNode(value);
//$(name).appendChild(txtNode);
$(name).innerHTML = value;
}
}

}
 
                function changeCat(newCat) {
// DER AJAX Code.
                    var request = new Ajax.Request(
                        "AjaxServlet",
                    {
                        method: 'get',
                        parameters: "cat=" + newCat,
                        onComplete: parseTransferin,
                        onFailure: showAjaxError
                    }
                    );
                   }             

// CALLBACK METHODE von ajax call                   
                   function parseTransferin(transport) {

                    var response = transport.responseText;
                    var jsonObj = eval("(" + response + ")");
                    //$(transferin).innerHTML = jsonObj.transferout;
                    //var children = $A(ui.transferin.childNodes);
                    //children.each (
                    // function(child){
                    //    alert (child.text);
                     //}
                    //);
                   
                   
                    ui.transferin.innerHTML = jsonObj.transferout;
                    processTransferin();
                    //alert ("transferout=" + jsonObj.transferout + "\nunid=" + jsonObj.unid);
                 }
// NOCH NE CALLBACK METHODE VON AJAX.                   
                 function showAjaxError(request) {
                    alert("error");
                 }
</script>
   
    </head>
    <body onload="processTransferin();">

<div id="transferin">Category°=°Muppets Show~~~RequestAuthorsMailRecipients°=°CN=Kermit der Frosch/O=ozzµCN=/OU=Miss Piggy/O=ozz~~~RequestAuthors°=°CN=Waldorf/O=ozz~~~RequestReaders°=°CN=Statler/O=OzzµCN=Fozzy Bär/O=ozzµCN=Gonzo/O=ozz</div>


<hr />
<div>category:<span id="Category"/></div>
<div>requestauthormailrecipients:<span id="RequestAuthorsMailRecipients"/></div>
<div>requestauthors:<span id="RequestAuthors"></span></div>
<div>requestreaders:<span id="RequestReaders"></span></div>
    <form>
        <input name="buttonChangeCat" type="button" value="buttonChangeCat" onclick="changeCat()"></button>
       
      <!--  <input name="buttonChangeCat" type="button" value="buttonChangeCat" onclick="processTransferin()"/></button>-->
     
       
   
    </form>
   
    </body>
</html>

--- Ende Code ---

SERVLET:

--- Code: ---package ajax;

import java.io.*;
import java.net.*;

import javax.servlet.*;
import javax.servlet.http.*;

/**
 *
 * @author ajanssen
 * @version
 */
public class AjaxServlet extends HttpServlet {
   
    /** Processes requests for both HTTP <code>GET</code> and <code>POST</code> methods.
     * @param request servlet request
     * @param response servlet response
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        response.setContentType("text/javascript;charset=UTF-8");
        PrintWriter out = response.getWriter();
        System.out.println("servlet run");
        out.println("{");
        out.println("transferout:\"Category°=°Presidentes~~~RequestAuthorsMailRecipients°=°CN=Michelle Bachelet/O=chileµCN=Ricardo Lagos/O=chile~~~RequestAuthors°=°CN=Pedro I/O=Brasil~~~RequestReaders°=°CN=Alan Garcia/O=PeruµCN=Irrigoyen/O=ArgentinienµCN=de Lozada/O=Bolivien\",");
        out.println("unid:111222333");
        out.println("}");
       
        out.close();
    }
   
 
   
    /** Returns a short description of the servlet.
     */
    public String getServletInfo() {
        return "Short description";
    }
    // </editor-fold>
}

--- Ende Code ---

Der Effekt ist jedenfalls, dass man auch bei Abhängigkeiten mehrerer Felder von einem "Transferfeld" (typisches Domino Idiom, ihr kennt es alle) Ajax angewendet werden kann.

flaite:
Also nochmal kurz zum architektürlichen.
Das Notes Dokument wird NICHT im Editmode geöffnet sondern im READ Mode (?openDocument, nicht ?editDocument).
Der Anwender kann in einer select-Box die Kategorie wählen.
Diese Auswahl wird asynchron per ajax an einen Agenten Domino Server gesandt.
DEr Agent schreibt im Backend in das Notes-Dokument (im Browser ist es ja im Read Mode) und speichert.
Ajax fängt die Rückgabe des Agenten auf und ändert dann die entsprechenden Felder. Von der Änderungen der Kategorie waren mehrere abhängige Felder betroffen.

Das ist grandios an dem eigentlichen Web Modell von Domino vorbeiprogrammiert, aber für Manageable Ajax in Domino (TM) macht es Sinn.   

flaite:
Nun denn.
Ajax ist zwar eigentlich tendentiell eine Clienttechnologie, aber es redet eben auch mit dem Server.
Zwar wird der Server NICHT veranlaßt eine ganz neue Seite zu liefern, aber Informationen vom Server können mit Ajax abgefragt werden. Diese Informationen vom Server werden dann in die Webseite "reingemischt".

Oben ist diese Serverseitige Komponente als Servlet implementiert.
Wie kann ich das als Notes Agenten programmieren?

Erste konkrete Frage: Wie soll der Agent überhaupt angesprochen werden?
Antwort: so:

--- Code: ---var request = new Ajax.Request(
                        "AjaxServlet",
                    {
                        method: 'get',
                        parameters: "cat=" + newCat,
                        onComplete: parseTransferin,
                        onFailure: showAjaxError
                    }
                    );

--- Ende Code ---
1. "AjaxServlet" ist die URL  zu der Resource.
In Domino  sollte das so aussehen:
"/<dbPath>/<agentName>?openAgent"
Wie geht der Path?
Das ist einfach eine JavaScript Funktion im HtmlHead Feld der Maske:

--- Code: ---"<script>" + @NewLine +
"function getBaseURL() {" + @NewLine +
"return \"/" + @WebDbName + "\";" + @NewLine +
"}" + @NewLine +
"</script>" + @NewLine +
@NewLine +

--- Ende Code ---


2. method: ist die http-Methode. Ich glaub bei Notes-Agenten müsste es 'post' heissen.
3. parameters: sind die url parameters. Das kann so bleiben. Wie das im Agenten ausgelesen wird, kommt später.
4. onComplete: ist die Methode, die im javaScript aufgerufen wird, wenn der Server etwas mit HTTP-State 2xx zurückliefert (wenn der Agent also erfolgreich etwas zurückliefert). Das kann so bleiben.
5. Auch onFailure ist rein JavaScript intern, kann so bleiben.

Der Ajax Aufruf hat sich also gegen das höchst wissenschaftliche Servlet kaum geändert:

--- Code: ---var baseURL = getBaseURL();
var request = new Ajax.Request(
                        baseURL + "/AjaxController?openAgent,
                    {
                        method: 'post',
                        parameters: "cat=" + newCat,
                        onComplete: parseTransferin,
                        onFailure: showAjaxError
                    }
                    );

--- Ende Code ---

Der Agent heisst AjaxController. Den mach ich als nächstes.




Navigation

[0] Themen-Index

[#] Nächste Seite

Zur normalen Ansicht wechseln