Bug 27981: Add option to automatically set 001 to the biblionumber
[koha.git] / koha-tmpl / intranet-tmpl / prog / en / includes / cateditor-ui.inc
1 [% USE raw %]
2 [% USE Koha %]
3 [% Asset.js("lib/codemirror/codemirror.min.js") | $raw %]
4 [% Asset.js("lib/filesaver.js") | $raw %]
5 [% Asset.css("lib/keyboard/css/keyboard.min.css") | $raw %]
6 [% Asset.js("lib/keyboard/js/jquery.keyboard.js") | $raw %]
7 [% Asset.js("lib/keyboard/languages/all.min.js") | $raw %]
8 [% Asset.js("lib/keyboard/layouts/all.min.js") | $raw %]
9 [% Asset.js("lib/koha/cateditor/marc-mode.js") | $raw %]
10 [% Asset.js("lib/require.js") | $raw %]
11 <!-- cateditor-ui.inc -->
12 <script>
13 [% FOREACH shortcut IN shortcuts -%]
14     var [% shortcut.shortcut_name | html %] = "[% shortcut.shortcut_keys | html %]";
15 [% END %]
16     var authInfo = {
17         [%- FOREACH authtag = authtags -%]
18             [% authtag.tagfield | html %]: {
19                 subfield: '[% authtag.tagsubfield | html %]',
20                 authtypecode: '[% authtag.authtypecode | html %]',
21                 },
22         [%- END -%]
23     };
24 require.config( {
25     baseUrl: '[% interface | html %]/lib/koha/cateditor/',
26     config: {
27         resources: {
28             marcflavour: '[% marcflavour | html %]',
29             themelang: '[% themelang | html %]',
30         },
31     },
32     waitSeconds: 30,
33 } );
34 </script>
35
36 [% IF marcflavour == 'MARC21' %]
37 [% PROCESS 'cateditor-widgets-marc21.inc' %]
38 [% ELSE %]
39 <script>var editorWidgets = {};</script>
40 [% END %]
41
42 <script>
43 require( [ 'koha-backend', 'search', 'macros', 'marc-editor', 'marc-record', 'preferences', 'resources', 'text-marc', 'widget' ], function( KohaBackend, Search, Macros, MARCEditor, MARC, Preferences, Resources, TextMARC, Widget ) {
44     var z3950Servers = [
45         {
46             server_id: 'koha:biblioserver',
47             name: _("Local catalog"),
48             recordtype: 'biblio',
49             checked: false,
50         },
51         [%- FOREACH server = z3950_servers -%]
52             {
53                 server_id: [% server.id | html %],
54                 name: '[% server.servername | html_entity %]',
55                 recordtype: '[% server.recordtype | html %]',
56                 checked: [% server.checked ? 'true' : 'false' | html %],
57             },
58         [%- END -%]
59     ];
60
61     // The columns that should show up in a search, in order, and keyed by the corresponding <metadata> tag in the XSL and Pazpar2 config
62     var z3950Labels = [
63         [ "local_number", _("Local number") ],
64         [ "title", _("Title") ],
65         [ "subtitle",_("Subtitle") ],
66         [ "series", _("Series title") ],
67         [ "author", _("Author") ],
68         [ "lccn", _("LCCN") ],
69         [ "isbn", _("ISBN") ],
70         [ "issn", _("ISSN") ],
71         [ "medium", _("Medium") ],
72         [ "edition", _("Edition") ],
73         [ "date", _("Published") ],
74         [ "notes", _("Notes") ],
75     ];
76
77     var state = {
78         backend: '',
79         saveBackend: 'catalog',
80         recordID: undefined
81     };
82
83     var editor;
84     var macroEditor;
85
86     function makeAuthorisedValueWidgets( frameworkCode ) {
87         $.each( KohaBackend.GetAllTagsInfo( frameworkCode ), function( tag, tagInfo ) {
88             $.each( tagInfo.subfields, function( subfield, subfieldInfo ) {
89                 if ( !subfieldInfo.authorised_value ) return;
90                 var authvals = KohaBackend.GetAuthorisedValues( subfieldInfo.authorised_value );
91                 if ( !authvals ) return;
92
93                 var defaultvalue = subfield.defaultvalue || authvals[0].value;
94
95                 Widget.Register( tag + subfield, {
96                     init: function() {
97                         var $result = $( '<span class="subfield-widget"></span>' );
98
99                         return $result[0];
100                     },
101                     postCreate: function() {
102                         var value = defaultvalue;
103                         var widget = this;
104
105                         $.each( authvals, function() {
106                             if ( this.value == widget.text ) {
107                                 value = this.value;
108                             }
109                         } );
110
111                         this.setText( value );
112
113                         $( '<select></select>' ).appendTo( this.node );
114                         var $node = $( this.node ).find( 'select' );
115                         $.each( authvals, function( undef, authval ) {
116                             $node.append( '<option value="' + authval.value + '"' + (authval.value == value ? ' selected="selected"' : '') + '>' + authval.lib + '</option>' );
117                         } );
118                         $node.val( this.text );
119
120                         $node.change( $.proxy( function() {
121                             this.setText( $node.val() );
122                         }, this ) );
123                     },
124                     makeTemplate: function() {
125                         return defaultvalue;
126                     },
127                 } );
128             } );
129         } );
130     }
131
132     function bindGlobalKeys() {
133         shortcut.add( 'ctrl+s', function(event) {
134             $( '#save-record' ).click();
135
136             event.preventDefault();
137         } );
138
139         shortcut.add( 'alt+ctrl+k', function(event) {
140             $( '#search-by-keywords' ).focus();
141
142             return false;
143         } );
144
145         shortcut.add( 'alt+ctrl+a', function(event) {
146             $( '#search-by-author' ).focus();
147
148             return false;
149         } );
150
151         shortcut.add( 'alt+ctrl+i', function(event) {
152             $( '#search-by-isbn' ).focus();
153
154             return false;
155         } );
156
157         shortcut.add( 'alt+ctrl+t', function(event) {
158             $( '#search-by-title' ).focus();
159
160             return false;
161         } );
162
163         shortcut.add( 'ctrl+h', function() {
164             var field = editor.getCurrentField();
165
166             if ( !field ) return;
167
168             window.open( getFieldHelpURL( field.tag ) );
169         } );
170
171         $('#quicksearch .search-box').each( function() {
172             shortcut.add( 'enter', $.proxy( function() {
173                 var terms = [];
174
175                 $('#quicksearch .search-box').each( function() {
176                     if ( !this.value ) return;
177
178                     terms.push( [ $(this).data('qualifier'), this.value ] );
179                 } );
180
181                 if ( !terms.length ) return;
182
183                 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
184                     $("#search-overlay").show();
185                     showResultsBox();
186                 }
187
188                 return false;
189             }, this), { target: this, type: 'keypress' } );
190         } );
191     }
192
193     function getFieldHelpURL( tag ) {
194         [% IF Koha.Preference('marcfielddocurl') %]
195             var docurl = "[% Koha.Preference('marcfielddocurl').replace('"','&quot;') | html %]";
196             docurl = docurl.replace("{MARC}", "[% marcflavour | html %]");
197             docurl = docurl.replace("{FIELD}", ""+tag);
198             docurl = docurl.replace("{LANG}", "[% lang | html %]");
199             return docurl;
200         [% ELSIF ( marcflavour == 'MARC21' ) %]
201             if ( tag == '000' ) {
202                 return "http://www.loc.gov/marc/bibliographic/bdleader.html";
203             } else if ( tag >= '090' && tag < '100' ) {
204                 return "http://www.loc.gov/marc/bibliographic/bd09x.html";
205             } else if ( tag < '900' ) {
206                 return "http://www.loc.gov/marc/bibliographic/bd" + tag + ".html";
207             } else {
208                 return "http://www.loc.gov/marc/bibliographic/bd9xx.html";
209             }
210         [% ELSIF ( marcflavour == 'UNIMARC' ) %]
211             /* http://archive.ifla.org/VI/3/p1996-1/ is an outdated version of UNIMARC, but
212                seems to be the only version available that can be linked to per tag.  More recent
213                versions of the UNIMARC standard are available on the IFLA website only as
214                PDFs!
215             */
216             if ( tag == '000' ) {
217                return  "http://archive.ifla.org/VI/3/p1996-1/uni.htm";
218             } else {
219                 var first = tag[0];
220                 var url = "http://archive.ifla.org/VI/3/p1996-1/uni" + first + ".htm#";
221                 if ( first == '0' ) url += "b";
222                 if ( first != '9' ) url += tag;
223
224                 return url;
225             }
226         [% END %]
227     }
228
229     // Record loading
230     var backends = {
231        'new': {
232             titleForRecord: _("Editing new record"),
233             get: function( id, callback ) {
234                 record = new MARC.Record();
235                 KohaBackend.FillRecord( '', record );
236
237                 callback( record );
238             },
239         },
240         'new-full': {
241             titleForRecord: _("Editing new full record"),
242             get: function( id, callback ) {
243                 record = new MARC.Record();
244                 KohaBackend.FillRecord( '', record, true );
245
246                 callback( record );
247             },
248         },
249         'duplicate': {
250             titleForRecord: _("Editing duplicate record of #{ID}"),
251             saveLabel: _("Duplicate"),
252             get: function( id, callback ) {
253                 if ( !id ) return false;
254                 var remove_control_num = [% IF Koha.Preference('autoControlNumber') == 'OFF' %] 0 [% ELSE %] 1 [% END %];
255
256                 KohaBackend.GetRecord( id, remove_control_num, callback );
257             },
258             save: function( id, record, done ) {
259                 function finishCb( data ) {
260                     done( { error: data.error, newRecord: data.marcxml && data.marcxml[0], newId: data.biblionumber && [ 'catalog', data.biblionumber ] } );
261                 }
262                 [% IF Koha.Preference('autoControlNumber') != 'OFF' %]
263                     record.removeField("001");
264                 [% END %]
265
266                 KohaBackend.CreateRecord( record, finishCb );
267             }
268         },
269         'catalog': {
270             titleForRecord: _("Editing catalog record #{ID}"),
271             links: [
272                 { title: _("view"), href: "/cgi-bin/koha/catalogue/detail.pl?biblionumber={ID}" },
273                 { title: _("edit items"), href: "/cgi-bin/koha/cataloguing/additem.pl?biblionumber={ID}" },
274             ],
275             saveLabel: _("Save to catalog"),
276             get: function( id, callback ) {
277                 if ( !id ) return false;
278
279                 KohaBackend.GetRecord( id, "", callback );
280             },
281             save: function( id, record, done ) {
282                 function finishCb( data ) {
283                     done( { error: data.error, newRecord: data.marcxml && data.marcxml[0], newId: data.biblionumber && [ 'catalog', data.biblionumber ] } );
284                 }
285
286                 if ( id ) {
287                     KohaBackend.SaveRecord( id, record, finishCb );
288                 } else {
289                     KohaBackend.CreateRecord( record, finishCb );
290                 }
291             }
292         },
293         'iso2709': {
294             saveLabel: _("Save as MARC (.mrc) file"),
295             save: function( id, record, done ) {
296                 var recname = 'record.mrc';
297                 if(state.recordID) {
298                     recname = 'bib-'+state.recordID+'.mrc';
299                 }
300
301                 [% IF (Koha.Preference('DefaultSaveRecordFileID') == 'controlnumber') %]
302                     var controlnumfield = record.field('001');
303                     if(controlnumfield) {
304                         recname = 'record-'+controlnumfield.subfield('@')+'.mrc';
305                     }
306                 [% END %]
307                 saveAs( new Blob( [record.toISO2709()], { 'type': 'application/octet-stream;charset=utf-8' } ), recname );
308
309                 done( {} );
310             }
311         },
312         'marcxml': {
313             saveLabel: _("Save as MARCXML (.xml) file"),
314             save: function( id, record, done ) {
315                 var recname = 'record.xml';
316                 if(state.recordID) {
317                     recname = 'bib-'+state.recordID+'.xml';
318                 }
319
320                 [% IF (Koha.Preference('DefaultSaveRecordFileID') == 'controlnumber') %]
321                     var controlnumfield = record.field('001');
322                     if(controlnumfield) {
323                         recname = 'record-'+controlnumfield.subfield('@')+'.xml';
324                     }
325                 [% END %]
326                 saveAs( new Blob( [record.toXML()], { 'type': 'application/octet-stream;charset=utf-8' } ), recname );
327
328                 done( {} );
329             }
330         },
331         'search': {
332             titleForRecord: _("Editing search result"),
333             get: function( id, callback ) {
334                 if ( !id ) return false;
335                 if ( !backends.search.records[ id ] ) {
336                     callback( { error: _( "Invalid record" ) } );
337                     return false;
338                 }
339
340                 callback( backends.search.records[ id ] );
341             },
342             records: {},
343         },
344     };
345
346     function setSource(parts) {
347         state.backend = parts[0];
348         state.recordID = parts[1];
349         state.canSave = backends[ state.backend ].save != null;
350         state.saveBackend = state.canSave ? state.backend : 'catalog';
351
352         var backend = backends[state.backend];
353
354         document.location.hash = '#' + parts[0] + '/' + parts[1];
355
356         $('#title').text( backend.titleForRecord.replace( '{ID}', parts[1] ) );
357
358         $.each( backend.links || [], function( i, link ) {
359             $('#title').append(' <a target="_blank" href="' + link.href.replace( '{ID}', parts[1] ) + '">(' + link.title + ')</a>' );
360         } );
361         $( 'title', document.head ).html( _("Koha &rsaquo; Cataloging &rsaquo; ") + backend.titleForRecord.replace( '{ID}', parts[1] ) );
362         $('#save-record span').text( backends[ state.saveBackend ].saveLabel );
363     }
364
365     function saveRecord( recid, editor, callback ) {
366         var parts = recid.split('/');
367         if ( parts.length != 2 ) return false;
368
369         if ( !backends[ parts[0] ] || !backends[ parts[0] ].save ) return false;
370
371         editor.removeErrors();
372         var record = editor.getRecord();
373
374         if ( record.errors ) {
375             state.saving = false;
376             callback( { error: 'syntax', errors: record.errors } );
377             return;
378         }
379
380         var errors = KohaBackend.ValidateRecord( '', record );
381         if ( errors.length ) {
382             state.saving = false;
383             callback( { error: 'invalid', errors: errors } );
384             return;
385         }
386
387         backends[ parts[0] ].save( parts[1], record, function(data) {
388             state.saving = false;
389
390             if (data.newRecord) {
391                 var record = new MARC.Record();
392                 record.loadMARCXML(data.newRecord);
393                 record.frameworkcode = data.newRecord.frameworkcode;
394                 editor.displayRecord( record );
395             }
396
397             if (data.newId) {
398                 setSource(data.newId);
399             } else {
400                 setSource( [ state.backend, state.recordID ] );
401             }
402
403             if (callback) callback( data );
404         } );
405     }
406
407     function loadRecord( recid, editor, callback ) {
408         var parts = recid.split('/');
409         if ( parts.length != 2 ) return false;
410
411         if ( !backends[ parts[0] ] || !backends[ parts[0] ].get ) return false;
412
413         backends[ parts[0] ].get( parts[1], function( record ) {
414             if ( !record.error ) {
415                 editor.displayRecord( record );
416                 editor.focus();
417             }
418
419             if (callback) callback(record);
420         } );
421
422         return true;
423     }
424
425     function openRecord( recid, editor, callback ) {
426         return loadRecord( recid, editor, function ( record ) {
427             setSource( recid.split('/') );
428
429             if (callback) callback( record );
430         } );
431     }
432
433     // Search functions
434     function showAdvancedSearch() {
435         $('#advanced-search-servers').empty();
436         $.each( z3950Servers, function( index, server ) {
437             $('#advanced-search-servers').append( '<li data-server-id="' + index + '"><label><input class="search-toggle-server" type="checkbox"' + ( server.checked ? ' checked="checked">' : '>' ) + server.name + '</label></li>' );
438         } );
439         $('#advanced-search-ui').modal('show');
440     }
441
442     function startAdvancedSearch() {
443         var terms = [];
444
445         $('#advanced-search-ui .search-box').each( function() {
446             if ( !this.value ) return;
447
448             terms.push( [ $(this).data('qualifier'), this.value ] );
449         } );
450
451         if ( !terms.length ) return;
452
453         if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
454             $('#advanced-search-ui').modal('hide');
455             $("#search-overlay").show();
456             showResultsBox();
457         }
458     }
459
460     function showResultsBox(data) {
461         $('#search-top-pages, #search-bottom-pages').find('nav').empty();
462         $('#searchresults thead tr').empty();
463         $('#searchresults tbody').empty();
464         $('#search-serversinfo').empty().append('<li>' + _("Loading...") + '</li>');
465         $('#search-results-ui').modal('show');
466     }
467
468     function showSearchSorting( sort_key, sort_direction ) {
469         var $th = $('#searchresults thead tr th[data-sort-label="' + sort_key + '"]');
470         $th.parent().find( 'th[data-sort-label]' ).attr( 'class', 'sorting' );
471
472         if ( sort_direction == 'asc' ) {
473             direction = 'asc';
474             $th.attr( 'class', 'sorting_asc' );
475         } else {
476             direction = 'desc';
477             $th.attr( 'class', 'sorting_desc' );
478         }
479     }
480
481     function showSearchResults( editor, data ) {
482         backends.search.records = {};
483
484         $('#searchresults thead tr').empty();
485         $('#searchresults tbody').empty();
486         $('#search-serversinfo').empty();
487
488         $.each( z3950Servers, function( index, server ) {
489             var num_fetched = data.num_fetched[server.server_id];
490
491             if ( data.errors[server.server_id] ) {
492                 num_fetched = data.errors[server.server_id];
493             } else if ( num_fetched == null ) {
494                 num_fetched = '-';
495             } else if ( num_fetched < data.num_hits[server.server_id] ) {
496                 num_fetched += '+';
497             }
498
499             $('#search-serversinfo').append( '<li data-server-id="' + index + '"><label><input class="search-toggle-server" type="checkbox"' + ( server.checked ? ' checked="checked">' : '>' ) + server.name + ' (' + num_fetched + ')' + '</label></li>' );
500         } );
501
502         var seenColumns = {};
503
504         $.each( data.hits, function( undef, hit ) {
505             $.each( hit.metadata, function(key) {
506                 seenColumns[key] = true;
507             } );
508         } );
509
510         $('#searchresults thead tr').append('<th>' + _("Source") + '</th>');
511
512         $.each( z3950Labels, function( undef, label ) {
513             if ( seenColumns[ label[0] ] ) {
514                 $('#searchresults thead tr').append( '<th class="sorting" data-sort-label="' + label[0] + '">' + label[1] + '</th>' );
515             }
516         } );
517
518         showSearchSorting( data.sort_key, data.sort_direction );
519
520         $('#searchresults thead tr').append('<th>' + _("Tools") + '</th>');
521
522         var bibnumMap = KohaBackend.GetSubfieldForKohaField('biblio.biblionumber');
523         $.each( data.hits, function( undef, hit ) {
524             backends.search.records[ hit.server + ':' + hit.index ] = hit.record;
525
526             switch ( hit.server ) {
527                 case 'koha:biblioserver':
528                     var bibnumField = hit.record.field( bibnumMap[0] );
529
530                     if ( bibnumField && bibnumField.hasSubfield( bibnumMap[1] ) ) {
531                         hit.id = 'catalog/' + bibnumField.subfield( bibnumMap[1] );
532                         break;
533                     }
534
535                     // Otherwise, fallthrough
536
537                 default:
538                     hit.id = 'search/' + hit.server + ':' + hit.index;
539             }
540
541             var result = '<tr>';
542             var server_name = hit.servername == 'koha:biblioserver' ? _("Local catalog") : hit.servername;
543             result += '<td class="sourcecol">' + server_name + '</td>';
544
545             $.each( z3950Labels, function( undef, label ) {
546                 if ( !seenColumns[ label[0] ] ) return;
547
548                 if ( hit.metadata[ label[0] ] ) {
549                     result += '<td class="infocol">' + hit.metadata[ label[0] ] + '</td>';
550                 } else {
551                     result += '<td class="infocol">&nbsp;</td>';
552                 }
553             } );
554
555             result += '<td class="toolscol"><ul><li><a href="#" class="marc-link">' + _("View MARC") + '</a></li>';
556             result += '<li><a href="#" class="open-link">' + ( hit.server == 'koha:biblioserver' ? _("Edit") : _("Import") ) + '</a></li>';
557             if ( state.canSave ) result += '<li><a href="#" class="substitute-link" title="' + _("Replace the current record's contents") + '">' + _("Substitute") + '</a></li>';
558             result += '</ul></td></tr>';
559
560             var $tr = $( result );
561             $tr.find( '.marc-link' ).click( function() {
562                 var $info_columns = $tr.find( '.infocol' );
563                 var $marc_column = $tr.find( '.marccol' );
564
565                 if ( !$marc_column.length ) {
566                     $marc_column = $( '<td class="marccol" colspan="' + $info_columns.length + '"></td>' ).insertAfter( $info_columns.eq(-1) ).hide();
567                     CodeMirror.runMode( TextMARC.RecordToText( hit.record ), 'marc', $marc_column[0] );
568                 }
569
570                 if ( $marc_column.is(':visible') ) {
571                     $tr.find('.marc-link').text( _("View MARC") );
572                     $info_columns.show();
573                     $marc_column.hide();
574                 } else {
575                     $tr.find('.marc-link').text( _("Hide MARC") );
576                     $marc_column.show();
577                     $info_columns.hide();
578                 }
579
580                 return false;
581             } );
582             $tr.find( '.open-link' ).click( function() {
583                 $( '#search-results-ui' ).modal('hide');
584                 openRecord( hit.id, editor );
585
586                 return false;
587             } );
588             $tr.find( '.substitute-link' ).click( function() {
589                 $( '#search-results-ui' ).modal('hide');
590                 loadRecord( hit.id, editor );
591
592                 return false;
593             } );
594             $('#searchresults tbody').append( $tr );
595         } );
596
597         var pages = [];
598         var cur_page = data.offset / data.page_size;
599         var max_page = Math.ceil( data.total_fetched / data.page_size ) - 1;
600
601         if ( cur_page != 0 ) {
602             pages.push( '<li><a class="search-nav" href="#" data-offset="' + (data.offset - data.page_size) + '"><span aria-hidden="true">&laquo;</span> ' + _("Previous") + '</a></li>' );
603         }
604
605         for ( var page = Math.max( 0, cur_page - 9 ); page <= Math.min( max_page, cur_page + 9 ); page++ ) {
606             if ( page == cur_page ) {
607                 pages.push( ' <li class="active"><a href="#">' + ( page + 1 ) + '</a></li>' );
608             } else {
609                 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + ( page * data.page_size ) + '">' + ( page + 1 ) + '</a></li>' );
610             }
611         }
612
613         if ( cur_page < max_page ) {
614             pages.push( ' <li><a class="search-nav" href="#" data-offset="' + (data.offset + data.page_size) + '">' + _("Next") + ' <span aria-hidden="true">&raquo;</span></a></li>' );
615         }
616
617         $( '#search-top-pages, #search-bottom-pages' ).find( 'nav' ).html( pages.length > 1 ? ( '<ul class="pagination pagination-sm">' + pages.join( '' ) + '</ul>' ) : '' );
618
619         var $overlay = $('#search-overlay');
620         $overlay.find('span').text(_("Loading"));
621         $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
622
623         if ( data.activeclients ) {
624             $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
625             $overlay.show();
626         } else {
627             $overlay.find('.bar').css( { display: 'block', width: '100%' } );
628             $overlay.fadeOut();
629             $('#searchresults')[0].focus();
630         }
631     }
632
633     function invalidateSearchResults() {
634         var $overlay = $('#search-overlay');
635         $overlay.find('span').text(_("Search expired, please try again"));
636         $overlay.find('.bar').css( { display: 'none' } );
637         $overlay.show();
638     }
639
640     function handleSearchError(error) {
641         if (error.code == 1) {
642             invalidateSearchResults();
643             Search.Reconnect();
644         } else {
645             humanMsg.displayMsg( '<h3>' + _("Internal search error") + '</h3><p>' + error.responseText + '</p><p>' + _("Please refresh the page and try again.") + '</p>', { className: 'humanError' } );
646         }
647     }
648
649     function handleSearchInitError(error) {
650         $('#quicksearch-overlay').fadeIn().find('p').text(error);
651     }
652
653     // Preference functions
654     function showPreference( pref ) {
655         var value = Preferences.user[pref];
656
657         switch (pref) {
658             case 'fieldWidgets':
659                 $( '#set-field-widgets' ).text( value ? _("Show fields verbatim") : _("Show helpers for fixed and coded fields") );
660                 break;
661             case 'font':
662                 $( '#editor .CodeMirror' ).css( { fontFamily: value } );
663                 editor.refresh();
664                 break;
665             case 'fontSize':
666                 $( '#editor .CodeMirror' ).css( { fontSize: value } );
667                 editor.refresh();
668                 break;
669             case 'macros':
670                 // Macros loaded on first show of modal
671                 break;
672             case 'selected_search_targets':
673                 $.each( z3950Servers, function( index, server ) {
674                     var saved_val = Preferences.user.selected_search_targets[server.server_id];
675
676                     if ( saved_val != null ) server.checked = saved_val;
677                 } );
678                 break;
679         }
680     }
681
682     function bindPreference( editor, pref ) {
683         function _addHandler( sel, event, handler ) {
684             $( sel ).on( event, function (e) {
685                 e.preventDefault();
686                 handler( e, Preferences.user[pref] );
687                 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
688                 showPreference(pref);
689             } );
690         }
691
692         switch (pref) {
693             case 'fieldWidgets':
694                 _addHandler( '#set-field-widgets', 'click', function( e, oldValue ) {
695                     editor.setUseWidgets( Preferences.user.fieldWidgets = !Preferences.user.fieldWidgets );
696                 } );
697                 break;
698             case 'font':
699                 _addHandler( '#prefs-menu .set-font', 'click', function( e, oldValue ) {
700                     Preferences.user.font = $( e.target ).css( 'font-family' );
701                 } );
702                 break;
703             case 'fontSize':
704                 _addHandler( '#prefs-menu .set-fontSize', 'click', function( e, oldValue ) {
705                     Preferences.user.fontSize = $( e.target ).css( 'font-size' );
706                 } );
707                 break;
708             case 'selected_search_targets':
709                 $( document ).on( 'change', 'input.search-toggle-server', function() {
710                     var server_id = $( this ).closest('li').data('server-id');
711                     Preferences.user.selected_search_targets[server_id] = this.checked;
712                     Preferences.Save( [% logged_in_user.borrowernumber | html %] );
713                 } );
714                 break;
715         }
716     }
717
718     function displayPreferences( editor ) {
719         $.each( Preferences.user, function( pref, value ) {
720             showPreference( pref );
721             bindPreference( editor, pref );
722         } );
723     }
724
725     //> Macro functions
726     var canCreatePublic = "[% CAN_user_editcatalogue_create_shared_macros | html %]";
727     var canDeletePublic = "[% CAN_user_editcatalogue_delete_shared_macros | html %]";
728
729     function deleteMacro( id ){
730         $( '#macro-list' ).empty();
731         var shared = macroEditor.activeMacroShared;
732         var id = macroEditor.activeMacroId;
733         macroEditor.activeMacroId = null;
734         api_url = "/api/v1/advanced_editor/macros/";
735         if( shared ) { api_url += "shared/" }
736         let options = {
737             url: api_url + id,
738             method: "DELETE",
739             contentType: "application/json",
740         };
741         $.ajax(options)
742             .then(function(result) {
743                 humanMsg.displayAlert( _("Macro successfully deleted") );
744                 showSavedMacros();
745             })
746             .fail(function(err) {
747                 var err_message;
748                 if( err.status == "404" ){
749                     err_message = "Macro not found";
750                 } else if ( err.status == "403" ){
751                     err_message = _("You do not have permission to delete this macro");
752                 } else {
753                     err_message = _("There was a problem, please check the logs");
754                 }
755                 humanMsg.displayAlert( _("Failed to delete macro: " + err_message), { className: 'humanError' } );
756             });
757     }
758
759
760     function loadMacro( name, id, shared ) {
761         $( '#macro-list li' ).removeClass( 'active' );
762         $(".macro_shared").prop("checked",false).hide();
763         $("#delete-macro").prop("disabled",true);
764         macroEditor.setOption( 'readOnly', false );
765         macroEditor.activeMacro = name;
766         macroEditor.activeMacroId = id;
767
768         if ( !name ) {
769             macroEditor.setValue( '' );
770             return;
771         }
772         $( '#macro-list li[data-name="' + name + '"][data-id="' + id + '"]' ).addClass( 'active' );
773         api_url = "/api/v1/advanced_editor/macros/";
774         if( shared ) { api_url += "shared/" }
775         let options = {
776             url: api_url + id,
777             method: "GET",
778             contentType: "application/json",
779         };
780         $.ajax(options)
781             .then(function(result) {
782                 macroEditor.setValue( result.macro_text );
783                 $(".macro_shared").show();
784                 if( result.shared ){
785                     $(".macro_shared").prop("checked",true);
786                     if( canCreatePublic ){
787                         macroEditor.setOption( 'readOnly', false );
788                     } else {
789                         macroEditor.setOption( 'readOnly', true );
790                     }
791                     if( canDeletePublic ){
792                         $("#delete-macro").prop("disabled",false);
793                     }
794                 } else {
795                     macroEditor.setOption( 'readOnly', false );
796                     $("#delete-macro").prop("disabled",false);
797                 }
798                 macroEditor.activeMacroShared = result.shared;
799             })
800             .fail(function(err) {
801                 var err_message;
802                 if( err.status == "404" ){
803                     err_message = "Macro not found";
804                 } else if ( err.status == "403" ){
805                     err_message = _("You do not have permission to access this macro");
806                 } else {
807                     err_message = _("There was a problem, please check the logs");
808                 }
809                 humanMsg.displayAlert( _("Failed to load macros: ") + err_message, { className: 'humanError' } );
810             });
811
812     }
813
814     function convertOldMacros(){
815         $("#convert-macros").remove();
816         var macro_list = $.map( Preferences.user.macros, function( macro, name ) {
817             return $.extend( { name: name }, macro );
818         } );
819         macro_list.sort( function( a, b ) {
820             return a.name.localeCompare(b.name);
821         } );
822         $.each( macro_list, function( index, macro ) {
823             let options = {
824                 url: "/api/v1/advanced_editor/macros/",
825                 method: "POST",
826                 contentType: "application/json",
827                 data: JSON.stringify({
828                     name: macro.name,
829                     patron_id: [% logged_in_user.borrowernumber | html %],
830                     macro_text: macro.contents,
831                     shared: false
832                 })
833             };
834             $.ajax(options)
835                 .then(function(undef, result) {
836                     delete Preferences.user.macros[macro.name];
837                     Preferences.Save( [% logged_in_user.borrowernumber | html %] );
838                     if( index == macro_list.length -1 ){
839                         showSavedMacros();
840                     }
841
842                 })
843                 .fail(function(err) {
844                     var err_message;
845                     if( err.status == "403" ){
846                         err_message = _("You do not have permission to create this macro");
847                     } else {
848                         err_message = _("There was a problem, please check the logs");
849                     }
850                     humanMsg.displayAlert( _("Failed to create macro: ") + err_message, { className: 'humanError' } );
851                 });
852         } );
853     }
854
855     function showSavedMacros( macros ) {
856         var scrollTop = $('#macro-list').scrollTop();
857         $( '#macro-list' ).empty();
858         $("#convert-macros").remove();
859         if( Object.keys(Preferences.user.macros).length ){
860             $convert = $( '<button class="btn btn-default" id="convert-macros" title="'+_("Convert browser storage macros")+'><i class="fa fa-adjust" aria-hidden="true"></i> Convert old macros</button>' );
861             $convert.click( function(){
862                 if( !confirm( _("This will retrieve macros stored in the brower, save them in the database, and delete them from the browser. Proceed?") ) ){
863                     return;
864                 }
865                 convertOldMacros();
866             });
867             $("#macro-toolbar").prepend($convert);
868         }
869         let options = {
870             url: "/api/v1/advanced_editor/macros/",
871             method: "GET",
872             contentType: "application/json",
873         };
874         $.ajax(options)
875             .then(function(result) {
876                     $.each(result,function( undef, macro ){
877                         var $li = $( '<li data-name="' + macro.name + '" data-id="' + macro.macro_id + '"><a href="#">' + macro.name + '</a><ol class="macro-info"></ol></li>' );
878                         if ( macro.macro_id == macroEditor.activeMacroId ) $li.addClass( 'active' );
879                         $li.click( function() {
880                             loadMacro(macro.name, macro.macro_id, macro.shared);
881                             return false;
882                         } );
883                         $('#macro-list').append($li);
884                     });
885             })
886             .fail(function(err) {
887                 var err_message = _("There was a problem, please check the logs");
888                 humanMsg.displayAlert( _("Failed to load macros: ") + err_message, { className: 'humanError' } );
889             });
890         var $new_li = $( '<li class="new-macro"><a href="#">' + _("New macro...") + '</a></li>' );
891         $new_li.click( function() {
892             // TODO: make this a bit less retro
893             var name = prompt(_("Please enter the name for the new macro:"));
894             if (!name) return;
895
896 //            if ( !Preferences.user.macros[name] ) storeMacro( name, { format: "rancor", contents: "" } );
897             let options = {
898                 url: "/api/v1/advanced_editor/macros/",
899                 method: "POST",
900                 contentType: "application/json",
901                 data: JSON.stringify({
902                     name: name,
903                     patron_id: [% logged_in_user.borrowernumber | html %],
904                     macro_text: "",
905                     shared: false
906                 })
907             };
908             $.ajax(options)
909                 .then(function(result) {
910                     showSavedMacros();
911                     loadMacro( result.name, result.macro_id );
912                 })
913                 .fail(function(err) {
914                     var err_message;
915                     if( err.status == "403" ){
916                         err_message = _("You do not have permission to access this macro");
917                     } else {
918                         err_message = _("There was a problem, please check the logs");
919                     }
920                     humanMsg.displayAlert( _("Failed to create macro: ") + err_message, { className: 'humanError' } );
921                 });
922         } );
923         $('#macro-list').append($new_li);
924         $('#macro-list').scrollTop(scrollTop);
925     }
926
927     function saveMacro(shared) {
928         var name = macroEditor.activeMacro;
929         var macro_id = macroEditor.activeMacroId;
930         var was_shared = macroEditor.activeMacroShared;
931
932         if ( !name || macroEditor.savedGeneration == macroEditor.changeGeneration() && was_shared == shared ) return;
933
934         macroEditor.savedGeneration = macroEditor.changeGeneration();
935         api_url = "/api/v1/advanced_editor/macros/";
936         if( shared || was_shared ) { api_url += "shared/" }
937
938         let options = {
939             url: api_url + macro_id,
940             method: "PUT",
941             contentType: "application/json",
942             data: JSON.stringify({
943                 name: name,
944                 patron_id: [% logged_in_user.borrowernumber | html %],
945                 macro_text: macroEditor.getValue(),
946                 shared: shared
947             })
948         };
949         $.ajax(options)
950             .then(function(result) {
951                 $('#macro-save-message').text(_("Saved"));
952                 macroEditor.activeMacroShared = shared;
953                 showSavedMacros();
954             })
955             .fail(function(err) {
956                 var err_message;
957                 if( err.status == "404" ){
958                     err_message = _("Macro not found");
959                 } else if ( err.status ="403" ){
960                     err_message = _("You do not have permission to access this macro");
961                 } else {
962                     err_message = _("There was a problem, please check the logs");
963                 }
964                 humanMsg.displayAlert( _("Failed to save macro: ") + err_message, { className: 'humanError' } );
965             });
966     }
967
968     $(".macro_shared").change(function(){
969         if(this.checked){
970             saveMacro(true);
971         } else {
972             saveMacro(false);
973         }
974     });
975
976     // END Macro functions
977
978     $(document).ready( function() {
979         // Editor setup
980         editor = new MARCEditor( {
981             onCursorActivity: function() {
982                 $('#status-tag-info').empty();
983                 $('#status-subfield-info').empty();
984
985                 var field = editor.getCurrentField();
986                 var cur = editor.getCursor();
987
988                 if ( !field ) return;
989
990                 var taginfo = KohaBackend.GetTagInfo( '', field.tag );
991                 $('#status-tag-info').html( '<strong>' + field.tag + ':</strong> ' );
992
993                 if ( taginfo ) {
994                     $('#status-tag-info').append( '<a href="' + getFieldHelpURL( field.tag ) + '" target="_blank" class="show-field-help" title="' + _("Show help for this tag") + '">[?]</a> '  + taginfo.lib );
995
996                     var subfield = field.getSubfieldAt( cur.ch );
997                     if ( !subfield ) return;
998
999                     var subfieldinfo = taginfo.subfields[ subfield.code ];
1000                     $('#status-subfield-info').html( '<strong>‡' + subfield.code + ':</strong> ' );
1001
1002                     if ( subfieldinfo ) {
1003                         $('#status-subfield-info').append( subfieldinfo.lib );
1004                     } else {
1005                         $('#status-subfield-info').append( '<em>' + _("Unknown subfield") + '</em>' );
1006                     }
1007                 } else {
1008                     $('#status-tag-info').append( '<em>' + _("Unknown tag") + '</em>' );
1009                 }
1010             },
1011             position: function (elt) { $(elt).insertAfter('#toolbar') },
1012         } );
1013
1014         // Automatically detect resizes and change the height of the editor and position of modals.
1015         var resizeTimer = null;
1016         function onResize() {
1017             if ( resizeTimer == null ) resizeTimer = setTimeout( function() {
1018                 resizeTimer = null;
1019
1020                 var pos = $('#editor .CodeMirror').offset();
1021                 $('#editor .CodeMirror').height( $(window).height() - pos.top - 24 - $('#changelanguage').height() ); // 24 is hardcoded value but works well
1022
1023                 $('.modal-body').each( function() {
1024                     $(this).height( $(window).height() * .8 - $(this).prevAll('.modal-header').height() );
1025                 } );
1026             }, 100);
1027         }
1028
1029         $( '#macro-ui' ).on( 'shown.bs.modal', function() {
1030             if ( macroEditor ) return;
1031
1032             macroEditor = CodeMirror(
1033                 $('#macro-editor')[0],
1034                 {
1035                     extraKeys: {
1036                         'Ctrl-D': function( cm ) {
1037                             var cur = cm.getCursor();
1038
1039                             cm.replaceRange( "‡", cur, null );
1040                         },
1041                     },
1042                     mode: 'null',
1043                     lineNumbers: true,
1044                     readOnly: true,
1045                 }
1046             );
1047             var saveTimeout;
1048             macroEditor.on( 'change', function( cm, change ) {
1049                 $('#macro-save-message').empty();
1050                 if ( change.origin == 'setValue' ) return;
1051
1052                 if ( saveTimeout ) clearTimeout( saveTimeout );
1053                 saveTimeout = setTimeout( function() {
1054                     saveMacro(macroEditor.activeMacroShared);
1055
1056                     saveTimeout = null;
1057                 }, 500 );
1058             } );
1059
1060             showSavedMacros();
1061         } );
1062
1063         var saveableBackends = [];
1064         $.each( backends, function( id, backend ) {
1065             if ( backend.save ) saveableBackends.push( [ backend.saveLabel, id ] );
1066         } );
1067         saveableBackends.sort();
1068         $.each( saveableBackends, function( undef, backend ) {
1069             $( '#save-dropdown' ).append( '<li><a href="#" data-backend="' + backend[1] + '">' + backend[0] + '</a></li>' );
1070         } );
1071
1072         // Click bindings
1073         $( '#save-record, #save-dropdown a' ).click( function() {
1074              $( '#save-record' ).find('i').attr( 'class', 'fa fa-spinner fa-spin' ).siblings( 'span' ).text( _("Saving...") );
1075
1076             function finishCb(result) {
1077                 if ( result.error == 'syntax' ) {
1078                     humanMsg.displayAlert( _("Incorrect syntax, cannot save"), { className: 'humanError' } );
1079                 } else if ( result.error == 'invalid' ) {
1080                     humanMsg.displayAlert( _("Record structure invalid, cannot save"), { className: 'humanError' } );
1081                 } else if ( result.error ) {
1082                     humanMsg.displayAlert( _("Something went wrong, cannot save"), { className: 'humanError' } );
1083                 } else if ( !result.error ) {
1084                     humanMsg.displayAlert( _("Record saved "), { className: 'humanSuccess' } );
1085                 }
1086
1087                 $.each( result.errors || [], function( undef, error ) {
1088                     switch ( error.type ) {
1089                         case 'noTag':
1090                             editor.addError( error.line, _("Invalid tag number") );
1091                             break;
1092                         case 'noIndicators':
1093                             editor.addError( error.line, _("Invalid indicators") );
1094                             break;
1095                         case 'noSubfields':
1096                             editor.addError( error.line, _("Tag has no subfields") );
1097                             break;
1098                         case 'missingTag':
1099                             editor.addError( null, _("Missing mandatory tag: ") + error.tag );
1100                             break;
1101                         case 'missingSubfield':
1102                             if ( error.subfield == '@' ) {
1103                                 editor.addError( error.line, _("Missing control field contents") );
1104                             } else {
1105                                 editor.addError( error.line, _("Missing mandatory subfield: â€¡") + error.subfield );
1106                             }
1107                             break;
1108                         case 'unrepeatableTag':
1109                             editor.addError( error.line, _("Tag ") + error.tag + _(" cannot be repeated") );
1110                             break;
1111                         case 'unrepeatableSubfield':
1112                             editor.addError( error.line, _("Subfield â€¡") + error.subfield + _(" cannot be repeated") );
1113                             break;
1114                         case 'itemTagUnsupported':
1115                             editor.addError( error.line, _("Item tags cannot currently be saved") );
1116                             break;
1117                     }
1118                 } );
1119
1120                 $( '#save-record' ).find('i').attr( 'class', 'fa fa-hdd-o' );
1121
1122                 if ( result.error ) {
1123                     // Reset backend info
1124                     setSource( [ state.backend, state.recordID ] );
1125                 }
1126             }
1127
1128             var backend = $( this ).data( 'backend' ) || ( state.saveBackend );
1129             if ( state.backend == backend ) {
1130                 saveRecord( backend + '/' + state.recordID, editor, finishCb );
1131             } else {
1132                 saveRecord( backend + '/', editor, finishCb );
1133             }
1134
1135             return false;
1136         } );
1137
1138         $('#import-records').click( function() {
1139             $('#import-records-input')
1140                 .off('change')
1141                 .change( function() {
1142                     if ( !this.files || !this.files.length ) return;
1143
1144                     var file = this.files[0];
1145                     var reader = new FileReader();
1146
1147                     reader.onload = function() {
1148                         var record = new MARC.Record();
1149
1150                         if ( /\.(mrc|marc|iso|iso2709|marcstd)$/.test( file.name ) ) {
1151                             record.loadISO2709( reader.result );
1152                         } else if ( /\.(xml|marcxml)$/.test( file.name ) ) {
1153                             record.loadMARCXML( reader.result );
1154                         } else {
1155                             humanMsg.displayAlert( _("Unknown record type, cannot import"), { className: 'humanError' } );
1156                             return;
1157                         }
1158
1159                         if (record.marc8_corrupted) humanMsg.displayMsg( '<h3>' + _("Possible record corruption") + '</h3><p>' + _("Record not marked as UTF-8, may be corrupted") + '</p>', { className: 'humanError' } );
1160
1161                         editor.displayRecord( record );
1162                     };
1163
1164                     reader.readAsText( file );
1165                 } )
1166                 .click();
1167
1168             return false;
1169         } );
1170
1171         $('#open-macros').click( function() {
1172             $('#macro-ui').modal('show');
1173
1174             return false;
1175         } );
1176
1177         $('#run-macro').click( function() {
1178             var result = Macros.Run( editor, 'rancor', macroEditor.getValue() );
1179
1180             if ( !result.errors.length ) {
1181                 $('#macro-ui').modal('hide');
1182                 editor.focus(); //Return cursor to editor after macro run
1183                 return false;
1184             }
1185
1186             var errors = [];
1187             $.each( result.errors, function() {
1188                 var error = '<strong>' + _("Line ") + (this.line + 1) + ':</strong> ';
1189
1190                 switch ( this.error ) {
1191                     case 'failed': error += _("failed to run"); break;
1192                     case 'unrecognized': error += _("unrecognized command"); break;
1193                 }
1194
1195                 errors.push(error);
1196             } );
1197
1198             humanMsg.displayMsg( '<h3>' + _("Failed to run macro:") + '</h3><ul><li>' + errors.join('</li><li>') + '</li></ul>', { className: 'humanError' } );
1199
1200             return false;
1201         } );
1202
1203         $('#delete-macro').click( function() {
1204             if ( !macroEditor.activeMacro || !confirm( _("Are you sure you want to delete this macro?") ) ) return;
1205             deleteMacro();
1206             loadMacro( undefined );
1207
1208             return false;
1209         } );
1210
1211         $( '#switch-editor' ).click( function() {
1212             if ( !confirm( _("Any changes will not be saved. Continue?") ) ) return;
1213
1214             Cookies.set( "catalogue_editor_[% logged_in_user.borrowernumber | html %]", "basic", { expires: 365, path: '/', sameSite: 'Lax'} );
1215
1216             if ( state.backend == 'catalog' ) {
1217                 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=' + state.recordID;
1218             } else if ( state.backend == 'new' ) {
1219                 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl';
1220             } else {
1221                 humanMsg.displayAlert( _("Cannot open this record in the basic editor"), { className: 'humanError' } );
1222             }
1223         } );
1224
1225         $( '#show-advanced-search' ).click( function() {
1226             showAdvancedSearch();
1227
1228             return false;
1229         } );
1230
1231         $('#advanced-search').submit( function() {
1232             startAdvancedSearch();
1233
1234             return false;
1235         } );
1236
1237         $( document ).on( 'click', 'a.search-nav', function() {
1238             if ( Search.Fetch( { offset: $( this ).data( 'offset' ) } ) ) {
1239                 $("#search-overlay").show();
1240             }
1241
1242             return false;
1243         });
1244
1245         $( document ).on( 'click', 'th[data-sort-label]', function() {
1246             var direction;
1247
1248             if ( $( this ).hasClass( 'sorting_asc' ) ) {
1249                 direction = 'desc';
1250             } else {
1251                 direction = 'asc';
1252             }
1253
1254             if ( Search.Fetch( { sort_key: $( this ).data( 'sort-label' ), sort_direction: direction } ) ) {
1255                 showSearchSorting( $( this ).data( 'sort-label' ), direction );
1256
1257                 $("#search-overlay").show();
1258             }
1259
1260             return false;
1261         });
1262
1263         $( document ).on( 'change', 'input.search-toggle-server', function() {
1264             var server = z3950Servers[ $( this ).closest('li').data('server-id') ];
1265             server.checked = this.checked;
1266
1267             if ( $('#search-results-ui').is( ':visible' ) && Search.Fetch() ) {
1268                 $("#search-overlay").show();
1269             }
1270         } );
1271
1272         // Key bindings
1273         bindGlobalKeys();
1274
1275         // Setup UI
1276         $("#advanced-search-ui, #search-results-ui, #macro-ui").each( function() {
1277             $(this).modal({ show: false });
1278         } );
1279
1280         var $quicksearch = $('#quicksearch fieldset');
1281         $('<div id="quicksearch-overlay"><h3>' + _("Search unavailable") + '</h3> <p></p></div>').css({
1282             position: 'absolute',
1283             top: $quicksearch.offset().top,
1284             left: $quicksearch.offset().left,
1285             height: $quicksearch.outerHeight(),
1286             width: $quicksearch.outerWidth(),
1287         }).appendTo(document.body).hide();
1288
1289         var prevAlerts = [];
1290         humanMsg.logMsg = function(msg, options) {
1291             $('#show-alerts').popover('hide');
1292             prevAlerts.unshift('<li>' + msg + '</li>');
1293             prevAlerts.splice(5, 999); // Truncate old messages
1294         };
1295
1296         $('#show-alerts').popover({
1297             html: true,
1298             placement: 'bottom',
1299             content: function() {
1300                 return '<div id="alerts-container"><ul>' + prevAlerts.join('') + '</ul></div>';
1301             },
1302         });
1303
1304         $('#show-shortcuts').popover({
1305             html: true,
1306             placement: 'bottom',
1307             content: function() {
1308                 return '<div id="shortcuts-container">' + $('#shortcuts-contents').html() + '</div>';
1309             },
1310         });
1311
1312         $('#new-record' ).click( function() {
1313             if ( editor.modified && !confirm( _("Are you sure you want to erase your changes?") ) ) return;
1314
1315             openRecord( 'new/', editor );
1316             return false;
1317         } );
1318
1319         window.onbeforeunload = function() {
1320             if(editor.modified )
1321                 { return 1; }
1322             else
1323                 { return undefined; }
1324         };
1325
1326         $('a.change-framework').click( function() {
1327             $("#loading").show();
1328             editor.setFrameworkCode(
1329                 $(this).data( 'frameworkcode' ),
1330                 true,
1331                 function ( error ) {
1332                     if ( typeof error !== 'undefined' ) {
1333                         humanMsg.displayAlert( _("Failed to change framework"), { className: 'humanError' } );
1334                     }
1335                     $('#loading').hide();
1336                 }
1337             );
1338         } );
1339
1340         // Start editor
1341         Preferences.Load( [% logged_in_user.borrowernumber || 0 | html %] );
1342         displayPreferences(editor);
1343         makeAuthorisedValueWidgets( '' );
1344         Search.Init( {
1345             page_size: 20,
1346             onresults: function(data) { showSearchResults( editor, data ) },
1347             onerror: handleSearchError,
1348         } );
1349
1350         function finishCb( data ) {
1351             if ( data.error ) {
1352                 humanMsg.displayAlert( data.error );
1353                 openRecord( 'new/', editor, finishCb );
1354             }
1355
1356             Resources.GetAll().done( function() {
1357                 $("#loading").hide();
1358                 $( window ).resize( onResize ).resize();
1359                 editor.focus();
1360             } );
1361         }
1362
1363         if ( "[% auth_forwarded_hash | html %]" ) {
1364             document.location.hash = "[% auth_forwarded_hash | html %]";
1365         }
1366
1367         if ( !document.location.hash || !openRecord( document.location.hash.slice(1), editor, finishCb ) ) {
1368             openRecord( 'new/', editor, finishCb );
1369         }
1370     } );
1371 } )();
1372
1373 </script>
1374 <!-- / cateditor-ui.inc -->