Bug 11559: (followup) Fill in default values, fix redirect and UNIMARC
[koha-equinox.git] / koha-tmpl / intranet-tmpl / prog / en / includes / cateditor-ui.inc
1 <script src="/intranet-tmpl/lib/codemirror/codemirror-compressed.js"></script>
2 <script src="/intranet-tmpl/lib/filesaver.js"></script>
3 <script src="/intranet-tmpl/lib/koha/cateditor/marc-mode.js"></script>
4 <script src="/intranet-tmpl/lib/require.js"></script>
5 <script>
6 require.config( {
7     baseUrl: '/intranet-tmpl/lib/koha/cateditor/',
8     config: {
9         resources: {
10             marcflavour: '[% marcflavour %]',
11             themelang: '[% themelang %]',
12         },
13     },
14     waitSeconds: 30,
15 } );
16 </script>
17
18 [% IF marcflavour == 'MARC21' %]
19 [% PROCESS 'cateditor-widgets-marc21.inc' %]
20 [% ELSE %]
21 <script>var editorWidgets = {};</script>
22 [% END %]
23
24 <script>
25 require( [ 'koha-backend', 'search', 'macros', 'marc-editor', 'marc-record', 'preferences', 'resources', 'text-marc', 'widget' ], function( KohaBackend, Search, Macros, MARCEditor, MARC, Preferences, Resources, TextMARC, Widget ) {
26     var z3950Servers = {
27         'koha:biblioserver': {
28             name: _("Local catalog"),
29             recordtype: 'biblio',
30             checked: false,
31         },
32         [%- FOREACH server = z3950_servers -%]
33             [% server.id %]: {
34                 name: '[% server.servername %]',
35                 recordtype: '[% server.recordtype %]',
36                 checked: [% server.checked ? 'true' : 'false' %],
37             },
38         [%- END -%]
39     };
40
41     // The columns that should show up in a search, in order, and keyed by the corresponding <metadata> tag in the XSL and Pazpar2 config
42     var z3950Labels = [
43                 [ "local_number", _("Local number") ],
44                 [ "title", _("Title") ],
45                 [ "series", _("Series title") ],
46                 [ "author", _("Author") ],
47                 [ "lccn", _("LCCN") ],
48                 [ "isbn", _("ISBN") ],
49                 [ "issn", _("ISSN") ],
50                 [ "medium", _("Medium") ],
51                 [ "edition", _("Edition") ],
52                 [ "notes", _("Notes") ],
53     ];
54
55     var state = {
56         backend: '',
57         saveBackend: 'catalog',
58         recordID: undefined
59     };
60
61     var editor;
62     var macroEditor;
63
64     function makeAuthorisedValueWidgets( frameworkCode ) {
65         $.each( KohaBackend.GetAllTagsInfo( frameworkCode ), function( tag, tagInfo ) {
66             $.each( tagInfo.subfields, function( subfield, subfieldInfo ) {
67                 if ( !subfieldInfo.authorised_value ) return;
68                 var authvals = KohaBackend.GetAuthorisedValues( subfieldInfo.authorised_value );
69                 if ( !authvals ) return;
70
71                 var defaultvalue = subfield.defaultvalue || authvals[0].value;
72
73                 Widget.Register( tag + subfield, {
74                     init: function() {
75                         var $result = $( '<span class="subfield-widget"></span>' );
76
77                         return $result[0];
78                     },
79                     postCreate: function() {
80                         this.setText( defaultvalue );
81
82                         $( '<select></select>' ).appendTo( this.node );
83                         var $node = $( this.node ).find( 'select' );
84                         $.each( authvals, function( undef, authval ) {
85                             $node.append( '<option value="' + authval.value + '"' + (authval.value == defaultvalue ? ' selected="selected"' : '') + '>' + authval.lib + '</option>' );
86                         } );
87                         $node.val( this.text );
88
89                         $node.change( $.proxy( function() {
90                             this.setText( $node.val() );
91                         }, this ) );
92                     },
93                     makeTemplate: function() {
94                         return defaultvalue;
95                     },
96                 } );
97             } );
98         } );
99     }
100
101     function bindGlobalKeys() {
102         shortcut.add( 'ctrl+s', function(event) {
103             $( '#save-record' ).click();
104
105             event.preventDefault();
106         } );
107
108         shortcut.add( 'alt+ctrl+k', function(event) {
109             $( '#search-by-keywords' ).focus();
110
111             return false;
112         } );
113
114         shortcut.add( 'alt+ctrl+a', function(event) {
115             $( '#search-by-author' ).focus();
116
117             return false;
118         } );
119
120         shortcut.add( 'alt+ctrl+i', function(event) {
121             $( '#search-by-isbn' ).focus();
122
123             return false;
124         } );
125
126         shortcut.add( 'alt+ctrl+t', function(event) {
127             $( '#search-by-title' ).focus();
128
129             return false;
130         } );
131
132         shortcut.add( 'ctrl+h', function() {
133             var field = editor.getCurrentField();
134
135             if ( !field ) return;
136
137             window.open( getFieldHelpURL( field.tag ) );
138         } );
139
140         $('#quicksearch .search-box').each( function() {
141             shortcut.add( 'enter', $.proxy( function() {
142                 var terms = [];
143
144                 $('#quicksearch .search-box').each( function() {
145                     if ( !this.value ) return;
146
147                     terms.push( [ $(this).data('qualifier'), this.value ] );
148                 } );
149
150                 if ( !terms.length ) return;
151
152                 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
153                     $("#search-overlay").show();
154                     showResultsBox();
155                 }
156
157                 return false;
158             }, this), { target: this, type: 'keypress' } );
159         } );
160     }
161
162     function getFieldHelpURL( tag ) {
163         [% IF ( marcflavour == 'MARC21' ) %]
164             if ( tag == '000' ) {
165                 return "http://www.loc.gov/marc/bibliographic/bdleader.html";
166             } else if ( tag < '900' ) {
167                 return "http://www.loc.gov/marc/bibliographic/bd" + tag + ".html";
168             } else {
169                 return "http://www.loc.gov/marc/bibliographic/bd9xx.html";
170             }
171         [% ELSIF ( marcflavour == 'UNIMARC' ) %]
172             /* http://archive.ifla.org/VI/3/p1996-1/ is an outdated version of UNIMARC, but
173                seems to be the only version available that can be linked to per tag.  More recent
174                versions of the UNIMARC standard are available on the IFLA website only as
175                PDFs!
176             */
177             if ( tag == '000' ) {
178                return  "http://archive.ifla.org/VI/3/p1996-1/uni.htm";
179             } else {
180                 var first = tag[0];
181                 var url = "http://archive.ifla.org/VI/3/p1996-1/uni" + first + ".htm#";
182                 if ( first == '0' ) url += "b";
183                 if ( first != '9' ) url += tag;
184
185                 return url;
186             }
187         [% END %]
188     }
189
190     // Record loading
191     var backends = {
192        'new': {
193             titleForRecord: _("Editing new record"),
194             get: function( id, callback ) {
195                 record = new MARC.Record();
196                 KohaBackend.FillRecord( '', record );
197
198                 callback( record );
199             },
200         },
201         'new-full': {
202             titleForRecord: _("Editing new full record"),
203             get: function( id, callback ) {
204                 record = new MARC.Record();
205                 KohaBackend.FillRecord( '', record, true );
206
207                 callback( record );
208             },
209         },
210         'catalog': {
211             titleForRecord: _("Editing catalog record #{ID}"),
212             links: [
213                 { title: _("view"), href: "/cgi-bin/koha/catalogue/detail.pl?biblionumber={ID}" },
214                 { title: _("edit items"), href: "/cgi-bin/koha/cataloguing/additem.pl?biblionumber={ID}" },
215             ],
216             saveLabel: _("Save to catalog"),
217             get: function( id, callback ) {
218                 if ( !id ) return false;
219
220                 KohaBackend.GetRecord( id, callback );
221             },
222             save: function( id, record, done ) {
223                 function finishCb( data ) {
224                     done( { error: data.error, newRecord: data.marcxml && data.marcxml[0], newId: data.biblionumber && [ 'catalog', data.biblionumber ] } );
225                 }
226
227                 if ( id ) {
228                     KohaBackend.SaveRecord( id, record, finishCb );
229                 } else {
230                     KohaBackend.CreateRecord( record, finishCb );
231                 }
232             }
233         },
234         'iso2709': {
235             saveLabel: _("Save as ISO2709 (.mrc) file"),
236             save: function( id, record, done ) {
237                 saveAs( new Blob( [record.toISO2709()], { 'type': 'application/octet-stream;charset=utf-8' } ), 'record.mrc' );
238
239                 done( {} );
240             }
241         },
242         'marcxml': {
243             saveLabel: _("Save as MARCXML (.xml) file"),
244             save: function( id, record, done ) {
245                 saveAs( new Blob( [record.toXML()], { 'type': 'application/octet-stream;charset=utf-8' } ), 'record.xml' );
246
247                 done( {} );
248             }
249         },
250         'search': {
251             titleForRecord: _("Editing search result"),
252             get: function( id, callback ) {
253                 if ( !id ) return false;
254                 if ( !backends.search.records[ id ] ) {
255                     callback( { error: _( "Invalid record" ) } );
256                     return false;
257                 }
258
259                 callback( backends.search.records[ id ] );
260             },
261             records: {},
262         },
263     };
264
265     function setSource(parts) {
266         state.backend = parts[0];
267         state.recordID = parts[1];
268         state.canSave = backends[ state.backend ].save != null;
269         state.saveBackend = state.canSave ? state.backend : 'catalog';
270
271         var backend = backends[state.backend];
272
273         document.location.hash = '#' + parts[0] + '/' + parts[1];
274
275         $('#title').text( backend.titleForRecord.replace( '{ID}', parts[1] ) );
276
277         $.each( backend.links || [], function( i, link ) {
278             $('#title').append(' <a target="_blank" href="' + link.href.replace( '{ID}', parts[1] ) + '">(' + link.title + ')</a>' );
279         } );
280         $( 'title', document.head ).html( _("Koha &rsaquo; Cataloging &rsaquo; ") + backend.titleForRecord.replace( '{ID}', parts[1] ) );
281         $('#save-record span').text( backends[ state.saveBackend ].saveLabel );
282     }
283
284     function saveRecord( recid, editor, callback ) {
285         var parts = recid.split('/');
286         if ( parts.length != 2 ) return false;
287
288         if ( !backends[ parts[0] ] || !backends[ parts[0] ].save ) return false;
289
290         editor.removeErrors();
291         var record = editor.getRecord();
292
293         if ( record.errors ) {
294             state.saving = false;
295             callback( { error: 'syntax', errors: record.errors } );
296             return;
297         }
298
299         var errors = KohaBackend.ValidateRecord( '', record );
300         if ( errors.length ) {
301             state.saving = false;
302             callback( { error: 'invalid', errors: errors } );
303             return;
304         }
305
306         backends[ parts[0] ].save( parts[1], record, function(data) {
307             state.saving = false;
308
309             if (data.newRecord) {
310                 var record = new MARC.Record();
311                 record.loadMARCXML(data.newRecord);
312                 editor.displayRecord( record );
313             }
314
315             if (data.newId) {
316                 setSource(data.newId);
317             } else {
318                 setSource( [ state.backend, state.recordID ] );
319             }
320
321             if (callback) callback( data );
322         } );
323     }
324
325     function loadRecord( recid, editor, callback ) {
326         var parts = recid.split('/');
327         if ( parts.length != 2 ) return false;
328
329         if ( !backends[ parts[0] ] || !backends[ parts[0] ].get ) return false;
330
331         backends[ parts[0] ].get( parts[1], function( record ) {
332             if ( !record.error ) {
333                 editor.displayRecord( record );
334                 editor.focus();
335             }
336
337             if (callback) callback(record);
338         } );
339
340         return true;
341     }
342
343     function openRecord( recid, editor, callback ) {
344         return loadRecord( recid, editor, function ( record ) {
345             setSource( recid.split('/') );
346
347             if (callback) callback( record );
348         } );
349     }
350
351     // Search functions
352     function showAdvancedSearch() {
353         $('#advanced-search-servers').empty();
354         $.each( z3950Servers, function( server_id, server ) {
355             $('#advanced-search-servers').append( '<li data-server-id="' + server_id + '"><label><input class="search-toggle-server" type="checkbox"' + ( server.checked ? ' checked="checked">' : '>' ) + server.name + '</label></li>' );
356         } );
357         $('#advanced-search-ui').modal('show');
358     }
359
360     function startAdvancedSearch() {
361         var terms = [];
362
363         $('#advanced-search-ui .search-box').each( function() {
364             if ( !this.value ) return;
365
366             terms.push( [ $(this).data('qualifier'), this.value ] );
367         } );
368
369         if ( !terms.length ) return;
370
371         if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
372             $('#advanced-search-ui').modal('hide');
373             $("#search-overlay").show();
374             showResultsBox();
375         }
376     }
377
378     function showResultsBox(data) {
379         $('#search-top-pages, #search-bottom-pages').find('.pagination').empty();
380         $('#searchresults thead tr').empty();
381         $('#searchresults tbody').empty();
382         $('#search-serversinfo').empty().append('<li>' + _("Loading...") + '</li>');
383         $('#search-results-ui').modal('show');
384     }
385
386     function showSearchSorting( sort_key, sort_direction ) {
387         var $th = $('#searchresults thead tr th[data-sort-label="' + sort_key + '"]');
388         $th.parent().find( 'th[data-sort-label]' ).attr( 'class', 'sorting' );
389
390         if ( sort_direction == 'asc' ) {
391             direction = 'asc';
392             $th.attr( 'class', 'sorting_asc' );
393         } else {
394             direction = 'desc';
395             $th.attr( 'class', 'sorting_desc' );
396         }
397     }
398
399     function showSearchResults( editor, data ) {
400         backends.search.records = {};
401
402         $('#searchresults thead tr').empty();
403         $('#searchresults tbody').empty();
404         $('#search-serversinfo').empty();
405
406         $.each( z3950Servers, function( server_id, server ) {
407             var num_fetched = data.num_fetched[server_id];
408
409             if ( data.errors[server_id] ) {
410                 num_fetched = data.errors[server_id];
411             } else if ( num_fetched == null ) {
412                 num_fetched = '-';
413             } else if ( num_fetched < data.num_hits[server_id] ) {
414                 num_fetched += '+';
415             }
416
417             $('#search-serversinfo').append( '<li data-server-id="' + server_id + '"><label><input class="search-toggle-server" type="checkbox"' + ( server.checked ? ' checked="checked">' : '>' ) + server.name + ' (' + num_fetched + ')' + '</label></li>' );
418         } );
419
420         var seenColumns = {};
421
422         $.each( data.hits, function( undef, hit ) {
423             $.each( hit.metadata, function(key) {
424                 seenColumns[key] = true;
425             } );
426         } );
427
428         $('#searchresults thead tr').append('<th>' + _("Source") + '</th>');
429
430         $.each( z3950Labels, function( undef, label ) {
431             if ( seenColumns[ label[0] ] ) {
432                 $('#searchresults thead tr').append( '<th class="sorting" data-sort-label="' + label[0] + '">' + label[1] + '</th>' );
433             }
434         } );
435
436         showSearchSorting( data.sort_key, data.sort_direction );
437
438         $('#searchresults thead tr').append('<th>' + _("Tools") + '</th>');
439
440         $.each( data.hits, function( undef, hit ) {
441             backends.search.records[ hit.server + ':' + hit.index ] = hit.record;
442             hit.id = 'search/' + hit.server + ':' + hit.index;
443
444             var result = '<tr>';
445             result += '<td class="sourcecol">' + z3950Servers[ hit.server ].name + '</td>';
446
447             $.each( z3950Labels, function( undef, label ) {
448                 if ( !seenColumns[ label[0] ] ) return;
449
450                 if ( hit.metadata[ label[0] ] ) {
451                     result += '<td class="infocol">' + hit.metadata[ label[0] ] + '</td>';
452                 } else {
453                     result += '<td class="infocol">&nbsp;</td>';
454                 }
455             } );
456
457             result += '<td class="toolscol"><ul><li><a href="#" class="marc-link">' + _("View MARC") + '</a></li>';
458             result += '<li><a href="#" class="open-link">' + _("Import") + '</a></li>';
459             if ( state.canSave ) result += '<li><a href="#" class="substitute-link" title="' + _("Replace the current record's contents") + '">' + _("Substitute") + '</a></li>';
460             result += '</ul></td></tr>';
461
462             var $tr = $( result );
463             $tr.find( '.marc-link' ).click( function() {
464                 var $info_columns = $tr.find( '.infocol' );
465                 var $marc_column = $tr.find( '.marccol' );
466
467                 if ( !$marc_column.length ) {
468                     $marc_column = $( '<td class="marccol" colspan="' + $info_columns.length + '"></td>' ).insertAfter( $info_columns.eq(-1) ).hide();
469                     CodeMirror.runMode( TextMARC.RecordToText( hit.record ), 'marc', $marc_column[0] );
470                 }
471
472                 if ( $marc_column.is(':visible') ) {
473                     $tr.find('.marc-link').text( _("View MARC") );
474                     $info_columns.show();
475                     $marc_column.hide();
476                 } else {
477                     $tr.find('.marc-link').text( _("Hide MARC") );
478                     $marc_column.show();
479                     $info_columns.hide();
480                 }
481
482                 return false;
483             } );
484             $tr.find( '.open-link' ).click( function() {
485                 $( '#search-results-ui' ).modal('hide');
486                 openRecord( hit.id, editor );
487
488                 return false;
489             } );
490             $tr.find( '.substitute-link' ).click( function() {
491                 $( '#search-results-ui' ).modal('hide');
492                 loadRecord( hit.id, editor );
493
494                 return false;
495             } );
496             $('#searchresults tbody').append( $tr );
497         } );
498
499         var pages = [];
500         var cur_page = data.offset / data.page_size;
501         var max_page = Math.ceil( data.total_fetched / data.page_size ) - 1;
502
503         if ( cur_page != 0 ) {
504             pages.push( '<li><a class="search-nav" href="#" data-offset="' + (data.offset - data.page_size) + '">&laquo; ' + _("Previous") + '</a></li>' );
505         }
506
507         for ( var page = Math.max( 0, cur_page - 9 ); page <= Math.min( max_page, cur_page + 9 ); page++ ) {
508             if ( page == cur_page ) {
509                 pages.push( ' <li class="active"><a href="#">' + ( page + 1 ) + '</a></li>' );
510             } else {
511                 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + ( page * data.page_size ) + '">' + ( page + 1 ) + '</a></li>' );
512             }
513         }
514
515         if ( cur_page < max_page ) {
516             pages.push( ' <li><a class="search-nav" href="#" data-offset="' + (data.offset + data.page_size) + '">' + _("Next") + ' &raquo;</a></li>' );
517         }
518
519         if ( pages.length > 1 ) $( '#search-top-pages, #search-bottom-pages' ).find( '.pagination' ).html( '<ul>' + pages.join( '' ) + '</ul>');
520
521         var $overlay = $('#search-overlay');
522         $overlay.find('span').text(_("Loading"));
523         $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
524
525         if ( data.activeclients ) {
526             $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
527             $overlay.show();
528         } else {
529             $overlay.find('.bar').css( { display: 'block', width: '100%' } );
530             $overlay.fadeOut();
531             $('#searchresults')[0].focus();
532         }
533     }
534
535     function invalidateSearchResults() {
536         var $overlay = $('#search-overlay');
537         $overlay.find('span').text(_("Search expired, please try again"));
538         $overlay.find('.bar').css( { display: 'none' } );
539         $overlay.show();
540     }
541
542     function handleSearchError(error) {
543         if (error.code == 1) {
544             invalidateSearchResults();
545             Search.Reconnect();
546         } else {
547             humanMsg.displayMsg( _("<h3>Internal search error</h3>") + '<p>' + error + '</p>' + _("<p>Please <b>refresh</b> the page and try again."), { className: 'humanError' } );
548         }
549     }
550
551     function handleSearchInitError(error) {
552         $('#quicksearch-overlay').fadeIn().find('p').text(error);
553     }
554
555     // Preference functions
556     function showPreference( pref ) {
557         var value = Preferences.user[pref];
558
559         switch (pref) {
560             case 'fieldWidgets':
561                 $( '#set-field-widgets' ).text( value ? _("Show fields verbatim") : _("Show helpers for fixed and coded fields") );
562                 break;
563             case 'font':
564                 $( '#editor .CodeMirror' ).css( { fontFamily: value } );
565                 editor.refresh();
566                 break;
567             case 'fontSize':
568                 $( '#editor .CodeMirror' ).css( { fontSize: value } );
569                 editor.refresh();
570                 break;
571             case 'macros':
572                 showSavedMacros();
573                 break;
574             case 'selected_search_targets':
575                 $.each( z3950Servers, function( server_id, server ) {
576                     var saved_val = Preferences.user.selected_search_targets[server_id];
577
578                     if ( saved_val != null ) server.checked = saved_val;
579                 } );
580                 break;
581         }
582     }
583
584     function bindPreference( editor, pref ) {
585         function _addHandler( sel, event, handler ) {
586             $( sel ).on( event, function (e) {
587                 e.preventDefault();
588                 handler( e, Preferences.user[pref] );
589                 Preferences.Save( [% USER_INFO.0.borrowernumber %] );
590                 showPreference(pref);
591             } );
592         }
593
594         switch (pref) {
595             case 'fieldWidgets':
596                 _addHandler( '#set-field-widgets', 'click', function( e, oldValue ) {
597                     editor.setUseWidgets( Preferences.user.fieldWidgets = !Preferences.user.fieldWidgets );
598                 } );
599                 break;
600             case 'font':
601                 _addHandler( '#prefs-menu .set-font', 'click', function( e, oldValue ) {
602                     Preferences.user.font = $( e.target ).css( 'font-family' );
603                 } );
604                 break;
605             case 'fontSize':
606                 _addHandler( '#prefs-menu .set-fontSize', 'click', function( e, oldValue ) {
607                     Preferences.user.fontSize = $( e.target ).css( 'font-size' );
608                 } );
609                 break;
610             case 'selected_search_targets':
611                 $( document ).on( 'change', 'input.search-toggle-server', function() {
612                     var server_id = $( this ).closest('li').data('server-id');
613                     Preferences.user.selected_search_targets[server_id] = this.checked;
614                     Preferences.Save( [% USER_INFO.0.borrowernumber %] );
615                 } );
616                 break;
617         }
618     }
619
620     function displayPreferences( editor ) {
621         $.each( Preferences.user, function( pref, value ) {
622             showPreference( pref );
623             bindPreference( editor, pref );
624         } );
625     }
626
627     //> Macro functions
628     function loadMacro( name ) {
629         $( '#macro-list li' ).removeClass( 'active' );
630         macroEditor.activeMacro = name;
631
632         if ( !name ) {
633             macroEditor.setValue( '' );
634             return;
635         }
636
637         $( '#macro-list li[data-name="' + name + '"]' ).addClass( 'active' );
638         var macro = Preferences.user.macros[name];
639         macroEditor.setValue( macro.contents );
640         $( '#macro-format' ).val( macro.format || 'its' );
641         if ( macro.history ) macroEditor.setHistory( macro.history );
642     }
643
644     function storeMacro( name, macro ) {
645         if ( macro ) {
646             Preferences.user.macros[name] = macro;
647         } else {
648             delete Preferences.user.macros[name];
649         }
650
651         Preferences.Save( [% USER_INFO.0.borrowernumber %] );
652     }
653
654     function showSavedMacros( macros ) {
655         var scrollTop = $('#macro-list').scrollTop();
656         $( '#macro-list' ).empty();
657         var macro_list = $.map( Preferences.user.macros, function( macro, name ) {
658             return $.extend( { name: name }, macro );
659         } );
660         macro_list.sort( function( a, b ) {
661             return a.name.localeCompare(b.name);
662         } );
663         $.each( macro_list, function( undef, macro ) {
664             var $li = $( '<li data-name="' + macro.name + '"><a href="#">' + macro.name + '</a><ol class="macro-info"></ol></li>' );
665             $li.click( function() {
666                 loadMacro(macro.name);
667                 return false;
668             } );
669             if ( macro.name == macroEditor.activeMacro ) $li.addClass( 'active' );
670             var modified = macro.modified && new Date(macro.modified);
671             $li.find( '.macro-info' ).append(
672                 '<li><span class="label">' + _("Last changed:") + '</span>' +
673                 ( modified ? modified.toLocaleFormat() : _("never") ) + '</li>'
674             );
675             $('#macro-list').append($li);
676         } );
677         var $new_li = $( '<li class="new-macro"><a href="#">' + _("New macro...") + '</a></li>' );
678         $new_li.click( function() {
679             // TODO: make this a bit less retro
680             var name = prompt(_("Please enter the name for the new macro:"));
681             if (!name) return;
682
683             if ( !Preferences.user.macros[name] ) storeMacro( name, { format: "rancor", contents: "" } );
684             showSavedMacros();
685             loadMacro( name );
686         } );
687         $('#macro-list').append($new_li);
688         $('#macro-list').scrollTop(scrollTop);
689     }
690
691     function saveMacro() {
692         var name = macroEditor.activeMacro;
693
694         if ( !name || macroEditor.savedGeneration == macroEditor.changeGeneration() ) return;
695
696         macroEditor.savedGeneration = macroEditor.changeGeneration();
697         storeMacro( name, { contents: macroEditor.getValue(), modified: (new Date()).valueOf(), history: macroEditor.getHistory(), format: $('#macro-format').val() } );
698         $('#macro-save-message').text(_("Saved"));
699         showSavedMacros();
700     }
701
702     $(document).ready( function() {
703         // Editor setup
704         editor = new MARCEditor( {
705             onCursorActivity: function() {
706                 $('#status-tag-info').empty();
707                 $('#status-subfield-info').empty();
708
709                 var field = editor.getCurrentField();
710                 var cur = editor.getCursor();
711
712                 if ( !field ) return;
713
714                 var taginfo = KohaBackend.GetTagInfo( '', field.tag );
715                 $('#status-tag-info').html( '<strong>' + field.tag + ':</strong> ' );
716
717                 if ( taginfo ) {
718                     $('#status-tag-info').append( '<a href="' + getFieldHelpURL( field.tag ) + '" target="_blank" class="show-field-help" title="' + _("Show help for this tag") + '">[?]</a> '  + taginfo.lib );
719
720                     var subfield = field.getSubfieldAt( cur.ch );
721                     if ( !subfield ) return;
722
723                     var subfieldinfo = taginfo.subfields[ subfield.code ];
724                     $('#status-subfield-info').html( '<strong>$' + subfield.code + ':</strong> ' );
725
726                     if ( subfieldinfo ) {
727                         $('#status-subfield-info').append( subfieldinfo.lib );
728                     } else {
729                         $('#status-subfield-info').append( '<em>' + _("Unknown subfield") + '</em>' );
730                     }
731                 } else {
732                     $('#status-tag-info').append( '<em>' + _("Unknown tag") + '</em>' );
733                 }
734             },
735             position: function (elt) { $(elt).insertAfter('#toolbar') },
736         } );
737
738         macroEditor = CodeMirror(
739             $('#macro-editor')[0],
740             {
741                 mode: 'null',
742                 lineNumbers: true,
743             }
744         );
745
746         // Automatically detect resizes and change the height of the editor and position of modals.
747         var resizeTimer = null;
748         $( window ).resize( function() {
749             if ( resizeTimer == null ) resizeTimer = setTimeout( function() {
750                 resizeTimer = null;
751
752                 var pos = $('#editor .CodeMirror').position();
753                 $('#editor .CodeMirror').height( $(window).height() - pos.top - 24 - $('#changelanguage').height() ); // 24 is hardcoded value but works well
754
755                 $('.modal-body').each( function() {
756                     $(this).height( $(window).height() * .8 - $(this).prevAll('.modal-header').height() );
757                 } );
758             }, 100);
759
760             $("#advanced-search-ui, #search-results-ui, #macro-ui").css( {
761                 marginLeft: function() {
762                     return -($(this).width() / 2);
763                 }
764             } );
765
766         } ).resize();
767
768         var saveableBackends = [];
769         $.each( backends, function( id, backend ) {
770             if ( backend.save ) saveableBackends.push( [ backend.saveLabel, id ] );
771         } );
772         saveableBackends.sort();
773         $.each( saveableBackends, function( undef, backend ) {
774             $( '#save-dropdown' ).append( '<li><a href="#" data-backend="' + backend[1] + '">' + backend[0] + '</a></li>' );
775         } );
776
777         var macro_format_list = $.map( Macros.formats, function( format, name ) {
778             return $.extend( { name: name }, format );
779         } );
780         macro_format_list.sort( function( a, b ) {
781             return a.description.localeCompare(b.description);
782         } );
783         $.each( macro_format_list, function() {
784             $('#macro-format').append( '<option value="' + this.name + '">' + this.description + '</option>' );
785         } );
786
787         // Click bindings
788         $( '#save-record, #save-dropdown a' ).click( function() {
789             $( '#save-record' ).find('i').attr( 'class', 'icon-loading' ).siblings( 'span' ).text( _("Saving...") );
790
791             function finishCb(result) {
792                 if ( result.error == 'syntax' ) {
793                     humanMsg.displayAlert( _("Incorrect syntax, cannot save"), { className: 'humanError' } );
794                 } else if ( result.error == 'invalid' ) {
795                     humanMsg.displayAlert( _("Record structure invalid, cannot save"), { className: 'humanError' } );
796                 } else if ( !result.error ) {
797                     humanMsg.displayAlert( _("Record saved "), { className: 'humanSuccess' } );
798                 }
799
800                 $.each( result.errors || [], function( undef, error ) {
801                     switch ( error.type ) {
802                         case 'noTag':
803                             editor.addError( error.line, _("Invalid tag number") );
804                             break;
805                         case 'noIndicators':
806                             editor.addError( error.line, _("Invalid indicators") );
807                             break;
808                         case 'missingTag':
809                             editor.addError( null, _("Missing mandatory tag: ") + error.tag );
810                             break;
811                         case 'missingSubfield':
812                             if ( error.subfield == '@' ) {
813                                 editor.addError( error.line, _("Missing control field contents") );
814                             } else {
815                                 editor.addError( error.line, _("Missing mandatory subfield: $") + error.subfield );
816                             }
817                             break;
818                         case 'unrepeatableTag':
819                             editor.addError( error.line, _("Tag ") + error.tag + _(" cannot be repeated") );
820                             break;
821                         case 'unrepeatableSubfield':
822                             editor.addError( error.line, _("Subfield $") + error.subfield + _(" cannot be repeated") );
823                             break;
824                     }
825                 } );
826
827                 $( '#save-record' ).find('i').attr( 'class', 'icon-hdd' );
828
829                 if ( result.error ) {
830                     // Reset backend info
831                     setSource( [ state.backend, state.recordID ] );
832                 }
833             }
834
835             var backend = $( this ).data( 'backend' ) || ( state.saveBackend );
836             if ( state.backend == backend ) {
837                 saveRecord( backend + '/' + state.recordID, editor, finishCb );
838             } else {
839                 saveRecord( backend + '/', editor, finishCb );
840             }
841
842             return false;
843         } );
844
845         $('#import-records').click( function() {
846             $('#import-records-input')
847                 .off('change')
848                 .change( function() {
849                     if ( !this.files || !this.files.length ) return;
850
851                     var file = this.files[0];
852                     var reader = new FileReader();
853
854                     reader.onload = function() {
855                         var record = new MARC.Record();
856
857                         if ( /\.mrc$/.test( file.name ) ) {
858                             record.loadISO2709( reader.result );
859                         } else if ( /\.xml$/.test( file.name ) ) {
860                             record.loadMARCXML( reader.result );
861                         } else {
862                             humanMsg.displayAlert( _("Unknown record type, cannot import"), { className: 'humanError' } );
863                             return;
864                         }
865
866                         editor.displayRecord( record );
867                     };
868
869                     reader.readAsText( file );
870                 } )
871                 .click();
872
873             return false;
874         } );
875
876         $('#open-macros').click( function() {
877             $('#macro-ui').modal('show');
878
879             return false;
880         } );
881
882         $('#run-macro').click( function() {
883             var result = Macros.Run( editor, $('#macro-format').val(), macroEditor.getValue() );
884
885             if ( !result.errors.length ) {
886                 $('#macro-ui').modal('hide');
887                 return false;
888             }
889
890             var errors = [];
891             $.each( result.errors, function() {
892                 var error = '<b>' + _("Line ") + (this.line + 1) + ':</b> ';
893
894                 switch ( this.error ) {
895                     case 'failed': error += _("failed to run"); break;
896                     case 'unrecognized': error += _("unrecognized command"); break;
897                 }
898
899                 errors.push(error);
900             } );
901
902             humanMsg.displayMsg( _("<h3>Failed to run macro:</h3>") + '<ul><li>' + errors.join('</li><li>') + '</li></ul>', { className: 'humanError' } );
903
904             return false;
905         } );
906
907         $('#delete-macro').click( function() {
908             if ( !macroEditor.activeMacro || !confirm( _("Are you sure you want to delete this macro?") ) ) return;
909
910             storeMacro( macroEditor.activeMacro, undefined );
911             showSavedMacros();
912             loadMacro( undefined );
913
914             return false;
915         } );
916
917         var saveTimeout;
918         macroEditor.on( 'change', function( cm, change ) {
919             $('#macro-save-message').empty();
920             if ( change.origin == 'setValue' ) return;
921
922             if ( saveTimeout ) clearTimeout( saveTimeout );
923             saveTimeout = setTimeout( function() {
924                 saveMacro();
925
926                 saveTimeout = null;
927             }, 500 );
928         } );
929
930         $( '#switch-editor' ).click( function() {
931             if ( !confirm( _("Any changes will not be saved. Continue?") ) ) return;
932
933             $.cookie( 'catalogue_editor_[% USER_INFO.0.borrowernumber %]', 'basic', { expires: 365, path: '/' } );
934
935             if ( state.backend == 'catalog' ) {
936                 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=' + state.recordID;
937             } else if ( state.backend == 'new' ) {
938                 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl';
939             } else {
940                 humanMsg.displayAlert( _("Cannot open this record in the basic editor"), { className: 'humanError' } );
941             }
942         } );
943
944         $( '#show-advanced-search' ).click( function() {
945             showAdvancedSearch();
946
947             return false;
948         } );
949
950         $('#advanced-search').submit( function() {
951             startAdvancedSearch();
952
953             return false;
954         } );
955
956         $( document ).on( 'click', 'a.search-nav', function() {
957             $("#search-overlay").show();
958             Search.Fetch( { offset: $( this ).data( 'offset' ) } );
959             return false;
960         });
961
962         $( document ).on( 'click', 'th[data-sort-label]', function() {
963             $("#search-overlay").show();
964             var direction;
965
966             if ( $( this ).hasClass( 'sorting_asc' ) ) {
967                 direction = 'desc';
968             } else {
969                 direction = 'asc';
970             }
971
972             showSearchSorting( $( this ).data( 'sort-label' ), direction );
973
974             Search.Fetch( { sort_key: $( this ).data( 'sort-label' ), sort_direction: direction } );
975             return false;
976         });
977
978         $( document ).on( 'change', 'input.search-toggle-server', function() {
979             var server = z3950Servers[ $( this ).closest('li').data('server-id') ];
980             server.checked = this.checked;
981
982             if ( $('#search-results-ui').is( ':visible' ) ) {
983                 $("#search-overlay").show();
984                 Search.Fetch();
985             }
986         } );
987
988         // Key bindings
989         bindGlobalKeys();
990
991         // Setup UI
992         $("#advanced-search-ui, #search-results-ui, #macro-ui").each( function() {
993             $(this).modal({ show: false });
994         } );
995
996         var $quicksearch = $('#quicksearch fieldset');
997         $('<div id="quicksearch-overlay"><h3>' + _("Search unavailable") + '</h3> <p></p></div>').css({
998             position: 'absolute',
999             top: $quicksearch.offset().top,
1000             left: $quicksearch.offset().left,
1001             height: $quicksearch.outerHeight(),
1002             width: $quicksearch.outerWidth(),
1003         }).appendTo(document.body).hide();
1004
1005         var prevAlerts = [];
1006         humanMsg.logMsg = function(msg, options) {
1007             $('#show-alerts').popover('hide');
1008             prevAlerts.unshift('<li>' + msg + '</li>');
1009             prevAlerts.splice(5, 999); // Truncate old messages
1010         };
1011
1012         $('#show-alerts').popover({
1013             html: true,
1014             placement: 'bottom',
1015             content: function() {
1016                 return '<div id="alerts-container"><ul>' + prevAlerts.join('') + '</ul></div>';
1017             },
1018         });
1019         $('#new-record' ).click( function() {
1020             openRecord( 'new/', editor );
1021             return false;
1022         } );
1023
1024         // Start editor
1025         Preferences.Load( [% USER_INFO.0.borrowernumber || 0 %] );
1026         displayPreferences(editor);
1027         makeAuthorisedValueWidgets( '' );
1028         Search.Init( {
1029             page_size: 20,
1030             onresults: function(data) { showSearchResults( editor, data ) },
1031             onerror: handleSearchError,
1032         } );
1033
1034         function finishCb( data ) {
1035             if ( data.error ) openRecord( 'new/', editor, finishCb );
1036
1037             Resources.GetAll().done( function() {
1038                 $("#loading").hide();
1039                 editor.focus();
1040             } );
1041         }
1042
1043         if ( "[% auth_forwarded_hash %]" ) {
1044             document.location.hash = "[% auth_forwarded_hash %]";
1045         }
1046
1047         if ( !document.location.hash || !openRecord( document.location.hash.slice(1), editor, finishCb ) ) {
1048             openRecord( 'new/', editor, finishCb );
1049         }
1050     } );
1051 } )();
1052
1053 </script>