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 >= '090' && tag < '100' ) {
167 return "http://www.loc.gov/marc/bibliographic/bd09x.html";
168 } else if ( tag < '900' ) {
169 return "http://www.loc.gov/marc/bibliographic/bd" + tag + ".html";
171 return "http://www.loc.gov/marc/bibliographic/bd9xx.html";
173 [% ELSIF ( marcflavour == 'UNIMARC' ) %]
174 /* http://archive.ifla.org/VI/3/p1996-1/ is an outdated version of UNIMARC, but
175 seems to be the only version available that can be linked to per tag. More recent
176 versions of the UNIMARC standard are available on the IFLA website only as
179 if ( tag == '000' ) {
180 return "http://archive.ifla.org/VI/3/p1996-1/uni.htm";
183 var url = "http://archive.ifla.org/VI/3/p1996-1/uni" + first + ".htm#";
184 if ( first == '0' ) url += "b";
185 if ( first != '9' ) url += tag;
195 titleForRecord: _("Editing new record"),
196 get: function( id, callback ) {
197 record = new MARC.Record();
198 KohaBackend.FillRecord( '', record );
204 titleForRecord: _("Editing new full record"),
205 get: function( id, callback ) {
206 record = new MARC.Record();
207 KohaBackend.FillRecord( '', record, true );
213 titleForRecord: _("Editing catalog record #{ID}"),
215 { title: _("view"), href: "/cgi-bin/koha/catalogue/detail.pl?biblionumber={ID}" },
216 { title: _("edit items"), href: "/cgi-bin/koha/cataloguing/additem.pl?biblionumber={ID}" },
218 saveLabel: _("Save to catalog"),
219 get: function( id, callback ) {
220 if ( !id ) return false;
222 KohaBackend.GetRecord( id, callback );
224 save: function( id, record, done ) {
225 function finishCb( data ) {
226 done( { error: data.error, newRecord: data.marcxml && data.marcxml[0], newId: data.biblionumber && [ 'catalog', data.biblionumber ] } );
230 KohaBackend.SaveRecord( id, record, finishCb );
232 KohaBackend.CreateRecord( record, finishCb );
237 saveLabel: _("Save as ISO2709 (.mrc) file"),
238 save: function( id, record, done ) {
239 saveAs( new Blob( [record.toISO2709()], { 'type': 'application/octet-stream;charset=utf-8' } ), 'record.mrc' );
245 saveLabel: _("Save as MARCXML (.xml) file"),
246 save: function( id, record, done ) {
247 saveAs( new Blob( [record.toXML()], { 'type': 'application/octet-stream;charset=utf-8' } ), 'record.xml' );
253 titleForRecord: _("Editing search result"),
254 get: function( id, callback ) {
255 if ( !id ) return false;
256 if ( !backends.search.records[ id ] ) {
257 callback( { error: _( "Invalid record" ) } );
261 callback( backends.search.records[ id ] );
267 function setSource(parts) {
268 state.backend = parts[0];
269 state.recordID = parts[1];
270 state.canSave = backends[ state.backend ].save != null;
271 state.saveBackend = state.canSave ? state.backend : 'catalog';
273 var backend = backends[state.backend];
275 document.location.hash = '#' + parts[0] + '/' + parts[1];
277 $('#title').text( backend.titleForRecord.replace( '{ID}', parts[1] ) );
279 $.each( backend.links || [], function( i, link ) {
280 $('#title').append(' <a target="_blank" href="' + link.href.replace( '{ID}', parts[1] ) + '">(' + link.title + ')</a>' );
282 $( 'title', document.head ).html( _("Koha › Cataloging › ") + backend.titleForRecord.replace( '{ID}', parts[1] ) );
283 $('#save-record span').text( backends[ state.saveBackend ].saveLabel );
286 function saveRecord( recid, editor, callback ) {
287 var parts = recid.split('/');
288 if ( parts.length != 2 ) return false;
290 if ( !backends[ parts[0] ] || !backends[ parts[0] ].save ) return false;
292 editor.removeErrors();
293 var record = editor.getRecord();
295 if ( record.errors ) {
296 state.saving = false;
297 callback( { error: 'syntax', errors: record.errors } );
301 var errors = KohaBackend.ValidateRecord( '', record );
302 if ( errors.length ) {
303 state.saving = false;
304 callback( { error: 'invalid', errors: errors } );
308 backends[ parts[0] ].save( parts[1], record, function(data) {
309 state.saving = false;
311 if (data.newRecord) {
312 var record = new MARC.Record();
313 record.loadMARCXML(data.newRecord);
314 editor.displayRecord( record );
318 setSource(data.newId);
320 setSource( [ state.backend, state.recordID ] );
323 if (callback) callback( data );
327 function loadRecord( recid, editor, callback ) {
328 var parts = recid.split('/');
329 if ( parts.length != 2 ) return false;
331 if ( !backends[ parts[0] ] || !backends[ parts[0] ].get ) return false;
333 backends[ parts[0] ].get( parts[1], function( record ) {
334 if ( !record.error ) {
335 editor.displayRecord( record );
339 if (callback) callback(record);
345 function openRecord( recid, editor, callback ) {
346 return loadRecord( recid, editor, function ( record ) {
347 setSource( recid.split('/') );
349 if (callback) callback( record );
354 function showAdvancedSearch() {
355 $('#advanced-search-servers').empty();
356 $.each( z3950Servers, function( server_id, server ) {
357 $('#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>' );
359 $('#advanced-search-ui').modal('show');
362 function startAdvancedSearch() {
365 $('#advanced-search-ui .search-box').each( function() {
366 if ( !this.value ) return;
368 terms.push( [ $(this).data('qualifier'), this.value ] );
371 if ( !terms.length ) return;
373 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
374 $('#advanced-search-ui').modal('hide');
375 $("#search-overlay").show();
380 function showResultsBox(data) {
381 $('#search-top-pages, #search-bottom-pages').find('.pagination').empty();
382 $('#searchresults thead tr').empty();
383 $('#searchresults tbody').empty();
384 $('#search-serversinfo').empty().append('<li>' + _("Loading...") + '</li>');
385 $('#search-results-ui').modal('show');
388 function showSearchSorting( sort_key, sort_direction ) {
389 var $th = $('#searchresults thead tr th[data-sort-label="' + sort_key + '"]');
390 $th.parent().find( 'th[data-sort-label]' ).attr( 'class', 'sorting' );
392 if ( sort_direction == 'asc' ) {
394 $th.attr( 'class', 'sorting_asc' );
397 $th.attr( 'class', 'sorting_desc' );
401 function showSearchResults( editor, data ) {
402 backends.search.records = {};
404 $('#searchresults thead tr').empty();
405 $('#searchresults tbody').empty();
406 $('#search-serversinfo').empty();
408 $.each( z3950Servers, function( server_id, server ) {
409 var num_fetched = data.num_fetched[server_id];
411 if ( data.errors[server_id] ) {
412 num_fetched = data.errors[server_id];
413 } else if ( num_fetched == null ) {
415 } else if ( num_fetched < data.num_hits[server_id] ) {
419 $('#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>' );
422 var seenColumns = {};
424 $.each( data.hits, function( undef, hit ) {
425 $.each( hit.metadata, function(key) {
426 seenColumns[key] = true;
430 $('#searchresults thead tr').append('<th>' + _("Source") + '</th>');
432 $.each( z3950Labels, function( undef, label ) {
433 if ( seenColumns[ label[0] ] ) {
434 $('#searchresults thead tr').append( '<th class="sorting" data-sort-label="' + label[0] + '">' + label[1] + '</th>' );
438 showSearchSorting( data.sort_key, data.sort_direction );
440 $('#searchresults thead tr').append('<th>' + _("Tools") + '</th>');
442 var bibnumMap = KohaBackend.GetSubfieldForKohaField('biblio.biblionumber');
443 $.each( data.hits, function( undef, hit ) {
444 backends.search.records[ hit.server + ':' + hit.index ] = hit.record;
446 switch ( hit.server ) {
447 case 'koha:biblioserver':
448 var bibnumField = hit.record.field( bibnumMap[0] );
450 if ( bibnumField && bibnumField.hasSubfield( bibnumMap[1] ) ) {
451 hit.id = 'catalog/' + bibnumField.subfield( bibnumMap[1] );
455 // Otherwise, fallthrough
458 hit.id = 'search/' + hit.server + ':' + hit.index;
462 result += '<td class="sourcecol">' + z3950Servers[ hit.server ].name + '</td>';
464 $.each( z3950Labels, function( undef, label ) {
465 if ( !seenColumns[ label[0] ] ) return;
467 if ( hit.metadata[ label[0] ] ) {
468 result += '<td class="infocol">' + hit.metadata[ label[0] ] + '</td>';
470 result += '<td class="infocol"> </td>';
474 result += '<td class="toolscol"><ul><li><a href="#" class="marc-link">' + _("View MARC") + '</a></li>';
475 result += '<li><a href="#" class="open-link">' + ( hit.server == 'koha:biblioserver' ? _("Edit") : _("Import") ) + '</a></li>';
476 if ( state.canSave ) result += '<li><a href="#" class="substitute-link" title="' + _("Replace the current record's contents") + '">' + _("Substitute") + '</a></li>';
477 result += '</ul></td></tr>';
479 var $tr = $( result );
480 $tr.find( '.marc-link' ).click( function() {
481 var $info_columns = $tr.find( '.infocol' );
482 var $marc_column = $tr.find( '.marccol' );
484 if ( !$marc_column.length ) {
485 $marc_column = $( '<td class="marccol" colspan="' + $info_columns.length + '"></td>' ).insertAfter( $info_columns.eq(-1) ).hide();
486 CodeMirror.runMode( TextMARC.RecordToText( hit.record ), 'marc', $marc_column[0] );
489 if ( $marc_column.is(':visible') ) {
490 $tr.find('.marc-link').text( _("View MARC") );
491 $info_columns.show();
494 $tr.find('.marc-link').text( _("Hide MARC") );
496 $info_columns.hide();
501 $tr.find( '.open-link' ).click( function() {
502 $( '#search-results-ui' ).modal('hide');
503 openRecord( hit.id, editor );
507 $tr.find( '.substitute-link' ).click( function() {
508 $( '#search-results-ui' ).modal('hide');
509 loadRecord( hit.id, editor );
513 $('#searchresults tbody').append( $tr );
517 var cur_page = data.offset / data.page_size;
518 var max_page = Math.ceil( data.total_fetched / data.page_size ) - 1;
520 if ( cur_page != 0 ) {
521 pages.push( '<li><a class="search-nav" href="#" data-offset="' + (data.offset - data.page_size) + '">« ' + _("Previous") + '</a></li>' );
524 for ( var page = Math.max( 0, cur_page - 9 ); page <= Math.min( max_page, cur_page + 9 ); page++ ) {
525 if ( page == cur_page ) {
526 pages.push( ' <li class="active"><a href="#">' + ( page + 1 ) + '</a></li>' );
528 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + ( page * data.page_size ) + '">' + ( page + 1 ) + '</a></li>' );
532 if ( cur_page < max_page ) {
533 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + (data.offset + data.page_size) + '">' + _("Next") + ' »</a></li>' );
536 $( '#search-top-pages, #search-bottom-pages' ).find( '.pagination' ).html( pages.length > 1 ? ( '<ul>' + pages.join( '' ) + '</ul>' ) : '' );
538 var $overlay = $('#search-overlay');
539 $overlay.find('span').text(_("Loading"));
540 $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
542 if ( data.activeclients ) {
543 $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
546 $overlay.find('.bar').css( { display: 'block', width: '100%' } );
548 $('#searchresults')[0].focus();
552 function invalidateSearchResults() {
553 var $overlay = $('#search-overlay');
554 $overlay.find('span').text(_("Search expired, please try again"));
555 $overlay.find('.bar').css( { display: 'none' } );
559 function handleSearchError(error) {
560 if (error.code == 1) {
561 invalidateSearchResults();
564 humanMsg.displayMsg( '<h3>' + _("Internal search error") + '</h3><p>' + error + '</p><p>' + _("Please refresh the page and try again.") + '</p>', { className: 'humanError' } );
568 function handleSearchInitError(error) {
569 $('#quicksearch-overlay').fadeIn().find('p').text(error);
572 // Preference functions
573 function showPreference( pref ) {
574 var value = Preferences.user[pref];
578 $( '#set-field-widgets' ).text( value ? _("Show fields verbatim") : _("Show helpers for fixed and coded fields") );
581 $( '#editor .CodeMirror' ).css( { fontFamily: value } );
585 $( '#editor .CodeMirror' ).css( { fontSize: value } );
589 // Macros loaded on first show of modal
591 case 'selected_search_targets':
592 $.each( z3950Servers, function( server_id, server ) {
593 var saved_val = Preferences.user.selected_search_targets[server_id];
595 if ( saved_val != null ) server.checked = saved_val;
601 function bindPreference( editor, pref ) {
602 function _addHandler( sel, event, handler ) {
603 $( sel ).on( event, function (e) {
605 handler( e, Preferences.user[pref] );
606 Preferences.Save( [% USER_INFO.borrowernumber %] );
607 showPreference(pref);
613 _addHandler( '#set-field-widgets', 'click', function( e, oldValue ) {
614 editor.setUseWidgets( Preferences.user.fieldWidgets = !Preferences.user.fieldWidgets );
618 _addHandler( '#prefs-menu .set-font', 'click', function( e, oldValue ) {
619 Preferences.user.font = $( e.target ).css( 'font-family' );
623 _addHandler( '#prefs-menu .set-fontSize', 'click', function( e, oldValue ) {
624 Preferences.user.fontSize = $( e.target ).css( 'font-size' );
627 case 'selected_search_targets':
628 $( document ).on( 'change', 'input.search-toggle-server', function() {
629 var server_id = $( this ).closest('li').data('server-id');
630 Preferences.user.selected_search_targets[server_id] = this.checked;
631 Preferences.Save( [% USER_INFO.borrowernumber %] );
637 function displayPreferences( editor ) {
638 $.each( Preferences.user, function( pref, value ) {
639 showPreference( pref );
640 bindPreference( editor, pref );
645 function loadMacro( name ) {
646 $( '#macro-list li' ).removeClass( 'active' );
647 macroEditor.activeMacro = name;
650 macroEditor.setValue( '' );
654 $( '#macro-list li[data-name="' + name + '"]' ).addClass( 'active' );
655 var macro = Preferences.user.macros[name];
656 macroEditor.setValue( macro.contents );
657 macroEditor.setOption( 'readOnly', false );
658 $( '#macro-format' ).val( macro.format || 'its' );
659 if ( macro.history ) macroEditor.setHistory( macro.history );
662 function storeMacro( name, macro ) {
664 Preferences.user.macros[name] = macro;
666 delete Preferences.user.macros[name];
669 Preferences.Save( [% USER_INFO.borrowernumber %] );
672 function showSavedMacros( macros ) {
673 var scrollTop = $('#macro-list').scrollTop();
674 $( '#macro-list' ).empty();
675 var macro_list = $.map( Preferences.user.macros, function( macro, name ) {
676 return $.extend( { name: name }, macro );
678 macro_list.sort( function( a, b ) {
679 return a.name.localeCompare(b.name);
681 $.each( macro_list, function( undef, macro ) {
682 var $li = $( '<li data-name="' + macro.name + '"><a href="#">' + macro.name + '</a><ol class="macro-info"></ol></li>' );
683 $li.click( function() {
684 loadMacro(macro.name);
687 if ( macro.name == macroEditor.activeMacro ) $li.addClass( 'active' );
688 var modified = macro.modified && new Date(macro.modified);
689 $li.find( '.macro-info' ).append(
690 '<li><span class="label">' + _("Last changed:") + '</span>' +
691 ( modified ? ( modified.toLocaleDateString() + ', ' + modified.toLocaleTimeString() ) : _("never") ) + '</li>'
693 $('#macro-list').append($li);
695 var $new_li = $( '<li class="new-macro"><a href="#">' + _("New macro...") + '</a></li>' );
696 $new_li.click( function() {
697 // TODO: make this a bit less retro
698 var name = prompt(_("Please enter the name for the new macro:"));
701 if ( !Preferences.user.macros[name] ) storeMacro( name, { format: "rancor", contents: "" } );
705 $('#macro-list').append($new_li);
706 $('#macro-list').scrollTop(scrollTop);
709 function saveMacro() {
710 var name = macroEditor.activeMacro;
712 if ( !name || macroEditor.savedGeneration == macroEditor.changeGeneration() ) return;
714 macroEditor.savedGeneration = macroEditor.changeGeneration();
715 storeMacro( name, { contents: macroEditor.getValue(), modified: (new Date()).valueOf(), history: macroEditor.getHistory(), format: $('#macro-format').val() } );
716 $('#macro-save-message').text(_("Saved"));
720 $(document).ready( function() {
722 editor = new MARCEditor( {
723 onCursorActivity: function() {
724 $('#status-tag-info').empty();
725 $('#status-subfield-info').empty();
727 var field = editor.getCurrentField();
728 var cur = editor.getCursor();
730 if ( !field ) return;
732 var taginfo = KohaBackend.GetTagInfo( '', field.tag );
733 $('#status-tag-info').html( '<strong>' + field.tag + ':</strong> ' );
736 $('#status-tag-info').append( '<a href="' + getFieldHelpURL( field.tag ) + '" target="_blank" class="show-field-help" title="' + _("Show help for this tag") + '">[?]</a> ' + taginfo.lib );
738 var subfield = field.getSubfieldAt( cur.ch );
739 if ( !subfield ) return;
741 var subfieldinfo = taginfo.subfields[ subfield.code ];
742 $('#status-subfield-info').html( '<strong>‡' + subfield.code + ':</strong> ' );
744 if ( subfieldinfo ) {
745 $('#status-subfield-info').append( subfieldinfo.lib );
747 $('#status-subfield-info').append( '<em>' + _("Unknown subfield") + '</em>' );
750 $('#status-tag-info').append( '<em>' + _("Unknown tag") + '</em>' );
753 position: function (elt) { $(elt).insertAfter('#toolbar') },
756 // Automatically detect resizes and change the height of the editor and position of modals.
757 var resizeTimer = null;
758 function onResize() {
759 if ( resizeTimer == null ) resizeTimer = setTimeout( function() {
762 var pos = $('#editor .CodeMirror').position();
763 $('#editor .CodeMirror').height( $(window).height() - pos.top - 24 - $('#changelanguage').height() ); // 24 is hardcoded value but works well
765 $('.modal-body').each( function() {
766 $(this).height( $(window).height() * .8 - $(this).prevAll('.modal-header').height() );
770 $("#advanced-search-ui, #search-results-ui, #macro-ui").css( {
771 marginLeft: function() {
772 return -($(this).width() / 2);
777 $( '#macro-ui' ).on( 'shown', function() {
778 if ( macroEditor ) return;
780 macroEditor = CodeMirror(
781 $('#macro-editor')[0],
784 'Ctrl-D': function( cm ) {
785 var cur = cm.getCursor();
787 cm.replaceRange( "‡", cur, null );
796 macroEditor.on( 'change', function( cm, change ) {
797 $('#macro-save-message').empty();
798 if ( change.origin == 'setValue' ) return;
800 if ( saveTimeout ) clearTimeout( saveTimeout );
801 saveTimeout = setTimeout( function() {
811 var saveableBackends = [];
812 $.each( backends, function( id, backend ) {
813 if ( backend.save ) saveableBackends.push( [ backend.saveLabel, id ] );
815 saveableBackends.sort();
816 $.each( saveableBackends, function( undef, backend ) {
817 $( '#save-dropdown' ).append( '<li><a href="#" data-backend="' + backend[1] + '">' + backend[0] + '</a></li>' );
820 var macro_format_list = $.map( Macros.formats, function( format, name ) {
821 return $.extend( { name: name }, format );
823 macro_format_list.sort( function( a, b ) {
824 return a.description.localeCompare(b.description);
826 $.each( macro_format_list, function() {
827 $('#macro-format').append( '<option value="' + this.name + '">' + this.description + '</option>' );
831 $( '#save-record, #save-dropdown a' ).click( function() {
832 $( '#save-record' ).find('i').attr( 'class', 'icon-loading' ).siblings( 'span' ).text( _("Saving...") );
834 function finishCb(result) {
835 if ( result.error == 'syntax' ) {
836 humanMsg.displayAlert( _("Incorrect syntax, cannot save"), { className: 'humanError' } );
837 } else if ( result.error == 'invalid' ) {
838 humanMsg.displayAlert( _("Record structure invalid, cannot save"), { className: 'humanError' } );
839 } else if ( !result.error ) {
840 humanMsg.displayAlert( _("Record saved "), { className: 'humanSuccess' } );
843 $.each( result.errors || [], function( undef, error ) {
844 switch ( error.type ) {
846 editor.addError( error.line, _("Invalid tag number") );
849 editor.addError( error.line, _("Invalid indicators") );
852 editor.addError( error.line, _("Tag has no subfields") );
855 editor.addError( null, _("Missing mandatory tag: ") + error.tag );
857 case 'missingSubfield':
858 if ( error.subfield == '@' ) {
859 editor.addError( error.line, _("Missing control field contents") );
861 editor.addError( error.line, _("Missing mandatory subfield: ‡") + error.subfield );
864 case 'unrepeatableTag':
865 editor.addError( error.line, _("Tag ") + error.tag + _(" cannot be repeated") );
867 case 'unrepeatableSubfield':
868 editor.addError( error.line, _("Subfield ‡") + error.subfield + _(" cannot be repeated") );
870 case 'itemTagUnsupported':
871 editor.addError( error.line, _("Item tags cannot currently be saved") );
876 $( '#save-record' ).find('i').attr( 'class', 'icon-hdd' );
878 if ( result.error ) {
879 // Reset backend info
880 setSource( [ state.backend, state.recordID ] );
884 var backend = $( this ).data( 'backend' ) || ( state.saveBackend );
885 if ( state.backend == backend ) {
886 saveRecord( backend + '/' + state.recordID, editor, finishCb );
888 saveRecord( backend + '/', editor, finishCb );
894 $('#import-records').click( function() {
895 $('#import-records-input')
897 .change( function() {
898 if ( !this.files || !this.files.length ) return;
900 var file = this.files[0];
901 var reader = new FileReader();
903 reader.onload = function() {
904 var record = new MARC.Record();
906 if ( /\.(mrc|marc|iso|iso2709|marcstd)$/.test( file.name ) ) {
907 record.loadISO2709( reader.result );
908 } else if ( /\.(xml|marcxml)$/.test( file.name ) ) {
909 record.loadMARCXML( reader.result );
911 humanMsg.displayAlert( _("Unknown record type, cannot import"), { className: 'humanError' } );
915 if (record.marc8_corrupted) humanMsg.displayMsg( '<h3>' + _("Possible record corruption") + '</h3><p>' + _("Record not marked as UTF-8, may be corrupted") + '</p>', { className: 'humanError' } );
917 editor.displayRecord( record );
920 reader.readAsText( file );
927 $('#open-macros').click( function() {
928 $('#macro-ui').modal('show');
933 $('#run-macro').click( function() {
934 var result = Macros.Run( editor, $('#macro-format').val(), macroEditor.getValue() );
936 if ( !result.errors.length ) {
937 $('#macro-ui').modal('hide');
942 $.each( result.errors, function() {
943 var error = '<b>' + _("Line ") + (this.line + 1) + ':</b> ';
945 switch ( this.error ) {
946 case 'failed': error += _("failed to run"); break;
947 case 'unrecognized': error += _("unrecognized command"); break;
953 humanMsg.displayMsg( '<h3>' + _("Failed to run macro:") + '</h3><ul><li>' + errors.join('</li><li>') + '</li></ul>', { className: 'humanError' } );
958 $('#delete-macro').click( function() {
959 if ( !macroEditor.activeMacro || !confirm( _("Are you sure you want to delete this macro?") ) ) return;
961 storeMacro( macroEditor.activeMacro, undefined );
963 loadMacro( undefined );
968 $( '#switch-editor' ).click( function() {
969 if ( !confirm( _("Any changes will not be saved. Continue?") ) ) return;
971 $.cookie( 'catalogue_editor_[% USER_INFO.borrowernumber %]', 'basic', { expires: 365, path: '/' } );
973 if ( state.backend == 'catalog' ) {
974 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=' + state.recordID;
975 } else if ( state.backend == 'new' ) {
976 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl';
978 humanMsg.displayAlert( _("Cannot open this record in the basic editor"), { className: 'humanError' } );
982 $( '#show-advanced-search' ).click( function() {
983 showAdvancedSearch();
988 $('#advanced-search').submit( function() {
989 startAdvancedSearch();
994 $( document ).on( 'click', 'a.search-nav', function() {
995 if ( Search.Fetch( { offset: $( this ).data( 'offset' ) } ) ) {
996 $("#search-overlay").show();
1002 $( document ).on( 'click', 'th[data-sort-label]', function() {
1005 if ( $( this ).hasClass( 'sorting_asc' ) ) {
1011 if ( Search.Fetch( { sort_key: $( this ).data( 'sort-label' ), sort_direction: direction } ) ) {
1012 showSearchSorting( $( this ).data( 'sort-label' ), direction );
1014 $("#search-overlay").show();
1020 $( document ).on( 'change', 'input.search-toggle-server', function() {
1021 var server = z3950Servers[ $( this ).closest('li').data('server-id') ];
1022 server.checked = this.checked;
1024 if ( $('#search-results-ui').is( ':visible' ) && Search.Fetch() ) {
1025 $("#search-overlay").show();
1033 $("#advanced-search-ui, #search-results-ui, #macro-ui").each( function() {
1034 $(this).modal({ show: false });
1037 var $quicksearch = $('#quicksearch fieldset');
1038 $('<div id="quicksearch-overlay"><h3>' + _("Search unavailable") + '</h3> <p></p></div>').css({
1039 position: 'absolute',
1040 top: $quicksearch.offset().top,
1041 left: $quicksearch.offset().left,
1042 height: $quicksearch.outerHeight(),
1043 width: $quicksearch.outerWidth(),
1044 }).appendTo(document.body).hide();
1046 var prevAlerts = [];
1047 humanMsg.logMsg = function(msg, options) {
1048 $('#show-alerts').popover('hide');
1049 prevAlerts.unshift('<li>' + msg + '</li>');
1050 prevAlerts.splice(5, 999); // Truncate old messages
1053 $('#show-alerts').popover({
1055 placement: 'bottom',
1056 content: function() {
1057 return '<div id="alerts-container"><ul>' + prevAlerts.join('') + '</ul></div>';
1061 $('#show-shortcuts').popover({
1063 placement: 'bottom',
1064 content: function() {
1065 return '<div id="shortcuts-container">' + $('#shortcuts-contents').html() + '</div>';
1069 $('#new-record' ).click( function() {
1070 if ( editor.modified && !confirm( _("Are you sure you want to erase your changes?") ) ) return;
1072 openRecord( 'new/', editor );
1077 Preferences.Load( [% USER_INFO.borrowernumber || 0 %] );
1078 displayPreferences(editor);
1079 makeAuthorisedValueWidgets( '' );
1082 onresults: function(data) { showSearchResults( editor, data ) },
1083 onerror: handleSearchError,
1086 function finishCb( data ) {
1087 if ( data.error ) openRecord( 'new/', editor, finishCb );
1089 Resources.GetAll().done( function() {
1090 $("#loading").hide();
1091 $( window ).resize( onResize ).resize();
1096 if ( "[% auth_forwarded_hash %]" ) {
1097 document.location.hash = "[% auth_forwarded_hash %]";
1100 if ( !document.location.hash || !openRecord( document.location.hash.slice(1), editor, finishCb ) ) {
1101 openRecord( 'new/', editor, finishCb );