kamranahmedse / driver.js

A light-weight, no-dependency, vanilla JavaScript engine to drive user's focus across the page
https://driverjs.com
MIT License
22.65k stars 1.01k forks source link

[Feature Request] Lifecycle events #493

Open lonix1 opened 5 months ago

lonix1 commented 5 months ago

There are various events, e.g. for the tour: onDestroyed, and for steps: onNextClick, onPrevClick, onCloseClick.

But they're not useful for a reasonably complex page. I needed events like these:

Those would be more robust and cover the important cases: before/after the tour and before/after each step. That would also work well with async use cases.

lonix1 commented 5 months ago

Here is my workaround until (hopefully) such events would be natively supported. The trick is to repurpose the onPrevClick, onNextClick, onCloseClick and onDestroyed events.

const driver = window.driver.js.driver;
const driverObj = driver({

  onPrevClick: function(element, step, options) {
    let callback = function() { if (driverObj.hasPreviousStep() !== undefined) driverObj.movePrevious(); else driverObj.destroy(); };  // workaround for bug https://github.com/kamranahmedse/driver.js/issues/492
    if (typeof onStepDeactivating !== 'undefined') {
      onStepDeactivating(driverObj.getActiveIndex(), function () {
        if (typeof onStepActivating !== 'undefined' && !driverObj.isFirstStep()) {
          onStepActivating(driverObj.getActiveIndex() - 1, callback);
        }
        else {
          callback();
        }
      });
    }
    else if (typeof onStepActivating !== 'undefined' && !driverObj.isFirstStep()) {
      onStepActivating(driverObj.getActiveIndex() - 1, callback);
    }
    else {
      callback();
    }
  },

  onNextClick: function(element, step, options) {
    let callback = function() { if (driverObj.hasNextStep() !== undefined) driverObj.moveNext(); else driverObj.destroy(); };  // workaround for bug https://github.com/kamranahmedse/driver.js/issues/492
    if (typeof onStepDeactivating !== 'undefined') {
      onStepDeactivating(driverObj.getActiveIndex(), function () {
        if (typeof onStepActivating !== 'undefined' && !driverObj.isLastStep()) {
          onStepActivating(driverObj.getActiveIndex() + 1, callback);
        }
        else {
          callback();
        }
      });
    }
    else if (typeof onStepActivating !== 'undefined' && !driverObj.isLastStep()) {
      onStepActivating(driverObj.getActiveIndex() + 1, callback);
    }
    else {
      callback();
    }
  },

  onCloseClick: function (element, step, options) {
    let callback = function() { driverObj.destroy(); };
    if (typeof onStepDeactivating !== 'undefined') {
      onStepDeactivating(driverObj.getActiveIndex(), callback);
    }
    else {
      callback();
    }
  },

  onDestroyed: function(element, step, options) {
    if (typeof onTourStopped !== 'undefined') {
      onTourStopped();
    }
  },

});

let callbackTourStarting = function() { driverObj.drive(); };
if (typeof onTourStarting !== 'undefined') {
  onTourStarting(callbackTourStarting);
}
else {
  callbackTourStarting();
}

I use the CDN scripts on a server-rendered site, so I use the workaround like so:

<script>
  function onTourStarting(callback) {
    callback();
  }

  function onStepActivating(activeIndex, callback) {
    if (activeIndex === 3) {
      loadSomething();
      callback();
    }
    else if (activeIndex === 7) {
      doAsyncFoo(function() {
        callback();
      });
    }
    else {
      callback();
    }
  }

  function onStepDeactivating(activeIndex, callback) {
    if (activeIndex === 3) {
      reset();
    }
    callback();
  }

  function onTourStopped() {
    cleanup();
  }
</script>

That works really well, and as you can see in the second code block it exposes a very friendly API for callers.

But it would be nice if such events would be exposed natively by the library.

(If someone has a neater workaround, please post it below.)