Bug 28812: (bug 7882 follow up) Copy all subfields in plugin editor
[koha.git] / koha-tmpl / intranet-tmpl / prog / js / cataloging.js
1 /* exported openAuth ExpandField CloneField CloneSubfield UnCloneField CloneItemSubfield CheckMandatorySubfields */
2
3 /*
4  * Unified file for catalogue edition
5  */
6
7 /* Functions developed for addbiblio.tt and authorities.tt */
8
9 // returns the fieldcode based upon tag div id
10 function getFieldCode(tagDivId){
11     // format : tag_<tagnumber>_...
12     return tagDivId.substr(3+1,3);
13 }
14
15 //returns the field and subfieldcode based upon subfield div id
16 function getFieldAndSubfieldCode(subfieldDivId){
17     // format : subfield<tagnumber><subfieldnumber>...
18     return subfieldDivId.substr(8,3+1);
19 }
20
21 //returns the subfieldcode based upon subfieldid writing
22 function getSubfieldCode(tagsubfieldid){
23     // 3 : tag +3 : tagnumber +4 : number of _ +8 subfield -1 begins at 0
24     return tagsubfieldid.substr(3+3+4+8-1,1);
25 }
26
27 // Take the base of tagsubfield information (removing the subfieldcodes and subfieldindexes)
28 // returns the filter
29 function getTagInputnameFilter(tagsubfieldid){
30     var tagsubfield=tagsubfieldid.substr(0,tagsubfieldid.lastIndexOf("_"));
31     var tagcode=tagsubfield.substr(tagsubfield.lastIndexOf("_"));
32     tagsubfield=tagsubfield.substr(0,tagsubfield.lastIndexOf("_"));
33     tagsubfield=tagsubfield.substr(0,tagsubfield.lastIndexOf("_"));
34     tagsubfield=tagsubfield+"_."+tagcode;
35     return tagsubfield;
36 }
37
38 // if source is "auth", we are editing an authority otherwise it is a biblio
39 function openAuth(tagsubfieldid,authtype,source) {
40     // let's take the base of tagsubfield information (removing the indexes and the codes
41     var element=document.getElementById(tagsubfieldid);
42     var tagsubfield=getTagInputnameFilter(tagsubfieldid);
43     var elementsubfcode=getSubfieldCode(element.name);
44     var mainmainstring=element.value;
45     var mainstring = new Array();
46
47     var inputs = element.parentNode.parentNode.parentNode.getElementsByTagName("input");
48     for (var myindex =0; myindex<inputs.length;myindex++){
49         if (inputs[myindex].name && inputs[myindex].name.match(tagsubfield)){
50             var subfieldcode=getSubfieldCode(inputs[myindex].name);
51             if (isNaN(parseInt(subfieldcode)) && inputs[myindex].value != "" && subfieldcode!=elementsubfcode){
52                 mainstring.push(inputs[myindex].value);
53             }
54         }
55     }
56     mainstring = mainstring.join(' ');
57     window.open("../authorities/auth_finder.pl?source="+source+"&authtypecode="+authtype+"&index="+tagsubfieldid+"&value_mainstr="+encodeURIComponent(mainmainstring)+"&value_main="+encodeURIComponent(mainstring), "_blank",'width=700,height=550,toolbar=false,scrollbars=yes');
58 }
59
60 function ExpandField(index) {
61     var original = document.getElementById(index); //original <li>
62     var lis = original.getElementsByTagName('li');
63     for(var i=0,lislen = lis.length ; i<lislen ; i++){   // foreach li
64         if(lis[i].hasAttribute('id') == 0 ) {continue; } // li element is specific to Select2
65         if(lis[i].getAttribute('id').match(/^subfield/)){  // if it s a subfield
66             if (!lis[i].style.display) {
67                 // first time => show all subfields
68                 lis[i].style.display = 'flex';
69             } else if (lis[i].style.display == 'none') {
70                 // show
71                 lis[i].style.display = 'flex';
72             } else {
73                 // hide
74                 lis[i].style.display = 'none';
75             }
76         }
77     }
78 }
79
80 var current_select2;
81 var Select2Utils = {
82     removeSelect2: function(selects) {
83         if ($.fn.select2) {
84             $(selects).each(function(){
85                 $(this).select2('destroy');
86             });
87         }
88     },
89
90     initSelect2: function(selects) {
91         if ($.fn.select2) {
92             if ( window.CAN_user_parameters_manage_auth_values === undefined || ! CAN_user_parameters_manage_auth_values ) {
93                 $(selects).select2().on("select2:clear", function () {
94                     $(this).on("select2:opening.cancelOpen", function (evt) {
95                         evt.preventDefault();
96                         $(this).off("select2:opening.cancelOpen");
97                     });
98                 });
99             } else {
100                 $(selects).each(function(){
101                     if ( !$(this).data("category") ) {
102                         $(this).select2().on("select2:clear", function () {
103                             $(this).on("select2:opening.cancelOpen", function (evt) {
104                                 evt.preventDefault();
105                                 $(this).off("select2:opening.cancelOpen");
106                             });
107                         });
108                     } else {
109                         $(this).select2({
110                             tags: true,
111                             createTag: function (tag) {
112                                 return {
113                                     id: tag.term,
114                                     text: tag.term,
115                                     newTag: true
116                                 };
117                             },
118                             templateResult: function(state) {
119                                 if (state.newTag) {
120                                     return state.text + " " + __("(select to create)");
121                                 }
122                                 return state.text;
123                             }
124                         }).on("select2:select", function(e) {
125                             if(e.params.data.newTag){
126                                 current_select2 = this;
127                                 var category = $(this).data("category");
128                                 $("#avCreate #new_av_category").html(category);
129                                 $("#avCreate input[name='category']").val(category);
130                                 $("#avCreate input[name='value']").val('');
131                                 $("#avCreate input[name='description']").val(e.params.data.text);
132
133                                 $(this).val($(this).find("option:first").val()).trigger('change');
134                                 $('#avCreate').modal({show:true});
135                             }
136                         }).on("select2:clear", function () {
137                             $(this).on("select2:opening.cancelOpen", function (evt) {
138                                 evt.preventDefault();
139
140                                 $(this).off("select2:opening.cancelOpen");
141                             });
142                         });
143                     }
144                 });
145             }
146         }
147     }
148 };
149
150 /**
151  * To clone a field
152  * @param hideMarc '0' for false, '1' for true
153  * @param advancedMARCEditor '0' for false, '1' for true
154  */
155 function CloneField(index, hideMarc, advancedMARCEditor) {
156     var original = document.getElementById(index); //original <li>
157     Select2Utils.removeSelect2($(original).find('select'));
158
159     var clone = original.cloneNode(true);
160     var new_key = CreateKey();
161     var new_id  = original.getAttribute('id')+new_key;
162
163     clone.setAttribute('id',new_id); // setting a new id for the parent li
164
165     var divs = Array.from(clone.getElementsByTagName('li')).concat(Array.from(clone.getElementsByTagName('div')));
166
167     // if hide_marc, indicators are hidden fields
168     // setting a new name for the new indicator
169     for(var i=0; i < 2; i++) {
170         var indicator = clone.getElementsByTagName('input')[i];
171         indicator.setAttribute('name',indicator.getAttribute('name')+new_key);
172     }
173
174     // settings all subfields
175     var divslen = divs.length;
176     for( i=0; i < divslen ; i++ ){      // foreach div/li
177         if( divs[i].getAttribute("id") && divs[i].getAttribute("id").match(/^subfield/)){  // if it s a subfield
178
179             // set the attribute for the new 'li' subfields
180             divs[i].setAttribute('id',divs[i].getAttribute('id')+new_key);
181
182             var inputs   = divs[i].getElementsByTagName('input');
183             var id_input = "";
184             var olddiv;
185             var oldcontrol;
186
187             for( j = 0 ; j < inputs.length ; j++ ) {
188                 if(inputs[j].getAttribute("id") && inputs[j].getAttribute("id").match(/^tag_/) ){
189                     inputs[j].value = "";
190
191                     //Remove the color added by the automatic linker
192                     $(inputs[j]).removeClass("matching_authority_field no_matching_authority_field");
193                 }
194             }
195             var textareas = divs[i].getElementsByTagName('textarea');
196             for( j = 0 ; j < textareas.length ; j++ ) {
197                 if(textareas[j].getAttribute("id") && textareas[j].getAttribute("id").match(/^tag_/) ){
198                     textareas[j].value = "";
199                 }
200             }
201             // Remove the status icons added by the automatic linker
202             $(divs[i]).find('.subfield_status').remove();
203             if( inputs.length > 0 ){
204                 inputs[0].setAttribute('id',inputs[0].getAttribute('id')+new_key);
205                 inputs[0].setAttribute('name',inputs[0].getAttribute('name')+new_key);
206
207                 try {
208                     id_input = inputs[1].getAttribute('id')+new_key;
209                     inputs[1].setAttribute('id',id_input);
210                     inputs[1].setAttribute('name',inputs[1].getAttribute('name')+new_key);
211                 } catch(e) {
212                     try{ // it s a select if it is not an input
213                         var selects = divs[i].getElementsByTagName('select');
214                         id_input = selects[0].getAttribute('id')+new_key;
215                         selects[0].setAttribute('id',id_input);
216                         selects[0].setAttribute('name',selects[0].getAttribute('name')+new_key);
217                     }catch(e2){ // it is a textarea if it s not a select or an input
218                         var textareas = divs[i].getElementsByTagName('textarea');
219                         if( textareas.length > 0 ){
220                             id_input = textareas[0].getAttribute('id')+new_key;
221                             textareas[0].setAttribute('id',id_input);
222                             textareas[0].setAttribute('name',textareas[0].getAttribute('name')+new_key);
223                         }
224                     }
225                 }
226                 if( $(inputs[1]).hasClass('framework_plugin') ) {
227                     olddiv= original.getElementsByTagName('li')[i];
228                     oldcontrol= olddiv.getElementsByTagName('input')[1];
229                     AddEventHandlers( oldcontrol,inputs[1],id_input );
230                 }
231             }
232             // when cloning a subfield, re set its label too.
233             try {
234                 var labels = divs[i].getElementsByTagName('label');
235                 labels[0].setAttribute('for', id_input);
236             }
237             catch(e) {
238                 // do nothing if label does not exist.
239             }
240
241             // setting its '+' and '-' buttons
242             try {
243                 var anchors = divs[i].getElementsByTagName('a');
244                 for (var j = 0; j < anchors.length; j++) {
245                     if(anchors[j].getAttribute('class') == 'buttonPlus'){
246                         anchors[j].setAttribute('onclick',"CloneSubfield('" + divs[i].getAttribute('id') + "','" + advancedMARCEditor + "'); return false;");
247                     } else if (anchors[j].getAttribute('class') == 'buttonMinus') {
248                         anchors[j].setAttribute('onclick',"UnCloneField('" + divs[i].getAttribute('id') + "'); return false;");
249                     }
250                 }
251             }
252             catch(e){
253                 // do nothig if ButtonPlus & CloneButtonPlus don t exist.
254             }
255
256             // button ...
257             var spans=0;
258             try {
259                 spans = divs[i].getElementsByTagName('a');
260             } catch(e) {
261                 // no spans
262             }
263             if(spans){
264                 var buttonDot;
265                 if(!CloneButtonPlus){ // it s impossible to have  + ... (buttonDot AND buttonPlus)
266                     buttonDot = spans[0];
267                     if(buttonDot){
268                         // 2 possibilities :
269                         try{
270                             if( $(buttonDot).hasClass('framework_plugin') ) {
271                                 olddiv= original.getElementsByTagName('li')[i];
272                                 oldcontrol= olddiv.getElementsByTagName('a')[0];
273                                 AddEventHandlers(oldcontrol,buttonDot,id_input);
274                             }
275                             try {
276                                 // do not copy the script section.
277                                 var script = spans[0].getElementsByTagName('script')[0];
278                                 spans[0].removeChild(script);
279                             } catch(e) {
280                                 // do nothing if there is no script
281                             }
282                         } catch(e){
283                             //
284                         }
285                     }
286                 }
287             }
288
289         } else { // it's a indicator div
290             if ( divs[i].getAttribute("id") && divs[i].getAttribute('id').match(/^div_indicator/)) {
291
292                 // setting a new id for the indicator div
293                 divs[i].setAttribute('id',divs[i].getAttribute('id')+new_key);
294
295                 inputs = divs[i].getElementsByTagName('input');
296                 inputs[0].setAttribute('id',inputs[0].getAttribute('id')+new_key);
297                 inputs[1].setAttribute('id',inputs[1].getAttribute('id')+new_key);
298
299                 var CloneButtonPlus;
300                 try {
301                     anchors = divs[i].getElementsByTagName('a');
302                     for ( j = 0; j < anchors.length; j++) {
303                         if (anchors[j].getAttribute('class') == 'buttonPlus') {
304                             anchors[j].setAttribute('onclick',"CloneField('" + new_id + "','" + hideMarc + "','" + advancedMARCEditor + "'); return false;");
305                         } else if (anchors[j].getAttribute('class') == 'buttonMinus') {
306                             anchors[j].setAttribute('onclick',"UnCloneField('" + new_id + "'); return false;");
307                         } else if (anchors[j].getAttribute('class') == 'expandfield') {
308                             anchors[j].setAttribute('onclick',"ExpandField('" + new_id + "'); return false;");
309                         }
310                     }
311                 }
312                 catch(e){
313                     // do nothig CloneButtonPlus doesn't exist.
314                 }
315
316             }
317         }
318     }
319
320     // insert this line on the page
321     original.parentNode.insertBefore(clone,original.nextSibling);
322
323     $("ul.sortable_subfield", clone).sortable();
324
325     Select2Utils.initSelect2($(original).find('select'));
326     Select2Utils.initSelect2($(clone).find('select'));
327 }
328
329
330 /**
331  * To clone a subfield
332  * @param index
333  * @param advancedMARCEditor '0' for false, '1' for true
334  */
335 function CloneSubfield(index, advancedMARCEditor){
336     var original = document.getElementById(index); //original <div>
337     Select2Utils.removeSelect2($(original).find('select'));
338     var clone = original.cloneNode(true);
339     var new_key = CreateKey();
340     // set the attribute for the new 'li' subfields
341     var inputs     = clone.getElementsByTagName('input');
342     var selects    = clone.getElementsByTagName('select');
343     var textareas  = clone.getElementsByTagName('textarea');
344     var linkid;
345     var oldcontrol;
346
347     // input
348     var id_input = "";
349     for(var i=0,len=inputs.length; i<len ; i++ ){
350         id_input = inputs[i].getAttribute('id')+new_key;
351         inputs[i].setAttribute('id',id_input);
352         inputs[i].setAttribute('name',inputs[i].getAttribute('name')+new_key);
353         if(inputs[i].getAttribute("id") && inputs[i].getAttribute("id").match(/^tag_/) ){
354             inputs[i].value = "";
355         }
356         linkid = id_input;
357     }
358
359     // Plugin input
360     if( $(inputs[1]).hasClass('framework_plugin') ) {
361         oldcontrol= original.getElementsByTagName('input')[1];
362         AddEventHandlers( oldcontrol, inputs[1], linkid );
363     }
364
365     // select
366     for(i=0,len=selects.length; i<len ; i++ ){
367         id_input = selects[i].getAttribute('id')+new_key;
368         selects[i].setAttribute('id',selects[i].getAttribute('id')+new_key);
369         selects[i].setAttribute('name',selects[i].getAttribute('name')+new_key);
370         linkid = id_input;
371     }
372
373     // textarea
374     for( i=0,len=textareas.length; i<len ; i++ ){
375         id_input = textareas[i].getAttribute('id')+new_key;
376         textareas[i].setAttribute('id',textareas[i].getAttribute('id')+new_key);
377         textareas[i].setAttribute('name',textareas[i].getAttribute('name')+new_key);
378         if(textareas[i].getAttribute("id") && textareas[i].getAttribute("id").match(/^tag_/) ){
379             textareas[i].value = "";
380         }
381         linkid = id_input;
382     }
383
384     // Handle click event on buttonDot for plugin
385     var links  = clone.getElementsByTagName('a');
386     if( $(links[0]).hasClass('framework_plugin') ) {
387         oldcontrol= original.getElementsByTagName('a')[0];
388         AddEventHandlers( oldcontrol, links[0], linkid );
389     }
390
391     if(advancedMARCEditor == '0') {
392         // when cloning a subfield, reset its label too.
393         var label = clone.getElementsByTagName('label')[0];
394         if( label ){
395             label.setAttribute('for',id_input);
396         }
397     }
398
399     // setting a new id for the parent div
400     var new_id  = original.getAttribute('id')+new_key;
401     clone.setAttribute('id',new_id);
402
403     try {
404         var anchors = clone.getElementsByTagName('a');
405         if(anchors.length){
406             for( i = 0 ,len = anchors.length ; i < len ; i++){
407                 if(anchors[i].getAttribute('class') == 'buttonPlus'){
408                     anchors[i].setAttribute('onclick',"CloneSubfield('" + new_id + "','" + advancedMARCEditor + "'); return false;");
409                 } else if (anchors[i].getAttribute('class') == 'buttonMinus') {
410                     anchors[i].setAttribute('onclick',"UnCloneField('" + new_id + "'); return false;");
411                 }
412             }
413         }
414     }
415     catch(e){
416         // do nothig if ButtonPlus & CloneButtonPlus don't exist.
417     }
418     // insert this line on the page
419     original.parentNode.insertBefore(clone,original.nextSibling);
420
421     //Restablish select2 for the cloned elements.
422     Select2Utils.initSelect2($(original).find('select'));
423     Select2Utils.initSelect2($(clone).find('select'));
424
425     // delete data of cloned subfield
426     clone.querySelectorAll('input.input_marceditor').value = "";
427 }
428
429 function AddEventHandlers (oldcontrol, newcontrol, newinputid ) {
430 // This function is a helper for CloneField and CloneSubfield.
431 // It adds the event handlers from oldcontrol to newcontrol.
432 // newinputid is the id attribute of the cloned controlling input field
433 // Note: This code depends on the jQuery data for events; this structure
434 // is moved to _data as of jQuery 1.8.
435     var ev= $(oldcontrol).data('events');
436     if(typeof ev != 'undefined') {
437         $.each(ev, function(prop,val) {
438             $.each(val, function(prop2,val2) {
439                 $(newcontrol).off( val2.type );
440                 $(newcontrol).on( val2.type, {id: newinputid}, val2.handler );
441             });
442         });
443     }
444 }
445
446 /**
447  * This function removes or clears unwanted subfields
448  */
449 function UnCloneField(index) {
450     var original = document.getElementById(index);
451     var canUnclone = false;
452     if ($(original).hasClass("tag")) {
453         // unclone a field, check if there will remain one field
454         var fieldCode = getFieldCode(index);
455         // tag divs with id begining with original field code
456         var cloneFields = $('.tag[id^="tag_'+fieldCode+'"]');
457         if (cloneFields.length > 1) {
458             canUnclone = true;
459         }
460     } else {
461         // unclone a subfield, check if there will remain one subfield
462         var subfieldCode = getFieldAndSubfieldCode(index);
463         // subfield divs of same field with id begining with original field and subfield field code
464         var cloneSubfields = $(original).parent().children('.subfield_line[id^="subfield'+subfieldCode+'"]');
465         if (cloneSubfields.length > 1) {
466             canUnclone = true;
467         }
468     }
469     if (canUnclone) {
470         // remove clone
471         original.parentNode.removeChild(original);
472     } else {
473         // clear inputs, but don't delete
474         $(":input.input_marceditor", original).each(function(){
475             // thanks to http://www.learningjquery.com/2007/08/clearing-form-data for
476             // hint about clearing selects correctly
477             var type = this.type;
478             var tag = this.tagName.toLowerCase();
479             if (type == 'text' || type == 'password' || tag == 'textarea') {
480                 this.value = "";
481             } else if (type == 'checkbox' || type == 'radio') {
482                 this.checked = false;
483             } else if (tag == 'select') {
484                 this.selectedIndex = -1;
485                 // required for Select2 to be able to update its control
486                 $(this).trigger('change');
487             }
488         });
489         $(":input.indicator", original).val("");
490     }
491 }
492
493 /**
494  * This function create a random number
495  */
496 function CreateKey(){
497     return parseInt(Math.random() * 100000);
498 }
499
500 /* Functions developed for additem.tt */
501
502 /**
503  * To clone a subfield.<br>
504  * @param original subfield div to clone
505  */
506 function CloneItemSubfield(original){
507     Select2Utils.removeSelect2($(original).find('select'));
508     var clone = original.cloneNode(true);
509     var new_key = CreateKey();
510
511     // set the attribute for the new 'li' subfields
512     var inputs     = clone.getElementsByTagName('input');
513     var selects    = clone.getElementsByTagName('select');
514     var textareas  = clone.getElementsByTagName('textarea');
515
516     // input (except hidden type)
517     var id_input = "";
518     for(var i=0,len=inputs.length; i<len ; i++ ){
519         if (inputs[i].getAttribute('type') != 'hidden') {
520             id_input = inputs[i].getAttribute('id')+new_key;
521             inputs[i].setAttribute('id',id_input);
522         }
523     }
524
525     // select
526     for( i=0,len=selects.length; i<len ; i++ ){
527         id_input = selects[i].getAttribute('id')+new_key;
528         selects[i].setAttribute('id',selects[i].getAttribute('id')+new_key);
529     }
530
531     // textarea
532     for( i=0,len=textareas.length; i<len ; i++ ){
533         id_input = textareas[i].getAttribute('id')+new_key;
534         textareas[i].setAttribute('id',textareas[i].getAttribute('id')+new_key);
535     }
536
537     // when cloning a subfield, reset its label too.
538     var label = clone.getElementsByTagName('label')[0];
539     label.setAttribute('for',id_input);
540
541     // setting a new if for the parent div
542     var new_id = original.getAttribute('id')+new_key;
543     clone.setAttribute('id',new_id);
544
545     // insert this line on the page
546     original.parentNode.insertBefore(clone,original.nextSibling);
547     Select2Utils.initSelect2($(original).find('select'));
548     Select2Utils.initSelect2($(clone).find('select'));
549 }
550
551 /**
552  * Check mandatory subfields of a cataloging form and adds <code>missing</code> class to those who are empty.<br>
553  * @param p the parent object of subfields to check
554  * @return the number of empty mandatory subfields
555  */
556 function CheckMandatorySubfields(p){
557     var total = 0;
558     $(p).find(".subfield_line input[name='mandatory'][value='1']").each(function(){
559         var editor = $(this).siblings("[name='field_value']");
560         if (!editor.val()) {
561             editor.addClass("missing");
562             total++;
563         }
564     });
565     return total;
566 }
567
568 function CheckImportantSubfields(p){
569     var total = 0;
570     $(p).find(".subfield_line input[name='important'][value='1']").each(function(i){
571         var editor = $(this).siblings("[name='field_value']");
572         if (!editor.val()) {
573             editor.addClass("missing");
574             total++;
575         }
576     });
577     return total;
578 }
579
580 $(document).ready(function() {
581     $("input.input_marceditor, input.indicator").addClass('noEnterSubmit');
582     $(document).ajaxSuccess(function() {
583         $("input.input_marceditor, input.indicator").addClass('noEnterSubmit');
584     });
585
586     if ( window.editor === undefined ) { // TODO This does not work with the advanced editor
587         Select2Utils.initSelect2($('.subfield_line select[data-category=""]')); // branches, itemtypes and cn_source
588         Select2Utils.initSelect2($('.subfield_line select[data-category!=""]'));
589     }
590
591     $("#add_new_av").on("submit", function(){
592         var category         = $(this).find('input[name="category"]').val();
593         var value            = $(this).find('input[name="value"]').val();
594         var description      = $(this).find('input[name="description"]').val();
595         var opac_description = $(this).find('input[name="opac_description"]').val();
596
597         var data = "category="+encodeURIComponent(category)
598             +"&value="+encodeURIComponent(value)
599             +"&description="+encodeURIComponent(description)
600             +"&opac_description="+encodeURIComponent(opac_description);
601
602         $.ajax({
603             type: "POST",
604             url: "/cgi-bin/koha/svc/authorised_values",
605             data: data,
606             success: function(response) {
607                 $('#avCreate').modal('hide');
608
609                 $(current_select2).append('<option selected value="'+response.value+'">'+response.description+'</option>');
610             },
611             error: function(err) {
612                 $("#avCreate .error").html(_("Something went wrong, maybe the value already exists?"))
613             }
614         });
615         return false;
616     });
617 });