Search code examples
javascriptjqueryjqgridlocal-storage

Set and use localStorage to retain checkbox selections during form edit


I have a form that uses jqGrid for a section of the form. This section displays user info that can be selected via the checkbox. On the review page there is an option to edit the form and I'm trying to retain the checkbox selections during this process.

I'm trying to use window.localStorage for this however I'm not sure how to setItem properly. I'm calling getItem in the loadComplete and my coding may not be quite correct here either. What I have now, when a user goes to edit, selects the first item in the jqGrid instead of the actual saved item. I'm guessing because I haven't properly setItem. Can anyone offer guidance?

$.jgrid.gridUnload("#list");

myGrid = $("#list").jqGrid({
    url: baseURL + '/scripts/get_user_list.php' + urlString,
    datatype: "json",
    mtype: 'POST',
    width: 660,
    height: '100%',
    pager: '#pager',
    rowNum: 10,
    rowList: [20, 30, 40, 50, 60],
    sortname: 'id',
    sortorder: "asc",
    viewrecords: true,
    multiselect: true,
    repeatitems: false,
    imgpath: '/scripts/jquery-ui/images',

    colNames: ['id', 'Building', 'Company ID', 'Description', 'Individual Name', 'SECCode'],
    colModel: [
        {name: 'id', index: 'id', jsonmap: 'id', hidden: true, width: 20},
        {name: 'Building', index: 'Building', jsonmap: 'Building', hidden: true, width: 20},
        {name: 'CompanyId', index: 'CompanyId', jsonmap: 'CompanyId', width: 110},
        {name: 'Description', index: 'Description', jsonmap: 'Description', sortable: true, width: 300},
        {name: 'IndName', index: 'IndName', jsonmap: 'IndName', width: 200},
        {name: 'UsrNum', hidden: true, index: 'UsrNum', jsonmap: 'UsrNum'}
    ],
    jsonReader: {
        repeatitems: false,
        root: 'rows',
        id: '0',
        cell: '',
        subgrid: {
            root: 'rows',
            id: '0',
            cell: '',
            repeatitems: false
        }
    },

    // subgrid support
    subGrid: true,
    subGridUrl: baseURL + '/scripts/get_user_list.php' + urlString,
    subGridModel: [{
        name: ['Date', 'ID'],
        params: ['CompanyId'],
        align: ['center', 'right'],
        width: [150, 80]
    }
    ],

    ondblClickRow: function (id) {
        $('#recId').val(id);
    },


    beforeRequest: function () {
        blnGridLoading = true;
        // console.log("beforeRequest(); setting blnGridLoading=true");
        fnValidateAccount(); // Check that user has data available
    },
    loadComplete: function () {

        $(':checkbox').each(function () {
            var status = localStorage.getItem(this.id) === "false" ? false : true;
            $(this).prop("checked", status);
        });

        blnGridLoading = false;
        // console.log("loadcomplete(); setting
        blnGridLoading = false;
        // ");

        for (swap in arySwap) {
            if (typeof arySwap[swap]['CompanyId'] != 'undefined') {

                $("#list").jqGrid('setSelection', arySwap[swap]['CompanyId']); // select companyId
            }
        }
        fnValidateAccount(); // Check that user has data available

    },
});

Here's the localStorage.getItem in loadComplate, isolated from the rest of the code:

$(':checkbox').each(function () {
    var status = localStorage.getItem(this.id) === "false" ? false : true;
    $(this).prop("checked", status);
});

Here's what I tried for setItem and where I'm not sure where to place this or if this is the correct way to go.

$(':checkbox').on('change', function () {
    //set the check value of checkbox
    localStorage.setItem(this.id, this.checked);
});

Solution

  • Placement

    Your current placement of the getItem is correct. You should put the setItem block there too. Inside the loadComplete which means once the jqGrid is loaded, your getItem would run, also the setItem events would be bound to the checkboxes.

    localStorage mechanism

    localStorage only supports storing key value pairs and the values can be String only. Which means if you set a key customCheckbox to true and run a query for that key, the return value would be "true"

    localStorage.setItem('test', true); // item set
    localStorage.getItem('test') // returns "true"; string, not boolean.
    

    However, there is a way to define one key and store an object to that. You should use JSON.stringify() before storing and JSON.parse() after retrieving.

    let person = {name: 'John', age: 30, weight: 60} // a pure object.
    localStorage.setItem('testPerson', JSON.stringify(person)); // object stringified;
    
    localStorage.getItem('testPerson') // returns a stringified version of 'person' object;
    
    // to use the stored stringified object like a regular object, you need to parse it.
    person = JSON.parse(localStorage.getItem('testPerson'));
    

    In your code

    You have used different keys for each different checkboxes, thus your keys and values are wide open for the whole script or some other script on the same page. You might accidentally end up deleting or modifying the values of those keys, or maybe not. Also, other pages can modify those items, as they are on localStorage. So try to set as unique keys as possible.

    Note: Open the browser inspector and check the storage tab to check your localStorage status, there might be a panel at the left with sessionStorage and localStorage

    Logic

    Now let's talk about your current logic. It looks like at the initial page load, when there is no keys sot to localStorage, all your checkbox would be checked. Is that intended ?

    Even if it is, let's break it down:

    var status = localStorage.getItem(this.id) === "false" ? false : true;
    

    localStorage.getItem('key') would return null if there is no such key set. so null === "false" returns false and your ternary logic sets the status to true

    Hence:

    $(this).prop("checked", status); // sets all the checkboxes to checked at initial page load;
    

    What you should do is:

    • Check if the key query returns a value with the typeof object, as null is a typeof object
    • If it's an object, set the status to false immediately. because when you set the value to true or false it'll be a string type, not object.
    • If it's not an object type, then check the value comparing to 'true' and 'false' and set accordingly.

    Here is the final logic:

    let status = typeof localStorage.getItem($(this).attr('id')) === 'object' ? false : localStorage.getItem($(this).attr('id')) === 'true';
    

    Snippet

    Finally some code, have a look at the snippet, Note that the browser will return an error, because an iframe trying to modify localStorage. but extract the code at your end to view the results.

    $(document).ready(function() {
      $(':checkbox').each(function() {
        let status = typeof localStorage.getItem($(this).attr('id')) === 'object' ? false : localStorage.getItem($(this).attr('id')) === 'true';
        $(this).prop('checked', status);
        // console.log(status, typeof localStorage.getItem($(this).attr('id')))
      });
    
      // setItem.
    
      $(':checkbox').on('change', function() {
        //set the check value of checkbox
        localStorage.setItem($(this).attr('id'), $(this).prop('checked'));
        console.log('updated, ', $(this).attr('id'));
        console.log(localStorage);
      });
    
      $('#clear').on('click', function() {
        localStorage.clear();
      });
    });
    h3 {
      margin: 40px 0;
    }
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script src='https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js'></script>
    
    
    <div class="container">
      <div class="row">
        <div class="col-12">
          <h3>Checkboxes status to LocalStorage</h3>
          <div class="custom-control custom-checkbox">
            <input type="checkbox" class="custom-control-input" id="customCheck1">
            <label class="custom-control-label" for="customCheck1">One</label>
          </div>
        </div>
        <div class="col-12">
          <div class="custom-control custom-checkbox">
            <input type="checkbox" class="custom-control-input" id="customCheck2">
            <label class="custom-control-label" for="customCheck2">Two</label>
          </div>
        </div>
        <div class="col-12">
          <div class="custom-control custom-checkbox">
            <input type="checkbox" class="custom-control-input" id="customCheck3">
            <label class="custom-control-label" for="customCheck3">Three</label>
          </div>
        </div>
        <div class="col-12">
          <div class="custom-control custom-checkbox">
            <input type="checkbox" class="custom-control-input" id="customCheck4">
            <label class="custom-control-label" for="customCheck4">Four</label>
          </div>
        </div>
        <div class="col-12">
          <div class="custom-control custom-checkbox">
            <input type="checkbox" class="custom-control-input" id="customCheck5">
            <label class="custom-control-label" for="customCheck5">Five</label>
          </div>
        </div>
        <div class="col-12">
          <div class="custom-control custom-checkbox">
            <input type="checkbox" class="custom-control-input" id="customCheck6">
            <label class="custom-control-label" for="customCheck6">Six</label>
          </div>
        </div>
        <div class="col-12">
          <div class="custom-control custom-checkbox">
            <input type="checkbox" class="custom-control-input" id="customCheck7">
            <label class="custom-control-label" for="customCheck7">Seven</label>
          </div>
        </div>
        <div class="col-12">
          <div class="form-group"></div>
          <button id="clear" type="button" class="btn btn-danger">Clear LocalStorage</button>
        </div>
      </div>
    </div>