Is there a way to keep track of an object-property's value-history, like memorizing and recalling any of its recent values?

225 Views Asked by At

Say I have a method that changes a value based on what the current value is. I will just make it very simple here:

  changeValue(){
    if(!this.value){
      this.value = 'value1'
    }
    else if(this.value === 'value1'){
      this.value = 'value2'
    }
    else if(this.value === 'value2'){
      this.value = 'value3'
    }
  },

And then I have another method that changes to a fourth value if the current value is either 2 or 3

  changeValues(){
    if(this.value === 'value2' || this.value === 'value3'){
      this.value = 'valueX'
    }
  },

Now I would like to add a click event that changes the value of valueX back to what it previously was, but I cannot specify it in the code, because the previous value could either be value2 or value3.

Is there a way to look at the variable "history", check what is previously was and then set it back to that?

  changeBack(){
    // if last value was value2 change back to value2
    // if last value was value3 then change back to value3
  }
4

There are 4 best solutions below

0
Jaykant On BEST ANSWER

You can use an array to keep track of the history of values.

Example:

class YourClass {
  constructor() {
    this.value = null;
    this.valueHistory = [];
  }

  changeValue() {
    if (!this.value) {
      this.value = 'value1';
    } else if (this.value === 'value1') {
      this.value = 'value2';
    } else if (this.value === 'value2') {
      this.value = 'value3';
    }

    // Save the current value to history
    this.valueHistory.push(this.value);
  }

  changeValues() {
    if (this.value === 'value2' || this.value === 'value3') {
      this.value = 'valueX';
    }
  }

  changeBack() {
    // Pop the last value from history and set it as the current value
    if (this.valueHistory.length > 0) {
      this.value = this.valueHistory.pop();
    }
  }
}

valueHistory is an array that keeps track of the history of values. The changeBack method pops the last value from the history array and sets it as the current value.

1
Hasan Raza On
let valueHistory = [];
function changeValue(newValue) {
    valueHistory.push(this.value);
    this.value = newValue;
}
function changeBack() {
    if (valueHistory.length > 0) {
        this.value = valueHistory.pop();
    }
}
changeValue('hasanraza');
console.log(this.value);
0
Eyad OP On

Your question is tagged with vue.js, so my answer in vue

You can use vue watchers and make a list and select them by their indexs or object with key: value pairs of all changes.

data() {
  return {
    val: '',
    listOfValues: [],
  }
},
watch: {
  val(newVal, oldVal) {
    this.listOfValues.push(newVal)
    // or with if statement if you want to check
    if (...) {
    
    }
  }
},

Vue Watchers

1
Peter Seliger On

A generic, non invasive, thus re-usable approach needs to be based on a WeakMap. Such a registry's keys are exclusively references of objects where one for the latter wants to store a value-history for each of the object's entries. Therefore each value of the registry needs to be a Map instance where a mapping string-based key is the property-name of an object's entry and the value is a Set instance. The latter serves as the history-storage for all the recent unique values of an object's certain entry (key-value pair).

Any of an object's property then could be implemented via get/set functionality which upon the to be set value does keep track of each new, uniquely set value.

The code then has to be accompanied by two helper functions e.g. getPreviousOfValueHistory and getNextOfValueHistory where each function upon the passed parameters target, key and value does return for the target-object specific key-indicated property either the value previously or next to the passed value parameter.

In case value does not show up in the value-history of the target/key indicated property, the Symbol-value Symbol('no history value found') will be returned.

In case value matches either the first or the last unique history value, the value itself gets returned.

Example code ...

const noHistoryValueFound = Symbol('no history value found');

const valueHistoryRegistry = new WeakMap;

const getPreviousOfValueHistory = (target, key, value) => {
  let result = noHistoryValueFound;

  const valueHistory =
    valueHistoryRegistry?.get(target)?.get(key);

  if (valueHistory) {
    const valueList = [...valueHistory];
    let valueIdx = valueList.indexOf(value);

    if (valueIdx >= 0) {

      result = (--valueIdx in valueList)
        ? valueList.at(valueIdx)
        : value;
    }
  }
  return result;
}
const getNextOfValueHistory = (target, key, value) => {
  let result = noHistoryValueFound;

  const valueHistory =
    valueHistoryRegistry?.get(target)?.get(key);

  if (valueHistory) {
    const valueList = [...valueHistory];
    let valueIdx = valueList.indexOf(value);

    if (valueIdx >= 0) {

      result = (++valueIdx in valueList)
        ? valueList.at(valueIdx)
        : value;
    }
  }
  return result;
}

const logUniqueValueHistory = (target, key, value) => {
  if (valueHistoryRegistry.has(target)) {

    const valueRegistry = valueHistoryRegistry.get(target);
    if (valueRegistry.has(key)) {

      const valueHistory = valueRegistry.get(key);
      if (!valueHistory.has(value)) {

        valueHistory
          .add(value);
      }
    } else {
      valueRegistry
        .set(key, new Set([value]));
    }
  } else {
    valueHistoryRegistry
      .set(target, new Map([[key, new Set([value])]]));
  }
}

const getSnapshotOfWritableOwnProperties = (value) => {
  const source = Object(value);

  return Object
    .entries(
      Object.getOwnPropertyDescriptors(source)
    )
    .concat(
      Object
        .getOwnPropertySymbols(source)
        .map(symbol => [symbol, Object.getOwnPropertyDescriptor(source, symbol)])
    )
    .filter(([_, descriptor]) =>
      !!descriptor.writable && Object.hasOwn(descriptor, 'value')
    )
    .reduce((snapshot, [key, { value }]) => {

      snapshot[key] = value;
      return snapshot;

    }, {});
}

function withContextuallyBoundUniqueValueHistory(key, value) {
  const { context, state } = this;

  Reflect.defineProperty(context, key, {
    get: () => Reflect.get(state, key),
    set: value => {
      logUniqueValueHistory(context, key, value);

      return Reflect.set(state, key, value);
    },
  });
}

function withOwnUniqueValueHistory() {
  const state = getSnapshotOfWritableOwnProperties(this);

  Object
    .entries(state)
    .concat(
      Object
        .getOwnPropertySymbols(state)
        .map(symbol => [symbol, Reflect.get(state, symbol)])
    )
    .forEach(([key, value]) => {

      withContextuallyBoundUniqueValueHistory
        .call({ context: this, state }, key, value);

      // - force 1st history-value entry
      //   for every instance property.
      this[key] = value;
    });
}

class KeyValueStorageWithUniqueValueHistory {

  constructor(initialState= {}) {
    Object
      .assign(this, Object(initialState));

    withOwnUniqueValueHistory.call(this);
  }
  previousValueOf(/* key, value? */...args) {
    const key = args.at(0);
    const value = ('1' in args) ? args.at(1) : this[key];

    return getPreviousOfValueHistory(this, key, value);
  }
  nextValueOf(/* key, value? */...args) {
    const key = args.at(0);
    const value = ('1' in args) ? args.at(1) : this[key];

    return getNextOfValueHistory(this, key, value);
  }
}
const fooBarSymbol = Symbol('fooBar');

const storage = new KeyValueStorageWithUniqueValueHistory({
  quickBrownFox: 'the',
  [fooBarSymbol]: 'foo',
});

console.log(`const storage = new KeyValueStorageWithUniqueValueHistory({
  quickBrownFox: 'the',
  [fooBarSymbol]: 'foo',
});`);
console.log('storage.quickBrownFox ...', storage.quickBrownFox);
console.log('\n');

console.log("storage.quickBrownFox = 'quick';");
storage.quickBrownFox = 'quick';
console.log('storage.quickBrownFox ...', storage.quickBrownFox);
console.log('\n');

console.log("storage.quickBrownFox = 'brown';");
storage.quickBrownFox = 'brown';
console.log('storage.quickBrownFox ...', storage.quickBrownFox);
console.log('\n');

console.log("storage.quickBrownFox = 'fox';");
storage.quickBrownFox = 'fox';
console.log('storage.quickBrownFox ...', storage.quickBrownFox);
console.log('\n');

console.log("storage.quickBrownFox = 'jumps';");
storage.quickBrownFox = 'jumps';
console.log('storage.quickBrownFox ...', storage.quickBrownFox);

console.log('----- ----- ----- ----- -----');

console.log("storage.quickBrownFox = 'brown';");
storage.quickBrownFox = 'brown';
console.log('storage.quickBrownFox ...', storage.quickBrownFox);
console.log('\n');

console.log(
  "storage.previousValueOf('quickBrownFox') ...",
  storage.previousValueOf('quickBrownFox'),
);
console.log(
  "storage.nextValueOf('quickBrownFox') ...",
  storage.nextValueOf('quickBrownFox'),
);
console.log('\n');

console.log(
  "storage.previousValueOf('quickBrownFox', 'quick') ...",
  storage.previousValueOf('quickBrownFox', 'quick'),
);
console.log(
  "storage.previousValueOf('quickBrownFox', 'jumps') ...",
  storage.previousValueOf('quickBrownFox', 'jumps'),
);
console.log('\n');

console.log(
  "storage.nextValueOf('quickBrownFox', 'the') ...",
  storage.nextValueOf('quickBrownFox', 'the'),
);
console.log(
  "storage.nextValueOf('quickBrownFox', 'fox') ...",
  storage.nextValueOf('quickBrownFox', 'fox'),
);

console.log('----- ----- ----- ----- -----');

console.log('storage[fooBarSymbol] ...',storage[fooBarSymbol]);

console.log(
  "storage.previousValueOf(fooBarSymbol) ...",
  storage.previousValueOf(fooBarSymbol),
);
console.log(
  "storage.nextValueOf(fooBarSymbol) ...",
  storage.nextValueOf(fooBarSymbol),
);
console.log('\n');

console.log(
  "storage.nextValueOf(fooBarSymbol, 'bar') ...",
  String(storage.nextValueOf(fooBarSymbol, 'bar')),
);

console.log('----- ----- ----- ----- -----');

console.log("storage[fooBarSymbol] = 'bar';");
storage[fooBarSymbol] = 'bar';
console.log('storage[fooBarSymbol] ...', storage[fooBarSymbol]);
console.log('\n');

console.log("storage[fooBarSymbol] = 'baz';");
storage[fooBarSymbol] = 'baz';
console.log('storage[fooBarSymbol] ...', storage[fooBarSymbol]);
console.log('\n');

console.log("storage[fooBarSymbol] = 'biz';");
storage[fooBarSymbol] = 'biz';
console.log('storage[fooBarSymbol] ...', storage[fooBarSymbol]);

console.log('----- ----- ----- ----- -----');

console.log("storage[fooBarSymbol] = 'bar';");
storage[fooBarSymbol] = 'bar';
console.log('storage[fooBarSymbol] ...', storage[fooBarSymbol]);
console.log('\n');

console.log(
  "storage.previousValueOf(fooBarSymbol) ...",
  storage.previousValueOf(fooBarSymbol),
);
console.log(
  "storage.nextValueOf(fooBarSymbol) ...",
  storage.nextValueOf(fooBarSymbol),
);
console.log('\n');

console.log(
  "storage.previousValueOf(fooBarSymbol, 'biz') ...",
  storage.previousValueOf(fooBarSymbol, 'biz'),
);
console.log(
  "storage.nextValueOf(fooBarSymbol, 'baz') ...",
  storage.nextValueOf(fooBarSymbol, 'baz'),
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

Taking everything into account which got provided by the above example code, the OP now could easily implement a generic changeBack methods like follows ...

setPreviousValueOf(/* key, value? */...args) {
  const key = args.at(0);
  const value = ('1' in args) ? args.at(1) : this[key];

  this[key] = getPreviousOfValueHistory(this, key, value);
}

... which then could be used like ...

this.setPreviousValueOf('valueX'/*,anyCurrentOrPreviousValueOfProperty_valueX*/);

Again, the stack snippet's example code does provide the implementation of getPreviousOfValueHistory and an example of its usage.