Search filter groups admin UI / API
authorBill Erickson <berick@esilibrary.com>
Fri, 25 May 2012 13:22:36 +0000 (09:22 -0400)
committerDan Scott <dscott@laurentian.ca>
Wed, 1 Aug 2012 21:25:14 +0000 (17:25 -0400)
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>
Open-ILS/examples/fm_IDL.xml
Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm
Open-ILS/src/templates/conify/global/actor/search_filter_group.tt2 [new file with mode: 0644]
Open-ILS/web/js/ui/default/conify/global/actor/search_filter_group.js [new file with mode: 0644]

index 7170638..eb75532 100644 (file)
@@ -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">
index 50cff13..660cebd 100644 (file)
@@ -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 (file)
index 0000000..4118015
--- /dev/null
@@ -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">&#x2196; [% 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 (file)
index 0000000..3aadee7
--- /dev/null
@@ -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);
+