Bookbags have descriptions now (and they're reflected in feeds).
Bookbag item notes are editable.
Bookbags can now be sorted by title or author using QP tricks.
You can export a bookbag as CSV.
Signed-off-by: Lebbeous Fogle-Weekley <lebbeous@esilibrary.com>
<field name="btype" reporter:datatype="text"/>
<field name="id" reporter:datatype="id" />
<field name="name" reporter:datatype="text"/>
+ <field name="description" reporter:datatype="text"/>
<field name="owner" reporter:datatype="link"/>
<field name="pub" reporter:datatype="bool"/>
<field name="create_time" reporter:datatype="timestamp" />
<field name="btype" reporter:datatype="text"/>
<field name="id" reporter:datatype="id" />
<field name="name" reporter:datatype="text"/>
+ <field name="description" reporter:datatype="text"/>
<field name="owner" reporter:datatype="link"/>
<field name="pub" reporter:datatype="bool"/>
<field name="create_time" reporter:datatype="timestamp" />
<field name="btype" reporter:datatype="text"/>
<field name="id" reporter:datatype="id" />
<field name="name" reporter:datatype="text"/>
+ <field name="description" reporter:datatype="text"/>
<field name="owner" reporter:datatype="link"/>
<field name="pub" reporter:datatype="bool"/>
<field name="create_time" reporter:datatype="timestamp" />
<field name="btype" reporter:datatype="text"/>
<field name="id" reporter:datatype="id" />
<field name="name" reporter:datatype="text"/>
+ <field name="description" reporter:datatype="text"/>
<field name="owner" reporter:datatype="link"/>
<field name="pub" reporter:datatype="bool"/>
<field name="create_time" reporter:datatype="timestamp" />
sub item_note_cud {
my($self, $conn, $auth, $class, $note) = @_;
+
+ return new OpenILS::Event("BAD_PARAMS") unless
+ $note->class_name =~ /bucket_item_note$/;
+
my $e = new_editor(authtoken => $auth, xact => 1);
return $e->die_event unless $e->checkauth;
- my $meth = 'retrieve_' . $ctypes{$class};
- my $nclass = $note->class_name;
- (my $iclass = $nclass) =~ s/n$//og;
+ my $meat = $ctypes{$class} . "_item_note";
+ my $meth = "retrieve_$meat";
- my $db_note = $e->$meth($note->id, {
- flesh => 2,
- flesh_fields => {
- $nclass => ['item'],
- $iclass => ['bucket']
- }
- });
+ my $item_meat = $ctypes{$class} . "_item";
+ my $item_meth = "retrieve_$item_meat";
+
+ my $nhint = $Fieldmapper::fieldmap->{$note->class_name}->{hint};
+ (my $ihint = $nhint) =~ s/n$//og;
+
+ my ($db_note, $item);
+
+ if ($note->isnew) {
+ $db_note = $note;
+
+ $item = $e->$item_meth([
+ $note->item, {
+ flesh => 1, flesh_fields => {$ihint => ["bucket"]}
+ }
+ ]) or return $e->die_event;
+ } else {
+ $db_note = $e->$meth([
+ $note->id, {
+ flesh => 2,
+ flesh_fields => {
+ $nhint => ['item'],
+ $ihint => ['bucket']
+ }
+ }
+ ]) or return $e->die_event;
+
+ $item = $db_note->item;
+ }
- if($db_note->item->bucket->owner ne $e->requestor->id) {
- return $e->die_event unless
- $e->allowed('UPDATE_CONTAINER', $db_note->item->bucket);
+ if($item->bucket->owner ne $e->requestor->id) {
+ return $e->die_event unless $e->allowed("UPDATE_CONTAINER");
}
- $meth = 'create_' . $ctypes{$class} if $note->isnew;
- $meth = 'update_' . $ctypes{$class} if $note->ischanged;
- $meth = 'delete_' . $ctypes{$class} if $note->isdeleted;
+ $meth = 'create_' . $meat if $note->isnew;
+ $meth = 'update_' . $meat if $note->ischanged;
+ $meth = 'delete_' . $meat if $note->isdeleted;
return $e->die_event unless $e->$meth($note);
$e->commit;
}
return $attrs;
}
+sub bib_container_items_via_search {
+ my ($class, $container_id, $search_query, $search_args) = @_;
+
+ # First, Use search API to get container items sorted in any way that crad
+ # sorters support.
+ my $search_result = $class->simplereq(
+ "open-ils.search", "open-ils.search.biblio.multiclass.query",
+ $search_args, $search_query
+ );
+ unless ($search_result) {
+ # empty result sets won't cause this, but actual errors should.
+ $logger->warn("bib_container_items_via_search() got nothing from search");
+ return;
+ }
+
+ # Throw away other junk from search, keeping only bib IDs.
+ my $id_list = [ map { pop @$_ } @{$search_result->{ids}} ];
+
+ return [] unless @$id_list;
+
+ # Now get the bib container items themselves...
+ my $e = new OpenILS::Utils::CStoreEditor;
+ unless ($e) {
+ $logger->warn("bib_container_items_via_search() couldn't get cstoreeditor");
+ return;
+ }
+
+ my $items = $e->search_container_biblio_record_entry_bucket_item([
+ {
+ "target_biblio_record_entry" => $id_list,
+ "bucket" => $container_id
+ }, {
+ flesh => 1,
+ flesh_fields => {"cbrebi" => [qw/notes target_biblio_record_entry/]}
+ }
+ ]);
+ unless ($items) {
+ $logger->warn(
+ "bib_container_items_via_search() couldn't get bucket items: " .
+ $e->die_event->{textcode}
+ );
+ return;
+ }
+
+ $e->disconnect;
+
+ # ... and put them in the same order that the search API said they
+ # should be in.
+ my %ordering_hash = map { $_->target_biblio_record_entry->id, $_ } @$items;
+ return [map { $ordering_hash{$_} } @$id_list];
+}
+
1;
use DateTime;
use DateTime::Format::ISO8601;
use Unicode::Normalize;
+use XML::LibXML;
use OpenSRF::Utils qw/:datetime/;
use OpenSRF::Utils::Logger qw(:logger);
use OpenILS::Application::AppUtils;
return;
},
+ csv_datum => sub {
+ my ($str) = @_;
+
+ if ($str =~ /\,/ || $str =~ /"/) {
+ $str =~ s/"/""/g;
+ $str = '"' . $str . '"';
+ }
+
+ return $str;
+ },
+
+ xml_doc => sub {
+ my ($str) = @_;
+ return $str ? (new XML::LibXML)->parse_string($str) : undef;
+ }
+
};
--- /dev/null
+package OpenILS::Application::Trigger::Reactor::ContainerCSV;
+use base "OpenILS::Application::Trigger::Reactor";
+use strict;
+use warnings;
+use OpenSRF::Utils::Logger qw/:logger/;
+use Data::Dumper;
+$Data::Dumper::Indent = 0;
+my $U = "OpenILS::Application::AppUtils";
+
+sub ABOUT {
+ return q|
+
+The ContainerCSV Reactor Module processes the configured template after
+fetching the items from the bookbag refererred to in $env->{target}
+by using the search api with the query in $env->{params}{search}. It's
+the event-creator's responsibility to build a correct search query and check
+permissions and do that sort of thing.
+
+open-ils.trigger is not a public service, so that should be ok.
+
+The output, like all processed templates, is stored in the event_output table.
+
+|;
+}
+
+sub handler {
+ my ($self, $env) = @_;
+
+ # get items for bookbags (bib containers of btype bookbag)
+ if ($env->{user_data}{item_search}) {
+ # use the search api for bib container items
+ my $items = $U->bib_container_items_via_search(
+ $env->{target}->id, $env->{user_data}{item_search}
+ ) or return 0; # TODO build error output for db?
+
+ $env->{items} = $items;
+ } else {
+ # XXX TODO If we're going to support other types of containers here,
+ # we'll probably just want to flesh those containers' items directly,
+ # not involve the search API.
+
+ $logger->warn("ContainerCSV reactor used without item_search, doesn't know what to do."); # XXX
+ }
+
+ return 1 if $self->run_TT($env);
+ return 0;
+}
+
+1;
return $self->load_myopac_update_password if $path =~ m|opac/myopac/update_password|;
return $self->load_myopac_update_username if $path =~ m|opac/myopac/update_username|;
return $self->load_myopac_bookbags if $path =~ m|opac/myopac/lists|;
+ return $self->load_myopac_bookbag_print if $path =~ m|opac/myopac/list/print|;
return $self->load_myopac_bookbag_update if $path =~ m|opac/myopac/list/update|;
return $self->load_myopac_circ_history if $path =~ m|opac/myopac/circ_history|;
return $self->load_myopac_hold_history if $path =~ m|opac/myopac/hold_history|;
return undef;
}
+sub _get_bookbag_sort_params {
+ my ($self) = @_;
+
+ # The interface that feeds this cgi parameter will provide a single
+ # argument for a QP sort filter, and potentially a modifier after a period.
+ # In practice this means the "sort" parameter will be something like
+ # "titlesort" or "authorsort.descending".
+ my $sorter = $self->cgi->param("sort") || "";
+ my $modifier;
+ if ($sorter) {
+ $sorter =~ s/^(.*?)\.(.*)/$1/;
+ $modifier = $2 || undef;
+ }
+
+ return ($sorter, $modifier);
+}
+
+sub _prepare_bookbag_container_query {
+ my ($self, $container_id, $sorter, $modifier) = @_;
+
+ return sprintf(
+ "container(bre,bookbag,%d,%s)%s%s",
+ $container_id, $self->editor->authtoken,
+ ($sorter ? " sort($sorter)" : ""),
+ ($modifier ? "#$modifier" : "")
+ );
+}
+
sub load_myopac_prefs_settings {
my $self = shift;
my $e = $self->editor;
my $ctx = $self->ctx;
+ my ($sorter, $modifier) = $self->_get_bookbag_sort_params;
$e->xact_begin; # replication...
my $rv = $self->load_mylist;
return $rv;
}
- my $args = {
- order_by => {cbreb => 'name'},
- limit => $self->cgi->param('limit') || 10,
- offset => $self->cgi->param('offset') || 0
- };
-
$ctx->{bookbags} = $e->search_container_biblio_record_entry_bucket(
[
- {owner => $self->editor->requestor->id, btype => 'bookbag'},
- {"flesh" => 1, "flesh_fields" => {"cbreb" => ["items"]}, %$args}
- ],
+ {owner => $e->requestor->id, btype => 'bookbag'}, {
+ order_by => {cbreb => 'name'},
+ limit => $self->cgi->param('limit') || 10,
+ offset => $self->cgi->param('offset') || 0
+ }
+ ],
{substream => 1}
);
return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
}
- # get unique record IDs
- my %rec_ids = ();
- foreach my $bbag (@{$ctx->{bookbags}}) {
- foreach my $rec_id (
- map { $_->target_biblio_record_entry } @{$bbag->items}
- ) {
- $rec_ids{$rec_id} = 1;
+ # Here is the loop that uses search to find the bib records in each
+ # bookbag. XXX This should be parallelized. Should this be done
+ # with OpenSRF::MultiSession, or is it enough to use OpenSRF::AppSession
+ # and call ->request() without calling ->gather() on any of those objects
+ # until all the requests have been issued?
+
+ foreach my $bookbag (@{$ctx->{bookbags}}) {
+ my $query = $self->_prepare_bookbag_container_query(
+ $bookbag->id, $sorter, $modifier
+ );
+
+ # XXX we need to limit the number of records per bbag; use third arg
+ # of bib_container_items_via_search() i think.
+ my $items = $U->bib_container_items_via_search($bookbag->id, $query)
+ or return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
+
+ # Maybe save a little memory by creating only one XML::LibXML::Document
+ # instance for each record, even if record is repeated across bookbags.
+
+ foreach my $rec (map { $_->target_biblio_record_entry } @$items) {
+ next if $ctx->{bookbags_marc_xml}{$rec->id};
+ $ctx->{bookbags_marc_xml}{$rec->id} =
+ (new XML::LibXML)->parse_string($rec->marc);
}
- }
- $ctx->{bookbags_marc_xml} = $self->fetch_marc_xml_by_id([keys %rec_ids]);
+ $bookbag->items($items);
+ }
$e->rollback;
return Apache2::Const::OK;
my $e = $self->editor;
my $cgi = $self->cgi;
+ # save_notes is effectively another action, but is passed in a separate
+ # CGI parameter for what are really just layout reasons.
+ $action = 'save_notes' if $cgi->param('save_notes');
$action ||= $cgi->param('action');
+
$list_id ||= $cgi->param('list');
my @add_rec = $cgi->param('add_rec') || $cgi->param('record');
my @selected_item = $cgi->param('selected_item');
my $shared = $cgi->param('shared');
my $name = $cgi->param('name');
+ my $description = $cgi->param('description');
my $success = 0;
my $list;
- if($action eq 'create') {
+ # This url intentionally leaves off the edit_notes parameter, but
+ # may need to add some back in for paging.
+
+ my $url = "https://" . $self->apache->hostname .
+ $self->ctx->{opac_root} . "/myopac/lists?";
+
+ $url .= 'sort=' . uri_escape($cgi->param("sort")) if $cgi->param("sort");
+
+ if ($action eq 'create') {
$list = Fieldmapper::container::biblio_record_entry_bucket->new;
$list->name($name);
+ $list->description($description);
$list->owner($e->requestor->id);
$list->btype('bookbag');
$list->pub($shared ? 't' : 'f');
);
last unless $success;
}
+ } elsif ($action eq 'save_notes') {
+ $success = $self->update_bookbag_item_notes;
}
- return $self->generic_redirect if $success;
+ return $self->generic_redirect($url) if $success;
+
+ # XXX FIXME Bucket failure doesn't have a page to show the user anything
+ # right now. User just sees a 404 currently.
$self->ctx->{bucket_action} = $action;
$self->ctx->{bucket_action_failed} = 1;
return Apache2::Const::OK;
}
-1
+sub update_bookbag_item_notes {
+ my ($self) = @_;
+ my $e = $self->editor;
+
+ my @note_keys = grep /^note-\d+/, keys(%{$self->cgi->Vars});
+ my @item_keys = grep /^item-\d+/, keys(%{$self->cgi->Vars});
+
+ # We're going to leverage an API call that's already been written to check
+ # permissions appropriately.
+
+ my $a = create OpenSRF::AppSession("open-ils.actor");
+ my $method = "open-ils.actor.container.item_note.cud";
+
+ for my $note_key (@note_keys) {
+ my $note;
+
+ my $id = ($note_key =~ /(\d+)/)[0];
+
+ if (!($note =
+ $e->retrieve_container_biblio_record_entry_bucket_item_note($id))) {
+ my $event = $e->die_event;
+ $self->apache->log->warn(
+ "error retrieving cbrebin id $id, got event " .
+ $event->{textcode}
+ );
+ $a->kill_me;
+ $self->ctx->{bucket_action_event} = $event;
+ return;
+ }
+
+ if (length($self->cgi->param($note_key))) {
+ $note->ischanged(1);
+ $note->note($self->cgi->param($note_key));
+ } else {
+ $note->isdeleted(1);
+ }
+
+ my $r = $a->request($method, $e->authtoken, "biblio", $note)->gather(1);
+
+ if (defined $U->event_code($r)) {
+ $self->apache->log->warn(
+ "attempt to modify cbrebin " . $note->id .
+ " returned event " . $r->{textcode}
+ );
+ $e->rollback;
+ $a->kill_me;
+ $self->ctx->{bucket_action_event} = $r;
+ return;
+ }
+ }
+
+ for my $item_key (@item_keys) {
+ my $id = int(($item_key =~ /(\d+)/)[0]);
+ my $text = $self->cgi->param($item_key);
+
+ chomp $text;
+ next unless length $text;
+
+ my $note = new Fieldmapper::container::biblio_record_entry_bucket_item_note;
+ $note->isnew(1);
+ $note->item($id);
+ $note->note($text);
+
+ my $r = $a->request($method, $e->authtoken, "biblio", $note)->gather(1);
+
+ if (defined $U->event_code($r)) {
+ $self->apache->log->warn(
+ "attempt to create cbrebin for item " . $note->item .
+ " returned event " . $r->{textcode}
+ );
+ $e->rollback;
+ $a->kill_me;
+ $self->ctx->{bucket_action_event} = $r;
+ return;
+ }
+ }
+
+ $a->kill_me;
+ return 1; # success
+}
+
+sub load_myopac_bookbag_print {
+ my ($self) = @_;
+
+ $self->apache->content_type("text/plain; encoding=utf8");
+
+ my $id = int($self->cgi->param("list"));
+
+ my ($sorter, $modifier) = $self->_get_bookbag_sort_params;
+
+ my $item_search =
+ $self->_prepare_bookbag_container_query($id, $sorter, $modifier);
+
+ my $bbag;
+
+ # Get the bookbag object itself, assuming we're allowed to.
+ if ($self->editor->allowed("VIEW_CONTAINER")) {
+
+ $bbag = $self->editor->retrieve_container_biblio_record_entry_bucket($id) or return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
+ } else {
+ my $bookbags = $self->editor->search_container_biblio_record_entry_bucket(
+ {
+ "id" => $id,
+ "-or" => {
+ "owner" => $self->editor->requestor->id,
+ "pub" => "t"
+ }
+ }
+ ) or return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
+
+ $bbag = pop @$bookbags;
+ }
+
+ # If we have a bookbag we're allowed to look at, issue the A/T event
+ # to get CSV, passing as a user param that search query we built before.
+ if ($bbag) {
+ $self->ctx->{csv} = $U->fire_object_event(
+ undef, "container.biblio_record_entry_bucket.csv",
+ $bbag, $self->editor->requestor->home_ou,
+ undef, {"item_search" => $item_search}
+ );
+ }
+
+ return Apache2::Const::OK;
+}
+
+1;
$feed->id($bucket_tag);
$feed->title("Items in Book Bag [".$bucket->name."]");
+ $feed->description($bucket->description || ("Items in Book Bag [".$bucket->name."]"));
$feed->creator($host);
$feed->update_ts();
my $self = shift;
my $text = shift;
$self->_create_node('/rss/channel',undef,'title', $text);
- # RSS2 demands a /channel/description element; just dupe title until we give
- # users the ability to provide a description for their bookbags
+}
+
+sub description {
+ my $self = shift;
+ my $text = shift;
$self->_create_node('/rss/channel',undef,'description', $text);
}
INITIALLY DEFERRED,
name TEXT NOT NULL,
btype TEXT NOT NULL DEFAULT 'misc' REFERENCES container.copy_bucket_type (code) DEFERRABLE INITIALLY DEFERRED,
+ description TEXT,
pub BOOL NOT NULL DEFAULT FALSE,
create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
CONSTRAINT cb_name_once_per_owner UNIQUE (owner,name,btype)
INITIALLY DEFERRED,
name TEXT NOT NULL,
btype TEXT NOT NULL DEFAULT 'misc' REFERENCES container.call_number_bucket_type (code) DEFERRABLE INITIALLY DEFERRED,
+ description TEXT,
pub BOOL NOT NULL DEFAULT FALSE,
create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
CONSTRAINT cnb_name_once_per_owner UNIQUE (owner,name,btype)
INITIALLY DEFERRED,
name TEXT NOT NULL,
btype TEXT NOT NULL DEFAULT 'misc' REFERENCES container.biblio_record_entry_bucket_type (code) DEFERRABLE INITIALLY DEFERRED,
+ description TEXT,
pub BOOL NOT NULL DEFAULT FALSE,
create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
CONSTRAINT breb_name_once_per_owner UNIQUE (owner,name,btype)
INITIALLY DEFERRED,
name TEXT NOT NULL,
btype TEXT NOT NULL DEFAULT 'misc' REFERENCES container.user_bucket_type (code) DEFERRABLE INITIALLY DEFERRED,
+ description TEXT,
pub BOOL NOT NULL DEFAULT FALSE,
create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
CONSTRAINT ub_name_once_per_owner UNIQUE (owner,name,btype)
,( 47, 'record.queue.owner')
;
+INSERT INTO action_trigger.hook (key, core_type, description, passive)
+VALUES (
+ 'container.biblio_record_entry_bucket.csv',
+ 'cbreb',
+ oils_i18n_gettext(
+ 'container.biblio_record_entry_bucket.csv',
+ 'Produce a CSV file representing a bookbag',
+ 'ath',
+ 'description'
+ ),
+ FALSE
+);
+
+INSERT INTO action_trigger.reactor (module, description)
+VALUES (
+ 'ContainerCSV',
+ oils_i18n_gettext(
+ 'ContainerCSV',
+ 'Facilitates produce a CSV file representing a bookbag by introducing an "items" variable into the TT environment, sorted as dictated according to user params',
+ 'atr',
+ 'description'
+ )
+);
+
+INSERT INTO action_trigger.event_definition (
+ id, active, owner,
+ name, hook, reactor,
+ validator, template
+) VALUES (
+ 48, TRUE, 1,
+ 'Bookbag CSV', 'container.biblio_record_entry_bucket.csv', 'ContainerCSV',
+ 'NOOP_True',
+$$
+[%-
+# target is the bookbag itself. The 'items' variable does not need to be in
+# the environment because a special reactor will take care of filling it in.
+
+FOR item IN items;
+ bibxml = helpers.xml_doc(item.target_biblio_record_entry.marc);
+ title = "";
+ FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]');
+ title = title _ part.textContent;
+ END;
+ author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent;
+
+ helpers.csv_datum(title) %],[% helpers.csv_datum(author) %],[% FOR note IN item.notes; helpers.csv_datum(note.note); ","; END; "\n";
+END -%]
+$$
+);
+
SELECT SETVAL('authority.control_set_id_seq'::TEXT, 100);
SELECT SETVAL('authority.control_set_authority_field_id_seq'::TEXT, 1000);
SELECT SETVAL('authority.control_set_bib_field_id_seq'::TEXT, 1000);
--- /dev/null
+-- Evergreen DB patch YYYY.schema.bookbag-goodies.sql
+
+BEGIN;
+
+SELECT evergreen.upgrade_deps_block_check('YYYY', :eg_version);
+
+ALTER TABLE container.biblio_record_entry_bucket
+ ADD COLUMN description TEXT;
+
+ALTER TABLE container.call_number_bucket
+ ADD COLUMN description TEXT;
+
+ALTER TABLE container.copy_bucket
+ ADD COLUMN description TEXT;
+
+ALTER TABLE container.user_bucket
+ ADD COLUMN description TEXT;
+
+INSERT INTO action_trigger.hook (key, core_type, description, passive)
+VALUES (
+ 'container.biblio_record_entry_bucket.csv',
+ 'cbreb',
+ oils_i18n_gettext(
+ 'container.biblio_record_entry_bucket.csv',
+ 'Produce a CSV file representing a bookbag',
+ 'ath',
+ 'description'
+ ),
+ FALSE
+);
+
+INSERT INTO action_trigger.reactor (module, description)
+VALUES (
+ 'ContainerCSV',
+ oils_i18n_gettext(
+ 'ContainerCSV',
+ 'Facilitates produce a CSV file representing a bookbag by introducing an "items" variable into the TT environment, sorted as dictated according to user params',
+ 'atr',
+ 'description'
+ )
+);
+
+INSERT INTO action_trigger.event_definition (
+ id, active, owner,
+ name, hook, reactor,
+ validator, template
+) VALUES (
+ 48, TRUE, 1,
+ 'Bookbag CSV', 'container.biblio_record_entry_bucket.csv', 'ContainerCSV',
+ 'NOOP_True',
+$$
+[%-
+# target is the bookbag itself. The 'items' variable does not need to be in
+# the environment because a special reactor will take care of filling it in.
+
+FOR item IN items;
+ bibxml = helpers.xml_doc(item.target_biblio_record_entry.marc);
+ title = "";
+ FOR part IN bibxml.findnodes('//*[@tag="245"]/*[@code="a" or @code="b"]');
+ title = title _ part.textContent;
+ END;
+ author = bibxml.findnodes('//*[@tag="100"]/*[@code="a"]').textContent;
+
+ helpers.csv_datum(title) %],[% helpers.csv_datum(author) %],[% FOR note IN item.notes; helpers.csv_datum(note.note); ","; END; "\n";
+END -%]
+$$
+);
+
+COMMIT;
--- /dev/null
+[%- ctx.csv.template_output.data -%]
<tr>
<td>
<label for="list_create_name">[% l('Enter the name of the new list:') %]</label>
+ </td>
+ <td>
<input id="list_create_name" type="text" name="name" />
<input type="hidden" name="action" value="create" />
</td>
class="opac-button" />
</td>
</tr>
+ <tr>
+ <td class="text-right-top">
+ <label for="list_description">[% l("List description (optional):") %]</label>
+ </td>
+ <td colspan="3">
+ <textarea cols="40" rows="3" name="description"
+ id="list_description"></textarea>
+ </td>
</table>
</form>
+ <h2>[% l("Your existing lists") %]</h2>
+ <p>
+ <form method="GET">
+ <label for="opac.result.sort">[% l("Sort list items by: ") %]</label>
+ [% INCLUDE "opac/parts/filtersort.tt2"
+ value=CGI.param('sort') %]
+ <input type="submit" value="[% l('Sort') %]" />
+ </form>
+ </p>
+
[% INCLUDE "opac/parts/anon_list.tt2" %]
[% IF ctx.bookbags.size %]
<div id='acct_lists_prime'>
<input type="submit" value="[% l('Delete List') %]" />
</div>
</form>
+ <form action="[% ctx.opac_root %]/myopac/list/print" method="POST" target="_blank">
+ <div class="bookbag-controls">
+ <input type="hidden" name="list" value="[% bbag.id %]" />
+ <input type="hidden" name="sort" value="[% CGI.param('sort') | html %]" />
+ <input type="submit" value="[% l('Download CSV') %]" />
+ </div>
+ </form>
<div class="bookbag-controls">
<big><strong>
[% IF bbag.pub == 't' %]
[% bbag.name | html %]
[% END %]
</strong></big>
+ [% IF bbag.description %]<br /><em>[% bbag.description | html %]</em>[% END %]
</div>
<div class="bookbag-controls">
[% IF bbag.pub == 't'; %]
</div>
<form action="[% ctx.opac_root %]/myopac/list/update" method="POST">
<input type="hidden" name="list" value="[% bbag.id %]" />
+ <input type="hidden" name="sort" value="[% CGI.param('sort') | uri %]" />
<table cellpadding='0' cellspacing='0' border='0'>
<thead id="acct_list_header">
<tr>
inputs[i].checked = this.checked;}"/>
</td>
- <td width="49%" style="padding-left: 5px;">[% l('Title') %]</td>
- <td width="49%">[% l('Author(s)') %]</td>
+ <td width="32%" style="padding-left: 5px;">
+ <a href="[% ctx.opac_root %]/myopac/lists?sort=titlesort">[% l('Title') %]</a>
+ </td>
+ <td width="33%">
+ <a href="[% ctx.opac_root %]/myopac/lists?sort=authorsort">[% l('Author(s)') %]</a>
+ </td>
+ <td width="32%">
+ [% l('Notes') %]
+ [% IF CGI.param("edit_notes") != bbag.id %]
+ | <a href="[% ctx.opac_root %]/myopac/lists?[% IF CGI.param('sort'); 'sort='; (CGI.param('sort')) | uri; '&'; END %]edit_notes=[% bbag.id %]">[% l('Edit') %]</a>
+ [% END %]
+ </td>
<td width="1%" class="nowrap">
<select class="selector_actions_for_list" name="action">
<option>[% l('-- Actions for this list --') %]</option>
</td></tr>
[% END %]
[% FOR item IN bbag.items;
- rec_id = item.target_biblio_record_entry;
+ rec_id = item.target_biblio_record_entry.id;
attrs = {marc_xml => ctx.bookbags_marc_xml.$rec_id};
PROCESS get_marc_attrs args=attrs %]
- <tr>
+ <tr class="bookbag-item-row">
<td class="item_list_padding" style="padding-left: 10px;">
<input type="checkbox" name="selected_item" value="[% item.id %]" bbag='[% bbag.id %]'/>
</td>
authorquery = attrs.author | replace('[,\.:;]', '');
mkurl(ctx.opac_root _ '/results', {qtype => 'author', query => authorquery}, ['page'])
-%]">[% attrs.author | html %]</a>
+ [% IF CGI.param("edit_notes") == bbag.id %]
+ <td class="opac-auto-097b">
+ [% FOR note IN item.notes %]
+ <input type="text" name="note-[% note.id %]" value="[% note.note | html %]" />
+ [% END %]
+ <input type="text" name="item-[% item.id %]" />
+ </td>
+ [% ELSE %]
+ <td class="opac-auto-097b">
+ [% FOR note IN item.notes %]
+ <div>[% note.note | html %]</div>
+ [% END %]
+ </td>
+ [% END %]
+ </tr>
+ [% END %]
+ [% IF CGI.param("edit_notes") == bbag.id %]
+ <tr>
+ <td colspan="3"><!-- All space left of notes column --></td>
+ <td>
+ <input type="submit" name="save_notes" value="[% l('Save Notes') %]" />
</td>
</tr>
[% END %]
</tbody>
</table>
</form>
- <br /><br />
+ <hr /><br />
</div>
[% END %]
</div>
<tr>
<td align='center' width='100%'>
[% INCLUDE "opac/parts/filtersort.tt2"
- value=CGI.param('sort') %]
+ value=CGI.param('sort') class='results_header_sel' %]
</td>
</tr>
</table>
-<select class="results_header_sel" id='opac.result.sort' name="sort"
- [% IF submit_on_change %]onchange='this.form.submit()'[% END %]>
+<select [% class ? ('class="' _ class _ '"') : '' %] id='opac.result.sort' name="[% name || 'sort' %]" [% IF submit_on_change %]onchange='this.form.submit()'[% END %]>
<option value=''>[% l("Sort by Relevance") %]</option>
<optgroup label='[% l("Sort by Title") %]'>
<option value='titlesort'[% value == 'titlesort' ? ' selected="selected"' : '' %]>[% l("Title: A to Z") %]</option>
- <option value='titlesort.desc'[% value == 'titlesort.desc' ? ' selected="selected"' : '' %]>[% l("Title: Z to A") %]</option>
+ <option value='titlesort.descending'[% value == 'titlesort.descending' ? ' selected="selected"' : '' %]>[% l("Title: Z to A") %]</option>
</optgroup>
<optgroup label='[% l("Sort by Author") %]'>
<option value='authorsort'[% value == 'authorsort' ? ' selected="selected"' : '' %]>[% l("Author: A to Z") %]</option>
- <option value='authorsort.desc'[% value == 'authorsort.desc' ? ' selected="selected"' : '' %]>[% l("Author: Z to A") %]</option>
+ <option value='authorsort.descending'[% value == 'authorsort.descending' ? ' selected="selected"' : '' %]>[% l("Author: Z to A") %]</option>
</optgroup>
<optgroup label='[% l("Sort by Publication Date") %]'>
- <option value='pubdate.desc'[% value == 'pubdate.desc' ? ' selected="selected"' : '' %]>[% l("Date: Newest to Oldest") %]</option>
+ <option value='pubdate.descending'[% value == 'pubdate.descending' ? ' selected="selected"' : '' %]>[% l("Date: Newest to Oldest") %]</option>
<option value='pubdate'[% value == 'pubdate' ? ' selected="selected"' : '' %]>[% l("Date: Oldest to Newest") %]</option>
</optgroup>
</select>
.hold-editor-controls a { padding-left: 2em; }
.text-right { text-align: right; }
+.text-right-top { text-align: right; vertical-align: top; }
.rdetail-author-div { padding-bottom: 10px; }
.invisible { visibility: hidden; }
}
+.bookbag-item-row td { vertical-align: top; }