[% BLOCK APP_JS %]
<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/list.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/grid.js"></script>
<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/ui.js"></script>
<script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/bucket/record/app.js"></script>
[% END %]
<li ng-class="{active : tab == 'search'}">
<a href="./cat/bucket/record/search/{{bucketSvc.currentBucket.id()}}">
[% l('Record Query') %]
- <span ng-cloak>({{bucketSvc.searchList.totalCount}})</span>
+ <span ng-cloak>({{bucketSvc.queryRecords.length}})</span>
</a>
</li>
<li ng-class="{active : tab == 'pending'}">
<a href="./cat/bucket/record/pending/{{bucketSvc.currentBucket.id()}}">
[% l('Pending Records') %]
- <span ng-cloak>({{bucketSvc.pendingList.count()}})</span>
+ <span ng-cloak>({{bucketSvc.pendingList.length}})</span>
</a>
</li>
<li ng-class="{active : tab == 'view'}">
<a href="./cat/bucket/record/view/{{bucketSvc.currentBucket.id()}}">
[% l('Bucket View') %]
- <span ng-cloak>({{bucketSvc.viewList.totalCount}})</span>
+ <span ng-cloak>({{bucketSvc.currentBucket.items().length}})</span>
</a>
</li>
</ul>
<!-- use <form> so we get submit-on-enter for free -->
<form class="form-validated" novalidate name="form" ng-submit="ok(args)">
- <div class="modal-dialog">
- <div class="modal-content">
- <div class="modal-header">
- <button type="button" class="close"
- ng-click="cancel()" aria-hidden="true">×</button>
- <h4 class="modal-title">[% l('Create Bucket') %]</h4>
+ <div>
+ <div class="modal-header">
+ <button type="button" class="close"
+ ng-click="cancel()" aria-hidden="true">×</button>
+ <h4 class="modal-title">[% l('Create Bucket') %]</h4>
+ </div>
+ <div class="modal-body">
+ <div class="form-group">
+ <label for="edit-bucket-name">[% l('Name') %]</label>
+ <input type="text" class="form-control" focus-me='focusMe' required
+ id="edit-bucket-name" ng-model="args.name" placeholder="[% l('Name...') %]"/>
</div>
- <div class="modal-body">
- <div class="form-group">
- <label for="edit-bucket-name">[% l('Name') %]</label>
- <input type="text" class="form-control" focus-me='focusMe' required
- id="edit-bucket-name" ng-model="args.name" placeholder="[% l('Name...') %]"/>
- </div>
- <div class="form-group">
- <label for="edit-bucket-desc">[% l('Description') %]</label>
- <input type="text" class="form-control" id="edit-bucket-desc"
- ng-model="args.desc" placeholder="[% l('Description...') %]"/>
- </div>
- <div class="checkbox">
- <label>
- <input ng-model="args.pub" type="checkbox"/>
- [% l('Publicly Visible?') %]
- </label>
- </div>
+ <div class="form-group">
+ <label for="edit-bucket-desc">[% l('Description') %]</label>
+ <input type="text" class="form-control" id="edit-bucket-desc"
+ ng-model="args.desc" placeholder="[% l('Description...') %]"/>
</div>
- <div class="modal-footer">
- <input type="submit" ng-disabled="form.$invalid"
- class="btn btn-primary" value="[% l('Create Bucket') %]"/>
- <button class="btn btn-warning" ng-click="cancel()">[% l('Cancel') %]</button>
+ <div class="checkbox">
+ <label>
+ <input ng-model="args.pub" type="checkbox"/>
+ [% l('Publicly Visible?') %]
+ </label>
</div>
- </div> <!-- modal-content -->
- </div> <!-- modal-dialog -->
+ </div>
+ <div class="modal-footer">
+ <input type="submit" ng-disabled="form.$invalid"
+ class="btn btn-primary" value="[% l('Create Bucket') %]"/>
+ <button class="btn btn-warning" ng-click="cancel()">[% l('Cancel') %]</button>
+ </div>
+ </div> <!-- modal-content -->
</form>
<!-- edit bucket dialog -->
<form class="form-validated" novalidate ng-submit="ok(args)" name="form">
- <div class="modal-dialog">
- <div class="modal-content">
- <div class="modal-header">
- <button type="button" class="close"
- ng-click="cancel()" aria-hidden="true">×</button>
- <h4 class="modal-title">[% l('Edit Bucket') %]</h4>
+ <div>
+ <div class="modal-header">
+ <button type="button" class="close"
+ ng-click="cancel()" aria-hidden="true">×</button>
+ <h4 class="modal-title">[% l('Edit Bucket') %]</h4>
+ </div>
+ <div class="modal-body">
+ <div class="form-group">
+ <label for="edit-bucket-name">[% l('Name') %]</label>
+ <input type="text" class="form-control" focus-me='focusMe' required
+ id="edit-bucket-name" ng-model="args.name" placeholder="[% l('Name...') %]"/>
</div>
- <div class="modal-body">
- <div class="form-group">
- <label for="edit-bucket-name">[% l('Name') %]</label>
- <input type="text" class="form-control" focus-me='focusMe' required
- id="edit-bucket-name" ng-model="args.name" placeholder="[% l('Name...') %]"/>
- </div>
- <div class="form-group">
- <label for="edit-bucket-desc">[% l('Description') %]</label>
- <input type="text" class="form-control" id="edit-bucket-desc"
- ng-model="args.desc" placeholder="[% l('Description...') %]"/>
- </div>
- <div class="checkbox">
- <label>
- <input ng-model="args.pub" type="checkbox">
- [% l('Publicly Visible?') %]
- </label>
- </div>
+ <div class="form-group">
+ <label for="edit-bucket-desc">[% l('Description') %]</label>
+ <input type="text" class="form-control" id="edit-bucket-desc"
+ ng-model="args.desc" placeholder="[% l('Description...') %]"/>
</div>
- <div class="modal-footer">
- <input type="submit" class="btn btn-primary"
- ng-disabled="form.$invalid" value="[% l('Apply Changes') %]"/>
- <button class="btn btn-warning" ng-click="cancel()"
- ng-class="{disabled : actionPending}">[% l('Cancel') %]</button>
+ <div class="checkbox">
+ <label>
+ <input ng-model="args.pub" type="checkbox">
+ [% l('Publicly Visible?') %]
+ </label>
</div>
- </div> <!-- modal-content -->
- </div> <!-- modal-dialog -->
+ </div>
+ <div class="modal-footer">
+ <input type="submit" class="btn btn-primary"
+ ng-disabled="form.$invalid" value="[% l('Apply Changes') %]"/>
+ <button class="btn btn-warning" ng-click="cancel()"
+ ng-class="{disabled : actionPending}">[% l('Cancel') %]</button>
+ </div>
+ </div> <!-- modal-content -->
</form>
<!-- export bucket dialog -->
<form ng-submit="ok(args)">
- <div class="modal-dialog">
- <div class="modal-content">
- <div class="modal-header">
- <button type="button" class="close"
- ng-click="cancel()" aria-hidden="true">×</button>
- <h4 class="modal-title">[% l('Export Records') %]</h4>
+ <div>
+ <div class="modal-header">
+ <button type="button" class="close"
+ ng-click="cancel()" aria-hidden="true">×</button>
+ <h4 class="modal-title">[% l('Export Records') %]</h4>
+ </div>
+ <div class="modal-body">
+ <div class="form-group">
+ <label for="export-bucket-format">[% l('Record Format') %]</label>
+ <select class="form-control" ng-model="args.format" id="export-bucket-format">
+ <option value="XML">[% l('MARC XML') %]</option>
+ <option value="USMARC">[% l('USMARC') %]</option>
+ <option value="UNIMARC">[% l('UNIMARC') %]</option>
+ <option value="BRE">[% l('Evergreen Record Entry') %]</option>
+ </select>
</div>
- <div class="modal-body">
- <div class="form-group">
- <label for="export-bucket-format">[% l('Record Format') %]</label>
- <select class="form-control" ng-model="args.format" id="export-bucket-format">
- <option value="XML">[% l('MARC XML') %]</option>
- <option value="USMARC">[% l('USMARC') %]</option>
- <option value="UNIMARC">[% l('UNIMARC') %]</option>
- <option value="BRE">[% l('Evergreen Record Entry') %]</option>
- </select>
- </div>
- <div class="form-group">
- <label for="export-bucket-encoding">[% l('Encoding') %]</label>
- <select class="form-control" ng-model="args.encoding" id="export-bucket-encoding">
- <option value="UTF-8">[% l('UTF-8') %]</option>
- <option value="MARC8">[% l('MARC8') %]</option>
- </select>
- </div>
-
- <div class="checkbox">
- <label>
- <input ng-model="args.holdings" type="checkbox">
- [% l('Include Items?') %]
- </label>
- </div>
+ <div class="form-group">
+ <label for="export-bucket-encoding">[% l('Encoding') %]</label>
+ <select class="form-control" ng-model="args.encoding" id="export-bucket-encoding">
+ <option value="UTF-8">[% l('UTF-8') %]</option>
+ <option value="MARC8">[% l('MARC8') %]</option>
+ </select>
</div>
- <div class="modal-footer">
- <input type="submit" class="btn btn-primary"
- ng-click="ok(args)" value="[% l('Export') %]"/>
- <button class="btn btn-warning"
- ng-click="cancel()">[% l('Cancel') %]</button>
+
+ <div class="checkbox">
+ <label>
+ <input ng-model="args.holdings" type="checkbox">
+ [% l('Include Items?') %]
+ </label>
</div>
- </div> <!-- modal-content -->
- </div> <!-- modal-dialog -->
+ </div>
+ <div class="modal-footer">
+ <input type="submit" class="btn btn-primary"
+ ng-click="ok(args)" value="[% l('Export') %]"/>
+ <button class="btn btn-warning"
+ ng-click="cancel()">[% l('Cancel') %]</button>
+ </div>
+ </div> <!-- modal-content -->
</form>
<div ng-show="bucket()">
- <strong>[% l('Bucket {{bucket().name()}}') %]</strong>
+ <strong>[% l('Bucket: {{bucket().name()}}') %]</strong>
<span>
- <ng-pluralize count="bucketSvc.viewList.totalCount"
+ <ng-pluralize count="bucketSvc.currentBucket.items().length"
when="{'one': '[% l("1 item") %]', 'other': '[% l("{} items") %]'}">
</ng-pluralize>
</span>
<!-- load bucket by id ("shared") -->
<form class="form-validated" novalidate name="form" ng-submit="ok(args)">
- <div class="modal-dialog">
- <div class="modal-content">
- <div class="modal-header">
- <button type="button" class="close"
- ng-click="cancel()" aria-hidden="true">×</button>
- <h4 class="modal-title">[% l('Load Shared Bucket Bucket by ID') %]</h4>
+ <div>
+ <div class="modal-header">
+ <button type="button" class="close"
+ ng-click="cancel()" aria-hidden="true">×</button>
+ <h4 class="modal-title">[% l('Load Shared Bucket Bucket by ID') %]</h4>
+ </div>
+ <div class="modal-body">
+ <div class="form-group">
+ <label for="load-bucket-id">[% l('Bucket ID') %]</label>
+ <!-- NOTE: type='number' / required -->
+ <input type="number" class="form-control" focus-me='focusMe' required
+ id="load-bucket-id" ng-model="args.id" placeholder="[% l('Bucket ID...') %]"/>
</div>
- <div class="modal-body">
- <div class="form-group">
- <label for="load-bucket-id">[% l('Bucket ID') %]</label>
- <!-- NOTE: type='number' / required -->
- <input type="number" class="form-control" focus-me='focusMe' required
- id="load-bucket-id" ng-model="args.id" placeholder="[% l('Bucket ID...') %]"/>
- </div>
- </div>
- <div class="modal-footer">
- <input type="submit" ng-disabled="form.$invalid"
- class="btn btn-primary" value="[% l('Load Bucket') %]"/>
- <button class="btn btn-warning"
- ng-click="cancel()">[% l('Cancel') %]</button>
- </div>
- </div> <!-- modal-content -->
- </div> <!-- modal-dialog -->
+ </div>
+ <div class="modal-footer">
+ <input type="submit" ng-disabled="form.$invalid"
+ class="btn btn-primary" value="[% l('Load Bucket') %]"/>
+ <button class="btn btn-warning"
+ ng-click="cancel()">[% l('Cancel') %]</button>
+ </div>
+ </div> <!-- modal-content -->
</form>
-<br/>
-
<div class="row">
<div class="col-md-6">
[% INCLUDE 'staff/cat/bucket/record/t_bucket_info.tt2' %]
</div>
+</div>
- <div class="col-md-6 text-right">
- [% INCLUDE 'staff/cat/bucket/record/t_bucket_selector.tt2' %]
-
- <div class="btn-group text-left">
- <!-- first page -->
- <button type="button" class="btn btn-default"
- ng-class="{disabled : pageList.onFirstPage()}"
- ng-click="pageList.offset = 0;draw()">[% l('Start') %]</button>
-
- <!-- previous page -->
- <button type="button" class="btn btn-default"
- ng-class="{disabled : pageList.onFirstPage()}"
- ng-click="pageList.decrementPage();draw()">«</button>
-
- <!-- next page -->
- <!-- todo: paging needs a total count value to be fully functional -->
- <button type="button" class="btn btn-default"
- ng-class="{disabled : !pageList.hasNextPage()}"
- ng-click="pageList.incrementPage();draw()">»</button>
+<div class="col-md-10 col-md-offset-1" ng-show="forbidden">
+ <div class="alert alert-warning">
+ [% l('The selected bucket "{{bucketId}}" is not visible to this login.') %]
+ </div>
+</div>
- <div class="btn-group">
- <button type="button" class="btn btn-default dropdown-toggle"
- ng-class="{disabled : action_pending}" data-toggle="dropdown">
- [% l('Actions') %] <span class="caret"></span>
- </button>
- <ul class="dropdown-menu pull-right">
+<eg-grid
+ ng-hide="forbidden"
+ id-field="id"
+ idl-class="rmsr"
+ auto-fields="true"
+ items-provider="gridDataProvider"
+ menu-label="[% l('Buckets') %]"
+ persist-key="eg.staff.cat.bucket.record.view">
+
+ <!-- global menu -->
+ <eg-grid-menu-item label="[% l('New Bucket') %]"
+ handler="openCreateBucketDialog"></eg-grid-menu-item>
+ <eg-grid-menu-item label="[% l('Edit Bucket') %]"
+ handler="openEditBucketDialog"></eg-grid-menu-item>
+ <eg-grid-menu-item label="[% l('Delete Bucket') %]"
+ handler="openDeleteBucketDialog"></eg-grid-menu-item>
+ <eg-grid-menu-item label="[% l('Shared Bucket') %]"
+ handler="openSharedBucketDialog"></eg-grid-menu-item>
+ <eg-grid-menu-item divider="true"></eg-grid-menu-item>
+ <eg-grid-menu-item ng-repeat="bkt in bucketSvc.allBuckets"
+ label="{{bkt.name()}}" handler-data="bkt"
+ handler="loadBucketFromMenu"></eg-grid-menu-item>
+
+ <!-- actions drop-down -->
+ <eg-grid-action label="[% l('Add To Bucket') %]"
+ handler="addToBucket"></eg-grid-action>
+ <eg-grid-action label="[% l('Clear List') %]"
+ handler="clearPendingList"></eg-grid-action>
+
+</eg-grid>
+
+<!--
<li ng-class="{disabled : !bucket() || !pageList.selectedItems().length}">
<a href="javascript:;" ng-click="addToBucket()">[% l('Add Selected To Bucket') %]</a>
<li ng-class="{disabled : !pageList.count()}">
<a href="javascript:;" ng-click="pageList.reset()">[% l('Clear List') %]</a>
</li>
- </ul>
- </div>
+-->
- <div class="btn-group col-picker">
- <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
- <span class="caret"></span>
- </button>
- <ul class="dropdown-menu pull-right">
- <li ng-repeat="col in pageList.allColumns">
- <a href='javascript:;'
- ng-click="pageList.displayColumns[col.name] =
- !pageList.displayColumns[col.name]">
- <span ng-if="pageList.displayColumns[col.name]" class="label label-success">✓</span>
- <span ng-if="!pageList.displayColumns[col.name]" class="label label-warning">✗</span>
- <span>{{col.label}}</span>
- </a>
- </li>
- <li role="presentation" class="divider"></li>
- <li>
- <a href='javascript:;' ng-click="pageList.showAllColumns()">[% l('Show All Columns') %]</a>
- </li>
- <li>
- <a href='javascript:;' ng-click="pageList.hideAllColumns()">[% l('Hide All Columns') %]</a>
- </li>
- <li class='disabled'>
- <a href='javascript:;' ng-click="">[% l('Save Columns') %]</a>
- </li>
- </ul>
- </div>
- </div>
- </div>
-</div>
-
-<br/>
-
-<div class="row">
- <div class="col-md-12">
-
- <table class="list table table-hover _table-striped table-condensed"
- ng-show="pageList.count()"
- ng-init="pageList.defaultColumns([
- 'id', 'author', 'isbn', 'issn', 'pubdate',
- 'publisher', 'tcn_value', 'title'
- ])">
- <thead>
- <tr>
- <th>#</th>
- <th>
- <a href='javascript:;'
- ng-click="pageList.toggleSelectAll()">✓</a>
- </th>
- <th ng-repeat="field in pageList.allColumns"
- ng-show="pageList.displayColumns[field.name]">
- <a href='javascript:;'
- ng-click="sort(field.name);draw()">{{field.label}}</a>
- </th>
- </tr>
- </thead>
- <tbody>
- <tr ng-repeat="rec in pageList.items"
- ng-class="{selected : pageList.selected[rec.id]}"
- ng-click="applyRowSelection($event, rec.id)">
- <td>{{$index + 1 + pageList.offset}}</td>
- <td><span ng-if="pageList.selected[rec.id]">✓</span>
- </td>
- <td ng-repeat="field in pageList.allColumns"
- ng-show="pageList.displayColumns[field.name]">
- <div ng-switch="field.name">
- <div ng-switch-when="title">
- <a target='_top' href='[% ctx.base_path %]/opac/record/{{rec.id}}'>
- {{rec[field.name]}}
- </a>
- </div>
- <div ng-switch-default>
- {{rec[field.name]}}
- </div>
- </div>
- </td>
- </tr>
- </tbody>
- </table>
- </div>
-</div>
-
-<br/>
-
<div class="row">
<div class="col-md-6">
[% INCLUDE 'staff/cat/bucket/record/t_bucket_info.tt2' %]
</div>
+</div>
- <div class="col-md-6 text-right">
- [% INCLUDE 'staff/cat/bucket/record/t_bucket_selector.tt2' %]
-
- <div class="btn-group text-left">
- <!-- first page -->
- <button type="button" class="btn btn-default"
- ng-class="{disabled : pageList.onFirstPage()}"
- ng-click="pageList.offset = 0;draw()">[% l('Start') %]</button>
-
- <!-- previous page -->
- <button type="button" class="btn btn-default"
- ng-class="{disabled : pageList.onFirstPage()}"
- ng-click="pageList.decrementPage();draw()">«</button>
-
- <!-- next page -->
- <!-- todo: paging needs a total count value to be fully functional -->
- <button type="button" class="btn btn-default"
- ng-class="{disabled : !pageList.hasNextPage()}"
- ng-click="pageList.incrementPage();draw()">»</button>
-
- <div class="btn-group">
- <button type="button" class="btn btn-default dropdown-toggle"
- ng-class="{disabled : action_pending}" data-toggle="dropdown">
- [% l('Actions') %] <span class="caret"></span>
- </button>
- <ul class="dropdown-menu pull-right">
-
- <li ng-class="{disabled : !pageList.selectedItems().length}">
- <a href="javascript:;" ng-click="addToPending()">
- [% l('Add Selected To Pending List') %]</a></li>
-
- <li ng-class="{disabled : !pageList.count()}">
- <a href="javascript:;" ng-click="addToPending(true)">
- [% l('Add All To Pending List') %]</a></li>
-
- <li ng-class="{disabled : !bucket() || !pageList.selectedItems().length}">
- <a href="javascript:;" ng-click="addToBucket()">
- [% l('Add Selected To Bucket') %]</a></li>
-
- <li ng-class="{disabled : !bucket() || !pageList.count()}">
- <a href="javascript:;" ng-click="addToBucket(true)">
- [% l('Add All To Bucket') %]</a></li>
- </ul>
- </div>
-
- <div class="btn-group col-picker">
- <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
- <span class="caret"></span>
- </button>
- <ul class="dropdown-menu pull-right">
- <li ng-repeat="col in pageList.allColumns">
- <a href='javascript:;'
- ng-click="pageList.displayColumns[col.name] =
- !pageList.displayColumns[col.name]">
- <span ng-if="pageList.displayColumns[col.name]" class="label label-success">✓</span>
- <span ng-if="!pageList.displayColumns[col.name]" class="label label-warning">✗</span>
- <span>{{col.label}}</span>
- </a>
- </li>
- <li role="presentation" class="divider"></li>
- <li>
- <a href='javascript:;' ng-click="pageList.showAllColumns()">[% l('Show All Columns') %]</a>
- </li>
- <li>
- <a href='javascript:;' ng-click="pageList.hideAllColumns()">[% l('Hide All Columns') %]</a>
- </li>
- <li class='disabled'>
- <a href='javascript:;' ng-click="">[% l('Save Columns') %]</a>
- </li>
- </ul>
- </div>
- </div>
+<div class="col-md-10 col-md-offset-1" ng-show="forbidden">
+ <div class="alert alert-warning">
+ [% l('The selected bucket "{{bucketId}}" is not visible to this login.') %]
</div>
</div>
<div class="input-group">
<span class="input-group-addon">[% l('Record Query') %]</span>
<input type="text" class="form-control" focus-me="focusMe"
- ng-model="bucketSvc.queryString" placeholder="[% l('Query...') %]">
+ ng-model="bucketSvc.queryString" placeholder="[% l('Query...') %]">
</div>
</form>
</div>
</div>
-
<br/>
<div class="row" ng-show="searchInProgress">
<div class="col-md-6">
</div>
-<div class="row">
- <div class="col-md-12">
-
- <table class="list table table-hover _table-striped table-condensed"
- ng-show="pageList.count()"
- ng-init="pageList.defaultColumns([
- 'id', 'author', 'isbn', 'issn', 'pubdate',
- 'publisher', 'tcn_value', 'title'
- ])">
- <thead>
- <tr>
- <th>#</th>
- <th>
- <a href='javascript:;'
- ng-click="pageList.toggleSelectAll()">✓</a>
- </th>
- <th ng-repeat="field in pageList.allColumns"
- ng-show="pageList.displayColumns[field.name]">
- <a href='javascript:;'
- ng-click="sort(field.name);draw()">{{field.label}}</a>
- </th>
- </tr>
- </thead>
- <tbody>
- <tr ng-repeat="rec in pageList.items"
- ng-class="{selected : pageList.selected[rec.id]}"
- ng-click="applyRowSelection($event, rec.id)">
- <td>{{$index + 1 + pageList.offset}}</td>
- <td><span ng-if="pageList.selected[rec.id]">✓</span>
- </td>
- <td ng-repeat="field in pageList.allColumns"
- ng-show="pageList.displayColumns[field.name]">
- <div ng-switch="field.name">
- <div ng-switch-when="title">
- <a target='_top' href='[% ctx.base_path %]/opac/record/{{rec.id}}'>
- {{rec[field.name]}}
- </a>
- </div>
- <div ng-switch-default>
- {{rec[field.name]}}
- </div>
- </div>
- </td>
- </tr>
- </tbody>
- </table>
- </div>
-</div>
+<eg-grid
+ ng-hide="forbidden"
+ id-field="id"
+ idl-class="rmsr"
+ auto-fields="true"
+ query="gridQuery"
+ menu-label="[% l('Buckets') %]"
+ persist-key="eg.staff.cat.bucket.record.view">
+
+ <!-- global menu -->
+ <eg-grid-menu-item label="[% l('New Bucket') %]"
+ handler="openCreateBucketDialog"></eg-grid-menu-item>
+ <eg-grid-menu-item label="[% l('Edit Bucket') %]"
+ handler="openEditBucketDialog"></eg-grid-menu-item>
+ <eg-grid-menu-item label="[% l('Delete Bucket') %]"
+ handler="openDeleteBucketDialog"></eg-grid-menu-item>
+ <eg-grid-menu-item label="[% l('Shared Bucket') %]"
+ handler="openSharedBucketDialog"></eg-grid-menu-item>
+ <eg-grid-menu-item divider="true"></eg-grid-menu-item>
+ <eg-grid-menu-item ng-repeat="bkt in bucketSvc.allBuckets"
+ label="{{bkt.name()}}" handler-data="bkt"
+ handler="loadBucketFromMenu"></eg-grid-menu-item>
+
+ <!-- actions drop-down -->
+ <eg-grid-action label="[% l('Add To Pending') %]"
+ handler="addToPending"></eg-grid-action>
+ <eg-grid-action label="[% l('Add To Bucket') %]"
+ handler="addToBucket"></eg-grid-action>
+
+</eg-grid>
-<br/>
<div class="row">
<div class="col-md-6">
[% INCLUDE 'staff/cat/bucket/record/t_bucket_info.tt2' %]
</div>
-
- <div class="col-md-6 text-right">
- [% INCLUDE 'staff/cat/bucket/record/t_bucket_selector.tt2' %]
-
- <!-- TODO: paging and actions drop-downs can be extracted out and shared -->
- <div class="btn-group text-left">
-
- <!-- first page -->
- <button type="button" class="btn btn-default"
- ng-class="{disabled : pageList.onFirstPage()}"
- ng-click="pageList.offset = 0;draw()">[% l('Start') %]</button>
-
- <!-- previous page -->
- <button type="button" class="btn btn-default"
- ng-class="{disabled : pageList.onFirstPage()}"
- ng-click="pageList.decrementPage();draw()">«</button>
-
- <!-- next page -->
- <!-- todo: paging needs a total count value to be fully functional -->
- <button type="button" class="btn btn-default"
- ng-class="{disabled : !pageList.hasNextPage()}"
- ng-click="pageList.incrementPage();draw()">»</button>
-
- <div class="btn-group">
- <button type="button" class="btn btn-default dropdown-toggle"
- ng-class="{disabled : action_pending}" data-toggle="dropdown">
- [% l('Actions') %] <span class="caret"></span>
- </button>
- <ul class="dropdown-menu pull-right">
- <li ng-class="{disabled : !bucket()}">
- <a href="javascript:;" ng-click="detachRecords()">
- [% l('Remove Selected Records') %]</a>
- </li>
- <li ng-class="{disabled : !bucket()}">
- <a href='' ng-click="openExportBucketDialog()">
- [% l('Export Bucket Records') %]
- </a>
- </li>
- </ul>
- </div>
-
- <div class="btn-group col-picker">
- <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
- <span class="caret"></span>
- </button>
- <ul class="dropdown-menu pull-right">
- <li ng-repeat="col in pageList.allColumns">
- <a href='javascript:;'
- ng-click="pageList.displayColumns[col.name] =
- !pageList.displayColumns[col.name]">
- <span ng-if="pageList.displayColumns[col.name]" class="label label-success">✓</span>
- <span ng-if="!pageList.displayColumns[col.name]" class="label label-warning">✗</span>
- <span>{{col.label}}</span>
- </a>
- </li>
- <li role="presentation" class="divider"></li>
- <li>
- <a href='javascript:;' ng-click="pageList.showAllColumns()">[% l('Show All Columns') %]</a>
- </li>
- <li>
- <a href='javascript:;' ng-click="pageList.hideAllColumns()">[% l('Hide All Columns') %]</a>
- </li>
- <li class='disabled'>
- <a href='javascript:;' ng-click="">[% l('Save Columns') %]</a>
- </li>
- </ul>
- </div>
- </div>
- </div>
</div>
-<br/>
-<div class="row">
-
- <div class="col-md-10 col-md-offset-1" ng-show="forbidden">
- <div class="alert alert-warning">
- [% l('The selected bucket "{{bucketId}}" is not visible to this login.') %]
- </div>
- </div>
-
- <div class="col-md-12">
- <table class="list table table-hover table-condensed" ng-show="pageList.count()"
- ng-init="pageList.defaultColumns([
- 'id', 'author', 'isbn', 'issn', 'pubdate',
- 'publisher', 'tcn_value', 'title'
- ])">
- <thead>
- <tr>
- <th>#</th>
- <th>
- <a href='javascript:;'
- ng-click="pageList.toggleSelectAll()">✓</a>
- </th>
- <th ng-repeat="field in pageList.allColumns"
- ng-show="pageList.displayColumns[field.name]">
- <a href='javascript:;'
- ng-click="sort(field.name);draw()">{{field.label}}</a>
- </th>
- </tr>
- </thead>
- <tbody>
- <tr ng-repeat="rec in pageList.items"
- ng-class="{selected : pageList.selected[rec.item_id]}"
- ng-click="applyRowSelection($event, rec.item_id)">
- <td>{{$index + 1 + pageList.offset}}</td>
- <td><span ng-if="pageList.selected[rec.item_id]">✓</span>
- </td>
- <td ng-repeat="field in pageList.allColumns"
- ng-show="pageList.displayColumns[field.name]">
- <div ng-switch="field.name">
- <div ng-switch-when="title">
- <a target='_top' href='[% ctx.base_path %]/opac/record/{{rec.id}}'>
- {{rec[field.name]}}
- </a>
- </div>
- <div ng-switch-default>
- {{rec[field.name]}}
- </div>
- </div>
- </td>
- </tr>
- </tbody>
- </table>
+<div class="col-md-10 col-md-offset-1" ng-show="forbidden">
+ <div class="alert alert-warning">
+ [% l('The selected bucket "{{bucketId}}" is not visible to this login.') %]
</div>
</div>
+
+<eg-grid
+ ng-hide="forbidden"
+ id-field="id"
+ idl-class="rmsr"
+ auto-fields="true"
+ query="gridQuery"
+ menu-label="[% l('Buckets') %]"
+ persist-key="eg.staff.cat.bucket.record.view">
+
+ <!-- global menu -->
+ <eg-grid-menu-item label="[% l('New Bucket') %]"
+ handler="openCreateBucketDialog"></eg-grid-menu-item>
+ <eg-grid-menu-item label="[% l('Edit Bucket') %]"
+ handler="openEditBucketDialog"></eg-grid-menu-item>
+ <eg-grid-menu-item label="[% l('Delete Bucket') %]"
+ handler="openDeleteBucketDialog"></eg-grid-menu-item>
+ <eg-grid-menu-item label="[% l('Shared Bucket') %]"
+ handler="openSharedBucketDialog"></eg-grid-menu-item>
+ <eg-grid-menu-item divider="true"></eg-grid-menu-item>
+ <eg-grid-menu-item ng-repeat="bkt in bucketSvc.allBuckets"
+ label="{{bkt.name()}}" handler-data="bkt"
+ handler="loadBucketFromMenu"></eg-grid-menu-item>
+
+ <!-- actions drop-down -->
+ <eg-grid-action label="[% l('Remove Selected Records') %]"
+ handler="detachRecords"></eg-grid-action>
+ <eg-grid-action label="[% l('Export Records') %]"
+ handler="openExportBucketDialog"></eg-grid-action>
+
+</eg-grid>
%]
[% BLOCK APP_JS %]
-<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/list.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/grid.js"></script>
<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/ui.js"></script>
<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/user.js"></script>
<script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/checkin/app.js"></script>
</div>
</form>
</div>
- <div class="col-md-2 col-md-offset-6 text-right">
- <div class="btn-group text-left">
- [% INCLUDE 'staff/parts/column_picker.tt2' listname='checkins' %]
- </div>
- </div>
</div>
[% INCLUDE 'staff/circ/checkin/t_checkin_table.tt2' %]
+<!-- checkins list -->
-[%
-# checkin table columns
-COLUMNS = [
-{label => l('Barcode'), name => 'copy_barcode' display => 1},
-{label => l('Circ ID'), name => 'payload.circ.id', display => 1},
-{label => l('Due Date'), name => 'payload.circ.due_date' display => 1},
-# once we are handling all response types, we probably don't need to show
-# Response. Or, at least, make it more friendly / localizable
-{label => l('Response'), name => 'textcode', display => 1},
-{label => l('Title'), name => 'payload.record.title', display => 1},
-{label => l('Author'), name => 'payload.record.author', display => 1},
-{label => l('Call Number'),name => 'payload.copy.call_number.label', display => 1},
-{label => l('Alert Msg'), name => 'payload.copy.alert_message' display => 1},
-]
-%]
+<eg-grid
+ id-field="id"
+ features="-display,-sort,-multisort"
+ main-label="[% l('Items Checked In') %]"
+ items-provider="gridDataProvider"
+ persist-key="eg.staff.circ.checkin">
+ <eg-grid-field label="[% ('Circ ID') %]" path='payload.circ.id' visible></eg-grid-field>
+ <eg-grid-field label="[% ('Barcode') %]" path='copy_barcode' visible></eg-grid-field>
+ <eg-grid-field label="[% l('Due Date') %]" path='payload.circ.due_date' visible></eg-grid-field>
+ <eg-grid-field label="[% l('Response') %]" path="textcode" visible></eg-grid-field>
+ <eg-grid-field label="[% l('Title') %]" path="payload.record.title" visible></eg-grid-field>
+ <eg-grid-field label="[% l('Author') %]" path="payload.record.author" visible></eg-grid-field>
+ <eg-grid-field label="[% l('Call Number') %]" path="payload.copy.call_number.label" visible></eg-grid-field>
+ <eg-grid-field label="[% l('Alert Msg') %]" path="payload.copy.alert_message" visible></eg-grid-field>
+</eg-grid>
-<!-- tell JS about our columns so they can be dynamically managed -->
-<div ng-init="
-checkins.setColumns([
-[%- FOR col IN COLUMNS %]
-{label:'[% col.label %]',name:'[% col.name %]'[% IF col.display %],display:true[% END %]}[% IF !loop.last; ','; END -%]
-[% END %]
-])">
-</div>
-
-<div class="row pad-vert" ng-cloak>
- <div class="col-md-12">
- <table class="table table-hover table-condensed table-striped">
- <thead>
- <tr>
- <th>#</th>
- <th ng-repeat="col in checkins.allColumns"
- ng-show="checkins.displayColumns[col.name]">
- {{col.label}}
- </th>
- </tr>
- </thead>
- <tbody>
- <tr ng-repeat="checkin in checkins.items | reverse track by $index">
- <td>{{checkins.count() - $index}}</td>
- <td ng-repeat="col in checkins.allColumns"
- ng-show="checkins.displayColumns[col.name]">
- {{checkins.fieldValue(checkin, col.name)}}
- </td>
- </tr>
- </tbody>
- </table>
- </div>
-</div>
-<div class="modal-dialog">
- <div class="modal-content">
- <div class="modal-header">
- <button type="button" class="close"
- ng-click="ok()" aria-hidden="true">×</button>
- <h4 class="modal-title">[% l('Hold Slip') %]</h4>
- </div>
- <div class="modal-body">
- <!-- TODO: pub / priv shelf -->
- <div ng-switch on="evt.payload.hold.behind_desk()">
- <div ng-switch-when="t">
- [% l('This item should be routed to the Private Holds Shelf') %]
- </div>
- <div ng-switch-when="f">
- [% l('This item should be routed to the Public Holds Shelf') %]
- </div>
- </div>
- <br/>
- <div>
- <span>[% l('Item Barcode:') %]</span>
- <span>{{evt.payload.copy.barcode()}}</span>
- </div>
- <div>
- <span>[% l('Title:') %]</span>
- <span>{{evt.payload.record.title()}}</span>
- </div>
- <div>
- <span>[% l('Author:') %]</span>
- <span>{{evt.payload.record.author()}}</span>
- </div>
- <br/>
- <div>
-
- <div ng-show="holdUser.alias()">
- [% l('Hold for patron {{holdUser.alias()}}') %]
- </div>
- <div ng-hide="holdUser.alias()">
- [% |l %]
- Hold for patron {{holdUser.family_name()}},
- {{holdUser.first_given_name()}} {{holdUser.second_given_name()}}
- [% END %]
- </div>
- <div>
- <span>[% l('Patron Barcode:') %]</span>
- <span>{{holdUser.card().barcode()}}</span>
- </div>
- <br/>
- <div>
- <span>[% l('Request Date:') %]</span>
- <span>{{evt.payload.hold.request_time() | date:'shortDate'}}</span>
+<div class="">
+ <div class="modal-header">
+ <button type="button" class="close"
+ ng-click="ok()" aria-hidden="true">×</button>
+ <h4 class="modal-title">[% l('Hold Slip') %]</h4>
+ </div>
+ <div class="modal-body">
+ <!-- TODO: pub / priv shelf -->
+ <div ng-switch on="evt.payload.hold.behind_desk()">
+ <div ng-switch-when="t">
+ [% l('This item should be routed to the Private Holds Shelf') %]
</div>
- <div>
- <span>[% l('Slip Date:') %]</span>
- <span>{{now | date:'shortDate'}}</span>
+ <div ng-switch-when="f">
+ [% l('This item should be routed to the Public Holds Shelf') %]
</div>
</div>
- <div class="modal-footer">
- <!--
- <input type="button" class="btn btn-primary" disabled="disabled"
- ng-click="print()" value="[% l('Print') %]"/>
- -->
- <input type="submit" class="btn btn-warning"
- ng-click="ok()" value="[% l('Do Not Print') %]"/>
+ <br/>
+ <div>
+ <span>[% l('Item Barcode:') %]</span>
+ <span>{{evt.payload.copy.barcode()}}</span>
</div>
+ <div>
+ <span>[% l('Title:') %]</span>
+ <span>{{evt.payload.record.title()}}</span>
+ </div>
+ <div>
+ <span>[% l('Author:') %]</span>
+ <span>{{evt.payload.record.author()}}</span>
+ </div>
+ <br/>
+ <div>
+
+ <div ng-show="holdUser.alias()">
+ [% l('Hold for patron {{holdUser.alias()}}') %]
+ </div>
+ <div ng-hide="holdUser.alias()">
+ [% |l %]
+ Hold for patron {{holdUser.family_name()}},
+ {{holdUser.first_given_name()}} {{holdUser.second_given_name()}}
+ [% END %]
+ </div>
+ <div>
+ <span>[% l('Patron Barcode:') %]</span>
+ <span>{{holdUser.card().barcode()}}</span>
+ </div>
+ <br/>
+ <div>
+ <span>[% l('Request Date:') %]</span>
+ <span>{{evt.payload.hold.request_time() | date:'shortDate'}}</span>
+ </div>
+ <div>
+ <span>[% l('Slip Date:') %]</span>
+ <span>{{now | date:'shortDate'}}</span>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <!--
+ <input type="button" class="btn btn-primary" disabled="disabled"
+ ng-click="print()" value="[% l('Print') %]"/>
+ -->
+ <input type="submit" class="btn btn-warning"
+ ng-click="ok()" value="[% l('Do Not Print') %]"/>
</div>
</div>
-<div class="modal-dialog">
- <div class="modal-content">
+<div class="">
+ <div class="">
<div class="modal-header">
<button type="button" class="close"
ng-click="ok()" aria-hidden="true">×</button>
<!-- edit bucket dialog -->
<form class="form-validated" novalidate ng-submit="ok(count)" name="form">
- <div class="modal-dialog">
- <div class="modal-content">
+ <div class="">
+ <div class="">
<div class="modal-header">
<button type="button" class="close"
ng-click="cancel()" aria-hidden="true">×</button>
#print-div { display: none; }
+/* by default, give all tab panes some top padding */
+.tab-pane { padding-top: 20px; }
/* ----------------------------------------------------------------------
* Grid
<div style="flex:1">
<div class="eg-grid-primary-label">{{grid.mainLabel}}</div>
</div>
+
+ <div class="btn-group" ng-if="grid.menuLabel" style="margin-right: 10px">
+ <button type="button"
+ class="btn btn-default dropdown-toggle" data-toggle="dropdown">
+ {{grid.menuLabel}}<span class="caret"></span>
+ </button>
+ <ul class="dropdown-menu">
+ <li ng-repeat="item in grid.menuItems" ng-class="{divider: item.divider}">
+ <a ng-if="!item.divider" href=''
+ ng-click="item.handler(item, item.handlerData)">{{item.label}}</a>
+ </li>
+ </ul>
+ </div>
- <!-- column picker -->
+ <!-- column picker, pager, etc. -->
<div class="btn-group column-picker">
<!-- first page -->
<span class="glyphicon glyphicon-forward"></span>
</button>
+ <!-- actions drop-down menu -->
+ <div class="btn-group" ng-if="grid.actions.length">
+ <button type="button" class="btn btn-default dropdown-toggle"
+ ng-class="{disabled : false}" data-toggle="dropdown">
+ [% l('Actions') %] <span class="caret"></span>
+ </button>
+ <ul class="dropdown-menu pull-right">
+ <li ng-class="{disabled : false}" ng-repeat="action in grid.actions">
+ <a href="" ng-click="grid.actionLauncher(action)">{{action.label}}</a>
+ </li>
+ </ul>
+ </div>
+
<div class="btn-group">
<button type="button" title="[% ('Select Row Count') %]"
class="btn btn-default dropdown-toggle" data-toggle="dropdown">
*/
angular.module('egCatRecordBuckets',
- ['ngRoute', 'ui.bootstrap', 'egCoreMod', 'egUiMod', 'egListMod'])
+ ['ngRoute', 'ui.bootstrap', 'egCoreMod', 'egUiMod', 'egGridMod'])
.config(function($routeProvider, $locationProvider) {
$locationProvider.html5Mode(true);
* tab click (i.e. route persistence).
*/
.factory('bucketSvc',
- ['$q','egList','egNet','egAuth','egIDL','egEvent',
-function($q, egList, egNet, egAuth, egIDL, egEvent) {
+ ['$q','egNet','egAuth','egIDL','egEvent',
+function($q, egNet, egAuth, egIDL, egEvent) {
var service = {
allBuckets : [], // un-fleshed user buckets
currentBucket : null, // currently viewed bucket
// per-page list collections
- searchList : egList.create(),
- pendingList : egList.create(),
- viewList : egList.create({indexField : 'item_id'}),
+ searchList : [],
+ pendingList : [],
+ viewList : [],
// fetches all staff/biblio buckets for the authenticated user
// this function may only be called after startup.
deferred.reject(evt);
return;
}
+ console.log('detached bucket item ' + itemId);
deferred.resolve(resp);
});
// tabs: search, pending, view
$scope.setTab = function(tab) {
$scope.tab = tab;
- $scope.pageList = bucketSvc[tab + 'List'];
// for bucket selector; must be called after route resolve
bucketSvc.fetchUserBuckets();
};
+ $scope.loadBucketFromMenu = function(item, bucket) {
+ if (bucket) return $scope.loadBucket(bucket.id());
+ }
+
$scope.loadBucket = function(id) {
$location.path(
'/cat/bucket/record/' +
$scope.tab + '/' + encodeURIComponent(id));
}
+ // TODO: grid selected items..
$scope.addToBucket = function(all) {
/** TODO: open-ils.actor.container.item.create almost works
* with batches, but not quite ... */
// the list size). The data stored is inconsistent, but since
// we are forcing a bucket refresh on the next rendering of
// the view pane, the list will be repaired.
- bucketSvc.viewList.items.push(resp);
- bucketSvc.viewList.totalCount++;
+ bucketSvc.currentBucket.items().push(resp);
});
}
);
}
-
- // same for all controllers
- $scope.applyRowSelection = function($event, index) {
- if ($event.ctrlKey || $event.metaKey) { // metaKey == mac command
- $scope.pageList.toggleOneSelection(index);
- } else {
- $scope.pageList.selectOne(index);
- }
- }
-
-
- /** ----------------
- * this will all change when we stop using rmsr's
- * TODO: stop using rmsr's
- */
- $scope.sort = function(field) {
- $scope.pageList.offset = 0;
- if (typeof $scope.pageList.sort == 'string' &&
- $scope.pageList.sort == field) {
- // already sorting on 'field', now sort descending
- $scope.pageList.sort = {};
- $scope.pageList.sort[field] = 'desc';
- } else {
- $scope.pageList.sort = field;
- }
- }
-
- $scope.setupColumns = function() {
- $scope.sortedFields = egIDL.classes.rmsr.fields.sort(
- function(a, b) { return a.label < b.label ? -1 : 1 });
-
- $scope.fields = {};
- $scope.queryFields = {};
- var cols = [];
- angular.forEach($scope.sortedFields, function(field) {
- if (field.virtual) return;
- cols.push(field);
- $scope.fields[field.name] = field;
- $scope.queryFields[field.name] = field.name;
- });
-
- $scope.pageList.setColumns(cols);
- }
-
- $scope.getRecords = function(ids) {
- if (ids.length == 0) return $q.when();
-
- $scope.pageList.totalCount = ids.length;
-
- // grab the lot in one go
- return egNet.request(
- 'open-ils.fielder',
- 'open-ils.fielder.flattened_search',
- egAuth.token(), "rmsr", $scope.queryFields,
- {id : ids},
- { sort : [$scope.pageList.sort || 'id'],
- limit : $scope.pageList.limit,
- offset : $scope.pageList.offset
- }
- ).then(
- null, // success
- null, // error
- function(record) { // notify handler
-
- // apply some data munging to make the list values
- // of 'rmsr' more human friendly.
- if (record.isbn) {
- record.isbn = record.isbn.replace(/\{NULL\}/,'');
- record.isbn = record.isbn.replace(/\{(.*)\}/,'$1');
- }
- if (record.issn) {
- record.issn = record.issn.replace(/\{NULL\}/,'');
- record.issn = record.issn.replace(/\{(.*)\}/,'$1');
- }
- $scope.pageList.items.push(record);
- }
- );
- }
-
$scope.openCreateBucketDialog = function() {
$modal.open({
templateUrl: './cat/bucket/record/t_bucket_create',
bucketSvc.createBucket(args.name, args.desc).then(
function(id) {
if (!id) return;
- bucketSvc.viewList.reset();
+ bucketSvc.viewList = [];
bucketSvc.allBuckets = []; // reset
$location.path(
'/cat/bucket/record/' + $scope.tab + '/' + id);
.controller('SearchCtrl',
['$scope','$routeParams','egAuth','egNet','egIDL','bucketSvc',
function($scope, $routeParams, egAuth, egNet, egIDL, bucketSvc) {
+
$scope.setTab('search');
- $scope.setupColumns();
$scope.focusMe = true;
+ var idQueryHash = {};
+ $scope.gridQuery = function() { return idQueryHash }
// add selected items directly to the pending list
- $scope.addToPending = function(all) {
- var recs = all ? $scope.pageList.items : $scope.pageList.selectedItems();
+ $scope.addToPending = function(recs) {
angular.forEach(recs, function(rec) {
- if (bucketSvc.pendingList.items.filter( // remove dupes
+ if (bucketSvc.pendingList.filter( // remove dupes
function(r) {return r.id == rec.id}).length) return;
- bucketSvc.pendingList.items.push(rec);
+ bucketSvc.pendingList.push(rec);
});
}
$scope.search = function() {
- $scope.pageList.resetPageData();
+ $scope.searchList = [];
$scope.searchInProgress = true;
bucketSvc.queryRecords = [];
egNet.request(
'open-ils.search',
'open-ils.search.biblio.multiclass.query', {
- // full search limit needs to be larger than page list limit
- limit : $scope.pageList.limit * 10,
+ limit : 500 // meh
}, bucketSvc.queryString, true
).then(function(resp) {
$scope.searchInProgress = false;
bucketSvc.queryRecords = resp.ids.map(function(id){return id[0]});
- $scope.pageList.totalCount = bucketSvc.queryRecords.length;
- $scope.getRecords(bucketSvc.queryRecords);
+ if (bucketSvc.queryRecords.length) {
+ idQueryHash = {id : bucketSvc.queryRecords};
+ } else {
+ idQueryHash = {};
+ }
});
}
- $scope.draw = function() {
- $scope.pageList.resetPageData();
- $scope.getRecords(bucketSvc.queryRecords);
- }
-
if ($routeParams.id &&
(!bucketSvc.currentBucket ||
bucketSvc.currentBucket.id() != $routeParams.id)) {
// fetch the bucket for display, then set the totalCount
// (also for display), but avoid fully fetching the bucket,
// since it's premature, in this UI.
- bucketSvc.fetchBucket($routeParams.id)
- .then(function(bucket) {
- bucketSvc.viewList.totalCount = bucket.items().length;
- });
+ bucketSvc.fetchBucket($routeParams.id);
}
}])
.controller('PendingCtrl',
- ['$scope','$routeParams','egAuth','egNet','egIDL','bucketSvc',
-function($scope, $routeParams, egAuth, egNet, egIDL, bucketSvc) {
+ ['$scope','$routeParams','bucketSvc','egGridDataProvider',
+function($scope, $routeParams, bucketSvc , egGridDataProvider) {
$scope.setTab('pending');
- $scope.setupColumns();
- $scope.draw = function() {
- // only called when sorting an existing list of records
- var ids = $scope.pageList.items.map(function(r) {return r.id});
- $scope.pageList.resetPageData();
- $scope.getRecords(ids);
+ var provider = egGridDataProvider.instance({});
+ provider.get = function(offset, count) {
+ return provider.arrayNotifier(
+ bucketSvc.pendingList, offset, count);
}
+ provider.itemFieldValue = provider.flatItemFieldValue;
+ $scope.gridDataProvider = provider;
+
+ $scope.resetPendingList = function() {
+ bucketSvc.pendingList = [];
+ }
+
if ($routeParams.id &&
(!bucketSvc.currentBucket ||
// fetch the bucket for display, then set the totalCount
// (also for display), but avoid fully fetching the bucket,
// since it's premature, in this UI.
- bucketSvc.fetchBucket($routeParams.id)
- .then(function(bucket) {
- bucketSvc.viewList.totalCount = bucket.items().length;
- });
+ bucketSvc.fetchBucket($routeParams.id);
}
}])
.controller('ViewCtrl',
- ['$scope','$window','$timeout','$location','$routeParams','egAuth','egNet','egIDL','bucketSvc','egEvent',
-function($scope, $window, $timeout, $location, $routeParams, egAuth, egNet, egIDL, bucketSvc, egEvent) {
+ ['$scope','$q','$routeParams','bucketSvc',
+function($scope, $q , $routeParams, bucketSvc) {
$scope.setTab('view');
- $scope.setupColumns();
-
$scope.bucketId = $routeParams.id;
- // no bucket selected, clear out any cached data
- if (!$scope.bucketId) {
- bucketSvc.currentBucket = null;
- bucketSvc.viewList.reset();
- return;
- }
-
- $scope.detachRecords = function() {
- var records = $scope.pageList.selectedItems();
- angular.forEach(records, function(rec) {
- bucketSvc.detachRecord(rec.item_id).then(function(resp) {
- $scope.pageList.removeItem(rec.item_id);
- $scope.pageList.totalCount--;
- });
- });
- }
-
- function getBucketRecords(recordIds) {
- $scope.getRecords(recordIds).then(function() {
- // link the bucket item to the record.
- var matched = {};
- angular.forEach($scope.bucket().items(), function(item) {
- var rec;
- var rid = item.target_biblio_record_entry();
- if (matched[rid]) {
- // dupe bib record. clone it into the list
- rec = angular.copy(matched[rid]);
- $scope.pageList.items.push(rec);
- } else {
- // find the record in the data we just fetched
- // note: don't use getItem, since our index field is 'item_id'
- rec = $scope.pageList.items.filter(
- function(r) {return r.id == rid})[0];
- matched[rid] = rec;
- }
- // rec will be unset if the record in question is not
- // visible in this page of data.
- if (rec) rec.item_id = item.id();
- });
- });
- }
+ // idQuery contents will change with each bucket loaded
+ // as the query changes, the grid will notice and refresh itself
+ var idQueryHash = {};
+ $scope.gridQuery = function() { return idQueryHash }
- // fetch the bucket and linked records as needed to
- // populate the page list.
- $scope.draw = function() {
- $scope.pageList.resetPageData();
- bucketSvc.fetchBucket($scope.bucketId).then(
+ function drawBucket() {
+ bucketSvc.bucketNeedsRefresh = true; // meh about this..
+ return bucketSvc.fetchBucket($scope.bucketId).then(
function(bucket) {
- ids = bucketSvc.currentBucket.items().map(
+ ids = bucket.items().map(
function(i){return i.target_biblio_record_entry()}
);
- getBucketRecords(ids);
- },
- function(evt) { $scope.forbidden = true }
+ if (ids.length) {
+ idQueryHash = {id : ids};
+ } else {
+ idQueryHash = {}; // avoid empty array query errors
+ }
+ }
);
- };
+ }
+
+ $scope.detachRecords = function(records) {
+ var promises = [];
+ angular.forEach(records, function(rec) {
+ var item = bucketSvc.currentBucket.items().filter(
+ function(i) {
+ return (i.target_biblio_record_entry() == rec.id)
+ }
+ );
+ if (item.length)
+ promises.push(bucketSvc.detachRecord(item[0].id()));
+ });
+
+ return $q.all(promises).then(drawBucket);
+ }
// avoid re-fetching the records for a bucket if the bucket
// is already loaded and we are navigating back to the
// view tab.
- if (bucketSvc.bucketRefreshLevel($scope.bucketId) == 1 ||
- bucketSvc.viewList.count() == 0 ) {
- $scope.draw();
+ if ($scope.bucketId && (
+ bucketSvc.bucketRefreshLevel($scope.bucketId) == 1 ||
+ bucketSvc.currentBucket.items().length == 0) ) {
+
+ drawBucket()['catch'](
+ function() { $scope.forbidden = true }
+ );
}
}])
angular.module('egCheckinApp', ['ngRoute', 'ui.bootstrap',
- 'egCoreMod', 'egUiMod', 'egListMod', 'egUserMod'])
+ 'egCoreMod', 'egUiMod', 'egGridMod', 'egUserMod'])
-.config(function($routeProvider, $locationProvider) {
+.config(function($routeProvider, $locationProvider, $compileProvider) {
$locationProvider.html5Mode(true);
- // no routes needed
+ $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); // grid export
})
/**
* checkin service
*/
.factory('checkinSvc',
- ['$q','egList','egNet','egAuth','egUser','egEnv','egOrg','egList',
-function($q, egList, egNet, egAuth, egUser, egEnv, egOrg, egList) {
+ ['$q','egNet','egAuth','egUser','egEnv','egOrg',
+function($q, egNet, egAuth, egUser, egEnv, egOrg) {
var service = {};
- service.checkins = egList.create();
+ service.checkins = [];
return service;
}])
* Manages checkin
*/
.controller('CheckinCtrl',
- ['$scope','$q','$modal','egStartup','checkinSvc','egNet', 'egAuth',
+ ['$scope','$q','$modal','egStartup','checkinSvc','egNet','egAuth','egGridDataProvider',
'orgAddrSvc','egOrg','egPCRUD','egAlertDialog','egConfirmDialog','egCheckinStrings',
-function($scope, $q, $modal, egStartup, checkinSvc, egNet, egAuth,
+function($scope, $q, $modal, egStartup, checkinSvc, egNet , egAuth , egGridDataProvider,
orgAddrSvc, egOrg, egPCRUD, egAlertDialog, egConfirmDialog, egCheckinStrings) {
// run egStartup here since it's not handled via resolver
$scope.focusMe = true;
$scope.checkins = checkinSvc.checkins;
+ var provider = egGridDataProvider.instance({});
+ provider.initialize = function() {
+ return {offset : 0};
+ }
+
+ provider.get = function(offset, count) {
+ return provider.arrayNotifier(
+ $scope.checkins, offset, count
+ );
+ }
+
+ provider.itemFieldValue = function(item, column) {
+ return provider.nestedItemFieldValue(item, column);
+ };
+
+ $scope.gridDataProvider = provider;
+
+ function addCheckin(evt) {
+ checkinSvc.checkins.push(evt);
+ provider.increment();
+ }
+
+
$scope.checkin = function(args) {
if (args && args.copy_barcode) {
performCheckin(angular.copy(args));
}
if (angular.isArray(evt)) evt = evt[0];
- evt.id = checkinSvc.checkins.count();
+ evt.id = checkinSvc.checkins.length;
evt.copy_barcode = args.copy_barcode;
handleCheckinResponse(evt, args, override);
});
switch (evt.textcode) {
case 'SUCCESS':
case 'NO_CHANGE':
- checkinSvc.checkins.items.push(evt);
+ addCheckin(evt);
if (copy.status() == 8) { // on holds shelf
if (hold &&
hold.pickup_lib() == egAuth.user().ws_ou()) {
break;
case 'ROUTE_ITEM':
- checkinSvc.checkins.items.push(evt);
+ addCheckin(evt);
openRouteDialog('./circ/checkin/t_transit_dialog', evt, args);
break;
case 'ASSET_COPY_NOT_FOUND':
cancel : function() {
// on cancel, push the event on the list
// to show that it happened
- checkinSvc.checkins.items.push(evt);
+ addCheckin(evt);
}
}
).result.then(function() {$scope.focusMe = true})
console.debug('checkin: ' + js2JSON(evt));
// push it on the list so the user can at least see
// something happened.
- $scope.checkins.items.push(evt);
+ addCheckin(evt);
}
}
resolve : {
destAddr : function() {
if (!evt.org) return $q.when();
- // TODO: response payload should flesh dest addr
+ // TODO SERVER: response payload should flesh dest addr
return orgAddrSvc.getAddr(evt.org, 'holds_address');
},
holdUser : function() {
- // TODO: response payload should flesh hold recipient
+ // TODO SERVER: response payload should flesh hold recipient
if (!evt.payload.hold) return $q.when();
return egPCRUD.retrieve('au',
evt.payload.hold.usr(), {
// overridden for checkouts to this patron for this instance of
// the interface.
checkout_overrides : {},
-
- // delivers the content of an array as a stream of promise notifications
- arrayNotifier : function(arr) {
- var def = $q.defer();
- // promise notifications are only witnessed when delivered
- // after the caller has his hands on the promise object
- $timeout(function() {
- angular.forEach(arr, def.notify);
- def.resolve();
- });
- return def.promise;
- }
};
// when we change the default patron, we need to clear out any
// Grid Provider -------------------
var provider = egGridDataProvider.instance({});
provider.get = function(offset, count) {
- return patronSvc.arrayNotifier(
- $scope.checkouts.slice(offset, offset + count)
+ return provider.arrayNotifier(
+ $scope.checkouts, offset, count
);
}
provider.itemFieldValue = function(item, column) {
// see if we have the requested range cached
if (patronSvc.holds[offset]) {
- return patronSvc.arrayNotifier(
- patronSvc.holds.slice(offset, offset + count)
+ return provider.arrayNotifier(
+ patronSvc.holds, offset, count
);
}
// see if we have the requested range cached
if (patronSvc.items_out[offset]) {
- return patronSvc.arrayNotifier(
- patronSvc.items_out.slice(offset, offset + count)
- );
+ return provider.arrayNotifier(
+ patronSvc.items_out, offset, count);
}
// see if we have the circ IDs for this range already loaded
// IDL class hint (e.g. "aou")
idlClass : '@',
- // points to a structure in the calling scope which defines
- // a PCRUD-compliant query.
+ // reference to a function returning a query for the default
+ // egGridFlatDataProvider handler. Any time the return value
+ // changes, the grid will be refreshed.
query : '=',
// if true, grid columns are derived from all non-virtual
// can determine the primary key directly from the IDL.
idField : '@',
- // egList containting our tabular data is provided for us
- // and managed externally.
+ // Reference to externally provided egGridDataProvider
itemsProvider : '=',
// comma-separated list of supported or disabled grid features
// optional primary grid label
mainLabel : '@',
+
+ // if true, use the IDL class label as the mainLael
+ autoLabel : '=',
+
+ // optional context menu label
+ menuLabel : '@'
},
// TODO: avoid hard-coded url
grid.limit = 25;
grid.items = [];
grid.selected = {}; // idField-based
+ grid.actions = [];
grid.totalCount = -1;
- grid.dataProvider = $scope.itemsProvider;
grid.idlClass = $scope.idlClass;
grid.mainLabel = $scope.mainLabel;
grid.indexField = $scope.idField;
grid.showGridConf = false;
+ grid.dataProvider = $scope.itemsProvider;
+ grid.menuLabel = $scope.menuLabel;
+
+ grid.menuItems = [];
+ grid.addMenuItem = function(item) {
+ grid.menuItems.push(item);
+ }
// default flex values for the index and selector columns
grid.indexFlex = 1;
if ($scope.autoFields) {
grid.indexField = egIDL.classes[grid.idlClass].pkey;
- if (!grid.mainLabel)
+ if (grid.autoLabel)
grid.mainLabel = egIDL.classes[grid.idlClass].label;
grid.columnsProvider.compileAutoColumns();
}
columnsProvider : grid.columnsProvider,
query : $scope.query
});
+
+ $scope.$watch(
+ function() { return $scope.query() },
+ function() { grid.collect() },
+ true // object comparison
+ );
}
// this allows the caller to pass in initializtion
return item;
}
+ // fires the action handler function
+ grid.actionLauncher = function(action) {
+ action.handler(grid.getSelectedItems());
+ }
+
+ // returns the list of selected item objects
+ grid.getSelectedItems = function() {
+ return grid.items.filter(
+ function(item) {
+ return Boolean(grid.selected[grid.indexValue(item)]);
+ }
+ );
+ }
+
// selects one row after deselecting all of the others
grid.selectOneItem = function(index) {
grid.selected = {};
};
})
+/**
+ * eg-grid-action : used for specifying actions which may be applied
+ * to items within the grid.
+ */
+.directive('egGridAction', function() {
+ return {
+ require : '^egGrid',
+ restrict : 'AE',
+ transclude : true,
+ scope : {
+ label : '@', // Action label
+ handler : '=' // Action function handler
+ },
+ template : '<div></div>', // NOOP template
+ link : function(scope, element, attrs, egGridCtrl) {
+ egGridCtrl.actions.push({
+ label : scope.label,
+ handler : scope.handler
+ });
+ }
+ };
+})
+
.factory('egGridColumnsProvider', ['egIDL', function(egIDL) {
function ColumnsProvider(args) {
* meet the needs of each individual grid.
*/
.factory('egGridDataProvider',
- ['$filter','egNet','egAuth','egIDL',
- function($filter , egNet , egAuth , egIDL) {
+ ['$q','$timeout','$filter','egNet','egAuth','egIDL',
+ function($q , $timeout , $filter , egNet , egAuth , egIDL) {
function GridDataProvider(args) {
var gridData = this;
gridData.idlClass = args.idlClass;
gridData.columnsProvider = args.columnsProvider;
+ // Delivers a stream of array data via promise.notify()
+ // Useful for passing an array of data to egGrid.get()
+ // If a count is provided, the array will be trimmed to
+ // the range defined by count and offset
+ gridData.arrayNotifier = function(arr, offset, count) {
+ if (!arr || arr.length == 0) return $q.when();
+ if (count) arr = arr.slice(offset, offset + count);
+ var def = $q.defer();
+ // promise notifications are only witnessed when delivered
+ // after the caller has his hands on the promise object
+ $timeout(function() {
+ angular.forEach(arr, def.notify);
+ def.resolve();
+ });
+ return def.promise;
+ }
+
gridData.initialize = function() {
return {};
}
// Factory service for egGridDataManager instances, which are
// responsible for collecting flattened grid data.
.factory('egGridFlatDataProvider',
- ['egNet','egAuth','egGridDataProvider',
- function(egNet , egAuth , egGridDataProvider) {
+ ['$q','egNet','egAuth','egGridDataProvider',
+ function($q , egNet , egAuth , egGridDataProvider) {
return {
instance : function(args) {
var provider = egGridDataProvider.instance(args);
provider.get = function(offset, count) {
+ if (!angular.isFunction(provider.query))
+ return $q.when();
+
+ var query = provider.query();
+ if (!query || angular.equals(query, {}))
+ return $q.when();
// find all of the currently visible columns
var queryFields = {}
return egNet.request(
'open-ils.fielder',
'open-ils.fielder.flattened_search',
- egAuth.token(), provider.idlClass, queryFields,
- provider.query,
+ egAuth.token(), provider.idlClass,
+ queryFields, query,
{ sort : provider.sort,
limit : count,
offset : offset
}
};
})
+
+.directive('egGridMenuItem', function() {
+ return {
+ restrict : 'AE',
+ require : '^egGrid',
+ scope : {
+ label : '@',
+ handler : '=', // onclick handler function
+ divider : '=', // if true, show a divider only
+ handlerData : '=' // if set, passed as second argument to handler
+ },
+ link : function(scope, element, attrs, egGridCtrl) {
+ egGridCtrl.addMenuItem({
+ label : scope.label,
+ handler : scope.handler,
+ divider : scope.divider,
+ handlerData : scope.handlerData
+ });
+ }
+ };
+})