From: Bill Erickson <berick@esilibrary.com> Date: Fri, 25 May 2012 13:22:36 +0000 (-0400) Subject: Search filter groups admin UI / API X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=42505e6c89ef51cdba62d112222739114e8de8dd;p=evergreen%2Fmasslnc.git Search filter groups admin UI / API 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> --- diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index 71706383d8..eb75532675 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -4934,13 +4934,13 @@ SELECT usr, </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"> diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm index 50cff13551..660cebd2e7 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm @@ -4644,4 +4644,116 @@ sub get_all_at_reactors_in_use { 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; diff --git a/Open-ILS/src/templates/conify/global/actor/search_filter_group.tt2 b/Open-ILS/src/templates/conify/global/actor/search_filter_group.tt2 new file mode 100644 index 0000000000..4118015256 --- /dev/null +++ b/Open-ILS/src/templates/conify/global/actor/search_filter_group.tt2 @@ -0,0 +1,104 @@ +[% + 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 %] + + diff --git a/Open-ILS/web/js/ui/default/conify/global/actor/search_filter_group.js b/Open-ILS/web/js/ui/default/conify/global/actor/search_filter_group.js new file mode 100644 index 0000000000..3aadee78ae --- /dev/null +++ b/Open-ILS/web/js/ui/default/conify/global/actor/search_filter_group.js @@ -0,0 +1,182 @@ +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); +