3b2d66cec8
This should fix the first-character deletion bug reported by Nick, and also: * Fix subfield deletion * Attempted fix for special keys on dropdowns * Allow deleting empty fields * Fix bug with composed characters Signed-off-by: Nick Clemens <nick@quecheelibrary.org> Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de>
316 lines
12 KiB
JavaScript
316 lines
12 KiB
JavaScript
/**
|
|
* Copyright 2015 ByWater Solutions
|
|
*
|
|
* This file is part of Koha.
|
|
*
|
|
* Koha is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Koha is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Koha; if not, see <http://www.gnu.org/licenses>.
|
|
*/
|
|
|
|
define( [ 'resources' ], function( Resources ) {
|
|
var _widgets = {};
|
|
|
|
var Widget = {
|
|
Register: function( tagfield, widget ) {
|
|
_widgets[tagfield] = widget;
|
|
},
|
|
|
|
PadNum: function( number, length ) {
|
|
var result = number.toString();
|
|
|
|
while ( result.length < length ) result = '0' + result;
|
|
|
|
return result;
|
|
},
|
|
|
|
PadString: function( result, length ) {
|
|
while ( result.length < length ) result = ' ' + result;
|
|
|
|
return result;
|
|
},
|
|
|
|
PadStringRight: function( result, length ) {
|
|
result = '' + result;
|
|
while ( result.length < length ) result += ' ';
|
|
|
|
return result;
|
|
},
|
|
|
|
Base: {
|
|
// Marker utils
|
|
clearToText: function() {
|
|
var range = this.mark.find();
|
|
if ( this.text == null ) throw new Error('Tried to clear widget with no text');
|
|
this.mark.doc.replaceRange( this.text, range.from, range.to, 'widget.clearToText' );
|
|
},
|
|
|
|
reCreate: function() {
|
|
this.postCreate( this.node, this.mark );
|
|
},
|
|
|
|
// Fixed field utils
|
|
bindFixed: function( sel, start, end ) {
|
|
var $node = $( this.node ).find( sel );
|
|
$node.val( this.getFixed( start, end ) );
|
|
|
|
var widget = this;
|
|
var $collapsed = $( '<span class="fixed-collapsed" title="' + $node.attr('title') + '">' + $node.val() + '</span>' ).insertAfter( $node );
|
|
|
|
function show() {
|
|
$collapsed.hide();
|
|
$node.val( widget.getFixed( start, end ).replace(/\s+$/, '') );
|
|
$node.show();
|
|
$node[0].focus();
|
|
}
|
|
|
|
function hide() {
|
|
$node.hide();
|
|
$collapsed.text( Widget.PadStringRight( $node.val(), end - start ) ).show();
|
|
}
|
|
|
|
$node.on( 'change keyup', function() {
|
|
widget.setFixed( start, end, $node.val(), '+input' );
|
|
} ).focus( show ).blur( hide );
|
|
|
|
hide();
|
|
|
|
$collapsed.click( show );
|
|
},
|
|
|
|
getFixed: function( start, end ) {
|
|
return this.text.substring( start, end );
|
|
},
|
|
|
|
setFixed: function( start, end, value, source ) {
|
|
this.setText( this.text.substring( 0, start ) + Widget.PadStringRight( value.toString().substr( 0, end - start ), end - start ) + this.text.substring( end ), source );
|
|
},
|
|
|
|
setText: function( text, source ) {
|
|
if ( source == '+input' ) this.mark.doc.cm.addLineClass( this.mark.find().from.line, 'wrapper', 'modified-line' );
|
|
this.text = text;
|
|
this.editor.startNotify();
|
|
},
|
|
|
|
createFromXML: function( resourceId ) {
|
|
var widget = this;
|
|
|
|
Resources[resourceId].done( function( xml ) {
|
|
$(widget.node).find('.widget-loading').remove();
|
|
var $matSelect = $('<select class="material-select"></select>').appendTo(widget.node);
|
|
var $contents = $('<span class="material-contents"/>').appendTo(widget.node);
|
|
var materialInfo = {};
|
|
|
|
$('Tagfield', xml).children('Material').each( function() {
|
|
$matSelect.append( '<option value="' + $(this).attr('id') + '">' + $(this).attr('id') + ' - ' + $(this).children('name').text() + '</option>' );
|
|
|
|
materialInfo[ $(this).attr('id') ] = this;
|
|
} );
|
|
|
|
$matSelect.change( function() {
|
|
widget.loadXMLMaterial( materialInfo[ $matSelect.val() ] );
|
|
} ).change();
|
|
} );
|
|
},
|
|
|
|
loadXMLMaterial: function( materialInfo ) {
|
|
var $contents = $(this.node).children('.material-contents');
|
|
$contents.empty();
|
|
|
|
var widget = this;
|
|
|
|
$(materialInfo).children('Position').each( function() {
|
|
var match = $(this).attr('pos').match(/(\d+)(?:-(\d+))?/);
|
|
if (!match) return;
|
|
|
|
var start = parseInt(match[1]);
|
|
var end = ( match[2] ? parseInt(match[2]) : start ) + 1;
|
|
var $input;
|
|
var $values = $(this).children('Value');
|
|
|
|
if ($values.length == 0) {
|
|
$contents.append( '<span title="' + $(this).children('name').text() + '">' + widget.getFixed(start, end) + '</span>' );
|
|
return;
|
|
}
|
|
|
|
if ( match[2] ) {
|
|
$input = $( '<input name="f' + Widget.PadNum(start, 2) + '" title="' + $(this).children('name').text() + '" maxlength="' + (end - start) + '" />' );
|
|
} else {
|
|
$input = $( '<select name="f' + Widget.PadNum(start, 2) + '" title="' + $(this).children('name').text() + '"></select>' );
|
|
|
|
$values.each( function() {
|
|
$input.append( '<option value="' + $(this).attr('code') + '">' + $(this).attr('code') + ' - ' + $(this).children('description').text() + '</option>' );
|
|
} );
|
|
}
|
|
|
|
$contents.append( $input );
|
|
widget.bindFixed( $input, start, end );
|
|
} );
|
|
},
|
|
|
|
nodeChanged: function() {
|
|
this.mark.changed();
|
|
var widget = this;
|
|
|
|
var $inputs = $(this.node).find('input, select');
|
|
if ( !$inputs.length ) return;
|
|
|
|
$inputs.off('keydown.marc-tab');
|
|
var editor = widget.editor;
|
|
|
|
$inputs.each( function( i ) {
|
|
$(this).on( 'keydown.marc-tab', function( e ) {
|
|
// Cheap hack to disable backspace and special keys
|
|
if ( ( this.nodeName.toLowerCase() == 'select' && e.which == 9 ) || e.ctrlKey ) {
|
|
e.preventDefault();
|
|
return;
|
|
} else if ( e.which != 9 ) { // Tab
|
|
return;
|
|
}
|
|
|
|
var span = widget.mark.find();
|
|
var cur = editor.cm.getCursor();
|
|
|
|
if ( e.shiftKey ) {
|
|
if ( i > 0 ) {
|
|
$inputs.eq(i - 1).trigger( 'focus' );
|
|
} else {
|
|
editor.cm.setCursor( span.from );
|
|
// FIXME: ugly hack
|
|
editor.cm.options.extraKeys['Shift-Tab']( editor.cm );
|
|
editor.focus();
|
|
}
|
|
} else {
|
|
if ( i < $inputs.length - 1 ) {
|
|
$inputs.eq(i + 1).trigger( 'focus' );
|
|
} else {
|
|
editor.cm.setCursor( span.to );
|
|
editor.focus();
|
|
}
|
|
}
|
|
|
|
return false;
|
|
} );
|
|
} );
|
|
},
|
|
|
|
// Template utils
|
|
insertTemplate: function( sel ) {
|
|
var wsOnly = /^\s*$/;
|
|
$( sel ).contents().clone().each( function() {
|
|
if ( this.nodeType == Node.TEXT_NODE ) {
|
|
this.data = this.data.replace( /^\s+|\s+$/g, '' );
|
|
}
|
|
} ).appendTo( this.node );
|
|
},
|
|
},
|
|
|
|
ActivateAt: function( editor, cur, idx ) {
|
|
var marks = editor.findMarksAt( cur );
|
|
if ( !marks.length || !marks[0].widget ) return false;
|
|
|
|
var $input = $(marks[0].widget.node).find('input, select').eq(idx || 0);
|
|
if ( !$input.length ) return false;
|
|
|
|
$input.focus();
|
|
return true;
|
|
},
|
|
|
|
Notify: function( editor ) {
|
|
$.each( editor.cm.getAllMarks(), function( undef, mark ) {
|
|
if ( mark.widget && mark.widget.notify ) mark.widget.notify();
|
|
} );
|
|
},
|
|
|
|
UpdateLine: function( editor, line ) {
|
|
var info = editor.getLineInfo( { line: line, ch: 0 } );
|
|
var lineh = editor.cm.getLineHandle( line );
|
|
if ( !lineh ) return;
|
|
|
|
if ( !info ) {
|
|
if ( lineh.markedSpans ) {
|
|
$.each( lineh.markedSpans, function ( undef, span ) {
|
|
var mark = span.marker;
|
|
if ( !mark.widget ) return;
|
|
|
|
mark.widget.clearToText();
|
|
} );
|
|
}
|
|
return;
|
|
}
|
|
|
|
var subfields = [];
|
|
|
|
var end = editor.cm.getLine( line ).length;
|
|
if ( info.tagNumber < '010' ) {
|
|
if ( end >= 4 ) subfields.push( { code: '@', from: 4, to: end } );
|
|
} else {
|
|
for ( var i = 0; i < info.subfields.length; i++ ) {
|
|
var next = ( i < info.subfields.length - 1 ) ? info.subfields[i + 1].ch : end;
|
|
subfields.push( { code: info.subfields[i].code, from: info.subfields[i].ch + 2, to: next } );
|
|
}
|
|
// If not a fixed field, and we didn't find any subfields, we need to throw in the
|
|
// '@' subfield so we can properly remove it
|
|
if ( subfields.length == 0 ) subfields.push( { code: '@', from: 4, to: end } );
|
|
}
|
|
|
|
$.each( subfields, function ( undef, subfield ) {
|
|
var id = info.tagNumber + subfield.code;
|
|
var marks = editor.cm.findMarksAt( { line: line, ch: subfield.from } );
|
|
|
|
if ( marks.length ) {
|
|
if ( marks[0].id == id ) {
|
|
return;
|
|
} else if ( marks[0].widget ) {
|
|
marks[0].widget.clearToText();
|
|
}
|
|
}
|
|
|
|
if ( !_widgets[id] ) return;
|
|
var fullBase = $.extend( Object.create( Widget.Base ), _widgets[id] );
|
|
var widget = Object.create( fullBase );
|
|
|
|
if ( subfield.from == subfield.to ) {
|
|
editor.cm.replaceRange( widget.makeTemplate ? widget.makeTemplate() : '<empty>', { line: line, ch: subfield.from }, null, 'marcWidgetPrefill' );
|
|
return; // We'll do the actual work when the change event is triggered again
|
|
}
|
|
|
|
var text = editor.cm.getRange( { line: line, ch: subfield.from }, { line: line, ch: subfield.to } );
|
|
|
|
widget.text = text;
|
|
var node = widget.init();
|
|
|
|
var mark = editor.cm.markText( { line: line, ch: subfield.from }, { line: line, ch: subfield.to }, {
|
|
atomic: true,
|
|
inclusiveLeft: false,
|
|
inclusiveRight: false,
|
|
replacedWith: node,
|
|
} );
|
|
|
|
mark.id = id;
|
|
mark.widget = widget;
|
|
|
|
widget.node = node;
|
|
widget.mark = mark;
|
|
widget.editor = editor;
|
|
|
|
if ( widget.postCreate ) {
|
|
widget.postCreate();
|
|
}
|
|
|
|
widget.nodeChanged();
|
|
} );
|
|
},
|
|
};
|
|
|
|
return Widget;
|
|
} );
|