UI for managing search filter groups and group entries.
This inclues a new API call for performing CRUD actions on filter
group entries:
open-ils.actor.filter_group_entry.crud
This new API call was necessary because entries link to
actor.search_query's, whose write access cannot be controled by pcrud.
Signed-off-by: Bill Erickson <berick@esilibrary.com>
Signed-off-by: Kathy Lussier <klussier@masslnc.org>
Signed-off-by: Dan Scott <dscott@laurentian.ca>
</class>
<class id="asfge" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="actor::search_filter_group_entry" oils_persist:tablename="actor.search_filter_group_entry" reporter:label="Search Filter Group Entry" oils_persist:field_safe="true">
<fields oils_persist:primary="id" oils_persist:sequence="actor.search_filter_group_entry_id_seq">
- <field name="id" reporter:datatype="id" reporter:selector="label"/>
+ <field name="id" reporter:datatype="id"/>
<field name="grp" reporter:datatype="link"/>
<field name="pos" reporter:datatype="int"/>
<field name="query" reporter:datatype="link"/>
</fields>
<links>
- <link field="grp" reltype="has_a" key="id" map="" class="asfge"/>
+ <link field="grp" reltype="has_a" key="id" map="" class="asfg"/>
<link field="query" reltype="has_a" key="id" map="" class="asq"/>
</links>
<permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
return [ map { $_->{reactor} } @$reactors ];
}
+__PACKAGE__->register_method(
+ method => "filter_group_entry_crud",
+ api_name => "open-ils.actor.filter_group_entry.crud",
+ signature => {
+ desc => q/
+ Provides CRUD access to filter group entry objects. These are not full accessible
+ via PCRUD, since they requre "asq" objects for storing the query, and "asq" objects
+ are not accessible via PCRUD (because they have no fields against which to link perms)
+ /,
+ params => [
+ {desc => "Authentication token", type => "string"},
+ {desc => "Entry ID / Entry Object", type => "number"},
+ {desc => "Additional note text (optional)", type => "string"},
+ {desc => "penalty org unit ID (optional, default to top of org tree)",
+ type => "number"}
+ ],
+ return => {
+ desc => "Entry fleshed with query on Create, Retrieve, and Uupdate. 1 on Delete",
+ type => "object"
+ }
+ }
+);
+
+sub filter_group_entry_crud {
+ my ($self, $conn, $auth, $arg) = @_;
+
+ return OpenILS::Event->new('BAD_PARAMS') unless $arg;
+ my $e = new_editor(authtoken => $auth, xact => 1);
+ return $e->die_event unless $e->checkauth;
+
+ if (ref $arg) {
+
+ if ($arg->isnew) {
+
+ my $grp = $e->retrieve_actor_search_filter_group($arg->grp)
+ or return $e->die_event;
+
+ return $e->die_event unless $e->allowed(
+ 'ADMIN_SEARCH_FILTER_GROUP', $grp->owner);
+
+ my $query = $arg->query;
+ $query = $e->create_actor_search_query($query) or return $e->die_event;
+ $arg->query($query->id);
+ my $entry = $e->create_actor_search_filter_group_entry($arg) or return $e->die_event;
+ $entry->query($query);
+
+ $e->commit;
+ return $entry;
+
+ } elsif ($arg->ischanged) {
+
+ my $entry = $e->retrieve_actor_search_filter_group_entry([
+ $arg->id, {
+ flesh => 1,
+ flesh_fields => {asfge => ['grp']}
+ }
+ ]) or return $e->die_event;
+
+ return $e->die_event unless $e->allowed(
+ 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
+
+ my $query = $e->update_actor_search_query($arg->query) or return $e->die_event;
+ $arg->query($arg->query->id);
+ $e->update_actor_search_filter_group_entry($arg) or return $e->die_event;
+ $arg->query($query);
+
+ $e->commit;
+ return $arg;
+
+ } elsif ($arg->isdeleted) {
+
+ my $entry = $e->retrieve_actor_search_filter_group_entry([
+ $arg->id, {
+ flesh => 1,
+ flesh_fields => {asfge => ['grp', 'query']}
+ }
+ ]) or return $e->die_event;
+
+ return $e->die_event unless $e->allowed(
+ 'ADMIN_SEARCH_FILTER_GROUP', $entry->grp->owner);
+
+ $e->delete_actor_search_filter_group_entry($entry) or return $e->die_event;
+ $e->delete_actor_search_query($entry->query) or return $e->die_event;
+
+ $e->commit;
+ return 1;
+
+ } else {
+
+ $e->rollback;
+ return undef;
+ }
+
+ } else {
+
+ my $entry = $e->retrieve_actor_search_filter_group_entry([
+ $arg, {
+ flesh => 1,
+ flesh_fields => {asfge => ['grp', 'query']}
+ }
+ ]) or return $e->die_event;
+
+ return $e->die_event unless $e->allowed(
+ ['ADMIN_SEARCH_FILTER_GROUP', 'VIEW_SEARCH_FILTER_GROUP'],
+ $entry->grp->owner);
+
+ $e->rollback;
+ $entry->grp($entry->grp->id); # for consistency
+ return $entry;
+ }
+}
+
1;
--- /dev/null
+[%
+ WRAPPER base.tt2;
+ ctx.page_title = l('Search Filter Group');
+ filter_group_id = ctx.page_args.0;
+%]
+
+<style>
+ #fge-edit-div {margin-top: 20px;}
+ #fge-edit-div td {padding : 5px;}
+</style>
+
+<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+
+ [% IF filter_group_id %]
+
+ <div dojoType="dijit.layout.ContentPane" layoutAlign="top" class='oils-header-panel'>
+ <div>[% l('Search Filter Group') %]</div>
+ <div>
+ <button dojoType='dijit.form.Button' onClick='showFgeEditor(null, true)'>[% l('New') %]</button>
+ <button dojoType='dijit.form.Button' onClick='fgeDelete()'>[% l('Delete Selected') %]</button>
+ </div>
+ </div>
+
+ <a href="[% ctx.base_path %]/conify/global/actor/search_filter_group">↖ [% l('Return to Filter Groups') %]</a>
+ <br/> <br/>
+
+ <table
+ id="fgeGrid"
+ jsid="fgeGrid"
+ dojoType="openils.widget.FlattenerGrid"
+ columnPersistKey='"conify.actor.search_filter_group_entry"'
+ autoHeight="true"
+ showLoadFilter="true"
+ fmClass="'asfge'"
+ defaultSort="['pos']"
+ _mapExtras="{id : {path : 'id'}}"
+ query="{'grp': ['[% filter_group_id %]']}">
+ <thead>
+ <tr>
+ <th field="query_label" fpath="query.label" ffilter="true" get='getFgeLabel' formatter='formatFgeLabel'>[% l('Label') %]</th>
+ <th field="query_text" fpath="query.query_text" ffilter="true">[% l('Query Text') %]</th>
+ <th field="pos">[% l('Sort Position') %]</th>
+ <!-- mapExtras should cover "id", but it's not working; investigate.. -->
+ <th field="id">[% l('ID') %]</th>
+ </tr>
+ </thead>
+ </table>
+
+ <div id='fge-edit-div' class='hidden'>
+ <table>
+ <tr><td>[% l('Label') %]</td><td><div id='fge-edit-label'></div></td></tr>
+ <tr><td>[% l('Query') %]</td><td><div id='fge-edit-query'></div></td></tr>
+ <tr><td>[% l('Position') %]</td><td><div id='fge-edit-pos'></div></td></tr>
+ <tr>
+ <td><button dojoType='dijit.form.Button' jsId='fgeSave'>[% l('Save') %]</button></td>
+ <td><button dojoType='dijit.form.Button' jsId='fgeCancel'>[% l('Cancel') %]</button></td>
+ </tr>
+ </table>
+ </div>
+
+ [% ELSE %]
+
+ <div dojoType="dijit.layout.ContentPane" layoutAlign="top" class='oils-header-panel'>
+ <div>[% l('Search Filter Group') %]</div>
+ <div>
+ <button dojoType='dijit.form.Button' onClick='fgGrid.showCreateDialog()'>[% l('New') %]</button>
+ <button dojoType='dijit.form.Button' onClick='fgGrid.deleteSelected()'>[% l('Delete Selected') %]</button>
+ </div>
+ </div>
+
+ <table
+ id="fgGrid"
+ jsid="fgGrid"
+ dojoType="openils.widget.FlattenerGrid"
+ columnPersistKey='"conify.actor.search_filter_group"'
+ autoHeight="true"
+ editOnEnter="true"
+ editStyle="pane"
+ showLoadFilter="true"
+ fmClass="'asfg'"
+ defaultSort="['code']"
+ mapExtras="{owner: {path: 'owner.id'}}"
+ fetchLock="true">
+ <thead>
+ <tr>
+ <th field="code" fpath="code" ffilter="true" get='getFgCode' formatter='formatFgCode'>[% l('Code') %]</th>
+ <th field="owner_sn" fpath="owner.shortname" ffilter="true">[% l('Owner') %]</th>
+ <th field="label" fpath="label" ffilter="true">[% l('Label') %]</th>
+ <th field="create_date" fpath="create_date" ffilter="true">[% l('Create Date') %]</th>
+ </tr>
+ </thead>
+ </table>
+
+ <br/><br/>
+ <div><i>[% l('To view groups for a different location, use the "Filter" option') %]</i></div>
+
+ [% END %]
+</div>
+
+<script type="text/javascript">var filterGroupId = '[% filter_group_id %]'</script>
+<script type="text/javascript" src='[% ctx.media_prefix %]/js/ui/default/conify/global/actor/search_filter_group.js'> </script>
+[% END %]
+
+
--- /dev/null
+dojo.require('dijit.form.TextBox');
+dojo.require('openils.Util');
+dojo.require('openils.User');
+dojo.require('fieldmapper.OrgUtils');
+dojo.require("openils.widget.FlattenerGrid");
+dojo.require('openils.widget.AutoFieldWidget');
+dojo.require('openils.PermaCrud');
+
+var fgeEditLabel, fgeEditQuery, fgeEditPos;
+var curEntry, ocHandler;
+
+// Builds an editor table for filter group entries
+function showFgeEditor(fgeId, create) {
+
+ dojo.addClass(fgeGrid.domNode, 'hidden');
+ dojo.removeClass(dojo.byId('fge-edit-div'), 'hidden');
+
+ function cancelHandler() {
+ dojo.removeClass(fgeGrid.domNode, 'hidden');
+ dojo.addClass(dojo.byId('fge-edit-div'), 'hidden');
+ }
+
+ if (!fgeEditLabel) {
+
+ // first time loading the editor. build the widgets.
+
+ fgeEditLabel = new openils.widget.AutoFieldWidget({
+ fmField : 'label',
+ fmClass : 'asq',
+ parentNode : dojo.byId('fge-edit-label')
+ });
+
+ fgeEditLabel.build();
+
+ fgeEditQuery = new openils.widget.AutoFieldWidget({
+ fmField : 'query_text',
+ fmClass : 'asq',
+ parentNode : dojo.byId('fge-edit-query')
+ });
+
+ fgeEditQuery.build();
+
+ fgeEditPos = new openils.widget.AutoFieldWidget({
+ fmField : 'pos',
+ fmClass : 'asfge',
+ parentNode : dojo.byId('fge-edit-pos')
+ });
+
+ fgeEditPos.build();
+ dojo.connect(fgeCancel, 'onClick', cancelHandler);
+ }
+
+ var pcrud = new openils.PermaCrud({authtoken : openils.User.authtoken});
+
+ if (create) {
+
+ curEntry = new fieldmapper.asfge();
+ curEntry.isnew(true);
+ curEntry.grp(filterGroupId);
+ curEntry.query(new fieldmapper.asq());
+
+ fgeEditLabel.widget.attr('value', '');
+ fgeEditQuery.widget.attr('value', '');
+ fgeEditPos.widget.attr('value', '');
+
+ } else {
+
+ // we're editing an existing entry, fetch it first
+
+ curEntry = fieldmapper.standardRequest(
+ ['open-ils.actor', 'open-ils.actor.filter_group_entry.crud'],
+ {params : [openils.User.authtoken, fgeId], async : false}
+ );
+
+ fgeEditLabel.widget.attr('value', curEntry.query().label());
+ fgeEditQuery.widget.attr('value', curEntry.query().query_text());
+ fgeEditPos.widget.attr('value', curEntry.pos());
+ curEntry.ischanged(true);
+ }
+
+ if (ocHandler) dojo.disconnect(ocHandler);
+ ocHandler = dojo.connect(fgeSave, 'onClick',
+ function() {
+
+ // creates / updates entries
+
+ curEntry.query().label(fgeEditLabel.widget.attr('value'));
+ curEntry.query().query_text(fgeEditQuery.widget.attr('value'));
+ curEntry.pos(fgeEditPos.widget.attr('value'));
+
+ var stat = fieldmapper.standardRequest(
+ ['open-ils.actor', 'open-ils.actor.filter_group_entry.crud'],
+ {params : [openils.User.authtoken, curEntry], async : false}
+ );
+
+ cancelHandler();
+ fgeGrid.refresh();
+ }
+ );
+}
+
+// deletes filter group entries (after fetching them first)
+function fgeDelete() {
+
+ dojo.forEach(
+ fgeGrid.getSelectedItems(),
+ function(item) {
+
+ console.log(item);
+ var id = fgeGrid.store.getValue(item, 'id');
+
+ var entry = fieldmapper.standardRequest(
+ ['open-ils.actor', 'open-ils.actor.filter_group_entry.crud'],
+ {params : [openils.User.authtoken, id], async : false}
+ );
+
+ entry.isdeleted(true);
+
+ var stat = fieldmapper.standardRequest(
+ ['open-ils.actor', 'open-ils.actor.filter_group_entry.crud'],
+ {params : [openils.User.authtoken, entry], async : false}
+ );
+ }
+ );
+
+ fgeGrid.refresh();
+}
+
+// builds a link to show the editor table
+function getFgeLabel(rowIdx, item) {
+ if (item) {
+ return {
+ id : this.grid.store.getValue(item, 'id'),
+ label : this.grid.store.getValue(item, 'query_label')
+ };
+ }
+}
+
+function formatFgeLabel(args) {
+ if (!args) return '';
+ return '<a href="javascript:showFgeEditor(' + args.id + ')">' + args.label + '</a>';
+}
+
+// builds a link to this group's entries page
+function getFgCode(rowIdx, item) {
+ if (item) {
+ return {
+ id : this.grid.store.getValue(item, 'id'),
+ code : this.grid.store.getValue(item, 'code')
+ };
+ }
+}
+
+function formatFgCode(args) {
+ if (!args) return '';
+ return '<a href="' + oilsBasePath + '/conify/global/actor/search_filter_group/' + args.id + '">' + args.code + '</a>';
+}
+
+function load() {
+
+ if (filterGroupId) {
+
+ // entries grid loads itself from template data.
+ // nothing for us to do.
+
+ } else {
+
+ // filter groups by where we have edit permission
+ new openils.User().getPermOrgList(
+ ['ADMIN_SEARCH_FILTER_GROUP'],
+ function(list) {
+ fgGrid.query = {owner : list};
+ fgGrid.refresh();
+ fgGrid.suppressEditFields = ['id', 'create_date'];
+ },
+ false, true
+ );
+ }
+}
+
+openils.Util.addOnLoad(load);
+