Bug 7882: Add ability to move and reorder fields in MARC editor
authorMaryse Simard <maryse.simard@inlibro.com>
Wed, 23 Oct 2019 19:34:39 +0000 (15:34 -0400)
committerMartin Renvoize <martin.renvoize@ptfs-europe.com>
Fri, 3 Jan 2020 16:27:59 +0000 (16:27 +0000)
Use jQueryUI sortable to make fields and subfields moveable in the
MARC editor for both records and authorities.

This patch convert items from div's to ul's and consequently alter css
and js to match. It also replace the up arrow with a more adapted icon
(from font awesome).

Primary authorship by Elliott Davis.

To Test:

1. Add or edit a record in cataloguing module.
2. You should be able to move the fields and subfields around.
    - You can click on any part of the element to drag it, the move
    icon to the left of the item is a good place to do it.
    => You can only change the order of fields of the same tag.
3. Make sure all of the javascript driven fonctionnality still work :
    - Duplicate fields/subfields
    - Remove fields/subfields
    - Using the tag editor for control fields or to link authorities
    - etc
4. Reorder some fields/subfields and save the record.
5. Edit the record again.
6. The order in the editor should match the changes which were saved.
    - Empty subfields should appear after the ones with content.
7. Repeat steps 1-6 with the authority editor.

Signed-off-by: Séverine QUEUNE <severine.queune@bulac.fr>
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
cataloguing/addbiblio.pl
koha-tmpl/intranet-tmpl/prog/css/addbiblio.css
koha-tmpl/intranet-tmpl/prog/en/modules/authorities/authorities.tt
koha-tmpl/intranet-tmpl/prog/en/modules/authorities/blinddetail-biblio-search.tt
koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbiblio.tt
koha-tmpl/intranet-tmpl/prog/img/up.png [deleted file]
koha-tmpl/intranet-tmpl/prog/js/cataloging.js

index 00b2501..a548bab 100755 (executable)
@@ -511,7 +511,7 @@ sub build_tabs {
     for ( my $tabloop = 0 ; $tabloop <= $max_num_tab ; $tabloop++ ) {
         my @loop_data = (); #innerloop in the template.
         my $i = 0;
-        foreach my $tag (@tab_data) {
+        foreach my $tag (sort @tab_data) {
             $i++;
             next if ! $tag;
             my ($indicator1, $indicator2);
index 9fdbedb..28cf341 100644 (file)
@@ -15,6 +15,49 @@ div#toolbar {
     margin: .3em 0;
 }
 
+ul.sortable_field, ul.sortable_subfield {
+    margin-bottom: 0;
+    padding-left: 0;
+}
+
+ul li.tag, ul li.tag li.subfield_line {
+    list-style-type: none;
+    position: relative;
+    padding-left: 30px;
+}
+
+ul li.tag:before, ul li.tag li.subfield_line:before {
+    position: absolute;
+    font-family: 'FontAwesome';
+    font-size: .8em;
+    top: 0;
+    left: 10px;
+    content: "\f047";
+}
+
+ul li.tag:before {
+    padding-top: 1.7em;
+}
+
+li.ui-sortable-helper {
+    background-color: #e0e0e0;
+    max-height: 150px;
+    padding: 2px;
+    border-radius: 4px;
+}
+
+li.ui-sortable-helper ul li {
+       display: none !important;
+}
+
+.sortable_subfield .ui-sortable-helper input.flat {
+       background-color: transparent;
+}
+
+ul li.tag li.subfield_line.ui-sortable-helper::before {
+    top: 5px;
+}
+
 .buttonPlus {
        font-weight : bold;
        text-decoration : none;
@@ -44,14 +87,14 @@ a.expandfield {
     padding: .7em 0;
 }
 
-div.subfield_line {
+li.subfield_line {
     padding-bottom: .3em;
     float: left;
     clear: left;
     width: 100%;
 }
 
-div.subfield_line label {
+li.subfield_line label {
     font-size:89%;
     float: left;
         padding-right : .4em;
@@ -266,7 +309,7 @@ tbody tr.active td {
 }
 
 @media (min-width: 768px) {
-    div.subfield_line label {
+    li.subfield_line label {
         width: 20em;
     }
 
@@ -280,7 +323,7 @@ tbody tr.active td {
 }
 
 @media (min-width: 1200px) {
-    div.subfield_line label {
+    li.subfield_line label {
         width: 25em;
     }
 
index 51f4b2a..bf2dffc 100644 (file)
@@ -12,7 +12,9 @@
     });
     var Sticky;
         $(document).ready(function() {
-        $('#authoritytabs').tabs();
+        var tabs = $('#authoritytabs').tabs();
+        $( "ul.sortable_field", tabs ).sortable();
+        $( "ul.sortable_subfield", tabs ).sortable();
         Sticky = $("#toolbar");
         Sticky.hcSticky({
             stickTo: ".main",
@@ -235,9 +237,17 @@ function confirmnotdup(redirect){
 [% FOREACH BIG_LOO IN BIG_LOOP %]
     <div id="tab[% BIG_LOO.number | html %]XX">
 
+    [% previous = "" %]
     [% FOREACH innerloo IN BIG_LOO.innerloop %]
     [% IF ( innerloo.tag ) %]
-    <div class="tag clearfix" id="tag_[% innerloo.tag | html %]_[% innerloo.index | html %][% innerloo.random | html %]">
+    [% IF innerloo.tag != previous %]
+        [% IF previous != "" %]
+            </ul>
+        [% END %]
+        [% previous = innerloo.tag %]
+        <ul class="sortable_field">
+    [% END %]
+    <li class="tag clearfix" id="tag_[% innerloo.tag | html %]_[% innerloo.index | html %][% innerloo.random | html %]">
         <div class="tag_title" id="div_indicator_tag_[% innerloo.tag | html %]_[% innerloo.index | html %][% innerloo.random | html %]">
         [% UNLESS hide_marc %]
             [% IF advancedMARCEditor %]
@@ -316,9 +326,10 @@ function confirmnotdup(redirect){
 
         </div>
 
+        <ul class="sortable_subfield">
         [% FOREACH subfield_loo IN innerloo.subfield_loop %]
             <!--  One line on the marc editor -->
-            <div class="subfield_line" style="[% subfield_loo.visibility | html %]" id="subfield[% subfield_loo.tag | html %][% subfield_loo.subfield | html %][% subfield_loo.random | html %]">
+            <li class="subfield_line" style="[% subfield_loo.visibility | html %]" id="subfield[% subfield_loo.tag | html %][% subfield_loo.subfield | html %][% subfield_loo.random | html %]">
 
                 [% UNLESS advancedMARCEditor %]
                     [% IF ( subfield_loo.fixedfield ) %]<label for="tag_[% subfield_loo.tag | html %]_subfield_[% subfield_loo.subfield | html %]_[% subfield_loo.index | html %]_[% subfield_loo.index_subfield | html %]" style="display:none;" class="labelsubfield">
@@ -328,11 +339,6 @@ function confirmnotdup(redirect){
                 
                 [% UNLESS hide_marc %]
                 <span class="subfieldcode">
-                    [% IF ( subfield_loo.fixedfield ) %]
-                        <img class="buttonUp" style="display:none;" src="[% interface | html %]/[% theme | html %]/img/up.png" onclick="upSubfield('subfield[% subfield_loo.tag | html %][% subfield_loo.subfield | html %][% subfield_loo.random | html %]')" alt="Move Up" title="Move Up" />
-                    [% ELSE %]
-                        <img class="buttonUp" src="[% interface | html %]/[% theme | html %]/img/up.png" onclick="upSubfield('subfield[% subfield_loo.tag | html %][% subfield_loo.subfield | html %][% subfield_loo.random | html %]')" alt="Move Up" title="Move Up" />
-                    [% END %]
                         <input type="text"
                             title="[% subfield_loo.marc_lib | $raw %]"
                             style=" [% IF ( subfield_loo.fixedfield ) %]display:none; [% END %]border:0;"
@@ -400,12 +406,14 @@ function confirmnotdup(redirect){
                 [% END %]
                 </span>
                 
-            </div>
+            </li>
             <!-- End of the line -->
         [% END %]
 
-    </div>
+        </ul> <!-- /.sortable_subfield -->
+    </li>
     [% END %]<!-- if innerloo.tag -->
+    </ul> <!-- /.sortable_field -->
     [% END %]<!-- BIG_LOO.innerloop -->
     </div>
 [% END %]<!-- BIG_LOOP -->
index b9635b3..6822225 100644 (file)
@@ -46,7 +46,7 @@
             } catch(e) {
                 return;
             }
-            var field_start = whichfield.parentNode.parentNode;
+            var field_start = whichfield.parentNode.parentNode.parentNode;
 
             // Sets the good number of form fields for the specified subfield
             // Returns false if the cloning failed
@@ -58,7 +58,7 @@
 
                 // Find the subfield we want to clone
                 var re = new RegExp('^subfield' + subfield_name,'g');
-                var subfields = $(field_start).children('div').filter( function() {
+                var subfields = $(field_start).children('ul').children('li').filter( function() {
                     return this.id.match(re);
                 });
 
@@ -89,7 +89,7 @@
 
                 // Find the subfields where we will add the new values
                 var re = new RegExp('^subfield' + subfield_name,'g');
-                var subfields = $(field_start).children('div').filter( function() {
+                var subfields = $(field_start).children('ul').children('li').filter( function() {
                     return this.id.match(re);
                 });
 
index fe2922f..a8e7901 100644 (file)
             $("#toolbar").hide();
         [% END %]
 
-        $('#addbibliotabs').tabs().bind('show.ui-tabs', function(e, ui) {
+        var $tabs = $('#addbibliotabs').tabs().bind('show.ui-tabs', function(e, ui) {
             $("#"+ui.panel.id+" input:eq(0)").focus();
         });
+        $( "ul.sortable_field", $tabs ).sortable();
+        $( "ul.sortable_subfield", $tabs ).sortable();
 
         [% IF tab %]
             link = $("a[href='#[% tab | html %]']");
                             [% IF ( BIG_LOOP.size > 1 ) %]
                                 <h3>Section [% BIG_LOO.number | html %]</h3>
                             [% END %]
+                            [% previous = "" %]
                             [% FOREACH innerloo IN BIG_LOO.innerloop %]
                                 [% IF ( innerloo.tag ) %]
-                                    <div class="tag clearfix" id="tag_[% innerloo.tag | html %]_[% innerloo.index | html %][% innerloo.random | html %]">
+                                [% IF innerloo.tag != previous %]
+                                    [% IF previous != "" %]
+                                        </ul>
+                                    [% END %]
+                                    [% previous = innerloo.tag %]
+                                    <ul class="sortable_field">
+                                [% END %]
+                                    <li class="tag clearfix" id="tag_[% innerloo.tag | html %]_[% innerloo.index | html %][% innerloo.random | html %]">
                                         <div class="tag_title" id="div_indicator_tag_[% innerloo.tag | html %]_[% innerloo.index | html %][% innerloo.random | html %]">
                                             [% IF advancedMARCEditor %]
                                                 <a href="#" tabindex="1" class="tagnum" title="[% innerloo.tag_lib | html %] - Click to Expand this Tag" onclick="ExpandField('tag_[% innerloo.tag | html %]_[% innerloo.index | html %][% innerloo.random | html %]'); return false;">[% innerloo.tag | html %]</a>
                                             </span> <!-- /.field_controls -->
                                         </div> <!-- /div.tag_title -->
 
+                                        <ul class="sortable_subfield">
                                         [% FOREACH subfield_loo IN innerloo.subfield_loop %]
                                             <!--  One line on the marc editor -->
-                                            <div class="subfield_line" style="[% subfield_loo.visibility | html %]" id="subfield[% subfield_loo.tag | html %][% subfield_loo.subfield | html %][% subfield_loo.random | html %]">
+                                            <li class="subfield_line" style="[% subfield_loo.visibility | html %]" id="subfield[% subfield_loo.tag | html %][% subfield_loo.subfield | html %][% subfield_loo.random | html %]">
                                                 [% UNLESS advancedMARCEditor %]
                                                     [% IF ( subfield_loo.fixedfield ) %]
                                                         <label for="tag_[% subfield_loo.tag | html %]_subfield_[% subfield_loo.subfield | html %]_[% subfield_loo.index | html %]_[% subfield_loo.index_subfield | html %]" style="display:none;" class="labelsubfield">
                                                 [% END %]
 
                                                     <span class="subfieldcode">
-                                                        [% IF ( subfield_loo.fixedfield ) %]
-                                                            <img class="buttonUp" style="display:none;" src="[% interface | html %]/[% theme | html %]/img/up.png" onclick="upSubfield('subfield[% subfield_loo.tag | html %][% subfield_loo.subfield | html %][% subfield_loo.random | html %]')" alt="Move Up" title="Move Up" />
-                                                        [% ELSE %]
-                                                            <img class="buttonUp" src="[% interface | html %]/[% theme | html %]/img/up.png" onclick="upSubfield('subfield[% subfield_loo.tag | html %][% subfield_loo.subfield | html %][% subfield_loo.random | html %]')" alt="Move Up" title="Move Up" />
-                                                        [% END %]
                                                             <input type="text"
                                                                 title="[% subfield_loo.marc_lib | $raw %]"
                                                                 style=" [% IF ( subfield_loo.fixedfield ) %]display:none; [% END %]border:0;"
                                                         </a>
                                                     [% END %]
                                                 </span>
-                                            </div> <!-- /.subfield_line -->
+                                            </li> <!-- /.subfield_line -->
                                             <!-- End of the line -->
                                         [% END # /FOREACH subfield_loop %]
-                                    </div> <!-- /.tag.clearfix -->
+                                        </ul> <!--  /.sortable_subfield -->
+                                    </li> <!-- /.tag.clearfix -->
                                 [% END %]<!-- if innerloo.tag -->
                             [% END # /FOREACH BIG_LOO.innerloop %]
+                            </ul> <!--  /.sortable_field -->
                         </div> <!-- /#tabXXX -->
                     [% END # /FOREACH BIG_LOOP %]
                 </div><!-- /#addbibliotabs -->
diff --git a/koha-tmpl/intranet-tmpl/prog/img/up.png b/koha-tmpl/intranet-tmpl/prog/img/up.png
deleted file mode 100644 (file)
index f3ede90..0000000
Binary files a/koha-tmpl/intranet-tmpl/prog/img/up.png and /dev/null differ
index be183af..c7a3062 100644 (file)
@@ -56,20 +56,20 @@ function openAuth(tagsubfieldid,authtype,source) {
 }
 
 function ExpandField(index) {
-    var original = document.getElementById(index); //original <div>
-    var divs = original.getElementsByTagName('div');
-    for(var i=0,divslen = divs.length ; i<divslen ; i++){   // foreach div
-        if(divs[i].hasAttribute('id') == 0 ) {continue; } // div element is specific to Select2
-        if(divs[i].getAttribute('id').match(/^subfield/)){  // if it s a subfield
-            if (!divs[i].style.display) {
+    var original = document.getElementById(index); //original <li>
+    var lis = original.getElementsByTagName('li');
+    for(var i=0,lislen = lis.length ; i<lislen ; i++){   // foreach li
+        if(lis[i].hasAttribute('id') == 0 ) {continue; } // li element is specific to Select2
+        if(lis[i].getAttribute('id').match(/^subfield/)){  // if it s a subfield
+            if (!lis[i].style.display) {
                 // first time => show all subfields
-                divs[i].style.display = 'block';
-            } else if (divs[i].style.display == 'none') {
+                lis[i].style.display = 'block';
+            } else if (lis[i].style.display == 'none') {
                 // show
-                divs[i].style.display = 'block';
+                lis[i].style.display = 'block';
             } else {
                 // hide
-                divs[i].style.display = 'none';
+                lis[i].style.display = 'none';
             }
         }
     }
@@ -101,16 +101,16 @@ var Select2Utils = {
  * @param advancedMARCEditor '0' for false, '1' for true
  */
 function CloneField(index, hideMarc, advancedMARCEditor) {
-    var original = document.getElementById(index); //original <div>
+    var original = document.getElementById(index); //original <li>
     Select2Utils.removeSelect2(original);
 
     var clone = original.cloneNode(true);
     var new_key = CreateKey();
     var new_id  = original.getAttribute('id')+new_key;
 
-    clone.setAttribute('id',new_id); // setting a new id for the parent div
+    clone.setAttribute('id',new_id); // setting a new id for the parent li
 
-    var divs = clone.getElementsByTagName('div');
+    var divs = Array.from(clone.getElementsByTagName('li')).concat(Array.from(clone.getElementsByTagName('div')));
 
     // if hide_marc, indicators are hidden fields
     // setting a new name for the new indicator
@@ -120,10 +120,10 @@ function CloneField(index, hideMarc, advancedMARCEditor) {
     }
 
     // settings all subfields
-    for(var i=0,divslen = divs.length ; i<divslen ; i++){      // foreach div
+    for(var i=0,divslen = divs.length ; i<divslen ; i++){      // foreach div/li
         if(divs[i].getAttribute("id").match(/^subfield/)){  // if it s a subfield
 
-            // set the attribute for the new 'div' subfields
+            // set the attribute for the new 'li' subfields
             divs[i].setAttribute('id',divs[i].getAttribute('id')+new_key);
 
             var inputs   = divs[i].getElementsByTagName('input');
@@ -162,7 +162,7 @@ function CloneField(index, hideMarc, advancedMARCEditor) {
                 }
             }
             if( $(inputs[1]).hasClass('framework_plugin') ) {
-                var olddiv= original.getElementsByTagName('div')[i];
+                var olddiv= original.getElementsByTagName('li')[i];
                 var oldcontrol= olddiv.getElementsByTagName('input')[1];
                 AddEventHandlers( oldcontrol,inputs[1],id_input );
             }
@@ -173,12 +173,6 @@ function CloneField(index, hideMarc, advancedMARCEditor) {
                 labels[0].setAttribute('for',id_input);
             }
 
-            if(hideMarc == '0') {
-                // updating javascript parameters on button up
-                var imgs = divs[i].getElementsByTagName('img');
-                imgs[0].setAttribute('onclick',"upSubfield(\'"+divs[i].getAttribute('id')+"\');");
-            }
-
             // setting its '+' and '-' buttons
             try {
                 var anchors = divs[i].getElementsByTagName('a');
@@ -209,7 +203,7 @@ function CloneField(index, hideMarc, advancedMARCEditor) {
                         // 2 possibilities :
                         try{
                             if( $(buttonDot).hasClass('framework_plugin') ) {
-                                var olddiv= original.getElementsByTagName('div')[i];
+                                var olddiv= original.getElementsByTagName('li')[i];
                                 var oldcontrol= olddiv.getElementsByTagName('a')[0];
                                 AddEventHandlers(oldcontrol,buttonDot,id_input);
                             } else {
@@ -236,10 +230,6 @@ function CloneField(index, hideMarc, advancedMARCEditor) {
                     }
                 }
             }
-            if(hideMarc == '0') {
-                var buttonUp = divs[i].getElementsByTagName('img')[0];
-                buttonUp.setAttribute('onclick',"upSubfield('" + divs[i].getAttribute('id') + "')");
-            }
 
         } else { // it's a indicator div
             if(divs[i].getAttribute('id').match(/^div_indicator/)){
@@ -275,6 +265,8 @@ function CloneField(index, hideMarc, advancedMARCEditor) {
     // insert this line on the page
     original.parentNode.insertBefore(clone,original.nextSibling);
 
+    $("ul.sortable_subfield", clone).sortable();
+
     Select2Utils.initSelect2(original);
     Select2Utils.initSelect2(clone);
 }
@@ -290,7 +282,7 @@ function CloneSubfield(index, advancedMARCEditor){
     Select2Utils.removeSelect2(original);
     var clone = original.cloneNode(true);
     var new_key = CreateKey();
-    // set the attribute for the new 'div' subfields
+    // set the attribute for the new 'li' subfields
     var inputs     = clone.getElementsByTagName('input');
     var selects    = clone.getElementsByTagName('select');
     var textareas  = clone.getElementsByTagName('textarea');
@@ -351,8 +343,6 @@ function CloneSubfield(index, advancedMARCEditor){
     clone.setAttribute('id',new_id);
 
     try {
-        var buttonUp = clone.getElementsByTagName('img')[0];
-        buttonUp.setAttribute('onclick',"upSubfield('" + new_id + "')");
         var anchors = clone.getElementsByTagName('a');
         if(anchors.length){
             for(var i = 0 ,lenanchors = anchors.length ; i < lenanchors ; i++){
@@ -500,7 +490,7 @@ function CloneItemSubfield(original){
     var clone = original.cloneNode(true);
     var new_key = CreateKey();
 
-    // set the attribute for the new 'div' subfields
+    // set the attribute for the new 'li' subfields
     var inputs     = clone.getElementsByTagName('input');
     var selects    = clone.getElementsByTagName('select');
     var textareas  = clone.getElementsByTagName('textarea');