[open-ils-commits] [GIT] Evergreen ILS branch master updated. 6ef8711978cb37e3d3b09ce5737daec551d8947d

Evergreen Git git at git.evergreen-ils.org
Wed Mar 28 15:53:47 EDT 2012


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "Evergreen ILS".

The branch, master has been updated
       via  6ef8711978cb37e3d3b09ce5737daec551d8947d (commit)
       via  251717941bae2a4606d968065d7aa241349bb0b5 (commit)
       via  6d5b4a8f818c6ebf50dd46c0d9fd0b0ca40dda5e (commit)
       via  5785841ead89c506102c236fd57d766b7dde259d (commit)
       via  cc05855a2b1562acd0816c6b0b41b23b83c90720 (commit)
       via  60ec86d28c32ac71c1092dd7491bdbc09a36e18c (commit)
       via  85480a7198ec50b0c32fd434c0d59feafa0a013d (commit)
       via  0e06c711fe06322a44f8a019d63027c92201463a (commit)
       via  4ec1d1489bc6978354dbb0cbd2c2478d4559f077 (commit)
       via  b06ba3b8a2ab96ba22d7896f50691156e50a9e15 (commit)
      from  29a1f4178f83fe57942d07afd33d62b2a8268c21 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 6ef8711978cb37e3d3b09ce5737daec551d8947d
Author: Thomas Berezansky <tsbere at mvlc.org>
Date:   Wed Mar 28 15:52:21 2012 -0400

    Stamping upgrade script for custom toolbars
    
    Signed-off-by: Thomas Berezansky <tsbere at mvlc.org>

diff --git a/Open-ILS/src/sql/Pg/002.schema.config.sql b/Open-ILS/src/sql/Pg/002.schema.config.sql
index acac661..52dbf17 100644
--- a/Open-ILS/src/sql/Pg/002.schema.config.sql
+++ b/Open-ILS/src/sql/Pg/002.schema.config.sql
@@ -86,7 +86,7 @@ CREATE TRIGGER no_overlapping_deps
     BEFORE INSERT OR UPDATE ON config.db_patch_dependencies
     FOR EACH ROW EXECUTE PROCEDURE evergreen.array_overlap_check ('deprecates');
 
-INSERT INTO config.upgrade_log (version, applied_to) VALUES ('0694', :eg_version); -- mrpeters/miker
+INSERT INTO config.upgrade_log (version, applied_to) VALUES ('0695', :eg_version); -- phasefx/tsbere
 
 CREATE TABLE config.bib_source (
 	id		SERIAL	PRIMARY KEY,
diff --git a/Open-ILS/src/sql/Pg/upgrade/0695.schema.custom_toolbars.sql b/Open-ILS/src/sql/Pg/upgrade/0695.schema.custom_toolbars.sql
new file mode 100644
index 0000000..4ebb8bb
--- /dev/null
+++ b/Open-ILS/src/sql/Pg/upgrade/0695.schema.custom_toolbars.sql
@@ -0,0 +1,68 @@
+-- Evergreen DB patch 0695.schema.custom_toolbars.sql
+--
+-- FIXME: insert description of change, if needed
+--
+BEGIN;
+
+
+-- check whether patch can be applied
+SELECT evergreen.upgrade_deps_block_check('0695', :eg_version);
+
+CREATE TABLE actor.toolbar (
+    id          BIGSERIAL   PRIMARY KEY,
+    ws          INT         REFERENCES actor.workstation (id) ON DELETE CASCADE,
+    org         INT         REFERENCES actor.org_unit (id) ON DELETE CASCADE,
+    usr         INT         REFERENCES actor.usr (id) ON DELETE CASCADE,
+    label       TEXT        NOT NULL,
+    layout      TEXT        NOT NULL,
+    CONSTRAINT only_one_type CHECK (
+        (ws IS NOT NULL AND COALESCE(org,usr) IS NULL) OR
+        (org IS NOT NULL AND COALESCE(ws,usr) IS NULL) OR
+        (usr IS NOT NULL AND COALESCE(org,ws) IS NULL)
+    ),
+    CONSTRAINT layout_must_be_json CHECK ( is_json(layout) )
+);
+CREATE UNIQUE INDEX label_once_per_ws ON actor.toolbar (ws, label) WHERE ws IS NOT NULL;
+CREATE UNIQUE INDEX label_once_per_org ON actor.toolbar (org, label) WHERE org IS NOT NULL;
+CREATE UNIQUE INDEX label_once_per_usr ON actor.toolbar (usr, label) WHERE usr IS NOT NULL;
+
+-- this one unrelated to toolbars but is a gap in the upgrade scripts
+INSERT INTO permission.perm_list ( id, code, description )
+    SELECT
+        522,
+        'IMPORT_AUTHORITY_MARC',
+        oils_i18n_gettext(
+            522,
+            'Allows a user to create new authority records',
+            'ppl',
+            'description'
+        )
+    WHERE NOT EXISTS (
+        SELECT 1
+        FROM permission.perm_list
+        WHERE
+            id = 522
+    );
+
+INSERT INTO permission.perm_list ( id, code, description ) VALUES (
+    523,
+    'ADMIN_TOOLBAR',
+    oils_i18n_gettext(
+        523,
+        'Allows a user to create, edit, and delete custom toolbars',
+        'ppl',
+        'description'
+    )
+);
+
+-- Don't want to assume stock perm groups in an upgrade script, but here for ease of testing
+-- INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable) SELECT pgt.id, perm.id, aout.depth, FALSE FROM permission.grp_tree pgt, permission.perm_list perm, actor.org_unit_type aout WHERE pgt.name = 'Staff' AND aout.name = 'Branch' AND perm.code = 'ADMIN_TOOLBAR';
+
+INSERT INTO actor.toolbar(org,label,layout) VALUES
+    ( 1, 'circ', '["circ_checkout","circ_checkin","toolbarseparator.1","search_opac","copy_status","toolbarseparator.2","patron_search","patron_register","toolbarspacer.3","hotkeys_toggle"]' ),
+    ( 1, 'cat', '["circ_checkin","toolbarseparator.1","search_opac","copy_status","toolbarseparator.2","create_marc","authority_manage","retrieve_last_record","toolbarspacer.3","hotkeys_toggle"]' );
+
+-- delete from permission.grp_perm_map where perm in (select id from permission.perm_list where code ~ 'TOOLBAR'); delete from permission.perm_list where code ~ 'TOOLBAR'; drop table actor.toolbar ;
+
+
+COMMIT;

commit 251717941bae2a4606d968065d7aa241349bb0b5
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Wed Mar 28 15:19:42 2012 -0400

    fix a Down action bug in Configure Toolbars
    
    where the list selection index could be set to an invalid value, which could
    cause errors for actions acting upon that index
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Thomas Berezansky <tsbere at mvlc.org>

diff --git a/Open-ILS/xul/staff_client/server/admin/toolbar.js b/Open-ILS/xul/staff_client/server/admin/toolbar.js
index 8e899be..8a0e17a 100644
--- a/Open-ILS/xul/staff_client/server/admin/toolbar.js
+++ b/Open-ILS/xul/staff_client/server/admin/toolbar.js
@@ -303,6 +303,7 @@ function populate_list2_list3(list3_idx) {
 
         if (list3_idx) {
             if (list3_idx < 0) { list3_idx = 0; }
+            if (list3_idx >= g.list3.node.view.rowCount) { list3_idx = g.list3.node.view.rowCount - 1; }
             g.list3.node.view.selection.select(list3_idx);
         }
 

commit 6d5b4a8f818c6ebf50dd46c0d9fd0b0ca40dda5e
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Wed Mar 28 11:37:12 2012 -0400

    fix editing of multiple toolbarseparators
    
    and toolbarspacers.  Also beef up the hardcoded toolbar to match the stock circ
    toolbar.
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Thomas Berezansky <tsbere at mvlc.org>

diff --git a/Open-ILS/src/sql/Pg/950.data.seed-values.sql b/Open-ILS/src/sql/Pg/950.data.seed-values.sql
index b9ebb7e..b6b88d4 100644
--- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql
+++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql
@@ -11512,5 +11512,5 @@ INSERT INTO action_trigger.environment (event_def, path)
     );
 
 INSERT INTO actor.toolbar(org,label,layout) VALUES
-    ( 1, 'circ', '["circ_checkout","circ_checkin","toolbarseparator","search_opac","copy_status","toolbarseparator","patron_search","patron_register","toolbarspacer","hotkeys_toggle"]' ),
-    ( 1, 'cat', '["circ_checkin","toolbarseparator","search_opac","copy_status","toolbarseparator","create_marc","authority_manage","retrieve_last_record","toolbarspacer","hotkeys_toggle"]' );
+    ( 1, 'circ', '["circ_checkout","circ_checkin","toolbarseparator.1","search_opac","copy_status","toolbarseparator.2","patron_search","patron_register","toolbarspacer.3","hotkeys_toggle"]' ),
+    ( 1, 'cat', '["circ_checkin","toolbarseparator.1","search_opac","copy_status","toolbarseparator.2","create_marc","authority_manage","retrieve_last_record","toolbarspacer.3","hotkeys_toggle"]' );
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.custom_toolbars.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.custom_toolbars.sql
index 386f052..63c7a61 100644
--- a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.custom_toolbars.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.custom_toolbars.sql
@@ -50,7 +50,7 @@ INSERT INTO permission.perm_list ( id, code, description ) VALUES (
 -- INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable) SELECT pgt.id, perm.id, aout.depth, FALSE FROM permission.grp_tree pgt, permission.perm_list perm, actor.org_unit_type aout WHERE pgt.name = 'Staff' AND aout.name = 'Branch' AND perm.code = 'ADMIN_TOOLBAR';
 
 INSERT INTO actor.toolbar(org,label,layout) VALUES
-    ( 1, 'circ', '["circ_checkout","circ_checkin","toolbarseparator","search_opac","copy_status","toolbarseparator","patron_search","patron_register","toolbarspacer","hotkeys_toggle"]' ),
-    ( 1, 'cat', '["circ_checkin","toolbarseparator","search_opac","copy_status","toolbarseparator","create_marc","authority_manage","retrieve_last_record","toolbarspacer","hotkeys_toggle"]' );
+    ( 1, 'circ', '["circ_checkout","circ_checkin","toolbarseparator.1","search_opac","copy_status","toolbarseparator.2","patron_search","patron_register","toolbarspacer.3","hotkeys_toggle"]' ),
+    ( 1, 'cat', '["circ_checkin","toolbarseparator.1","search_opac","copy_status","toolbarseparator.2","create_marc","authority_manage","retrieve_last_record","toolbarspacer.3","hotkeys_toggle"]' );
 
 -- delete from permission.grp_perm_map where perm in (select id from permission.perm_list where code ~ 'TOOLBAR'); delete from permission.perm_list where code ~ 'TOOLBAR'; drop table actor.toolbar ;
diff --git a/Open-ILS/xul/staff_client/chrome/content/main/menu.js b/Open-ILS/xul/staff_client/chrome/content/main/menu.js
index ac27aec..4d8b482 100644
--- a/Open-ILS/xul/staff_client/chrome/content/main/menu.js
+++ b/Open-ILS/xul/staff_client/chrome/content/main/menu.js
@@ -2605,7 +2605,7 @@ commands:
             if (!def) def = util.functional.find_list( this.data.list.atb, function(e) { return e.label == button_bar; } );
             if (!def) {
                 dump('Could not find layout for specified toolbar. Defaulting to a stock toolbar.\n');
-                layout = [ 'circ_checkin', 'toolbarseparator', 'toolbarspacer', 'hotkeys_toggle' ];
+                layout = ["circ_checkout","circ_checkin","toolbarseparator","search_opac","copy_status","toolbarseparator","patron_search","patron_register","toolbarspacer","hotkeys_toggle"];
             } else {
                 layout = JSON2js(def.layout());
             }
@@ -2634,26 +2634,23 @@ commands:
             // create new one
             for (var i = 0; i < layout.length; i++) {
                 var e = layout[i];
-                switch(e) {
-                    case 'toolbarseparator':
+                if (e.match('toolbarseparator')) {
                         toolbar.appendChild( document.createElement('toolbarseparator') );
-                    break;
-                    case 'toolbarspacer':
-                        var spacer = document.createElement('toolbarspacer');
-                        spacer.setAttribute('flex','1');
-                        toolbar.appendChild( spacer );
-                    break;
-                    default:
-                        var templates = $('palette').getElementsByAttribute('templateid',e);
-                        var template = templates.length > 0 ? templates[0] : null;
-                        if (template) {
-                            var clone = template.cloneNode(true);
-                            toolbar.appendChild( clone );
-                        } else {
-                            var label = document.createElement('label');
-                            label.setAttribute('value',e);
-                            toolbar.appendChild( label );
-                        }
+                } else if (e.match('toolbarspacer')) {
+                    var spacer = document.createElement('toolbarspacer');
+                    spacer.setAttribute('flex','1');
+                    toolbar.appendChild( spacer );
+                } else {
+                    var templates = $('palette').getElementsByAttribute('templateid',e);
+                    var template = templates.length > 0 ? templates[0] : null;
+                    if (template) {
+                        var clone = template.cloneNode(true);
+                        toolbar.appendChild( clone );
+                    } else {
+                        var label = document.createElement('label');
+                        label.setAttribute('value',e);
+                        toolbar.appendChild( label );
+                    }
                 }
             }
             toolbar.setAttribute('hidden','false');
diff --git a/Open-ILS/xul/staff_client/server/admin/toolbar.js b/Open-ILS/xul/staff_client/server/admin/toolbar.js
index 717a87a..8e899be 100644
--- a/Open-ILS/xul/staff_client/server/admin/toolbar.js
+++ b/Open-ILS/xul/staff_client/server/admin/toolbar.js
@@ -279,17 +279,13 @@ function populate_list2_list3(list3_idx) {
             var value = g.layout[i];
             var label;
 
-            switch(value) {
-                case 'toolbarseparator':
-                    label = $('adminStrings').getString('staff.admin.toolbar.toolbar_separator.list_entry');
-                break;
-                case 'toolbarspacer':
-                    label = $('adminStrings').getString('staff.admin.toolbar.toolbar_spacer.list_entry');
-                break;
-                default:
-                    label = g.data.toolbar_buttons[value];
-                    seen[value] = true;
-                break;
+            if (value.match('toolbarseparator')) {
+                label = $('adminStrings').getString('staff.admin.toolbar.toolbar_separator.list_entry');
+            } else if (value.match('toolbarspacer')) {
+                label = $('adminStrings').getString('staff.admin.toolbar.toolbar_spacer.list_entry');
+            } else {
+                label = g.data.toolbar_buttons[value];
+                seen[value] = true;
             }
 
             var rdata3 = g.list3.append({
@@ -406,6 +402,9 @@ function Add(ev) {
 
         for (var i = values_to_add.length - 1; i >= 0; i--) { // iterate backwards so that we add them forwards
             if (!values_to_add[i]) { continue; }
+            if (values_to_add[i].match('toolbarseparator') || values_to_add[i].match('toolbarspacer')) {
+                values_to_add[i] = values_to_add[i] + '.' + (new Date()).getTime();
+            }
             g.layout.splice(add_after_this_position,0,values_to_add[i]);
         }
 

commit 5785841ead89c506102c236fd57d766b7dde259d
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Tue Mar 20 16:52:13 2012 -0400

    reworked toolbars for dynamism
    
    uses the data from action.toolbar
    
    also a Toolbar Configuration UI
    
    also added more toolbar buttons, though they need icons
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Thomas Berezansky <tsbere at mvlc.org>

diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml
index 28f8ee3..484c1ed 100644
--- a/Open-ILS/examples/fm_IDL.xml
+++ b/Open-ILS/examples/fm_IDL.xml
@@ -2795,7 +2795,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 		</links>
 		<permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
 			<actions>
-				<retrieve permission="ADMIN_TOOLBAR VIEW_TOOLBAR" context_field="org">
+				<retrieve permission="ADMIN_TOOLBAR STAFF_LOGIN" context_field="org">
 					<context link="usr" field="home_ou" />
 					<context link="ws" field="owning_lib" />
 				</retrieve>
diff --git a/Open-ILS/web/opac/locale/en-US/lang.dtd b/Open-ILS/web/opac/locale/en-US/lang.dtd
index a20ea1f..7acd10d 100644
--- a/Open-ILS/web/opac/locale/en-US/lang.dtd
+++ b/Open-ILS/web/opac/locale/en-US/lang.dtd
@@ -190,6 +190,26 @@
 <!ENTITY staff.admin.survey.save_question.label "Save this Question">
 <!ENTITY staff.admin.survey.staff_client.label "Staff Client:">
 <!ENTITY staff.admin.survey.start.label "Start:">
+<!ENTITY staff.admin.toolbar.new_toolbar.label "New Toolbar">
+<!ENTITY staff.admin.toolbar.new_toolbar.accesskey "N">
+<!ENTITY staff.admin.toolbar.delete_toolbar.label "Delete Toolbar">
+<!ENTITY staff.admin.toolbar.delete_toolbar.accesskey "">
+<!ENTITY staff.admin.toolbar.add_toolbar_button.label "--&gt;">
+<!ENTITY staff.admin.toolbar.add_toolbar_button.accesskey "a">
+<!ENTITY staff.admin.toolbar.remove_toolbar_button.label "&lt;--">
+<!ENTITY staff.admin.toolbar.remove_toolbar_button.accesskey "r">
+<!ENTITY staff.admin.toolbar.move_toolbar_button_up.label "Up">
+<!ENTITY staff.admin.toolbar.move_toolbar_button_up.accesskey "U">
+<!ENTITY staff.admin.toolbar.move_toolbar_button_down.label "Down">
+<!ENTITY staff.admin.toolbar.move_toolbar_button_down.accesskey "D">
+<!ENTITY staff.admin.toolbar.permission_context.label "Permission Context">
+<!ENTITY staff.admin.toolbar.permission_context.accesskey "P">
+<!ENTITY staff.admin.toolbar.cancel_changes.label "Cancel Changes">
+<!ENTITY staff.admin.toolbar.cancel_changes.accesskey "C">
+<!ENTITY staff.admin.toolbar.save_toolbar.label "Save Toolbar">
+<!ENTITY staff.admin.toolbar.save_toolbar.accesskey "S">
+<!ENTITY staff.admin.toolbar.header.available "Available">
+<!ENTITY staff.admin.toolbar.header.selected "Selected">
 <!ENTITY staff.admin.work_log.list1.header "Most Recently Logged Staff Actions">
 <!ENTITY staff.admin.work_log.list2.header "Most Recently Affected Patrons for Logged Staff Actions and Last Action for each">
 <!ENTITY staff.admin.work_log.refresh_btn.label "Refresh">
@@ -664,9 +684,6 @@
 <!ENTITY staff.main.auth.status "Status">
 <!ENTITY staff.main.auth.version "Version">
 <!ENTITY staff.main.auth.workstation "Workstation">
-<!ENTITY staff.main.button_bar.none "None">
-<!ENTITY staff.main.button_bar.circ "Circulation">
-<!ENTITY staff.main.button_bar.cat "Cataloging">
 <!ENTITY staff.main.button_bar.check_out.label "Check Out">
 <!ENTITY staff.main.button_bar.check_out.accesskey "">
 <!ENTITY staff.main.button_bar.check_in.label "Check In">
@@ -846,6 +863,8 @@
 <!ENTITY staff.main.menu.admin.client.hotkeys.clearworkstation.accesskey "">
 <!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">
+<!ENTITY staff.main.menu.admin.client.toolbars.config.accesskey "">
 <!ENTITY staff.main.menu.admin.client.toolbars.setworkstation.label "Set Workstation Default to Current">
 <!ENTITY staff.main.menu.admin.client.toolbars.setworkstation.accesskey "">
 <!ENTITY staff.main.menu.admin.client.toolbars.clearworkstation.label "Clear Workstation Default">
diff --git a/Open-ILS/xul/staff_client/chrome/content/OpenILS/data.js b/Open-ILS/xul/staff_client/chrome/content/OpenILS/data.js
index cf31b79..f31ab3e 100644
--- a/Open-ILS/xul/staff_client/chrome/content/OpenILS/data.js
+++ b/Open-ILS/xul/staff_client/chrome/content/OpenILS/data.js
@@ -973,6 +973,40 @@ OpenILS.data.prototype = {
         this.chain.push(
             function() {
                 var f = gen_fm_retrieval_func(
+                    'atb',
+                    [
+                        api.FM_ATB_RETRIEVE_VIA_PCRUD.app,
+                        api.FM_ATB_RETRIEVE_VIA_PCRUD.method,
+                        [
+                            obj.session.key,
+                            {
+                                "-or": [
+                                    { "ws" : obj.list.au[0].wsid() },
+                                    { "usr" : obj.list.au[0].id() },
+                                    { "org" : util.functional.map_list( obj.list.my_aou, function(o) { return o.id(); } ) }
+                                ]
+                            },
+                            {
+                                "order_by":{"atb":"label"}
+                            }
+                        ],
+                        false
+                    ]
+                );
+                try {
+                    f();
+                } catch(E) {
+                    var error = 'Error: ' + js2JSON(E);
+                    obj.error.sdump('D_ERROR',error);
+                    throw(E);
+                }
+            }
+        );
+
+
+        this.chain.push(
+            function() {
+                var f = gen_fm_retrieval_func(
                     'acnp',
                     [
                         api.FM_ACNP_RETRIEVE_VIA_PCRUD.app,
diff --git a/Open-ILS/xul/staff_client/chrome/content/main/constants.js b/Open-ILS/xul/staff_client/chrome/content/main/constants.js
index eea83a4..6bc4d44 100644
--- a/Open-ILS/xul/staff_client/chrome/content/main/constants.js
+++ b/Open-ILS/xul/staff_client/chrome/content/main/constants.js
@@ -100,6 +100,7 @@ var api = {
     'FM_ACNC_RETRIEVE_VIA_PCRUD' : { 'app' : 'open-ils.pcrud', 'method' : 'open-ils.pcrud.search.acnc.atomic' },
     'FM_ACNP_RETRIEVE_VIA_PCRUD' : { 'app' : 'open-ils.pcrud', 'method' : 'open-ils.pcrud.search.acnp.atomic' },
     'FM_ACNS_RETRIEVE_VIA_PCRUD' : { 'app' : 'open-ils.pcrud', 'method' : 'open-ils.pcrud.search.acns.atomic' },
+    'FM_ATB_RETRIEVE_VIA_PCRUD' : { 'app' : 'open-ils.pcrud', 'method' : 'open-ils.pcrud.search.atb.atomic' },
     'FM_ACP_DETAILS' : { 'app' : 'open-ils.circ', 'method' : 'open-ils.circ.copy_details.retrieve' },
     'FM_ACP_DETAILS_VIA_BARCODE' : { 'app' : 'open-ils.circ', 'method' : 'open-ils.circ.copy_details.retrieve.barcode' },
     'FM_ACP_DETAILS_VIA_BARCODE.authoritative' : { 'app' : 'open-ils.circ', 'method' : 'open-ils.circ.copy_details.retrieve.barcode.authoritative' },
@@ -490,6 +491,7 @@ var urls = {
     'XUL_STAT_CAT_EDIT' : '/xul/server/admin/stat_cat_editor.xhtml',
     'XUL_SURVEY_WIZARD' : 'chrome://open_ils_staff_client/content/admin/survey_wizard.xul',
     'XUL_TIMESTAMP_DIALOG' : '/xul/server/util/timestamp.xul',
+    'XUL_TOOLBAR_CONFIG' : '/xul/server/admin/toolbar.xul',
     'XUL_TRIGGER_EVENTS' : '/xul/server/patron/trigger_events.xul',
     'XUL_USER_BUCKETS' : '/xul/server/patron/user_buckets.xul',
     'XUL_VERIFY_CREDENTIALS' : '/xul/server/main/verify_credentials.xul',
diff --git a/Open-ILS/xul/staff_client/chrome/content/main/menu.js b/Open-ILS/xul/staff_client/chrome/content/main/menu.js
index d541553..ac27aec 100644
--- a/Open-ILS/xul/staff_client/chrome/content/main/menu.js
+++ b/Open-ILS/xul/staff_client/chrome/content/main/menu.js
@@ -72,73 +72,7 @@ main.menu.prototype = {
             eval( r.responseText );
         }
 
-        // Try workstation pref for button bar
-        var button_bar = xulG.pref.getCharPref('open-ils.menu.toolbar');
-
-        if (!button_bar) // No workstation pref? Try org unit pref.
-            button_bar = String( obj.data.hash.aous['ui.general.button_bar'] );
-
-        if (button_bar) {
-            var x = document.getElementById('toolbar_' + button_bar);
-            if (x) x.setAttribute('hidden','false');
-            this.toolbar = button_bar;
-        }
-
-        // Check for alternate Size pref
-        var toolbar_size = xulG.pref.getCharPref('open-ils.menu.toolbar.iconsize');
-        if(toolbar_size) this.toolbar_size = toolbar_size;
-        // Check for alternate Mode pref
-        var toolbar_mode = xulG.pref.getCharPref('open-ils.menu.toolbar.mode');
-        if(toolbar_mode) this.toolbar_mode = toolbar_mode;
-        // Check for alternate Label Position pref
-        var toolbar_labelpos = xulG.pref.getBoolPref('open-ils.menu.toolbar.labelbelow');
-        if(toolbar_labelpos) this.toolbar_labelpos = toolbar_labelpos;
-
-        if(button_bar || toolbar_size || toolbar_mode || toolbar_labelpos) {
-            var toolbox = document.getElementById('main_toolbox');
-            var toolbars = toolbox.getElementsByTagName('toolbar');
-            for(var i = 0; i < toolbars.length; i++) {
-                if(toolbars[i].id == 'toolbar_' + button_bar)
-                    toolbars[i].setAttribute('hidden', 'false');
-                else
-                    toolbars[i].setAttribute('hidden', 'true');
-                if(toolbar_mode) toolbars[i].setAttribute('mode', toolbar_mode);
-                if(toolbar_size) toolbars[i].setAttribute('iconsize', toolbar_size);
-                if(toolbar_labelpos) addCSSClass(toolbars[i], 'labelbelow');
-            }
-        }
-
-        if(button_bar) {
-            var x = document.getElementById('main.menu.admin.client.toolbars.current.popup');
-            if (x) {
-                var selectitems = x.getElementsByAttribute('value',button_bar);
-                if(selectitems.length > 0) selectitems[0].setAttribute('checked','true');
-            }
-        }
-
-        if(toolbar_size) {
-            var x = document.getElementById('main.menu.admin.client.toolbars.size.popup');
-            if (x) {
-                var selectitems = x.getElementsByAttribute('value',toolbar_size);
-                if(selectitems.length > 0) selectitems[0].setAttribute('checked','true');
-            }
-        }
-
-        if(toolbar_mode) {
-            var x = document.getElementById('main.menu.admin.client.toolbars.mode.popup');
-            if (x) {
-                var selectitems = x.getElementsByAttribute('value',toolbar_mode);
-                if(selectitems.length > 0) selectitems[0].setAttribute('checked','true');
-            }
-        }
-
-        if(toolbar_labelpos) {
-            var x = document.getElementById('main.menu.admin.client.toolbars.label_position.popup');
-            if (x) {
-                var selectitems = x.getElementsByAttribute('value',"under");
-                if(selectitems.length > 0) selectitems[0].setAttribute('checked','true');
-            }
-        }
+        this.button_bar_init();
 
         var cl_first = xulG.pref.getBoolPref('oils.copy_editor.copy_location_name_first');
         var menuitems = document.getElementsByAttribute('command','cmd_copy_editor_copy_location_first_toggle');
@@ -1564,14 +1498,7 @@ main.menu.prototype = {
                 ['oncommand'],
                 function(event) {
                     var newToolbar = event.explicitOriginalTarget.getAttribute('value');
-                    var toolbox = document.getElementById('main_toolbox');
-                    var toolbars = toolbox.getElementsByTagName('toolbar');
-                    for(var i = 0; i < toolbars.length; i++) {
-                        if(toolbars[i].id == 'toolbar_' + newToolbar)
-                            toolbars[i].setAttribute('hidden', 'false');
-                        else
-                            toolbars[i].setAttribute('hidden', 'true');
-                    }
+                    obj.render_toolbar(newToolbar);
                     obj.toolbar = newToolbar;
                 }
             ],
@@ -1612,6 +1539,13 @@ main.menu.prototype = {
                     obj.toolbar_labelpos = (altPosition ? "under" : "side");
                 }
             ],
+            'cmd_toolbar_configure' : [
+                ['oncommand'],
+                function(event) {
+                    var url = obj.url_prefix( urls.XUL_TOOLBAR_CONFIG ); 
+                    obj.command_tab(event,url,{},{});
+                }
+            ],
             'cmd_toolbar_setworkstation' : [
                 ['oncommand'],
                 function() {
@@ -1708,6 +1642,130 @@ main.menu.prototype = {
         }
     },
 
+    'button_bar_init' : function() {
+        try {
+
+            var obj = this;
+
+            JSAN.use('util.widgets');
+
+            // populate the menu of available toolbars
+            var x = document.getElementById('main.menu.admin.client.toolbars.current.popup');
+            if (x) {
+                util.widgets.remove_children(x);
+
+                function create_menuitem(label,value,checked) {
+                    var menuitem = document.createElement('menuitem');
+                        menuitem.setAttribute('name','current_toolbar');
+                        menuitem.setAttribute('type','radio');
+                        menuitem.setAttribute('label',label);
+                        menuitem.setAttribute('value',value);
+                        menuitem.setAttribute('command','cmd_toolbar_set');
+                        if (checked) menuitem.setAttribute('checked','true');
+                    return menuitem;
+                }
+
+                x.appendChild(
+                    create_menuitem(
+                        offlineStrings.getString('staff.main.button_bar.none'),
+                        'none',
+                        true
+                    )
+                );
+
+                for (var i = 0; i < this.data.list.atb.length; i++) {
+                    var def = this.data.list.atb[i];
+                    x.appendChild(
+                        create_menuitem(
+                            def.label(),
+                            def.id()
+                        )
+                    );
+                }
+            }
+
+            // Try workstation pref for button bar
+            var button_bar = xulG.pref.getCharPref('open-ils.menu.toolbar');
+
+            if (!button_bar) { // No workstation pref? Try org unit pref.
+                if (obj.data.hash.aous['ui.general.button_bar']) {
+                    button_bar = String( obj.data.hash.aous['ui.general.button_bar'] );
+                }
+            }
+
+            if (button_bar) {
+                this.render_toolbar(button_bar);
+                this.toolbar = button_bar;
+            }
+
+            // Check for alternate Size pref
+            var toolbar_size = xulG.pref.getCharPref('open-ils.menu.toolbar.iconsize');
+            if(toolbar_size) this.toolbar_size = toolbar_size;
+            // Check for alternate Mode pref
+            var toolbar_mode = xulG.pref.getCharPref('open-ils.menu.toolbar.mode');
+            if(toolbar_mode) this.toolbar_mode = toolbar_mode;
+            // Check for alternate Label Position pref
+            var toolbar_labelpos = xulG.pref.getBoolPref('open-ils.menu.toolbar.labelbelow');
+            if(toolbar_labelpos) this.toolbar_labelpos = toolbar_labelpos;
+
+            if(button_bar || toolbar_size || toolbar_mode || toolbar_labelpos) {
+                var toolbar = document.getElementById('toolbar_main');
+                if(toolbar_mode) toolbar.setAttribute('mode', toolbar_mode);
+                if(toolbar_size) toolbar.setAttribute('iconsize', toolbar_size);
+                if(toolbar_labelpos) addCSSClass(toolbar, 'labelbelow');
+            }
+
+            if(button_bar) {
+                var x = document.getElementById('main.menu.admin.client.toolbars.current.popup');
+                if (x) {
+                    var selectitems = x.getElementsByAttribute('value',button_bar);
+                    if(selectitems.length > 0) selectitems[0].setAttribute('checked','true');
+                }
+            }
+
+            if(toolbar_size) {
+                var x = document.getElementById('main.menu.admin.client.toolbars.size.popup');
+                if (x) {
+                    var selectitems = x.getElementsByAttribute('value',toolbar_size);
+                    if(selectitems.length > 0) selectitems[0].setAttribute('checked','true');
+                }
+            }
+
+            if(toolbar_mode) {
+                var x = document.getElementById('main.menu.admin.client.toolbars.mode.popup');
+                if (x) {
+                    var selectitems = x.getElementsByAttribute('value',toolbar_mode);
+                    if(selectitems.length > 0) selectitems[0].setAttribute('checked','true');
+                }
+            }
+
+            if(toolbar_labelpos) {
+                var x = document.getElementById('main.menu.admin.client.toolbars.label_position.popup');
+                if (x) {
+                    var selectitems = x.getElementsByAttribute('value',"under");
+                    if(selectitems.length > 0) selectitems[0].setAttribute('checked','true');
+                }
+            }
+
+            // stash the available toolbar buttons for later use in the toolbar editing interface
+            if (typeof this.data.toolbar_buttons == 'undefined') {
+                this.data.toolbar_buttons = {};
+                var nl = $('palette').childNodes;
+                for (var i = 0; i < nl.length; i++) {
+                    var id = nl[i].getAttribute('templateid');
+                    var label = nl[i].getAttribute('label');
+                    if (id && label) {
+                        this.data.toolbar_buttons[ id ] = label;
+                    }
+                }
+                this.data.stash('toolbar_buttons');
+            }
+
+        } catch(E) {
+            alert('Error in menu.js, button_bar_init(): ' + E);
+        }
+    },
+
     'spawn_search' : function(s) {
         var obj = this;
         obj.error.sdump('D_TRACE', offlineStrings.getFormattedString('menu.spawn_search.msg', [js2JSON(s)]) ); 
@@ -2217,6 +2275,7 @@ commands:
         content_params.network_meter = obj.network_meter;
         content_params.page_meter = obj.page_meter;
         content_params.get_barcode = obj.get_barcode;
+        content_params.render_toolbar_layout = function(layout) { return obj.render_toolbar_layout(layout); };
         content_params.set_statusbar = function(slot,text,tooltiptext,click_handler) {
             var e = document.getElementById('statusbarpanel'+slot);
             if (e) {
@@ -2525,6 +2584,83 @@ commands:
 
     'stop_observing' : function() {
         xulG.pref.removeObserver('oils.copy_editor.*', this);
+    },
+
+    'render_toolbar' : function(button_bar) {
+        try {
+
+            this.last_sanctioned_toolbar = button_bar;
+
+            var toolbar = document.getElementById('toolbar_main');
+
+            if (button_bar == 'none' || typeof button_bar == 'undefined') {
+                toolbar.setAttribute('hidden','true');
+                return;
+            }
+
+            // find the layout
+            var layout;
+            JSAN.use('util.widgets'); JSAN.use('util.functional');
+            var def = this.data.hash.atb[ button_bar ];
+            if (!def) def = util.functional.find_list( this.data.list.atb, function(e) { return e.label == button_bar; } );
+            if (!def) {
+                dump('Could not find layout for specified toolbar. Defaulting to a stock toolbar.\n');
+                layout = [ 'circ_checkin', 'toolbarseparator', 'toolbarspacer', 'hotkeys_toggle' ];
+            } else {
+                layout = JSON2js(def.layout());
+            }
+
+            this.render_toolbar_layout(layout);
+
+        } catch(E) {
+            alert('Error in menu.js, render_toolbar('+button_bar+'): ' + E);
+        }
+    },
+
+    'render_toolbar_layout' : function(layout) {
+        try {
+
+            if (!layout) {
+                this.data.stash_retrieve();
+                this.render_toolbar( this.last_sanctioned_toolbar );
+                return;
+            }
+
+            var toolbar = document.getElementById('toolbar_main');
+
+            // destroy existing toolbar
+            util.widgets.remove_children(toolbar);
+
+            // create new one
+            for (var i = 0; i < layout.length; i++) {
+                var e = layout[i];
+                switch(e) {
+                    case 'toolbarseparator':
+                        toolbar.appendChild( document.createElement('toolbarseparator') );
+                    break;
+                    case 'toolbarspacer':
+                        var spacer = document.createElement('toolbarspacer');
+                        spacer.setAttribute('flex','1');
+                        toolbar.appendChild( spacer );
+                    break;
+                    default:
+                        var templates = $('palette').getElementsByAttribute('templateid',e);
+                        var template = templates.length > 0 ? templates[0] : null;
+                        if (template) {
+                            var clone = template.cloneNode(true);
+                            toolbar.appendChild( clone );
+                        } else {
+                            var label = document.createElement('label');
+                            label.setAttribute('value',e);
+                            toolbar.appendChild( label );
+                        }
+                }
+            }
+            toolbar.setAttribute('hidden','false');
+
+        } catch(E) {
+            alert('Error in menu.js, render_toolbar_layout('+layout+'): ' + E);
+        }
     }
 }
 
diff --git a/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul b/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
index c71f8e1..bd98d4b 100644
--- a/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
+++ b/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
@@ -256,6 +256,7 @@
     <command id="cmd_hotkeys_set" />
     <command id="cmd_hotkeys_setworkstation" />
     <command id="cmd_hotkeys_clearworkstation" />
+    <command id="cmd_toolbar_configure" />
     <command id="cmd_toolbar_set" />
     <command id="cmd_toolbar_setworkstation" />
     <command id="cmd_toolbar_clearworkstation" />
@@ -457,9 +458,6 @@
                     <menupopup id="main.menu.admin.client.toolbars.popup">
                         <menu id="main.menu.admin.client.toolbars.current" label="&staff.main.menu.admin.client.toolbars.current;">
                             <menupopup id="main.menu.admin.client.toolbars.current.popup">
-                                <menuitem name="current_toolbar" type="radio" label="&staff.main.button_bar.none;" value="none" command="cmd_toolbar_set" checked="true"/>
-                                <menuitem name="current_toolbar" type="radio" label="&staff.main.button_bar.circ;" value="circ" command="cmd_toolbar_set"/>
-                                <menuitem name="current_toolbar" type="radio" label="&staff.main.button_bar.cat;" value="cat" command="cmd_toolbar_set"/>
                             </menupopup>
                         </menu>
                         <menu id="main.menu.admin.client.toolbars.mode" label="&staff.main.menu.admin.client.toolbars.mode;">
@@ -482,6 +480,8 @@
                             </menupopup>
                         </menu>
                         <menuseparator />
+                        <menuitem label="&staff.main.menu.admin.client.toolbars.config.label;" accesskey="&staff.main.menu.admin.client.toolbars.config.accesskey;" command="cmd_toolbar_configure"/>
+                        <menuseparator />
                         <menuitem label="&staff.main.menu.admin.client.toolbars.setworkstation.label;" accesskey="&staff.main.menu.admin.client.toolbars.setworkstation.accesskey;" command="cmd_toolbar_setworkstation"/>
                         <menuitem label="&staff.main.menu.admin.client.toolbars.clearworkstation.label;" accesskey="&staff.main.menu.admin.client.toolbars.clearworkstation.accesskey;" command="cmd_toolbar_clearworkstation"/>
                     </menupopup>
diff --git a/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_overlay.xul b/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_overlay.xul
index ad4686e..eb75d98 100644
--- a/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_overlay.xul
+++ b/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_overlay.xul
@@ -60,6 +60,212 @@
     </tooltip>
 </box>
 
+<toolbarpallete id="palette">
+    <toolbarbutton 
+        templateid="circ_checkout"
+        command="cmd_circ_checkout" 
+        label="&staff.main.button_bar.check_out.label;" 
+        tooltiptext="&staff.main.button_bar.check_out.label;"
+        type="menu-button">
+        <menupopup tooltiptext=""> <!-- Little note on this first one - The blank tooltiptext stops the button's tooltiptext from applying to the menu and items -->
+            <menuitem label="&staff.main.menu.circ.checkout.label;" accesskey="&staff.main.menu.circ.checkout.accesskey;" command="cmd_circ_checkout"/>
+            <menuitem label="&staff.main.menu.circ.in_house.label;" accesskey="&staff.main.menu.circ.in_house.accesskey;" command="cmd_in_house_use"/>
+        </menupopup>
+    </toolbarbutton>
+    <toolbarbutton
+        templateid="circ_checkin"
+        command="cmd_circ_checkin" 
+        label="&staff.main.button_bar.check_in.label;" 
+        tooltiptext="&staff.main.button_bar.check_in.label;"
+        type="menu-button">
+        <menupopup tooltiptext="">
+            <menuitem label="&staff.main.menu.circ.checkin.label;" accesskey="&staff.main.menu.circ.checkin.accesskey;" command="cmd_circ_checkin"/>
+            <menuitem label="&staff.main.menu.circ.hold_capture.label;" accesskey="&staff.main.menu.circ.hold_capture.accesskey;" command="cmd_circ_hold_capture"/>
+        </menupopup>
+    </toolbarbutton>
+    <toolbarbutton
+        templateid="search_opac"
+        command="cmd_search_opac" 
+        label="&staff.main.button_bar.search_opac.label;" 
+        tooltiptext="&staff.main.button_bar.search_opac.label;"
+        type="menu-button">
+        <menupopup tooltiptext="">
+            <menuitem label="&staff.main.menu.cat.bib_search.label;" accesskey="&staff.main.menu.cat.bib_search.accesskey;" command="cmd_search_opac"/>
+            <menuitem label="&staff.main.menu.cat.search_tcn.label;" accesskey="&staff.main.menu.cat.search_tcn.accesskey;" command="cmd_search_tcn" />
+            <menuitem label="&staff.main.menu.cat.search_bib_id.label;" accesskey="&staff.main.menu.cat.search_bib_id.accesskey;" command="cmd_search_bib_id" />
+        </menupopup>
+    </toolbarbutton>
+    <toolbarbutton
+        templateid="copy_status"
+        command="cmd_copy_status" 
+        label="&staff.main.button_bar.item_status.label;" 
+        tooltiptext="&staff.main.button_bar.item_status.label;"
+        type="menu-button">
+        <menupopup tooltiptext="">
+            <menuitem label="&staff.main.menu.circ.barcode.show_item;" accesskey="&staff.main.menu.circ.barcode.show_item.accesskey;" command="cmd_copy_status"/>
+            <menuitem label="&staff.main.menu.replace_barcode.label;" command="cmd_replace_barcode"/>
+        </menupopup>
+    </toolbarbutton>
+    <toolbarbutton
+        templateid="patron_search"
+        command="cmd_patron_search" 
+        label="&staff.main.button_bar.patron_search.label;" 
+        tooltiptext="&staff.main.button_bar.patron_search.label;"
+        type="menu-button">
+        <menupopup tooltiptext="">
+            <menuitem label="&staff.main.menu.search.patrons.label;" accesskey="&staff.main.menu.search.patrons.accesskey;" command="cmd_patron_search" />
+            <menuitem label="&staff.main.menu.search.patrons_barcode.label;" accesskey="&staff.main.menu.search.patrons_barcode.accesskey;" command="cmd_circ_checkout"/>
+            <menuitem label="&staff.main.menu.search.patron_db_id.label;" accesskey="&staff.main.menu.search.patron_db_id.accesskey;" command="cmd_search_usr_id"/>
+        </menupopup>
+    </toolbarbutton>
+    <toolbarbutton
+        templateid="patron_register"
+        command="cmd_patron_register" 
+        label="&staff.main.button_bar.patron_registration.label;" 
+        tooltiptext="&staff.main.button_bar.patron_registration.label;"
+        type="menu-button">
+        <menupopup tooltiptext="">
+            <menuitem label="&staff.main.menu.circ.patron_registration.label;" accesskey="&staff.main.menu.circ.patron_registration.accesskey;" command="cmd_patron_register"/>
+            <menuitem label="&staff.main.menu.circ.staged_patrons.label;" accesskey="&staff.main.menu.circ.staged_patrons.accesskey;" command="cmd_staged_patrons"/>
+        </menupopup>
+    </toolbarbutton>
+    <toolbarbutton
+        templateid="create_marc"
+        command="cmd_create_marc"
+        label="&staff.main.button_bar.create_marc;"
+        tooltiptext="&staff.main.button_bar.create_marc;"
+        type="menu-button">
+        <menupopup tooltiptext="">
+            <menuitem label="&staff.main.menu.cat.create_marc.label;" accesskey="&staff.main.menu.cat.create_marc.accesskey;" command="cmd_create_marc"/>
+            <menuitem label="&staff.main.menu.cat.z39_50_import.label;" accesskey="&staff.main.menu.cat.z39_50_import.accesskey;" command="cmd_z39_50_import"/>
+            <menuitem label="&staff.main.menu.cat.vandelay.label;" command="cmd_open_vandelay"/>
+        </menupopup>
+    </toolbarbutton>
+    <toolbarbutton
+        templateid="authority_manage"
+        command="cmd_authority_manage"
+        label="&staff.main.button_bar.authority_manage;"
+        tooltiptext="&staff.main.button_bar.authority_manage;" />
+    <toolbarbutton
+        templateid="retrieve_last_record"
+        command="cmd_retrieve_last_record"
+        label="&staff.main.button_bar.retrieve_last_record;"
+        tooltiptext="&staff.main.button_bar.retrieve_last_record;" />
+    <toolbarbutton
+        templateid="portal"
+        command="cmd_portal"
+        label="&staff.main.menu.file.portal.label;"
+        tooltiptext="&staff.main.menu.file.portal.label;" />
+    <toolbarbutton
+        id="cmd_edit_copy_buckets"
+        command="cmd_edit_copy_buckets"
+        label="&staff.main.menu.edit.buckets.copies;"
+        tooltiptext="&staff.main.menu.edit.buckets.copies;" />
+    <toolbarbutton
+        templateid="edit_record_buckets"
+        command="cmd_edit_record_buckets"
+        label="&staff.main.menu.edit.buckets.records;"
+        tooltiptext="&staff.main.menu.edit.buckets.records;" />
+    <toolbarbutton
+        label="&staff.main.menu.circ.renew.label;"
+        tooltiptext="&staff.main.menu.circ.renew.label;"
+        templateid="circ_renew"
+        command="cmd_circ_renew"/>
+    <toolbarbutton
+        label="&staff.main.menu.circ.hold_pull.label;"
+        tooltiptext="&staff.main.menu.circ.hold_pull.label;"
+        command="cmd_circ_hold_pull_list"
+        templateid="circ_hold_pull_list" />
+    <toolbarbutton
+        label="&staff.main.menu.circ.hold_browse.label;"
+        tooltiptext="&staff.main.menu.circ.hold_browse.label;"
+        command="cmd_browse_holds_shelf"
+        templateid="browse_holds_shelf" />
+    <toolbarbutton
+        label="&staff.main.menu.circ.patron_retrieve.label;"
+        tooltiptext="&staff.main.menu.circ.patron_retrieve.label;"
+        command="cmd_retrieve_last_patron"
+        templateid="retrieve_last_patron" />
+    <toolbarbutton
+        label="&staff.main.menu.acq.unified_search.label;"
+        tooltiptext="&staff.main.menu.acq.unified_search.label;"
+        command="cmd_acq_unified_search"
+        templateid="acq_unified_search" />
+    <toolbarbutton
+        label="&staff.main.menu.acq.view_my_pl.label;"
+        tooltiptext="&staff.main.menu.acq.view_my_pl.label;"
+        command="cmd_acq_view_my_pl"
+        templateid="acq_view_my_pl" />
+    <toolbarbutton
+        label="&staff.main.menu.acq.brief_record.label;"
+        tooltiptext="&staff.main.menu.acq.brief_record.label;"
+        command="cmd_acq_new_brief_record"
+        templateid="acq_new_brief_record" />
+    <toolbarbutton
+        label="&staff.main.menu.acq.user_requests.label;"
+        tooltiptext="&staff.main.menu.acq.user_requests.label;"
+        command="cmd_acq_user_requests" 
+        templateid="acq_user_requests" />
+    <toolbarbutton
+        label="&staff.main.menu.acq.bib_search.label;"
+        tooltiptext="&staff.main.menu.acq.bib_search.label;"
+        command="cmd_acq_bib_search"
+        templateid="acq_bib_search" />
+    <toolbarbutton
+        label="&staff.main.menu.acq.upload.label;"
+        tooltiptext="&staff.main.menu.acq.upload.label;"
+        command="cmd_acq_upload"
+        templateid="acq_upload" />
+    <toolbarbutton
+        label="&staff.main.menu.acq.create_po.label;"
+        tooltiptext="&staff.main.menu.acq.create_po.label;"
+        command="cmd_acq_create_po"
+        templateid="acq_create_po" />
+    <toolbarbutton
+        label="&staff.main.menu.acq.claim_eligible.label;"
+        tooltiptext="&staff.main.menu.acq.claim_eligible.label;"
+        command="cmd_acq_claim_eligible" 
+        templateid="acq_claim_eligible" />
+    <toolbarbutton
+        label="&staff.main.menu.acq.create_invoice.label;"
+        tooltiptext="&staff.main.menu.acq.create_invoice.label;"
+        command="cmd_acq_create_invoice"
+        templateid="acq_create_invoice" />
+    <toolbarbutton
+        label="&staff.main.menu.booking.reservation.label;"
+        tooltiptext="&staff.main.menu.booking.reservation.label;"
+        command="cmd_booking_reservation"
+        templateid="booking_reservation" />
+    <toolbarbutton
+        label="&staff.main.menu.booking.pull_list.label;"
+        tooltiptext="&staff.main.menu.booking.pull_list.label;"
+        command="cmd_booking_pull_list"
+        templateid="booking_pull_list" />
+    <toolbarbutton
+        label="&staff.main.menu.booking.capture.label;"
+        tooltiptext="&staff.main.menu.booking.capture.label;"
+        command="cmd_booking_capture"
+        templateid="booking_capture" />
+    <toolbarbutton
+        label="&staff.main.menu.booking.reservation_pickup.label;"
+        tooltiptext="&staff.main.menu.booking.reservation_pickup.label;"
+        command="cmd_booking_reservation_pickup"
+        templateid="booking_reservation_pickup" />
+    <toolbarbutton
+        label="&staff.main.menu.booking.reservation_return.label;"
+        tooltiptext="&staff.main.menu.booking.reservation_return.label;"
+        command="cmd_booking_reservation_return"
+        templateid="booking_reservation_return" />
+    <toolbarspacer flex="1" />
+    <toolbarbutton
+        templateid="hotkeys_toggle"
+        command="cmd_hotkeys_toggle"
+        type="checkbox"
+        autocheck="false"
+        label="&staff.main.button_bar.hotkeys_toggle;"
+        tooltiptext="&staff.main.button_bar.hotkeys_toggle;"/>
+</toolbarpallete>
+
 <!-- The main top level menubar -->
 <toolbox id="main_toolbox">
     <menubar id="main_menubar">
@@ -74,140 +280,8 @@
         <menu id="main.menu.admin" />
         <menu id="main.menu.help" />
     </menubar>
-    <toolbar id="toolbar_circ" hidden="true">
-        <toolbarbutton 
-            command="cmd_circ_checkout" 
-            label="&staff.main.button_bar.check_out.label;" 
-            tooltiptext="&staff.main.button_bar.check_out.label;"
-            type="menu-button">
-            <menupopup tooltiptext=""> <!-- Little note on this first one - The blank tooltiptext stops the button's tooltiptext from applying to the menu and items -->
-                <menuitem label="&staff.main.menu.circ.checkout.label;" accesskey="&staff.main.menu.circ.checkout.accesskey;" command="cmd_circ_checkout"/>
-                <menuitem label="&staff.main.menu.circ.in_house.label;" accesskey="&staff.main.menu.circ.in_house.accesskey;" command="cmd_in_house_use"/>
-            </menupopup>
-        </toolbarbutton>
-        <toolbarbutton
-            command="cmd_circ_checkin" 
-            label="&staff.main.button_bar.check_in.label;" 
-            tooltiptext="&staff.main.button_bar.check_in.label;"
-            type="menu-button">
-            <menupopup tooltiptext="">
-                <menuitem label="&staff.main.menu.circ.checkin.label;" accesskey="&staff.main.menu.circ.checkin.accesskey;" command="cmd_circ_checkin"/>
-                <menuitem label="&staff.main.menu.circ.hold_capture.label;" accesskey="&staff.main.menu.circ.hold_capture.accesskey;" command="cmd_circ_hold_capture"/>
-            </menupopup>
-        </toolbarbutton>
-        <toolbarseparator />
-        <toolbarbutton
-            command="cmd_search_opac" 
-            label="&staff.main.button_bar.search_opac.label;" 
-            tooltiptext="&staff.main.button_bar.search_opac.label;"
-            type="menu-button">
-            <menupopup tooltiptext="">
-                <menuitem label="&staff.main.menu.cat.bib_search.label;" accesskey="&staff.main.menu.cat.bib_search.accesskey;" command="cmd_search_opac"/>
-                <menuitem label="&staff.main.menu.cat.search_tcn.label;" accesskey="&staff.main.menu.cat.search_tcn.accesskey;" command="cmd_search_tcn" />
-                <menuitem label="&staff.main.menu.cat.search_bib_id.label;" accesskey="&staff.main.menu.cat.search_bib_id.accesskey;" command="cmd_search_bib_id" />
-            </menupopup>
-        </toolbarbutton>
-        <toolbarbutton
-            command="cmd_copy_status" 
-            label="&staff.main.button_bar.item_status.label;" 
-            tooltiptext="&staff.main.button_bar.item_status.label;"
-            type="menu-button">
-            <menupopup tooltiptext="">
-                <menuitem label="&staff.main.menu.circ.barcode.show_item;" accesskey="&staff.main.menu.circ.barcode.show_item.accesskey;" command="cmd_copy_status"/>
-                <menuitem label="&staff.main.menu.replace_barcode.label;" command="cmd_replace_barcode"/>
-            </menupopup>
-        </toolbarbutton>
-        <toolbarseparator />
-        <toolbarbutton
-            command="cmd_patron_search" 
-            label="&staff.main.button_bar.patron_search.label;" 
-            tooltiptext="&staff.main.button_bar.patron_search.label;"
-            type="menu-button">
-            <menupopup tooltiptext="">
-                <menuitem label="&staff.main.menu.search.patrons.label;" accesskey="&staff.main.menu.search.patrons.accesskey;" command="cmd_patron_search" />
-                <menuitem label="&staff.main.menu.search.patrons_barcode.label;" accesskey="&staff.main.menu.search.patrons_barcode.accesskey;" command="cmd_circ_checkout"/>
-                <menuitem label="&staff.main.menu.search.patron_db_id.label;" accesskey="&staff.main.menu.search.patron_db_id.accesskey;" command="cmd_search_usr_id"/>
-            </menupopup>
-        </toolbarbutton>
-        <toolbarbutton
-            command="cmd_patron_register" 
-            label="&staff.main.button_bar.patron_registration.label;" 
-            tooltiptext="&staff.main.button_bar.patron_registration.label;"
-            type="menu-button">
-            <menupopup tooltiptext="">
-                <menuitem label="&staff.main.menu.circ.patron_registration.label;" accesskey="&staff.main.menu.circ.patron_registration.accesskey;" command="cmd_patron_register"/>
-                <menuitem label="&staff.main.menu.circ.staged_patrons.label;" accesskey="&staff.main.menu.circ.staged_patrons.accesskey;" command="cmd_staged_patrons"/>
-            </menupopup>
-        </toolbarbutton>
-        <toolbarspacer flex="1" />
-        <toolbarbutton
-            command="cmd_hotkeys_toggle"
-            type="checkbox"
-            autocheck="false"
-            label="&staff.main.button_bar.hotkeys_toggle;"
-            tooltiptext="&staff.main.button_bar.hotkeys_toggle;"/>
-    </toolbar>
-    <toolbar id="toolbar_cat" hidden="true">
-        <toolbarbutton
-            command="cmd_circ_checkin" 
-            label="&staff.main.button_bar.check_in.label;" 
-            tooltiptext="&staff.main.button_bar.check_in.label;"
-            type="menu-button">
-            <menupopup tooltiptext="">
-                <menuitem label="&staff.main.menu.circ.checkin.label;" accesskey="&staff.main.menu.circ.checkin.accesskey;" command="cmd_circ_checkin"/>
-                <menuitem label="&staff.main.menu.circ.hold_capture.label;" accesskey="&staff.main.menu.circ.hold_capture.accesskey;" command="cmd_circ_hold_capture"/>
-            </menupopup>
-        </toolbarbutton>
-        <toolbarseparator />
-        <toolbarbutton
-            command="cmd_search_opac" 
-            label="&staff.main.button_bar.search_opac.label;" 
-            tooltiptext="&staff.main.button_bar.search_opac.label;"
-            type="menu-button">
-            <menupopup tooltiptext="">
-                <menuitem label="&staff.main.menu.cat.bib_search.label;" accesskey="&staff.main.menu.cat.bib_search.accesskey;" command="cmd_search_opac"/>
-                <menuitem label="&staff.main.menu.cat.search_tcn.label;" accesskey="&staff.main.menu.cat.search_tcn.accesskey;" command="cmd_search_tcn" />
-                <menuitem label="&staff.main.menu.cat.search_bib_id.label;" accesskey="&staff.main.menu.cat.search_bib_id.accesskey;" command="cmd_search_bib_id" />
-            </menupopup>
-        </toolbarbutton>
-        <toolbarbutton
-            command="cmd_copy_status" 
-            label="&staff.main.button_bar.item_status.label;" 
-            tooltiptext="&staff.main.button_bar.item_status.label;"
-            type="menu-button">
-            <menupopup tooltiptext="">
-                <menuitem label="&staff.main.menu.circ.barcode.show_item;" accesskey="&staff.main.menu.circ.barcode.show_item.accesskey;" command="cmd_copy_status"/>
-                <menuitem label="&staff.main.menu.replace_barcode.label;" command="cmd_replace_barcode"/>
-            </menupopup>
-        </toolbarbutton>
-        <toolbarseparator />
-        <toolbarbutton
-            command="cmd_create_marc"
-            label="&staff.main.button_bar.create_marc;"
-            tooltiptext="&staff.main.button_bar.create_marc;"
-            type="menu-button">
-            <menupopup tooltiptext="">
-                <menuitem label="&staff.main.menu.cat.create_marc.label;" accesskey="&staff.main.menu.cat.create_marc.accesskey;" command="cmd_create_marc"/>
-                <menuitem label="&staff.main.menu.cat.z39_50_import.label;" accesskey="&staff.main.menu.cat.z39_50_import.accesskey;" command="cmd_z39_50_import"/>
-                <menuitem label="&staff.main.menu.cat.vandelay.label;" command="cmd_open_vandelay"/>
-            </menupopup>
-        </toolbarbutton>
-        <toolbarbutton
-            command="cmd_authority_manage"
-            label="&staff.main.button_bar.authority_manage;"
-            tooltiptext="&staff.main.button_bar.authority_manage;" />
-        <toolbarbutton
-            command="cmd_retrieve_last_record"
-            label="&staff.main.button_bar.retrieve_last_record;"
-            tooltiptext="&staff.main.button_bar.retrieve_last_record;" />
-        <toolbarspacer flex="1" />
-        <toolbarbutton
-            command="cmd_hotkeys_toggle"
-            type="checkbox"
-            autocheck="false"
-            label="&staff.main.button_bar.hotkeys_toggle;"
-            tooltiptext="&staff.main.button_bar.hotkeys_toggle;"/>
-    </toolbar>
+    <toolbar id="toolbar_main" hidden="true"/>
+    <toolbarpallete id="palette" hidden="true"/>
 </toolbox>
 
 </overlay>
diff --git a/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties b/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties
index 71485fc..af40f08 100644
--- a/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties
+++ b/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties
@@ -316,3 +316,4 @@ barcode_choice.actor_label=Patron : %1$s
 barcode_choice.asset_label=Item : %1$s
 barcode_choice.serial_label=Serial : %1$s
 barcode_choice.booking_label=Booking : %1$s
+staff.main.button_bar.none=None
diff --git a/Open-ILS/xul/staff_client/server/admin/toolbar.js b/Open-ILS/xul/staff_client/server/admin/toolbar.js
new file mode 100644
index 0000000..717a87a
--- /dev/null
+++ b/Open-ILS/xul/staff_client/server/admin/toolbar.js
@@ -0,0 +1,616 @@
+var g = {};
+
+function my_init() {
+    try {
+        netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+        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'); g.error = new util.error();
+        g.error.sdump('D_TRACE','my_init() for toolbar.xul');
+
+        JSAN.use('OpenILS.data'); g.data = new OpenILS.data();
+        g.data.stash_retrieve();
+
+        JSAN.use('util.widgets');
+        JSAN.use('util.functional');
+
+        dojo.require('openils.PermaCrud');
+
+        g.pcrud = new openils.PermaCrud({
+            authtoken :ses()
+        });
+
+        if (typeof window.xulG == 'object' && typeof window.xulG.set_tab_name == 'function') {
+            try { window.xulG.set_tab_name($('adminStrings').getString('staff.admin.toolbar.tab_name')); } catch(E) { alert(E); }
+        }
+
+        init_lists();
+        $('list_actions').appendChild( g.list1.render_list_actions() );
+        g.list1.set_list_actions();
+        populate_list1();
+        render_lib_menu();
+
+        // toolbutton manipulators
+        $('Add').addEventListener('command',Add,'false');
+        $('Remove').addEventListener('command',Remove,'false');
+        $('Up').addEventListener('command',Up,'false');
+        $('Down').addEventListener('command',Down,'false');
+
+        // toolbar manipulators
+        $('Delete').addEventListener('command',Delete,'false');
+        $('New').addEventListener('command',New,'false');
+        $('Cancel').addEventListener('command',Cancel,'false');
+        $('Save').addEventListener('command',Save,'false');
+
+        // restore the toolbar selection
+        window.addEventListener(
+            'unload',
+            function(ev) {
+                xulG.render_toolbar_layout();
+            },
+            false
+        );
+
+        // i18n
+        $('context_org').setAttribute('label', fieldmapper.IDL.fmclasses.atb.field_map.org.label);
+        $('context_usr').setAttribute('label', fieldmapper.IDL.fmclasses.atb.field_map.usr.label);
+        $('context_ws').setAttribute('label', fieldmapper.IDL.fmclasses.atb.field_map.ws.label);
+
+    } catch(E) {
+        try { g.error.standard_unexpected_error_alert('admin/toolbar.xul',E); } catch(F) { alert(E); }
+    }
+}
+
+function init_lists() {
+    try {
+        JSAN.use('util.list'); JSAN.use('patron.util');
+
+        // list1 = main list containing the action.toolbar entries
+        // list2 = left list containing available toolbar buttons
+        // list3 = right list containing selected toolbar buttons
+
+        init_list1();
+        init_list2();
+        init_list3();
+
+    } catch(E) {
+        alert('Error in toolbar.js, init_lists(): ' + E);
+    }
+}
+
+function init_list1() {
+    try {
+        g.list1 = new util.list('atb_tree');
+
+        var list1_columns = g.list1.fm_columns('atb',{
+            '*':{'hidden':true, 'flex':0},
+            'atb_usr' : {
+                'hidden' : false,
+                'render' : function(my) {
+                    if (! my.atb.usr()) return;
+                    return my.atb.usr() == ses('staff_id')
+                        ? ses('staff_usrname')
+                        : patron.util.retrieve_au_via_id(ses(),my.atb.usr()).usrname();
+                }
+            },
+            'atb_org' : {
+                'hidden' : false,
+                'fleshed_display_field' : 'shortname'
+            },
+            'atb_ws' : {
+                'hidden' : false,
+                'render' : function(my) {
+                    if (! my.atb.ws()) return;
+                    return my.atb.ws() == ses('ws_id')
+                        ? ses('ws_name')
+                        : my.atb.ws();
+                }
+            },
+            'atb_label' : { 'hidden' : false, 'flex' : 1 },
+            'atb_layout' : { 'hidden' : false, 'flex' : 2 }
+        });
+
+        g.list1.init({
+            'columns' : list1_columns,
+            'on_select' : handle_list1_selection
+        });
+    } catch(E) {
+        alert('Error in toolbar.js, init_list1(): ' + E);
+    }
+}
+
+function handle_list1_selection(ev) {
+    try {
+        if (oils_lock > 0) {
+            if (g.list1.node.currentIndex != g.list1_last_index) {
+                alert( $('adminStrings').getString('staff.admin.toolbar.unsaved_changes') );
+                g.list1.node.view.selection.select( g.list1_last_index );
+            }
+            return util.widgets.stop_event(ev);
+        }
+        g.list1_last_index = g.list1.node.currentIndex;
+        g.selected_atb = get_atb_from_selection();
+        if (!g.selected_atb) { return; }
+        if (g.selected_atb.org()) {
+            $('lib_menu').value = g.selected_atb.org();
+            $('context').selectedIndex = 0;
+        }
+        if (g.selected_atb.ws()) { $('context').selectedIndex = 1; }
+        if (g.selected_atb.usr()) { $('context').selectedIndex = 2; }
+        g.layout = JSON2js(g.selected_atb.layout());
+        populate_list2_list3();
+        xulG.render_toolbar_layout(g.layout);
+    } catch(E) {
+        alert('Error in toolbar.js, handle_list1_selection(): ' + E);
+    }
+}
+
+function get_atb_from_selection() {
+    try {
+
+        var selected = g.list1.retrieve_selection();
+        if (selected.length > 0) {
+            var treeitem = selected[0]; // seltype="single", so can be only one
+            return g.list1_map[ treeitem.getAttribute('unique_row_counter') ].row.my.atb;
+        } else {
+            return null;
+        }
+
+    } catch(E) {
+        alert('Error in toolbar.js, get_atb_id_from_selection(): ' + E);
+    }
+}
+
+function init_list2() {
+    try {
+        g.list2 = new util.list('left');
+
+        var list2_columns = [
+            {
+                'id' : 'value',
+                'label' : $('adminStrings').getString('staff.admin.toolbar.button_id.header'),
+                'render' : function(my) { return my.value; },
+                'flex' : 1
+            },
+            {
+                'id' : 'label',
+                'label' : $('adminStrings').getString('staff.admin.toolbar.label.header'),
+                'render' : function(my) { return my.label; },
+                'flex' : 1
+            }
+        ];
+
+        g.list2.init({
+            'columns' : list2_columns
+        });
+
+    } catch(E) {
+        alert('Error in toolbar.js, init_list2(): ' + E);
+    }
+}
+
+function get_list2_values_from_selection() {
+    try {
+        var values = [];
+        var selected = g.list2.retrieve_selection();
+        for (var i = 0; i < selected.length; i++) {
+            var treeitem = selected[i];
+            values.push( g.list2_map[ treeitem.getAttribute('unique_row_counter') ].row.my.value );
+        }
+        return values;
+    } catch(E) {
+        alert('Error in toolbar.js, get_list2_values_from_selection(): ' + E);
+    }
+}
+
+function init_list3() {
+    try {
+        g.list3 = new util.list('right');
+
+        var list3_columns = [
+            {
+                'id' : 'value',
+                'label' : $('adminStrings').getString('staff.admin.toolbar.button_id.header'),
+                'render' : function(my) { return my.value; },
+                'flex' : 1
+            },
+            {
+                'id' : 'label',
+                'label' : $('adminStrings').getString('staff.admin.toolbar.label.header'),
+                'render' : function(my) { return my.label; },
+                'flex' : 1
+            }
+        ];
+
+        g.list3.init({
+            'columns' : list3_columns
+        });
+
+    } catch(E) {
+        alert('Error in toolbar.js, init_list2(): ' + E);
+    }
+}
+
+function get_list3_values_from_selection() {
+    try {
+        var values = [];
+        var selected = g.list3.retrieve_selection();
+        for (var i = 0; i < selected.length; i++) {
+            var treeitem = selected[i];
+            values.push( g.list3_map[ treeitem.getAttribute('unique_row_counter') ].row.my.value );
+        }
+        return values;
+    } catch(E) {
+        alert('Error in toolbar.js, get_list3_values_from_selection(): ' + E);
+    }
+}
+
+function populate_list1() {
+    try {
+        g.list1.clear();
+        g.list1_map = {};
+        for (var i = 0; i < g.data.list.atb.length; i++) {
+            var rdata = g.list1.append({
+                'row' : {
+                    'my' : {
+                        'atb' : g.data.list.atb[i]
+                    }
+                }
+            });
+            g.list1_map[ rdata.unique_row_counter ] = rdata;
+        }
+    } catch(E) {
+        alert('Error in toolbar.js, populate_list1(): ' + E);
+    }
+}
+
+function populate_list2_list3(list3_idx) {
+    try {
+
+        g.list2.clear(); g.list2_map = {};
+        g.list3.clear(); g.list3_map = {};
+
+        var seen = {};
+
+        // populate list3, keep track of what to filter from list2
+        for (var i = 0; i < g.layout.length; i++) {
+
+            var value = g.layout[i];
+            var label;
+
+            switch(value) {
+                case 'toolbarseparator':
+                    label = $('adminStrings').getString('staff.admin.toolbar.toolbar_separator.list_entry');
+                break;
+                case 'toolbarspacer':
+                    label = $('adminStrings').getString('staff.admin.toolbar.toolbar_spacer.list_entry');
+                break;
+                default:
+                    label = g.data.toolbar_buttons[value];
+                    seen[value] = true;
+                break;
+            }
+
+            var rdata3 = g.list3.append({
+                'row' : {
+                    'my' : {
+                        'value' : value,
+                        'label' : label
+                    }
+                },
+                'to_bottom' : true,
+                'no_auto_select' : typeof list3_idx != 'undefined' ? true : undefined
+            });
+            g.list3_map[ rdata3.unique_row_counter ] = rdata3;
+        }
+
+        if (list3_idx) {
+            if (list3_idx < 0) { list3_idx = 0; }
+            g.list3.node.view.selection.select(list3_idx);
+        }
+
+        // populate list2
+        var list2_data = [];
+        for (var value in g.data.toolbar_buttons) {
+            if (seen[value]) { continue; }
+            list2_data.push( { 'value' : value, 'label' : g.data.toolbar_buttons[value] } );
+        }
+        list2_data.sort(
+            function(a,b) {
+                if (a.label < b.label) { return -1; }
+                if (a.label > b.label) { return 1; }
+                return 0;
+            }
+        );
+        list2_data = [
+            { 'value' : 'toolbarseparator', 'label' : $('adminStrings').getString('staff.admin.toolbar.toolbar_separator.list_entry') },
+            { 'value' : 'toolbarspacer', 'label' : $('adminStrings').getString('staff.admin.toolbar.toolbar_spacer.list_entry') }
+            //,{ 'value' : null, 'label' : '---' } // if we want to visually separate the spacer/separator from the other actions
+        ].concat(list2_data);
+
+        for (var i = 0; i < list2_data.length; i++) {
+            var rdata2 = g.list2.append({
+                'row' : {
+                    'my' : list2_data[i]
+                },
+                'to_bottom' : true
+            });
+            g.list2_map[ rdata2.unique_row_counter ] = rdata2;
+        }
+
+    } catch(E) {
+        alert('Error in toolbar.js, populate_list2_list3(): ' + E);
+    }
+}
+
+function render_lib_menu() {
+    try {
+        var list = util.functional.map_list(
+            g.data.list.aou,
+            function(o) {
+                var sname = o.shortname(); for (i = sname.length; i < 20; i++) sname += ' ';
+                return [
+                    o.name() ? sname + ' ' + o.name() : o.shortname(),
+                    o.id(),
+                    false,
+                    ( g.data.hash.aout[ o.ou_type() ].depth() * 2),
+                ];
+            }
+        );
+        var ml = util.widgets.make_menulist( list, ses('ws_ou') );
+        ml.setAttribute('id','lib_menu');
+
+        var x = $('lib_menu_placeholder');
+        if (x) {
+            util.widgets.remove_children(x);
+            x.appendChild(ml);
+        }
+
+    } catch(E) {
+        alert('Error in toolbar.js, render_lib_menu(): ' + E);
+    }
+}
+
+function lock_top_buttons() {
+    try {
+        oils_lock_page();
+        $('New').disabled = true;
+        $('Delete').disabled = true;
+        $('Save').disabled = false;
+        $('Cancel').disabled = false;
+    } catch(E) {
+        alert('Error in toolbar.js, lock_top_buttons(): ' + E);
+    }
+}
+
+function unlock_top_buttons() {
+    try {
+        oils_unlock_page();
+        $('New').disabled = false;
+        $('Delete').disabled = false;
+        $('Save').disabled = true;
+        $('Cancel').disabled = true;
+    } catch(E) {
+        alert('Error in toolbar.js, lock_top_buttons(): ' + E);
+    }
+}
+
+function Add(ev) {
+    try {
+        lock_top_buttons();
+        var values_to_add = get_list2_values_from_selection();
+        var temp = get_list3_values_from_selection();
+        var add_after_this_value = temp[ temp.length - 1 ]; // last selected value from list3
+        var add_after_this_position = g.layout.indexOf(add_after_this_value) + 1;
+
+        for (var i = values_to_add.length - 1; i >= 0; i--) { // iterate backwards so that we add them forwards
+            if (!values_to_add[i]) { continue; }
+            g.layout.splice(add_after_this_position,0,values_to_add[i]);
+        }
+
+        populate_list2_list3();
+        xulG.render_toolbar_layout(g.layout);
+
+    } catch(E) {
+        alert('Error in toolbar.js, Add(): ' + E);
+    }
+}
+
+function Remove(ev) {
+    try {
+        lock_top_buttons();
+        var values_to_remove = get_list3_values_from_selection();
+        for (var i = 0; i < values_to_remove.length; i++) {
+            var idx = g.layout.indexOf(values_to_remove[i]);
+            g.layout.splice(idx,1);
+        }
+
+        populate_list2_list3();
+        xulG.render_toolbar_layout(g.layout);
+
+    } catch(E) {
+        alert('Error in toolbar.js, Remove(): ' + E);
+    }
+}
+
+function Up(ev) {
+    try {
+        lock_top_buttons();
+        var values_to_move = get_list3_values_from_selection();
+        var idx;
+        for (var i = 0; i < values_to_move.length; i++) {
+            idx = g.layout.indexOf(values_to_move[i]);
+            if (idx == 0) { continue; }
+            g.layout.splice(idx,1);
+            g.layout.splice(idx-1,0,values_to_move[i]);
+        }
+
+        populate_list2_list3(idx-1);
+        xulG.render_toolbar_layout(g.layout);
+
+    } catch(E) {
+        alert('Error in toolbar.js, Up(): ' + E);
+    }
+}
+
+function Down(ev) {
+    try {
+        lock_top_buttons();
+        var values_to_move = get_list3_values_from_selection();
+        var idx;
+        for (var i = values_to_move.length - 1; i >= 0; i--) {
+            idx = g.layout.indexOf(values_to_move[i]);
+            g.layout.splice(idx+2,0,values_to_move[i]);
+            g.layout.splice(idx,1);
+        }
+
+        populate_list2_list3(idx+1);
+        xulG.render_toolbar_layout(g.layout);
+
+    } catch(E) {
+        alert('Error in toolbar.js, Down(): ' + E);
+    }
+}
+
+function Delete(ev) {
+    try {
+        g.selected_atb.isdeleted(1);
+
+        g.pcrud.apply(g.selected_atb);
+
+        delete g.data.hash.atb[ g.selected_atb.id() ];
+
+        var idx;
+        for (var i = 0; i < g.data.list.atb.length; i++) {
+            if ( g.data.list.atb[i].id() == g.selected_atb.id() ) { idx = i; } 
+        }
+        g.data.list.atb.splice(idx,1);
+
+        g.data.stash('hash','list');
+
+        unlock_top_buttons();
+
+        populate_list1();
+
+
+    } catch(E) {
+        alert('Error in toolbar.js, Delete(): ' + E);
+    }
+}
+
+function New(ev) {
+    try {
+        var name = window.prompt('Enter label for toolbar:');
+        if (!name) { return; }
+
+        var new_atb = new atb();
+        new_atb.isnew('1');
+        new_atb.label(name);
+        new_atb.layout('[]');
+        new_atb.usr(ses('staff_id'));
+
+        var rdata = g.list1.append({
+            'row' : {
+                'my' : {
+                    'atb' : new_atb
+                }
+            }
+        });
+        g.list1_map[ rdata.unique_row_counter ] = rdata;
+
+        setTimeout(
+            function() {
+                lock_top_buttons();
+            }, 1000
+        );
+
+    } catch(E) {
+        alert('Error in toolbar.js, New(): ' + E);
+    }
+}
+
+function Cancel(ev) {
+    try {
+        unlock_top_buttons();
+        g.selected_atb = get_atb_from_selection();
+        if (!g.selected_atb) { return; }
+
+        if (g.selected_atb.id()) { // existing atb
+
+            g.layout = JSON2js(g.selected_atb.layout());
+            populate_list2_list3();
+            xulG.render_toolbar_layout(g.layout);
+
+        } else { // new atb
+
+            populate_list1();
+            populate_list2_list3();
+        }
+
+    } catch(E) {
+        alert('Error in toolbar.js, Cancel(): ' + E);
+    }
+}
+
+function Save(ev) {
+    try {
+        g.selected_atb.layout( js2JSON( g.layout ) );
+        switch($('context').selectedIndex) {
+            case 0: // org
+                g.selected_atb.org($('lib_menu').value);
+                g.selected_atb.ws(null);
+                g.selected_atb.usr(null);
+            break;
+            case 1: // ws
+                g.selected_atb.org(null);
+                g.selected_atb.ws(ses('ws_id'));
+                g.selected_atb.usr(null);
+            break;
+            case 2: // usr
+                g.selected_atb.org(null);
+                g.selected_atb.ws(null);
+                g.selected_atb.usr(ses('staff_id'));
+            break;
+        }
+        g.selected_atb.ischanged(1);
+
+        g.pcrud.apply(g.selected_atb);
+
+        setTimeout( // is pcrud implicitly authoritative?
+            function() {
+                JSAN.use('util.network');
+                var net = new util.network;
+                var r = net.simple_request(
+                    'FM_ATB_RETRIEVE_VIA_PCRUD',
+                    [
+                        ses(),
+                        {
+                            "-or": [
+                                { "ws" : g.data.list.au[0].wsid() },
+                                { "usr" : g.data.list.au[0].id() },
+                                { "org" : util.functional.map_list( g.data.list.my_aou, function(o) { return o.id(); } ) }
+                            ]
+                        },
+                        {
+                            "order_by":{"atb":"label"}
+                        }
+                    ]
+                );
+                g.data.hash.atb = util.functional.convert_object_list_to_hash(r,null);
+                g.data.list.atb = r;
+
+                g.data.stash('hash','list');
+
+                unlock_top_buttons();
+
+                populate_list1();
+            }, 1000
+        );
+
+    } catch(E) {
+        alert('Error in toolbar.js, Save(): ' + E);
+    }
+}
+
+
diff --git a/Open-ILS/xul/staff_client/server/admin/toolbar.xul b/Open-ILS/xul/staff_client/server/admin/toolbar.xul
new file mode 100644
index 0000000..5732674
--- /dev/null
+++ b/Open-ILS/xul/staff_client/server/admin/toolbar.xul
@@ -0,0 +1,109 @@
+<?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"?>
+
+<!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
+<!-- LOCALIZATION -->
+<!DOCTYPE window PUBLIC "" ""[
+    <!--#include virtual="/opac/locale/${locale}/lang.dtd"-->
+]>
+
+<!-- ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -->
+<!-- OVERLAYS -->
+<?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
+
+<window id="main_toolbar_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"/>
+    <messagecatalog id="adminStrings" src='/xul/server/locale/<!--#echo var="locale"-->/admin.properties'/>
+
+    <script type="text/javascript" src="/xul/server/main/JSAN.js"/>
+    <script type="text/javascript" src="toolbar.js"/>
+
+    <vbox flex="1">
+
+        <hbox>
+            <spacer flex="1"/>
+            <button id="Delete"
+                label="&staff.admin.toolbar.delete_toolbar.label;"
+                accesskey="&staff.admin.toolbar.delete_toolbar.accesskey;"
+            />
+            <button id="New"
+                label="&staff.admin.toolbar.new_toolbar.label;"
+                accesskey="&staff.admin.toolbar.new_toolbar.accesskey;"
+            />
+        </hbox>
+        <tree id="atb_tree" flex="1" enableColumnDrag="true" seltype="single"/>
+        <hbox id="list_actions"/>
+        <splitter><grippy/></splitter>
+        <hbox flex="1">
+            <vbox flex="1">
+                <label value="&staff.admin.toolbar.header.available;" />
+                <tree id="left" flex="1" enableColumnDrag="true" />
+            </vbox>
+            <splitter/>
+            <vbox>
+                <spacer flex="1"/>
+                <button id="Remove"
+                    label="&staff.admin.toolbar.remove_toolbar_button.label;"
+                    accesskey="&staff.admin.toolbar.remove_toolbar_button.accesskey;"
+                />
+                <button id="Add"
+                    label="&staff.admin.toolbar.add_toolbar_button.label;"
+                    accesskey="&staff.admin.toolbar.add_toolbar_button.accesskey;"
+                />
+                <spacer flex="1"/>
+                <button id="Up"
+                    label="&staff.admin.toolbar.move_toolbar_button_up.label;"
+                    accesskey="&staff.admin.toolbar.move_toolbar_button_up.accesskey;"
+                />
+                <button id="Down"
+                    label="&staff.admin.toolbar.move_toolbar_button_down.label;"
+                    accesskey="&staff.admin.toolbar.move_toolbar_button_down.accesskey;"
+                />
+                <spacer flex="1"/>
+            </vbox>
+            <splitter/>
+            <vbox flex="1">
+                <label value="&staff.admin.toolbar.header.selected;" />
+                <tree id="right" flex="1" enableColumnDrag="true" />
+            </vbox>
+        </hbox>
+        <hbox>
+            <label control="context"
+                value="&staff.admin.toolbar.permission_context.label;"
+                accesskey="&staff.admin.toolbar.permission_context.accesskey;"
+            />
+            <hbox id="lib_menu_placeholder" />
+            <radiogroup id="context" orient="horizontal">
+                <radio id="context_org" />
+                <radio id="context_ws" />
+                <radio id="context_usr" />
+            </radiogroup>
+            <spacer flex="1"/>
+            <button id="Cancel" disabled="true"
+                label="&staff.admin.toolbar.cancel_changes.label;"
+                accesskey="&staff.admin.toolbar.cancel_changes.accesskey;"
+            />
+            <button id="Save" disabled="true"
+                label="&staff.admin.toolbar.save_toolbar.label;"
+                accesskey="&staff.admin.toolbar.save_toolbar.accesskey;"
+            />
+        </hbox>
+
+    </vbox>
+
+</window>
+
diff --git a/Open-ILS/xul/staff_client/server/locale/en-US/admin.properties b/Open-ILS/xul/staff_client/server/locale/en-US/admin.properties
index 3f0428a..5287a56 100644
--- a/Open-ILS/xul/staff_client/server/locale/en-US/admin.properties
+++ b/Open-ILS/xul/staff_client/server/locale/en-US/admin.properties
@@ -28,6 +28,12 @@ staff.admin.font_settings.status_msg.ALL_FONTS_15PT=Global Font set to 15pt
 staff.admin.font_settings.status_msg.ALL_FONTS_16PT=Global Font set to 16pt
 staff.admin.font_settings.status_msg.ALL_FONTS_17PT=Global Font set to 17pt
 staff.admin.font_settings.status_msg.ALL_FONTS_18PT=Global Font set to 18pt
+staff.admin.toolbar.unsaved_changes=Unsaved changes. Choose either Save Toolbar or Cancel Changes if you wish to select a different toolbar.
+staff.admin.toolbar.tab_name=Toolbars
+staff.admin.toolbar.button_id.header=Button ID
+staff.admin.toolbar.label.header=Label
+staff.admin.toolbar.toolbar_separator.list_entry=Toolbar Separator
+staff.admin.toolbar.toolbar_spacer.list_entry=Toolbar Spacer
 staff.admin.transit_list.missing_list=Missing library list.
 staff.admin.transit_list.no_match=No matching transits.
 staff.admin.transit_list.invalid_date=Invalid Date (%1$s), setting to Today

commit cc05855a2b1562acd0816c6b0b41b23b83c90720
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Tue Mar 20 13:03:21 2012 -0400

    fix constraint syntax
    
    and add in toolbar related permissions, plus a missing and unrelated perm: IMPORT_AUTHORITY_MARC
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Thomas Berezansky <tsbere at mvlc.org>

diff --git a/Open-ILS/src/sql/Pg/005.schema.actors.sql b/Open-ILS/src/sql/Pg/005.schema.actors.sql
index 4fead81..a6edeb9 100644
--- a/Open-ILS/src/sql/Pg/005.schema.actors.sql
+++ b/Open-ILS/src/sql/Pg/005.schema.actors.sql
@@ -637,9 +637,6 @@ CREATE TABLE actor.toolbar (
     usr         INT         REFERENCES actor.usr (id) ON DELETE CASCADE,
     label       TEXT        NOT NULL,
     layout      TEXT        NOT NULL,
-	CONSTRAINT label_once_per_ws UNIQUE (ws, label) WHERE ws IS NOT NULL,
-	CONSTRAINT label_once_per_org UNIQUE (org, label) WHERE org IS NOT NULL,
-	CONSTRAINT label_once_per_usr UNIQUE (usr, label) WHERE usr IS NOT NULL,
     CONSTRAINT only_one_type CHECK (
         (ws IS NOT NULL AND COALESCE(org,usr) IS NULL) OR
         (org IS NOT NULL AND COALESCE(ws,usr) IS NULL) OR
@@ -647,5 +644,8 @@ CREATE TABLE actor.toolbar (
     ),
     CONSTRAINT layout_must_be_json CHECK ( is_json(layout) )
 );
+CREATE UNIQUE INDEX label_once_per_ws ON actor.toolbar (ws, label) WHERE ws IS NOT NULL;
+CREATE UNIQUE INDEX label_once_per_org ON actor.toolbar (org, label) WHERE org IS NOT NULL;
+CREATE UNIQUE INDEX label_once_per_usr ON actor.toolbar (usr, label) WHERE usr IS NOT NULL;
 
 COMMIT;
diff --git a/Open-ILS/src/sql/Pg/950.data.seed-values.sql b/Open-ILS/src/sql/Pg/950.data.seed-values.sql
index 953c02e..b9ebb7e 100644
--- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql
+++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql
@@ -1533,8 +1533,9 @@ INSERT INTO permission.perm_list ( id, code, description ) VALUES
  ( 521, 'IMPORT_ACQ_LINEITEM_BIB_RECORD_UPLOAD', oils_i18n_gettext( 521,
     'Allows a user to create new bibs directly from an ACQ MARC file upload', 'ppl', 'description' )),
  ( 522, 'IMPORT_AUTHORITY_MARC', oils_i18n_gettext( 522,
-    'Allows a user to create new authority records', 'ppl', 'description' ));
-
+    'Allows a user to create new authority records', 'ppl', 'description' )),
+ ( 523, 'ADMIN_TOOLBAR', oils_i18n_gettext( 523,
+    'Allows a user to create, edit, and delete custom toolbars', 'ppl', 'description' ));
 
 SELECT SETVAL('permission.perm_list_id_seq'::TEXT, 1000);
 
@@ -1743,6 +1744,7 @@ INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
 			'RENEW_HOLD_OVERRIDE',
 			'UPDATE_COPY',
 			'UPDATE_VOLUME',
+			'ADMIN_TOOLBAR',
 			'VOLUME_HOLDS');
 
 
@@ -11509,3 +11511,6 @@ INSERT INTO action_trigger.environment (event_def, path)
         'target_copy.call_number'
     );
 
+INSERT INTO actor.toolbar(org,label,layout) VALUES
+    ( 1, 'circ', '["circ_checkout","circ_checkin","toolbarseparator","search_opac","copy_status","toolbarseparator","patron_search","patron_register","toolbarspacer","hotkeys_toggle"]' ),
+    ( 1, 'cat', '["circ_checkin","toolbarseparator","search_opac","copy_status","toolbarseparator","create_marc","authority_manage","retrieve_last_record","toolbarspacer","hotkeys_toggle"]' );
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.custom_toolbars.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.custom_toolbars.sql
index ae94191..386f052 100644
--- a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.custom_toolbars.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.custom_toolbars.sql
@@ -6,9 +6,6 @@ CREATE TABLE actor.toolbar (
     usr         INT         REFERENCES actor.usr (id) ON DELETE CASCADE,
     label       TEXT        NOT NULL,
     layout      TEXT        NOT NULL,
-       CONSTRAINT label_once_per_ws UNIQUE (ws, label) WHERE ws IS NOT NULL,
-       CONSTRAINT label_once_per_org UNIQUE (org, label) WHERE org IS NOT NULL,
-       CONSTRAINT label_once_per_usr UNIQUE (usr, label) WHERE usr IS NOT NULL,
     CONSTRAINT only_one_type CHECK (
         (ws IS NOT NULL AND COALESCE(org,usr) IS NULL) OR
         (org IS NOT NULL AND COALESCE(ws,usr) IS NULL) OR
@@ -16,4 +13,44 @@ CREATE TABLE actor.toolbar (
     ),
     CONSTRAINT layout_must_be_json CHECK ( is_json(layout) )
 );
+CREATE UNIQUE INDEX label_once_per_ws ON actor.toolbar (ws, label) WHERE ws IS NOT NULL;
+CREATE UNIQUE INDEX label_once_per_org ON actor.toolbar (org, label) WHERE org IS NOT NULL;
+CREATE UNIQUE INDEX label_once_per_usr ON actor.toolbar (usr, label) WHERE usr IS NOT NULL;
 
+-- this one unrelated to toolbars but is a gap in the upgrade scripts
+INSERT INTO permission.perm_list ( id, code, description )
+    SELECT
+        522,
+        'IMPORT_AUTHORITY_MARC',
+        oils_i18n_gettext(
+            522,
+            'Allows a user to create new authority records',
+            'ppl',
+            'description'
+        )
+    WHERE NOT EXISTS (
+        SELECT 1
+        FROM permission.perm_list
+        WHERE
+            id = 522
+    );
+
+INSERT INTO permission.perm_list ( id, code, description ) VALUES (
+    523,
+    'ADMIN_TOOLBAR',
+    oils_i18n_gettext(
+        523,
+        'Allows a user to create, edit, and delete custom toolbars',
+        'ppl',
+        'description'
+    )
+);
+
+-- Don't want to assume stock perm groups in an upgrade script, but here for ease of testing
+-- INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable) SELECT pgt.id, perm.id, aout.depth, FALSE FROM permission.grp_tree pgt, permission.perm_list perm, actor.org_unit_type aout WHERE pgt.name = 'Staff' AND aout.name = 'Branch' AND perm.code = 'ADMIN_TOOLBAR';
+
+INSERT INTO actor.toolbar(org,label,layout) VALUES
+    ( 1, 'circ', '["circ_checkout","circ_checkin","toolbarseparator","search_opac","copy_status","toolbarseparator","patron_search","patron_register","toolbarspacer","hotkeys_toggle"]' ),
+    ( 1, 'cat', '["circ_checkin","toolbarseparator","search_opac","copy_status","toolbarseparator","create_marc","authority_manage","retrieve_last_record","toolbarspacer","hotkeys_toggle"]' );
+
+-- delete from permission.grp_perm_map where perm in (select id from permission.perm_list where code ~ 'TOOLBAR'); delete from permission.perm_list where code ~ 'TOOLBAR'; drop table actor.toolbar ;

commit 60ec86d28c32ac71c1092dd7491bdbc09a36e18c
Author: Mike Rylander <mrylander at gmail.com>
Date:   Tue Mar 20 12:28:58 2012 -0400

    Beginning an unwrapped upgrade script
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Thomas Berezansky <tsbere at mvlc.org>

diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.custom_toolbars.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.custom_toolbars.sql
new file mode 100644
index 0000000..ae94191
--- /dev/null
+++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.custom_toolbars.sql
@@ -0,0 +1,19 @@
+
+CREATE TABLE actor.toolbar (
+    id          BIGSERIAL   PRIMARY KEY,
+    ws          INT         REFERENCES actor.workstation (id) ON DELETE CASCADE,
+    org         INT         REFERENCES actor.org_unit (id) ON DELETE CASCADE,
+    usr         INT         REFERENCES actor.usr (id) ON DELETE CASCADE,
+    label       TEXT        NOT NULL,
+    layout      TEXT        NOT NULL,
+       CONSTRAINT label_once_per_ws UNIQUE (ws, label) WHERE ws IS NOT NULL,
+       CONSTRAINT label_once_per_org UNIQUE (org, label) WHERE org IS NOT NULL,
+       CONSTRAINT label_once_per_usr UNIQUE (usr, label) WHERE usr IS NOT NULL,
+    CONSTRAINT only_one_type CHECK (
+        (ws IS NOT NULL AND COALESCE(org,usr) IS NULL) OR
+        (org IS NOT NULL AND COALESCE(ws,usr) IS NULL) OR
+        (usr IS NOT NULL AND COALESCE(org,ws) IS NULL)
+    ),
+    CONSTRAINT layout_must_be_json CHECK ( is_json(layout) )
+);
+

commit 85480a7198ec50b0c32fd434c0d59feafa0a013d
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Mar 19 15:58:51 2012 -0400

    DB layout for recording custom toolbars
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Thomas Berezansky <tsbere at mvlc.org>

diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml
index 21236c3..28f8ee3 100644
--- a/Open-ILS/examples/fm_IDL.xml
+++ b/Open-ILS/examples/fm_IDL.xml
@@ -1095,10 +1095,12 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 			<field reporter:label="Workstation ID" name="id" reporter:datatype="id"/>
 			<field reporter:label="Workstation Name" name="name" reporter:datatype="text"/>
 			<field reporter:label="Owning Library" name="owning_lib"  reporter:datatype="org_unit"/>
+			<field reporter:label="Toolbars" name="toolbars" oils_persist:virtual="true" reporter:datatype="link"/>
 			<field reporter:label="Circulations" name="circulations" oils_persist:virtual="true" reporter:datatype="link"/>
 		</fields>
 		<links>
 			<link field="owning_lib" reltype="has_a" key="id" map="" class="aou"/>
+			<link field="toolbars" reltype="has_many" key="ws" map="" class="atb"/>
 			<link field="circulations" reltype="has_many" key="workstation" map="" class="circ"/>
 		</links>
 	</class>
@@ -2777,6 +2779,41 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 			</actions>
 		</permacrud>
 	</class>
+	<class id="atb" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="actor::toolbar" oils_persist:tablename="actor.toolbar" reporter:label="Custom Toolbar">
+		<fields oils_persist:primary="id" oils_persist:sequence="actor.toolbar_id_seq">
+			<field name="id" reporter:label="ID" reporter:datatype="id" />
+			<field name="usr" reporter:label="Owning User" reporter:datatype="link" />
+			<field name="org" reporter:label="Owning Org Unit" reporter:datatype="link" />
+			<field name="ws" reporter:label="Owning Workstation" reporter:datatype="link" />
+			<field name="label" reporter:label="Label" reporter:datatype="text" oils_persist:i18n="true" />
+			<field name="layout" reporter:label="Layout" reporter:datatype="text" />
+        </fields>
+        <links>
+			<link field="usr" reltype="might_have" key="id" map="" class="au"/>
+			<link field="org" reltype="might_have" key="id" map="" class="aou"/>
+			<link field="ws" reltype="might_have" key="id" map="" class="aws"/>
+		</links>
+		<permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+			<actions>
+				<retrieve permission="ADMIN_TOOLBAR VIEW_TOOLBAR" context_field="org">
+					<context link="usr" field="home_ou" />
+					<context link="ws" field="owning_lib" />
+				</retrieve>
+				<create permission="ADMIN_TOOLBAR" context_field="org">
+					<context link="usr" field="home_ou" />
+					<context link="ws" field="owning_lib" />
+				</create>
+				<update permission="ADMIN_TOOLBAR" context_field="org">
+					<context link="usr" field="home_ou" />
+					<context link="ws" field="owning_lib" />
+				</update>
+				<delete permission="ADMIN_TOOLBAR" context_field="org">
+					<context link="usr" field="home_ou" />
+					<context link="ws" field="owning_lib" />
+				</delete>
+			</actions>
+		</permacrud>
+	</class>
 	<class id="csg" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="config::settings_group" oils_persist:tablename="config.settings_group" reporter:label="Settings Group">
 		<fields oils_persist:primary="name">
 			<field name="name" reporter:datatype="text"/>
diff --git a/Open-ILS/src/sql/Pg/005.schema.actors.sql b/Open-ILS/src/sql/Pg/005.schema.actors.sql
index 9c219e0..4fead81 100644
--- a/Open-ILS/src/sql/Pg/005.schema.actors.sql
+++ b/Open-ILS/src/sql/Pg/005.schema.actors.sql
@@ -630,4 +630,22 @@ CREATE TABLE actor.usr_activity (
     event_time  TIMESTAMPTZ NOT NULL DEFAULT NOW()
 );
 
+CREATE TABLE actor.toolbar (
+    id          BIGSERIAL   PRIMARY KEY,
+    ws          INT         REFERENCES actor.workstation (id) ON DELETE CASCADE,
+    org         INT         REFERENCES actor.org_unit (id) ON DELETE CASCADE,
+    usr         INT         REFERENCES actor.usr (id) ON DELETE CASCADE,
+    label       TEXT        NOT NULL,
+    layout      TEXT        NOT NULL,
+	CONSTRAINT label_once_per_ws UNIQUE (ws, label) WHERE ws IS NOT NULL,
+	CONSTRAINT label_once_per_org UNIQUE (org, label) WHERE org IS NOT NULL,
+	CONSTRAINT label_once_per_usr UNIQUE (usr, label) WHERE usr IS NOT NULL,
+    CONSTRAINT only_one_type CHECK (
+        (ws IS NOT NULL AND COALESCE(org,usr) IS NULL) OR
+        (org IS NOT NULL AND COALESCE(ws,usr) IS NULL) OR
+        (usr IS NOT NULL AND COALESCE(org,ws) IS NULL)
+    ),
+    CONSTRAINT layout_must_be_json CHECK ( is_json(layout) )
+);
+
 COMMIT;

commit 0e06c711fe06322a44f8a019d63027c92201463a
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Thu Mar 22 00:12:34 2012 -0400

    utility functions for Javascript Shell
    
    win_list() returns an array of "eg_main" chrome windows
    
    get_tab() takes two forms:
    
        get_tab(chrome_window,tab_index)
        get_tab(tab_index)
    
    The latter invocation assumes the first chrome window returned by win_list().
    
    get_tab returns an object with the keys 'name' and 'content', pointing to the
    tab label and the tab panel -> iframe -> contentWindow, respectively.
    
    So let's say you had a patron account open in tab 1, and the Javascript Shell
    open in tab 2.  In the shell, you could do:
    
        var o = get_tab(1);
    
    o.name might contain something like
    
        1 Patron: Circulator, Ima
    
    and you could do this to refresh that interface:
    
        o.content.g.patron.refresh_all()
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Thomas Berezansky <tsbere at mvlc.org>

diff --git a/Open-ILS/xul/staff_client/chrome/content/util/shell.js b/Open-ILS/xul/staff_client/chrome/content/util/shell.js
index eadc228..7dda6e3 100644
--- a/Open-ILS/xul/staff_client/chrome/content/util/shell.js
+++ b/Open-ILS/xul/staff_client/chrome/content/util/shell.js
@@ -9,6 +9,38 @@ _out,
 tooManyMatches = null,
 lastError = null;
 
+function win_list() {
+    var list = [];
+    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+    var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].
+        getService(Components.interfaces.nsIWindowMediator);
+    var enumerator = wm.getEnumerator('eg_menu');
+    while(enumerator.hasMoreElements()) {
+        targetwindow = enumerator.getNext();
+        list.push(targetwindow);
+    }
+    return list;
+}
+
+function get_tab(a,b) {
+
+    var win;
+    var idx;
+
+    if (typeof b == 'undefined') {
+        idx = a;
+        win = win_list()[0];
+    } else {
+        win = a;
+        idx = b;
+    }
+
+    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+    var tabs = win.document.getElementById('main_tabs');
+    var panels = win.document.getElementById('main_panels');
+    return { 'name' : tabs.childNodes[idx].getAttribute('label'), 'content' : panels.childNodes[idx].firstChild.contentWindow };
+}
+
 function refocus()
 {
   _in.blur(); // Needed for Mozilla to scroll correctly.

commit 4ec1d1489bc6978354dbb0cbd2c2478d4559f077
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Thu Mar 22 01:54:20 2012 -0400

    Fix ordinal column with multiple lists
    
    Before this fix, if more than one util.list powered list existed within a given
    document/window, they would each intefere with the other's ordinal column.
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Thomas Berezansky <tsbere at mvlc.org>

diff --git a/Open-ILS/xul/staff_client/chrome/content/util/list.js b/Open-ILS/xul/staff_client/chrome/content/util/list.js
index 6dcc359..ef4c96f 100644
--- a/Open-ILS/xul/staff_client/chrome/content/util/list.js
+++ b/Open-ILS/xul/staff_client/chrome/content/util/list.js
@@ -803,7 +803,7 @@ util.list.prototype = {
         try {
             setTimeout( // Otherwise we can miss a row just added
                 function() {
-                    var nl = document.getElementsByAttribute('label','_');
+                    var nl = obj.node.getElementsByAttribute('label','_');
                     for (var i = 0; i < nl.length; i++) {
                         nl[i].setAttribute(
                             'ord_col',
@@ -814,7 +814,7 @@ util.list.prototype = {
                             'ordinal'
                         );
                     }
-                    nl = document.getElementsByAttribute('ord_col','true');
+                    nl = obj.node.getElementsByAttribute('ord_col','true');
                     for (var i = 0; i < nl.length; i++) {
                         nl[i].setAttribute(
                             'label',

commit b06ba3b8a2ab96ba22d7896f50691156e50a9e15
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Wed Mar 21 23:05:52 2012 -0400

    more workstation related options for ses()
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Thomas Berezansky <tsbere at mvlc.org>

diff --git a/Open-ILS/xul/staff_client/chrome/content/OpenILS/global_util.js b/Open-ILS/xul/staff_client/chrome/content/OpenILS/global_util.js
index 58d6484..6a85fdd 100644
--- a/Open-ILS/xul/staff_client/chrome/content/OpenILS/global_util.js
+++ b/Open-ILS/xul/staff_client/chrome/content/OpenILS/global_util.js
@@ -142,6 +142,12 @@
                 case 'staff' : return data.list.au[0]; break;
                 case 'staff_id' : return data.list.au[0].id(); break;
                 case 'staff_usrname' : return data.list.au[0].usrname(); break;
+                case 'ws_name':
+                    return data.ws_name;
+                break;
+                case 'ws_id' :
+                    return data.list.au[0].wsid();
+                break;
                 case 'ws_ou' :
                     return data.list.au[0].ws_ou();
                 break;

-----------------------------------------------------------------------

Summary of changes:
 Open-ILS/examples/fm_IDL.xml                       |   37 ++
 Open-ILS/src/sql/Pg/002.schema.config.sql          |    2 +-
 Open-ILS/src/sql/Pg/005.schema.actors.sql          |   18 +
 Open-ILS/src/sql/Pg/950.data.seed-values.sql       |    9 +-
 .../sql/Pg/upgrade/0695.schema.custom_toolbars.sql |   68 +++
 .../sql/Pg/upgrade/XXXX.schema.custom_toolbars.sql |   56 ++
 Open-ILS/web/opac/locale/en-US/lang.dtd            |   25 +-
 .../staff_client/chrome/content/OpenILS/data.js    |   34 ++
 .../chrome/content/OpenILS/global_util.js          |    6 +
 .../staff_client/chrome/content/main/constants.js  |    2 +
 .../xul/staff_client/chrome/content/main/menu.js   |  283 +++++++---
 .../chrome/content/main/menu_frame_menus.xul       |    6 +-
 .../chrome/content/main/menu_frame_overlay.xul     |  342 +++++++-----
 .../xul/staff_client/chrome/content/util/list.js   |    4 +-
 .../xul/staff_client/chrome/content/util/shell.js  |   32 +
 .../chrome/locale/en-US/offline.properties         |    1 +
 Open-ILS/xul/staff_client/server/admin/toolbar.js  |  616 ++++++++++++++++++++
 Open-ILS/xul/staff_client/server/admin/toolbar.xul |  109 ++++
 .../server/locale/en-US/admin.properties           |    6 +
 19 files changed, 1436 insertions(+), 220 deletions(-)
 create mode 100644 Open-ILS/src/sql/Pg/upgrade/0695.schema.custom_toolbars.sql
 create mode 100644 Open-ILS/src/sql/Pg/upgrade/XXXX.schema.custom_toolbars.sql
 create mode 100644 Open-ILS/xul/staff_client/server/admin/toolbar.js
 create mode 100644 Open-ILS/xul/staff_client/server/admin/toolbar.xul


hooks/post-receive
-- 
Evergreen ILS


More information about the open-ils-commits mailing list