JSAN module autoloader for "add-ons"
authorJason Etheridge <jason@esilibrary.com>
Fri, 21 Dec 2012 05:19:37 +0000 (00:19 -0500)
committerMike Rylander <mrylander@gmail.com>
Thu, 12 Sep 2013 17:50:39 +0000 (13:50 -0400)
This adds a "Server Add-ons" menu entry under Admin -> Workstation
Administration in the staff client.  Choosing this allows you to edit or set a
list of identifiers that correspond to JSAN-style modules found in
/server/addons/, and is meant mainly for activating 3rd party modules within the
staff client on a per-workstation basis.  You need the
ADMIN_SERVER_ADDON_FOR_WORKSTATION permission to use it.

Configuration options for activated add-ons may also show up in this interface.

More technical details:

This Server Add-ons list is stored as a JSON array in the Mozilla preference
'oils.addon.autoload.list'.

We also add an addon.autoloader module that gets called in most XUL interfaces
via the util_overlay file.  This autoloader will loop through the modules
specified in 'oils.addon.autoload.list' and instantiate each, storing a
reference to each of the newly created objects in a data structure in
window.oils_autoloaded.

So, as an example, let's say there is a module: /xul/server/addon/uber_scan.js
And we have the identifier 'uber_scan' in our Server Add-ons list.  Then, on
every page load of a XUL interface using the stock util overlay, we will
effectively call:

    new addon.uber_scan();

Causing the code in the module to execute.  It is up to the module to determine
whether it needs to do anything within a given interface.

Signed-off-by: Jason Etheridge <jason@esilibrary.com>
Conflicts [permission numbering]:
Open-ILS/src/sql/Pg/950.data.seed-values.sql

12 files changed:
Open-ILS/src/sql/Pg/950.data.seed-values.sql
Open-ILS/src/sql/Pg/upgrade/XXXX.data.server_addon_perms.sql [new file with mode: 0644]
Open-ILS/web/opac/locale/en-US/lang.dtd
Open-ILS/xul/staff_client/chrome/content/OpenILS/global_util.js
Open-ILS/xul/staff_client/chrome/content/main/constants.js
Open-ILS/xul/staff_client/chrome/content/main/menu.js
Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
Open-ILS/xul/staff_client/server/addon/addons.js [new file with mode: 0644]
Open-ILS/xul/staff_client/server/addon/addons.xul [new file with mode: 0644]
Open-ILS/xul/staff_client/server/addon/autoloader.js [new file with mode: 0644]
Open-ILS/xul/staff_client/server/locale/en-US/addon/addons.properties [new file with mode: 0644]
docs/RELEASE_NOTES_NEXT/xul_server_addons.txt [new file with mode: 0644]

index 8242492..1ceb535 100644 (file)
@@ -1607,7 +1607,9 @@ INSERT INTO permission.perm_list ( id, code, description ) VALUES
         'Allows the user to check-in long-overdue items, prompting ' ||
             'long-overdue check-in processing', 'ppl', 'code')), 
  ( 550, 'SET_CIRC_LONG_OVERDUE', oils_i18n_gettext(550,
-        'Allows the user to mark a circulation as long-overdue', 'ppl', 'code'))
+        'Allows the user to mark a circulation as long-overdue', 'ppl', 'code')),
+ ( 551, 'ADMIN_SERVER_ADDON_FOR_WORKSTATION', oils_i18n_gettext( 551,
+        'Allows a user to specify which Server Add-ons get invoked at the current workstation', 'ppl', 'description'))
 ;
 
 
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.server_addon_perms.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.server_addon_perms.sql
new file mode 100644 (file)
index 0000000..efbd6a4
--- /dev/null
@@ -0,0 +1,17 @@
+BEGIN;
+
+SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+INSERT INTO permission.perm_list ( id, code, description ) VALUES (
+    551,
+    'ADMIN_SERVER_ADDON_FOR_WORKSTATION',
+    oils_i18n_gettext(
+        551,
+        'Allows a user to specify which Server Add-ons get invoked at the current workstation',
+        'ppl',
+        'description'
+    )
+);
+
+COMMIT;
+
index 2249624..8a653fc 100644 (file)
 <!ENTITY staff.main.menu.admin.client.hotkeys.clearworkstation.label "Clear Workstation Default">
 <!ENTITY staff.main.menu.admin.client.hotkeys.clearworkstation.accesskey "">
 <!ENTITY staff.main.menu.admin.client.search_prefs.label "Set Search Preferences">
+<!ENTITY staff.main.menu.admin.client.server_addon_ws_configure.label "Server Add-ons">
 <!ENTITY staff.main.menu.admin.client.toolbars "Toolbars">
 <!ENTITY staff.main.menu.admin.client.toolbars.current "Current">
 <!ENTITY staff.main.menu.admin.client.toolbars.config.label "Configure Toolbars">
index fabe682..d1be186 100644 (file)
             alert('Error in global_utils.js, widget_prompt(): ' + E);
         }
     }
+
+
+    window.addEventListener(
+        'load',
+        function(ev) {
+            try {
+                if (window.oils_autoloaded) { return; }
+                JSAN.use('addon.autoloader');
+                window.oils_autoloaded = new addon.autoloader();
+            } catch(E) {
+                dump('Error in global_util.js with addon.autoloader: ' + E + '\n');
+            }
+        },
+        false
+    );
index 80748ca..95fa50f 100644 (file)
@@ -517,5 +517,6 @@ var urls = {
     'SERIAL_PRINT_ROUTING_LIST_USERS' : 'oils://remote/eg/serial/print_routing_list_users',
     'XUL_SERIAL_BATCH_RECEIVE': 'oils://remote/xul/server/serial/batch_receive.xul',
     'EG_TRIGGER_EVENTS' : 'oils://remote/eg/actor/user/event_log',
-    'XUL_SEARCH_PREFS' : 'chrome://open_ils_staff_client/content/main/search_prefs.xul'
+    'XUL_SEARCH_PREFS' : 'chrome://open_ils_staff_client/content/main/search_prefs.xul',
+    'XUL_SERVER_ADDONS' : 'oils://remote/xul/server/addon/addons.xul'
 }
index c461602..c98b224 100644 (file)
@@ -1747,6 +1747,16 @@ main.menu.prototype = {
                     }
                 }
             ],
+            'cmd_server_addon_ws_configure' : [
+                ['oncommand'],
+                function() {
+                    try {
+                        obj.set_tab(obj.url_prefix('XUL_SERVER_ADDONS'),{'browser' : false});
+                    } catch(E) {
+                        alert(E);
+                    }
+                }
+            ]
         };
 
         JSAN.use('util.controller');
index 647ad70..2fba991 100644 (file)
              />
     <command id="cmd_copy_editor_copy_location_first_toggle" />
     <command id="cmd_search_prefs" />
+    <command id="cmd_server_addon_ws_configure"
+             perm="ADMIN_SERVER_ADDON_FOR_WORKSTATION"
+            />
 </commandset>
 
 <!-- The File menu on the main menu -->
                 <menuitem label="&staff.main.menu.admin.template_edit.label;" accesskey="&staff.main.menu.admin.template_edit.accesskey;" command="cmd_print_list_template_edit"/>
                 <menuitem label="&staff.server.admin.index.fonts_and_sounds;" command="cmd_local_admin_fonts_and_sounds"/>
                 <menuitem label="&staff.main.menu.admin.client.search_prefs.label;" command="cmd_search_prefs"/>
+                <menuitem label="&staff.main.menu.admin.client.server_addon_ws_configure.label;" command="cmd_server_addon_ws_configure"/>
                 <menuitem type="checkbox" label="&staff.main.menu.admin.client.copy_editor.copy_location.label;" command="cmd_copy_editor_copy_location_first_toggle"/>
                 <menu id="main.menu.admin.client.hotkeys" label="&staff.main.menu.admin.client.hotkeys;">
                     <menupopup id="main.menu.admin.client.hotkeys.popup">
diff --git a/Open-ILS/xul/staff_client/server/addon/addons.js b/Open-ILS/xul/staff_client/server/addon/addons.js
new file mode 100644 (file)
index 0000000..3824c0f
--- /dev/null
@@ -0,0 +1,83 @@
+var error;
+var g = { 'addons_ui' : true };
+var prefs;
+
+function my_init() {
+    try {
+        if (typeof JSAN == 'undefined') {
+            throw( "The JSAN library object is missing."); }
+        JSAN.errorLevel = "die"; // none, warn, or die
+        JSAN.addRepository('/xul/server/');
+        JSAN.use('util.error'); error = new util.error();
+        error.sdump('D_TRACE','my_init() for addon/addon.xul');
+
+        if (typeof window.xulG == 'object'
+            && typeof window.xulG.set_tab_name == 'function') {
+            try {
+                window.xulG.set_tab_name(
+                    $('addonStrings').getString('addons.tab.label')
+                );
+            } catch(E) {
+                alert(E);
+            }
+        }
+
+        const Cc = Components.classes;
+        const Ci = Components.interfaces;
+        const prefs_Cc = '@mozilla.org/preferences-service;1';
+        prefs = Cc[prefs_Cc].getService(Ci['nsIPrefBranch']);
+
+        var addons = JSON2js(
+            pref.prefHasUserValue('oils.addon.autoload.list')
+            ? pref.getCharPref('oils.addon.autoload.list')
+            : '[]'
+        );
+
+        $('addonlist_tb').value = addons.join("\n");
+
+        $('addonlist_desc').textContent = $('addonStrings').getString(
+            'addons.list.desc');
+        // Why messagecat instead of lang.dtd here? Mostly as an example for add-ons
+        // that won't have access to lang.dtd
+
+        $('addonlist_caption').setAttribute('label',$('addonStrings').getString(
+            'addons.list.caption'));
+
+        $('addonlist_save_btn').setAttribute(
+            'label', $('addonStrings').getString(
+                'addons.list.update_btn.label'));
+
+        $('addonlist_save_btn').setAttribute(
+            'accesskey', $('addonStrings').getString(
+                'addons.list.update_btn.accesskey'));
+
+        $('addonpref_caption').setAttribute('label',$('addonStrings').getString(
+            'addons.pref.caption'));
+
+    } catch(E) {
+        alert('Error in addons.js, my_init(): ' + E);
+    }
+}
+
+function update() {
+    try {
+        JSAN.use('util.functional');
+        var addon_string = $('addonlist_tb').value.replace(' ','','g');
+        var addons = util.functional.filter_list(
+            addon_string.split("\n"),
+            function(s) {
+                return s != ''; // filtering out empty lines
+            }
+        );
+
+        pref.setCharPref(
+            'oils.addon.autoload.list',
+            js2JSON(addons)
+        );
+
+        location.href = location.href;
+
+    } catch(E) {
+        alert('Error in addons.js, update(): ' + E);
+    }
+}
diff --git a/Open-ILS/xul/staff_client/server/addon/addons.xul b/Open-ILS/xul/staff_client/server/addon/addons.xul
new file mode 100644 (file)
index 0000000..8e8e5ca
--- /dev/null
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<!-- Application: Evergreen Staff Client -->
+<!-- Screen: Example Template for remote xul -->
+
+<!-- /////////////////////////////////////////////////////////////////////// -->
+<!-- STYLESHEETS -->
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="/xul/server/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="/xul/server/skin/addon/addons.css" type="text/css"?>
+
+<!-- /////////////////////////////////////////////////////////////////////// -->
+<!-- LOCALIZATION -->
+<!DOCTYPE window PUBLIC "" ""[
+    <!--#include virtual="/opac/locale/${locale}/lang.dtd"-->
+]>
+
+<!-- /////////////////////////////////////////////////////////////////////// -->
+<!-- OVERLAYS -->
+<?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
+
+<window id="addon_win"
+    onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
+    xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+    <!-- /////////////////////////////////////////////////////////////////// -->
+    <!-- BEHAVIOR -->
+    <script type="text/javascript">
+        var myPackageDir = 'open_ils_staff_client'; var IAMXUL = true;
+    </script>
+    <scripts id="openils_util_scripts"/>
+
+    <script type="text/javascript" src="/xul/server/main/JSAN.js"/>
+    <script type="text/javascript" src="addons.js"/>
+
+    <messagecatalog id="addonStrings"
+        src="/xul/server/locale/<!--#echo var='locale'-->/addon/addons.properties" />
+
+    <vbox id="main" flex="1">
+        <groupbox id="addonlist_groupbox" flex="1">
+            <caption id="addonlist_caption" />
+            <description id="addonlist_desc"/>
+            <textbox id="addonlist_tb" multiline="true" flex="1"/>
+            <button id="addonlist_save_btn" oncommand="update()"/>
+        </groupbox>
+        <groupbox id="addonpref_groupbox" flex="1">
+            <caption id="addonpref_caption" />
+            <tabbox id="addonpref_tabbox" flex="1">
+                <tabs id="addonpref_tabs" />
+                <tabpanels id="addonpref_tabpanels" flex="1" />
+            </tabbox>
+        </groupbox>
+    </vbox>
+
+</window>
+
diff --git a/Open-ILS/xul/staff_client/server/addon/autoloader.js b/Open-ILS/xul/staff_client/server/addon/autoloader.js
new file mode 100644 (file)
index 0000000..1cc8f80
--- /dev/null
@@ -0,0 +1,78 @@
+dump('entering addon/autoloader.js\n');
+// vim:noet:sw=4:ts=4:
+
+/*
+    Usage example:
+
+    JSAN.use('addon.autoloader');
+    var autoloader = new addon.autoloader({'pref':'oils.addon.autoload.list'});
+    autoloader.list();
+    autoloader.objects();
+*/
+
+if (typeof addon == 'undefined') addon = {};
+addon.autoloader = function (params) {
+    try {
+        dump('addon: autoloader() constructor at ' + location.href + '\n');
+        if (typeof params == 'undefined') {
+            params = { 'pref' : 'oils.addon.autoload.list' };
+        }
+
+        const Cc = Components.classes;
+        const Ci = Components.interfaces;
+        const prefs_Cc = '@mozilla.org/preferences-service;1';
+        this.prefs = Cc[prefs_Cc].getService(Ci['nsIPrefBranch']);
+
+        this._list = this.list(params);
+
+        this._hash = this.load( this._list, params );
+
+        return this;
+
+    } catch(E) {
+        dump('addon: Error in autoloader(): ' + E + '\n');
+    }
+}
+
+addon.autoloader.prototype = {
+    'list' : function(params) {
+        var list = [];
+        if (typeof params == 'undefined') {
+            list = this._list;
+        }
+        if (params.pref) {
+            if (this.prefs.prefHasUserValue(params.pref)) {
+                list = list.concat(
+                    JSON2js(
+                        this.prefs.getCharPref(
+                            params.pref
+                        )
+                    )
+                );
+            }
+        }
+        if (params.list) {
+            list = list.concat( params.list );
+        }
+        return list;
+    },
+    'objects' : function() {
+        return this._hash;
+    },
+    'load' : function(list,params) {
+        dump('addon: autloader load()\n');
+        var objs = {};
+        for (var i = 0; i < list.length; i++) {
+            try {
+                dump('addon: autloader load() -> ' + list[i] + '\n');
+                JSAN.use('addon.'+list[i]);
+                objs[list[i]] = new addon[list[i]](params);
+            } catch(E) {
+                dump('addon: autloader load() -> ' + list[i] + ' error: ' + E + '\n');
+                objs[list[i]] = function(e){return e;}(E);
+            }
+        }
+        return objs;
+    }
+}
+
diff --git a/Open-ILS/xul/staff_client/server/locale/en-US/addon/addons.properties b/Open-ILS/xul/staff_client/server/locale/en-US/addon/addons.properties
new file mode 100644 (file)
index 0000000..35d3e71
--- /dev/null
@@ -0,0 +1,6 @@
+addons.tab.label=Server Add-ons
+addons.list.caption=Active Server Add-Ons
+addons.list.desc=Specify one add-on identifier per line
+addons.list.update_btn.label=Update Active Add-Ons
+addons.list.update_btn.accesskey=U
+addons.pref.caption=Add-on Preferences
diff --git a/docs/RELEASE_NOTES_NEXT/xul_server_addons.txt b/docs/RELEASE_NOTES_NEXT/xul_server_addons.txt
new file mode 100644 (file)
index 0000000..dd303ed
--- /dev/null
@@ -0,0 +1,12 @@
+Upgrade Notes
+-------------
+Server Add-ons
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+This adds a "Server Add-ons" menu entry under Admin -> Workstation
+Administration in the staff client.  Choosing this allows you to edit or set a
+list of identifiers that correspond to JSAN-style modules found in
+/server/addons/, and is meant mainly for activating 3rd party modules within the
+staff client on a per-workstation basis.  You need the
+ADMIN_SERVER_ADDON_FOR_WORKSTATION permission to use it.
+
+Configuration options for activated add-ons may also show up in this interface.