Smart-Table icon indicating copy to clipboard operation
Smart-Table copied to clipboard

stPersist stores but does not restore multiple search predicates

Open skidvd opened this issue 8 years ago • 13 comments

I am using the stPersist directive as referenced on the smart-table-website as "Persist table state in local storage". I am using this in combination with smart-table version 2.1.0.

In my use case, I am applying multiple search predicates concurrently. stPersist reliably stores all of the predicates upon examination of the localStorage item created/updated. However, upon subsequent accesses to the stTable associated with that item, the net result is that only one of the multiple predicates appears to survive/be restored [ultimately!]. My debugging efforts to date show more detail below. In short, the collection of predicates (all that were initially saved) is initially successfully retrieved (and applied) from storage. However, something appears to apply each of them individually, resulting in the net result of only the last one being preserved.

For example, if I begin by applying 3 concurrent search predicates; inspection of both ctrl.tableState and the associated localStorage entry reflect the expected data (inclusive of status, name and location/division filters):

{"sort":{"predicate":"id","reverse":false},"search":{"predicateObject":{"status":"6","name":"JT","location":{"division":"1"}}},"pagination":{"start":0,"number":5,"numberOfPages":1}}

Subsequent access to the same table reflects the following story:

  1. Initial retrieval of storage item from debugging stPersist appears correct:
{"sort":{"predicate":"id","reverse":false},"search":{"predicateObject":{"status":"6","name":"JT","location":{"division":"1"}}},"pagination":{"start":0,"number":5,"numberOfPages":4}}
  1. I then see that stTable's pipe() is correctly invoked with the combination of all 3 stored items:
pipe has tableState: {
    "status": "6",
    "name": "JT",
    "location": {
        "division": "1"
    }
}
  1. However, the stSearch observe statements then override this individually resulting in only one ultimately being applied. The stSearch directive's $observe triggers multiple calls to tableCtrl.search() [one for each of the 3 predicates individually, rather than a single combined invocation].
       attr.$observe('stSearch', function (newValue, oldValue) {
          var input = element[0].value;
          if (newValue !== oldValue && input) {
            ctrl.tableState().search = {};
console.log('stSearch observe... ' + input + ' ' + newValue + ' ' + oldValue);
            tableCtrl.search(input, newValue);
          }
        });

Produced output:

stSearch observe... JT name undefined stSearch observe... 1 location.division undefined stSearch observe... 6 status undefined

  1. The above ultimately calls the pipe() function [again, one for each of the 3 predicates individually, rather than a single combined invocation]
    this.pipe = function pipe () {
console.log('pipe has tableState: ' + JSON.stringify(tableState.search.predicateObject, null, '\t'));
      var pagination = tableState.pagination;
      var output;
      filtered = tableState.search.predicateObject ? filter(safeCopy, tableState.search.predicateObject) : safeCopy;
      if (tableState.sort.predicate) {
        filtered = orderBy(filtered, tableState.sort.predicate, tableState.sort.reverse);
      }
      if (pagination.number !== undefined) {
        pagination.numberOfPages = filtered.length > 0 ? Math.ceil(filtered.length / pagination.number) : 1;
        pagination.start = pagination.start >= filtered.length ? (pagination.numberOfPages - 1) * pagination.number : pagination.start;
        output = filtered.slice(pagination.start, pagination.start + parseInt(pagination.number));
      }
      displaySetter($scope, output || filtered);
    };

Produced output:

pipe has tableState: { "name": "JT" } pipe has tableState: { "location": { "division": "1" } } pipe has tableState: { "status": "6" }

  1. This results in the table being only searched/filtered by the last predicate (status in this case). The table's visual state, the internal state as debugged and the localStorage end up looking like:
{"sort":{"predicate":"id","reverse":false},"search":{"predicateObject":{"status":"6"}},"pagination":{"start":0,"number":5,"numberOfPages":1}}

Here are the relevant st-search HTML snippets for your reference:

<th><input st-search="name" placeholder="" class="input-sm form-control" type="search"/></th>
                <th>
                    <select st-search="location.division" size="1" class="input-sm form-control">
                        <options>
                            <option value="">--not filtered--</option>
                            <option ng-repeat="option in ::$parent.$parent.locations"
                                    value={{option.id}} ng-selected="option.id === smartTableState.search.predicateObject.location.division">{{option.name}}</option>
                        </options>
                    </select>
                </th>
<th>
                    <select st-search="status" size="1" class="input-sm form-control">
                        <options>
                            <option value="">--not filtered--</option>
                            <option ng-repeat="option in ::$parent.$parent.permitStatuses"
                                    value={{option.id}} ng-selected="option.id === smartTableState.search.predicateObject.status">{{option.name}}</option>
                        </options>
                    </select>
                </th>

Is this a known/expected behavior? Is there a way to achieve the net result that all applied predicates (3 in this case) are both reliably stored and applied following subsequent access? Am I missing something simple/obvious?

I greatly appreciate your assistance please!

skidvd avatar Sep 17 '15 18:09 skidvd

If I modify the stSearch attr.$observe block discussed in 3) above to comment out

ctrl.tableState().search = {};

Then all 3 of the above predicates are restored and applied successfully. This appears to keep the predicate collection in tact as fetched from localStorage. However, I'm not sure if this may create other issues downstream?

skidvd avatar Sep 17 '15 22:09 skidvd

@lorenzofox3, I notice you added the documentation tag. Does this imply that there is (or should be) something in the docs that clears this up? From my debugging efforts, it feels like a bug, but perhaps I am missing something else?

skidvd avatar Sep 20 '15 20:09 skidvd

nope, it is because stPersist is not really part of smart-table code. So the fix is more to do on the documentation website or the plunker

lorenzofox3 avatar Sep 21 '15 02:09 lorenzofox3

I understand that stPersist is not really part of smart-table code. However, the change that I needed to make to 'fix' this issue was in stSearch, which I assume is part of the smart-table code. Not sure how that changes anything. Either way, I'm hoping to get to the bottom of this and an 'official' answer of what is the best/correct way to handle the above use case please.

skidvd avatar Sep 21 '15 13:09 skidvd

That line of code is also causing an issue for me as well where I have multiple st-search's on a table with ng-model to set an initial value and only the last st-search's value is properly set in the search predicate. Modifying it to check to see if the search object exists before creating a new object would fix this bug for both this issue and my own.

if(angular.isUndefined(ctrl.tableState().search)) {
  ctrl.tableState().search = {};
}

natevecc avatar Sep 21 '15 14:09 natevecc

Ok, can you help me to make a scenario to reproduce from this plunker

so far by

  1. opening the plunker with a new session (incognito mode for example)
  2. filtering the first column (firstname) by "robert" for example and the second (age) by 7 for example
  3. clicking the "stop" button of the plunker
  4. clicking the "run" button of the plunker

Everything seems restored correctly, what am I missing ? Note the search result will not be exactly the same as the data is randomly generated

Side note: the attr.$observer is actually more if you want dynamic predicate but does not change if the input value changes

lorenzofox3 avatar Sep 22 '15 07:09 lorenzofox3

I have forked your plunk and modified it ever so slightly - http://plnkr.co/edit/COscK7?p=preview. I believe I have narrowed this problem down further to a scope issue. The key change was a modification to the table tag to include ng-if="rowCollection && rowCollection.length > 0"

In our use case, we don't want to display the table unless there is data to display. We additionally prefer to keep the DOM as clean as possible, so we typically use ng-if instead of ng-show.

With the above in place, only one of the multiple predicates will be restored. If I remove it, or change it to ng-show, then the multiples are restored. I hope this helps to get to the bottom of this and further isolate the trouble. However, with both the ng-if as well as the change to st-search discussed above, both work and multiple predicates are restored.

skidvd avatar Sep 22 '15 15:09 skidvd

Yes it's definitely related to ng-if and compile. I have two tables in different tabs but have the body of each tab wrapped in an ng-if to force the table to re-render when changing tabs.

natevecc avatar Sep 22 '15 15:09 natevecc

Any update on this?

skidvd avatar Oct 02 '15 17:10 skidvd

I Have edited the Plunker : http://plnkr.co/edit/Rxvxavb8vqrpbDcyBYW0?p=preview

When you comment out the line "ctrl.tableState().search = {};" here:

attr.$observe('stSearch', function (newValue, oldValue) {
          var input = element[0].value;
          if (newValue !== oldValue && input) {
            ctrl.tableState().search = {};
            tableCtrl.search(input, newValue);
          }
        });

It actually works. I supect that each of these observes are handled speratly and thus the last one wins. Investigated it further: When my smart-table is loaded all the oldValues are undefined and are filled one by one from a collection of headers

    <md-input-container ng-repeat="header in data.headers" ng-show="header.canFilter">
        <label>{{header.searchLabel}}</label>
        <input  ng-model="header.value" st-search="{{header.filterField}}" placeholder="Suchen ..."/>
    </md-input-container>

And thus the st-search observer triggers... causing the problem.

Moepelchen avatar Mar 11 '16 14:03 Moepelchen

Kindly nudged as this remains an issue. Any updates on fixes based upon the requested and provided plunker (http://plnkr.co/edit/COscK7?p=preview) will be greatly appreciated!

skidvd avatar May 16 '16 17:05 skidvd

Could do with this too, as said it seems with an ng-if in or around the table only one of the search values are restored.

Thanks for help.

marctalary avatar Aug 01 '16 08:08 marctalary

Hi there,

trying to push this once again. I think i now found the error: In the stSearch directive you define the obeserver

attr.$observe('stSearch', function (newValue, oldValue) {
          var input = element[0].value;
          if (newValue !== oldValue && input) {
            ctrl.tableState().search = {};
            tableCtrl.search(input, newValue);
          }
        });

The problem here is that the function which is called when the interpolated value changes only should accept one paramter: see https://docs.angularjs.org/api/ng/type/$compile.directive.Attributes So when the observer always resets ALL other search fields because the condition is always true if you set a value programmatically.

Why would you reset all other search fields if one stSearch changes? Edit: i think i know .... one can not find out the last value so, you can not remove it seperately from search ...

Moepelchen avatar Aug 12 '16 12:08 Moepelchen