22df857d6b781090e3c45e87891779e8f45a0c68
[working/Evergreen.git] / Open-ILS / web / js / ui / default / staff / cat / services / record.js
1 /**
2  * Simple directive for rending the HTML view of a MARC record.
3  *
4  * <eg-record-html record-id="myRecordIdScopeVariable"></eg-record-id>
5  * OR
6  * <eg-record-html marc-xml="myMarcXmlVariable"></eg-record-html>
7  *
8  * The value of myRecordIdScopeVariable is watched internally and the 
9  * record is updated to match.
10  */
11 angular.module('egCoreMod')
12
13 .directive('egRecordHtml', function() {
14     return {
15         restrict : 'AE',
16         scope : {
17             recordId : '=',
18             marcXml  : '@',
19         },
20         link : function(scope, element, attrs) {
21             scope.element = angular.element(element);
22
23             // kill refs to destroyed DOM elements
24             element.bind("$destroy", function() {
25                 delete scope.element;
26             });
27         },
28         controller : 
29                    ['$scope','egCore',
30             function($scope , egCore) {
31
32                 function loadRecordHtml() {
33                     egCore.net.request(
34                         'open-ils.search',
35                         'open-ils.search.biblio.record.html',
36                         $scope.recordId,
37                         false,
38                         $scope.marcXml
39                     ).then(function(html) {
40                         if (!html) return;
41
42                         // Remove those pesky non-i8n labels / actions.
43                         // Note: for printing, use the browser print page
44                         // option.  The end result is the same.
45                         html = html.replace(
46                             /<button onclick="window.print(.*?)<\/button>/,'');
47                         html = html.replace(/<title>(.*?)<\/title>/,'');
48
49                         // remove reference to nonexistant CSS file
50                         html = html.replace(/<link(.*?)\/>/,'');
51
52                         $scope.element.html(html);
53                     });
54                 }
55
56                 $scope.$watch('recordId', 
57                     function(newVal, oldVal) {
58                         if (newVal && newVal !== oldVal) {
59                             loadRecordHtml();
60                         }
61                     }
62                 );
63                 $scope.$watch('marcXml', 
64                     function(newVal, oldVal) {
65                         if (newVal && newVal !== oldVal) {
66                             loadRecordHtml();
67                         }
68                     }
69                 );
70
71                 if ($scope.recordId || $scope.marcXml) 
72                     loadRecordHtml();
73             }
74         ]
75     }
76 })
77
78 .directive('egRecordBreaker', function() {
79     return {
80         restrict : 'AE',
81         template : '<pre>{{breaker}}</pre>',
82         scope : {
83             recordId : '=',
84             marcXml  : '=',
85         },
86         link : function(scope, element, attrs) {
87             scope.element = angular.element(element);
88
89             // kill refs to destroyed DOM elements
90             element.bind("$destroy", function() {
91                 delete scope.element;
92             });
93         },
94         controller : 
95                    ['$scope','egCore',
96             function($scope , egCore) {
97
98                 function loadRecordBreaker() {
99                     var xml;
100                     if ($scope.marcXml) {
101                         $scope.breaker = new MARC21.Record({ marcxml : $scope.marcXml }).toBreaker();
102                     } else {
103                         egCore.pcrud.retrieve('bre', $scope.recordId)
104                         .then(function(rec) {
105                             $scope.breaker = new MARC21.Record({ marcxml : rec.marc() }).toBreaker();
106                         });
107                     }
108                 }
109
110                 $scope.$watch('recordId', 
111                     function(newVal, oldVal) {
112                         if (newVal && newVal !== oldVal) {
113                             loadRecordBreaker();
114                         }
115                     }
116                 );
117                 $scope.$watch('marcXml', 
118                     function(newVal, oldVal) {
119                         if (newVal && newVal !== oldVal) {
120                             loadRecordBreaker();
121                         }
122                     }
123                 );
124
125                 if ($scope.recordId || $scope.marcXml) 
126                     loadRecordBreaker();
127             }
128         ]
129     }
130 })
131
132 /*
133  * A record='foo' attribute is required as a storage location of the 
134  * retrieved record
135  */
136 .directive('egRecordSummary', function() {
137     return {
138         restrict : 'AE',
139         scope : {
140             recordId : '=',
141             record : '=',
142             noMarcLink : '@'
143         },
144         templateUrl : './cat/share/t_record_summary',
145         controller : 
146                    ['$scope','egCore','$sce','egBibDisplay',
147             function($scope , egCore , $sce , egBibDisplay) {
148
149                 function loadRecord() {
150                     egCore.pcrud.retrieve('bre', $scope.recordId, {
151                         flesh : 1,
152                         flesh_fields : {
153                             bre : ['creator','editor','flat_display_entries']
154                         }
155                     }).then(function(rec) {
156                         rec.owner(egCore.org.get(rec.owner()));
157                         $scope.record = rec;
158                         $scope.rec_display = 
159                             egBibDisplay.mfdeToHash(rec.flat_display_entries());
160                     });
161                     $scope.bib_cn = null;
162                     $scope.bib_cn_tooltip = '';
163                     var label_class = 1;
164                     if (egCore.env.aous) 
165                         label_class = egCore.env.aous['cat.default_classification_scheme'] || 1;
166                     egCore.net.request(
167                         'open-ils.cat',
168                         'open-ils.cat.biblio.record.marc_cn.retrieve',
169                         $scope.recordId,
170                         label_class
171                     ).then(function(cn_array) {
172                         var tooltip = '';
173                         if (cn_array.length > 0) {
174                             for (var field in cn_array[0]) {
175                                 $scope.bib_cn = cn_array[0][field];
176                             }
177                             for (var i in cn_array) {
178                                 for (var field in cn_array[i]) {
179                                     tooltip += 
180                                         field + ' : ' + cn_array[i][field] + '<br>';
181                                 }
182                             }
183                             $scope.bib_cn_tooltip = $sce.trustAsHtml(tooltip);
184                         }
185                     });
186                 }
187
188                 $scope.$watch('recordId', 
189                     function(newVal, oldVal) {
190                         if (newVal && newVal !== oldVal) {
191                             loadRecord();
192                         }
193                     }
194                 );
195
196
197                 if ($scope.recordId) 
198                     loadRecord();
199
200                 $scope.toggle_expand_summary = function() {
201                     if ($scope.collapseRecordSummary) {
202                         $scope.collapseRecordSummary = false;
203                         egCore.hatch.removeItem('eg.cat.record.summary.collapse');
204                     } else {
205                         $scope.collapseRecordSummary = true;
206                         egCore.hatch.setItem('eg.cat.record.summary.collapse', true);
207                     }
208                 }
209             
210                 $scope.collapse_summary = function() {
211                     return $scope.collapseRecordSummary;
212                 }
213             
214                 egCore.hatch.getItem('eg.cat.record.summary.collapse')
215                 .then(function(val) {$scope.collapseRecordSummary = Boolean(val)});
216
217             }
218         ]
219     }
220 })
221
222 /**
223  * Utility functions for translating bib record display fields into
224  * various formats / structures.
225  *
226  * Note that 'mwde' objects (which are proper IDL objects) only contain
227  * the prescribed fields from the IDL (and database view), while the
228  * 'mfde' hash-based objects contain all configured display fields,
229  * including local/custom fields.
230  *
231  * Example:
232  *
233  *  --
234  *  // Display well-known fields
235  *
236  *  $scope.record = copy.call_number().record();
237  *
238  *  // translate wide display entry values inline
239  *  egBibDisplay.mwdeJSONToJS($scope.record.wide_display_entry());
240  *
241  *  <div>Title:</div>
242  *  <div>{{record.wide_display_entry().title()}}</div>
243  *
244  *  ---
245  *  //  Display any field using known keys
246  *
247  *  $scope.all_display_fields = 
248  *      egBibDisplay.mfdeToHash(record.flat_display_entries());
249  *
250  *  <div>Title:</div>
251  *  <div>{{all_display_fields.title}}</div>
252  *
253  *  ---
254  *  // Display all fields dynamically, using confgured labels
255  *
256  *  $scope.all_display_fields_with_meta = 
257  *      egBibDisplay.mfdeToMetaHash(record.flat_display_entries());
258  *
259  *  <div ng-repeat="(key, content) in all_display_fields_with_meta">
260  *    <div>Field Label</div><div>{{content.label}}</div>
261  *    <div ng-if="content.multi == 't'">
262  *      <div ng-repeat="val in content.value">
263  *        <div>Field Value</div><div>{{val}}</div>
264  *      </div>
265  *    </div>
266  *    <div ng-if="content.multi == 'f'">
267  *      <div>Field Value</div><div>{{content.value}}</div>
268  *    </div>
269  *  </div>
270  *
271  */
272 .factory('egBibDisplay', ['$q', 'egCore', function($q, egCore) {
273     var service = {};
274
275     /**
276      * Converts JSON-encoded values within a mwde object to Javascript
277      * native strings, numbers, and arrays.
278      *
279      * @collapseMulti collapse array (multi) fields down to a single string
280      * with values separated by a comma+space.  Useful for quickly 
281      * building displays (e.g. grids) without having to first munge 
282      * the array into a string.
283      */
284     service.mwdeJSONToJS = function(entry, collapseMulti) {
285         angular.forEach(egCore.idl.classes.mwde.fields, function(f) {
286             if (f.virtual) return;
287             var val = JSON.parse(entry[f.name]());
288             if (collapseMulti && angular.isArray(val))
289                 val = val.join(', ');
290             entry[f.name](val);
291         });
292     }
293
294     /**
295      * Converts a list of 'mfde' entry objects to a simple key=>value hash.
296      * Non-multi values are strings or numbers.
297      * Multi values are arrays of strings or numbers.
298      *
299      * @collapseMulti See service.mwdeJSONToJS()
300      */
301     service.mfdeToHash = function(entries, collapseMulti) {
302         var hash = service.mfdeToMetaHash(entries, collapseMulti);
303         angular.forEach(hash, 
304             function(sub_hash, name) { hash[name] = sub_hash.value });
305         return hash;
306     }
307
308     /**
309      * Converts a list of 'mfde' entry objects to a nested hash like so:
310      * {name => field_name, label => field_label, value => scalar_or_array}
311      * The scalar_or_array value is a string/number or an array of
312      * string/numbers
313      *
314      * @collapseMulti See service.mwdeJSONToJS()
315      */
316     service.mfdeToMetaHash = function(entries, collapseMulti) {
317         var hash = {};
318         angular.forEach(entries, function(entry) {
319
320             if (!hash[entry.name()]) {
321                 hash[entry.name()] = {
322                     name : entry.name(),
323                     label : entry.label(),
324                     multi : entry.multi() == 't',
325                     value : entry.multi() == 't' ? [] : null
326                 }
327             }
328
329             if (entry.multi() == 't') {
330                 if (collapseMulti) {
331                     if (angular.isArray(hash[entry.name()].value)) {
332                         // new collapsed string
333                         hash[entry.name()].value = entry.value();
334                     } else {
335                         // append to collapsed string
336                         hash[entry.name()].value += ', ' + entry.value();
337                     }
338                 } else {
339                     hash[entry.name()].value.push(entry.value());
340                 }
341             } else {
342                 hash[entry.name()].value = entry.value();
343             }
344         });
345
346         return hash;
347     }
348
349     return service;
350 }])