简体   繁体   中英

How to use segment.io's analytics.js in a knockout custom bindings

I am using knockout to make a custom binding for analytics.track, but it seems to be having trouble. It seems if the analytics.track is nested in more than 2 functions the track call fails silently. It doesn't hit the callback and it doesn't report in segments debugger. I have provided 2 examples demonstrating the problem here:

Without Closure (works):

function sendTrack(event, props) {
  console.log("Enter sendTrack");
  analytics.track('Signed Up', {
    plan: 'Enterprise'
  }, {}, function () {
    console.log('track callback logged');
  });
}

ko.bindingHandlers.segmentTrack = {
  init: function (element, valueAccessor) {
    console.log("Init");
    var value = ko.unwrap(valueAccessor());
    ko.applyBindingsToNode(element, { click: sendTrack });
  }
};
ko.applyBindings({});

With Closure (doesn't work):

(function(ko, $, analytics){
  'use strict';
  function sendTrack(event, props) {
    console.log("Enter sendTrack");
    analytics.track('Signed Up', {
      plan: 'Enterprise'
      }, {}, function () {
        console.log('track callback logged');
      });
  }

  ko.bindingHandlers.segmentTrack = {
    init: function (element, valueAccessor) {
      console.log("Init");
      var value = ko.unwrap(valueAccessor());
      ko.applyBindingsToNode(element, { click: sendTrack });
    }
  };

  ko.applyBindings({});
})(window.ko, window.jQuery, window.analytics);

Edit1: Also note this works with if I move the analytics.track to init:

(function(ko, $, analytics){
  'use strict';
  ko.bindingHandlers.segmentTrack = {
    init: function (element, valueAccessor) {
      console.log("Init");
      analytics.track('Signed Up', {
      plan: 'Enterprise'
      }, {}, function () {
        console.log('track callback logged');
      });
    }
  };

  ko.applyBindings({});
})(window.ko, window.jQuery, window.analytics);

Please advise

This is very likely because of the order things load / are initialized on the window object. Because the iife executes immediately , the analytics variable will be set to whatever window.analytics is the moment the iife is encountered by the browser . In the first case, window.analytics will be resolved when the code is run .

Put differently: the closure captures window.analytics in a scoped analytics variable at the time the iife executes .

Here's a demo showing the problem.

Without closure:

 function sendTrack() { console.log("Tracking..."); analytics.track("stuff"); } ko.bindingHandlers.segmentTrack = { init: function(element) { console.log("Init"); ko.applyBindingsToNode(element, { click: sendTrack }); } } ko.applyBindings({ }); // Simulate loading analytics now: window.analytics = { track: function(txt) { console.log("Tracking " + txt); } }; 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script> <div data-bind="segmentTrack: true">CLICK ME</div> 

vs with closure:

 (function(ko, analytics) { function sendTrack() { console.log("Tracking..."); analytics.track("stuff"); } ko.bindingHandlers.segmentTrack = { init: function(element) { console.log("Init"); ko.applyBindingsToNode(element, { click: sendTrack }); } } ko.applyBindings({}); })(window.ko, window.analytics); // window.analytics isn't quite okay yet! // Simulate loading analytics now: window.analytics = { track: function(txt) { console.log("Tracking " + txt); } }; 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script> <div data-bind="segmentTrack: true">CLICK ME</div> 

True, in my examples the second scenario throws an error whereas you mention in the question nothing happens, but then again the question doesn't contain an actual repro so it's hard to tell where that difference lies.

So analytics.js asynchronously loads its self in the page. In the mean time it queues all calls to the API with a snub version of the object. Once analytics.js loads it executes all the calls in the queue. Then redefines its self, breaking all refs to the original window.analytics. So any calls that are encountered fast enough to My only work around for this is to make my exposer a function call that returns the current version of the window.analytics.

(function (ko, $, analytics) {

    function sendTrack(event, props) {
        analytics().track(event, props);
    }

    ko.bindingHandlers.segmentTrack = {
        init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
            var value = ko.unwrap(valueAccessor());
            ko.applyBindingsToNode(element, { click: function () { sendTrack(value.event, value.options) }});
        }
    }
})(window.ko, window.jQuery, function () { return window.analytics; });

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM