1 <script src="[% interface %]/lib/codemirror/codemirror-compressed.js"></script>
2 <script src="[% interface %]/lib/filesaver.js"></script>
3 <script src="[% interface %]/lib/koha/cateditor/marc-mode.js"></script>
4 <script src="[% interface %]/lib/require.js"></script>
7 [%- FOREACH authtag = authtags -%]
8 [% authtag.tagfield %]: {
9 subfield: '[% authtag.tagsubfield %]',
10 authtypecode: '[% authtag.authtypecode %]',
15 baseUrl: '[% interface %]/lib/koha/cateditor/',
18 marcflavour: '[% marcflavour %]',
19 themelang: '[% themelang %]',
26 [% IF marcflavour == 'MARC21' %]
27 [% PROCESS 'cateditor-widgets-marc21.inc' %]
29 <script>var editorWidgets = {};</script>
33 require( [ 'koha-backend', 'search', 'macros', 'marc-editor', 'marc-record', 'preferences', 'resources', 'text-marc', 'widget' ], function( KohaBackend, Search, Macros, MARCEditor, MARC, Preferences, Resources, TextMARC, Widget ) {
35 'koha:biblioserver': {
36 name: _("Local catalog"),
40 [%- FOREACH server = z3950_servers -%]
42 name: '[% server.servername %]',
43 recordtype: '[% server.recordtype %]',
44 checked: [% server.checked ? 'true' : 'false' %],
49 // The columns that should show up in a search, in order, and keyed by the corresponding <metadata> tag in the XSL and Pazpar2 config
51 [ "local_number", _("Local number") ],
52 [ "title", _("Title") ],
53 [ "series", _("Series title") ],
54 [ "author", _("Author") ],
55 [ "lccn", _("LCCN") ],
56 [ "isbn", _("ISBN") ],
57 [ "issn", _("ISSN") ],
58 [ "medium", _("Medium") ],
59 [ "edition", _("Edition") ],
60 [ "notes", _("Notes") ],
65 saveBackend: 'catalog',
72 function makeAuthorisedValueWidgets( frameworkCode ) {
73 $.each( KohaBackend.GetAllTagsInfo( frameworkCode ), function( tag, tagInfo ) {
74 $.each( tagInfo.subfields, function( subfield, subfieldInfo ) {
75 if ( !subfieldInfo.authorised_value ) return;
76 var authvals = KohaBackend.GetAuthorisedValues( subfieldInfo.authorised_value );
77 if ( !authvals ) return;
79 var defaultvalue = subfield.defaultvalue || authvals[0].value;
81 Widget.Register( tag + subfield, {
83 var $result = $( '<span class="subfield-widget"></span>' );
87 postCreate: function() {
88 var value = defaultvalue;
91 $.each( authvals, function() {
92 if ( this.value == widget.text ) {
97 this.setText( value );
99 $( '<select></select>' ).appendTo( this.node );
100 var $node = $( this.node ).find( 'select' );
101 $.each( authvals, function( undef, authval ) {
102 $node.append( '<option value="' + authval.value + '"' + (authval.value == value ? ' selected="selected"' : '') + '>' + authval.lib + '</option>' );
104 $node.val( this.text );
106 $node.change( $.proxy( function() {
107 this.setText( $node.val() );
110 makeTemplate: function() {
118 function bindGlobalKeys() {
119 shortcut.add( 'ctrl+s', function(event) {
120 $( '#save-record' ).click();
122 event.preventDefault();
125 shortcut.add( 'alt+ctrl+k', function(event) {
126 $( '#search-by-keywords' ).focus();
131 shortcut.add( 'alt+ctrl+a', function(event) {
132 $( '#search-by-author' ).focus();
137 shortcut.add( 'alt+ctrl+i', function(event) {
138 $( '#search-by-isbn' ).focus();
143 shortcut.add( 'alt+ctrl+t', function(event) {
144 $( '#search-by-title' ).focus();
149 shortcut.add( 'ctrl+h', function() {
150 var field = editor.getCurrentField();
152 if ( !field ) return;
154 window.open( getFieldHelpURL( field.tag ) );
157 $('#quicksearch .search-box').each( function() {
158 shortcut.add( 'enter', $.proxy( function() {
161 $('#quicksearch .search-box').each( function() {
162 if ( !this.value ) return;
164 terms.push( [ $(this).data('qualifier'), this.value ] );
167 if ( !terms.length ) return;
169 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
170 $("#search-overlay").show();
175 }, this), { target: this, type: 'keypress' } );
179 function getFieldHelpURL( tag ) {
180 [% IF ( marcflavour == 'MARC21' ) %]
181 if ( tag == '000' ) {
182 return "http://www.loc.gov/marc/bibliographic/bdleader.html";
183 } else if ( tag >= '090' && tag < '100' ) {
184 return "http://www.loc.gov/marc/bibliographic/bd09x.html";
185 } else if ( tag < '900' ) {
186 return "http://www.loc.gov/marc/bibliographic/bd" + tag + ".html";
188 return "http://www.loc.gov/marc/bibliographic/bd9xx.html";
190 [% ELSIF ( marcflavour == 'UNIMARC' ) %]
191 /* http://archive.ifla.org/VI/3/p1996-1/ is an outdated version of UNIMARC, but
192 seems to be the only version available that can be linked to per tag. More recent
193 versions of the UNIMARC standard are available on the IFLA website only as
196 if ( tag == '000' ) {
197 return "http://archive.ifla.org/VI/3/p1996-1/uni.htm";
200 var url = "http://archive.ifla.org/VI/3/p1996-1/uni" + first + ".htm#";
201 if ( first == '0' ) url += "b";
202 if ( first != '9' ) url += tag;
212 titleForRecord: _("Editing new record"),
213 get: function( id, callback ) {
214 record = new MARC.Record();
215 KohaBackend.FillRecord( '', record );
221 titleForRecord: _("Editing new full record"),
222 get: function( id, callback ) {
223 record = new MARC.Record();
224 KohaBackend.FillRecord( '', record, true );
230 titleForRecord: _("Editing catalog record #{ID}"),
232 { title: _("view"), href: "/cgi-bin/koha/catalogue/detail.pl?biblionumber={ID}" },
233 { title: _("edit items"), href: "/cgi-bin/koha/cataloguing/additem.pl?biblionumber={ID}" },
235 saveLabel: _("Save to catalog"),
236 get: function( id, callback ) {
237 if ( !id ) return false;
239 KohaBackend.GetRecord( id, callback );
241 save: function( id, record, done ) {
242 function finishCb( data ) {
243 done( { error: data.error, newRecord: data.marcxml && data.marcxml[0], newId: data.biblionumber && [ 'catalog', data.biblionumber ] } );
247 KohaBackend.SaveRecord( id, record, finishCb );
249 KohaBackend.CreateRecord( record, finishCb );
254 saveLabel: _("Save as ISO2709 (.mrc) file"),
255 save: function( id, record, done ) {
256 saveAs( new Blob( [record.toISO2709()], { 'type': 'application/octet-stream;charset=utf-8' } ), 'record.mrc' );
262 saveLabel: _("Save as MARCXML (.xml) file"),
263 save: function( id, record, done ) {
264 saveAs( new Blob( [record.toXML()], { 'type': 'application/octet-stream;charset=utf-8' } ), 'record.xml' );
270 titleForRecord: _("Editing search result"),
271 get: function( id, callback ) {
272 if ( !id ) return false;
273 if ( !backends.search.records[ id ] ) {
274 callback( { error: _( "Invalid record" ) } );
278 callback( backends.search.records[ id ] );
284 function setSource(parts) {
285 state.backend = parts[0];
286 state.recordID = parts[1];
287 state.canSave = backends[ state.backend ].save != null;
288 state.saveBackend = state.canSave ? state.backend : 'catalog';
290 var backend = backends[state.backend];
292 document.location.hash = '#' + parts[0] + '/' + parts[1];
294 $('#title').text( backend.titleForRecord.replace( '{ID}', parts[1] ) );
296 $.each( backend.links || [], function( i, link ) {
297 $('#title').append(' <a target="_blank" href="' + link.href.replace( '{ID}', parts[1] ) + '">(' + link.title + ')</a>' );
299 $( 'title', document.head ).html( _("Koha › Cataloging › ") + backend.titleForRecord.replace( '{ID}', parts[1] ) );
300 $('#save-record span').text( backends[ state.saveBackend ].saveLabel );
303 function saveRecord( recid, editor, callback ) {
304 var parts = recid.split('/');
305 if ( parts.length != 2 ) return false;
307 if ( !backends[ parts[0] ] || !backends[ parts[0] ].save ) return false;
309 editor.removeErrors();
310 var record = editor.getRecord();
312 if ( record.errors ) {
313 state.saving = false;
314 callback( { error: 'syntax', errors: record.errors } );
318 var errors = KohaBackend.ValidateRecord( '', record );
319 if ( errors.length ) {
320 state.saving = false;
321 callback( { error: 'invalid', errors: errors } );
325 backends[ parts[0] ].save( parts[1], record, function(data) {
326 state.saving = false;
328 if (data.newRecord) {
329 var record = new MARC.Record();
330 record.loadMARCXML(data.newRecord);
331 editor.displayRecord( record );
335 setSource(data.newId);
337 setSource( [ state.backend, state.recordID ] );
340 if (callback) callback( data );
344 function loadRecord( recid, editor, callback ) {
345 var parts = recid.split('/');
346 if ( parts.length != 2 ) return false;
348 if ( !backends[ parts[0] ] || !backends[ parts[0] ].get ) return false;
350 backends[ parts[0] ].get( parts[1], function( record ) {
351 if ( !record.error ) {
352 editor.displayRecord( record );
356 if (callback) callback(record);
362 function openRecord( recid, editor, callback ) {
363 return loadRecord( recid, editor, function ( record ) {
364 setSource( recid.split('/') );
366 if (callback) callback( record );
371 function showAdvancedSearch() {
372 $('#advanced-search-servers').empty();
373 $.each( z3950Servers, function( server_id, server ) {
374 $('#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>' );
376 $('#advanced-search-ui').modal('show');
379 function startAdvancedSearch() {
382 $('#advanced-search-ui .search-box').each( function() {
383 if ( !this.value ) return;
385 terms.push( [ $(this).data('qualifier'), this.value ] );
388 if ( !terms.length ) return;
390 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
391 $('#advanced-search-ui').modal('hide');
392 $("#search-overlay").show();
397 function showResultsBox(data) {
398 $('#search-top-pages, #search-bottom-pages').find('nav').empty();
399 $('#searchresults thead tr').empty();
400 $('#searchresults tbody').empty();
401 $('#search-serversinfo').empty().append('<li>' + _("Loading...") + '</li>');
402 $('#search-results-ui').modal('show');
405 function showSearchSorting( sort_key, sort_direction ) {
406 var $th = $('#searchresults thead tr th[data-sort-label="' + sort_key + '"]');
407 $th.parent().find( 'th[data-sort-label]' ).attr( 'class', 'sorting' );
409 if ( sort_direction == 'asc' ) {
411 $th.attr( 'class', 'sorting_asc' );
414 $th.attr( 'class', 'sorting_desc' );
418 function showSearchResults( editor, data ) {
419 backends.search.records = {};
421 $('#searchresults thead tr').empty();
422 $('#searchresults tbody').empty();
423 $('#search-serversinfo').empty();
425 $.each( z3950Servers, function( server_id, server ) {
426 var num_fetched = data.num_fetched[server_id];
428 if ( data.errors[server_id] ) {
429 num_fetched = data.errors[server_id];
430 } else if ( num_fetched == null ) {
432 } else if ( num_fetched < data.num_hits[server_id] ) {
436 $('#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>' );
439 var seenColumns = {};
441 $.each( data.hits, function( undef, hit ) {
442 $.each( hit.metadata, function(key) {
443 seenColumns[key] = true;
447 $('#searchresults thead tr').append('<th>' + _("Source") + '</th>');
449 $.each( z3950Labels, function( undef, label ) {
450 if ( seenColumns[ label[0] ] ) {
451 $('#searchresults thead tr').append( '<th class="sorting" data-sort-label="' + label[0] + '">' + label[1] + '</th>' );
455 showSearchSorting( data.sort_key, data.sort_direction );
457 $('#searchresults thead tr').append('<th>' + _("Tools") + '</th>');
459 var bibnumMap = KohaBackend.GetSubfieldForKohaField('biblio.biblionumber');
460 $.each( data.hits, function( undef, hit ) {
461 backends.search.records[ hit.server + ':' + hit.index ] = hit.record;
463 switch ( hit.server ) {
464 case 'koha:biblioserver':
465 var bibnumField = hit.record.field( bibnumMap[0] );
467 if ( bibnumField && bibnumField.hasSubfield( bibnumMap[1] ) ) {
468 hit.id = 'catalog/' + bibnumField.subfield( bibnumMap[1] );
472 // Otherwise, fallthrough
475 hit.id = 'search/' + hit.server + ':' + hit.index;
479 result += '<td class="sourcecol">' + z3950Servers[ hit.server ].name + '</td>';
481 $.each( z3950Labels, function( undef, label ) {
482 if ( !seenColumns[ label[0] ] ) return;
484 if ( hit.metadata[ label[0] ] ) {
485 result += '<td class="infocol">' + hit.metadata[ label[0] ] + '</td>';
487 result += '<td class="infocol"> </td>';
491 result += '<td class="toolscol"><ul><li><a href="#" class="marc-link">' + _("View MARC") + '</a></li>';
492 result += '<li><a href="#" class="open-link">' + ( hit.server == 'koha:biblioserver' ? _("Edit") : _("Import") ) + '</a></li>';
493 if ( state.canSave ) result += '<li><a href="#" class="substitute-link" title="' + _("Replace the current record's contents") + '">' + _("Substitute") + '</a></li>';
494 result += '</ul></td></tr>';
496 var $tr = $( result );
497 $tr.find( '.marc-link' ).click( function() {
498 var $info_columns = $tr.find( '.infocol' );
499 var $marc_column = $tr.find( '.marccol' );
501 if ( !$marc_column.length ) {
502 $marc_column = $( '<td class="marccol" colspan="' + $info_columns.length + '"></td>' ).insertAfter( $info_columns.eq(-1) ).hide();
503 CodeMirror.runMode( TextMARC.RecordToText( hit.record ), 'marc', $marc_column[0] );
506 if ( $marc_column.is(':visible') ) {
507 $tr.find('.marc-link').text( _("View MARC") );
508 $info_columns.show();
511 $tr.find('.marc-link').text( _("Hide MARC") );
513 $info_columns.hide();
518 $tr.find( '.open-link' ).click( function() {
519 $( '#search-results-ui' ).modal('hide');
520 openRecord( hit.id, editor );
524 $tr.find( '.substitute-link' ).click( function() {
525 $( '#search-results-ui' ).modal('hide');
526 loadRecord( hit.id, editor );
530 $('#searchresults tbody').append( $tr );
534 var cur_page = data.offset / data.page_size;
535 var max_page = Math.ceil( data.total_fetched / data.page_size ) - 1;
537 if ( cur_page != 0 ) {
538 pages.push( '<li><a class="search-nav" href="#" data-offset="' + (data.offset - data.page_size) + '"><span aria-hidden="true">«</span> ' + _("Previous") + '</a></li>' );
541 for ( var page = Math.max( 0, cur_page - 9 ); page <= Math.min( max_page, cur_page + 9 ); page++ ) {
542 if ( page == cur_page ) {
543 pages.push( ' <li class="active"><a href="#">' + ( page + 1 ) + '</a></li>' );
545 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + ( page * data.page_size ) + '">' + ( page + 1 ) + '</a></li>' );
549 if ( cur_page < max_page ) {
550 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + (data.offset + data.page_size) + '">' + _("Next") + ' <span aria-hidden="true">»</span></a></li>' );
553 $( '#search-top-pages, #search-bottom-pages' ).find( 'nav' ).html( pages.length > 1 ? ( '<ul class="pagination pagination-sm">' + pages.join( '' ) + '</ul>' ) : '' );
555 var $overlay = $('#search-overlay');
556 $overlay.find('span').text(_("Loading"));
557 $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
559 if ( data.activeclients ) {
560 $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
563 $overlay.find('.bar').css( { display: 'block', width: '100%' } );
565 $('#searchresults')[0].focus();
569 function invalidateSearchResults() {
570 var $overlay = $('#search-overlay');
571 $overlay.find('span').text(_("Search expired, please try again"));
572 $overlay.find('.bar').css( { display: 'none' } );
576 function handleSearchError(error) {
577 if (error.code == 1) {
578 invalidateSearchResults();
581 humanMsg.displayMsg( '<h3>' + _("Internal search error") + '</h3><p>' + error + '</p><p>' + _("Please refresh the page and try again.") + '</p>', { className: 'humanError' } );
585 function handleSearchInitError(error) {
586 $('#quicksearch-overlay').fadeIn().find('p').text(error);
589 // Preference functions
590 function showPreference( pref ) {
591 var value = Preferences.user[pref];
595 $( '#set-field-widgets' ).text( value ? _("Show fields verbatim") : _("Show helpers for fixed and coded fields") );
598 $( '#editor .CodeMirror' ).css( { fontFamily: value } );
602 $( '#editor .CodeMirror' ).css( { fontSize: value } );
606 // Macros loaded on first show of modal
608 case 'selected_search_targets':
609 $.each( z3950Servers, function( server_id, server ) {
610 var saved_val = Preferences.user.selected_search_targets[server_id];
612 if ( saved_val != null ) server.checked = saved_val;
618 function bindPreference( editor, pref ) {
619 function _addHandler( sel, event, handler ) {
620 $( sel ).on( event, function (e) {
622 handler( e, Preferences.user[pref] );
623 Preferences.Save( [% USER_INFO.borrowernumber %] );
624 showPreference(pref);
630 _addHandler( '#set-field-widgets', 'click', function( e, oldValue ) {
631 editor.setUseWidgets( Preferences.user.fieldWidgets = !Preferences.user.fieldWidgets );
635 _addHandler( '#prefs-menu .set-font', 'click', function( e, oldValue ) {
636 Preferences.user.font = $( e.target ).css( 'font-family' );
640 _addHandler( '#prefs-menu .set-fontSize', 'click', function( e, oldValue ) {
641 Preferences.user.fontSize = $( e.target ).css( 'font-size' );
644 case 'selected_search_targets':
645 $( document ).on( 'change', 'input.search-toggle-server', function() {
646 var server_id = $( this ).closest('li').data('server-id');
647 Preferences.user.selected_search_targets[server_id] = this.checked;
648 Preferences.Save( [% USER_INFO.borrowernumber %] );
654 function displayPreferences( editor ) {
655 $.each( Preferences.user, function( pref, value ) {
656 showPreference( pref );
657 bindPreference( editor, pref );
662 function loadMacro( name ) {
663 $( '#macro-list li' ).removeClass( 'active' );
664 macroEditor.activeMacro = name;
667 macroEditor.setValue( '' );
671 $( '#macro-list li[data-name="' + name + '"]' ).addClass( 'active' );
672 var macro = Preferences.user.macros[name];
673 macroEditor.setValue( macro.contents );
674 macroEditor.setOption( 'readOnly', false );
675 $( '#macro-format' ).val( macro.format || 'its' );
676 if ( macro.history ) macroEditor.setHistory( macro.history );
679 function storeMacro( name, macro ) {
681 Preferences.user.macros[name] = macro;
683 delete Preferences.user.macros[name];
686 Preferences.Save( [% USER_INFO.borrowernumber %] );
689 function showSavedMacros( macros ) {
690 var scrollTop = $('#macro-list').scrollTop();
691 $( '#macro-list' ).empty();
692 var macro_list = $.map( Preferences.user.macros, function( macro, name ) {
693 return $.extend( { name: name }, macro );
695 macro_list.sort( function( a, b ) {
696 return a.name.localeCompare(b.name);
698 $.each( macro_list, function( undef, macro ) {
699 var $li = $( '<li data-name="' + macro.name + '"><a href="#">' + macro.name + '</a><ol class="macro-info"></ol></li>' );
700 $li.click( function() {
701 loadMacro(macro.name);
704 if ( macro.name == macroEditor.activeMacro ) $li.addClass( 'active' );
705 var modified = macro.modified && new Date(macro.modified);
706 $li.find( '.macro-info' ).append(
707 '<li><span class="label">' + _("Last changed:") + '</span>' +
708 ( modified ? ( modified.toLocaleDateString() + ', ' + modified.toLocaleTimeString() ) : _("never") ) + '</li>'
710 $('#macro-list').append($li);
712 var $new_li = $( '<li class="new-macro"><a href="#">' + _("New macro...") + '</a></li>' );
713 $new_li.click( function() {
714 // TODO: make this a bit less retro
715 var name = prompt(_("Please enter the name for the new macro:"));
718 if ( !Preferences.user.macros[name] ) storeMacro( name, { format: "rancor", contents: "" } );
722 $('#macro-list').append($new_li);
723 $('#macro-list').scrollTop(scrollTop);
726 function saveMacro() {
727 var name = macroEditor.activeMacro;
729 if ( !name || macroEditor.savedGeneration == macroEditor.changeGeneration() ) return;
731 macroEditor.savedGeneration = macroEditor.changeGeneration();
732 storeMacro( name, { contents: macroEditor.getValue(), modified: (new Date()).valueOf(), history: macroEditor.getHistory(), format: $('#macro-format').val() } );
733 $('#macro-save-message').text(_("Saved"));
737 $(document).ready( function() {
739 editor = new MARCEditor( {
740 onCursorActivity: function() {
741 $('#status-tag-info').empty();
742 $('#status-subfield-info').empty();
744 var field = editor.getCurrentField();
745 var cur = editor.getCursor();
747 if ( !field ) return;
749 var taginfo = KohaBackend.GetTagInfo( '', field.tag );
750 $('#status-tag-info').html( '<strong>' + field.tag + ':</strong> ' );
753 $('#status-tag-info').append( '<a href="' + getFieldHelpURL( field.tag ) + '" target="_blank" class="show-field-help" title="' + _("Show help for this tag") + '">[?]</a> ' + taginfo.lib );
755 var subfield = field.getSubfieldAt( cur.ch );
756 if ( !subfield ) return;
758 var subfieldinfo = taginfo.subfields[ subfield.code ];
759 $('#status-subfield-info').html( '<strong>‡' + subfield.code + ':</strong> ' );
761 if ( subfieldinfo ) {
762 $('#status-subfield-info').append( subfieldinfo.lib );
764 $('#status-subfield-info').append( '<em>' + _("Unknown subfield") + '</em>' );
767 $('#status-tag-info').append( '<em>' + _("Unknown tag") + '</em>' );
770 position: function (elt) { $(elt).insertAfter('#toolbar') },
773 // Automatically detect resizes and change the height of the editor and position of modals.
774 var resizeTimer = null;
775 function onResize() {
776 if ( resizeTimer == null ) resizeTimer = setTimeout( function() {
779 var pos = $('#editor .CodeMirror').position();
780 $('#editor .CodeMirror').height( $(window).height() - pos.top - 24 - $('#changelanguage').height() ); // 24 is hardcoded value but works well
782 $('.modal-body').each( function() {
783 $(this).height( $(window).height() * .8 - $(this).prevAll('.modal-header').height() );
788 $( '#macro-ui' ).on( 'shown.bs.modal', function() {
789 if ( macroEditor ) return;
791 macroEditor = CodeMirror(
792 $('#macro-editor')[0],
795 'Ctrl-D': function( cm ) {
796 var cur = cm.getCursor();
798 cm.replaceRange( "‡", cur, null );
807 macroEditor.on( 'change', function( cm, change ) {
808 $('#macro-save-message').empty();
809 if ( change.origin == 'setValue' ) return;
811 if ( saveTimeout ) clearTimeout( saveTimeout );
812 saveTimeout = setTimeout( function() {
822 var saveableBackends = [];
823 $.each( backends, function( id, backend ) {
824 if ( backend.save ) saveableBackends.push( [ backend.saveLabel, id ] );
826 saveableBackends.sort();
827 $.each( saveableBackends, function( undef, backend ) {
828 $( '#save-dropdown' ).append( '<li><a href="#" data-backend="' + backend[1] + '">' + backend[0] + '</a></li>' );
831 var macro_format_list = $.map( Macros.formats, function( format, name ) {
832 return $.extend( { name: name }, format );
834 macro_format_list.sort( function( a, b ) {
835 return a.description.localeCompare(b.description);
837 $.each( macro_format_list, function() {
838 $('#macro-format').append( '<option value="' + this.name + '">' + this.description + '</option>' );
842 $( '#save-record, #save-dropdown a' ).click( function() {
843 $( '#save-record' ).find('i').attr( 'class', 'fa fa-spinner' ).siblings( 'span' ).text( _("Saving...") );
845 function finishCb(result) {
846 if ( result.error == 'syntax' ) {
847 humanMsg.displayAlert( _("Incorrect syntax, cannot save"), { className: 'humanError' } );
848 } else if ( result.error == 'invalid' ) {
849 humanMsg.displayAlert( _("Record structure invalid, cannot save"), { className: 'humanError' } );
850 } else if ( !result.error ) {
851 humanMsg.displayAlert( _("Record saved "), { className: 'humanSuccess' } );
854 $.each( result.errors || [], function( undef, error ) {
855 switch ( error.type ) {
857 editor.addError( error.line, _("Invalid tag number") );
860 editor.addError( error.line, _("Invalid indicators") );
863 editor.addError( error.line, _("Tag has no subfields") );
866 editor.addError( null, _("Missing mandatory tag: ") + error.tag );
868 case 'missingSubfield':
869 if ( error.subfield == '@' ) {
870 editor.addError( error.line, _("Missing control field contents") );
872 editor.addError( error.line, _("Missing mandatory subfield: ‡") + error.subfield );
875 case 'unrepeatableTag':
876 editor.addError( error.line, _("Tag ") + error.tag + _(" cannot be repeated") );
878 case 'unrepeatableSubfield':
879 editor.addError( error.line, _("Subfield ‡") + error.subfield + _(" cannot be repeated") );
881 case 'itemTagUnsupported':
882 editor.addError( error.line, _("Item tags cannot currently be saved") );
887 $( '#save-record' ).find('i').attr( 'class', 'fa fa-hdd-o' );
889 if ( result.error ) {
890 // Reset backend info
891 setSource( [ state.backend, state.recordID ] );
895 var backend = $( this ).data( 'backend' ) || ( state.saveBackend );
896 if ( state.backend == backend ) {
897 saveRecord( backend + '/' + state.recordID, editor, finishCb );
899 saveRecord( backend + '/', editor, finishCb );
905 $('#import-records').click( function() {
906 $('#import-records-input')
908 .change( function() {
909 if ( !this.files || !this.files.length ) return;
911 var file = this.files[0];
912 var reader = new FileReader();
914 reader.onload = function() {
915 var record = new MARC.Record();
917 if ( /\.(mrc|marc|iso|iso2709|marcstd)$/.test( file.name ) ) {
918 record.loadISO2709( reader.result );
919 } else if ( /\.(xml|marcxml)$/.test( file.name ) ) {
920 record.loadMARCXML( reader.result );
922 humanMsg.displayAlert( _("Unknown record type, cannot import"), { className: 'humanError' } );
926 if (record.marc8_corrupted) humanMsg.displayMsg( '<h3>' + _("Possible record corruption") + '</h3><p>' + _("Record not marked as UTF-8, may be corrupted") + '</p>', { className: 'humanError' } );
928 editor.displayRecord( record );
931 reader.readAsText( file );
938 $('#open-macros').click( function() {
939 $('#macro-ui').modal('show');
944 $('#run-macro').click( function() {
945 var result = Macros.Run( editor, $('#macro-format').val(), macroEditor.getValue() );
947 if ( !result.errors.length ) {
948 $('#macro-ui').modal('hide');
949 editor.focus(); //Return cursor to editor after macro run
954 $.each( result.errors, function() {
955 var error = '<b>' + _("Line ") + (this.line + 1) + ':</b> ';
957 switch ( this.error ) {
958 case 'failed': error += _("failed to run"); break;
959 case 'unrecognized': error += _("unrecognized command"); break;
965 humanMsg.displayMsg( '<h3>' + _("Failed to run macro:") + '</h3><ul><li>' + errors.join('</li><li>') + '</li></ul>', { className: 'humanError' } );
970 $('#delete-macro').click( function() {
971 if ( !macroEditor.activeMacro || !confirm( _("Are you sure you want to delete this macro?") ) ) return;
973 storeMacro( macroEditor.activeMacro, undefined );
975 loadMacro( undefined );
980 $( '#switch-editor' ).click( function() {
981 if ( !confirm( _("Any changes will not be saved. Continue?") ) ) return;
983 $.cookie( 'catalogue_editor_[% USER_INFO.borrowernumber %]', 'basic', { expires: 365, path: '/' } );
985 if ( state.backend == 'catalog' ) {
986 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=' + state.recordID;
987 } else if ( state.backend == 'new' ) {
988 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl';
990 humanMsg.displayAlert( _("Cannot open this record in the basic editor"), { className: 'humanError' } );
994 $( '#show-advanced-search' ).click( function() {
995 showAdvancedSearch();
1000 $('#advanced-search').submit( function() {
1001 startAdvancedSearch();
1006 $( document ).on( 'click', 'a.search-nav', function() {
1007 if ( Search.Fetch( { offset: $( this ).data( 'offset' ) } ) ) {
1008 $("#search-overlay").show();
1014 $( document ).on( 'click', 'th[data-sort-label]', function() {
1017 if ( $( this ).hasClass( 'sorting_asc' ) ) {
1023 if ( Search.Fetch( { sort_key: $( this ).data( 'sort-label' ), sort_direction: direction } ) ) {
1024 showSearchSorting( $( this ).data( 'sort-label' ), direction );
1026 $("#search-overlay").show();
1032 $( document ).on( 'change', 'input.search-toggle-server', function() {
1033 var server = z3950Servers[ $( this ).closest('li').data('server-id') ];
1034 server.checked = this.checked;
1036 if ( $('#search-results-ui').is( ':visible' ) && Search.Fetch() ) {
1037 $("#search-overlay").show();
1045 $("#advanced-search-ui, #search-results-ui, #macro-ui").each( function() {
1046 $(this).modal({ show: false });
1049 var $quicksearch = $('#quicksearch fieldset');
1050 $('<div id="quicksearch-overlay"><h3>' + _("Search unavailable") + '</h3> <p></p></div>').css({
1051 position: 'absolute',
1052 top: $quicksearch.offset().top,
1053 left: $quicksearch.offset().left,
1054 height: $quicksearch.outerHeight(),
1055 width: $quicksearch.outerWidth(),
1056 }).appendTo(document.body).hide();
1058 var prevAlerts = [];
1059 humanMsg.logMsg = function(msg, options) {
1060 $('#show-alerts').popover('hide');
1061 prevAlerts.unshift('<li>' + msg + '</li>');
1062 prevAlerts.splice(5, 999); // Truncate old messages
1065 $('#show-alerts').popover({
1067 placement: 'bottom',
1068 content: function() {
1069 return '<div id="alerts-container"><ul>' + prevAlerts.join('') + '</ul></div>';
1073 $('#show-shortcuts').popover({
1075 placement: 'bottom',
1076 content: function() {
1077 return '<div id="shortcuts-container">' + $('#shortcuts-contents').html() + '</div>';
1081 $('#new-record' ).click( function() {
1082 if ( editor.modified && !confirm( _("Are you sure you want to erase your changes?") ) ) return;
1084 openRecord( 'new/', editor );
1088 window.onbeforeunload = function() {
1089 if(editor.modified )
1096 Preferences.Load( [% USER_INFO.borrowernumber || 0 %] );
1097 displayPreferences(editor);
1098 makeAuthorisedValueWidgets( '' );
1101 onresults: function(data) { showSearchResults( editor, data ) },
1102 onerror: handleSearchError,
1105 function finishCb( data ) {
1106 if ( data.error ) openRecord( 'new/', editor, finishCb );
1108 Resources.GetAll().done( function() {
1109 $("#loading").hide();
1110 $( window ).resize( onResize ).resize();
1115 if ( "[% auth_forwarded_hash %]" ) {
1116 document.location.hash = "[% auth_forwarded_hash %]";
1119 if ( !document.location.hash || !openRecord( document.location.hash.slice(1), editor, finishCb ) ) {
1120 openRecord( 'new/', editor, finishCb );