2 * Copyright 2015 ByWater Solutions
4 * This file is part of Koha.
6 * Koha is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
11 * Koha is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with Koha; if not, see <http://www.gnu.org/licenses>.
20 define( [ 'resources' ], function( Resources ) {
24 Register: function( tagfield, widget ) {
25 _widgets[tagfield] = widget;
28 PadNum: function( number, length ) {
29 var result = number.toString();
31 while ( result.length < length ) result = '0' + result;
36 PadString: function( result, length ) {
37 while ( result.length < length ) result = ' ' + result;
42 PadStringRight: function( result, length ) {
44 while ( result.length < length ) result += ' ';
51 clearToText: function() {
52 var range = this.mark.find();
53 if ( this.text == null ) throw new Error('Tried to clear widget with no text');
54 this.mark.doc.replaceRange( this.text, range.from, range.to, 'widget.clearToText' );
57 reCreate: function() {
58 this.postCreate( this.node, this.mark );
62 bindFixed: function( sel, start, end ) {
63 var $node = $( this.node ).find( sel );
64 $node.val( this.getFixed( start, end ) );
67 var $collapsed = $( '<span class="fixed-collapsed" title="' + $node.attr('title') + '">' + $node.val() + '</span>' ).insertAfter( $node );
71 $node.val( widget.getFixed( start, end ).replace(/\s+$/, '') );
78 $collapsed.text( Widget.PadStringRight( $node.val(), end - start ) ).show();
81 $node.on( 'change keyup', function() {
82 widget.setFixed( start, end, $node.val(), '+input' );
83 } ).focus( show ).blur( hide );
87 $collapsed.click( show );
90 getFixed: function( start, end ) {
91 return this.text.substring( start, end );
94 setFixed: function( start, end, value, source ) {
95 this.setText( this.text.substring( 0, start ) + Widget.PadStringRight( value.toString().substr( 0, end - start ), end - start ) + this.text.substring( end ), source );
98 setText: function( text, source ) {
99 if ( source == '+input' ) this.mark.doc.cm.addLineClass( this.mark.find().from.line, 'wrapper', 'modified-line' );
101 this.editor.startNotify();
104 createFromXML: function( resourceId ) {
107 Resources[resourceId].done( function( xml ) {
108 $(widget.node).find('.widget-loading').remove();
109 var $matSelect = $('<select class="material-select"></select>').appendTo(widget.node);
110 var $contents = $('<span class="material-contents"/>').appendTo(widget.node);
111 var materialInfo = {};
113 $('Tagfield', xml).children('Material').each( function() {
114 $matSelect.append( '<option value="' + $(this).attr('id') + '">' + $(this).attr('id') + ' - ' + $(this).children('name').text() + '</option>' );
116 materialInfo[ $(this).attr('id') ] = this;
119 $matSelect.change( function() {
120 widget.loadXMLMaterial( materialInfo[ $matSelect.val() ] );
125 loadXMLMaterial: function( materialInfo ) {
126 var $contents = $(this.node).children('.material-contents');
131 $(materialInfo).children('Position').each( function() {
132 var match = $(this).attr('pos').match(/(\d+)(?:-(\d+))?/);
135 var start = parseInt(match[1]);
136 var end = ( match[2] ? parseInt(match[2]) : start ) + 1;
138 var $values = $(this).children('Value');
140 if ($values.length == 0) {
141 $contents.append( '<span title="' + $(this).children('name').text() + '">' + widget.getFixed(start, end) + '</span>' );
146 $input = $( '<input name="f' + Widget.PadNum(start, 2) + '" title="' + $(this).children('name').text() + '" maxlength="' + (end - start) + '" />' );
148 $input = $( '<select name="f' + Widget.PadNum(start, 2) + '" title="' + $(this).children('name').text() + '"></select>' );
150 $values.each( function() {
151 $input.append( '<option value="' + $(this).attr('code') + '">' + $(this).attr('code') + ' - ' + $(this).children('description').text() + '</option>' );
155 $contents.append( $input );
156 widget.bindFixed( $input, start, end );
160 nodeChanged: function() {
164 var $inputs = $(this.node).find('input, select');
165 if ( !$inputs.length ) return;
167 $inputs.off('keydown.marc-tab');
168 var editor = widget.editor;
170 $inputs.each( function( i ) {
171 $(this).on( 'keydown.marc-tab', function( e ) {
172 // Cheap hack to disable backspace and special keys
173 if ( ( this.nodeName.toLowerCase() == 'select' && e.which == 9 ) || e.ctrlKey ) {
176 } else if ( e.which != 9 ) { // Tab
180 var span = widget.mark.find();
181 var cur = editor.cm.getCursor();
185 $inputs.eq(i - 1).trigger( 'focus' );
187 editor.cm.setCursor( span.from );
189 editor.cm.options.extraKeys['Shift-Tab']( editor.cm );
193 if ( i < $inputs.length - 1 ) {
194 $inputs.eq(i + 1).trigger( 'focus' );
196 editor.cm.setCursor( span.to );
207 insertTemplate: function( sel ) {
208 var wsOnly = /^\s*$/;
209 $( sel ).contents().clone().each( function() {
210 if ( this.nodeType == Node.TEXT_NODE ) {
211 this.data = this.data.replace( /^\s+|\s+$/g, '' );
213 } ).appendTo( this.node );
217 ActivateAt: function( editor, cur, idx ) {
218 var marks = editor.findMarksAt( cur );
219 if ( !marks.length || !marks[0].widget ) return false;
221 var $input = $(marks[0].widget.node).find('input, select').eq(idx || 0);
222 if ( !$input.length ) return false;
228 Notify: function( editor ) {
229 $.each( editor.cm.getAllMarks(), function( undef, mark ) {
230 if ( mark.widget && mark.widget.notify ) mark.widget.notify();
234 UpdateLine: function( editor, line ) {
235 var info = editor.getLineInfo( { line: line, ch: 0 } );
236 var lineh = editor.cm.getLineHandle( line );
237 if ( !lineh ) return;
240 if ( lineh.markedSpans ) {
241 $.each( lineh.markedSpans, function ( undef, span ) {
242 var mark = span.marker;
243 if ( !mark.widget ) return;
245 mark.widget.clearToText();
253 var end = editor.cm.getLine( line ).length;
254 if ( info.tagNumber < '010' ) {
255 if ( end >= 4 ) subfields.push( { code: '@', from: 4, to: end } );
257 for ( var i = 0; i < info.subfields.length; i++ ) {
258 var next = ( i < info.subfields.length - 1 ) ? info.subfields[i + 1].ch : end;
259 subfields.push( { code: info.subfields[i].code, from: info.subfields[i].ch + 2, to: next } );
261 // If not a fixed field, and we didn't find any subfields, we need to throw in the
262 // '@' subfield so we can properly remove it
263 if ( subfields.length == 0 ) subfields.push( { code: '@', from: 4, to: end } );
266 $.each( subfields, function ( undef, subfield ) {
267 var id = info.tagNumber + subfield.code;
268 var marks = editor.cm.findMarksAt( { line: line, ch: subfield.from } );
270 if ( marks.length ) {
271 if ( marks[0].id == id ) {
273 } else if ( marks[0].widget ) {
274 marks[0].widget.clearToText();
278 if ( !_widgets[id] ) return;
279 var fullBase = $.extend( Object.create( Widget.Base ), _widgets[id] );
280 var widget = Object.create( fullBase );
282 if ( subfield.from == subfield.to ) {
283 editor.cm.replaceRange( widget.makeTemplate ? widget.makeTemplate() : '<empty>', { line: line, ch: subfield.from }, null, 'marcWidgetPrefill' );
284 return; // We'll do the actual work when the change event is triggered again
287 var text = editor.cm.getRange( { line: line, ch: subfield.from }, { line: line, ch: subfield.to } );
290 var node = widget.init();
292 var mark = editor.cm.markText( { line: line, ch: subfield.from }, { line: line, ch: subfield.to }, {
294 inclusiveLeft: false,
295 inclusiveRight: false,
300 mark.widget = widget;
304 widget.editor = editor;
306 if ( widget.postCreate ) {
310 widget.nodeChanged();