codedecoder

breaking into the unknown…


1 Comment

inactivity warning and logout timer

Almost all the secured Application support session time out due to inactivity after specific period of time say 15 minute or 30 minute. It is good to warn the user of logout due to inactivity. Say if your application support session expire on 30 minutes of inactivity. warn user at 25 minutes. If he do not click on the warning for next 5 minute but say after 6 minute, log him out.

You should note that, you have to handle the session expiry at your application level. I am from ruby on rails background and handle this with devise gem. Devise internally clear session and cookies, if the user is inactive for 30 minute. So if user come after 30 minutes and try to reload or submit the page he is taken back to the login page.

Here, we will warn the user at 25 minutes and log him out after 30 minutes.

The logic should be :

=> You need to track user doing something on your site or not. If not keep increasing the idle time.

=> If idle time reach 25 minute give him warning popup ( I used bootstrap modal for the popup)

      NOTE : don’t use alert . it will stop your timer

=> Once popup is shown, keep counting the sub-time for the logout

=> If user come after sub-time log him out

      NOTE : In my case, as I have told above session expiry is managed by devise, reloading the page is sufficient to take user back to the login page.

In case your application do not do that, clear session cookies etc using jquery (not covered in this post) and redirect him to login page.

=> whenever user show some activity : move mouse, press key or reload page, reset the timer i,e idle time to 0 value.

To create the timer we will use setInterval and clearInterval function of JavaScript . setInterval repeat the code within it after the specified time. The time is specified in milisecod i,e if you want to repeat the process after 1 seocnd, you should pass 1000 to it .

let us first create other required things.

=> create Alert PopUP.

As mentioned above alert will stop the execution of setInterval. so let us create our own popup. I have used bootstrap . The code is as below.

<div id="inactivity_warning" class="modal hide fade" data-backdrop="static" style="top:30%">
  <div class="modal-header">
    <button type="button" class="close inactivity_ok" data-dismiss="modal" aria-hidden="true">&times;</button>
  </div>
  <div class="modal-body">
    <div class="row-fluid">
      <div id="custom_alert_message" class="span12">
       You will be logged out in 5 minutes due to inactivity. Please save your credit 
       application if you have not already done so.
     </div>
   </div>
  <div class="modal-footer">
    <a href="javascript:void(0)" class="btn inactivity_ok" data-dismiss="modal" aria-hidden="true">O.K</a>
   </div>
  </div>
</div>

save these lines in you layout so that it is available through out the application.

=> set hidden flag to decide logout and timer

Below hidden field will decide whether to logout the user or not. we will change its value to inactive when time reach 30 minutes .Set the user_loged_in value to true when user is loged in, otherwise keep it false

<input id="user_activity" name="user_activity" type="hidden" value="active" />
<input id="user_loged_in" name="user_loged_in" type="hidden" value="true" />

=> jquery code for timer

    // reset main timer i,e idle time to 0 on mouse move, keypress or reload
    window.onload = reset_main_timer;
    document.onmousemove = reset_main_timer;
    document.onkeypress = reset_main_timer;
    
    // create main_timer and sub_timer variable with 0 value, we will assign them setInterval object
    var main_timer = 0;
    var sub_timer = 0;

    // this will ensure that timer will start only when user is loged in
    var user_loged_in = $("#user_loged_in").val()

   // within dilog_set_interval function we have created object of setInterval and assigned it to main_timer.
   // within this we have again created an object of setInterval and assigned it to sub_timer. for the main_timer
   // value is set to 15000000 i,e 25 minute.note that if subtimer repeat itself after 5 minute we set user_activity
   // flag to inactive
    function dialog_set_interval(){
        main_timer = setInterval(function(){
            if(user_loged_in == "true"){
                $("#inactivity_warning").modal("show");
                sub_timer = setInterval(function(){
                    $("#user_activity").val("inactive")
                },300000);
            }
        },1500000);
    }
   // maintimer is set to 0 by calling the clearInterval function. note that clearInterval function takes
   // setInterval object as argument, which it then set to 0
    function reset_main_timer(){
        clearInterval(main_timer);
        dialog_set_interval();
    }

    // logout user if user_activity flag set to inactive, when he click ok on popup. whenuser click O.K
    // on the warning the subtimer is reset to 0
    $(".inactivity_ok").click(function(){
        clearInterval(sub_timer);
        if($("#user_activity").val() == "inactive"){
            window.location = window.location // if your application not manage session expire 
                                              //automatically. clear cookies and session her
        }
    });

NOTE : value taken by setInterval is in milisecond i,e 1000 = 1 second. In the above code 25 minutes = 15000000 and 5 minute = 300000

 

Reference:

https://developer.mozilla.org/en/docs/Web/API/window.setInterval

https://developer.mozilla.org/en-US/docs/Web/API/window.clearInterval

https://codedecoder.wordpress.com/2013/02/08/rails-with-bootstrap-javascript-jquery-plugin/

 

 


1 Comment

forcing scroll down to read terms and conditions

We often come across sites, asking user to read terms and conditions before they create there account or say order a item online. Most of the time user check the accept term and condition checkbox without reading the terms and conditions (generally it is big one and no one bother to read it ). Though, there is no way, we can ensure that user has really read the terms and conditions before giving his consent, we can do ensure that user has scroll down to the end of content. One of my client from banking sector has this requirement. His logic is that he do not want to get into any legal hustle if user claims that he do not know that there is more of content of terms conditions and he forget to scroll down.

This call for validation of scroll. The client specification is as below.

=> When applicant click  ” I accept ” button without scrolling down to bottom of terms and condition , he should be given alert message “Scroll down to read complete terms and conditions before accepting it”

=>Scroll down should be validated only once i,e if user scroll to the bottom and again move the scroll to top, it should be treated as that he has read it and allowed to submit form on clicking I accept.

The logic to implement it is as below :

=> Keep a hidden filed with its value set to false by default

<input type=”hidden” value=”false” name=”applicant_read_the_content” id=”applicant_read_the_content”>

=> Implement jQuery logic to check that scroll is at bottom and set the above value to true

basically if Scroll is at bottom, below condition should be true

total height of scroll = height of scrollbar + top position of scroll bar

=> when user click “I accept” check the value of the hidden field, it it is still false give the alert message otherwise let the form to get submit.

My HTML markup for the terms and condition acceptance page is as below :

<div>
  <form method="post" id="esign_acceptance_form" action="/application">
    <div>
      <div>ESIGN Disclosure Statement and Consent</div>
      <div id="applicant_esign_content" style="height: 310px">
        your lenghty terms and condition will come here, which will result in scroll bar
      </div>
    </div>
    <div style="text-align: right;">
      <div>
        <input type="hidden" value="false" name="applicant_read_the_content" id="applicant_read_the_content" />
        <a href="javascript:void(0)" id="accept_esign_disclosure_applicant">I Accept</a>
        <a href="javascript:void(0)" id="decline_esign_disclosure_applicant">Cancel</a>
      </div>
    </div>
  </form>
</div>

The javascript code to validate the scroll is as below.

jQuery(function(){
    $(document).ready(function(){
        $("#applicant_esign_content").scroll(function(){
            var totalScrollHeight = $("#applicant_esign_content")[0].scrollHeight
            var scrollBarHeight = $("#applicant_esign_content")[0].clientHeight
            var scrollBarTopPosition = $("#applicant_esign_content")[0].scrollTop
            if (totalScrollHeight== scrollBarHeight + scrollBarTopPosition){
                $("#applicant_read_the_content").val("true")
            }
        })

        $("#accept_esign_disclosure_applicant").click(function(){
            if ($("#applicant_read_the_content").val() != "true"){
                alert("Please scroll through the disclosure text before clicking I Accept.")
                return false
            }
            else{
                $("#esign_acceptance_form").submit()
            }
        })
    });
})

The Important point to note in the above code is scrollHeight , clientHeight and scrollTop . To read them in detail read the reference below

Reference :

https://developer.mozilla.org/en-US/docs/Web/API/element.clientHeight


8 Comments

cascading dropdown or autocomplete with handsontable excel

Recently for one of our project, we need to support excel editing online i,e user should able to enter his assets and liabilities details in browser itself and able to submit it online. we used handsontable library for this. It is great with detailed documentation and many events and call back.

My financial sheet created with handsontable look as below.

financial sheet

online financial sheet

So, its look great. The only problem is that, while filling real state detail user need to select state, suburb, and postal code from the dropdown. Now for any country there can be thousands of suburb and postal code. Loading such big data in the dropdown with script hang the page. What if we load suburb only within a selected state and postal code within the selected suburb. Well it will reduce the suburb to 50 and postal code to 100-200.

This call for cascading dropdown list implementation , where value of next dropdown based on value of its parent. Whether you are implementing cascading dropdown in pure HTML or jquery based handsontable here. The logic and flow will remain same :

STEP 1: when user try to select suburb without selecting state, alert message should be given “Select a State first”, same if he try to select postal code without selecting a suburb “Select a Suburb first”

STEP 2: When a state is selected, you should trigger a ajax call which will update the suburb drop down. Similarly when a Suburb is selected it should update the post code dropdown.

I am assuming, you have all the required handsontable js file in place. Also create a css and js file to write cutom js and css code related to the grid. Let us call it handsontable_custom.css and handsontable_custom.js

Below is the code which implement the cascading dropdown.

=> Controller Code :

class BrowserBasedExcelController < ApplicationController   
  require 'carmen'
  include Carmen

  def handsontable
    @real_state = [["Select", 3, 20, "Select", "Select", "Park Avenu", "Select", 0 ]]
  end
  # state corresponding to country passed in param is fetched with carmen gem
  def state_list
    us = Country.named(params[:country])
    subregions = us.subregions.map{|subregion| subregion.name}
    render :json => {"data" => subregions}
  end

  // we will fetch suburb corresponding to state passed in params. if
  // no state is present in params we will return back only" Select" in the list
  def suburb_list
    suburb = []
    if params[:state].present?  && (params[:state] != "Select")
      if params[:state] == "Alaska"
        suburb = ["Dedham", "Concord", "Chanhassen", "Minneapolis"]
      else
        suburb = ["Patna", "Gaya", "Dhanbad", "Ranchi"]
      end
    else
      suburb = ["Select"]
    end
    render :json => {"data" => suburb}
  end

  def postal_code
    post_code = []
    if params[:suburb].present? && (params[:suburb] != "Select")
      if params[:suburb] == "Dedham"
        post_code = ["55416", "92008", "20850", "07663"]
      else
        post_code = ["5555", "9299", "2222", "33333"]
      end
    else
      post_code = ["Select"]
    end
    render :json => {"data" => post_code}
  end

end

So my controller have 4 action, the first is the action which represent the financial page(for simplicity I have removed other code and kept the code which is needed to demo cascading dropdown). The other three are actions to which we will make the ajax call the get the list of suburb and post code.

NOTE:
The suburb and postal code is fetching dummy value from the ajax call for now. Iam using Carmen gem for listing different location.But it provide only the listing of all the country of the world and states within a specific country. It do not provide suburb details within a state or the postal codes within a suburb.try to find out some other gem which provides all the details of a location. If no such gem is available you have to create your own database containing all these data.

=> Create the routes :
We need routes corresponding to these action of controller.

  match 'handsontable'=> 'browser_based_excel#handsontable', :as => :handsontable
  match 'state_list'=> 'browser_based_excel#state_list', :as => :state_list
  match 'suburb_list'=> 'browser_based_excel#suburb_list', :as => :suburb_list
  match 'postal_code'=> 'browser_based_excel#postal_code', :as => :postal_code

=> customized handsontable css code

We will keep it in handsontable_custom.css file. We will add below line to it for now

.handsonbottom{
    padding-bottom: 10px;
    vertical-align: top
}

.wrapWords{
    overflow: hidden !important;
    text-overflow: ellipsis !important;
    white-space: nowrap !important;
}

=> customized handsontable js

We will keep it in handsontable_custom.js file. We will add below line to it for now.
NOTE : the below custom code is based on two other js file spin.min.js and jquery.blockUI.js , so download them and to your project

var create_spinner = function(target) {
    var opts = {
        lines : 13, // The number of lines to draw
        length : 20, // The length of each line
        width : 10, // The line thickness
        radius : 50, // The radius of the inner circle
        corners : 1, // Corner roundness (0..1)
        rotate : 0, // The rotation offset
        direction : 1, // 1: clockwise, -1: counterclockwise
        color : '#FFFF00', // #rgb or #rrggbb
        speed : 1, // Rounds per second
        trail : 60, // Afterglow percentage
        shadow : false, // Whether to render a shadow
        hwaccel : false, // Whether to use hardware acceleration
        className : 'spinner', // The CSS class to assign to the spinner
        zIndex : 2e9, // The z-index (defaults to 2000000000)
        top : 'auto', // Top position relative to parent in px
        left : 'auto' // Left position relative to parent in px
    };
    var spinner = new Spinner(opts);
    spinner.spin(document.getElementById(target));
    return spinner
}

=> View file

We will write code in view file to create online excel, for simplicity , I have removed other code used for the complete excel displayed above and kept only those which is needed to demonstrate cascading dropdown . Below code will create this excel

cascading drop down

cascading drop down

The handsontable action render our excel view so let us create  handsontable.html.erb file for this. Its content is as below.

<script>

  $(document).ready(function () {
    var realestate = <%= raw @real_state %>;

    function get_store_data(search_string){
      return $('#handsontable_block').data(search_string)
    }
    function set_store_data(search_string , data){
      $('#handsontable_block').data(search_string,data)
    }

    var wrapstring = function (instance, td, row, col, prop, value, cellProperties) {
      Handsontable.renderers.TextRenderer.apply(this, arguments);
      $(td).addClass("wrapWords")
    };
    function reale_state(){
      $('#realestate').handsontable({
        data: realestate,
        selectState: true,
        minSpareRows: 5,
        colWidths: [100, 100, 200, 70,100],
        colHeaders: ['State','Suburb', 'Street', 'Postcode'],
        columns: [
          {
            data: 0,
            type: 'autocomplete',
            source: function(query,process) {
              if(get_store_data('state'))
              {
                process(get_store_data('state'));
              }
              else
              {
                $("#handsontable_block").block({
                  message : null
                });
                spinner =  create_spinner('handsontable_block');

                $.ajax({
                  url: '/state_list',
                  data: {
                    country: "United States"
                  },
                  dataType: "json",
                  success: function (response) {
                    process(response.data);
                    set_store_data('state',response.data)
                    spinner.spin(false);
                    $('#handsontable_block').unblock();
                  },
                  error: function(response){
                    spinner.spin(false);
                    $('#handsontable_block').unblock();
                  }
                });
              }
            },
            strict: true

          },
          {
            data: 1,
            type: 'autocomplete',
            source: function(query,process) {
              var selected_state = $("#realestate").handsontable("getDataAtCell",this.row,0);
              if(!selected_state){
                selected_state = "Select"
              }

              if(get_store_data(selected_state) )
              {
                process(get_store_data(selected_state))
              }
              else
              {
                $("#handsontable_block").block({
                  message : null
                });
                spinner =  create_spinner('handsontable_block');

                $.ajax({
                  url: '/suburb_list',
                  data: {
                    state: selected_state
                  },
                  dataType: "json",
                  success: function (response) {
                    process(response.data);
                    set_store_data(selected_state , response.data)
                    spinner.spin(false);
                    $('#handsontable_block').unblock();
                  }
                });
              }
            },
            strict: true
          },
          {
            data: 2,
            renderer: wrapstring
          },
          {
            data: 3,
            type: 'dropdown',
            source: function(query,process) {
              var selected_suburb = $("#realestate").handsontable("getDataAtCell",this.row,1);
              if(!selected_suburb)
              {
                selected_suburb = 'Select'
              }

              if(get_store_data(selected_suburb) )
              {
                process(get_store_data(selected_suburb))
              }
              else
              {
                $("#handsontable_block").block({
                  message : null
                });
                spinner =  create_spinner('handsontable_block');

                $.ajax({
                  url: '/postal_code',
                  data: {
                    suburb: selected_suburb
                  },
                  dataType: "json",
                  success: function (response) {
                    process(response.data);
                    set_store_data(selected_suburb , response.data);
                    spinner.spin(false);
                    $('#handsontable_block').unblock();
                  }
                });
              }
            }
          }
        ],
        contextMenu: true
      });
    }

    // I have added afterSelection event on the setting selectState. any handsontable instance have
    // with its value set to true will fire the below function  
    Handsontable.PluginHooks.add('afterSelectionEnd', function() {
      if(this.getSettings().selectState){
        var selected_cell = this.getSelected()
        var selected_state = this.getDataAtCell(this.getSelected()[0], 0)
        var selected_suburb = this.getDataAtCell(this.getSelected()[0], 1)
        var selected_post_code = this.getDataAtCell(this.getSelected()[0], 3)
        if((selected_cell[1]== 1) && (selected_suburb ==null || selected_suburb =="Select") ){
          if (selected_state == null || selected_state == "Select"){
            alert("Select a State first.")
          }
        }
        if((selected_cell[1]== 3) && (selected_post_code == null || selected_post_code =="Select") ){
          if (selected_suburb == null || selected_suburb == "Select"){
            alert("Select a Suburb first.")
          }
        }
      }
    });

</script>
<div id ="handsontable_block">
<table border="1" width="100%">
   <tr  valign="top">
      <td colspan="7" width="100%">
            <div id="realestate"  class="handsontable handsonbottom"></div>
      </td>
   </tr>
</table>
</div>

See that we have minimal HTML above, the excel is created just by calling handsontable function on the div object with ID realestate.

Now we have a functional cascading dropdown working in online excel sheet.

The above implementation demonstrate below feature

=> Cascading dropdown through ajax call.

suburb will be populated based on state and postal code will be populated based on suburb

=> Blocking GRID when ajax call in progress

when user click a state or suburb dropdown it will fire a ajax call, which will take 2 to 3 second to complete. The Grid will show a spinner indicating the ongoing ajax call in progress and also block the UI so that user do not click any other dropdown. The UI will be unblocked on completion of the ajax call

=> caching of data

say we have retrieved all the state in India, so if user again select country as India we do not need to fire the ajax call to fetch its state. It is cached with data method of jQuery

=> Cell based css

The street cell may have long text. The above code apply truncation to the string in street cell if it increases the specified size of the cell, here the extra text will be displayed with dots and when user clicks the cell the complete text will be displayed.

Reference :

http://handsontable.com/index.html
https://github.com/warpech/jquery-handsontable/wiki/Events #handsontable event
https://github.com/warpech/jquery-handsontable/wiki/Options # options
https://github.com/warpech/jquery-handsontable/wiki/Methods #methods


2 Comments

clear session or cookie on browser close

The session and cookie automatically get cleared when a user logout. But there is possibility that user close the browser without loging out . So for security reasons here, you want to clear all session data when user close the Browser . I will explian it in terms of ruby, but the basic flow remain same for any language.

STEP 1 : catch the browser close event.

I am using jQuery, but find that unload function is not working for me .The browser get closed without firing my alert. After googling for some time I find the alternative in onbeforeunload .

<script type=”text/javascript”>

window.onbeforeunload = function (){

alert(“I will do cleanup here”)

return “any string or keep it empty string”

};

</script>

so, the alert appear before the browser get closed, showing that the callback is getting fired. We will write our clean up code in its place. The return statement will trigger popup asking the user to leave the page or stay on the page. If you do not want this behaviour don’t use the return statement.

keep the script on the page you want to do cleanup, when user close the browser. If you want it for all the pages put it in your common Layout page.

STEP 2 : make a ajax call to your controller which will clear the session

<script type=”text/javascript”>

window.onbeforeunload = function (){

$.ajax({

url: “<%=session_clear_url%>”,

type: “POST”,

data: {},

contentType: “application/json; charset=utf-8”,

dataType: “json”

});

};

</script>

The URL should be of your action where you clear the session. In my case the path session_clear_url is actually a ruby syntax it is defined in Routes as below:

match 'clearSession' => 'loans#session_clear', :as => :session_clear

So,  It will go to session_clear action of the loan controller.

STEP 3: writing the cleanup code in the controller action

  def session_clear
    session[:EsignDisclosureAccepted] = nil
    session[:AccountNo] = nil
    render :text => "session cleared"
  end

So you can see that I have set EsignDisclosureAccepted and AccountNo data in session to null, which is equivalent to deleting it. You can do any other cleanup here as provided by language you are using.


Leave a comment

custom file uploader

In this post we will discuss input type = “file” and learn to style it in our own way. This input filed is displayed as a text_filed followed by browse button. The html markup for a File Uploader is as below.

<input id="loan_attachments_file" type="file" name="loan[attachments][file]" />

The problem, with this default approach is that it is rendered by different browser in different way.
For example : this is how it looks in Mozilla and Chrome .

File Uploader display in chrome

NOTE : the complete HTML structure for the images below is given in step 4 towards the end

file_uploader_chrome

file_uploader_chrome

File Uploader display in Firefox.

firefox_file_uploader

firefox_file_uploader

So, you can see that the look and feel of uploader is different in different browser. We will now customize the uploader, with our own css, so that it look consistent in all the browser. We will follow the below approach.

STEP 1: Hide the default File Uploader

<input type="file" style="display:none;" name="loan[attachments][file]" 
id="loan_attachments_file" />

STEP 2: Create HTML structure similar to default one

create a text field followed by browse button or link. Obviously, you can apply any css look to the text-filed or the browse button.

I have used below HTML structure. showing only the customized uploader code

<div>
  <div>
    <input type="text" value="Please select a file" id="filename" />
    <label id="upload_doc_label" for="loan_attachments_file">Browse</label>
    <input type="file" style="display:none;" name="loan[attachments][file]" id="loan_attachments_file" />
  </div>
</div>

So here we have provided our own custom uploader and hidden the default uploader. But I have found that, in IE8 clicking on the browse label do not trigger the default file input we have marked as hidden as IE8 do not recognize element with display:none . since, our aim is to do not show the default file uploader, we can achieve it with setting its absolute position outside the view. The correct markup working in all IE version and browser is as below.

<div>
  <div>
    <input type="text" value="Please select a file" id="filename" />
    <label id="upload_doc_label" for="loan_attachments_file">Browse</label>
      <input type="file" style="display:none;" name="loan[attachments][file]" id="loan_attachments_file" 
      style= "-moz-opacity:0;filter:alpha(opacity:0);opacity:0; position:absolute; top:-50px"/>
  </div>
</div>

It look as below in all the browser

customized_file_uploader

customized_file_uploader

STEP 3: Trigger click event on the hidden file input

Here You should remember that, the default file input type do below two things.

=> on clicking browse button open up file navigation window for selecting file

=>on selecting a file, it populate the text filed with path of the selected file

Now, we will control these two aspect, with JavaScript code .

=> when user click custom browse button, we will trigger click event on the hidden field input

    if($.browser.mozilla) {
        $( "#upload_doc_label" ).on( "click", function(e) {
            e.preventDefault();
            $('#loan_attachments_file').trigger('click');
        });
    }

Here come the tricky part. You can see that, I have triggered the click event only when browser is mozilla. This is because, mozilla do not support for tag on lable while chrome and IE support it. Since we have made the browse button as label, whose for attribute have id of the hidden file type, when ever user click the browse lable, the hidden file input field automatically get selected and click event get fired on it. This do not happen in case of mozila, so we have explicitly triggered it.

Now, the question arise what is wrong if we allow it for IE and chrome also. Well if you do that, you have to search in google “Custom File Uploader not working in IE” . I mean the uploader not at all work in IE , though it will work in chrome and Firefox . The issue is discussed in depth here on the stackoverflow.

NOTE : browse button is designed as lable so that IE trigger click event on the file input itself rather then explicitly through the code.

=> Populate the custom text field with the file path of the selected file.

    $( "#loan_attachments_file" ).on( "change", function(e) {
        var temp = $(this).val().split('\\').length;
        var filename = $(this).val().split('\\')[temp - 1];
        $('#filename').val(filename);
    });

STEP 4 : click the upload button to upload the file

Below is the code having file field within a form with a upload and a cancel button

<form method="post" id="edit_loan_349" enctype="multipart/form-data" action="/loan_documents">
  <div>
    <div>
      <input type="text" value="Please select a file" id="filename" />
      <label id="upload_doc_label" for="loan_attachments_file">Browse</label>
    </div>
  </div>
  <input type="file" style="display:none;" name="loan[attachments][file]" id="loan_attachments_file"  
  style= "-moz-opacity:0;filter:alpha(opacity:0);opacity:0; position:absolute; top:-50px"/>
  <div>
    <div>
      <input type="submit" value="Upload" name="commit" id="upload_doc" />
      <a href="/loan_documents/document_listing">Cancel</a>
    </div>
  </div>
</form>


1 Comment

page vertical auto scroll jquery

One of my form have many inputs and user need to scroll down to reach till the end of form. I need to implement auto scrolling, for the page, so that when user, reach a input filed at the bottom of page, it automatically scroll up to expose the remaining fields. Below is the code, which use jQuery animate to implement vertical auto scroll. We have scroll_to method, which take two argument base and target . base is the element to be animated, target is used to calculate height from top , we need to scroll up . So, say as in  below example, when user click on city input box which is at the bottom, the page will move up, taking city input to top of the page, So exposing other input element below it. Similarly, I have a popup in which designation input is at bottom, so scroll_top is called on it. like $(‘html,body’) or $(‘.modal-body’) you can trigger auto_scroll on any element, provided that element have certain height set,which is less then required by whole content, triggering the scrollbar.

jQuery(function(){

   $(document).ready(function(){
     #when user click on city input scroll_to method will be called, which will
      scroll the city input to the top, showing all other input below it
     $("#city").on('focus',function(){
     scroll_to($('html,body'), $(this));
     });
     #when user click on designation input in the popup scroll_to method will be called, 
     which will scroll the city input to the top, showing all other input below it
     $("#designation").on('focus',function(){
     scroll_to($('.modal-body'), $(this));
     });
  })

  function scroll_to(base, target){
    base.animate({
    scrollTop: target.position().top
     },1000);
    }
})

I have implemented, the autoscroll basically for my popup. The problem, I faced there is that, the target(designation input) top position keep changing with each scroll. say, first time the popup get open, I clicked designation input (say it is 500px below the top), it moved up, so now it is 0 px from top, now you close the popup, again open the popup, now this time scroll not work as, it will try to move 0 px, which is equal to no scroll. So, I modified the scroll_to method as below.

function scroll_to(base, target){
   base.animate({
      scrollTop: target.position().top + parseInt(base.scrollTop())
   },1000);
}

Here, the height to scroll, will be sum of target position and height of the scroll relative to the base.

If, above auto scroll, don’t solve your problem. jQuery plugins are available with advance functionality. checkout this for vertical scroll and this for horizontal scroll

Referance:

http://stackoverflow.com/questions/4034659/is-it-possible-to-animate-scrolltop-with-jquery

http://api.jquery.com/offset/

http://api.jquery.com/position/

http://stackoverflow.com/questions/3202008/jquery-difference-between-position-and-offset

http://api.jquery.com/animate/

http://api.jquery.com/scrollTop/


1 Comment

jQuery datpicker with date of Birth validation

We often have date element in our forms, like for entering date of birth, joining date, delivery date etc. We provide, the user with a calender to enter the date. A number of plugin available out there for entering date to form input. But, We will use datepicker plugin of jQuery here. We will then validate the date enter by the user on the form submit. Note, that a date selected from datepicker is always a valid date fromat, but user can change it manually in the text box if he want and enter some wrong date… testing person always do it 🙂

Lets below is our simple form with one input field with id = dob and a submit button.

<form>
   <label>Your Date of Birth</label>
   <input type="text" value="" id="dob" />
   <input type="submit" value="Submit" id="form-submit" />
</form>

We will going to validate date for its correct format (assuming dd-mon-yyyy format) and other things like, it should not be less than the current date.

jQuery(function(){
  //attach the datepicker to input with id dob after page load
  $(document).ready(function(){
  //yearRange will determine no of years to show in year select box. currently it will list 100 year 
  from now .  setting changeMonth and changeYear to true will show month and year box in calender, 
  otherwise user will able to select day only.The format given here means date should be like 
  13-May-2013. all possible format is available here

  $("#dob").datepicker({
          yearRange: "-100:+0",
          changeMonth: true,
          changeYear: true,
          dateFormat : "d-M-yy",
          # onchangeMonthYear code below will refelct the date change as soon as user
           change year or month.If you do not use it date will cahnge only after
           select a day
          onChangeMonthYear: function(year, month, day){
            $(this).datepicker('setDate', new Date(year, month-1, "1"))
            var selectedDate = '1';
            if($(this).datepicker('getDate')!=null){
                selectedDate = $(this).datepicker('getDate').getDate();
            }
            $(this).datepicker('setDate', new Date(year, month-1, selectedDate));
          }
       });

    })

  // defining the function doing the date validation .our date format should be like 13-May-2013
  var validated_dob = function(dob){
             var dob_split = dob.split("-") #split the dob string into array
             var validated = "Yes"
             var msg = "Date of Birth is valid"
             var month_array = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
                                'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]

             // make sure that day year and month entered or return
             if(dob_split.length != 3){
                            validated = "NO"
                            msg = "Date of Birth must be entered in this format: DD-MON-YYYY"
                            return [validated, msg]
                }

             // make sure that day month and year in DD-MON-YYYY format
            if ((dob_split[2].length != 4) || (dob_split[1].length != 3) || 
                                 !(dob_split[0].length == 2 || dob_split[0].length == 1)){
                            validated = "NO"
                            msg = "Date of Birth must be entered in this format: DD-MON-YYYY"
                            return [validated, msg]
                        }

             // make sure that day month have only integer
             if((!(/^[0-9]+$/).test(dob_split[0])) || (!(/^[0-9]+$/).test(dob_split[2]))){
                           validated = "NO"
                           msg = "Date of Birth day and year can have only numeric value"
                           return [validated, msg]
              }

             // make sure that day is in range 1-31
             if(dob_split[0] > 31 || dob_split[0] <= 0){
                            validated = "NO"
                            msg = "Date of Birth day should have value in 1-31"
                            return [validated, msg]
               }

            // make sure that month entered as Jan Feb etc
            if($.inArray(dob_split[1],month_array ) < 0){
                           validated = "NO"
                           msg = "Date of Birth month should be in ['Jan', 'Feb', 'Mar', 'Apr', 'May', 
                                                    'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]"
                           return [validated, msg]
             }

           // these variable are defined here instead of top because datepicker
           // can parse only a valid date, so if date is wrong it will throw error
           var user_dob =$.datepicker.parseDate("d-M-yy", dob)
           var today_date = new Date()
           if(user_dob > today_date){
                           validated = "NO"
                           msg = "Your Date of Birth can't be less than Today's Date"
                           return [validated, msg]
            }

           // if all condition pass, return original value at top
           return [validated, msg]
        }

       // validate the date on form submit
    $("#form-submit").click(function(e){
              // collect value return by validated_dob in a variable
              var check_dob = validated_dob(dob)
              // if validation return No, alert the error message and return
              //false i,e prevent the form from submit
              if (check_dob[0] == "NO"){
                            alert(check_dob[1])
                            return false
               }
     })
})

Referance:

http://api.jquery.com/Types/ # will tell you all type of data in jquery

http://api.jquery.com/Types/#Array # detail on jQuery array
http://api.jquery.com/jQuery.inArray/ # matching a element in array, will return -1 if no element matched or index of element if matched
http://api.jquery.com/category/miscellaneous/dom-element-methods/ # list method for traversing dom
http://api.jqueryui.com/datepicker/ # detail documentation of datepicker