Saturday, September 10, 2011

Sequencing data in rails application

There are many plugins available for sequencing data. Sharing something similar which depends on the jQuery library.
Considering that there is a Single table inheritance with more that 15 different types.
There were more than 15 views of the inherited modules.
Now applying a view with required buttons in these many views may be posible but what if new types are introduced in future, styling may change, many things were there for pointing down.
Finally decided to do these all with jquery. Also consider that there 1 sequence column in the table.

Controller :
# reset_sequence action is called using the JavaScript Ajax function.
# The JS function passes client id, model type(constant) and the array of
# record id and the index to update in the constant
def reset_sequence
  model_type = params[:model_type]
  paramssequences = params[:sequences]
  clientid = params[:client_id].to_i
  paramssequences.each do |sequences|
    sequences.each do |sequence_array|
      sequence_record = sequence_array.split(",")
      id = sequence_record[0].to_i
      sequence = sequence_record[1].to_i
      object = model_type.singularize.camelize.constantize
      object.update_all({:sequence => sequence}, {:id => id, :client_id => clientid})
    end
  end
  respond_to do |format|
  format.js {
    render :update do |page|
      # need this block with out this it gives 302 error if sucess block added for ajax in javascript
      # else gives missing template error :: TODO
    end
  }
  end
end 

Routes :
reset_sequence

View :
  <table id="first_sub_types">
  <tbody>
  <tr>
    <th>Sequence</th>
    <th>Name</th>
    <th>Edit</th>
  </tr>
  <% first_sub_types.each do |subtype| %>
  <tr>
    <td><%= subtype.sequence %></td>
    <td><%= subtype.name %></td>
    <td><%= link_to "Edit", edit_first_sub_type_path(subtype.id) %></td>
  </tr>
  <% end %>
  </tbody>
  </table>
  

  <script type="text/javascript">  
    fetch_data("FirstSubType", 3, "first_sub_types")
  </script>  

Javascript :
Added 1 new .js file in public/javascript, we can say sequencing.js
Sequnce.js :
Very first function declared was fetch_data( modelType, clientID, divID )
this function will be called from the bottom of the view in which sequencing is required.
Just sending the modeltype for e.g ("FirstSubType", 3, "first_sub_types")

/*
* fetch_data function will get the id name of the table and convert it in to jquery object.
* This will also call build_html function by passing object created, model type(which model to be called), company id and divid separately.
*/

function fetch_data( modelType, clientID, divID ){
    var obj = jQuery("#"+divID);
    build_html( obj, modelType, clientID, divID );
  }

we can see above we have defined a variable with the divid which we passed from the view.
also called other function build_html.

/*
* This function is only called through the fetch_data().
* build_html will first check the object's tag name by jquery function .is() and based on this checkIfobject will be assigned a value.
* This assigned value will be used for sorting <td> OR <li> OR <option>.
* After checking the html tag the objects width is changed to fit the buttons on the right side.
* At the end loopingList() is called
*/

function build_html( obj, modelType, clientID, divID ){
    var checkIfobject;    

    if(obj.is("table")){
        checkIfobject = "TaBle"
    }else if(obj.is("ul")){
        checkIfobject = "UL"
    }else if(obj.is("select")){
        checkIfobject = "SelEct"
    }    

    var trheight = jQuery(obj).children().children('tr.bg1').height()
    var tdheight = (parseInt(trheight) * (jQuery(obj).children().children('tr').length-1)) + jQuery(obj).children().children('tr:first').height()
    jQuery(obj).after('<table cellpadding="0" cellspacing="0" id="setter" style="float: left;"><tbody><tr><th align="center" style="height: "+tdheight+"px;" valign="middle"> <input id="upbutton" name="upbutton" type="button" value=" ↑ " /> <br /> <input id="downbutton" name="downbutton" type="button" value=" ↓ " /> <input disabled="disabled" id="setbutton" name="setbutton" onclick="sendRequest( \""+checkIfobject+"\", \""+modelType+"\", \""+clientID+"\", \""+divID+"\" )" style="color: grey;" type="button" value="Set" /> </th></tr> </tbody></table>')

    var setterwidth = ( 100 * parseFloat(jQuery('#setter').css('width')) / parseFloat(jQuery('#setter').parent().css('width')) );
    var tblewidth = (99.7-setterwidth)
    jQuery(obj).css({
        "width" : tblewidth+"%",
        "float" : "left"
    });
    loopingList( obj, checkIfobject );
}
  
this function is actually building a html besides the original table. First its shifts it to left side. as we have not defined any width to new table it will take width only what the buttons will take.
I have also checked the object whether it is table UL or select for future modification in which I will change the code which will be compatible with UL and select also.
Everying done overhere is just for styling the UI. but main function starts by calling loopingList( obj, checkIfobject );

/*
* This function will actually add the hidden field which will be used for indexing the data
* check_button() is called at the end
*/

function loopingList( obj, checkIfobject ){
    var list, mxLength;
    switch( checkIfobject ){
        case "TaBle":
            list = obj.children().children('tr');
            mxLength = list.length-1
            break;
        case "UL":
            list = obj.children('li');
            break;
        case "SelEct":
            list = obj.children('option');
            break;
    }

    list.each(function( index ) {
        var listitem = this;
        var jListitem = jQuery(listitem);        

        jListitem.attr('onClick', "updateList(this)");
        jListitem.children('td:first').append('<input class="supportField" id="hidden_"+index+"" type="hidden" value=""+index+"" />');
    });

    check_button( checkIfobject, mxLength );
}
  
This will add 1 hidden field to each row for updating the index/ sequence. When this file is called from view first time/ by default its index is set by this function.
But will be modified later with other function. If in database no value is set and if the user clicks on set button it will save the sequence as per the default setting.

/*
* To add or remove selected/ clicked item's class.
* Need to pass the class name, list array, the clicked item and whether the class exists or not (true/ false)
*/
function updateList( listitem ){
  var jListitem = jQuery(listitem);
  var className = "selected_listitem"
  jQuery("tr").removeClass(className)
  if(jListitem.hasClass(className)){
    jListitem.removeClass(className);
  }else{
    jListitem.addClass(className);
  }
}


/*
* This function handles the up and down sorting button clicks on which the actual sorting takes place.
*/

function check_button( checkIfobject, mxLength ){
    jQuery('#upbutton').click(function(){
        var jListitem = jQuery(".selected_listitem");

        if( jListitem.length > 0 ){
            jQuery("#setbutton").removeAttr("disabled");
            jQuery("#setbutton").removeAttr("style");
            var prevlistitem = jListitem.prev();
            var prevlistitemIndex = prevlistitem.children("td:first").children("input.supportField").val();
            var jListitemIndex = jListitem.children("td:first").children("input.supportField").val();

            if( jListitemIndex == 1 ){ 
            }else{
                var innerhmtl = prevlistitem.html();
                prevlistitem.remove();
                jListitem.after(""+innerhmtl+"");
                jListitem.children("td:first").children("input.supportField").val(prevlistitemIndex);
                jListitem.next().children("td:first").children("input.supportField").val(jListitemIndex);
            }
        }else{
            alert("Select Record for sorting");
        }
    });

    jQuery('#downbutton').click(function(){
        var jListitem = jQuery(".selected_listitem");

        if( jListitem.length > 0 ){
            jQuery("#setbutton").removeAttr("disabled");
            jQuery("#setbutton").removeAttr("style");
            var nextlistitem = jListitem.next();
            var nextlistitemIndex = nextlistitem.children("td:first").children("input.supportField").val();
            var jListitemIndex = jListitem.children("td:first").children("input.supportField").val();

            if( jListitemIndex == mxLength ){
            }else{
                var innerhmtl = nextlistitem.html();
                nextlistitem.remove();
                jListitem.before(""+innerhmtl+"");
                jListitem.children("td:first").children("input.supportField").val(nextlistitemIndex);
                jListitem.prev().children("td:first").children("input.supportField").val(jListitemIndex);
            }
        }else{
            alert("Select Record for Sorting");
        }
    });

}
  
This above function handles the sorting onclick of up and down buttons.
It also considers that at least 1 row should be selected for sorting else it will show alert for selecting record.

Finally a main roll function which sends the ajax request to controller's action reset_sequence

function sendRequest( checkIfobject, modelType, clientID, divID ){
    var jListitem = jQuery(".selected_listitem");
    if( jListitem.length > 0 ){
      var list = jQuery(divID).children().children('tr');
      var sequences = []

      list.each(function(){
        var listitem = this;
        var jListitem = jQuery(listitem);
        var indNum = list.index(listitem);        

        if( indNum > 0 ){
            var id = jListitem.children("td:first").children("input.supportFieldID").val();
            var sequence = jListitem.children("td:first").children("input.supportField").val();
            sequences.push([id,sequence]);
        }
      });    

      jQuery.ajax({
        type: 'get',
        url: '/reset_sequence',
        data:
        {
            'sequences[]' : sequences,
            "model_type" : modelType,
            "client_id" : clientID
        },
        success: function(transform){
            //window.location.reload();
            window.location.href="/manage_company_utilities/"+modelType;
        }
      });
    }else{
            alert("Select Record for Sorting");
    }
}
  

1 comment:

  1. Great Job Supriya..
    Have seen this functionality working personally...
    n its really amazing..
    Keep up the good work..
    Best Luck... :)

    ReplyDelete