iNotes started using brand-new in-house AJAX framework, starting 8.0.1 Lite mode and 8.5 Full mode. It's called iNotes Lite framework. As it started with Lite mode, it's built for speed. It's designed to provide a way to componentize iNotes UI with a tiny framework, as having big framework will cause large JavaScript code to be downloaded. The story around iNotes Lite mode is well described in:
This article shows how iNotes Lite framework works, explaining the following:
iNotes shortens most of function names and variable names to two or three alphabets, to keep the download size small. Such shortened names appear a lot in this article, too. To see their more meaningful names in forms template (Forms85.nsf for 8.5), go to Shared Resources - Files in Domino Designer and open
.
- The specification of iNotes Lite framework, and/or how to write applications shown in this article, may be changed without notice to improve iNotes product.
iNotes Lite framework allows UI and JavaScript code to be in different <frame>s in <frameset>. Those different <frames> can be accessed via
, which works as a container for application-specific data, which allows UI code to be separated from application code. You'll see that
is used extensively in iNotes Lite framework. To access different <frames>, you can use:
UI built on top of iNotes Lite framework is called iNotes Lite widget. The idea around iNotes Lite widget is turning an HTML element into a UI. The following code is the simplest iNotes Lite widget:
is the function to initialize widgets.
goes through all HTML elements under its first argument and search for HTML elements with
attribute.
attribute, to the constructor. Such HTML element is called iNotes Lite widget's "root element".
class can start realizing the UI.
to the element. In such case,
in second argument initializes such widget. It's useful to defer initializing the widget - In fact, iNotes calls
for the entire body during the initialization.
Loading code for iNotes Lite widget can be deferred until it is actually needed for the first time, which we call "lazy loading". To lazy load iNotes Lite widget code, first add an entry to
, which is the table associating iNotes Lite widget class name with list of "code chunk symbols". We call it "lazy loading table". Specifying lazy loading table is like:
object keeps track of modules that have already been loaded. Also, you can specify multiple code chunks for an entry in lazy loading table. iNotes Lite framework automatically determines the list of code chunks that have not been loaded, and load them in a single HTTP request, using
Notes form, depending on the browser in use. The reason making it a single HTTP request is that having big numbers of HTTP requests hugely hits client response time.
- iNotes Lite framework automatically adds "s_Get" prefix and "Script" suffix to code chunk symbols when it loads
Notes form. It's under change targeted to 8.5.1 release, making the prefix "s_" and no suffix, in order to shorten the URL.
, as we have found that nested <table> has significant browser performance hit. Some AJAX frameworks do JavaScript-based calculation of layout as an alternative of nested <table>, but iNotes Lite codebase avoids it, too, to reduce the size of such JavaScript code, as well as the browser performance hit associated with executing such JavaScript code.
iNotes Lite codebase extensively uses CSS for its layout purpose. There are some key CSS classes that are used, which are:
|
|
|
|
|
The element takes up the whole region of the padding of containing element. The next element comes under the element. Useful when you want to make the element honor the padding of containing element, or you don't want to stack up multiple elements in one region.
|
|
|
The element takes up the whole region of containing element. The next element comes at the same position as the element. Useful when you want to stack up multiple elements in one region.
|
|
|
The element takes up the whole width of containing element, and placed at the top. Useful when you want to place an element at the top of containing element.
|
|
|
The element takes up the whole height of containing element, and placed at the right. Useful when you want to place an element at the right of containing element.
|
|
|
The element takes up the whole width of containing element, and placed at the bottom. Useful when you want to place an element at the bottom of
containing element.
|
|
|
The element takes up the whole height of containing element, and placed at the left. Useful when you want to place an element at the left of containing element.
|
An example using above classes is below, where blue area takes 5em of the top of body, and red area takes the rest:
1: <div class="s-stack" style="padding-top:5em;">
2: <div class="s-basicpanel"
3: style="border:solid red 1px;background-color:#FF8080">
4: </div>
5: </div>
6: <div class="s-toppanel" style="height:5em;border:solid blue 1px;background-color:#8080FF">
7: </div>

Another example where blue area takes 5em of the top, magenta area takes 5em of the left, and green area takes the rest is:
1: <div class="s-stack" style="padding-top:5em;">
2: <div class="s-basicpanel" style="border:solid yellow 1px;">
3: <div class="s-stack" style="padding-left:5em;">
4: <div class="s-basicpanel"
5: style="border:solid green 1px;background-color:#80FF80">
6: </div>
7: </div>
8: <div class="s-leftpanel"
9: style="width:5em;border:solid magenta 1px;background-color:#FF80FF">
10: </div>
11: </div>
12: </div>
13: <div class="s-toppanel"
14: style="height:5em;border:solid blue 1px;background-color:#8080FF">
15: </div>

You'll find that element that comes beforehand does not come to the top. It's because s-stack CSS class uses position:absolute style and it's a tendency of iNotes' layout.
Common property
iNotes Lite framework provides a way for two or more widgets to cooperate, which is called "common property". Common property is a named value that any widgets can attach (subscribe) to. When a change happens to a common property, EYK() method of the widget attached to it is called asynchronously. To obtain the common property named p-color and its value:
1: var oProperty = EPx.get('p-color');
2: var vValue = oProperty.BoB;
To set a new value to a common property:
1: var oProperty = EPx.get('p-color');
2: oProperty.BRW('#808080');
Using common property, you can create a widget something like below. You'll find that onchange attribute of <select> is written without knowing how com_ibm_dwa_ui_colorBox widget is implemented:
1: // ------------------
2: // Box changing color
3: // ------------------
4: function com_ibm_dwa_ui_colorBox(sId){
5: var oElem = AAA.EcK.getElementById(this.sId = sId);
6: var oProperty = EPx.get(oElem.getAttribute('com_ibm_dwa_misc_observes_color'));
7: oProperty.attach(this);
8: this.EYK(oProperty); // Pick up existing value
9: }
10:
11: // =====================================
12: // Observe the color
13: // Inputs:
14: // oProperty - The property to observe
15: // =====================================
16: com_ibm_dwa_ui_colorBox.prototype.EYK = function com_ibm_dwa_ui_colorBox_observe(oProperty){
17: // ERD() checks if oProperty represents the latest value - As this function is called asynchronously
18: if (oProperty.ERD && !oProperty.ERD())
19: return;
20: var oElem = AAA.EcK.getElementById(this.sId);
21: oElem.style.backgroundColor = oProperty.BoB;
22: };
And:
1: <div class="s-stack" style="padding-top:5em;">
2: <div class="s-basicpanel" style="border:solid black 1px;"
3: com_ibm_dwa_ui_widget_class="com_ibm_dwa_ui_colorBox" com_ibm_dwa_misc_observes_color="p-e-colorselect-currentselected">
4: </div>
5: </div>
6: <div class="s-toppanel" style="height:5em;">
7: <form>
8: <select name="e-colorselect" class="s-label-light" onchange="EPx.get('p-e-colorselect-currentselected').BRW(this.value);">
9: <option value="#ffffff">Select color</option>
10: <option value="#ffc0c0">Red</option>
11: <option value="#c0ffc0">Green</option>
12: <option value="#c0c0ff">Blue</option>
13: </select>
14: </form>
15: </div>
Example: Document property viewer
Note - We recommend creating a backup of original forms template before making any changes.
Let's see how iNotes Lite framework can be used to create to customize iNotes, by creating "document property viewer" - A UI to show what basic fields are used when a document is opened, and the actual values in such fields. iNotes have a small space at the bottom of outline area and this example uses that space. Preview pane in iNotes uses a common property to know last selected document and this example exploits it. In this section, we explain the following with the example:
- Write markup for new UI, containing exiting and new iNotes Lite widgets, and put it into iNotes markup.
- Implement a new iNotes Lite widget to react to events showing/hiding an area next to splitting UI, and to initialize a widget in such area.
- Implement a new iNotes Lite widget to show document properties. Write markups and definitions so that this widget is lazy loaded.
Defining markup for the new UI
First, we create a subform named CustomLeftSidebar_SkinComponent_Lite, with the following content:
1: <div id="e-splitter-leftpanel-sidebar" class="s-bottompanel" style="cursor:row-resize;height:4px;"
2: com_ibm_dwa_ui_widget_class="Ect"
3: com_ibm_dwa_ui_splitter_orient="bottom" com_ibm_dwa_ui_splitter_adjacentElements="e-leftpanel-sidebar-pane"
4: com_ibm_dwa_misc_observes_position="p-e-splitter-leftpanel-sidebar-position"
5: com_ibm_dwa_ui_widget_delayed="true" com_ibm_dwa_ui_draggable="true">
6: <div id="e-grippy-leftpanel-sidebar" class="s-basicpanel"
7: com_ibm_dwa_ui_widget_class="EeR"
8: com_ibm_dwa_ui_grippy_orient="bottom" com_ibm_dwa_ui_grippy_adjacentElements="e-leftpanel-sidebar-pane"
9: com_ibm_dwa_ui_grippy_splitter="e-splitter-leftpanel-sidebar"
10: com_ibm_dwa_ui_grippy_helptext="Click to open or collapse document property viewer"
11: com_ibm_dwa_misc_observes_position="p-e-splitter-leftpanel-sidebar-position">
12: </div>
13: </div>
14: <div id="e-leftpanel-sidebar-pane" class="s-bottompanel s-nodisplay" style="height:10em;"
15: com_ibm_dwa_ui_widget_class="com_ibm_dwa_ui_leftpanelSidebarPane"
16: com_ibm_dwa_misc_observes_position="p-e-splitter-leftpanel-sidebar-position">
17: <div id="e-propertyviewer"
18: class="s-basicpanel s-panel-border s-label-light" style="background-color:white;overflow:auto;"
19: com_ibm_dwa_ui_widget_class="com_ibm_dwa_ui_documentPropertyViewer" com_ibm_dwa_ui_widget_delayed="true"
20: com_ibm_dwa_misc_observes_selectedDocument="p-selecteddocument-mail">
21: </div>
22: </div>
To explain some things about iNotes Lite widgets used in above subform:
- Ect is what we call "splitter widget", which lets user slide a separator. EeR is what we call "grippy widget", which lets user show/hide an area next to a separator. The combination of those two is used for the separator area of outline, side bar and preview pane.
- com_ibm_dwa_misc_observes_position attribute in e-splitter-leftpanel-sidebar and e-grippy-leftpanel-sidebar tells what common property Ect widget and EeR widget should look at. Such common property is automatically updated when Ect widget detects sliding events, or EeR widget detects toggle events.
- com_ibm_dwa_ui_splitter_adjacentElements attribute in Ect widget and com_ibm_dwa_ui_grippy_adjacentElements attribute in EeR widget specifies what element should be shown/hidden upon the events.
com_ibm_dwa_ui_leftpanelSidebarPane and com_ibm_dwa_ui_documentPropertyViewer are new iNotes Lite widgets that we are about to write:
- com_ibm_dwa_ui_leftpanelSidebarPane controls element size and initiates com_ibm_dwa_ui_documentPropertyViewer when its area is first shown.
- com_ibm_dwa_ui_documentPropertyViewer is the widget to show document properties.
To put the new subform to the main markup defining iNotes' UI (we call it "skin"), go to Shared Resources - Files in Domino Designer and edit l_ShimmerSkin-h_ListFolder, l_ShimmerSkin_Gecko-h_ListFolder, and l_ShimmerSkin_Safari-h_ListFolder and change the following two places:
1: <div id="e-leftpanel" class="s-leftpanel">
2: <div class="s-basicpanel">
3: <div class="s-stack" style="padding:0.2em 4px 0em 0.2em;">
4: <div class="s-basicpanel">
5: <div id="e-leftpanel-container" class="s-stack" style="padding-top:30px;padding-bottom:5px;">
And:
1: <div id="e-leftpanel-title" class="s-toppanel">
2: <div class="s-basicpanel">
3: <InsertNotesSubForm name="ServerTitleAndMailQuota_SkinComponent_Lite">
4: </div>
5: </div>
6: </div>
7: </div>
8: <InsertNotesSubForm name="CustomLeftSidebar_SkinComponent_Lite">
9: </div>
com_ibm_dwa_ui_leftpanelSidebarPane iNotes Lite widget
Next, we implement com_ibm_dwa_ui_leftpanelSidebarPane iNotes Lite widget by writing the following code at the bottom of Custom_JS_Lite subform:
1: function com_ibm_dwa_ui_leftpanelSidebarPane(sId){
2: var oElem = AAA.EcK.getElementById(this.sId = sId);
3: var oProperty = EPx.get(oElem.getAttribute('com_ibm_dwa_misc_observes_position'));
4: oProperty.attach(this);
5: this.EYK(oProperty); // Pick up the first value
6: }
7:
8: com_ibm_dwa_ui_leftpanelSidebarPane.prototype.EYK = function com_ibm_dwa_ui_leftpanelSidebarPane_observe(oProperty){
9: // ERD() checks if oProperty represents the latest value - As this function is called asynchronously
10: if (typeof(oProperty.BoB) != 'number' || oProperty.ERD && !oProperty.ERD())
11: return;
12:
13: var EcK = AAA.EcK;
14: var oElem = EcK.getElementById(this.sId);
15: var nPos = oProperty.BoB;
16: var fVisible = nPos > 4;
17:
18: if (!this.fViewerInitialized && fVisible) {
19: EPl(EcK.getElementById('e-propertyviewer'), true);
20: this.fViewerInitialized = true;
21: }
22:
23: if (fVisible)
24: oElem.style.height = nPos + 'px';
25:
26: EJQ(oElem, 's-nodisplay', !fVisible); // Completely hide the element if nPos is too small
27: EcK.getElementById('e-splitter-leftpanel-sidebar').style.bottom = nPos + 'px';
28: EcK.getElementById('e-leftpanel-container').style.paddingBottom = nPos + 5 + 'px';
29: };
To explain things in above code:
- Given com_ibm_dwa_ui_leftpanelSidebarPane observes a common property used for splitter and grippy widgets, the value that comes to EYK() method (oProperty.BoB) is the position of the separator.
- When the separator comes to a position where user can see the adjacent content (nPos > 4) first time, the code calls EPl() to initialize com_ibm_dwa_ui_documentPropertyViewer widget defined in e-propertyviewer.
- EJQ() controls a specific CSS class should be there or not in an element. In the case of above code, it puts or removes s-nodisplay CSS class (equals to display:none style) depending on fVisible is true or false.
com_ibm_dwa_ui_documentPropertyViewer iNotes Lite widget
Next, we implement com_ibm_dwa_ui_documentPropertyViewer next. First, we edit l_StdPageOperations, l_StdPageOperaionts_Gecko and l_StdPageOperations_Safari forms to let the code lazy loaded. First, find </NotesDictionary> in the form and add the following before it:
1: <NotesVar name=s_GetPropertyViewerScript type=number initialvalue={0}>
And add the following at the end of the form:
1: <InsertNotesSubForm name=@{@If(s_GetPropertyViewerScript != 0;"Custom_PropertyViewer";"")}>
So, Custom_PropertyViewer is the subform where the code should be.
Let's add an lazy loading table entry to let iNotes Lite framework to automatically load Custom_PropertyViewer when com_ibm_dwa_ui_documentPropertyViewer is initialized. To do that, write the following code in Scene_PostLoad_Lite():
1: var asClass = (s_SceneName || '').split(':');
2: if (asClass[1] == 'EVy')
3: EKc.prototype.EQj['com_ibm_dwa_ui_documentPropertyViewer'] = ['PropertyViewer', 'Base', 'Std'];
Scene_PostLoad_Lite() is called every time a widget completes its initialization. The combination of the root element ID of the widget and the name of the widget comes in s_SceneName - So asClass[1] is the name of the widget being initialized. EVy is an widget that is initialized in the very beginning of iNotes' initialization, and the code exploits it. Code specified by "Base" and "Std" symbols are used in com_ibm_dwa_ui_documentPropertyViewer to load document property data.
Finally, the implementation of com_ibm_dwa_ui_documentPropertyViewer is below:
1: function com_ibm_dwa_ui_documentPropertyViewer(sId){
2: var oElem = AAA.EcK.getElementById(this.sId = sId);
3: var oProperty = EPx.get(oElem.getAttribute('com_ibm_dwa_misc_observes_selectedDocument'));
4: oProperty.attach(this);
5: this.EYK(oProperty);
6: }
7:
8: com_ibm_dwa_ui_documentPropertyViewer.prototype.EYK = function com_ibm_dwa_ui_documentPropertyViewer_observe(oProperty){
9: // ERD() checks if oProperty represents the latest value - As this function is called asynchronously
10: if (oProperty.ERD && !oProperty.ERD())
11: return;
12:
13: var oElem = AAA.EcK.getElementById(this.sId);
14: if (!oProperty.BoB) {
15: oElem.innerHTML
16: = 'Please click on a document in mail view to see document item values with basic types.<br>(Text, number, date and their list)';
17: return;
18: }
19:
20: var asValue = oProperty.BoB.split('|');
21: var oNote = new Ecw(asValue[1], asValue[0]);
22: var oListener = new EOc;
23:
24: oListener.DlJ = function com_ibm_dwa_ui_documentPropertyViewer_observe_onDatasetComplete(){
25: if (oProperty.ERD && !oProperty.ERD())
26: return;
27:
28: var oPsuedoDiv = AAA.EcK.createElement('div');
29: var asHtml = [];
30:
31: oNote.DXX.sort(function(oSrc, oDst){ return oSrc['@name'] > oDst['@name'] ? 1 : oSrc['@name'] < oDst['@name'] ? -1 : 0; });
32:
33: for (var i = 0; oNote.DXX && i < oNote.DXX.length; i++) {
34: var oValue = (new EKo).DvT(oNote.DXX[i]);
35: EVF(oPsuedoDiv, oValue);
36: asHtml[i] = '<tr>'
37: + '<td class="s-label-light s-panel-border" style="border-width:1px 0px 0px 0px;">'
38: + oNote.DXX[i]['@name'] + '<br>' + oValue.EFC() + '<br>' + (oPsuedoDiv.innerHTML || '(Null String)')
39: + '</td>'
40: + '</tr>';
41: }
42:
43: oElem.innerHTML = '<table border="1" cellpadding="0" cellspacing="0" style="width:100%;font-size:small;">'
44: + '<tbody>'
45: + '<tr>'
46: + '<td class="s-label-light" style="font-weight:bold;">Name<br>Type<br>Value</td>'
47: + '</tr>'
48: + asHtml.join('')
49: + '</tbody>'
50: + '</table>';
51: };
52:
53: oNote.load(oListener);
54: };
55:
56: EKc.prototype.FKd['propertyviewer'] = true;
To explain things in above code:
- The value coming into oProperty.BoB is a string separated by '|' - The first one is the view name, the second one is document UNID user selected.
- Ecw is a class to load document properties, which mainly used in document form code, such as mail read form. When server returns document properties upon the load() method, oListener.DlJ() is called.
- DXX property in Ecw is an array containing the list of document properties. (new EKo).DvT(oNote.DXX[i]) creates an object representing document property value. When JavaScript engine does automatic conversion from object to string, that object generates a string representing document property value. Also, EFC() method of such object returns the type of the value.
- EVF() sets a plain text (instead of HTML) to an element. Getting innerHTML property from the element afterwards allows us to convert plain text to HTML.
Finally - Once you start up your server with the edited version of forms template deployed, and log into iNotes, you'll see triangle at bottom left. If you click on that, you'll see the new UI. If you click on a document in mail view, you'll see the UI is updated with document properties, like below:
