Author Topic: Refresh data at each edit but preserve undo/redo functionality  (Read 3236 times)

qcdatabase.com

  • Pro Enterprise
  • Newbie
  • *
  • Posts: 5
    • View Profile
Hello, I just bought the pro version and I love what you guys have built here.

My code below is loosely based on the Inline-editing/Auto-save demo (I simplified it for this post). We will have multiple admins who will be editing this data throughout the workday. I would like the data to be refreshed at each edit to show the work of other users . I also want to preserve the undo/redo functionality.

In my code below I call $("#grid_container").pqGrid("refreshDataAndView"); at each successful update to the data. Under the history: method the console will log the ui object showing num_undo:1 and then immediatly showing num_undo:0. If I comment out the $("#grid_container").pqGrid("refreshDataAndView"); line of code the undo/redo function works perfectly. Am I refreshing the data in the wrong way? Is there a way to have both features?

//FROM CHROME CONSOLE
{type: "add",      canUndo: true,    canRedo: undefined,    num_undo: 1,     num_redo: 0}
{type: "reset",    canUndo:false,    canRedo: false,           num_undo: 0,     num_redo: 0}


//MY CODE
feed = function(arg = "") {
    $('.feedback').html(arg);
};
var $grid,
    colModel = [
        { title: "Job name",dataIndx: "job_name",minWidth: 180,dataType: "stringi"},
        {
            title: "Package name",
            dataIndx: "pkg_name",
            minWidth: 180,
            dataType: "stringi",
            filter: {
                type: "textbox",
                condition: "begin",
                listeners: ["keyup"]
            }
        },
        {title: "type",dataIndx: "pkg_type",minWidth: 36,dataType: "stringi"},
        {title: "Package description",dataIndx: "description",minWidth: 220,dataType: "stringi"]},
        {title: "Spec",dataIndx: "spec",minWidth: 100,dataType: "stringi"]}
    ],
    obj = {
      toolbar: {
          items: [
              {
                  type: 'button',
                  icon: 'ui-icon-arrowreturn-1-s',
                  label: 'Undo',
                  cls: 'changes',
                  listener: {
                      "click": function(evt, ui) {
                          $grid.pqGrid("history", {
                              method: 'undo'
                          });
                      }
                  },
                  options: {
                      disabled: true
                  }
              },
              {
                  type: 'button',
                  icon: 'ui-icon-arrowrefresh-1-s',
                  label: 'Redo',
                  listener: {
                      "click": function(evt, ui) {
                          $grid.pqGrid("history", {
                              method: 'redo'
                          });
                      }
                  },
                  options: {
                      disabled: true
                  }
              },
              {
                  type: 'separator'
              },
              {
                  type: "<span class=\"feedback\"></span>"
              }
          ]
      },
    create: function(evt, ui) {
        var opts = [];
        for (var i = 0; i < colModel.length; i++) {
            var column = colModel;
            if (column.hidden !== true) {
                opts.push(column.dataIndx);
            }
        }
        $(".columnSelector").val(opts);
    },
    height: 550,
    resizable: true,
    editor: {
        type: 'textbox'
    },
    editorKeyDown: function(evt, ui) {
        if (evt.keyCode == 13) {
            evt.originalEvent.keyCode = 40;
        }
    },
    numberCell: {
        show: true
    },
    trackModel: {
        on: true
    }, //to turn on the track changes.   
    historyModel: {
        checkEditableAdd: true
    },
    editModel: {
        allowInvalid: true,
        saveKey: $.ui.keyCode.ENTER,
        uponSave: 'next'
    },
    editor: {
        select: true
    },
    title: "<b>Hydro log</b>",
    change: function(evt, ui) {
        if (ui.source == 'commit' || ui.source == 'rollback' || !("updateList" in ui)) {
            alert("ERROR: Could not format edit data. Please refresh this page.");
            return;
        }
        var grid = $grid.pqGrid('getInstance').grid;
        var t_name = $('#t_container').find('input').first().attr('name');
        var t_val = $('#t_container').find('input').first().val();
        if (ui.addList.length || ui.updateList.length || ui.deleteList.length) {
            var send_data = {
                table: 'hydro',
                par: '0',
                list: JSON.stringify({
                    updateList: ui.updateList,
                    addList: ui.addList,
                    deleteList: ui.deleteList
                })
            };
            send_data[t_name + ""] = t_val;
            $.ajax({
                url: 'grid_update.php',
                data: send_data,
                dataType: "json",
                type: "POST",
                async: true,
                beforeSend: function(jqXHR, settings) {
                    feed('Saving...');
                },
                success: function(feedback) {
                    $("#grid_container").pqGrid("refreshDataAndView"); // If I comment this out, the undo/redo functionality works fine.
                    feed(feedback.feedback);
                },
                error: function(XMLHttpRequest, textStatus, errorThrown) {
                    console.log(XMLHttpRequest, "Status: " + textStatus, "Error: " + errorThrown);
                },
                complete: function() {
                    $grid.pqGrid("commit", {
                        type: 'update'
                    });
                }
            });
        }
    },
    history: function(evt, ui) {
        console.log(ui);
        if (ui.canUndo != null) {
            $("button.changes", $grid).button("option", {
                disabled: !ui.canUndo
            });
        }
        if (ui.canRedo != null) {
            $("button:contains('Redo')", $grid).button("option", "disabled", !ui.canRedo);
        }
        $("button:contains('Undo')", $grid).button("option", {
            label: 'Undo (' + ui.num_undo + ')'
        });
        $("button:contains('Redo')", $grid).button("option", {
            label: 'Redo (' + ui.num_redo + ')'
        });
    },
    colModel: colModel,
    dataModel: {
        location: "remote",
        sorting: "local",
        dataType: "JSON",
        method: "GET",
        rPP: 100000,
        recIndx: "id",
        url: "grid_json.php?table=hydro",
        getData: function(dataJSON) {
            return {
                data: dataJSON
            };
        }
    },
    load: function(evt, ui) {
        var grid = $grid.pqGrid('getInstance').grid;
        var data = grid.option('dataModel').data;
        $(this).pqTooltip();
        var ret = grid.isValid({
            data: data,
            allowInvalid: false
        });
    },
    refresh: function() {
        $("#grid_container")
            .find("button.delete_btn")
            .button({
                icons: {
                    primary: 'ui-icon-scissors'
                }
            })
            .unbind("click")
            .bind("click", function(evt) {
                var $tr = $(this).closest("tr");
                var rowIndx = $grid.pqGrid("getRowIndx", {
                    $tr: $tr
                }).rowIndx;
                $grid.pqGrid("deleteRow", {
                    rowIndx: rowIndx
                });
            });
    }
};
$(function() {
    $grid = $("#grid_container").pqGrid(obj);
});

paramvir

  • Administrator
  • Hero Member
  • *****
  • Posts: 6263
    • View Profile
Re: Refresh data at each edit but preserve undo/redo functionality
« Reply #1 on: June 11, 2018, 06:47:30 am »
Only the changes which are made through addRow, updateRow, deleteRow API methods are added to history.

refreshDataAndView refreshes / overwrites the whole data in grid independently of history undo/redo.

So no they can't be expected to work together.

qcdatabase.com

  • Pro Enterprise
  • Newbie
  • *
  • Posts: 5
    • View Profile
Re: Refresh data at each edit but preserve undo/redo functionality
« Reply #2 on: June 15, 2018, 04:29:53 pm »
Thanks for the response. I know it is an unusual ask. If anyone else has this need and is interested, I came up with a script for creating a parallel edit history.  It basically just saves ajax request objects to global undo_array and redo_array. When a user clicks the undo link, the ui.updateList is reversed so that the newRow and oldRow objects are switched with each other. This is done just before submitting them. When they are submitted to the server again from the undo_array, the object is un shifted to the redo_array before newRow and oldRow are switched.

The below script will allow updating all data on every edit and then reversing your changes without affecting the changes made by other users.

// utilities JavaScript file
/* Globals */
var $grid,
   undo_array    = [],
   redo_array    = [];
/* Extensions */
Date.prototype.toDateInputValue = (function() {
    var local = new Date(this);
      local.setMinutes(this.getMinutes() - this.getTimezoneOffset());
    return local.toJSON().slice(0,10);
});
Date.prototype.toTimestamp = (function() {
    var local = new Date(this);
      local.setMinutes(this.getMinutes() - this.getTimezoneOffset());
    return local.toJSON().replace('T',' ').slice(0,19);
});
feed = function(arg = ''){
   $('.feedback').html(arg);
};
undo = function(){
   if(undo_array.length){
      var use_obj      = {},
         shift_obj    = undo_array.shift();          // Pull the last request which was saved to here.
      redo_array.unshift(shift_obj);               // Move it to the redo_array untouched.
      var changes      = JSON.parse(shift_obj.list);   // Open up the stringified list property in order to flip newRow with oldRow properties.
      /* ONLY break down and re-organize list.updateList for now */
      if('updateList' in changes && changes.updateList.length){
         for(var i=0; i<changes.updateList.length; i++){
            if('oldRow' in changes.updateList && 'rowData' in changes.updateList){
               var newRowInfo = JSON.parse(JSON.stringify(changes.updateList.oldRow));
               var oldRowInfo = JSON.parse(JSON.stringify(changes.updateList.oldRow));
               changes.updateList.newRow = oldRowInfo;
               changes.updateList.oldRow = newRowInfo;
            }
         }
      }
      use_obj.table    = JSON.parse(JSON.stringify(shift_obj.table));
      use_obj.par    = JSON.parse(JSON.stringify(shift_obj.par));
      use_obj.list   = JSON.stringify(changes);
      post_to_model(use_obj,true);
      feed('...undoing changes');
   }else{
      feed('No UNDO information found.');
   }
   /*   NOTE:
      FORMAT OF ARG OBJECT AS SUBMITTED TO THIS FUNCTION FROM PAGE SCRIPT.
      CREATE SIMILAR OBJECT FOR UNDO ARRAY. DO NOT UN-COMMENT!
      var send_data = {
         table:    \''.$this->table.'\',
         par:    \''.$this->par.'\',
         list: JSON.stringify({
            updateList: ui.updateList,
            addList:    ui.addList,
            deleteList: ui.deleteList
         })
      };
   */

};
redo = function(){
   if(redo_array.length){
      post_to_model(redo_array.shift(),false,true);
      feed('...redoing');
   }else{
      feed('<span class="ui-icon ui-icon-warning"></span> No REDO information found.');
   }
};
post_to_model = function(arg = {}, is_undo = false){
   $('.undo_link').add('.redo_link').remove();
   var t_name   = $('#t_container').find('input').first().attr('name');
   var t_val   = $('#t_container').find('input').first().val();
   arg[t_name + ""] = t_val;
   arg.autodatetime    = new Date().toTimestamp();

   /* Load changes into undo array */
   if(!is_undo){ // Skip this step if the data submitted to the form is from the undo_array
      var und_obj       = {};
         und_obj.table    = 'table' in arg?    JSON.parse(JSON.stringify(arg.table)):    '';
         und_obj.par    = 'par' in arg?    JSON.parse(JSON.stringify(arg.par)):    '';
         und_obj.list    = JSON.stringify(JSON.parse(arg.list));
         undo_array.unshift(und_obj);
   }
   console.log('undo_array',undo_array);
   console.log('redo_array',redo_array);
   /* END Load undo array */
   
   $.ajax({
      url: 'grid_update.php',
      data: arg,
      dataType: "json",
      type: "POST",
      async: true,
      beforeSend: function (jqXHR, settings) {
         feed('Saving...');
      },
      success: function(feedback){
         $("#grid_container").pqGrid("refreshDataAndView");
         var grid = $grid.pqGrid("getInstance").grid;
         reset_undo_redo();
         feed(feedback.feedback);
      },
      error: function(XMLHttpRequest, textStatus, errorThrown){
         console.log(XMLHttpRequest,"Status: " + textStatus,"Error: " + errorThrown);
      },
      complete: function(){
         /* Global */
         $grid.pqGrid("commit",{type: 'update'});
      }
   });
};
reset_undo_redo = function(){
   var undoer = false, redoer = false;
   if(undo_array.length){
      undoer = $('<a class="undo_link pull-right ui-button ui-widget ui-state-default ui-corner-all ui-button-text-icon-primary" style="margin-left:20px;"> <span class="ui-button-text">Undo (' + undo_array.length + ')</span></a>');
      undoer.click(undo);
   }
   if(redo_array.length){
      redoer = $('<a class="redo_link pull-right ui-button ui-widget ui-state-default ui-corner-all ui-button-text-icon-primary" style="margin-left:20px;"> <span class="ui-button-text">Redo (' + redo_array.length + ')</span></a>');
      redoer.click(redo);
   }
   if(redoer){
      $('.pq-toolbar').append(redoer);
   }else{
      $('.pq-toolbar').append('<a class="undo_link pull-right ui-button ui-widget ui-state-default ui-corner-all ui-button-text-icon-primary" style="margin-left:20px;opacity:0.4;"><span class="ui-button-text">Redo (0)</span></a>');
   }
   if(undoer){
      $('.pq-toolbar').append(undoer);
   }else{
      $('.pq-toolbar').append('<a class="undo_link pull-right ui-button ui-widget ui-state-default ui-corner-all ui-button-text-icon-primary" style="margin-left:20px;opacity:0.4;"><span class="ui-button-text">Undo (0)</span></a>');
   }
};
$(document).keydown(function(e) {
   js_ticker      = 0;
   switch(e.keyCode){
      /* Z */
      case 90:
            if(e.ctrlKey){
               e.preventDefault();
               $('.undo_link').trigger('click');
            }
         break;
      /* Y */
      case 89:
            if(e.ctrlKey){
               e.preventDefault();
               $('.redo_link').trigger('click');
            }
         break;
      default:;
   }     
});   

// Script

var colModel = [{title: "Job name",dataIndx: "job_name", minWidth: 180, dataType: "stringi"... and so on.
var obj = {
   toolbar: {
      items: [
         {
            type: "<span class=\"feedback\"></span>"
         }
      ]
   },
   create: function(evt, ui) {
      var opts = [];
      for (var i = 0; i < colModel.length; i++) {
         var column = colModel;
         if (column.hidden !== true) {
            opts.push(column.dataIndx);
         }
      }
      $(".columnSelector").val(opts);
   },
   height: 550,
   resizable: true,
   rowBorders: false,
   editor: {
      type: 'textbox'
   },
   virtualX: true,
   editorKeyDown: function(evt, ui) {
      if (evt.keyCode == 13) { //enter key.
         evt.originalEvent.keyCode = 40; //disguise down key.
      }
   },
   numberCell: {
      show: true
   },
   trackModel: {
      on: true
   }, //to turn on the track changes.   
   historyModel: {
      checkEditableAdd: true
   },
   editModel: {
      allowInvalid: true,
      saveKey: $.ui.keyCode.ENTER,
      uponSave: 'next'
   },
   editor: {
      select: true
   },
   title: '<b>Table</b>',
   stripeRows: true,
   change: function(evt, ui) {
      //debugger;
      if (ui.source == 'commit' || ui.source == 'rollback' || !("updateList" in ui)) {
         alert("ERROR: Could not format edit data. Please refresh this page.");
         return;
      }
      if (ui.addList.length || ui.updateList.length || ui.deleteList.length) {
         var send_data = {
            table: 'hydro',
            par: '0',
            list: JSON.stringify({
               updateList: ui.updateList,
               addList: ui.addList,
               deleteList: ui.deleteList
            })
         };
         post_to_model(send_data);
      }
   },
   filterModel: {
      on: true,
      mode: "AND",
      header: true
   },
   colModel: colModel,
   dataModel: {
      location: "remote",
      sorting: "local",
      dataType: "JSON",
      method: "GET",
      rPP: 100000,
      recIndx: "id",
      url: "grid_json.php?table=hydro&par=0",
      getData: function(dataJSON) {
         return {
            data: dataJSON
         };
      }
   },
   load: function(evt, ui) {
      var grid = $grid.pqGrid('getInstance').grid;
      var data = grid.option('dataModel').data;
      $(this).pqTooltip();
      var ret = grid.isValid({
         data: data,
         allowInvalid: false
      });
   },
   refresh: function() {
      $("#grid_container")
         .find("button.delete_btn")
         .button({
            icons: {
               primary: 'ui-icon-scissors'
            }
         })
         .unbind("click")
         .bind("click", function(evt) {
            var $tr = $(this).closest("tr");
            var rowIndx = $grid.pqGrid("getRowIndx", {
               $tr: $tr
            }).rowIndx;
            $grid.pqGrid("deleteRow", {
               rowIndx: rowIndx
            });
         });
   }
};
$(function() {
   $grid = $("#grid_container").pqGrid(obj);
   setTimeout(function() {
      reset_undo_redo();
   }, 0);
});