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 baseUrl: '[% interface %]/lib/koha/cateditor/',
10 marcflavour: '[% marcflavour %]',
11 themelang: '[% themelang %]',
18 [% IF marcflavour == 'MARC21' %]
19 [% PROCESS 'cateditor-widgets-marc21.inc' %]
21 <script>var editorWidgets = {};</script>
25 require( [ 'koha-backend', 'search', 'macros', 'marc-editor', 'marc-record', 'preferences', 'resources', 'text-marc', 'widget' ], function( KohaBackend, Search, Macros, MARCEditor, MARC, Preferences, Resources, TextMARC, Widget ) {
27 'koha:biblioserver': {
28 name: _("Local catalog"),
32 [%- FOREACH server = z3950_servers -%]
34 name: '[% server.servername %]',
35 recordtype: '[% server.recordtype %]',
36 checked: [% server.checked ? 'true' : 'false' %],
41 // The columns that should show up in a search, in order, and keyed by the corresponding <metadata> tag in the XSL and Pazpar2 config
43 [ "local_number", _("Local number") ],
44 [ "title", _("Title") ],
45 [ "series", _("Series title") ],
46 [ "author", _("Author") ],
47 [ "lccn", _("LCCN") ],
48 [ "isbn", _("ISBN") ],
49 [ "issn", _("ISSN") ],
50 [ "medium", _("Medium") ],
51 [ "edition", _("Edition") ],
52 [ "notes", _("Notes") ],
57 saveBackend: 'catalog',
64 function makeAuthorisedValueWidgets( frameworkCode ) {
65 $.each( KohaBackend.GetAllTagsInfo( frameworkCode ), function( tag, tagInfo ) {
66 $.each( tagInfo.subfields, function( subfield, subfieldInfo ) {
67 if ( !subfieldInfo.authorised_value ) return;
68 var authvals = KohaBackend.GetAuthorisedValues( subfieldInfo.authorised_value );
69 if ( !authvals ) return;
71 var defaultvalue = subfield.defaultvalue || authvals[0].value;
73 Widget.Register( tag + subfield, {
75 var $result = $( '<span class="subfield-widget"></span>' );
79 postCreate: function() {
80 this.setText( defaultvalue );
82 $( '<select></select>' ).appendTo( this.node );
83 var $node = $( this.node ).find( 'select' );
84 $.each( authvals, function( undef, authval ) {
85 $node.append( '<option value="' + authval.value + '"' + (authval.value == defaultvalue ? ' selected="selected"' : '') + '>' + authval.lib + '</option>' );
87 $node.val( this.text );
89 $node.change( $.proxy( function() {
90 this.setText( $node.val() );
93 makeTemplate: function() {
101 function bindGlobalKeys() {
102 shortcut.add( 'ctrl+s', function(event) {
103 $( '#save-record' ).click();
105 event.preventDefault();
108 shortcut.add( 'alt+ctrl+k', function(event) {
109 $( '#search-by-keywords' ).focus();
114 shortcut.add( 'alt+ctrl+a', function(event) {
115 $( '#search-by-author' ).focus();
120 shortcut.add( 'alt+ctrl+i', function(event) {
121 $( '#search-by-isbn' ).focus();
126 shortcut.add( 'alt+ctrl+t', function(event) {
127 $( '#search-by-title' ).focus();
132 shortcut.add( 'ctrl+h', function() {
133 var field = editor.getCurrentField();
135 if ( !field ) return;
137 window.open( getFieldHelpURL( field.tag ) );
140 $('#quicksearch .search-box').each( function() {
141 shortcut.add( 'enter', $.proxy( function() {
144 $('#quicksearch .search-box').each( function() {
145 if ( !this.value ) return;
147 terms.push( [ $(this).data('qualifier'), this.value ] );
150 if ( !terms.length ) return;
152 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
153 $("#search-overlay").show();
158 }, this), { target: this, type: 'keypress' } );
162 function getFieldHelpURL( tag ) {
163 [% IF ( marcflavour == 'MARC21' ) %]
164 if ( tag == '000' ) {
165 return "http://www.loc.gov/marc/bibliographic/bdleader.html";
166 } else if ( tag < '900' ) {
167 return "http://www.loc.gov/marc/bibliographic/bd" + tag + ".html";
169 return "http://www.loc.gov/marc/bibliographic/bd9xx.html";
171 [% ELSIF ( marcflavour == 'UNIMARC' ) %]
172 /* http://archive.ifla.org/VI/3/p1996-1/ is an outdated version of UNIMARC, but
173 seems to be the only version available that can be linked to per tag. More recent
174 versions of the UNIMARC standard are available on the IFLA website only as
177 if ( tag == '000' ) {
178 return "http://archive.ifla.org/VI/3/p1996-1/uni.htm";
181 var url = "http://archive.ifla.org/VI/3/p1996-1/uni" + first + ".htm#";
182 if ( first == '0' ) url += "b";
183 if ( first != '9' ) url += tag;
193 titleForRecord: _("Editing new record"),
194 get: function( id, callback ) {
195 record = new MARC.Record();
196 KohaBackend.FillRecord( '', record );
202 titleForRecord: _("Editing new full record"),
203 get: function( id, callback ) {
204 record = new MARC.Record();
205 KohaBackend.FillRecord( '', record, true );
211 titleForRecord: _("Editing catalog record #{ID}"),
213 { title: _("view"), href: "/cgi-bin/koha/catalogue/detail.pl?biblionumber={ID}" },
214 { title: _("edit items"), href: "/cgi-bin/koha/cataloguing/additem.pl?biblionumber={ID}" },
216 saveLabel: _("Save to catalog"),
217 get: function( id, callback ) {
218 if ( !id ) return false;
220 KohaBackend.GetRecord( id, callback );
222 save: function( id, record, done ) {
223 function finishCb( data ) {
224 done( { error: data.error, newRecord: data.marcxml && data.marcxml[0], newId: data.biblionumber && [ 'catalog', data.biblionumber ] } );
228 KohaBackend.SaveRecord( id, record, finishCb );
230 KohaBackend.CreateRecord( record, finishCb );
235 saveLabel: _("Save as ISO2709 (.mrc) file"),
236 save: function( id, record, done ) {
237 saveAs( new Blob( [record.toISO2709()], { 'type': 'application/octet-stream;charset=utf-8' } ), 'record.mrc' );
243 saveLabel: _("Save as MARCXML (.xml) file"),
244 save: function( id, record, done ) {
245 saveAs( new Blob( [record.toXML()], { 'type': 'application/octet-stream;charset=utf-8' } ), 'record.xml' );
251 titleForRecord: _("Editing search result"),
252 get: function( id, callback ) {
253 if ( !id ) return false;
254 if ( !backends.search.records[ id ] ) {
255 callback( { error: _( "Invalid record" ) } );
259 callback( backends.search.records[ id ] );
265 function setSource(parts) {
266 state.backend = parts[0];
267 state.recordID = parts[1];
268 state.canSave = backends[ state.backend ].save != null;
269 state.saveBackend = state.canSave ? state.backend : 'catalog';
271 var backend = backends[state.backend];
273 document.location.hash = '#' + parts[0] + '/' + parts[1];
275 $('#title').text( backend.titleForRecord.replace( '{ID}', parts[1] ) );
277 $.each( backend.links || [], function( i, link ) {
278 $('#title').append(' <a target="_blank" href="' + link.href.replace( '{ID}', parts[1] ) + '">(' + link.title + ')</a>' );
280 $( 'title', document.head ).html( _("Koha › Cataloging › ") + backend.titleForRecord.replace( '{ID}', parts[1] ) );
281 $('#save-record span').text( backends[ state.saveBackend ].saveLabel );
284 function saveRecord( recid, editor, callback ) {
285 var parts = recid.split('/');
286 if ( parts.length != 2 ) return false;
288 if ( !backends[ parts[0] ] || !backends[ parts[0] ].save ) return false;
290 editor.removeErrors();
291 var record = editor.getRecord();
293 if ( record.errors ) {
294 state.saving = false;
295 callback( { error: 'syntax', errors: record.errors } );
299 var errors = KohaBackend.ValidateRecord( '', record );
300 if ( errors.length ) {
301 state.saving = false;
302 callback( { error: 'invalid', errors: errors } );
306 backends[ parts[0] ].save( parts[1], record, function(data) {
307 state.saving = false;
309 if (data.newRecord) {
310 var record = new MARC.Record();
311 record.loadMARCXML(data.newRecord);
312 editor.displayRecord( record );
316 setSource(data.newId);
318 setSource( [ state.backend, state.recordID ] );
321 if (callback) callback( data );
325 function loadRecord( recid, editor, callback ) {
326 var parts = recid.split('/');
327 if ( parts.length != 2 ) return false;
329 if ( !backends[ parts[0] ] || !backends[ parts[0] ].get ) return false;
331 backends[ parts[0] ].get( parts[1], function( record ) {
332 if ( !record.error ) {
333 editor.displayRecord( record );
337 if (callback) callback(record);
343 function openRecord( recid, editor, callback ) {
344 return loadRecord( recid, editor, function ( record ) {
345 setSource( recid.split('/') );
347 if (callback) callback( record );
352 function showAdvancedSearch() {
353 $('#advanced-search-servers').empty();
354 $.each( z3950Servers, function( server_id, server ) {
355 $('#advanced-search-servers').append( '<li data-server-id="' + server_id + '"><label><input class="search-toggle-server" type="checkbox"' + ( server.checked ? ' checked="checked">' : '>' ) + server.name + '</label></li>' );
357 $('#advanced-search-ui').modal('show');
360 function startAdvancedSearch() {
363 $('#advanced-search-ui .search-box').each( function() {
364 if ( !this.value ) return;
366 terms.push( [ $(this).data('qualifier'), this.value ] );
369 if ( !terms.length ) return;
371 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
372 $('#advanced-search-ui').modal('hide');
373 $("#search-overlay").show();
378 function showResultsBox(data) {
379 $('#search-top-pages, #search-bottom-pages').find('.pagination').empty();
380 $('#searchresults thead tr').empty();
381 $('#searchresults tbody').empty();
382 $('#search-serversinfo').empty().append('<li>' + _("Loading...") + '</li>');
383 $('#search-results-ui').modal('show');
386 function showSearchSorting( sort_key, sort_direction ) {
387 var $th = $('#searchresults thead tr th[data-sort-label="' + sort_key + '"]');
388 $th.parent().find( 'th[data-sort-label]' ).attr( 'class', 'sorting' );
390 if ( sort_direction == 'asc' ) {
392 $th.attr( 'class', 'sorting_asc' );
395 $th.attr( 'class', 'sorting_desc' );
399 function showSearchResults( editor, data ) {
400 backends.search.records = {};
402 $('#searchresults thead tr').empty();
403 $('#searchresults tbody').empty();
404 $('#search-serversinfo').empty();
406 $.each( z3950Servers, function( server_id, server ) {
407 var num_fetched = data.num_fetched[server_id];
409 if ( data.errors[server_id] ) {
410 num_fetched = data.errors[server_id];
411 } else if ( num_fetched == null ) {
413 } else if ( num_fetched < data.num_hits[server_id] ) {
417 $('#search-serversinfo').append( '<li data-server-id="' + server_id + '"><label><input class="search-toggle-server" type="checkbox"' + ( server.checked ? ' checked="checked">' : '>' ) + server.name + ' (' + num_fetched + ')' + '</label></li>' );
420 var seenColumns = {};
422 $.each( data.hits, function( undef, hit ) {
423 $.each( hit.metadata, function(key) {
424 seenColumns[key] = true;
428 $('#searchresults thead tr').append('<th>' + _("Source") + '</th>');
430 $.each( z3950Labels, function( undef, label ) {
431 if ( seenColumns[ label[0] ] ) {
432 $('#searchresults thead tr').append( '<th class="sorting" data-sort-label="' + label[0] + '">' + label[1] + '</th>' );
436 showSearchSorting( data.sort_key, data.sort_direction );
438 $('#searchresults thead tr').append('<th>' + _("Tools") + '</th>');
440 var bibnumMap = KohaBackend.GetSubfieldForKohaField('biblio.biblionumber');
441 $.each( data.hits, function( undef, hit ) {
442 backends.search.records[ hit.server + ':' + hit.index ] = hit.record;
444 switch ( hit.server ) {
445 case 'koha:biblioserver':
446 var bibnumField = hit.record.field( bibnumMap[0] );
448 if ( bibnumField && bibnumField.hasSubfield( bibnumMap[1] ) ) {
449 hit.id = 'catalog/' + bibnumField.subfield( bibnumMap[1] );
453 // Otherwise, fallthrough
456 hit.id = 'search/' + hit.server + ':' + hit.index;
460 result += '<td class="sourcecol">' + z3950Servers[ hit.server ].name + '</td>';
462 $.each( z3950Labels, function( undef, label ) {
463 if ( !seenColumns[ label[0] ] ) return;
465 if ( hit.metadata[ label[0] ] ) {
466 result += '<td class="infocol">' + hit.metadata[ label[0] ] + '</td>';
468 result += '<td class="infocol"> </td>';
472 result += '<td class="toolscol"><ul><li><a href="#" class="marc-link">' + _("View MARC") + '</a></li>';
473 result += '<li><a href="#" class="open-link">' + ( hit.server == 'koha:biblioserver' ? _("Edit") : _("Import") ) + '</a></li>';
474 if ( state.canSave ) result += '<li><a href="#" class="substitute-link" title="' + _("Replace the current record's contents") + '">' + _("Substitute") + '</a></li>';
475 result += '</ul></td></tr>';
477 var $tr = $( result );
478 $tr.find( '.marc-link' ).click( function() {
479 var $info_columns = $tr.find( '.infocol' );
480 var $marc_column = $tr.find( '.marccol' );
482 if ( !$marc_column.length ) {
483 $marc_column = $( '<td class="marccol" colspan="' + $info_columns.length + '"></td>' ).insertAfter( $info_columns.eq(-1) ).hide();
484 CodeMirror.runMode( TextMARC.RecordToText( hit.record ), 'marc', $marc_column[0] );
487 if ( $marc_column.is(':visible') ) {
488 $tr.find('.marc-link').text( _("View MARC") );
489 $info_columns.show();
492 $tr.find('.marc-link').text( _("Hide MARC") );
494 $info_columns.hide();
499 $tr.find( '.open-link' ).click( function() {
500 $( '#search-results-ui' ).modal('hide');
501 openRecord( hit.id, editor );
505 $tr.find( '.substitute-link' ).click( function() {
506 $( '#search-results-ui' ).modal('hide');
507 loadRecord( hit.id, editor );
511 $('#searchresults tbody').append( $tr );
515 var cur_page = data.offset / data.page_size;
516 var max_page = Math.ceil( data.total_fetched / data.page_size ) - 1;
518 if ( cur_page != 0 ) {
519 pages.push( '<li><a class="search-nav" href="#" data-offset="' + (data.offset - data.page_size) + '">« ' + _("Previous") + '</a></li>' );
522 for ( var page = Math.max( 0, cur_page - 9 ); page <= Math.min( max_page, cur_page + 9 ); page++ ) {
523 if ( page == cur_page ) {
524 pages.push( ' <li class="active"><a href="#">' + ( page + 1 ) + '</a></li>' );
526 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + ( page * data.page_size ) + '">' + ( page + 1 ) + '</a></li>' );
530 if ( cur_page < max_page ) {
531 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + (data.offset + data.page_size) + '">' + _("Next") + ' »</a></li>' );
534 $( '#search-top-pages, #search-bottom-pages' ).find( '.pagination' ).html( pages.length > 1 ? ( '<ul>' + pages.join( '' ) + '</ul>' ) : '' );
536 var $overlay = $('#search-overlay');
537 $overlay.find('span').text(_("Loading"));
538 $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
540 if ( data.activeclients ) {
541 $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
544 $overlay.find('.bar').css( { display: 'block', width: '100%' } );
546 $('#searchresults')[0].focus();
550 function invalidateSearchResults() {
551 var $overlay = $('#search-overlay');
552 $overlay.find('span').text(_("Search expired, please try again"));
553 $overlay.find('.bar').css( { display: 'none' } );
557 function handleSearchError(error) {
558 if (error.code == 1) {
559 invalidateSearchResults();
562 humanMsg.displayMsg( _("<h3>Internal search error</h3>") + '<p>' + error + '</p>' + _("<p>Please <b>refresh</b> the page and try again."), { className: 'humanError' } );
566 function handleSearchInitError(error) {
567 $('#quicksearch-overlay').fadeIn().find('p').text(error);
570 // Preference functions
571 function showPreference( pref ) {
572 var value = Preferences.user[pref];
576 $( '#set-field-widgets' ).text( value ? _("Show fields verbatim") : _("Show helpers for fixed and coded fields") );
579 $( '#editor .CodeMirror' ).css( { fontFamily: value } );
583 $( '#editor .CodeMirror' ).css( { fontSize: value } );
587 // Macros loaded on first show of modal
589 case 'selected_search_targets':
590 $.each( z3950Servers, function( server_id, server ) {
591 var saved_val = Preferences.user.selected_search_targets[server_id];
593 if ( saved_val != null ) server.checked = saved_val;
599 function bindPreference( editor, pref ) {
600 function _addHandler( sel, event, handler ) {
601 $( sel ).on( event, function (e) {
603 handler( e, Preferences.user[pref] );
604 Preferences.Save( [% USER_INFO.0.borrowernumber %] );
605 showPreference(pref);
611 _addHandler( '#set-field-widgets', 'click', function( e, oldValue ) {
612 editor.setUseWidgets( Preferences.user.fieldWidgets = !Preferences.user.fieldWidgets );
616 _addHandler( '#prefs-menu .set-font', 'click', function( e, oldValue ) {
617 Preferences.user.font = $( e.target ).css( 'font-family' );
621 _addHandler( '#prefs-menu .set-fontSize', 'click', function( e, oldValue ) {
622 Preferences.user.fontSize = $( e.target ).css( 'font-size' );
625 case 'selected_search_targets':
626 $( document ).on( 'change', 'input.search-toggle-server', function() {
627 var server_id = $( this ).closest('li').data('server-id');
628 Preferences.user.selected_search_targets[server_id] = this.checked;
629 Preferences.Save( [% USER_INFO.0.borrowernumber %] );
635 function displayPreferences( editor ) {
636 $.each( Preferences.user, function( pref, value ) {
637 showPreference( pref );
638 bindPreference( editor, pref );
643 function loadMacro( name ) {
644 $( '#macro-list li' ).removeClass( 'active' );
645 macroEditor.activeMacro = name;
648 macroEditor.setValue( '' );
652 $( '#macro-list li[data-name="' + name + '"]' ).addClass( 'active' );
653 var macro = Preferences.user.macros[name];
654 macroEditor.setValue( macro.contents );
655 macroEditor.setOption( 'readOnly', false );
656 $( '#macro-format' ).val( macro.format || 'its' );
657 if ( macro.history ) macroEditor.setHistory( macro.history );
660 function storeMacro( name, macro ) {
662 Preferences.user.macros[name] = macro;
664 delete Preferences.user.macros[name];
667 Preferences.Save( [% USER_INFO.0.borrowernumber %] );
670 function showSavedMacros( macros ) {
671 var scrollTop = $('#macro-list').scrollTop();
672 $( '#macro-list' ).empty();
673 var macro_list = $.map( Preferences.user.macros, function( macro, name ) {
674 return $.extend( { name: name }, macro );
676 macro_list.sort( function( a, b ) {
677 return a.name.localeCompare(b.name);
679 $.each( macro_list, function( undef, macro ) {
680 var $li = $( '<li data-name="' + macro.name + '"><a href="#">' + macro.name + '</a><ol class="macro-info"></ol></li>' );
681 $li.click( function() {
682 loadMacro(macro.name);
685 if ( macro.name == macroEditor.activeMacro ) $li.addClass( 'active' );
686 var modified = macro.modified && new Date(macro.modified);
687 $li.find( '.macro-info' ).append(
688 '<li><span class="label">' + _("Last changed:") + '</span>' +
689 ( modified ? ( modified.toLocaleDateString() + ', ' + modified.toLocaleTimeString() ) : _("never") ) + '</li>'
691 $('#macro-list').append($li);
693 var $new_li = $( '<li class="new-macro"><a href="#">' + _("New macro...") + '</a></li>' );
694 $new_li.click( function() {
695 // TODO: make this a bit less retro
696 var name = prompt(_("Please enter the name for the new macro:"));
699 if ( !Preferences.user.macros[name] ) storeMacro( name, { format: "rancor", contents: "" } );
703 $('#macro-list').append($new_li);
704 $('#macro-list').scrollTop(scrollTop);
707 function saveMacro() {
708 var name = macroEditor.activeMacro;
710 if ( !name || macroEditor.savedGeneration == macroEditor.changeGeneration() ) return;
712 macroEditor.savedGeneration = macroEditor.changeGeneration();
713 storeMacro( name, { contents: macroEditor.getValue(), modified: (new Date()).valueOf(), history: macroEditor.getHistory(), format: $('#macro-format').val() } );
714 $('#macro-save-message').text(_("Saved"));
718 $(document).ready( function() {
720 editor = new MARCEditor( {
721 onCursorActivity: function() {
722 $('#status-tag-info').empty();
723 $('#status-subfield-info').empty();
725 var field = editor.getCurrentField();
726 var cur = editor.getCursor();
728 if ( !field ) return;
730 var taginfo = KohaBackend.GetTagInfo( '', field.tag );
731 $('#status-tag-info').html( '<strong>' + field.tag + ':</strong> ' );
734 $('#status-tag-info').append( '<a href="' + getFieldHelpURL( field.tag ) + '" target="_blank" class="show-field-help" title="' + _("Show help for this tag") + '">[?]</a> ' + taginfo.lib );
736 var subfield = field.getSubfieldAt( cur.ch );
737 if ( !subfield ) return;
739 var subfieldinfo = taginfo.subfields[ subfield.code ];
740 $('#status-subfield-info').html( '<strong>‡' + subfield.code + ':</strong> ' );
742 if ( subfieldinfo ) {
743 $('#status-subfield-info').append( subfieldinfo.lib );
745 $('#status-subfield-info').append( '<em>' + _("Unknown subfield") + '</em>' );
748 $('#status-tag-info').append( '<em>' + _("Unknown tag") + '</em>' );
751 position: function (elt) { $(elt).insertAfter('#toolbar') },
754 // Automatically detect resizes and change the height of the editor and position of modals.
755 var resizeTimer = null;
756 function onResize() {
757 if ( resizeTimer == null ) resizeTimer = setTimeout( function() {
760 var pos = $('#editor .CodeMirror').position();
761 $('#editor .CodeMirror').height( $(window).height() - pos.top - 24 - $('#changelanguage').height() ); // 24 is hardcoded value but works well
763 $('.modal-body').each( function() {
764 $(this).height( $(window).height() * .8 - $(this).prevAll('.modal-header').height() );
768 $("#advanced-search-ui, #search-results-ui, #macro-ui").css( {
769 marginLeft: function() {
770 return -($(this).width() / 2);
775 $( '#macro-ui' ).on( 'shown', function() {
776 if ( macroEditor ) return;
778 macroEditor = CodeMirror(
779 $('#macro-editor')[0],
782 'Ctrl-D': function( cm ) {
783 var cur = cm.getCursor();
785 cm.replaceRange( "‡", cur, null );
794 macroEditor.on( 'change', function( cm, change ) {
795 $('#macro-save-message').empty();
796 if ( change.origin == 'setValue' ) return;
798 if ( saveTimeout ) clearTimeout( saveTimeout );
799 saveTimeout = setTimeout( function() {
809 var saveableBackends = [];
810 $.each( backends, function( id, backend ) {
811 if ( backend.save ) saveableBackends.push( [ backend.saveLabel, id ] );
813 saveableBackends.sort();
814 $.each( saveableBackends, function( undef, backend ) {
815 $( '#save-dropdown' ).append( '<li><a href="#" data-backend="' + backend[1] + '">' + backend[0] + '</a></li>' );
818 var macro_format_list = $.map( Macros.formats, function( format, name ) {
819 return $.extend( { name: name }, format );
821 macro_format_list.sort( function( a, b ) {
822 return a.description.localeCompare(b.description);
824 $.each( macro_format_list, function() {
825 $('#macro-format').append( '<option value="' + this.name + '">' + this.description + '</option>' );
829 $( '#save-record, #save-dropdown a' ).click( function() {
830 $( '#save-record' ).find('i').attr( 'class', 'icon-loading' ).siblings( 'span' ).text( _("Saving...") );
832 function finishCb(result) {
833 if ( result.error == 'syntax' ) {
834 humanMsg.displayAlert( _("Incorrect syntax, cannot save"), { className: 'humanError' } );
835 } else if ( result.error == 'invalid' ) {
836 humanMsg.displayAlert( _("Record structure invalid, cannot save"), { className: 'humanError' } );
837 } else if ( !result.error ) {
838 humanMsg.displayAlert( _("Record saved "), { className: 'humanSuccess' } );
841 $.each( result.errors || [], function( undef, error ) {
842 switch ( error.type ) {
844 editor.addError( error.line, _("Invalid tag number") );
847 editor.addError( error.line, _("Invalid indicators") );
850 editor.addError( error.line, _("Tag has no subfields") );
853 editor.addError( null, _("Missing mandatory tag: ") + error.tag );
855 case 'missingSubfield':
856 if ( error.subfield == '@' ) {
857 editor.addError( error.line, _("Missing control field contents") );
859 editor.addError( error.line, _("Missing mandatory subfield: ‡") + error.subfield );
862 case 'unrepeatableTag':
863 editor.addError( error.line, _("Tag ") + error.tag + _(" cannot be repeated") );
865 case 'unrepeatableSubfield':
866 editor.addError( error.line, _("Subfield ‡") + error.subfield + _(" cannot be repeated") );
868 case 'itemTagUnsupported':
869 editor.addError( error.line, _("Item tags cannot currently be saved") );
874 $( '#save-record' ).find('i').attr( 'class', 'icon-hdd' );
876 if ( result.error ) {
877 // Reset backend info
878 setSource( [ state.backend, state.recordID ] );
882 var backend = $( this ).data( 'backend' ) || ( state.saveBackend );
883 if ( state.backend == backend ) {
884 saveRecord( backend + '/' + state.recordID, editor, finishCb );
886 saveRecord( backend + '/', editor, finishCb );
892 $('#import-records').click( function() {
893 $('#import-records-input')
895 .change( function() {
896 if ( !this.files || !this.files.length ) return;
898 var file = this.files[0];
899 var reader = new FileReader();
901 reader.onload = function() {
902 var record = new MARC.Record();
904 if ( /\.(mrc|marc|iso|iso2709|marcstd)$/.test( file.name ) ) {
905 record.loadISO2709( reader.result );
906 } else if ( /\.(xml|marcxml)$/.test( file.name ) ) {
907 record.loadMARCXML( reader.result );
909 humanMsg.displayAlert( _("Unknown record type, cannot import"), { className: 'humanError' } );
913 if (record.marc8_corrupted) humanMsg.displayMsg( '<h3>' + _("Possible record corruption") + '</h3><p>' + _("Record not marked as UTF-8, may be corrupted") + '</p>', { className: 'humanError' } );
915 editor.displayRecord( record );
918 reader.readAsText( file );
925 $('#open-macros').click( function() {
926 $('#macro-ui').modal('show');
931 $('#run-macro').click( function() {
932 var result = Macros.Run( editor, $('#macro-format').val(), macroEditor.getValue() );
934 if ( !result.errors.length ) {
935 $('#macro-ui').modal('hide');
940 $.each( result.errors, function() {
941 var error = '<b>' + _("Line ") + (this.line + 1) + ':</b> ';
943 switch ( this.error ) {
944 case 'failed': error += _("failed to run"); break;
945 case 'unrecognized': error += _("unrecognized command"); break;
951 humanMsg.displayMsg( _("<h3>Failed to run macro:</h3>") + '<ul><li>' + errors.join('</li><li>') + '</li></ul>', { className: 'humanError' } );
956 $('#delete-macro').click( function() {
957 if ( !macroEditor.activeMacro || !confirm( _("Are you sure you want to delete this macro?") ) ) return;
959 storeMacro( macroEditor.activeMacro, undefined );
961 loadMacro( undefined );
966 $( '#switch-editor' ).click( function() {
967 if ( !confirm( _("Any changes will not be saved. Continue?") ) ) return;
969 $.cookie( 'catalogue_editor_[% USER_INFO.0.borrowernumber %]', 'basic', { expires: 365, path: '/' } );
971 if ( state.backend == 'catalog' ) {
972 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=' + state.recordID;
973 } else if ( state.backend == 'new' ) {
974 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl';
976 humanMsg.displayAlert( _("Cannot open this record in the basic editor"), { className: 'humanError' } );
980 $( '#show-advanced-search' ).click( function() {
981 showAdvancedSearch();
986 $('#advanced-search').submit( function() {
987 startAdvancedSearch();
992 $( document ).on( 'click', 'a.search-nav', function() {
993 if ( Search.Fetch( { offset: $( this ).data( 'offset' ) } ) ) {
994 $("#search-overlay").show();
1000 $( document ).on( 'click', 'th[data-sort-label]', function() {
1003 if ( $( this ).hasClass( 'sorting_asc' ) ) {
1009 if ( Search.Fetch( { sort_key: $( this ).data( 'sort-label' ), sort_direction: direction } ) ) {
1010 showSearchSorting( $( this ).data( 'sort-label' ), direction );
1012 $("#search-overlay").show();
1018 $( document ).on( 'change', 'input.search-toggle-server', function() {
1019 var server = z3950Servers[ $( this ).closest('li').data('server-id') ];
1020 server.checked = this.checked;
1022 if ( $('#search-results-ui').is( ':visible' ) && Search.Fetch() ) {
1023 $("#search-overlay").show();
1031 $("#advanced-search-ui, #search-results-ui, #macro-ui").each( function() {
1032 $(this).modal({ show: false });
1035 var $quicksearch = $('#quicksearch fieldset');
1036 $('<div id="quicksearch-overlay"><h3>' + _("Search unavailable") + '</h3> <p></p></div>').css({
1037 position: 'absolute',
1038 top: $quicksearch.offset().top,
1039 left: $quicksearch.offset().left,
1040 height: $quicksearch.outerHeight(),
1041 width: $quicksearch.outerWidth(),
1042 }).appendTo(document.body).hide();
1044 var prevAlerts = [];
1045 humanMsg.logMsg = function(msg, options) {
1046 $('#show-alerts').popover('hide');
1047 prevAlerts.unshift('<li>' + msg + '</li>');
1048 prevAlerts.splice(5, 999); // Truncate old messages
1051 $('#show-alerts').popover({
1053 placement: 'bottom',
1054 content: function() {
1055 return '<div id="alerts-container"><ul>' + prevAlerts.join('') + '</ul></div>';
1059 $('#show-shortcuts').popover({
1061 placement: 'bottom',
1062 content: function() {
1063 return '<div id="shortcuts-container">' + $('#shortcuts-contents').html() + '</div>';
1067 $('#new-record' ).click( function() {
1068 if ( editor.modified && !confirm( _("Are you sure you want to erase your changes?") ) ) return;
1070 openRecord( 'new/', editor );
1075 Preferences.Load( [% USER_INFO.0.borrowernumber || 0 %] );
1076 displayPreferences(editor);
1077 makeAuthorisedValueWidgets( '' );
1080 onresults: function(data) { showSearchResults( editor, data ) },
1081 onerror: handleSearchError,
1084 function finishCb( data ) {
1085 if ( data.error ) openRecord( 'new/', editor, finishCb );
1087 Resources.GetAll().done( function() {
1088 $("#loading").hide();
1089 $( window ).resize( onResize ).resize();
1094 if ( "[% auth_forwarded_hash %]" ) {
1095 document.location.hash = "[% auth_forwarded_hash %]";
1098 if ( !document.location.hash || !openRecord( document.location.hash.slice(1), editor, finishCb ) ) {
1099 openRecord( 'new/', editor, finishCb );