[open-ils-commits] r18265 - in branches/rel_1_6/Open-ILS: examples src/perlmods/OpenILS/Application src/perlmods/OpenILS/Application/Actor src/perlmods/OpenILS/WWW src/sql/Pg src/sql/Pg/upgrade web/css/theme web/js/dojo web/js/dojo/MARC web/js/dojo/fieldmapper web/js/dojo/openils web/js/ui web/templates (miker)

svn at svn.open-ils.org svn at svn.open-ils.org
Mon Oct 11 13:25:22 EDT 2010


Author: miker
Date: 2010-10-11 13:25:19 -0400 (Mon, 11 Oct 2010)
New Revision: 18265

Added:
   branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/WWW/TemplateBatchBibUpdate.pm
   branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0434.data.merge_template_container_type.sql
   branches/rel_1_6/Open-ILS/web/js/dojo/MARC/
   branches/rel_1_6/Open-ILS/web/js/dojo/MARC/Batch.js
   branches/rel_1_6/Open-ILS/web/js/dojo/MARC/Field.js
   branches/rel_1_6/Open-ILS/web/js/dojo/MARC/Record.js
Modified:
   branches/rel_1_6/Open-ILS/examples/opensrf.xml.example
   branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Actor/Container.pm
   branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Cat.pm
   branches/rel_1_6/Open-ILS/src/sql/Pg/950.data.seed-values.sql
   branches/rel_1_6/Open-ILS/web/css/theme/default.css
   branches/rel_1_6/Open-ILS/web/js/dojo/fieldmapper/AutoIDL.js
   branches/rel_1_6/Open-ILS/web/js/dojo/fieldmapper/Fieldmapper.js
   branches/rel_1_6/Open-ILS/web/js/dojo/fieldmapper/IDL.js
   branches/rel_1_6/Open-ILS/web/js/dojo/fieldmapper/dojoData.js
   branches/rel_1_6/Open-ILS/web/js/dojo/fieldmapper/hash.js
   branches/rel_1_6/Open-ILS/web/js/dojo/openils/User.js
   branches/rel_1_6/Open-ILS/web/js/ui/base.js
   branches/rel_1_6/Open-ILS/web/templates/login.tt2
Log:
Backporting batch update functionality for 1.6.2, along with some deps

Modified: branches/rel_1_6/Open-ILS/examples/opensrf.xml.example
===================================================================
--- branches/rel_1_6/Open-ILS/examples/opensrf.xml.example	2010-10-11 16:07:47 UTC (rev 18264)
+++ branches/rel_1_6/Open-ILS/examples/opensrf.xml.example	2010-10-11 17:25:19 UTC (rev 18265)
@@ -283,6 +283,15 @@
                 </servers>
                 <max_cache_time>86400</max_cache_time>
             </global>
+            <anon>
+                <!-- anonymous cache.  currently, primarily used for web session caching -->
+                <servers>
+                    <server>localhost:11211</server>
+                </servers>
+                <max_cache_time>1800</max_cache_time>
+                <!-- maximum size of a single cache entry / default = 100k-->
+                <max_cache_size>102400</max_cache_size>
+            </anon>
         </cache>
 
         <apps>

Modified: branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Actor/Container.pm
===================================================================
--- branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Actor/Container.pm	2010-10-11 16:07:47 UTC (rev 18264)
+++ branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Actor/Container.pm	2010-10-11 17:25:19 UTC (rev 18265)
@@ -7,6 +7,10 @@
 use OpenSRF::EX qw(:try);
 use OpenILS::Utils::Fieldmapper;
 use OpenILS::Utils::CStoreEditor qw/:funcs/;
+use OpenSRF::Utils::SettingsClient;
+use OpenSRF::Utils::Cache;
+use Digest::MD5 qw(md5_hex);
+use OpenSRF::Utils::JSON;
 
 my $apputils = "OpenILS::Application::AppUtils";
 my $U = $apputils;
@@ -506,7 +510,101 @@
 
 
 
+__PACKAGE__->register_method(
+	method	=> "anon_cache",
+	api_name	=> "open-ils.actor.anon_cache.set_value",
+    signature => {
+        desc => q/
+            Sets a value in the anon web cache.  If the session key is
+            undefined, one will be automatically generated.
+        /,
+        params => [
+		    {desc => 'Session key', type => 'string'},
+            {
+                desc => q/Field name.  The name of the field in this cache session whose value to set/, 
+                type => 'string'
+            },
+            {
+                desc => q/The cached value.  This can be any type of object (hash, array, string, etc.)/,
+                type => 'any'
+            },
+        ],
+        return => {
+            desc => 'session key on success, undef on error',
+            type => 'string'
+        }
+    }
+);
 
+__PACKAGE__->register_method(
+	method	=> "anon_cache",
+	api_name	=> "open-ils.actor.anon_cache.get_value",
+    signature => {
+        desc => q/
+            Returns the cached data at the specified field within the specified cache session.
+        /,
+        params => [
+		    {desc => 'Session key', type => 'string'},
+            {
+                desc => q/Field name.  The name of the field in this cache session whose value to set/, 
+                type => 'string'
+            },
+        ],
+        return => {
+            desc => 'cached value on success, undef on error',
+            type => 'any'
+        }
+    }
+);
+
+__PACKAGE__->register_method(
+	method	=> "anon_cache",
+	api_name	=> "open-ils.actor.anon_cache.delete_session",
+    signature => {
+        desc => q/
+            Deletes a cache session.
+        /,
+        params => [
+		    {desc => 'Session key', type => 'string'},
+        ],
+        return => {
+            desc => 'Session key',
+            type => 'string'
+        }
+    }
+);
+
+sub anon_cache {
+    my($self, $conn, $ses_key, $field_key, $value) = @_;
+
+    my $sc = OpenSRF::Utils::SettingsClient->new;
+	my $cache = OpenSRF::Utils::Cache->new('anon');
+    my $cache_timeout = $sc->config_value(cache => anon => 'max_cache_time') || 1800; # 30 minutes
+    my $cache_size = $sc->config_value(cache => anon => 'max_cache_size') || 102400; # 100k
+
+    if($self->api_name =~ /delete_session/) {
+
+       return $cache->delete_cache($ses_key); 
+
+    }  elsif( $self->api_name =~ /set_value/ ) {
+
+        $ses_key = md5_hex(time . rand($$)) unless $ses_key;
+        my $blob = $cache->get_cache($ses_key) || {};
+        $blob->{$field_key} = $value;
+        return undef if 
+            length(OpenSRF::Utils::JSON->perl2JSON($blob)) > $cache_size; # bytes, characters, whatever ;)
+        $cache->put_cache($ses_key, $blob, $cache_timeout);
+        return $ses_key;
+
+    } else {
+
+        my $blob = $cache->get_cache($ses_key) or return undef;
+        return $blob->{$field_key};
+    }
+}
+
+
+
 1;
 
 

Modified: branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Cat.pm
===================================================================
--- branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Cat.pm	2010-10-11 16:07:47 UTC (rev 18264)
+++ branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/Application/Cat.pm	2010-10-11 17:25:19 UTC (rev 18265)
@@ -175,6 +175,156 @@
 }
 
 __PACKAGE__->register_method(
+	method	=> "template_overlay_biblio_record_entry",
+	api_name	=> "open-ils.cat.biblio.record_entry.template_overlay",
+    stream  => 1,
+    signature => q#
+        Overlays biblio.record_entry MARC values
+        @param auth The authtoken
+        @param records The record ids to be updated by the template
+        @param template The overlay template
+        @return Stream of hashes record id in the key "record" and t or f for the success of the overlay operation in key "success"
+    #
+);
+
+sub template_overlay_biblio_record_entry {
+    my($self, $conn, $auth, $records, $template) = @_;
+    my $e = new_editor(authtoken=>$auth, xact=>1);
+    return $e->die_event unless $e->checkauth;
+
+    $records = [$records] if (!ref($records));
+
+    for my $rid ( @$records ) {
+        my $rec = $e->retrieve_biblio_record_entry($rid);
+        next unless $rec;
+
+        unless ($e->allowed('UPDATE_RECORD', $rec->owner, $rec)) {
+            $conn->respond({ record => $rid, success => 'f' });
+            next;
+        }
+
+        my $success = $e->json_query(
+            { from => [ 'vandelay.template_overlay_bib_record', $template, $rid ] }
+        )->[0]->{'vandelay.template_overlay_bib_record'};
+
+        $conn->respond({ record => $rid, success => $success });
+    }
+
+    $e->commit;
+    return undef;
+}
+
+__PACKAGE__->register_method(
+	method	=> "template_overlay_container",
+	api_name	=> "open-ils.cat.container.template_overlay",
+    stream  => 1,
+    signature => q#
+        Overlays biblio.record_entry MARC values
+        @param auth The authtoken
+        @param container The container, um, containing the records to be updated by the template
+        @param template The overlay template, or nothing and the method will look for a negative bib id in the container
+        @return Stream of hashes record id in the key "record" and t or f for the success of the overlay operation in key "success"
+    #
+);
+
+__PACKAGE__->register_method(
+	method	=> "template_overlay_container",
+	api_name	=> "open-ils.cat.container.template_overlay.background",
+    stream  => 1,
+    signature => q#
+        Overlays biblio.record_entry MARC values
+        @param auth The authtoken
+        @param container The container, um, containing the records to be updated by the template
+        @param template The overlay template, or nothing and the method will look for a negative bib id in the container
+        @return Cache key to check for status of the container overlay
+    #
+);
+
+sub template_overlay_container {
+    my($self, $conn, $auth, $container, $template) = @_;
+    my $e = new_editor(authtoken=>$auth, xact=>1);
+    return $e->die_event unless $e->checkauth;
+
+    my $actor = OpenSRF::AppSession->create('open-ils.actor') if ($self->api_name =~ /background$/);
+
+    my $items = $e->search_container_biblio_record_entry_bucket_item({ bucket => $container });
+
+    my $titem;
+    if (!$template) {
+        ($titem) = grep { $_->target_biblio_record_entry < 0 } @$items;
+        if (!$titem) {
+            $e->rollback;
+            return undef;
+        }
+        $items = [grep { $_->target_biblio_record_entry > 0 } @$items];
+
+        $template = $e->retrieve_biblio_record_entry( $titem->target_biblio_record_entry )->marc;
+    }
+
+    my $responses = [];
+    my $some_failed = 0;
+
+    $self->respond_complete(
+        $actor->request('open-ils.actor.anon_cache.set_value', $auth, res_list => $responses)->gather(1)
+    ) if ($actor);
+
+    for my $item ( @$items ) {
+        my $rec = $e->retrieve_biblio_record_entry($item->target_biblio_record_entry);
+        next unless $rec;
+
+        my $success = 'f';
+        if ($e->allowed('UPDATE_RECORD', $rec->owner, $rec)) {
+            $success = $e->json_query(
+                { from => [ 'vandelay.template_overlay_bib_record', $template, $rec->id ] }
+            )->[0]->{'vandelay.template_overlay_bib_record'};
+        }
+
+        $some_failed++ if ($success eq 'f');
+
+        if ($actor) {
+            push @$responses, { record => $rec->id, success => $success };
+            $actor->request('open-ils.actor.anon_cache.set_value', $auth, res_list => $responses);
+        } else {
+            $conn->respond({ record => $rec->id, success => $success });
+        }
+
+        if ($success eq 't') {
+            unless ($e->delete_container_biblio_record_entry_bucket_item($item)) {
+                $e->rollback;
+                if ($actor) {
+                    push @$responses, { complete => 1, success => 'f' };
+                    $actor->request('open-ils.actor.anon_cache.set_value', $auth, res_list => $responses);
+                    return undef;
+                } else {
+                    return { complete => 1, success => 'f' };
+                }
+            }
+        }
+    }
+
+    if ($titem && !$some_failed) {
+        return $e->die_event unless ($e->delete_container_biblio_record_entry_bucket_item($titem));
+    }
+
+    if ($e->commit) {
+        if ($actor) {
+            push @$responses, { complete => 1, success => 't' };
+            $actor->request('open-ils.actor.anon_cache.set_value', $auth, res_list => $responses);
+        } else {
+            return { complete => 1, success => 't' };
+        }
+    } else {
+        if ($actor) {
+            push @$responses, { complete => 1, success => 'f' };
+            $actor->request('open-ils.actor.anon_cache.set_value', $auth, res_list => $responses);
+        } else {
+            return { complete => 1, success => 'f' };
+        }
+    }
+    return undef;
+}
+
+__PACKAGE__->register_method(
 	method	=> "update_biblio_record_entry",
 	api_name	=> "open-ils.cat.biblio.record_entry.update",
     signature => q/

Added: branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/WWW/TemplateBatchBibUpdate.pm
===================================================================
--- branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/WWW/TemplateBatchBibUpdate.pm	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/src/perlmods/OpenILS/WWW/TemplateBatchBibUpdate.pm	2010-10-11 17:25:19 UTC (rev 18265)
@@ -0,0 +1,613 @@
+package OpenILS::WWW::TemplateBatchBibUpdate;
+use strict;
+use warnings;
+use bytes;
+
+use Apache2::Log;
+use Apache2::Const -compile => qw(OK REDIRECT DECLINED NOT_FOUND :log);
+use APR::Const    -compile => qw(:error SUCCESS);
+use APR::Table;
+
+use Apache2::RequestRec ();
+use Apache2::RequestIO ();
+use Apache2::RequestUtil;
+use CGI;
+use Data::Dumper;
+use Text::CSV;
+
+use OpenSRF::EX qw(:try);
+use OpenSRF::Utils qw/:datetime/;
+use OpenSRF::Utils::Cache;
+use OpenSRF::System;
+use OpenSRF::AppSession;
+use XML::LibXML;
+use XML::LibXSLT;
+
+use Encode;
+use Unicode::Normalize;
+use OpenILS::Utils::Fieldmapper;
+use OpenSRF::Utils::Logger qw/$logger/;
+
+use MARC::Record;
+use MARC::File::XML;
+
+use UNIVERSAL::require;
+
+our @formats = qw/USMARC UNIMARC XML BRE/;
+
+# set the bootstrap config and template include directory when
+# this module is loaded
+my $bootstrap;
+
+sub import {
+    my $self = shift;
+    $bootstrap = shift;
+}
+
+
+sub child_init {
+    OpenSRF::System->bootstrap_client( config_file => $bootstrap );
+    Fieldmapper->import(IDL => OpenSRF::Utils::SettingsClient->new->config_value("IDL"));
+}
+
+sub handler {
+    my $r = shift;
+    my $cgi = new CGI;
+
+    my $authid = $cgi->cookie('ses') || $cgi->param('ses');
+    my $usr = verify_login($authid);
+    return show_template($r) unless ($usr);
+
+
+    my $template = $cgi->param('template');
+    return show_template($r) unless ($template);
+
+    # find some IDs ...
+    my @records;
+
+    @records = map { $_ ? ($_) : () } $cgi->param('recid');
+
+    if (!@records) { # try for a file
+        my $file = $cgi->param('idfile');
+        if ($file) {
+            my $col = $cgi->param('idcolumn') || 0;
+            my $csv = new Text::CSV;
+
+            while (<$file>) {
+                $csv->parse($_);
+                my @data = $csv->fields;
+                my $id = $data[$col];
+                $id =~ s/\D+//o;
+                next unless ($id);
+                push @records, $id;
+            }
+        }
+    }
+
+    my $e = OpenSRF::AppSession->connect('open-ils.cstore');
+    $e->request('open-ils.cstore.transaction.begin')->gather(1);
+
+    # still no records ...
+    my $container = $cgi->param('containerid');
+    if ($container) {
+        my $bucket = $e->request(
+            'open-ils.cstore.direct.container.biblio_record_entry_bucket.retrieve',
+            $container
+        )->gather(1);
+        unless($bucket) {
+            $e->request('open-ils.cstore.transaction.rollback')->gather(1);
+            $e->disconnect;
+            $r->log->error("No such bucket $container");
+            $logger->error("No such bucket $container");
+            return Apache2::Const::NOT_FOUND;
+        }
+        my $recs = $e->request(
+            'open-ils.cstore.direct.container.biblio_record_entry_bucket_item.search.atomic',
+            { bucket => $container }
+        )->gather(1);
+        @records = map { ($_->target_biblio_record_entry) } @$recs;
+    }
+
+    unless (@records) {
+        $e->request('open-ils.cstore.transaction.rollback')->gather(1);
+        $e->disconnect;
+        return show_template($r);
+    }
+
+    # we have a template and some record ids, so...
+
+    # insert the template record
+    my $min_id = $e->request(
+        'open-ils.cstore.json_query',
+        { select => { bre => [{ column => 'id', transform => 'min', aggregate => 1}] }, from => 'bre' }
+    )->gather(1)->{id} - 1;
+
+    warn "new template bib id = $min_id\n";
+
+    my $tmpl_rec = Fieldmapper::biblio::record_entry->new;
+    $tmpl_rec->id($min_id);
+    $tmpl_rec->deleted('t');
+    $tmpl_rec->active('f');
+    $tmpl_rec->marc($template);
+    $tmpl_rec->creator($usr->id);
+    $tmpl_rec->editor($usr->id);
+
+    warn "about to create bib $min_id\n";
+    $e->request('open-ils.cstore.direct.biblio.record_entry.create', $tmpl_rec )->gather(1);
+
+    # create the new container for the records and the template
+    my $bucket = Fieldmapper::container::biblio_record_entry_bucket->new;
+    $bucket->owner($usr->id);
+    $bucket->btype('template_merge');
+
+    my $bname = $cgi->param('bname') || 'Temporary Merge Bucket '. localtime() . ' ' . $usr->id;
+    $bucket->name($bname);
+
+    $bucket = $e->request('open-ils.cstore.direct.container.biblio_record_entry_bucket.create', $bucket )->gather(1);
+
+    # create items in the bucket
+    my $item = Fieldmapper::container::biblio_record_entry_bucket_item->new;
+    $item->bucket($bucket->id);
+    $item->target_biblio_record_entry($min_id);
+
+    $e->request('open-ils.cstore.direct.container.biblio_record_entry_bucket_item.create', $item )->gather(1);
+
+    for my $r (@records) {
+        $item->target_biblio_record_entry($r);
+        $e->request('open-ils.cstore.direct.container.biblio_record_entry_bucket_item.create', $item )->gather(1);
+    }
+
+    $e->request('open-ils.cstore.transaction.commit')->gather(1);
+    $e->disconnect;
+
+    # fire the background bucket processor
+    my $cache_key = OpenSRF::AppSession
+        ->create('open-ils.cat')
+        ->request('open-ils.cat.container.template_overlay.background', $authid, $bucket->id)
+        ->gather(1);
+
+    return show_processing_template($r, $bucket->id, \@records, $cache_key);
+}
+
+sub verify_login {
+        my $auth_token = shift;
+        return undef unless $auth_token;
+
+        my $user = OpenSRF::AppSession
+                ->create("open-ils.auth")
+                ->request( "open-ils.auth.session.retrieve", $auth_token )
+                ->gather(1);
+
+        if (ref($user) eq 'HASH' && $user->{ilsevent} == 1001) {
+                return undef;
+        }
+
+        return $user if ref($user);
+        return undef;
+}
+
+sub show_processing_template {
+    my $r = shift;
+    my $bid = shift;
+    my $recs = shift;
+    my $cache_key = shift;
+
+    my $rec_string = @$recs;
+
+    $r->content_type('text/html');
+    $r->print(<<HTML);
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+    <head>
+        <title>Merging records...</title>
+        <style type="text/css">
+            \@import '/js/dojo/dojo/resources/dojo.css';
+            \@import '/js/dojo/dijit/themes/tundra/tundra.css';
+            .hide_me { display: none; visibility: hidden; }
+            th       { font-weight: bold; }
+        </style>
+
+        <script type="text/javascript">
+            var djConfig= {
+                isDebug: false,
+                parseOnLoad: true,
+                AutoIDL: ['aou','aout','pgt','au','cbreb']
+            }
+        </script>
+
+        <script src='/js/dojo/dojo/dojo.js'></script>
+        <!-- <script src="/js/dojo/dojo/openils_dojo.js"></script> -->
+
+        <script type="text/javascript">
+
+            dojo.require('fieldmapper.AutoIDL');
+            dojo.require('fieldmapper.dojoData');
+            dojo.require('openils.User');
+            dojo.require('openils.CGI');
+            dojo.require('openils.widget.ProgressDialog');
+
+            var cgi = new openils.CGI();
+            var u = new openils.User({ authcookie : 'ses' });
+
+            dojo.addOnLoad(function () {
+                progress_dialog.show(true);
+                progress_dialog.update({maximum:$rec_string});
+
+                var interval;
+                interval = setInterval( function() {
+                    fieldmapper.standardRequest(
+                        ['open-ils.actor','open-ils.actor.anon_cache.get_value'],
+                        { async : false,
+                          params: [ u.authtoken, 'res_list' ],
+                          onerror : function (r) { progress_dialog.hide(); },
+                          onresponse : function (r) {
+                            var counter = { success : 0, fail : 0, total : 0 };
+                            dojo.forEach( openils.Util.readResponse(r), function(x) {
+                                if (x.complete) {
+                                    clearInterval(interval);
+                                    progress_dialog.hide();
+                                    if (x.success == 't') dojo.byId('complete_msg').innerHTML = 'Overlay completed successfully';
+                                    else dojo.byId('complete_msg').innerHTML = 'Overlay did not complet successfully';
+                                } else {
+                                    counter.total++;
+                                    switch (x.success) {
+                                        case 't':
+                                            counter.success++;
+                                            break;
+                                        default:
+                                            counter.fail++;
+                                            break;
+                                    }
+                                }
+                            });
+
+                            // update the progress dialog
+                            progress_dialog.update({progress:counter.total});
+                            dojo.byId('success_count').innerHTML = counter.success;
+                            dojo.byId('fail_count').innerHTML = counter.fail;
+                            dojo.byId('total_count').innerHTML = counter.total;
+                          }
+                        }
+                    );
+                }, 1000);
+
+            });
+        </script>
+    </head>
+
+    <body class='tundra'>
+        <div class="hide_me"><div dojoType="openils.widget.ProgressDialog" jsId="progress_dialog"></div></div>
+
+        <table style="width:100%; margin-top:100px;">
+            <th>
+                <td>Status</td>
+                <td>Record Count</td>
+            </th>
+            <tr>
+                <td>Success</td>
+                <td id='success_count'></td>
+            </tr>
+            <tr>
+                <td>Failure</td>
+                <td id='fail_count'></td>
+            </tr>
+            <tr>
+                <td></td>
+                <td id='total_count'></td>
+            </tr>
+        </table>
+
+        <div id='complete_msg'></div>
+
+    </body>
+</html>
+HTML
+
+    return Apache2::Const::OK;
+}
+
+
+sub show_template {
+    my $r = shift;
+
+    $r->content_type('text/html');
+    $r->print(<<'HTML');
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+    <head>
+        <title>Merge Template Builder</title>
+        <style type="text/css">
+            @import '/js/dojo/dojo/resources/dojo.css';
+            @import '/js/dojo/dijit/themes/tundra/tundra.css';
+            .hide_me { display: none; visibility: hidden; }
+            th       { font-weight: bold; }
+        </style>
+
+        <script type="text/javascript">
+            var djConfig= {
+                isDebug: false,
+                parseOnLoad: true,
+                AutoIDL: ['aou','aout','pgt','au','cbreb']
+            }
+        </script>
+
+        <script src='/js/dojo/dojo/dojo.js'></script>
+        <!-- <script src="/js/dojo/dojo/openils_dojo.js"></script> -->
+
+        <script type="text/javascript">
+
+            dojo.require('dojo.data.ItemFileReadStore');
+            dojo.require('dijit.form.Form');
+            dojo.require('dijit.form.NumberSpinner');
+            dojo.require('dijit.form.FilteringSelect');
+            dojo.require('dijit.form.TextBox');
+            dojo.require('dijit.form.Textarea');
+            dojo.require('dijit.form.Button');
+            dojo.require('MARC.Batch');
+            dojo.require('fieldmapper.AutoIDL');
+            dojo.require('fieldmapper.dojoData');
+            dojo.require('openils.User');
+            dojo.require('openils.CGI');
+
+            var cgi = new openils.CGI();
+            var u = new openils.User({ authcookie : 'ses' });
+
+            var bucketStore = new dojo.data.ItemFileReadStore(
+                { data : cbreb.toStoreData(
+                        fieldmapper.standardRequest(
+                            ['open-ils.actor','open-ils.actor.container.retrieve_by_class'],
+                            [u.authtoken, u.user.id(), 'biblio', 'staff_client']
+                        )
+                    )
+                }
+            );
+
+            function render_preview () {
+                var rec = ruleset_to_record();
+                dojo.byId('marcPreview').innerHTML = rec.toBreaker();
+            }
+
+            function render_from_template () {
+                var kid_number = dojo.byId('ruleList').childNodes.length;
+                var clone = dojo.query('*[name=ruleTable]', dojo.byId('ruleTemplate'))[0].cloneNode(true);
+
+                var typeSelect = dojo.query('*[name=typeSelect]',clone).instantiate(dijit.form.FilteringSelect, {
+                    onChange : function (val) {
+                        switch (val) {
+                            case 'a':
+                            case 'r':
+                                dijit.byNode(dojo.query('*[name=marcDataContainer] .dijit',clone)[0]).attr('disabled',false);
+                                break;
+                            default :
+                                dijit.byNode(dojo.query('*[name=marcDataContainer] .dijit',clone)[0]).attr('disabled',true);
+                        };
+                        render_preview();
+                    }
+                })[0];
+
+                var marcData = dojo.query('*[name=marcData]',clone).instantiate(dijit.form.TextBox, {
+                    onChange : render_preview
+                })[0];
+
+
+                var tag = dojo.query('*[name=tag]',clone).instantiate(dijit.form.TextBox, {
+                    onChange : function (newtag) {
+                        var md = dijit.byNode(dojo.query('*[name=marcDataContainer] .dijit',clone)[0]);
+                        var current_marc = md.attr('value');
+
+                        if (newtag.length == 3) {
+                            if (current_marc.length == 0) newtag += ' \\\\';
+                            if (current_marc.substr(0,3) != newtag) current_marc = newtag + current_marc.substr(3);
+                        }
+                        md.attr('value', current_marc);
+                        render_preview();
+                    }
+                })[0];
+
+                var sf = dojo.query('*[name=sf]',clone).instantiate(dijit.form.TextBox, {
+                    onChange : function (newsf) {
+                        var md = dijit.byNode(dojo.query('*[name=marcDataContainer] .dijit',clone)[0]);
+                        var current_marc = md.attr('value');
+                        var sf_list = newsf.split('');
+
+                        for (var i in sf_list) {
+                            var re = '\\$' + sf_list[i];
+                            if (current_marc.match(re)) continue;
+                            current_marc += '$' + sf_list[i];
+                        }
+
+                        md.attr('value', current_marc);
+                        render_preview();
+                    }
+                })[0];
+
+                var matchSF = dojo.query('*[name=matchSF]',clone).instantiate(dijit.form.TextBox, {
+                    onChange : render_preview
+                })[0];
+
+                var matchRE = dojo.query('*[name=matchRE]',clone).instantiate(dijit.form.TextBox, {
+                    onChange : render_preview
+                })[0];
+
+                var removeButton = dojo.query('*[name=removeButton]',clone).instantiate(dijit.form.Button, {
+                    onClick : function() {
+                        dojo.addClass(
+                            dojo.byId('ruleList').childNodes[kid_number],
+                            'hide_me'
+                        );
+                        render_preview();
+                    }
+                })[0];
+
+                dojo.place(clone,'ruleList');
+            }
+
+            function ruleset_to_record () {
+                var rec = new MARC.Record ({ delimiter : '$' });
+
+                dojo.forEach( 
+                    dojo.query('#ruleList *[name=ruleTable]').filter( function (node) {
+                        if (node.className.match(/hide_me/)) return false;
+                        return true;
+                    }),
+                    function (tbl) {
+                        var rule_tag = new MARC.Field ({
+                            tag : '905',
+                            ind1 : ' ',
+                            ind2 : ' '
+                        });
+                        var rule_txt = dijit.byNode(dojo.query('*[name=tagContainer] .dijit',tbl)[0]).attr('value');
+                        rule_txt += dijit.byNode(dojo.query('*[name=sfContainer] .dijit',tbl)[0]).attr('value');
+
+                        var reSF = dijit.byNode(dojo.query('*[name=matchSFContainer] .dijit',tbl)[0]).attr('value');
+                        if (reSF) {
+                            var reRE = dijit.byNode(dojo.query('*[name=matchREContainer] .dijit',tbl)[0]).attr('value');
+                            rule_txt += '[' + reSF + '~' + reRE + ']';
+                        }
+
+                        var rtype = dijit.byNode(dojo.query('*[name=typeSelectContainer] .dijit',tbl)[0]).attr('value');
+                        rule_tag.addSubfields( rtype, rule_txt )
+                        rec.appendFields( rule_tag );
+
+                        if (rtype == 'a' || rtype == 'r') {
+                            rec.appendFields(
+                                new MARC.Record ({
+                                    delimiter : '$',
+                                    marcbreaker : dijit.byNode(dojo.query('*[name=marcDataContainer] .dijit',tbl)[0]).attr('value')
+                                }).fields[0]
+                            );
+                        }
+                    }
+                );
+
+                return rec;
+            }
+        </script>
+    </head>
+
+    <body class='tundra'>
+
+        <div dojoType="dijit.form.Form" id="myForm" jsId="myForm" encType="multipart/form-data" action="" method="POST">
+                <script type='dojo/method' event='onSubmit'>
+                    var rec = ruleset_to_record();
+
+                    // no-op to force replace mode
+                    rec.appendFields(
+                        new MARC.Field ({
+                            tag : '905',
+                            ind1 : ' ',
+                            ind2 : ' ',
+                            subfields : [['r','901c']]
+                        })
+                    );
+
+                    dojo.byId('template_value').value = rec.toXmlString();
+                    return true;
+                </script>
+
+            <input type='hidden' id='template_value' name='template'/>
+
+            <table>
+                <tr>
+                    <th>Optional merge queue name:</th>
+                    <td><input id='bucketName' type='text' dojoType='dijit.form.TextBox' name='bname' value=''/></td>
+                </tr>
+                <tr>
+                    <th>Batch update records in Bucket:</th>
+                    <td>
+                        <div name='containerid' jsId='bucketList' dojoType='dijit.form.FilteringSelect' store='bucketStore' searchAttr='name' id='bucketList'>
+                            <script type='dojo/method' event='postCreate'>
+                                if (cgi.param('containerid')) this.attr('value',cgi.param('containerid'));
+                            </script>
+                        </div>
+                    </td>
+                </tr>
+                <tr><th colspan='2'><div style='text-align: center;'>or</div></hd></tr>
+                <tr>
+                    <th>Batch update records from CSV file:</th>
+                    <td><input id='idfile' type="file" name="idfile"/><br/>Column <input style='width:75px;' type='text' dojoType='dijit.form.NumberSpinner' name='idcolumn' value='0' constraints='{min:0,max:100,places:0}' /> starting from 0</td>
+                </tr>
+                <tr><th colspan='2'><div style='text-align: center;'>or</div></th></tr>
+                <tr>
+                    <th>Test Ruleset by applying to one record:</th>
+                    <td><input name='recid' style='width:75px;' type='text' dojoType='dijit.form.NumberTextBox' name='id' value='' constraints='{min:0}' /></td>
+                </tr>
+            </table>
+
+            <button type="submit" dojoType='dijit.form.Button'>Apply Ruleset</button>
+
+        </div> <!-- end of the form -->
+
+        <hr/>
+        <table style='width: 100%'>
+            <tr>
+                <td style='width: 50%'><div id='ruleList'></div></td>
+                <td valign='top'>Update Template Preview:<br/><pre id='marcPreview'></pre></td>
+            </tr>
+        </table>
+
+        <button dojoType='dijit.form.Button'>Add Merge Rule
+            <script type='dojo/connect' event='onClick'>render_from_template()</script>
+            <script type='dojo/method' event='postCreate'>render_from_template()</script>
+        </button>
+
+        <div class='hide_me' id='ruleTemplate'>
+        <div name='ruleTable'>
+            <table>
+                <tbody>
+                    <tr>
+                        <th>Rule Type</th>
+                        <td name='typeSelectContainer'>
+                            <select name='typeSelect'>
+                                <option value='r'>Replace</option>
+                                <option value='a'>Add</option>
+                                <option value='d'>Delete</option>
+                            </select>
+                        </td>
+                    </tr>
+                    <tr>
+                        <th>MARC Tag</th>
+                        <td name='tagContainer'><input style='with: 2em;' name='tag' type='text'></input</td>
+                    </td>
+                    <tr>
+                        <th>Optional Subfields</th>
+                        <td name='sfContainer'><input name='sf' type='text'/></td>
+                    </tr>
+                    <tr>
+                        <th>MARC Data</th>
+                        <td name='marcDataContainer'><input name='marcData' type='text'/></td>
+                    </tr>
+                    <tr>
+                        <th colspan='2' style='padding-top: 10px; text-align: center;'>Optionally Restrict By</th>
+                    </tr>
+                    <tr>
+                        <th>Subfield</th>
+                        <td name='matchSFContainer'><input style='with: 2em;' name='matchSF' type='text'></input</td>
+                    </td>
+                    <tr>
+                        <th>Regular Expression</th>
+                        <td name='matchREContainer'><input name='matchRE' type='text'/></td>
+                    </tr>
+                    <tr>
+                        <td></td>
+                        <td>
+                            <button name='removeButton'>Remove Merge Rule
+                            </button>
+                        </td>
+                    </tr>
+                </tbody>
+            </table>
+        <hr/>
+        </div>
+        </div>
+
+    </body>
+</html>
+HTML
+
+    return Apache2::Const::OK;
+}
+
+1;
+
+

Modified: branches/rel_1_6/Open-ILS/src/sql/Pg/950.data.seed-values.sql
===================================================================
--- branches/rel_1_6/Open-ILS/src/sql/Pg/950.data.seed-values.sql	2010-10-11 16:07:47 UTC (rev 18264)
+++ branches/rel_1_6/Open-ILS/src/sql/Pg/950.data.seed-values.sql	2010-10-11 17:25:19 UTC (rev 18265)
@@ -1780,6 +1780,7 @@
 INSERT INTO container.biblio_record_entry_bucket_type (code,label) VALUES ('staff_client', oils_i18n_gettext('staff_client', 'General Staff Client container', 'cbrebt', 'label'));
 INSERT INTO container.biblio_record_entry_bucket_type (code,label) VALUES ('bookbag', oils_i18n_gettext('bookbag', 'Book Bag', 'cbrebt', 'label'));
 INSERT INTO container.biblio_record_entry_bucket_type (code,label) VALUES ('reading_list', oils_i18n_gettext('reading_list', 'Reading List', 'cbrebt', 'label'));
+INSERT INTO container.biblio_record_entry_bucket_type (code,label) VALUES ('template_merge',oils_i18n_gettext('template_merge','Template Merge Container', 'cbrebt', 'label'));
 
 INSERT INTO container.user_bucket_type (code,label) VALUES ('misc', oils_i18n_gettext('misc', 'Miscellaneous', 'cubt', 'label'));
 INSERT INTO container.user_bucket_type (code,label) VALUES ('folks', oils_i18n_gettext('folks', 'Friends', 'cubt', 'label'));

Copied: branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0434.data.merge_template_container_type.sql (from rev 18264, trunk/Open-ILS/src/sql/Pg/upgrade/0434.data.merge_template_container_type.sql)
===================================================================
--- branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0434.data.merge_template_container_type.sql	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/src/sql/Pg/upgrade/0434.data.merge_template_container_type.sql	2010-10-11 17:25:19 UTC (rev 18265)
@@ -0,0 +1,9 @@
+
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0434');
+
+INSERT INTO container.biblio_record_entry_bucket_type (code,label) VALUES ('template_merge','Template Merge Container');
+
+COMMIT;
+

Modified: branches/rel_1_6/Open-ILS/web/css/theme/default.css
===================================================================
--- branches/rel_1_6/Open-ILS/web/css/theme/default.css	2010-10-11 16:07:47 UTC (rev 18264)
+++ branches/rel_1_6/Open-ILS/web/css/theme/default.css	2010-10-11 17:25:19 UTC (rev 18265)
@@ -56,3 +56,4 @@
     border-bottom:1px solid #ACA899;
 }
 
+.oils-notify-text { color: red; font-weight:bold; }

Added: branches/rel_1_6/Open-ILS/web/js/dojo/MARC/Batch.js
===================================================================
--- branches/rel_1_6/Open-ILS/web/js/dojo/MARC/Batch.js	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/web/js/dojo/MARC/Batch.js	2010-10-11 17:25:19 UTC (rev 18265)
@@ -0,0 +1,82 @@
+/* ---------------------------------------------------------------------------
+ * Copyright (C) 2009  Equinox Software, Inc.
+ * Mike Rylander <miker at esilibrary.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * ---------------------------------------------------------------------------
+ */
+
+if(!dojo._hasResource["MARC.Batch"]) {
+
+    dojo.require('dojox.xml.parser');
+    dojo.require('MARC.Record');
+
+    dojo._hasResource["MARC.Batch"] = true;
+    dojo.provide("MARC.Batch");
+    dojo.declare('MARC.Batch', null, {
+
+        constructor : function(kwargs) {
+            this.ready = false;
+            this.records = [];
+            this.source = kwargs.source;
+            this.delimiter = kwargs.delimiter
+            this.current_record = 0;
+
+            if (this.source) this.ready = true;
+            if (!this.ready && kwargs.url) this.fetchURL( kwargs.url );
+
+            if (this.ready) this.parse();
+        },
+
+        parse : function () {
+            if (dojo.isObject( this.source )) { // assume an xml collection document
+                this.source = dojo.query('record', this.source);
+                this.type = 'xml';
+            } else if (this.source.match(/^\s*</)) { // this is xml text
+                this.source = dojox.xml.parser.parse( this.source );
+                this.parse();
+            } else { // must be a marcbreaker doc. split on blank lines
+                this.source = this.source.split(/^$/);
+                this.type = 'marcbreaker';
+            }
+        },
+
+        fetchURL : function (u) {
+            var me = this;
+            dojo.xhrGet({
+                url     : u,
+                sync    : true,
+                handleAs: 'text',
+                load    : function (mrc) {
+                    me.source = mrc;
+                    me.ready = true;
+                }
+            });
+        },
+
+        next : function () {
+            var chunk = this.source[this.current_record++];
+
+            if (chunk) {
+                var args = {};
+                args[this.type] = chunk;
+                if (this.delimiter) args.delimiter = this.delimiter;
+                return new MARC.Record(args);
+            }
+
+            return null;
+        }
+
+    });
+}
+            
+            
+

Added: branches/rel_1_6/Open-ILS/web/js/dojo/MARC/Field.js
===================================================================
--- branches/rel_1_6/Open-ILS/web/js/dojo/MARC/Field.js	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/web/js/dojo/MARC/Field.js	2010-10-11 17:25:19 UTC (rev 18265)
@@ -0,0 +1,130 @@
+/* ---------------------------------------------------------------------------
+ * Copyright (C) 2009  Equinox Software, Inc.
+ * Mike Rylander <miker at esilibrary.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * ---------------------------------------------------------------------------
+ */
+
+if(!dojo._hasResource["MARC.Field"]) {
+
+    dojo._hasResource["MARC.Field"] = true;
+    dojo.provide("MARC.Field");
+    dojo.declare('MARC.Field', null, {
+
+        error : false, // MARC record pointer
+        record : null, // MARC record pointer
+        tag : '', // MARC tag
+        ind1 : '', // MARC indicator 1
+        ind2 : '', // MARC indicator 2
+        data : '', // MARC data for a controlfield element
+        subfields : [], // list of MARC subfields for a datafield element
+
+        constructor : function(kwargs) {
+            this.record = kwargs.record;
+            this.tag = kwargs.tag;
+            this.ind1 = kwargs.ind1;
+            this.ind2 = kwargs.ind2;
+            this.data = kwargs.data;
+            if (kwargs.subfields) this.subfields = kwargs.subfields;
+            else this.subfields = [];
+        },
+
+        subfield : function (code) {
+            var list = dojo.filter( this.subfields, function (s) {
+                if (s[0] == code) return true; return true;
+            });
+            if (list.length == 1) return list[0];
+            return list;
+        },
+
+        addSubfields : function () {
+            for (var i = 0; i < arguments.length; i++) {
+                var code = arguments[i];
+                var value = arguments[++i];
+                this.subfields.push( [ code, value ] );
+            }
+        },
+
+        deleteSubfields : function (c) {
+            return this.deleteSubfield( { code : c } );
+        },
+
+        deleteSubfield : function (args) {
+            var me = this;
+            if (!dojo.isArray( args.code )) {
+                args.code = [ args.code ];
+            }
+
+            if (args.pos && !dojo.isArray( args.pos )) {
+                args.pos = [ args.pos ];
+            }
+
+            for (var i = 0; i < args.code.length; i++) {
+                var sub_pos = {};
+                for (var j = 0; j < me.subfields; j++) {
+                    if (me.subfields[j][0] == args.code[i]) {
+
+                        if (!sub_pos[args.code[i]]) sub_pos[args.code[j]] = 0;
+                        else sub_pos[args.code[i]]++;
+
+                        if (args.pos) {
+                            for (var k = 0; k < args.pos.length; k++) {
+                                if (sub_pos[args.code[i]] == args.pos[k]) me.subfields.splice(j,1);
+                            }
+                        } else if (args.match && me.subfields[j][1].match( args.match )) {
+                            me.subfields.splice(j,1);
+                        } else {
+                            me.subfields.splice(j,1);
+                        }
+                    }
+                }
+            }
+        },
+
+        update : function ( args ) {
+            if (this.isControlfield()) {
+                this.data = args;
+            } else {
+                if (args.ind1) this.ind1 = args.ind1;
+                if (args.ind2) this.ind2 = args.ind2;
+                if (args.tag) this.tag = args.tag;
+
+                for (var i in args) {
+                    if (i == 'tag' || i == 'ind1' || i == 'ind2') continue;
+                    var done = 0;
+                    dojo.forEach( this.subfields, function (f) {
+                        if (!done && f[0] == i) {
+                            f[1] = args[i];
+                            done = 1;
+                        }
+                    });
+                }
+            }
+        },
+
+        isControlfield : function () {
+            return this.tag < '010' ? true : false;
+        },
+
+        indicator : function (num, value) {
+            if (value) {
+                if (num == 1) this.ind1 = value;
+                else if (num == 2) this.ind2 = value;
+                else { this.error = true; return null; }
+            }
+            if (num == 1) return this.ind1;
+            else if (num == 2) return this.ind2;
+            else { this.error = true; return null; }
+        }
+
+    });
+}

Added: branches/rel_1_6/Open-ILS/web/js/dojo/MARC/Record.js
===================================================================
--- branches/rel_1_6/Open-ILS/web/js/dojo/MARC/Record.js	                        (rev 0)
+++ branches/rel_1_6/Open-ILS/web/js/dojo/MARC/Record.js	2010-10-11 17:25:19 UTC (rev 18265)
@@ -0,0 +1,299 @@
+/* ---------------------------------------------------------------------------
+ * Copyright (C) 2009  Equinox Software, Inc.
+ * Mike Rylander <miker at esilibrary.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * ---------------------------------------------------------------------------
+ */
+
+if(!dojo._hasResource["MARC.Record"]) {
+
+    dojo.require('dojox.xml.parser');
+    dojo.require('MARC.Field');
+
+    dojo._hasResource["MARC.Record"] = true;
+    dojo.provide("MARC.Record");
+    dojo.declare('MARC.Record', null, {
+
+        delimiter : '\u2021', // default subfield delimiter
+
+        constructor : function(kwargs) {
+            this.fields = [];
+            this.leader = '00000cam a2200205Ka 4500';
+
+            if (kwargs.delimiter) this.delimiter = kwargs.delimiter;
+            if (kwargs.onLoad) this.onLoad = kwargs.onLoad;
+            if (kwargs.url) {
+                this.fromXmlURL(kwargs.url);
+            } else if (kwargs.marcxml) {
+                this.fromXmlString(kwargs.marcxml);
+                if (this.onLoad) this.onLoad();
+            } else if (kwargs.xml) {
+                this.fromXmlDocument(kwargs.xml);
+                if (this.onLoad) this.onLoad();
+            } else if (kwargs.marcbreaker) {
+                this.fromBreaker(kwargs.marcbreaker);
+                if (this.onLoad) this.onLoad();
+            }
+        },
+
+        title : function () { return this.subfield('245','a') },
+
+        field : function (spec) {
+            var list = dojo.filter( this.fields, function (f) {
+                if (f.tag.match(spec)) return true;
+                return false;
+            });
+
+            if (list.length == 1) return list[0];
+            return list;
+        },
+
+        subfield : function (spec, code) { return this.field(spec)[0].subfield(code) },
+
+        appendFields : function () {
+            var me = this;
+            dojo.forEach( arguments, function (f) { me.fields.push( f ) } );
+        },
+
+        deleteField : function (f) { return this.deleteFields(f) },
+
+        insertOrderedFields : function () {
+            var me = this;
+            for (var i = 0; i < arguments.length; i++) {
+                var f = arguments[i];
+                var done = false;
+                for (var j = 0; j < this.fields.length; j++) {
+                    if (f.tag < this.fields[j].tag) {
+                        this.insertFieldsBefore(this.fields[j], f);
+                        done = true;
+                        break;
+                    }
+                }
+                if (!done) this.appendFields(f);
+            }
+        },
+
+        insertFieldsBefore : function (target) {
+            var args = Array.prototype.slice.call(arguments);
+            args.splice(0,1);
+            var me = this;
+            for (var j = 0; j < this.fields.length; j++) {
+                if (target === this.fields[j]) {
+                    j--;
+                    dojo.forEach( args, function (f) {
+                        me.fields.splice(j++,0,f);
+                    });
+                    break;
+                }
+            }
+        },
+
+        insertFieldsAfter : function (target) {
+            var args = Array.prototype.slice.call(arguments);
+            args.splice(0,1);
+            var me = this;
+            for (var j = 0; j < this.fields.length; j++) {
+                if (target === this.fields[j]) {
+                    dojo.forEach( args, function (f) {
+                        me.fields.splice(j++,0,f);
+                    });
+                    break;
+                }
+            }
+        },
+
+        deleteFields : function () {
+            var me = this;
+            var counter = 0;
+            for ( var i in arguments ) {
+                var f = arguments[i];
+                for (var j = 0; j < me.fields.length; j++) {
+                    if (f === me.fields[j]) {
+                        me.fields[j].record = null;
+                        me.fields.splice(j,0);
+                        counter++
+                        break;
+                    }
+                }
+            }
+            return counter;
+        },
+
+        clone : function () { return dojo.clone(this) },
+
+        fromXmlURL : function (url) {
+            this.ready   = false;
+            var me = this;
+            dojo.xhrGet({
+                url     : url,
+                sync    : true,
+                handleAs: 'xml',
+                load    : function (mxml) {
+                    me.fromXmlDocument(dojo.query('record', mxml)[0]);
+                    me.ready = true;
+                    if (me.onLoad) me.onLoad();
+                }
+            });
+        },
+
+        fromXmlString : function (mxml) {
+                return this.fromXmlDocument( dojox.xml.parser.parse( mxml ) );
+        },
+
+        fromXmlDocument : function (mxml) {
+            var me = this;
+            me.leader = dojox.xml.parser.textContent(dojo.query('leader', mxml)[0]) || '00000cam a2200205Ka 4500';
+
+            dojo.forEach( dojo.query('controlfield', mxml), function (cf) {
+                me.fields.push(
+                    new MARC.Field({
+                          record : me,
+                          tag    : cf.getAttribute('tag'),
+                          data   : dojox.xml.parser.textContent(cf)
+                    })
+                )
+            });
+
+            dojo.forEach( dojo.query('datafield', mxml), function (df) {
+                me.fields.push(
+                    new MARC.Field({
+                        record    : me,
+                        tag       : df.getAttribute('tag'),
+                        ind1      : df.getAttribute('ind1'),
+                        ind2      : df.getAttribute('ind2'),
+                        subfields : dojo.map(
+                            dojo.query('subfield', df),
+                            function (sf) {
+                                return [ sf.getAttribute('code'), dojox.xml.parser.textContent(sf) ];
+                            }
+                        )
+                    })
+                )
+            });
+
+            return this;
+        },
+
+        toXmlDocument : function () {
+
+            var doc = dojox.xml.parser.parse('<record xmlns="http://www.loc.gov/MARC21/slim"/>');
+            var rec_node = dojo.query('record', doc)[0];
+
+            var ldr = doc.createElementNS('http://www.loc.gov/MARC21/slim', 'leader');
+            dojox.xml.parser.textContent(ldr, this.leader);
+            rec_node.appendChild( ldr );
+
+            dojo.forEach( this.fields, function (f) {
+                var element = f.isControlfield() ? 'controlfield' : 'datafield';
+                var f_node = doc.createElementNS( 'http://www.loc.gov/MARC21/slim', element );
+                f_node.setAttribute('tag', f.tag);
+                
+                if (f.isControlfield() && f.data) {
+                    dojox.xml.parser.textContent(f_node, f.data);
+                } else {
+                    f_node.setAttribute('ind1', f.indicator(1));
+                    f_node.setAttribute('ind2', f.indicator(2));
+                    dojo.forEach( f.subfields, function (sf) {
+                        var sf_node = doc.createElementNS('http://www.loc.gov/MARC21/slim', 'subfield');
+                        sf_node.setAttribute('code', sf[0]);
+                        dojox.xml.parser.textContent(sf_node, sf[1]);
+                        f_node.appendChild(sf_node);
+                    });
+                }
+
+                rec_node.appendChild(f_node);
+            });
+
+            return doc;
+        },
+
+        toXmlString : function () {
+            return dojox.xml.parser.innerXML( this.toXmlDocument() );
+        },
+
+        fromBreaker : function (marctxt) {
+            var me = this;
+
+            function cf_line_data (l) { return l.substring(4) };
+            function df_line_data (l) { return l.substring(6) };
+            function line_tag (l) { return l.substring(0,3) };
+            function df_ind1 (l) { return l.substring(4,5).replace('\\',' ') };
+            function df_ind2 (l) { return l.substring(5,6).replace('\\',' ') };
+            function isControlField (l) {
+                var x = line_tag(l);
+                return (x == 'LDR' || x < '010') ? true : false;
+            }
+            
+            var lines = marctxt.replace(/^=/gm,'').split('\n');
+            dojo.forEach(lines, function (current_line) {
+
+                if (current_line.match(/^#/)) {
+                    // skip comment lines
+                } else if (isControlField(current_line)) {
+                    if (line_tag(current_line) == 'LDR') {
+                        me.leader = cf_line_data(current_line) || '00000cam a2200205Ka 4500';
+                    } else {
+                        me.fields.push(
+                            new MARC.Field({
+                                record : me,
+                                tag    : line_tag(current_line),
+                                data   : cf_line_data(current_line).replace('\\',' ','g')
+                            })
+                        );
+                    }
+                } else {
+                    var data = df_line_data(current_line);
+                    if (!(data.substring(0,1) == me.delimiter)) data = me.delimiter + 'a' + data;
+
+                    var sf_list = data.split(me.delimiter);
+                    sf_list.shift();
+
+                    me.fields.push(
+                        new MARC.Field({
+                                record    : me,
+                                tag       : line_tag(current_line),
+                                ind1      : df_ind1(current_line),
+                                ind2      : df_ind2(current_line),
+                                subfields : dojo.map(
+                                    sf_list,
+                                    function (sf) { return [ sf.substring(0,1), sf.substring(1) ] }
+                                )
+                        })
+                    );
+                }
+            });
+
+            return this;
+        },
+
+        toBreaker : function () {
+
+            var me = this;
+            var mtxt = '=LDR ' + this.leader + '\n';
+
+            mtxt += dojo.map( this.fields, function (f) {
+                if (f.isControlfield() && f.data) {
+                    return '=' + f.tag + ' ' + f.data.replace(' ','\\','g');
+                } else {
+                    return '=' + f.tag + ' ' +
+                        f.indicator(1).replace(' ','\\') + 
+                        f.indicator(2).replace(' ','\\') + 
+                        dojo.map( f.subfields, function (sf) {
+                            return me.delimiter + sf.join('');
+                        }).join('');
+                }
+            }).join('\n');
+
+            return mtxt;
+        }
+    });
+}

Modified: branches/rel_1_6/Open-ILS/web/js/dojo/fieldmapper/AutoIDL.js
===================================================================
--- branches/rel_1_6/Open-ILS/web/js/dojo/fieldmapper/AutoIDL.js	2010-10-11 16:07:47 UTC (rev 18264)
+++ branches/rel_1_6/Open-ILS/web/js/dojo/fieldmapper/AutoIDL.js	2010-10-11 17:25:19 UTC (rev 18265)
@@ -2,6 +2,14 @@
     dojo.provide("fieldmapper.AutoIDL");
     dojo.require("fieldmapper.IDL");
 
-    fieldmapper.IDL.load();
+
+    var classlist = [];
+    try {
+        classlist = dojo.config.AutoIDL || [];
+    } catch(x) {
+        /* meh */
+    }
+
+    fieldmapper.IDL.load(classlist);
 }
 

Modified: branches/rel_1_6/Open-ILS/web/js/dojo/fieldmapper/Fieldmapper.js
===================================================================
--- branches/rel_1_6/Open-ILS/web/js/dojo/fieldmapper/Fieldmapper.js	2010-10-11 16:07:47 UTC (rev 18264)
+++ branches/rel_1_6/Open-ILS/web/js/dojo/fieldmapper/Fieldmapper.js	2010-10-11 17:25:19 UTC (rev 18265)
@@ -53,7 +53,7 @@
 					obj.a[i] = thing.clone();
 				} else {
 
-					if(instanceOf(thing, Array)) {
+					if(dojo.isArray(thing)) {
 						obj.a[i] = new Array();
 
 						for( var j in thing ) {
@@ -71,11 +71,103 @@
 			return obj;
 		},
 
+        RequiredField : function (f) {
+            if (!f) return;
+            if (fieldmapper.IDL && fieldmapper.IDL.loaded)
+                return this.Structure.fields[f].required;
+            return;
+        },
+
+        ValidateField : function (f) {
+            if (!f) return;
+            if (fieldmapper.IDL && fieldmapper.IDL.loaded) {
+                if (this.Structure.fields[f] && this.Structure.fields[f].validate) {
+                    return this.Structure.fields[f].validate.test(this[f]());
+                }
+                return true;
+            }
+            return;
+        }
+            
+
+ 
+/*
 		isnew : function(n) { if(arguments.length == 1) this.a[0] =n; return this.a[0]; },
 		ischanged : function(n) { if(arguments.length == 1) this.a[1] =n; return this.a[1]; },
 		isdeleted : function(n) { if(arguments.length == 1) this.a[2] =n; return this.a[2]; }
+*/
+
 	});
 
+    fieldmapper.vivicateClass = function (cl) {
+		dojo.provide( cl );
+		dojo.declare( cl , fieldmapper.Fieldmapper, {
+			constructor : function () {
+				if (!this.a) this.a = [];
+				this.classname = this.declaredClass;
+                this._fields = [];
+
+                if (fieldmapper.IDL && fieldmapper.IDL.loaded) {
+                    this.Structure = fieldmapper.IDL.fmclasses[this.classname]
+
+                    for (var f in fieldmapper.IDL.fmclasses[this.classname].fields) {
+                        var field = fieldmapper.IDL.fmclasses[this.classname].fields[f];
+                        var p = field.array_position;
+    	    			this._fields.push( field.name );
+			    		this[field.name]=new Function('n', 'if(arguments.length==1)this.a['+p+']=n;return this.a['+p+'];');
+                    }
+                } else {
+				    this._fields = fmclasses[this.classname];
+
+    				for( var pos = 0; pos <  this._fields.length; pos++ ) {
+    					var p = parseInt(pos);
+	    				var f = this._fields[pos];
+		    			this[f]=new Function('n', 'if(arguments.length==1)this.a['+p+']=n;return this.a['+p+'];');
+			    	}
+                }
+
+			}
+		});
+		fieldmapper[cl] = window[cl]; // alias into place
+        if (fieldmapper.IDL && fieldmapper.IDL.loaded) fieldmapper[cl].Identifier = fieldmapper.IDL.fmclasses[cl].pkey;
+    };
+
+    if (!window.fmclasses) dojo.require("fieldmapper.fmall", true);
+    for( var cl in fmclasses ) {
+        fieldmapper.vivicateClass(cl);
+    }
+
+    // if we were NOT called by the IDL loader ...
+    // XXX This is now deprecated in preference to fieldmapper.AutoIDL
+    if ( !(fieldmapper.IDL && fieldmapper.IDL.loaded) ) {
+
+        fieldmapper.cmsa.Identifier = 'alias';
+        fieldmapper.cmc.Identifier = 'name';
+    	fieldmapper.i18n_l.Identifier = 'code';
+    	fieldmapper.ccpbt.Identifier = 'code';
+    	fieldmapper.ccnbt.Identifier = 'code';
+    	fieldmapper.cbrebt.Identifier = 'code';
+    	fieldmapper.cubt.Identifier = 'code';
+    	fieldmapper.ccm.Identifier = 'code';
+    	fieldmapper.cvrfm.Identifier = 'code';
+    	fieldmapper.clm.Identifier = 'code';
+    	fieldmapper.cam.Identifier = 'code';
+    	fieldmapper.cifm.Identifier = 'code';
+    	fieldmapper.citm.Identifier = 'code';
+    	fieldmapper.cblvl.Identifier = 'code';
+    	fieldmapper.clfm.Identifier = 'code';
+    	fieldmapper.mous.Identifier = 'usr';
+    	fieldmapper.moucs.Identifier = 'usr';
+    	fieldmapper.mucs.Identifier = 'usr';
+    	fieldmapper.mus.Identifier = 'usr';
+    	fieldmapper.rxbt.Identifier = 'xact';
+    	fieldmapper.rxpt.Identifier = 'xact';
+    	fieldmapper.cxt.Identifier = 'name';
+    	fieldmapper.amtr.Identifier = 'matchpoint';
+    	fieldmapper.coust.Identifier = 'name';
+
+    }
+
 	fieldmapper._request = function ( meth, staff, params ) {
 		var ses = OpenSRF.CachedClientSession( meth[0] );
 		if (!ses) return null;
@@ -90,7 +182,7 @@
 			if (dojo.isObject(params)) {
 				args = params;
 			} else {
-                args.params = [].splice.call(arguments, 1, arguments.length - 1);
+                args.params = [].splice.call(arguments, 2, arguments.length - 2);
 			}
 
 		}
@@ -136,76 +228,6 @@
 	fieldmapper.staffRequest = function (meth, params) { return fieldmapper._request(meth, true, params) };
 	fieldmapper.Fieldmapper.prototype.staffRequest = fieldmapper.staffRequest;
 
-    // if we were called by the IDL loader ...
-    if ( fieldmapper.IDL && fieldmapper.IDL.loaded ) {
-    	for( var cl in fieldmapper.IDL.fmclasses ) {
-    		dojo.provide( cl );
-    		dojo.declare( cl , fieldmapper.Fieldmapper, {
-    			constructor : function () {
-    				if (!this.a) this.a = [];
-    				this.classname = this.declaredClass;
-                    this._fields = [];
-                    this.Structure = fieldmapper.IDL.fmclasses[this.classname]
-
-                    for (var f in fieldmapper.IDL.fmclasses[this.classname].fields) {
-                        var field = fieldmapper.IDL.fmclasses[this.classname].fields[f];
-                        var p = field.array_position;
-        				this._fields.push( field.name );
-    					this[field.name]=new Function('n', 'if(arguments.length==1)this.a['+p+']=n;return this.a['+p+'];');
-                    }
-    			}
-    		});
-    		fieldmapper[cl] = window[cl]; // alias into place
-    		fieldmapper[cl].Identifier = fieldmapper.IDL.fmclasses[cl].pkey;
-    	}
-
-    // ... otherwise we need to get the oldschool fmall.js stuff, which will lack .structure
-    } else {
-    	if (!window.fmclasses)
-            dojo.require("fieldmapper.fmall", true);
-
-    	for( var cl in fmclasses ) {
-    		dojo.provide( cl );
-    		dojo.declare( cl , fieldmapper.Fieldmapper, {
-    			constructor : function () {
-    				if (!this.a) this.a = [];
-    				this.classname = this.declaredClass;
-    				this._fields = fmclasses[this.classname];
-    				for( var pos = 0; pos <  this._fields.length; pos++ ) {
-    					var p = parseInt(pos);
-    					var f = this._fields[pos];
-    					this[f]=new Function('n', 'if(arguments.length==1)this.a['+p+']=n;return this.a['+p+'];');
-    				}
-    			}
-    		});
-    		fieldmapper[cl] = window[cl]; // alias into place
-    		fieldmapper[cl].Identifier = 'id'; // alias into place
-    	}
-
-    	fieldmapper.i18n_l.Identifier = 'code';
-    	fieldmapper.ccpbt.Identifier = 'code';
-    	fieldmapper.ccnbt.Identifier = 'code';
-    	fieldmapper.cbrebt.Identifier = 'code';
-    	fieldmapper.cubt.Identifier = 'code';
-    	fieldmapper.ccm.Identifier = 'code';
-    	fieldmapper.cvrfm.Identifier = 'code';
-    	fieldmapper.clm.Identifier = 'code';
-    	fieldmapper.cam.Identifier = 'code';
-    	fieldmapper.cifm.Identifier = 'code';
-    	fieldmapper.citm.Identifier = 'code';
-    	fieldmapper.cblvl.Identifier = 'code';
-    	fieldmapper.clfm.Identifier = 'code';
-    	fieldmapper.mous.Identifier = 'usr';
-    	fieldmapper.moucs.Identifier = 'usr';
-    	fieldmapper.mucs.Identifier = 'usr';
-    	fieldmapper.mus.Identifier = 'usr';
-    	fieldmapper.rxbt.Identifier = 'xact';
-    	fieldmapper.rxpt.Identifier = 'xact';
-    	fieldmapper.cxt.Identifier = 'name';
-    	fieldmapper.amtr.Identifier = 'matchpoint';
-
-    }
-
 	fieldmapper.OpenSRF = {};
 
 	/*	Methods are defined as [ service, method, have_staff ]
@@ -291,7 +313,7 @@
 		FETCH_MR_DESCRIPTORS : ['open-ils.search','open-ils.search.metabib.record_to_descriptors'],
 		FETCH_HIGHEST_PERM_ORG : ['open-ils.actor','open-ils.actor.user.perm.highest_org.batch'],
 		FETCH_USER_NOTES : ['open-ils.actor','open-ils.actor.note.retrieve.all'],
-		FETCH_ORG_BY_SHORTNAME : ['open-ils.actor','open-ils.actor.org_unit.retrieve_by_shorname'],
+		FETCH_ORG_BY_SHORTNAME : ['open-ils.actor','open-ils.actor.org_unit.retrieve_by_shortname'],
 		FETCH_BIB_ID_BY_BARCODE : ['open-ils.search','open-ils.search.bib_id.by_barcode'],
 		FETCH_ORG_SETTING : ['open-ils.actor','open-ils.actor.ou_setting.ancestor_default'],
 		FETCH_ORG_SETTING_BATCH : ['open-ils.actor','open-ils.actor.ou_setting.ancestor_default.batch']

Modified: branches/rel_1_6/Open-ILS/web/js/dojo/fieldmapper/IDL.js
===================================================================
--- branches/rel_1_6/Open-ILS/web/js/dojo/fieldmapper/IDL.js	2010-10-11 16:07:47 UTC (rev 18264)
+++ branches/rel_1_6/Open-ILS/web/js/dojo/fieldmapper/IDL.js	2010-10-11 17:25:19 UTC (rev 18265)
@@ -1,45 +1,62 @@
 if(!dojo._hasResource["fieldmapper.IDL"]) {
+    dojo.require("DojoSRF");
     dojo.provide("fieldmapper.IDL");
     dojo.declare('fieldmapper.IDL', null, {
     
         _URL_PATH : '/reports/fm_IDL.xml', // XXX locale?
-        // -- just need to set up xmlent and use '/reports/'+dojo.locale+'/fm_IDL.xml'
+        // -- just need to set up xmlent and use '/reports/'+OpenSRF.locale+'/fm_IDL.xml'
         NS_REPORTS : 'http://open-ils.org/spec/opensrf/IDL/reporter/v1',
         NS_PERSIST : 'http://open-ils.org/spec/opensrf/IDL/persistence/v1',
         NS_OBJ : 'http://open-ils.org/spec/opensrf/IDL/objects/v1',
 
-        constructor : function(callback, force) {
-            if(!fieldmapper.IDL.fmclasses || force) {
+        constructor : function(classlist) {
+
+            if(!fieldmapper.IDL.fmclasses || (classlist && classlist.length)) {
+                var idl_url = this._URL_PATH;
+
+                if (classlist.length) {
+                    idl_url += '?';
+
+                    for (var i = 0; i < classlist.length; i++) {
+                        var trim_class = classlist[i];
+                        if (!trim_class) continue;
+
+                        if (i > 0) idl_url += '&';
+                        idl_url += 'class=' + trim_class;
+                    }
+                }
+                        
                 var self = this;
                 dojo.xhrGet({
-                    url : this._URL_PATH,
+                    url : idl_url,
                     handleAs : 'xml',
                     sync : true,
                     timeout : 10000,
                     load : function (response) {
-                        self._parse(response, callback);
+                        self._parse(response);
                         fieldmapper.IDL.loaded = true;
                     },
                     error : function (response) {
                         fieldmapper.IDL.loaded = false;
                         dojo.require('fieldmapper.fmall', true);
-                        if(callback)
-                            callback();
                     }
                 });
             }
+            dojo.require('fieldmapper.Fieldmapper'); 
 
-            return dojo.require('fieldmapper.Fieldmapper');
+            if (classlist && classlist.length)
+                dojo.forEach( classlist, function (c) { fieldmapper.vivicateClass(c); } );
         },
 
-        _parse : function(xmlNode, callback) {
-            var classes = xmlNode.getElementsByTagName('class');
-            var idl = fieldmapper.IDL.fmclasses = {};
-    
+        _parse : function(xmlNode) {
+            var classes = dojo.query('class',xmlNode);
+            if (!fieldmapper.IDL || !fieldmapper.IDL.fmclasses)
+                fieldmapper.IDL.fmclasses = {};
+
             for(var i = 0; i < classes.length; i++) {
                 var node = classes[i];
                 var id = node.getAttribute('id');
-                var fields = node.getElementsByTagName('fields')[0];
+                var fields = dojo.query('fields',node)[0];
                 window.fmclasses[id] = [];
                 
                 var fieldData = this._parseFields(node, id);
@@ -48,22 +65,25 @@
                     fields  : fieldData.list,
                     field_map : fieldData.map,
                     name    : node.getAttribute('id'),
-                    //table   : node.getAttributeNS(this.NS_PERSIST, 'tablename'),
-                    //core    : node.getAttributeNS(this.NS_REPORTS, 'core'),
-                    label   : node.getAttributeNS(this.NS_REPORTS, 'label'),
-                    restrict_primary   : node.getAttributeNS(this.NS_PERSIST, 'restrict_primary'),
-                    virtual : (node.getAttributeNS(this.NS_PERSIST, 'virtual') == 'true'),
-                    pkey    : fields.getAttributeNS(this.NS_PERSIST, 'primary'),
-                    pkey_sequence : fields.getAttributeNS(this.NS_PERSIST, 'sequence')
+                    //table   : fieldmapper._getAttributeNS(node,this.NS_PERSIST, 'tablename'),
+                    //core    : fieldmapper._getAttributeNS(node,this.NS_REPORTS, 'core'),
+                    label   : fieldmapper._getAttributeNS(node,this.NS_REPORTS, 'label'),
+                    restrict_primary   : fieldmapper._getAttributeNS(node,this.NS_PERSIST, 'restrict_primary'),
+                    virtual : (fieldmapper._getAttributeNS(node,this.NS_PERSIST, 'virtual') == 'true'),
+                    pkey    : fieldmapper._getAttributeNS(fields,this.NS_PERSIST, 'primary'),
+                    pkey_sequence : fieldmapper._getAttributeNS(fields,this.NS_PERSIST, 'sequence')
                 };
 
-                var permacrud = node.getElementsByTagName('permacrud')[0];
+                var valid = fieldmapper._getAttributeNS(node,this.NS_OBJ, 'validate');
+                if (valid) obj.validate = new RegExp( valid.replace(/\\/g, '\\\\') );
+
+                var permacrud = dojo.query('permacrud',node)[0];
                 if(permacrud) {
                     var actions = ['create', 'retrieve', 'update', 'delete'];
                     obj.permacrud = {};
                     for(var idx in actions) {
                         var action = actions[idx];
-                        var pnode = permacrud.getElementsByTagName(action)[0];
+                        var pnode = dojo.query(action,permacrud)[0];
                         if(pnode) {
                             var permString = pnode.getAttribute('permission');
                             var permList = null;
@@ -85,11 +105,9 @@
     
                 obj.core = (obj.core == 'true');
                 obj.label = (obj.label) ? obj.label : obj.name;
-                idl[id] = obj;
+                fieldmapper.IDL.fmclasses[id] = obj;
             }
     
-            if(callback)
-                callback();
         },
     
         /* parses the links and fields portion of the IDL */
@@ -97,11 +115,11 @@
             var data = [];
             var map = {};
     
-            var fields = node.getElementsByTagName('fields')[0];
-            fields = fields.getElementsByTagName('field');
+            var fields = dojo.query('fields',node)[0];
+            fields = dojo.query('field',fields);
     
-            var links = node.getElementsByTagName('links')[0];
-            if( links ) links = links.getElementsByTagName('link');
+            var links = dojo.query('links',node)[0];
+            if( links ) links = dojo.query('link',links);
             else links = [];
     
     
@@ -116,13 +134,15 @@
                 var obj = {
                     field : field,
                     name	: name,
-                    label : field.getAttributeNS(this.NS_REPORTS,'label'),
-                    datatype : field.getAttributeNS(this.NS_REPORTS,'datatype'),
-                    primitive : field.getAttributeNS(this.NS_PERSIST,'primitive'),
-                    selector : field.getAttributeNS(this.NS_REPORTS,'selector'),
+                    label : fieldmapper._getAttributeNS(field,this.NS_REPORTS,'label'),
+                    datatype : fieldmapper._getAttributeNS(field,this.NS_REPORTS,'datatype'),
+                    primitive : fieldmapper._getAttributeNS(field,this.NS_PERSIST,'primitive'),
+                    selector : fieldmapper._getAttributeNS(field,this.NS_REPORTS,'selector'),
                     array_position : position++,
                     type	: 'field',
-                    virtual : (fields[i].getAttributeNS(this.NS_PERSIST, 'virtual') == 'true') 
+                    virtual : (fieldmapper._getAttributeNS(fields[i],this.NS_PERSIST, 'virtual') == 'true'),
+                    required : (fieldmapper._getAttributeNS(fields[i],this.NS_OBJ, 'required') == 'true'),
+                    i18n : (fieldmapper._getAttributeNS(fields[i],this.NS_PERSIST, 'i18n') == 'true')
                 };
 
                 obj.label = obj.label || obj.name;
@@ -167,9 +187,20 @@
 
     });
 
+    fieldmapper._getAttributeNS = function (node,ns,attr) {
+        if (node.getAttributeNS) return node.getAttributeNS(ns,attr);
+        return node.getAttribute(attr);
+    };
+
     window.fmclasses = {};
-    fieldmapper.IDL.load = function (callback, force) { return new fieldmapper.IDL(callback, force); };
+    fieldmapper.IDL.load = function (list) { if (!list) list = []; return new fieldmapper.IDL(list); };
     fieldmapper.IDL.loaded = false;
 
+    JSON2js.fallbackObjectifier = function (arg, key_name, val_name) {
+        console.log("Firing IDL loader for " + arg[key_name]);
+        fieldmapper.IDL.load([arg[key_name]]);
+        return decodeJS(arg);
+    }
+ 
 }
 

Modified: branches/rel_1_6/Open-ILS/web/js/dojo/fieldmapper/dojoData.js
===================================================================
--- branches/rel_1_6/Open-ILS/web/js/dojo/fieldmapper/dojoData.js	2010-10-11 16:07:47 UTC (rev 18264)
+++ branches/rel_1_6/Open-ILS/web/js/dojo/fieldmapper/dojoData.js	2010-10-11 17:25:19 UTC (rev 18265)
@@ -26,10 +26,10 @@
 	function _fromStoreItem (data) {
 		this.fromHash(data);
 
-		for (var i in this._ignore_fields)
+		for (var i = 0; this._ignore_fields && i < this._ignore_fields.length; i++)
 			this[this._ignore_fields[i]](null);
 
-		for ( var i=0; i < this._fields.length; i++) {
+		for (var i = 0; this._fields && i < this._fields.length; i++) {
 			if (dojo.isArray( this[this._fields[i]]() ))
 				this[this._fields[i]]( this[this._fields[i]]()[0] );
 		}
@@ -53,22 +53,22 @@
 		if (!params) params = {};
         var data = this.initStoreData(label, params);
 
-		for (var i in list) data.items.push( list[i].toHash(true, params.virtualFields) );
+		for (var i = 0; list && i < list.length; i++) data.items.push( list[i].toHash(true, params.virtualFields) );
 
 		if (params.children && params.parent) {
 			var _hash_list = data.items;
 
 			var _find_root = {};
-			for (var i in _hash_list) {
+			for (var i = 0; _hash_list && i < _hash_list.length; i++) {
 				_find_root[_hash_list[i][params.identifier]] = _hash_list[i]; 
 			}
 
 			var item_data = [];
-			for (var i in _hash_list) {
+			for (var i = 0; _hash_list && i < _hash_list.length; i++) {
 				var obj = _hash_list[i]
 				obj[params.children] = [];
 
-				for (var j in _hash_list) {
+				for (var j = 0; _hash_list && j < _hash_list.length; j++) {
 					var kid = _hash_list[j];
 					if (kid[params.parent] == obj[params.identifier]) {
 						obj[params.children].push( { _reference : kid[params.identifier] } );
@@ -92,15 +92,18 @@
 		return data;
 	}
 
-	for (var i in fmclasses) fieldmapper[i].prototype.fromStoreItem = _fromStoreItem;
-	for (var i in fmclasses) fieldmapper[i].toStoreData = _toStoreData;
-	for (var i in fmclasses) fieldmapper[i].toStoreItem = _toStoreItem;
-	for (var i in fmclasses) fieldmapper[i].prototype.toStoreItem = function ( args ) { return _toStoreItem(this, args) };
-	for (var i in fmclasses) fieldmapper[i].initStoreData = _initStoreData;
+	for (var i in fmclasses) {
+		fieldmapper[i].prototype.fromStoreItem = _fromStoreItem;
+		fieldmapper[i].prototype.fromStoreItem = _fromStoreItem;
+		fieldmapper[i].toStoreData = _toStoreData;
+		fieldmapper[i].toStoreItem = _toStoreItem;
+		fieldmapper[i].prototype.toStoreItem = function ( args ) { return _toStoreItem(this, args) };
+		fieldmapper[i].initStoreData = _initStoreData;
+	}
 
-	fieldmapper.aou.prototype._ignore_fields = ['children'];
-	fieldmapper.aout.prototype._ignore_fields = ['children'];
-	fieldmapper.pgt.prototype._ignore_fields = ['children'];
+	if (fieldmapper.aou) fieldmapper.aou.prototype._ignore_fields = ['children'];
+	if (fieldmapper.aout) fieldmapper.aout.prototype._ignore_fields = ['children'];
+	if (fieldmapper.pgt) fieldmapper.pgt.prototype._ignore_fields = ['children'];
 
 	fieldmapper.aou.toStoreData = function (list, label) {
 		if (!label) label = 'shortname';

Modified: branches/rel_1_6/Open-ILS/web/js/dojo/fieldmapper/hash.js
===================================================================
--- branches/rel_1_6/Open-ILS/web/js/dojo/fieldmapper/hash.js	2010-10-11 16:07:47 UTC (rev 18264)
+++ branches/rel_1_6/Open-ILS/web/js/dojo/fieldmapper/hash.js	2010-10-11 17:25:19 UTC (rev 18265)
@@ -41,7 +41,7 @@
 		}
 
 		if (virtFields && virtFields.length > 0) {
-			for (var i in virtFields) {
+			for (var i = 0; i < virtFields.length; i++) {
 				if (!_hash[virtFields[i]])
 					_hash[virtFields[i]] = null;
 			}

Modified: branches/rel_1_6/Open-ILS/web/js/dojo/openils/User.js
===================================================================
--- branches/rel_1_6/Open-ILS/web/js/dojo/openils/User.js	2010-10-11 16:07:47 UTC (rev 18264)
+++ branches/rel_1_6/Open-ILS/web/js/dojo/openils/User.js	2010-10-11 17:25:19 UTC (rev 18265)
@@ -24,6 +24,7 @@
     dojo.require('fieldmapper.Fieldmapper');
     dojo.require('fieldmapper.OrgUtils');
     dojo.require('openils.Util');
+    dojo.require('dojo.cookie');
 
     dojo.declare('openils.User', null, {
 
@@ -50,6 +51,7 @@
             this.authcookie = kwargs.authcookie || openils.User.authcookie;
             this.permOrgStoreCache = {}; /* permName => permOrgUnitStore map */
 
+            if (this.authcookie) this.authtoken = dojo.cookie(this.authcookie);
             if (this.id && this.authtoken) this.user = this.getById( this.id );
             else if (this.authtoken) this.getBySession();
             else if (kwargs.login) this.login();
@@ -137,13 +139,16 @@
                 var authReq = OpenSRF.CachedClientSession('open-ils.auth').request('open-ils.auth.authenticate.complete', loginInfo);
                 authReq.oncomplete = function(rr) {
                     var data = rr.recv().content();
+
+                    if(!data || !data.payload)
+                        throw new Error("Login Failed: " + js2JSON(data));
+
                     _u.authtoken = data.payload.authtoken;
 					if (!openils.User.authtoken) openils.User.authtoken = _u.authtoken;
                     _u.authtime = data.payload.authtime;
 					if (!openils.User.authtime) openils.User.authtime = _u.authtime;
                     _u.getBySession(onComplete);
                     if(_u.authcookie) {
-                        dojo.require('dojo.cookie');
                         dojo.cookie(_u.authcookie, _u.authtoken, {path:'/'});
                     }
                 }
@@ -179,15 +184,18 @@
                 [loginInfo]
             );
 
+            if(!data || !data.payload) return false;
+
             _u.authtoken = data.payload.authtoken;
             if (!openils.User.authtoken) openils.User.authtoken = _u.authtoken;
             _u.authtime = data.payload.authtime;
             if (!openils.User.authtime) openils.User.authtime = _u.authtime;
 
             if(_u.authcookie) {
-                dojo.require('dojo.cookie');
                 dojo.cookie(_u.authcookie, _u.authtoken, {path:'/'});
             }
+
+            return true;
         },
 
     

Modified: branches/rel_1_6/Open-ILS/web/js/ui/base.js
===================================================================
--- branches/rel_1_6/Open-ILS/web/js/ui/base.js	2010-10-11 16:07:47 UTC (rev 18264)
+++ branches/rel_1_6/Open-ILS/web/js/ui/base.js	2010-10-11 17:25:19 UTC (rev 18265)
@@ -31,6 +31,7 @@
 }
 
 function oilsDoLogin() {
+    openils.Util.hide('oils-login-failed');
     var cgi = new openils.CGI();
     var workstation = cgi.param('ws') || dojo.cookie('ws');
     var user = new openils.User();
@@ -41,9 +42,14 @@
     };
     if(workstation) 
         args.workstation = workstation;
-    user.login(args);
-    dojo.cookie('ses', user.authtoken, {path : '/'});
-    location.href = location.href;
+
+    if(user.login(args)) {
+        dojo.cookie('ses', user.authtoken, {path : '/'});
+        location.href = location.href;
+    } else {
+        openils.Util.show('oils-login-failed');
+    }
+
     return false;
 }
 

Modified: branches/rel_1_6/Open-ILS/web/templates/login.tt2
===================================================================
--- branches/rel_1_6/Open-ILS/web/templates/login.tt2	2010-10-11 16:07:47 UTC (rev 18264)
+++ branches/rel_1_6/Open-ILS/web/templates/login.tt2	2010-10-11 17:25:19 UTC (rev 18265)
@@ -2,6 +2,7 @@
 <div style='display:none;' dojoType="dijit.Dialog" jsId='oilsLoginDialog' class='oils-login-dialog'>
     <script>dojo.require('dijit.form.TextBox');</script>
     <b>Please Login</b>
+    <div class='hidden oils-notify-text' id='oils-login-failed'>Login Failed</div>
     <form>
         <table>
             <tr>



More information about the open-ils-commits mailing list