Bug 21049: Set 007/00 when selecting material type (Rancor)
[koha.git] / koha-tmpl / intranet-tmpl / lib / koha / cateditor / widget.js
1 /**
2  * Copyright 2015 ByWater Solutions
3  *
4  * This file is part of Koha.
5  *
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.
10  *
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.
15  *
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>.
18  */
19
20 define( [ 'resources' ], function( Resources ) {
21     var _widgets = {};
22
23     var Widget = {
24         Register: function( tagfield, widget ) {
25             _widgets[tagfield] = widget;
26         },
27
28         PadNum: function( number, length ) {
29             var result = number.toString();
30
31             while ( result.length < length ) result = '0' + result;
32
33             return result;
34         },
35
36         PadString: function( result, length ) {
37             while ( result.length < length ) result = ' ' + result;
38
39             return result;
40         },
41
42         PadStringRight: function( result, length ) {
43             result = '' + result;
44             while ( result.length < length ) result += ' ';
45
46             return result;
47         },
48
49         Base: {
50             // Marker utils
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' );
55             },
56
57             reCreate: function() {
58                 this.postCreate( this.node, this.mark );
59             },
60
61             // Fixed field utils
62             bindFixed: function( sel, start, end ) {
63                 var $node = $( this.node ).find( sel );
64                 var val = this.getFixed( start, end );
65                 $node.val( val );
66
67                 var widget = this;
68                 var $collapsed = $( '<span class="fixed-collapsed" title="' + $node.attr('title') + '">' + val + '</span>' ).insertAfter( $node );
69
70                 function show() {
71                     $collapsed.hide();
72                     $node.val( widget.getFixed( start, end ).replace(/\s+$/, '') );
73                     $node.show();
74                     $node[0].focus();
75                 }
76
77                 function hide() {
78                     $node.hide();
79                     var val = $node.val();
80                     $collapsed.text( Widget.PadStringRight( val === null ? '' : val, end - start ) ).show();
81                 }
82
83                 $node.on( 'change keyup', function() {
84                     widget.setFixed( start, end, $node.val(), '+input' );
85                 } ).focus( show ).blur( hide );
86
87                 hide();
88
89                 $collapsed.click( show );
90             },
91
92             getFixed: function( start, end ) {
93                 return this.text.substring( start, end );
94             },
95
96             setFixed: function( start, end, value, source ) {
97                 if ( null === value ) {
98                     value = '';
99                 }
100                 this.setText( this.text.substring( 0, start ) + Widget.PadStringRight( value.toString().substr( 0, end - start ), end - start ) + this.text.substring( end ), source );
101             },
102
103             setText: function( text, source ) {
104                 if ( source == '+input' ) this.mark.doc.cm.addLineClass( this.mark.find().from.line, 'wrapper', 'modified-line' );
105                 this.text = text;
106                 this.editor.startNotify();
107             },
108
109             createFromXML: function( resourceId ) {
110                 var widget = this;
111
112                 return 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 = {};
117
118                     $('Tagfield', xml).children('Material').each( function() {
119                         $matSelect.append( '<option value="' + $(this).attr('id') + '">' + $(this).attr('id') + ' - ' + $(this).children('name').text() + '</option>' );
120
121                         materialInfo[ $(this).attr('id') ] = this;
122                     } );
123
124                     if (widget.getMaterial) {
125                         const material = widget.getMaterial();
126                         if (material) {
127                             $matSelect.val(material);
128                         }
129                     }
130
131                     $matSelect.change( function() {
132                         widget.loadXMLMaterial( materialInfo[ $matSelect.val() ] );
133                         widget.nodeChanged();
134                     } ).change();
135                 } );
136             },
137
138             loadXMLMaterial: function( materialInfo ) {
139                 var $contents = $(this.node).children('.material-contents');
140                 $contents.empty();
141
142                 var widget = this;
143
144                 $(materialInfo).children('Position').each( function() {
145                     var match = $(this).attr('pos').match(/(\d+)(?:-(\d+))?/);
146                     if (!match) return;
147
148                     var start = parseInt(match[1]);
149                     var end = ( match[2] ? parseInt(match[2]) : start ) + 1;
150                     var $input;
151                     var $values = $(this).children('Value');
152
153                     if ($values.length == 0) {
154                         $contents.append( '<span title="' + $(this).children('name').text() + '">' + widget.getFixed(start, end) + '</span>' );
155                         return;
156                     }
157
158                     if ( match[2] ) {
159                         $input = $( '<input name="f' + Widget.PadNum(start, 2) + '" title="' + $(this).children('name').text() + '" maxlength="' + (end - start) + '" />' );
160                     } else {
161                         $input = $( '<select name="f' + Widget.PadNum(start, 2) + '" title="' + $(this).children('name').text() + '"></select>' );
162
163                         $values.each( function() {
164                             $input.append( '<option value="' + $(this).attr('code') + '">' + $(this).attr('code') + ' - ' + $(this).children('description').text() + '</option>' );
165                         } );
166                     }
167
168                     $contents.append( $input );
169                     widget.bindFixed( $input, start, end );
170                 } );
171             },
172
173             nodeChanged: function() {
174                 this.mark.changed();
175                 var widget = this;
176
177                 var $inputs = $(this.node).find('input, select');
178                 if ( !$inputs.length ) return;
179
180                 $inputs.off('keydown.marc-tab');
181                 var editor = widget.editor;
182
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
188                             if ( e.ctrlKey ) {
189                                 e.preventDefault();
190                             }
191                             return;
192                         }
193
194                         var span = widget.mark.find();
195                         var cur = editor.cm.getCursor();
196
197                         if ( e.shiftKey ) {
198                             if ( i > 0 ) {
199                                 var $input = $inputs.eq( i - 1 );
200                                 if ( $input.is( ':visible' ) ) {
201                                     $input.focus();
202                                 } else {
203                                     $input.next('span').click();
204                                 }
205                             } else {
206                                 editor.cm.setCursor( span.from );
207                                 // FIXME: ugly hack
208                                 editor.cm.options.extraKeys['Shift-Tab']( editor.cm );
209                                 editor.focus();
210                             }
211                         } else {
212                             if ( i < $inputs.length - 1 ) {
213                                 var $input = $inputs.eq( i + 1 );
214                                 if ( $input.is( ':visible' ) ) {
215                                     $input.focus();
216                                 } else {
217                                     $input.next('span').click();
218                                 }
219                             } else {
220                                 editor.cm.setCursor( span.to );
221                                 editor.focus();
222                             }
223                         }
224
225                         return false;
226                     } );
227                 } );
228             },
229
230             // Template utils
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, '' );
236                     }
237                 } ).appendTo( this.node );
238             },
239         },
240
241         ActivateAt: function( editor, cur, idx ) {
242             var marks = editor.findMarksAt( cur );
243             if ( !marks.length || !marks[0].widget ) return false;
244
245             var $input = $(marks[0].widget.node).find('input, select').eq(idx || 0);
246             if ( !$input.length ) return false;
247
248             $input.focus();
249             return true;
250         },
251
252         Notify: function( editor ) {
253             $.each( editor.cm.getAllMarks(), function( undef, mark ) {
254                 if ( mark.widget && mark.widget.notify ) mark.widget.notify();
255             } );
256         },
257
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;
262
263             if ( !info ) {
264                 if ( lineh.markedSpans ) {
265                     $.each( lineh.markedSpans, function ( undef, span ) {
266                         var mark = span.marker;
267                         if ( !mark.widget ) return;
268
269                         mark.widget.clearToText();
270                     } );
271                 }
272                 return;
273             }
274
275             var subfields = [];
276
277             var end = editor.cm.getLine( line ).length;
278             if ( info.tagNumber < '010' ) {
279                 if ( end >= 4 ) subfields.push( { code: '@', from: 4, to: end } );
280             } else {
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 } );
284                 }
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 } );
288             }
289
290             $.each( subfields, function ( undef, subfield ) {
291                 var id = info.tagNumber + subfield.code;
292                 var marks = editor.cm.findMarksAt( { line: line, ch: subfield.from } );
293
294                 if ( marks.length ) {
295                     if ( marks[0].id == id ) {
296                         return;
297                     } else if ( marks[0].widget ) {
298                         marks[0].widget.clearToText();
299                     }
300                 }
301
302                 if ( !_widgets[id] ) return;
303                 var fullBase = $.extend( Object.create( Widget.Base ), _widgets[id] );
304                 var widget = Object.create( fullBase );
305
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
309                 }
310
311                 var text = editor.cm.getRange( { line: line, ch: subfield.from }, { line: line, ch: subfield.to } );
312
313                 widget.text = text;
314                 var node = widget.init();
315
316                 var mark = editor.cm.markText( { line: line, ch: subfield.from }, { line: line, ch: subfield.to }, {
317                     atomic: true,
318                     inclusiveLeft: false,
319                     inclusiveRight: false,
320                     replacedWith: node,
321                 } );
322
323                 mark.id = id;
324                 mark.widget = widget;
325
326                 widget.node = node;
327                 widget.mark = mark;
328                 widget.editor = editor;
329
330                 if ( widget.postCreate ) {
331                     widget.postCreate();
332                 }
333
334                 widget.nodeChanged();
335             } );
336         },
337     };
338
339     return Widget;
340 } );