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 editor.displayRecord( record );
336 setSource(data.newId);
338 setSource( [ state.backend, state.recordID ] );
341 if (callback) callback( data );
345 function loadRecord( recid, editor, callback ) {
346 var parts = recid.split('/');
347 if ( parts.length != 2 ) return false;
349 if ( !backends[ parts[0] ] || !backends[ parts[0] ].get ) return false;
351 backends[ parts[0] ].get( parts[1], function( record ) {
352 if ( !record.error ) {
353 editor.displayRecord( record );
357 if (callback) callback(record);
363 function openRecord( recid, editor, callback ) {
364 return loadRecord( recid, editor, function ( record ) {
365 setSource( recid.split('/') );
367 if (callback) callback( record );
372 function showAdvancedSearch() {
373 $('#advanced-search-servers').empty();
374 $.each( z3950Servers, function( server_id, server ) {
375 $('#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>' );
377 $('#advanced-search-ui').modal('show');
380 function startAdvancedSearch() {
383 $('#advanced-search-ui .search-box').each( function() {
384 if ( !this.value ) return;
386 terms.push( [ $(this).data('qualifier'), this.value ] );
389 if ( !terms.length ) return;
391 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
392 $('#advanced-search-ui').modal('hide');
393 $("#search-overlay").show();
398 function showResultsBox(data) {
399 $('#search-top-pages, #search-bottom-pages').find('nav').empty();
400 $('#searchresults thead tr').empty();
401 $('#searchresults tbody').empty();
402 $('#search-serversinfo').empty().append('<li>' + _("Loading...") + '</li>');
403 $('#search-results-ui').modal('show');
406 function showSearchSorting( sort_key, sort_direction ) {
407 var $th = $('#searchresults thead tr th[data-sort-label="' + sort_key + '"]');
408 $th.parent().find( 'th[data-sort-label]' ).attr( 'class', 'sorting' );
410 if ( sort_direction == 'asc' ) {
412 $th.attr( 'class', 'sorting_asc' );
415 $th.attr( 'class', 'sorting_desc' );
419 function showSearchResults( editor, data ) {
420 backends.search.records = {};
422 $('#searchresults thead tr').empty();
423 $('#searchresults tbody').empty();
424 $('#search-serversinfo').empty();
426 $.each( z3950Servers, function( server_id, server ) {
427 var num_fetched = data.num_fetched[server_id];
429 if ( data.errors[server_id] ) {
430 num_fetched = data.errors[server_id];
431 } else if ( num_fetched == null ) {
433 } else if ( num_fetched < data.num_hits[server_id] ) {
437 $('#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>' );
440 var seenColumns = {};
442 $.each( data.hits, function( undef, hit ) {
443 $.each( hit.metadata, function(key) {
444 seenColumns[key] = true;
448 $('#searchresults thead tr').append('<th>' + _("Source") + '</th>');
450 $.each( z3950Labels, function( undef, label ) {
451 if ( seenColumns[ label[0] ] ) {
452 $('#searchresults thead tr').append( '<th class="sorting" data-sort-label="' + label[0] + '">' + label[1] + '</th>' );
456 showSearchSorting( data.sort_key, data.sort_direction );
458 $('#searchresults thead tr').append('<th>' + _("Tools") + '</th>');
460 var bibnumMap = KohaBackend.GetSubfieldForKohaField('biblio.biblionumber');
461 $.each( data.hits, function( undef, hit ) {
462 backends.search.records[ hit.server + ':' + hit.index ] = hit.record;
464 switch ( hit.server ) {
465 case 'koha:biblioserver':
466 var bibnumField = hit.record.field( bibnumMap[0] );
468 if ( bibnumField && bibnumField.hasSubfield( bibnumMap[1] ) ) {
469 hit.id = 'catalog/' + bibnumField.subfield( bibnumMap[1] );
473 // Otherwise, fallthrough
476 hit.id = 'search/' + hit.server + ':' + hit.index;
480 result += '<td class="sourcecol">' + z3950Servers[ hit.server ].name + '</td>';
482 $.each( z3950Labels, function( undef, label ) {
483 if ( !seenColumns[ label[0] ] ) return;
485 if ( hit.metadata[ label[0] ] ) {
486 result += '<td class="infocol">' + hit.metadata[ label[0] ] + '</td>';
488 result += '<td class="infocol"> </td>';
492 result += '<td class="toolscol"><ul><li><a href="#" class="marc-link">' + _("View MARC") + '</a></li>';
493 result += '<li><a href="#" class="open-link">' + ( hit.server == 'koha:biblioserver' ? _("Edit") : _("Import") ) + '</a></li>';
494 if ( state.canSave ) result += '<li><a href="#" class="substitute-link" title="' + _("Replace the current record's contents") + '">' + _("Substitute") + '</a></li>';
495 result += '</ul></td></tr>';
497 var $tr = $( result );
498 $tr.find( '.marc-link' ).click( function() {
499 var $info_columns = $tr.find( '.infocol' );
500 var $marc_column = $tr.find( '.marccol' );
502 if ( !$marc_column.length ) {
503 $marc_column = $( '<td class="marccol" colspan="' + $info_columns.length + '"></td>' ).insertAfter( $info_columns.eq(-1) ).hide();
504 CodeMirror.runMode( TextMARC.RecordToText( hit.record ), 'marc', $marc_column[0] );
507 if ( $marc_column.is(':visible') ) {
508 $tr.find('.marc-link').text( _("View MARC") );
509 $info_columns.show();
512 $tr.find('.marc-link').text( _("Hide MARC") );
514 $info_columns.hide();
519 $tr.find( '.open-link' ).click( function() {
520 $( '#search-results-ui' ).modal('hide');
521 openRecord( hit.id, editor );
525 $tr.find( '.substitute-link' ).click( function() {
526 $( '#search-results-ui' ).modal('hide');
527 loadRecord( hit.id, editor );
531 $('#searchresults tbody').append( $tr );
535 var cur_page = data.offset / data.page_size;
536 var max_page = Math.ceil( data.total_fetched / data.page_size ) - 1;
538 if ( cur_page != 0 ) {
539 pages.push( '<li><a class="search-nav" href="#" data-offset="' + (data.offset - data.page_size) + '"><span aria-hidden="true">«</span> ' + _("Previous") + '</a></li>' );
542 for ( var page = Math.max( 0, cur_page - 9 ); page <= Math.min( max_page, cur_page + 9 ); page++ ) {
543 if ( page == cur_page ) {
544 pages.push( ' <li class="active"><a href="#">' + ( page + 1 ) + '</a></li>' );
546 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + ( page * data.page_size ) + '">' + ( page + 1 ) + '</a></li>' );
550 if ( cur_page < max_page ) {
551 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + (data.offset + data.page_size) + '">' + _("Next") + ' <span aria-hidden="true">»</span></a></li>' );
554 $( '#search-top-pages, #search-bottom-pages' ).find( 'nav' ).html( pages.length > 1 ? ( '<ul class="pagination pagination-sm">' + pages.join( '' ) + '</ul>' ) : '' );
556 var $overlay = $('#search-overlay');
557 $overlay.find('span').text(_("Loading"));
558 $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
560 if ( data.activeclients ) {
561 $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
564 $overlay.find('.bar').css( { display: 'block', width: '100%' } );
566 $('#searchresults')[0].focus();
570 function invalidateSearchResults() {
571 var $overlay = $('#search-overlay');
572 $overlay.find('span').text(_("Search expired, please try again"));
573 $overlay.find('.bar').css( { display: 'none' } );
577 function handleSearchError(error) {
578 if (error.code == 1) {
579 invalidateSearchResults();
582 humanMsg.displayMsg( '<h3>' + _("Internal search error") + '</h3><p>' + error.responseText + '</p><p>' + _("Please refresh the page and try again.") + '</p>', { className: 'humanError' } );
586 function handleSearchInitError(error) {
587 $('#quicksearch-overlay').fadeIn().find('p').text(error);
590 // Preference functions
591 function showPreference( pref ) {
592 var value = Preferences.user[pref];
596 $( '#set-field-widgets' ).text( value ? _("Show fields verbatim") : _("Show helpers for fixed and coded fields") );
599 $( '#editor .CodeMirror' ).css( { fontFamily: value } );
603 $( '#editor .CodeMirror' ).css( { fontSize: value } );
607 // Macros loaded on first show of modal
609 case 'selected_search_targets':
610 $.each( z3950Servers, function( server_id, server ) {
611 var saved_val = Preferences.user.selected_search_targets[server_id];
613 if ( saved_val != null ) server.checked = saved_val;
619 function bindPreference( editor, pref ) {
620 function _addHandler( sel, event, handler ) {
621 $( sel ).on( event, function (e) {
623 handler( e, Preferences.user[pref] );
624 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
625 showPreference(pref);
631 _addHandler( '#set-field-widgets', 'click', function( e, oldValue ) {
632 editor.setUseWidgets( Preferences.user.fieldWidgets = !Preferences.user.fieldWidgets );
636 _addHandler( '#prefs-menu .set-font', 'click', function( e, oldValue ) {
637 Preferences.user.font = $( e.target ).css( 'font-family' );
641 _addHandler( '#prefs-menu .set-fontSize', 'click', function( e, oldValue ) {
642 Preferences.user.fontSize = $( e.target ).css( 'font-size' );
645 case 'selected_search_targets':
646 $( document ).on( 'change', 'input.search-toggle-server', function() {
647 var server_id = $( this ).closest('li').data('server-id');
648 Preferences.user.selected_search_targets[server_id] = this.checked;
649 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
655 function displayPreferences( editor ) {
656 $.each( Preferences.user, function( pref, value ) {
657 showPreference( pref );
658 bindPreference( editor, pref );
663 function loadMacro( name ) {
664 $( '#macro-list li' ).removeClass( 'active' );
665 macroEditor.activeMacro = name;
668 macroEditor.setValue( '' );
672 $( '#macro-list li[data-name="' + name + '"]' ).addClass( 'active' );
673 var macro = Preferences.user.macros[name];
674 macroEditor.setValue( macro.contents );
675 macroEditor.setOption( 'readOnly', false );
676 $( '#macro-format' ).val( macro.format || 'its' );
677 if ( macro.history ) macroEditor.setHistory( macro.history );
680 function storeMacro( name, macro ) {
682 Preferences.user.macros[name] = macro;
684 delete Preferences.user.macros[name];
687 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
690 function showSavedMacros( macros ) {
691 var scrollTop = $('#macro-list').scrollTop();
692 $( '#macro-list' ).empty();
693 var macro_list = $.map( Preferences.user.macros, function( macro, name ) {
694 return $.extend( { name: name }, macro );
696 macro_list.sort( function( a, b ) {
697 return a.name.localeCompare(b.name);
699 $.each( macro_list, function( undef, macro ) {
700 var $li = $( '<li data-name="' + macro.name + '"><a href="#">' + macro.name + '</a><ol class="macro-info"></ol></li>' );
701 $li.click( function() {
702 loadMacro(macro.name);
705 if ( macro.name == macroEditor.activeMacro ) $li.addClass( 'active' );
706 var modified = macro.modified && new Date(macro.modified);
707 $li.find( '.macro-info' ).append(
708 '<li><span class="label">' + _("Last changed:") + '</span>' +
709 ( modified ? ( modified.toLocaleDateString() + ', ' + modified.toLocaleTimeString() ) : _("never") ) + '</li>'
711 $('#macro-list').append($li);
713 var $new_li = $( '<li class="new-macro"><a href="#">' + _("New macro...") + '</a></li>' );
714 $new_li.click( function() {
715 // TODO: make this a bit less retro
716 var name = prompt(_("Please enter the name for the new macro:"));
719 if ( !Preferences.user.macros[name] ) storeMacro( name, { format: "rancor", contents: "" } );
723 $('#macro-list').append($new_li);
724 $('#macro-list').scrollTop(scrollTop);
727 function saveMacro() {
728 var name = macroEditor.activeMacro;
730 if ( !name || macroEditor.savedGeneration == macroEditor.changeGeneration() ) return;
732 macroEditor.savedGeneration = macroEditor.changeGeneration();
733 storeMacro( name, { contents: macroEditor.getValue(), modified: (new Date()).valueOf(), history: macroEditor.getHistory(), format: $('#macro-format').val() } );
734 $('#macro-save-message').text(_("Saved"));
738 $(document).ready( function() {
740 editor = new MARCEditor( {
741 onCursorActivity: function() {
742 $('#status-tag-info').empty();
743 $('#status-subfield-info').empty();
745 var field = editor.getCurrentField();
746 var cur = editor.getCursor();
748 if ( !field ) return;
750 var taginfo = KohaBackend.GetTagInfo( '', field.tag );
751 $('#status-tag-info').html( '<strong>' + field.tag + ':</strong> ' );
754 $('#status-tag-info').append( '<a href="' + getFieldHelpURL( field.tag ) + '" target="_blank" class="show-field-help" title="' + _("Show help for this tag") + '">[?]</a> ' + taginfo.lib );
756 var subfield = field.getSubfieldAt( cur.ch );
757 if ( !subfield ) return;
759 var subfieldinfo = taginfo.subfields[ subfield.code ];
760 $('#status-subfield-info').html( '<strong>‡' + subfield.code + ':</strong> ' );
762 if ( subfieldinfo ) {
763 $('#status-subfield-info').append( subfieldinfo.lib );
765 $('#status-subfield-info').append( '<em>' + _("Unknown subfield") + '</em>' );
768 $('#status-tag-info').append( '<em>' + _("Unknown tag") + '</em>' );
771 position: function (elt) { $(elt).insertAfter('#toolbar') },
774 // Automatically detect resizes and change the height of the editor and position of modals.
775 var resizeTimer = null;
776 function onResize() {
777 if ( resizeTimer == null ) resizeTimer = setTimeout( function() {
780 var pos = $('#editor .CodeMirror').position();
781 $('#editor .CodeMirror').height( $(window).height() - pos.top - 24 - $('#changelanguage').height() ); // 24 is hardcoded value but works well
783 $('.modal-body').each( function() {
784 $(this).height( $(window).height() * .8 - $(this).prevAll('.modal-header').height() );
789 $( '#macro-ui' ).on( 'shown.bs.modal', function() {
790 if ( macroEditor ) return;
792 macroEditor = CodeMirror(
793 $('#macro-editor')[0],
796 'Ctrl-D': function( cm ) {
797 var cur = cm.getCursor();
799 cm.replaceRange( "‡", cur, null );
808 macroEditor.on( 'change', function( cm, change ) {
809 $('#macro-save-message').empty();
810 if ( change.origin == 'setValue' ) return;
812 if ( saveTimeout ) clearTimeout( saveTimeout );
813 saveTimeout = setTimeout( function() {
823 var saveableBackends = [];
824 $.each( backends, function( id, backend ) {
825 if ( backend.save ) saveableBackends.push( [ backend.saveLabel, id ] );
827 saveableBackends.sort();
828 $.each( saveableBackends, function( undef, backend ) {
829 $( '#save-dropdown' ).append( '<li><a href="#" data-backend="' + backend[1] + '">' + backend[0] + '</a></li>' );
832 var macro_format_list = $.map( Macros.formats, function( format, name ) {
833 return $.extend( { name: name }, format );
835 macro_format_list.sort( function( a, b ) {
836 return a.description.localeCompare(b.description);
838 $.each( macro_format_list, function() {
839 $('#macro-format').append( '<option value="' + this.name + '">' + this.description + '</option>' );
843 $( '#save-record, #save-dropdown a' ).click( function() {
844 $( '#save-record' ).find('i').attr( 'class', 'fa fa-spinner' ).siblings( 'span' ).text( _("Saving...") );
846 function finishCb(result) {
847 if ( result.error == 'syntax' ) {
848 humanMsg.displayAlert( _("Incorrect syntax, cannot save"), { className: 'humanError' } );
849 } else if ( result.error == 'invalid' ) {
850 humanMsg.displayAlert( _("Record structure invalid, cannot save"), { className: 'humanError' } );
851 } else if ( !result.error ) {
852 humanMsg.displayAlert( _("Record saved "), { className: 'humanSuccess' } );
855 $.each( result.errors || [], function( undef, error ) {
856 switch ( error.type ) {
858 editor.addError( error.line, _("Invalid tag number") );
861 editor.addError( error.line, _("Invalid indicators") );
864 editor.addError( error.line, _("Tag has no subfields") );
867 editor.addError( null, _("Missing mandatory tag: ") + error.tag );
869 case 'missingSubfield':
870 if ( error.subfield == '@' ) {
871 editor.addError( error.line, _("Missing control field contents") );
873 editor.addError( error.line, _("Missing mandatory subfield: ‡") + error.subfield );
876 case 'unrepeatableTag':
877 editor.addError( error.line, _("Tag ") + error.tag + _(" cannot be repeated") );
879 case 'unrepeatableSubfield':
880 editor.addError( error.line, _("Subfield ‡") + error.subfield + _(" cannot be repeated") );
882 case 'itemTagUnsupported':
883 editor.addError( error.line, _("Item tags cannot currently be saved") );
888 $( '#save-record' ).find('i').attr( 'class', 'fa fa-hdd-o' );
890 if ( result.error ) {
891 // Reset backend info
892 setSource( [ state.backend, state.recordID ] );
896 var backend = $( this ).data( 'backend' ) || ( state.saveBackend );
897 if ( state.backend == backend ) {
898 saveRecord( backend + '/' + state.recordID, editor, finishCb );
900 saveRecord( backend + '/', editor, finishCb );
906 $('#import-records').click( function() {
907 $('#import-records-input')
909 .change( function() {
910 if ( !this.files || !this.files.length ) return;
912 var file = this.files[0];
913 var reader = new FileReader();
915 reader.onload = function() {
916 var record = new MARC.Record();
918 if ( /\.(mrc|marc|iso|iso2709|marcstd)$/.test( file.name ) ) {
919 record.loadISO2709( reader.result );
920 } else if ( /\.(xml|marcxml)$/.test( file.name ) ) {
921 record.loadMARCXML( reader.result );
923 humanMsg.displayAlert( _("Unknown record type, cannot import"), { className: 'humanError' } );
927 if (record.marc8_corrupted) humanMsg.displayMsg( '<h3>' + _("Possible record corruption") + '</h3><p>' + _("Record not marked as UTF-8, may be corrupted") + '</p>', { className: 'humanError' } );
929 editor.displayRecord( record );
932 reader.readAsText( file );
939 $('#open-macros').click( function() {
940 $('#macro-ui').modal('show');
945 $('#run-macro').click( function() {
946 var result = Macros.Run( editor, $('#macro-format').val(), macroEditor.getValue() );
948 if ( !result.errors.length ) {
949 $('#macro-ui').modal('hide');
950 editor.focus(); //Return cursor to editor after macro run
955 $.each( result.errors, function() {
956 var error = '<b>' + _("Line ") + (this.line + 1) + ':</b> ';
958 switch ( this.error ) {
959 case 'failed': error += _("failed to run"); break;
960 case 'unrecognized': error += _("unrecognized command"); break;
966 humanMsg.displayMsg( '<h3>' + _("Failed to run macro:") + '</h3><ul><li>' + errors.join('</li><li>') + '</li></ul>', { className: 'humanError' } );
971 $('#delete-macro').click( function() {
972 if ( !macroEditor.activeMacro || !confirm( _("Are you sure you want to delete this macro?") ) ) return;
974 storeMacro( macroEditor.activeMacro, undefined );
976 loadMacro( undefined );
981 $( '#switch-editor' ).click( function() {
982 if ( !confirm( _("Any changes will not be saved. Continue?") ) ) return;
984 $.cookie( 'catalogue_editor_[% logged_in_user.borrowernumber | html %]', 'basic', { expires: 365, path: '/' } );
986 if ( state.backend == 'catalog' ) {
987 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=' + state.recordID;
988 } else if ( state.backend == 'new' ) {
989 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl';
991 humanMsg.displayAlert( _("Cannot open this record in the basic editor"), { className: 'humanError' } );
995 $( '#show-advanced-search' ).click( function() {
996 showAdvancedSearch();
1001 $('#advanced-search').submit( function() {
1002 startAdvancedSearch();
1007 $( document ).on( 'click', 'a.search-nav', function() {
1008 if ( Search.Fetch( { offset: $( this ).data( 'offset' ) } ) ) {
1009 $("#search-overlay").show();
1015 $( document ).on( 'click', 'th[data-sort-label]', function() {
1018 if ( $( this ).hasClass( 'sorting_asc' ) ) {
1024 if ( Search.Fetch( { sort_key: $( this ).data( 'sort-label' ), sort_direction: direction } ) ) {
1025 showSearchSorting( $( this ).data( 'sort-label' ), direction );
1027 $("#search-overlay").show();
1033 $( document ).on( 'change', 'input.search-toggle-server', function() {
1034 var server = z3950Servers[ $( this ).closest('li').data('server-id') ];
1035 server.checked = this.checked;
1037 if ( $('#search-results-ui').is( ':visible' ) && Search.Fetch() ) {
1038 $("#search-overlay").show();
1046 $("#advanced-search-ui, #search-results-ui, #macro-ui").each( function() {
1047 $(this).modal({ show: false });
1050 var $quicksearch = $('#quicksearch fieldset');
1051 $('<div id="quicksearch-overlay"><h3>' + _("Search unavailable") + '</h3> <p></p></div>').css({
1052 position: 'absolute',
1053 top: $quicksearch.offset().top,
1054 left: $quicksearch.offset().left,
1055 height: $quicksearch.outerHeight(),
1056 width: $quicksearch.outerWidth(),
1057 }).appendTo(document.body).hide();
1059 var prevAlerts = [];
1060 humanMsg.logMsg = function(msg, options) {
1061 $('#show-alerts').popover('hide');
1062 prevAlerts.unshift('<li>' + msg + '</li>');
1063 prevAlerts.splice(5, 999); // Truncate old messages
1066 $('#show-alerts').popover({
1068 placement: 'bottom',
1069 content: function() {
1070 return '<div id="alerts-container"><ul>' + prevAlerts.join('') + '</ul></div>';
1074 $('#show-shortcuts').popover({
1076 placement: 'bottom',
1077 content: function() {
1078 return '<div id="shortcuts-container">' + $('#shortcuts-contents').html() + '</div>';
1082 $('#new-record' ).click( function() {
1083 if ( editor.modified && !confirm( _("Are you sure you want to erase your changes?") ) ) return;
1085 openRecord( 'new/', editor );
1089 window.onbeforeunload = function() {
1090 if(editor.modified )
1093 { return undefined; }
1097 Preferences.Load( [% logged_in_user.borrowernumber || 0 | html %] );
1098 displayPreferences(editor);
1099 makeAuthorisedValueWidgets( '' );
1102 onresults: function(data) { showSearchResults( editor, data ) },
1103 onerror: handleSearchError,
1106 function finishCb( data ) {
1107 if ( data.error ) openRecord( 'new/', editor, finishCb );
1109 Resources.GetAll().done( function() {
1110 $("#loading").hide();
1111 $( window ).resize( onResize ).resize();
1116 if ( "[% auth_forwarded_hash | html %]" ) {
1117 document.location.hash = "[% auth_forwarded_hash | html %]";
1120 if ( !document.location.hash || !openRecord( document.location.hash.slice(1), editor, finishCb ) ) {
1121 openRecord( 'new/', editor, finishCb );