2 [% Asset.js("lib/codemirror/codemirror-compressed.js") | $raw %]
3 [% Asset.js("lib/filesaver.js") | $raw %]
4 [% Asset.js("lib/koha/cateditor/marc-mode.js") | $raw %]
5 [% Asset.js("lib/require.js") | $raw %]
8 [%- FOREACH authtag = authtags -%]
9 [% authtag.tagfield | html %]: {
10 subfield: '[% authtag.tagsubfield | html %]',
11 authtypecode: '[% authtag.authtypecode | html %]',
16 baseUrl: '[% interface | html %]/lib/koha/cateditor/',
19 marcflavour: '[% marcflavour | html %]',
20 themelang: '[% themelang | html %]',
27 [% IF marcflavour == 'MARC21' %]
28 [% PROCESS 'cateditor-widgets-marc21.inc' %]
30 <script>var editorWidgets = {};</script>
34 require( [ 'koha-backend', 'search', 'macros', 'marc-editor', 'marc-record', 'preferences', 'resources', 'text-marc', 'widget' ], function( KohaBackend, Search, Macros, MARCEditor, MARC, Preferences, Resources, TextMARC, Widget ) {
36 'koha:biblioserver': {
37 name: _("Local catalog"),
41 [%- FOREACH server = z3950_servers -%]
42 [% server.id | html %]: {
43 name: '[% server.servername | html %]',
44 recordtype: '[% server.recordtype | html %]',
45 checked: [% server.checked ? 'true' : 'false' | html %],
50 // The columns that should show up in a search, in order, and keyed by the corresponding <metadata> tag in the XSL and Pazpar2 config
52 [ "local_number", _("Local number") ],
53 [ "title", _("Title") ],
54 [ "series", _("Series title") ],
55 [ "author", _("Author") ],
56 [ "lccn", _("LCCN") ],
57 [ "isbn", _("ISBN") ],
58 [ "issn", _("ISSN") ],
59 [ "medium", _("Medium") ],
60 [ "edition", _("Edition") ],
61 [ "notes", _("Notes") ],
66 saveBackend: 'catalog',
73 function makeAuthorisedValueWidgets( frameworkCode ) {
74 $.each( KohaBackend.GetAllTagsInfo( frameworkCode ), function( tag, tagInfo ) {
75 $.each( tagInfo.subfields, function( subfield, subfieldInfo ) {
76 if ( !subfieldInfo.authorised_value ) return;
77 var authvals = KohaBackend.GetAuthorisedValues( subfieldInfo.authorised_value );
78 if ( !authvals ) return;
80 var defaultvalue = subfield.defaultvalue || authvals[0].value;
82 Widget.Register( tag + subfield, {
84 var $result = $( '<span class="subfield-widget"></span>' );
88 postCreate: function() {
89 var value = defaultvalue;
92 $.each( authvals, function() {
93 if ( this.value == widget.text ) {
98 this.setText( value );
100 $( '<select></select>' ).appendTo( this.node );
101 var $node = $( this.node ).find( 'select' );
102 $.each( authvals, function( undef, authval ) {
103 $node.append( '<option value="' + authval.value + '"' + (authval.value == value ? ' selected="selected"' : '') + '>' + authval.lib + '</option>' );
105 $node.val( this.text );
107 $node.change( $.proxy( function() {
108 this.setText( $node.val() );
111 makeTemplate: function() {
119 function bindGlobalKeys() {
120 shortcut.add( 'ctrl+s', function(event) {
121 $( '#save-record' ).click();
123 event.preventDefault();
126 shortcut.add( 'alt+ctrl+k', function(event) {
127 $( '#search-by-keywords' ).focus();
132 shortcut.add( 'alt+ctrl+a', function(event) {
133 $( '#search-by-author' ).focus();
138 shortcut.add( 'alt+ctrl+i', function(event) {
139 $( '#search-by-isbn' ).focus();
144 shortcut.add( 'alt+ctrl+t', function(event) {
145 $( '#search-by-title' ).focus();
150 shortcut.add( 'ctrl+h', function() {
151 var field = editor.getCurrentField();
153 if ( !field ) return;
155 window.open( getFieldHelpURL( field.tag ) );
158 $('#quicksearch .search-box').each( function() {
159 shortcut.add( 'enter', $.proxy( function() {
162 $('#quicksearch .search-box').each( function() {
163 if ( !this.value ) return;
165 terms.push( [ $(this).data('qualifier'), this.value ] );
168 if ( !terms.length ) return;
170 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
171 $("#search-overlay").show();
176 }, this), { target: this, type: 'keypress' } );
180 function getFieldHelpURL( tag ) {
181 [% IF ( marcflavour == 'MARC21' ) %]
182 if ( tag == '000' ) {
183 return "http://www.loc.gov/marc/bibliographic/bdleader.html";
184 } else if ( tag >= '090' && tag < '100' ) {
185 return "http://www.loc.gov/marc/bibliographic/bd09x.html";
186 } else if ( tag < '900' ) {
187 return "http://www.loc.gov/marc/bibliographic/bd" + tag + ".html";
189 return "http://www.loc.gov/marc/bibliographic/bd9xx.html";
191 [% ELSIF ( marcflavour == 'UNIMARC' ) %]
192 /* http://archive.ifla.org/VI/3/p1996-1/ is an outdated version of UNIMARC, but
193 seems to be the only version available that can be linked to per tag. More recent
194 versions of the UNIMARC standard are available on the IFLA website only as
197 if ( tag == '000' ) {
198 return "http://archive.ifla.org/VI/3/p1996-1/uni.htm";
201 var url = "http://archive.ifla.org/VI/3/p1996-1/uni" + first + ".htm#";
202 if ( first == '0' ) url += "b";
203 if ( first != '9' ) url += tag;
213 titleForRecord: _("Editing new record"),
214 get: function( id, callback ) {
215 record = new MARC.Record();
216 KohaBackend.FillRecord( '', record );
222 titleForRecord: _("Editing new full record"),
223 get: function( id, callback ) {
224 record = new MARC.Record();
225 KohaBackend.FillRecord( '', record, true );
231 titleForRecord: _("Editing catalog record #{ID}"),
233 { title: _("view"), href: "/cgi-bin/koha/catalogue/detail.pl?biblionumber={ID}" },
234 { title: _("edit items"), href: "/cgi-bin/koha/cataloguing/additem.pl?biblionumber={ID}" },
236 saveLabel: _("Save to catalog"),
237 get: function( id, callback ) {
238 if ( !id ) return false;
240 KohaBackend.GetRecord( id, callback );
242 save: function( id, record, done ) {
243 function finishCb( data ) {
244 done( { error: data.error, newRecord: data.marcxml && data.marcxml[0], newId: data.biblionumber && [ 'catalog', data.biblionumber ] } );
248 KohaBackend.SaveRecord( id, record, finishCb );
250 KohaBackend.CreateRecord( record, finishCb );
255 saveLabel: _("Save as ISO2709 (.mrc) file"),
256 save: function( id, record, done ) {
257 saveAs( new Blob( [record.toISO2709()], { 'type': 'application/octet-stream;charset=utf-8' } ), 'record.mrc' );
263 saveLabel: _("Save as MARCXML (.xml) file"),
264 save: function( id, record, done ) {
265 saveAs( new Blob( [record.toXML()], { 'type': 'application/octet-stream;charset=utf-8' } ), 'record.xml' );
271 titleForRecord: _("Editing search result"),
272 get: function( id, callback ) {
273 if ( !id ) return false;
274 if ( !backends.search.records[ id ] ) {
275 callback( { error: _( "Invalid record" ) } );
279 callback( backends.search.records[ id ] );
285 function setSource(parts) {
286 state.backend = parts[0];
287 state.recordID = parts[1];
288 state.canSave = backends[ state.backend ].save != null;
289 state.saveBackend = state.canSave ? state.backend : 'catalog';
291 var backend = backends[state.backend];
293 document.location.hash = '#' + parts[0] + '/' + parts[1];
295 $('#title').text( backend.titleForRecord.replace( '{ID}', parts[1] ) );
297 $.each( backend.links || [], function( i, link ) {
298 $('#title').append(' <a target="_blank" href="' + link.href.replace( '{ID}', parts[1] ) + '">(' + link.title + ')</a>' );
300 $( 'title', document.head ).html( _("Koha › Cataloging › ") + backend.titleForRecord.replace( '{ID}', parts[1] ) );
301 $('#save-record span').text( backends[ state.saveBackend ].saveLabel );
304 function saveRecord( recid, editor, callback ) {
305 var parts = recid.split('/');
306 if ( parts.length != 2 ) return false;
308 if ( !backends[ parts[0] ] || !backends[ parts[0] ].save ) return false;
310 editor.removeErrors();
311 var record = editor.getRecord();
313 if ( record.errors ) {
314 state.saving = false;
315 callback( { error: 'syntax', errors: record.errors } );
319 var errors = KohaBackend.ValidateRecord( '', record );
320 if ( errors.length ) {
321 state.saving = false;
322 callback( { error: 'invalid', errors: errors } );
326 backends[ parts[0] ].save( parts[1], record, function(data) {
327 state.saving = false;
329 if (data.newRecord) {
330 var record = new MARC.Record();
331 record.loadMARCXML(data.newRecord);
332 record.frameworkcode = data.newRecord.frameworkcode;
333 editor.displayRecord( record );
337 setSource(data.newId);
339 setSource( [ state.backend, state.recordID ] );
342 if (callback) callback( data );
346 function loadRecord( recid, editor, callback ) {
347 var parts = recid.split('/');
348 if ( parts.length != 2 ) return false;
350 if ( !backends[ parts[0] ] || !backends[ parts[0] ].get ) return false;
352 backends[ parts[0] ].get( parts[1], function( record ) {
353 if ( !record.error ) {
354 editor.displayRecord( record );
358 if (callback) callback(record);
364 function openRecord( recid, editor, callback ) {
365 return loadRecord( recid, editor, function ( record ) {
366 setSource( recid.split('/') );
368 if (callback) callback( record );
373 function showAdvancedSearch() {
374 $('#advanced-search-servers').empty();
375 $.each( z3950Servers, function( server_id, server ) {
376 $('#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>' );
378 $('#advanced-search-ui').modal('show');
381 function startAdvancedSearch() {
384 $('#advanced-search-ui .search-box').each( function() {
385 if ( !this.value ) return;
387 terms.push( [ $(this).data('qualifier'), this.value ] );
390 if ( !terms.length ) return;
392 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
393 $('#advanced-search-ui').modal('hide');
394 $("#search-overlay").show();
399 function showResultsBox(data) {
400 $('#search-top-pages, #search-bottom-pages').find('nav').empty();
401 $('#searchresults thead tr').empty();
402 $('#searchresults tbody').empty();
403 $('#search-serversinfo').empty().append('<li>' + _("Loading...") + '</li>');
404 $('#search-results-ui').modal('show');
407 function showSearchSorting( sort_key, sort_direction ) {
408 var $th = $('#searchresults thead tr th[data-sort-label="' + sort_key + '"]');
409 $th.parent().find( 'th[data-sort-label]' ).attr( 'class', 'sorting' );
411 if ( sort_direction == 'asc' ) {
413 $th.attr( 'class', 'sorting_asc' );
416 $th.attr( 'class', 'sorting_desc' );
420 function showSearchResults( editor, data ) {
421 backends.search.records = {};
423 $('#searchresults thead tr').empty();
424 $('#searchresults tbody').empty();
425 $('#search-serversinfo').empty();
427 $.each( z3950Servers, function( server_id, server ) {
428 var num_fetched = data.num_fetched[server_id];
430 if ( data.errors[server_id] ) {
431 num_fetched = data.errors[server_id];
432 } else if ( num_fetched == null ) {
434 } else if ( num_fetched < data.num_hits[server_id] ) {
438 $('#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>' );
441 var seenColumns = {};
443 $.each( data.hits, function( undef, hit ) {
444 $.each( hit.metadata, function(key) {
445 seenColumns[key] = true;
449 $('#searchresults thead tr').append('<th>' + _("Source") + '</th>');
451 $.each( z3950Labels, function( undef, label ) {
452 if ( seenColumns[ label[0] ] ) {
453 $('#searchresults thead tr').append( '<th class="sorting" data-sort-label="' + label[0] + '">' + label[1] + '</th>' );
457 showSearchSorting( data.sort_key, data.sort_direction );
459 $('#searchresults thead tr').append('<th>' + _("Tools") + '</th>');
461 var bibnumMap = KohaBackend.GetSubfieldForKohaField('biblio.biblionumber');
462 $.each( data.hits, function( undef, hit ) {
463 backends.search.records[ hit.server + ':' + hit.index ] = hit.record;
465 switch ( hit.server ) {
466 case 'koha:biblioserver':
467 var bibnumField = hit.record.field( bibnumMap[0] );
469 if ( bibnumField && bibnumField.hasSubfield( bibnumMap[1] ) ) {
470 hit.id = 'catalog/' + bibnumField.subfield( bibnumMap[1] );
474 // Otherwise, fallthrough
477 hit.id = 'search/' + hit.server + ':' + hit.index;
481 result += '<td class="sourcecol">' + z3950Servers[ hit.server ].name + '</td>';
483 $.each( z3950Labels, function( undef, label ) {
484 if ( !seenColumns[ label[0] ] ) return;
486 if ( hit.metadata[ label[0] ] ) {
487 result += '<td class="infocol">' + hit.metadata[ label[0] ] + '</td>';
489 result += '<td class="infocol"> </td>';
493 result += '<td class="toolscol"><ul><li><a href="#" class="marc-link">' + _("View MARC") + '</a></li>';
494 result += '<li><a href="#" class="open-link">' + ( hit.server == 'koha:biblioserver' ? _("Edit") : _("Import") ) + '</a></li>';
495 if ( state.canSave ) result += '<li><a href="#" class="substitute-link" title="' + _("Replace the current record's contents") + '">' + _("Substitute") + '</a></li>';
496 result += '</ul></td></tr>';
498 var $tr = $( result );
499 $tr.find( '.marc-link' ).click( function() {
500 var $info_columns = $tr.find( '.infocol' );
501 var $marc_column = $tr.find( '.marccol' );
503 if ( !$marc_column.length ) {
504 $marc_column = $( '<td class="marccol" colspan="' + $info_columns.length + '"></td>' ).insertAfter( $info_columns.eq(-1) ).hide();
505 CodeMirror.runMode( TextMARC.RecordToText( hit.record ), 'marc', $marc_column[0] );
508 if ( $marc_column.is(':visible') ) {
509 $tr.find('.marc-link').text( _("View MARC") );
510 $info_columns.show();
513 $tr.find('.marc-link').text( _("Hide MARC") );
515 $info_columns.hide();
520 $tr.find( '.open-link' ).click( function() {
521 $( '#search-results-ui' ).modal('hide');
522 openRecord( hit.id, editor );
526 $tr.find( '.substitute-link' ).click( function() {
527 $( '#search-results-ui' ).modal('hide');
528 loadRecord( hit.id, editor );
532 $('#searchresults tbody').append( $tr );
536 var cur_page = data.offset / data.page_size;
537 var max_page = Math.ceil( data.total_fetched / data.page_size ) - 1;
539 if ( cur_page != 0 ) {
540 pages.push( '<li><a class="search-nav" href="#" data-offset="' + (data.offset - data.page_size) + '"><span aria-hidden="true">«</span> ' + _("Previous") + '</a></li>' );
543 for ( var page = Math.max( 0, cur_page - 9 ); page <= Math.min( max_page, cur_page + 9 ); page++ ) {
544 if ( page == cur_page ) {
545 pages.push( ' <li class="active"><a href="#">' + ( page + 1 ) + '</a></li>' );
547 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + ( page * data.page_size ) + '">' + ( page + 1 ) + '</a></li>' );
551 if ( cur_page < max_page ) {
552 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + (data.offset + data.page_size) + '">' + _("Next") + ' <span aria-hidden="true">»</span></a></li>' );
555 $( '#search-top-pages, #search-bottom-pages' ).find( 'nav' ).html( pages.length > 1 ? ( '<ul class="pagination pagination-sm">' + pages.join( '' ) + '</ul>' ) : '' );
557 var $overlay = $('#search-overlay');
558 $overlay.find('span').text(_("Loading"));
559 $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
561 if ( data.activeclients ) {
562 $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
565 $overlay.find('.bar').css( { display: 'block', width: '100%' } );
567 $('#searchresults')[0].focus();
571 function invalidateSearchResults() {
572 var $overlay = $('#search-overlay');
573 $overlay.find('span').text(_("Search expired, please try again"));
574 $overlay.find('.bar').css( { display: 'none' } );
578 function handleSearchError(error) {
579 if (error.code == 1) {
580 invalidateSearchResults();
583 humanMsg.displayMsg( '<h3>' + _("Internal search error") + '</h3><p>' + error.responseText + '</p><p>' + _("Please refresh the page and try again.") + '</p>', { className: 'humanError' } );
587 function handleSearchInitError(error) {
588 $('#quicksearch-overlay').fadeIn().find('p').text(error);
591 // Preference functions
592 function showPreference( pref ) {
593 var value = Preferences.user[pref];
597 $( '#set-field-widgets' ).text( value ? _("Show fields verbatim") : _("Show helpers for fixed and coded fields") );
600 $( '#editor .CodeMirror' ).css( { fontFamily: value } );
604 $( '#editor .CodeMirror' ).css( { fontSize: value } );
608 // Macros loaded on first show of modal
610 case 'selected_search_targets':
611 $.each( z3950Servers, function( server_id, server ) {
612 var saved_val = Preferences.user.selected_search_targets[server_id];
614 if ( saved_val != null ) server.checked = saved_val;
620 function bindPreference( editor, pref ) {
621 function _addHandler( sel, event, handler ) {
622 $( sel ).on( event, function (e) {
624 handler( e, Preferences.user[pref] );
625 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
626 showPreference(pref);
632 _addHandler( '#set-field-widgets', 'click', function( e, oldValue ) {
633 editor.setUseWidgets( Preferences.user.fieldWidgets = !Preferences.user.fieldWidgets );
637 _addHandler( '#prefs-menu .set-font', 'click', function( e, oldValue ) {
638 Preferences.user.font = $( e.target ).css( 'font-family' );
642 _addHandler( '#prefs-menu .set-fontSize', 'click', function( e, oldValue ) {
643 Preferences.user.fontSize = $( e.target ).css( 'font-size' );
646 case 'selected_search_targets':
647 $( document ).on( 'change', 'input.search-toggle-server', function() {
648 var server_id = $( this ).closest('li').data('server-id');
649 Preferences.user.selected_search_targets[server_id] = this.checked;
650 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
656 function displayPreferences( editor ) {
657 $.each( Preferences.user, function( pref, value ) {
658 showPreference( pref );
659 bindPreference( editor, pref );
664 function loadMacro( name ) {
665 $( '#macro-list li' ).removeClass( 'active' );
666 macroEditor.activeMacro = name;
669 macroEditor.setValue( '' );
673 $( '#macro-list li[data-name="' + name + '"]' ).addClass( 'active' );
674 var macro = Preferences.user.macros[name];
675 macroEditor.setValue( macro.contents );
676 macroEditor.setOption( 'readOnly', false );
677 $( '#macro-format' ).val( macro.format || 'its' );
678 if ( macro.history ) macroEditor.setHistory( macro.history );
681 function storeMacro( name, macro ) {
683 Preferences.user.macros[name] = macro;
685 delete Preferences.user.macros[name];
688 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
691 function showSavedMacros( macros ) {
692 var scrollTop = $('#macro-list').scrollTop();
693 $( '#macro-list' ).empty();
694 var macro_list = $.map( Preferences.user.macros, function( macro, name ) {
695 return $.extend( { name: name }, macro );
697 macro_list.sort( function( a, b ) {
698 return a.name.localeCompare(b.name);
700 $.each( macro_list, function( undef, macro ) {
701 var $li = $( '<li data-name="' + macro.name + '"><a href="#">' + macro.name + '</a><ol class="macro-info"></ol></li>' );
702 $li.click( function() {
703 loadMacro(macro.name);
706 if ( macro.name == macroEditor.activeMacro ) $li.addClass( 'active' );
707 var modified = macro.modified && new Date(macro.modified);
708 $li.find( '.macro-info' ).append(
709 '<li><span class="label">' + _("Last changed:") + '</span>' +
710 ( modified ? ( modified.toLocaleDateString() + ', ' + modified.toLocaleTimeString() ) : _("never") ) + '</li>'
712 $('#macro-list').append($li);
714 var $new_li = $( '<li class="new-macro"><a href="#">' + _("New macro...") + '</a></li>' );
715 $new_li.click( function() {
716 // TODO: make this a bit less retro
717 var name = prompt(_("Please enter the name for the new macro:"));
720 if ( !Preferences.user.macros[name] ) storeMacro( name, { format: "rancor", contents: "" } );
724 $('#macro-list').append($new_li);
725 $('#macro-list').scrollTop(scrollTop);
728 function saveMacro() {
729 var name = macroEditor.activeMacro;
731 if ( !name || macroEditor.savedGeneration == macroEditor.changeGeneration() ) return;
733 macroEditor.savedGeneration = macroEditor.changeGeneration();
734 storeMacro( name, { contents: macroEditor.getValue(), modified: (new Date()).valueOf(), history: macroEditor.getHistory(), format: $('#macro-format').val() } );
735 $('#macro-save-message').text(_("Saved"));
739 $(document).ready( function() {
741 editor = new MARCEditor( {
742 onCursorActivity: function() {
743 $('#status-tag-info').empty();
744 $('#status-subfield-info').empty();
746 var field = editor.getCurrentField();
747 var cur = editor.getCursor();
749 if ( !field ) return;
751 var taginfo = KohaBackend.GetTagInfo( '', field.tag );
752 $('#status-tag-info').html( '<strong>' + field.tag + ':</strong> ' );
755 $('#status-tag-info').append( '<a href="' + getFieldHelpURL( field.tag ) + '" target="_blank" class="show-field-help" title="' + _("Show help for this tag") + '">[?]</a> ' + taginfo.lib );
757 var subfield = field.getSubfieldAt( cur.ch );
758 if ( !subfield ) return;
760 var subfieldinfo = taginfo.subfields[ subfield.code ];
761 $('#status-subfield-info').html( '<strong>‡' + subfield.code + ':</strong> ' );
763 if ( subfieldinfo ) {
764 $('#status-subfield-info').append( subfieldinfo.lib );
766 $('#status-subfield-info').append( '<em>' + _("Unknown subfield") + '</em>' );
769 $('#status-tag-info').append( '<em>' + _("Unknown tag") + '</em>' );
772 position: function (elt) { $(elt).insertAfter('#toolbar') },
775 // Automatically detect resizes and change the height of the editor and position of modals.
776 var resizeTimer = null;
777 function onResize() {
778 if ( resizeTimer == null ) resizeTimer = setTimeout( function() {
781 var pos = $('#editor .CodeMirror').position();
782 $('#editor .CodeMirror').height( $(window).height() - pos.top - 24 - $('#changelanguage').height() ); // 24 is hardcoded value but works well
784 $('.modal-body').each( function() {
785 $(this).height( $(window).height() * .8 - $(this).prevAll('.modal-header').height() );
790 $( '#macro-ui' ).on( 'shown.bs.modal', function() {
791 if ( macroEditor ) return;
793 macroEditor = CodeMirror(
794 $('#macro-editor')[0],
797 'Ctrl-D': function( cm ) {
798 var cur = cm.getCursor();
800 cm.replaceRange( "‡", cur, null );
809 macroEditor.on( 'change', function( cm, change ) {
810 $('#macro-save-message').empty();
811 if ( change.origin == 'setValue' ) return;
813 if ( saveTimeout ) clearTimeout( saveTimeout );
814 saveTimeout = setTimeout( function() {
824 var saveableBackends = [];
825 $.each( backends, function( id, backend ) {
826 if ( backend.save ) saveableBackends.push( [ backend.saveLabel, id ] );
828 saveableBackends.sort();
829 $.each( saveableBackends, function( undef, backend ) {
830 $( '#save-dropdown' ).append( '<li><a href="#" data-backend="' + backend[1] + '">' + backend[0] + '</a></li>' );
833 var macro_format_list = $.map( Macros.formats, function( format, name ) {
834 return $.extend( { name: name }, format );
836 macro_format_list.sort( function( a, b ) {
837 return a.description.localeCompare(b.description);
839 $.each( macro_format_list, function() {
840 $('#macro-format').append( '<option value="' + this.name + '">' + this.description + '</option>' );
844 $( '#save-record, #save-dropdown a' ).click( function() {
845 $( '#save-record' ).find('i').attr( 'class', 'fa fa-spinner fa-spin' ).siblings( 'span' ).text( _("Saving...") );
847 function finishCb(result) {
848 if ( result.error == 'syntax' ) {
849 humanMsg.displayAlert( _("Incorrect syntax, cannot save"), { className: 'humanError' } );
850 } else if ( result.error == 'invalid' ) {
851 humanMsg.displayAlert( _("Record structure invalid, cannot save"), { className: 'humanError' } );
852 } else if ( !result.error ) {
853 humanMsg.displayAlert( _("Record saved "), { className: 'humanSuccess' } );
856 $.each( result.errors || [], function( undef, error ) {
857 switch ( error.type ) {
859 editor.addError( error.line, _("Invalid tag number") );
862 editor.addError( error.line, _("Invalid indicators") );
865 editor.addError( error.line, _("Tag has no subfields") );
868 editor.addError( null, _("Missing mandatory tag: ") + error.tag );
870 case 'missingSubfield':
871 if ( error.subfield == '@' ) {
872 editor.addError( error.line, _("Missing control field contents") );
874 editor.addError( error.line, _("Missing mandatory subfield: ‡") + error.subfield );
877 case 'unrepeatableTag':
878 editor.addError( error.line, _("Tag ") + error.tag + _(" cannot be repeated") );
880 case 'unrepeatableSubfield':
881 editor.addError( error.line, _("Subfield ‡") + error.subfield + _(" cannot be repeated") );
883 case 'itemTagUnsupported':
884 editor.addError( error.line, _("Item tags cannot currently be saved") );
889 $( '#save-record' ).find('i').attr( 'class', 'fa fa-hdd-o' );
891 if ( result.error ) {
892 // Reset backend info
893 setSource( [ state.backend, state.recordID ] );
897 var backend = $( this ).data( 'backend' ) || ( state.saveBackend );
898 if ( state.backend == backend ) {
899 saveRecord( backend + '/' + state.recordID, editor, finishCb );
901 saveRecord( backend + '/', editor, finishCb );
907 $('#import-records').click( function() {
908 $('#import-records-input')
910 .change( function() {
911 if ( !this.files || !this.files.length ) return;
913 var file = this.files[0];
914 var reader = new FileReader();
916 reader.onload = function() {
917 var record = new MARC.Record();
919 if ( /\.(mrc|marc|iso|iso2709|marcstd)$/.test( file.name ) ) {
920 record.loadISO2709( reader.result );
921 } else if ( /\.(xml|marcxml)$/.test( file.name ) ) {
922 record.loadMARCXML( reader.result );
924 humanMsg.displayAlert( _("Unknown record type, cannot import"), { className: 'humanError' } );
928 if (record.marc8_corrupted) humanMsg.displayMsg( '<h3>' + _("Possible record corruption") + '</h3><p>' + _("Record not marked as UTF-8, may be corrupted") + '</p>', { className: 'humanError' } );
930 editor.displayRecord( record );
933 reader.readAsText( file );
940 $('#open-macros').click( function() {
941 $('#macro-ui').modal('show');
946 $('#run-macro').click( function() {
947 var result = Macros.Run( editor, $('#macro-format').val(), macroEditor.getValue() );
949 if ( !result.errors.length ) {
950 $('#macro-ui').modal('hide');
951 editor.focus(); //Return cursor to editor after macro run
956 $.each( result.errors, function() {
957 var error = '<b>' + _("Line ") + (this.line + 1) + ':</b> ';
959 switch ( this.error ) {
960 case 'failed': error += _("failed to run"); break;
961 case 'unrecognized': error += _("unrecognized command"); break;
967 humanMsg.displayMsg( '<h3>' + _("Failed to run macro:") + '</h3><ul><li>' + errors.join('</li><li>') + '</li></ul>', { className: 'humanError' } );
972 $('#delete-macro').click( function() {
973 if ( !macroEditor.activeMacro || !confirm( _("Are you sure you want to delete this macro?") ) ) return;
975 storeMacro( macroEditor.activeMacro, undefined );
977 loadMacro( undefined );
982 $( '#switch-editor' ).click( function() {
983 if ( !confirm( _("Any changes will not be saved. Continue?") ) ) return;
985 $.cookie( 'catalogue_editor_[% logged_in_user.borrowernumber | html %]', 'basic', { expires: 365, path: '/' } );
987 if ( state.backend == 'catalog' ) {
988 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=' + state.recordID;
989 } else if ( state.backend == 'new' ) {
990 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl';
992 humanMsg.displayAlert( _("Cannot open this record in the basic editor"), { className: 'humanError' } );
996 $( '#show-advanced-search' ).click( function() {
997 showAdvancedSearch();
1002 $('#advanced-search').submit( function() {
1003 startAdvancedSearch();
1008 $( document ).on( 'click', 'a.search-nav', function() {
1009 if ( Search.Fetch( { offset: $( this ).data( 'offset' ) } ) ) {
1010 $("#search-overlay").show();
1016 $( document ).on( 'click', 'th[data-sort-label]', function() {
1019 if ( $( this ).hasClass( 'sorting_asc' ) ) {
1025 if ( Search.Fetch( { sort_key: $( this ).data( 'sort-label' ), sort_direction: direction } ) ) {
1026 showSearchSorting( $( this ).data( 'sort-label' ), direction );
1028 $("#search-overlay").show();
1034 $( document ).on( 'change', 'input.search-toggle-server', function() {
1035 var server = z3950Servers[ $( this ).closest('li').data('server-id') ];
1036 server.checked = this.checked;
1038 if ( $('#search-results-ui').is( ':visible' ) && Search.Fetch() ) {
1039 $("#search-overlay").show();
1047 $("#advanced-search-ui, #search-results-ui, #macro-ui").each( function() {
1048 $(this).modal({ show: false });
1051 var $quicksearch = $('#quicksearch fieldset');
1052 $('<div id="quicksearch-overlay"><h3>' + _("Search unavailable") + '</h3> <p></p></div>').css({
1053 position: 'absolute',
1054 top: $quicksearch.offset().top,
1055 left: $quicksearch.offset().left,
1056 height: $quicksearch.outerHeight(),
1057 width: $quicksearch.outerWidth(),
1058 }).appendTo(document.body).hide();
1060 var prevAlerts = [];
1061 humanMsg.logMsg = function(msg, options) {
1062 $('#show-alerts').popover('hide');
1063 prevAlerts.unshift('<li>' + msg + '</li>');
1064 prevAlerts.splice(5, 999); // Truncate old messages
1067 $('#show-alerts').popover({
1069 placement: 'bottom',
1070 content: function() {
1071 return '<div id="alerts-container"><ul>' + prevAlerts.join('') + '</ul></div>';
1075 $('#show-shortcuts').popover({
1077 placement: 'bottom',
1078 content: function() {
1079 return '<div id="shortcuts-container">' + $('#shortcuts-contents').html() + '</div>';
1083 $('#new-record' ).click( function() {
1084 if ( editor.modified && !confirm( _("Are you sure you want to erase your changes?") ) ) return;
1086 openRecord( 'new/', editor );
1090 window.onbeforeunload = function() {
1091 if(editor.modified )
1094 { return undefined; }
1097 $('a.change-framework').click( function() {
1098 $("#loading").show();
1099 editor.setFrameworkCode(
1100 $(this).data( 'frameworkcode' ),
1101 function ( error ) {
1102 if ( typeof error !== 'undefined' ) {
1103 humanMsg.displayAlert( _("Failed to change framework"), { className: 'humanError' } );
1105 $('#loading').hide();
1111 Preferences.Load( [% logged_in_user.borrowernumber || 0 | html %] );
1112 displayPreferences(editor);
1113 makeAuthorisedValueWidgets( '' );
1116 onresults: function(data) { showSearchResults( editor, data ) },
1117 onerror: handleSearchError,
1120 function finishCb( data ) {
1122 humanMsg.displayAlert( data.error );
1123 openRecord( 'new/', editor, finishCb );
1126 Resources.GetAll().done( function() {
1127 $("#loading").hide();
1128 $( window ).resize( onResize ).resize();
1133 if ( "[% auth_forwarded_hash | html %]" ) {
1134 document.location.hash = "[% auth_forwarded_hash | html %]";
1137 if ( !document.location.hash || !openRecord( document.location.hash.slice(1), editor, finishCb ) ) {
1138 openRecord( 'new/', editor, finishCb );