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 var val = this.getFixed( start, end );
68 var $collapsed = $( '<span class="fixed-collapsed" title="' + $node.attr('title') + '">' + val + '</span>' ).insertAfter( $node );
72 $node.val( widget.getFixed( start, end ).replace(/\s+$/, '') );
79 var val = $node.val();
80 $collapsed.text( Widget.PadStringRight( val === null ? '' : val, end - start ) ).show();
83 $node.on( 'change keyup', function() {
84 widget.setFixed( start, end, $node.val(), '+input' );
85 } ).focus( show ).blur( hide );
89 $collapsed.click( show );
92 getFixed: function( start, end ) {
93 return this.text.substring( start, end );
96 setFixed: function( start, end, value, source ) {
97 if ( null === value ) {
100 this.setText( this.text.substring( 0, start ) + Widget.PadStringRight( value.toString().substr( 0, end - start ), end - start ) + this.text.substring( end ), source );
103 setText: function( text, source ) {
104 if ( source == '+input' ) this.mark.doc.cm.addLineClass( this.mark.find().from.line, 'wrapper', 'modified-line' );
106 this.editor.startNotify();
109 createFromXML: function( resourceId ) {
112 Resources[resourceId].done( function( xml ) {
113 $(widget.node).find('.widget-loading').remove();
114 var $matSelect = $('<select class="material-select"></select>').appendTo(widget.node);
115 var $contents = $('<span class="material-contents"/>').appendTo(widget.node);
116 var materialInfo = {};
118 $('Tagfield', xml).children('Material').each( function() {
119 $matSelect.append( '<option value="' + $(this).attr('id') + '">' + $(this).attr('id') + ' - ' + $(this).children('name').text() + '</option>' );
121 materialInfo[ $(this).attr('id') ] = this;
124 if (widget.getMaterial) {
125 const material = widget.getMaterial();
127 $matSelect.val(material);
131 $matSelect.change( function() {
132 widget.loadXMLMaterial( materialInfo[ $matSelect.val() ] );
133 widget.nodeChanged();
138 loadXMLMaterial: function( materialInfo ) {
139 var $contents = $(this.node).children('.material-contents');
144 $(materialInfo).children('Position').each( function() {
145 var match = $(this).attr('pos').match(/(\d+)(?:-(\d+))?/);
148 var start = parseInt(match[1]);
149 var end = ( match[2] ? parseInt(match[2]) : start ) + 1;
151 var $values = $(this).children('Value');
153 if ($values.length == 0) {
154 $contents.append( '<span title="' + $(this).children('name').text() + '">' + widget.getFixed(start, end) + '</span>' );
159 $input = $( '<input name="f' + Widget.PadNum(start, 2) + '" title="' + $(this).children('name').text() + '" maxlength="' + (end - start) + '" />' );
161 $input = $( '<select name="f' + Widget.PadNum(start, 2) + '" title="' + $(this).children('name').text() + '"></select>' );
163 $values.each( function() {
164 $input.append( '<option value="' + $(this).attr('code') + '">' + $(this).attr('code') + ' - ' + $(this).children('description').text() + '</option>' );
168 $contents.append( $input );
169 widget.bindFixed( $input, start, end );
173 nodeChanged: function() {
177 var $inputs = $(this.node).find('input, select');
178 if ( !$inputs.length ) return;
180 $inputs.off('keydown.marc-tab');
181 var editor = widget.editor;
183 $inputs.each( function( i ) {
184 $(this).on( 'keydown.marc-tab', function( e ) {
185 // Handle tab/shift-tab
186 if ( e.which != 9 ) { // 9 = Tab
187 // Cheap hack to disable backspace and special keys
194 var span = widget.mark.find();
195 var cur = editor.cm.getCursor();
199 var $input = $inputs.eq( i - 1 );
200 if ( $input.is( ':visible' ) ) {
203 $input.next('span').click();
206 editor.cm.setCursor( span.from );
208 editor.cm.options.extraKeys['Shift-Tab']( editor.cm );
212 if ( i < $inputs.length - 1 ) {
213 var $input = $inputs.eq( i + 1 );
214 if ( $input.is( ':visible' ) ) {
217 $input.next('span').click();
220 editor.cm.setCursor( span.to );
231 insertTemplate: function( sel ) {
232 var wsOnly = /^\s*$/;
233 $( sel ).contents().clone().each( function() {
234 if ( this.nodeType == Node.TEXT_NODE ) {
235 this.data = this.data.replace( /^\s+|\s+$/g, '' );
237 } ).appendTo( this.node );
241 ActivateAt: function( editor, cur, idx ) {
242 var marks = editor.findMarksAt( cur );
243 if ( !marks.length || !marks[0].widget ) return false;
245 var $input = $(marks[0].widget.node).find('input, select').eq(idx || 0);
246 if ( !$input.length ) return false;
252 Notify: function( editor ) {
253 $.each( editor.cm.getAllMarks(), function( undef, mark ) {
254 if ( mark.widget && mark.widget.notify ) mark.widget.notify();
258 UpdateLine: function( editor, line ) {
259 var info = editor.getLineInfo( { line: line, ch: 0 } );
260 var lineh = editor.cm.getLineHandle( line );
261 if ( !lineh ) return;
264 if ( lineh.markedSpans ) {
265 $.each( lineh.markedSpans, function ( undef, span ) {
266 var mark = span.marker;
267 if ( !mark.widget ) return;
269 mark.widget.clearToText();
277 var end = editor.cm.getLine( line ).length;
278 if ( info.tagNumber < '010' ) {
279 if ( end >= 4 ) subfields.push( { code: '@', from: 4, to: end } );
281 for ( var i = 0; i < info.subfields.length; i++ ) {
282 var next = ( i < info.subfields.length - 1 ) ? info.subfields[i + 1].ch : end;
283 subfields.push( { code: info.subfields[i].code, from: info.subfields[i].ch + 2, to: next } );
285 // If not a fixed field, and we didn't find any subfields, we need to throw in the
286 // '@' subfield so we can properly remove it
287 if ( subfields.length == 0 ) subfields.push( { code: '@', from: 4, to: end } );
290 $.each( subfields, function ( undef, subfield ) {
291 var id = info.tagNumber + subfield.code;
292 var marks = editor.cm.findMarksAt( { line: line, ch: subfield.from } );
294 if ( marks.length ) {
295 if ( marks[0].id == id ) {
297 } else if ( marks[0].widget ) {
298 marks[0].widget.clearToText();
302 if ( !_widgets[id] ) return;
303 var fullBase = $.extend( Object.create( Widget.Base ), _widgets[id] );
304 var widget = Object.create( fullBase );
306 if ( subfield.from == subfield.to ) {
307 editor.cm.replaceRange( widget.makeTemplate ? widget.makeTemplate() : '<empty>', { line: line, ch: subfield.from }, null, 'marcWidgetPrefill' );
308 return; // We'll do the actual work when the change event is triggered again
311 var text = editor.cm.getRange( { line: line, ch: subfield.from }, { line: line, ch: subfield.to } );
314 var node = widget.init();
316 var mark = editor.cm.markText( { line: line, ch: subfield.from }, { line: line, ch: subfield.to }, {
318 inclusiveLeft: false,
319 inclusiveRight: false,
324 mark.widget = widget;
328 widget.editor = editor;
330 if ( widget.postCreate ) {
334 widget.nodeChanged();