JAVASCRIPT   9

crosstab.js

Guest on 19th August 2021 04:24:10 PM

  1. /*!
  2.  * crosstab JavaScript Library v0.2.0
  3.  * https://github.com/tejacques/crosstab
  4.  *
  5.  * License: Apache 2.0 https://github.com/tejacques/crosstab/blob/master/LICENSE
  6.  */
  7. ; (function (define) {
  8. define(function(require,exports,module){
  9.     'use strict';
  10.  
  11.     // --- Handle Support ---
  12.     // See: http://detectmobilebrowsers.com/about
  13.     var useragent = navigator.userAgent || navigator.vendor || window.opera;
  14.     window.isMobile = (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(useragent) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(useragent.substr(0, 4)));
  15.  
  16.     var localStorage;
  17.     try {
  18.         localStorage = window.localStorage;
  19.     } catch (e) {
  20.         // New versions of Firefox throw a Security exception
  21.         // if cookies are disabled. See
  22.         // https://bugzilla.mozilla.org/show_bug.cgi?id=1028153
  23.     }
  24.  
  25.     // Other reasons
  26.     var frozenTabEnvironment = false;
  27.  
  28.     function notSupported() {
  29.         var errorMsg = 'crosstab not supported';
  30.         var reasons = [];
  31.         if (!localStorage) {
  32.             reasons.push('localStorage not available');
  33.         }
  34.         if (!window.addEventListener) {
  35.             reasons.push('addEventListener not available');
  36.         }
  37.         if (window.isMobile) {
  38.             reasons.push('mobile browser');
  39.         }
  40.         if (frozenTabEnvironment) {
  41.             reasons.push('frozen tab environment detected');
  42.         }
  43.  
  44.         if(reasons.length > 0) {
  45.             errorMsg += ': ' + reasons.join(', ');
  46.         }
  47.  
  48.         console.log(errorMsg);
  49.     }
  50.  
  51.     // --- Utility ---
  52.     var util = {
  53.         keys: {
  54.             MESSAGE_KEY: 'crosstab.MESSAGE_KEY',
  55.             TABS_KEY: 'crosstab.TABS_KEY',
  56.             MASTER_TAB: 'MASTER_TAB',
  57.             SUPPORTED_KEY: 'crosstab.SUPPORTED',
  58.             FROZEN_TAB_ENVIRONMENT: 'crosstab.FROZEN_TAB_ENVIRONMENT'
  59.         }
  60.     };
  61.  
  62.     util.forEachObj = function (thing, fn) {
  63.         for (var key in thing) {
  64.             if (thing.hasOwnProperty(key)) {
  65.                 fn.call(thing, thing[key], key);
  66.             }
  67.         }
  68.     };
  69.  
  70.     util.forEachArr = function (thing, fn) {
  71.         for (var i = 0; i < thing.length; i++) {
  72.             fn.call(thing, thing[i], i);
  73.         }
  74.     };
  75.  
  76.     util.forEach = function (thing, fn) {
  77.         if (Object.prototype.toString.call(thing) === '[object Array]') {
  78.             util.forEachArr(thing, fn);
  79.         } else {
  80.             util.forEachObj(thing, fn);
  81.         }
  82.     };
  83.  
  84.     util.map = function (thing, fn) {
  85.         var res = [];
  86.         util.forEach(thing, function (item) {
  87.             res.push(fn(item));
  88.         });
  89.  
  90.         return res;
  91.     };
  92.  
  93.     util.filter = function (thing, fn) {
  94.         var isArr = Object.prototype.toString.call(thing) === '[object Array]';
  95.         var res = isArr ? [] : {};
  96.  
  97.         if (isArr) {
  98.             util.forEachArr(thing, function (value, key) {
  99.                 if (fn(value, key)) {
  100.                     res.push(value);
  101.                 }
  102.             });
  103.         } else {
  104.             util.forEachObj(thing, function (value, key) {
  105.                 if (fn(value, key)) {
  106.                     res[key] = value;
  107.                 }
  108.             });
  109.         }
  110.  
  111.         return res;
  112.     };
  113.  
  114.     util.now = function () {
  115.         return (new Date()).getTime();
  116.     };
  117.  
  118.     util.tabs = getStoredTabs();
  119.  
  120.     util.eventTypes = {
  121.         becomeMaster: 'becomeMaster',
  122.         tabUpdated: 'tabUpdated',
  123.         tabClosed: 'tabClosed',
  124.         tabPromoted: 'tabPromoted'
  125.     };
  126.  
  127.     // --- Events ---
  128.     // node.js style events, with the main difference being object based
  129.     // rather than array based, as well as being able to add/remove
  130.     // events by key.
  131.     util.createEventHandler = function () {
  132.         var events = {};
  133.  
  134.         var addListener = function (event, listener, key) {
  135.             key = key || listener;
  136.             var handlers = listeners(event);
  137.             handlers[key] = listener;
  138.  
  139.             return key;
  140.         };
  141.  
  142.         var removeListener = function (event, key) {
  143.             if (events[event] && events[event][key]) {
  144.                 delete events[event][key];
  145.                 return true;
  146.             }
  147.             return false;
  148.         };
  149.  
  150.         var removeAllListeners = function (event) {
  151.             if (event) {
  152.                 if (events[event]) {
  153.                     delete events[event];
  154.                 }
  155.             } else {
  156.                 events = {};
  157.             }
  158.         };
  159.  
  160.         var emit = function (event) {
  161.             var args = Array.prototype.slice.call(arguments, 1);
  162.             var handlers = listeners(event);
  163.  
  164.             util.forEach(handlers, function (listener) {
  165.                 if (typeof (listener) === 'function') {
  166.                     listener.apply(this, args);
  167.                 }
  168.             });
  169.         };
  170.  
  171.         var once = function (event, listener, key) {
  172.             // Generate a unique id for this listener
  173.             var handlers = listeners(event);
  174.             while (!key || handlers[key]) {
  175.                 key = util.generateId();
  176.             }
  177.  
  178.             addListener(event, function () {
  179.                 removeListener(event, key);
  180.                 var args = Array.prototype.slice.call(arguments);
  181.                 listener.apply(this, args);
  182.             }, key);
  183.  
  184.             return key;
  185.         };
  186.  
  187.         var listeners = function (event) {
  188.             var handlers = events[event] = events[event] || {};
  189.             return handlers;
  190.         };
  191.  
  192.         return {
  193.             addListener: addListener,
  194.             on: addListener,
  195.             off: removeListener,
  196.             once: once,
  197.             emit: emit,
  198.             listeners: listeners,
  199.             removeListener: removeListener,
  200.             removeAllListeners: removeAllListeners
  201.         };
  202.     };
  203.  
  204.     // --- Setup Events ---
  205.     var eventHandler = util.createEventHandler();
  206.  
  207.     // wrap eventHandler so that setting it will not blow up
  208.     // any of the internal workings
  209.     util.events = {
  210.         addListener: eventHandler.addListener,
  211.         on: eventHandler.on,
  212.         off: eventHandler.off,
  213.         once: eventHandler.once,
  214.         emit: eventHandler.emit,
  215.         listeners: eventHandler.listeners,
  216.         removeListener: eventHandler.removeListener,
  217.         removeAllListeners: eventHandler.removeAllListeners
  218.     };
  219.  
  220.     function onStorageEvent(event) {
  221.         var eventValue;
  222.         try {
  223.             eventValue = event.newValue ? JSON.parse(event.newValue) : {};
  224.         } catch (e) {
  225.             eventValue = {};
  226.         }
  227.         if (!eventValue.id || eventValue.id === crosstab.id) {
  228.             // This is to force IE to behave properly
  229.             return;
  230.         }
  231.         if (event.key === util.keys.MESSAGE_KEY) {
  232.             var message = eventValue.data;
  233.             // only handle if this message was meant for this tab.
  234.             if (!message.destination || message.destination === crosstab.id) {
  235.                 eventHandler.emit(message.event, message);
  236.             }
  237.         }
  238.     }
  239.  
  240.     function setLocalStorageItem(key, data) {
  241.         var storageItem = {
  242.             id: crosstab.id,
  243.             data: data,
  244.             timestamp: util.now()
  245.         };
  246.  
  247.         localStorage.setItem(key, JSON.stringify(storageItem));
  248.     }
  249.  
  250.     function getLocalStorageItem(key) {
  251.         var item = getLocalStorageRaw(key);
  252.         return item.data;
  253.     }
  254.  
  255.     function getLocalStorageRaw(key) {
  256.         var json = localStorage ? localStorage.getItem(key) : null;
  257.         var item = json ? JSON.parse(json) : {};
  258.         return item;
  259.     }
  260.  
  261.     function beforeUnload() {
  262.         var numTabs = 0;
  263.         util.forEach(util.tabs, function (tab, key) {
  264.             if (key !== util.keys.MASTER_TAB) {
  265.                 numTabs++;
  266.             }
  267.         });
  268.  
  269.         if (numTabs === 1) {
  270.             util.tabs = {};
  271.             setStoredTabs();
  272.         } else {
  273.             broadcast(util.eventTypes.tabClosed, crosstab.id);
  274.         }
  275.     }
  276.  
  277.     function getMaster() {
  278.         return util.tabs[util.keys.MASTER_TAB];
  279.     }
  280.  
  281.     function setMaster(newMaster) {
  282.         util.tabs[util.keys.MASTER_TAB] = newMaster;
  283.     }
  284.  
  285.     function deleteMaster() {
  286.         delete util.tabs[util.keys.MASTER_TAB];
  287.     }
  288.  
  289.     function isMaster() {
  290.         return getMaster().id === crosstab.id;
  291.     }
  292.  
  293.     function masterTabElection() {
  294.         var maxId = null;
  295.         util.forEach(util.tabs, function (tab) {
  296.             if (!maxId || tab.id < maxId) {
  297.                 maxId = tab.id;
  298.             }
  299.         });
  300.  
  301.         // only broadcast the promotion if I am the new master
  302.         if (maxId === crosstab.id) {
  303.             broadcast(util.eventTypes.tabPromoted, crosstab.id);
  304.         } else {
  305.             // this is done so that in the case where multiple tabs are being
  306.             // started at the same time, and there is no current saved tab
  307.             // information, we will still have a value set for the master tab
  308.             setMaster({
  309.                 id: maxId,
  310.                 lastUpdated: util.now()
  311.             });
  312.         }
  313.     }
  314.  
  315.     // Handle other tabs closing by updating internal tab model, and promoting
  316.     // self if we are the lowest tab id
  317.     eventHandler.addListener(util.eventTypes.tabClosed, function (message) {
  318.         var id = message.data;
  319.         if (util.tabs[id]) {
  320.             delete util.tabs[id];
  321.         }
  322.  
  323.         if (!getMaster() || getMaster().id === id) {
  324.             // If the master was the closed tab, delete it and the highest
  325.             // tab ID becomes the new master, which will save the tabs
  326.             if (getMaster()) {
  327.                 deleteMaster();
  328.             }
  329.             masterTabElection();
  330.         } else if (getMaster().id === crosstab.id) {
  331.             // If I am master, save the new tabs out
  332.             setStoredTabs();
  333.         }
  334.     });
  335.  
  336.     eventHandler.addListener(util.eventTypes.tabUpdated, function (message) {
  337.         var tab = message.data;
  338.         util.tabs[tab.id] = tab;
  339.  
  340.         // If there is no master, hold an election
  341.         if (!getMaster()) {
  342.             masterTabElection();
  343.         }
  344.  
  345.         if (getMaster().id === tab.id) {
  346.             setMaster(tab);
  347.         }
  348.         if (getMaster().id === crosstab.id) {
  349.             // If I am master, save the new tabs out
  350.             setStoredTabs();
  351.         }
  352.     });
  353.  
  354.     eventHandler.addListener(util.eventTypes.tabPromoted, function (message) {
  355.         var id = message.data;
  356.         var lastUpdated = message.timestamp;
  357.         setMaster({
  358.             id: id,
  359.             lastUpdated: lastUpdated
  360.         });
  361.  
  362.         if (crosstab.id === id) {
  363.             // set the tabs in localStorage
  364.             setStoredTabs();
  365.  
  366.             // emit the become master event so we can handle it accordingly
  367.             util.events.emit(util.eventTypes.becomeMaster);
  368.         }
  369.     });
  370.  
  371.     function pad(num, width, padChar) {
  372.         padChar = padChar || '0';
  373.         var numStr = (num.toString());
  374.  
  375.         if (numStr.length >= width) {
  376.             return numStr;
  377.         }
  378.  
  379.         return new Array(width - numStr.length + 1).join(padChar) + numStr;
  380.     }
  381.  
  382.     util.generateId = function () {
  383.         /*jshint bitwise: false*/
  384.         return util.now().toString() + pad((Math.random() * 0x7FFFFFFF) | 0, 10);
  385.     };
  386.  
  387.     // --- Setup message sending and handling ---
  388.     function broadcast(event, data, destination) {
  389.         if (!crosstab.supported) {
  390.             notSupported();
  391.         }
  392.  
  393.         var message = {
  394.             event: event,
  395.             data: data,
  396.             destination: destination,
  397.             origin: crosstab.id,
  398.             timestamp: util.now()
  399.         };
  400.  
  401.         // If the destination differs from the origin send it out, otherwise
  402.         // handle it locally
  403.         if (message.destination !== message.origin) {
  404.             setLocalStorageItem(util.keys.MESSAGE_KEY, message);
  405.         }
  406.  
  407.         if (!message.destination || message.destination === message.origin) {
  408.             eventHandler.emit(event, message);
  409.         }
  410.     }
  411.  
  412.     function broadcastMaster(event, data) {
  413.         broadcast(event, data, getMaster().id);
  414.     }
  415.  
  416.     // ---- Return ----
  417.     var setupComplete = false;
  418.     util.events.once('setupComplete', function () {
  419.         setupComplete = true;
  420.     });
  421.  
  422.     var crosstab = function (fn) {
  423.         if (setupComplete) {
  424.             fn();
  425.         } else {
  426.             util.events.once('setupComplete', fn);
  427.         }
  428.     };
  429.  
  430.     crosstab.getItem=getLocalStorageItem;
  431.     crosstab.setItem=setLocalStorageItem;
  432.     crosstab.id = util.generateId();
  433.     crosstab.supported = !!localStorage && window.addEventListener && !window.isMobile;
  434.     crosstab.util = util;
  435.     crosstab.broadcast = broadcast;
  436.     crosstab.broadcastMaster = broadcastMaster;
  437.     crosstab.on = util.events.on;
  438.     crosstab.once = util.events.once;
  439.  
  440.     // --- Crosstab supported ---
  441.     // Check to see if the global supported key has been set.
  442.     if (!setupComplete && crosstab.supported) {
  443.         var supportedRaw = getLocalStorageRaw(util.keys.SUPPORTED_KEY);
  444.         var supported = supportedRaw.data;
  445.         if (supported === false || supported === true) {
  446.             // As long as it is explicitely set, use the value
  447.             crosstab.supported = supported;
  448.             util.events.emit('setupComplete');
  449.         }
  450.     }
  451.  
  452.     // Check to see if the global frozen tab environment key has been set.
  453.     if (!setupComplete && crosstab.supported) {
  454.         var frozenTabsRaw = getLocalStorageRaw(util.keys.FROZEN_TAB_ENVIRONMENT);
  455.         var frozenTabs = frozenTabsRaw.data;
  456.         if (frozenTabs === true) {
  457.             frozenTabEnvironmentDetected();
  458.             util.events.emit('setupComplete');
  459.         }
  460.     }
  461.  
  462.     function frozenTabEnvironmentDetected() {
  463.         crosstab.supported = false;
  464.         frozenTabEnvironment = true;
  465.         setLocalStorageItem(util.keys.FROZEN_TAB_ENVIRONMENT, true);
  466.         setLocalStorageItem(util.keys.SUPPORTED_KEY, false);
  467.     }
  468.  
  469.     // --- Tab Setup ---
  470.     // 3 second keepalive
  471.     var TAB_KEEPALIVE = 3 * 1000;
  472.     // 5 second timeout
  473.     var TAB_TIMEOUT = 5 * 1000;
  474.     // 100 ms ping timeout
  475.     var PING_TIMEOUT = 100;
  476.  
  477.     function getStoredTabs() {
  478.         var storedTabs = getLocalStorageItem(util.keys.TABS_KEY);
  479.         util.tabs = storedTabs || util.tabs || {};
  480.         return util.tabs;
  481.     }
  482.  
  483.     function setStoredTabs() {
  484.         setLocalStorageItem(util.keys.TABS_KEY, util.tabs);
  485.     }
  486.  
  487.     function keepalive() {
  488.         var now = util.now();
  489.  
  490.         var myTab = {
  491.             id: crosstab.id,
  492.             lastUpdated: now
  493.         };
  494.  
  495.         // broadcast tabUpdated event
  496.         broadcast(util.eventTypes.tabUpdated, myTab);
  497.  
  498.         // broadcast tabClosed event for each tab that timed out
  499.         function stillAlive(tab) {
  500.             return now - tab.lastUpdated < TAB_TIMEOUT;
  501.         }
  502.  
  503.         function notAlive(tab, key) {
  504.             return key !== util.keys.MASTER_TAB && !stillAlive(tab);
  505.         }
  506.  
  507.         var deadTabs = util.filter(util.tabs, notAlive);
  508.         util.forEach(deadTabs, function (tab) {
  509.             broadcast(util.eventTypes.tabClosed, tab.id);
  510.         });
  511.  
  512.         // check to see if setup is complete
  513.         if (!setupComplete) {
  514.             var masterTab = crosstab.util.tabs[crosstab.util.keys.MASTER_TAB];
  515.             // ping master
  516.             if (masterTab && masterTab.id !== myTab.id) {
  517.                 var timeout;
  518.                 var start;
  519.  
  520.                 crosstab.util.events.once('PONG', function () {
  521.                     if (!setupComplete) {
  522.                         clearTimeout(timeout);
  523.                         // set supported to true / frozen to false
  524.                         setLocalStorageItem(
  525.                             util.keys.SUPPORTED_KEY,
  526.                             true);
  527.                         setLocalStorageItem(
  528.                             util.keys.FROZEN_TAB_ENVIRONMENT,
  529.                             false);
  530.                         util.events.emit('setupComplete');
  531.                     }
  532.                 });
  533.  
  534.                 start = util.now();
  535.  
  536.                 // There is a nested timeout here. We'll give it 100ms
  537.                 // timeout, with iters "yields" to the event loop. So at least
  538.                 // iters number of blocks of javascript will be able to run
  539.                 // covering at least 100ms
  540.                 var recursiveTimeout = function (iters) {
  541.                     var diff = util.now() - start;
  542.  
  543.                     if (!setupComplete) {
  544.                         if (iters <= 0 && diff > PING_TIMEOUT) {
  545.                             frozenTabEnvironmentDetected();
  546.                             util.events.emit('setupComplete');
  547.                         } else {
  548.                             timeout = setTimeout(function () {
  549.                                 recursiveTimeout(iters - 1);
  550.                             }, 5);
  551.                         }
  552.                     }
  553.                 };
  554.  
  555.                 var iterations = 5;
  556.                 timeout = setTimeout(function () {
  557.                     recursiveTimeout(5);
  558.                 }, PING_TIMEOUT - 5 * iterations);
  559.                 crosstab.broadcastMaster('PING');
  560.             } else if (masterTab && masterTab.id === myTab.id) {
  561.                 util.events.emit('setupComplete');
  562.             }
  563.         }
  564.     }
  565.  
  566.     function keepaliveLoop() {
  567.         if (!crosstab.stopKeepalive) {
  568.             keepalive();
  569.             window.setTimeout(keepaliveLoop, TAB_KEEPALIVE);
  570.         }
  571.     }
  572.  
  573.     // --- Check if crosstab is supported ---
  574.     if (!crosstab.supported) {
  575.         crosstab.broadcast = notSupported;
  576.     } else {
  577.         // ---- Setup Storage Listener
  578.         window.addEventListener('storage', onStorageEvent, false);
  579.         window.addEventListener('beforeunload', beforeUnload, false);
  580.  
  581.         util.events.on('PING', function (message) {
  582.             // only handle direct messages
  583.             if (!message.destination || message.destination !== crosstab.id) {
  584.                 return;
  585.             }
  586.  
  587.             if (util.now() - message.timestamp < PING_TIMEOUT) {
  588.                 crosstab.broadcast('PONG', null, message.origin);
  589.             }
  590.         });
  591.  
  592.         keepaliveLoop();
  593.     }
  594.  
  595.     module.exports = crosstab;
  596.  
  597. /*!
  598.  * UMD/AMD/Global context Module Loader wrapper
  599.  * based off https://gist.github.com/wilsonpage/8598603
  600.  *
  601.  * This wrapper will try to use a module loader with the
  602.  * following priority:
  603.  *
  604.  *  1.) AMD
  605.  *  2.) CommonJS
  606.  *  3.) Context Variable (window in the browser)
  607.  */
  608. });})(typeof define == 'function' && define.amd ? define
  609.     : (function (name, context) {
  610.         'use strict';
  611.         return typeof module == 'object' ? function (define) {
  612.             define(require, exports, module);
  613.         }
  614.         : function (define) {
  615.             var module = {
  616.                 exports: {}
  617.             };
  618.             var require = function (n) {
  619.                 return context[n];
  620.             };
  621.  
  622.             define(require, module.exports, module);
  623.             context[name] = module.exports;
  624.         };
  625.     })('crosstab', this));

Raw Paste


Login or Register to edit or fork this paste. It's free.