bookclub-advr

DSLC Advanced R Book Club
git clone https://git.eamoncaddigan.net/bookclub-advr.git
Log | Files | Refs | README | LICENSE

htmlwidgets.js (33190B)


      1 (function() {
      2   // If window.HTMLWidgets is already defined, then use it; otherwise create a
      3   // new object. This allows preceding code to set options that affect the
      4   // initialization process (though none currently exist).
      5   window.HTMLWidgets = window.HTMLWidgets || {};
      6 
      7   // See if we're running in a viewer pane. If not, we're in a web browser.
      8   var viewerMode = window.HTMLWidgets.viewerMode =
      9       /\bviewer_pane=1\b/.test(window.location);
     10 
     11   // See if we're running in Shiny mode. If not, it's a static document.
     12   // Note that static widgets can appear in both Shiny and static modes, but
     13   // obviously, Shiny widgets can only appear in Shiny apps/documents.
     14   var shinyMode = window.HTMLWidgets.shinyMode =
     15       typeof(window.Shiny) !== "undefined" && !!window.Shiny.outputBindings;
     16 
     17   // We can't count on jQuery being available, so we implement our own
     18   // version if necessary.
     19   function querySelectorAll(scope, selector) {
     20     if (typeof(jQuery) !== "undefined" && scope instanceof jQuery) {
     21       return scope.find(selector);
     22     }
     23     if (scope.querySelectorAll) {
     24       return scope.querySelectorAll(selector);
     25     }
     26   }
     27 
     28   function asArray(value) {
     29     if (value === null)
     30       return [];
     31     if ($.isArray(value))
     32       return value;
     33     return [value];
     34   }
     35 
     36   // Implement jQuery's extend
     37   function extend(target /*, ... */) {
     38     if (arguments.length == 1) {
     39       return target;
     40     }
     41     for (var i = 1; i < arguments.length; i++) {
     42       var source = arguments[i];
     43       for (var prop in source) {
     44         if (source.hasOwnProperty(prop)) {
     45           target[prop] = source[prop];
     46         }
     47       }
     48     }
     49     return target;
     50   }
     51 
     52   // IE8 doesn't support Array.forEach.
     53   function forEach(values, callback, thisArg) {
     54     if (values.forEach) {
     55       values.forEach(callback, thisArg);
     56     } else {
     57       for (var i = 0; i < values.length; i++) {
     58         callback.call(thisArg, values[i], i, values);
     59       }
     60     }
     61   }
     62 
     63   // Replaces the specified method with the return value of funcSource.
     64   //
     65   // Note that funcSource should not BE the new method, it should be a function
     66   // that RETURNS the new method. funcSource receives a single argument that is
     67   // the overridden method, it can be called from the new method. The overridden
     68   // method can be called like a regular function, it has the target permanently
     69   // bound to it so "this" will work correctly.
     70   function overrideMethod(target, methodName, funcSource) {
     71     var superFunc = target[methodName] || function() {};
     72     var superFuncBound = function() {
     73       return superFunc.apply(target, arguments);
     74     };
     75     target[methodName] = funcSource(superFuncBound);
     76   }
     77 
     78   // Add a method to delegator that, when invoked, calls
     79   // delegatee.methodName. If there is no such method on
     80   // the delegatee, but there was one on delegator before
     81   // delegateMethod was called, then the original version
     82   // is invoked instead.
     83   // For example:
     84   //
     85   // var a = {
     86   //   method1: function() { console.log('a1'); }
     87   //   method2: function() { console.log('a2'); }
     88   // };
     89   // var b = {
     90   //   method1: function() { console.log('b1'); }
     91   // };
     92   // delegateMethod(a, b, "method1");
     93   // delegateMethod(a, b, "method2");
     94   // a.method1();
     95   // a.method2();
     96   //
     97   // The output would be "b1", "a2".
     98   function delegateMethod(delegator, delegatee, methodName) {
     99     var inherited = delegator[methodName];
    100     delegator[methodName] = function() {
    101       var target = delegatee;
    102       var method = delegatee[methodName];
    103 
    104       // The method doesn't exist on the delegatee. Instead,
    105       // call the method on the delegator, if it exists.
    106       if (!method) {
    107         target = delegator;
    108         method = inherited;
    109       }
    110 
    111       if (method) {
    112         return method.apply(target, arguments);
    113       }
    114     };
    115   }
    116 
    117   // Implement a vague facsimilie of jQuery's data method
    118   function elementData(el, name, value) {
    119     if (arguments.length == 2) {
    120       return el["htmlwidget_data_" + name];
    121     } else if (arguments.length == 3) {
    122       el["htmlwidget_data_" + name] = value;
    123       return el;
    124     } else {
    125       throw new Error("Wrong number of arguments for elementData: " +
    126         arguments.length);
    127     }
    128   }
    129 
    130   // http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
    131   function escapeRegExp(str) {
    132     return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
    133   }
    134 
    135   function hasClass(el, className) {
    136     var re = new RegExp("\\b" + escapeRegExp(className) + "\\b");
    137     return re.test(el.className);
    138   }
    139 
    140   // elements - array (or array-like object) of HTML elements
    141   // className - class name to test for
    142   // include - if true, only return elements with given className;
    143   //   if false, only return elements *without* given className
    144   function filterByClass(elements, className, include) {
    145     var results = [];
    146     for (var i = 0; i < elements.length; i++) {
    147       if (hasClass(elements[i], className) == include)
    148         results.push(elements[i]);
    149     }
    150     return results;
    151   }
    152 
    153   function on(obj, eventName, func) {
    154     if (obj.addEventListener) {
    155       obj.addEventListener(eventName, func, false);
    156     } else if (obj.attachEvent) {
    157       obj.attachEvent(eventName, func);
    158     }
    159   }
    160 
    161   function off(obj, eventName, func) {
    162     if (obj.removeEventListener)
    163       obj.removeEventListener(eventName, func, false);
    164     else if (obj.detachEvent) {
    165       obj.detachEvent(eventName, func);
    166     }
    167   }
    168 
    169   // Translate array of values to top/right/bottom/left, as usual with
    170   // the "padding" CSS property
    171   // https://developer.mozilla.org/en-US/docs/Web/CSS/padding
    172   function unpackPadding(value) {
    173     if (typeof(value) === "number")
    174       value = [value];
    175     if (value.length === 1) {
    176       return {top: value[0], right: value[0], bottom: value[0], left: value[0]};
    177     }
    178     if (value.length === 2) {
    179       return {top: value[0], right: value[1], bottom: value[0], left: value[1]};
    180     }
    181     if (value.length === 3) {
    182       return {top: value[0], right: value[1], bottom: value[2], left: value[1]};
    183     }
    184     if (value.length === 4) {
    185       return {top: value[0], right: value[1], bottom: value[2], left: value[3]};
    186     }
    187   }
    188 
    189   // Convert an unpacked padding object to a CSS value
    190   function paddingToCss(paddingObj) {
    191     return paddingObj.top + "px " + paddingObj.right + "px " + paddingObj.bottom + "px " + paddingObj.left + "px";
    192   }
    193 
    194   // Makes a number suitable for CSS
    195   function px(x) {
    196     if (typeof(x) === "number")
    197       return x + "px";
    198     else
    199       return x;
    200   }
    201 
    202   // Retrieves runtime widget sizing information for an element.
    203   // The return value is either null, or an object with fill, padding,
    204   // defaultWidth, defaultHeight fields.
    205   function sizingPolicy(el) {
    206     var sizingEl = document.querySelector("script[data-for='" + el.id + "'][type='application/htmlwidget-sizing']");
    207     if (!sizingEl)
    208       return null;
    209     var sp = JSON.parse(sizingEl.textContent || sizingEl.text || "{}");
    210     if (viewerMode) {
    211       return sp.viewer;
    212     } else {
    213       return sp.browser;
    214     }
    215   }
    216 
    217   // @param tasks Array of strings (or falsy value, in which case no-op).
    218   //   Each element must be a valid JavaScript expression that yields a
    219   //   function. Or, can be an array of objects with "code" and "data"
    220   //   properties; in this case, the "code" property should be a string
    221   //   of JS that's an expr that yields a function, and "data" should be
    222   //   an object that will be added as an additional argument when that
    223   //   function is called.
    224   // @param target The object that will be "this" for each function
    225   //   execution.
    226   // @param args Array of arguments to be passed to the functions. (The
    227   //   same arguments will be passed to all functions.)
    228   function evalAndRun(tasks, target, args) {
    229     if (tasks) {
    230       forEach(tasks, function(task) {
    231         var theseArgs = args;
    232         if (typeof(task) === "object") {
    233           theseArgs = theseArgs.concat([task.data]);
    234           task = task.code;
    235         }
    236         var taskFunc = tryEval(task);
    237         if (typeof(taskFunc) !== "function") {
    238           throw new Error("Task must be a function! Source:\n" + task);
    239         }
    240         taskFunc.apply(target, theseArgs);
    241       });
    242     }
    243   }
    244 
    245   // Attempt eval() both with and without enclosing in parentheses.
    246   // Note that enclosing coerces a function declaration into
    247   // an expression that eval() can parse
    248   // (otherwise, a SyntaxError is thrown)
    249   function tryEval(code) {
    250     var result = null;
    251     try {
    252       result = eval("(" + code + ")");
    253     } catch(error) {
    254       if (!(error instanceof SyntaxError)) {
    255         throw error;
    256       }
    257       try {
    258         result = eval(code);
    259       } catch(e) {
    260         if (e instanceof SyntaxError) {
    261           throw error;
    262         } else {
    263           throw e;
    264         }
    265       }
    266     }
    267     return result;
    268   }
    269 
    270   function initSizing(el) {
    271     var sizing = sizingPolicy(el);
    272     if (!sizing)
    273       return;
    274 
    275     var cel = document.getElementById("htmlwidget_container");
    276     if (!cel)
    277       return;
    278 
    279     if (typeof(sizing.padding) !== "undefined") {
    280       document.body.style.margin = "0";
    281       document.body.style.padding = paddingToCss(unpackPadding(sizing.padding));
    282     }
    283 
    284     if (sizing.fill) {
    285       document.body.style.overflow = "hidden";
    286       document.body.style.width = "100%";
    287       document.body.style.height = "100%";
    288       document.documentElement.style.width = "100%";
    289       document.documentElement.style.height = "100%";
    290       cel.style.position = "absolute";
    291       var pad = unpackPadding(sizing.padding);
    292       cel.style.top = pad.top + "px";
    293       cel.style.right = pad.right + "px";
    294       cel.style.bottom = pad.bottom + "px";
    295       cel.style.left = pad.left + "px";
    296       el.style.width = "100%";
    297       el.style.height = "100%";
    298 
    299       return {
    300         getWidth: function() { return cel.getBoundingClientRect().width; },
    301         getHeight: function() { return cel.getBoundingClientRect().height; }
    302       };
    303 
    304     } else {
    305       el.style.width = px(sizing.width);
    306       el.style.height = px(sizing.height);
    307 
    308       return {
    309         getWidth: function() { return cel.getBoundingClientRect().width; },
    310         getHeight: function() { return cel.getBoundingClientRect().height; }
    311       };
    312     }
    313   }
    314 
    315   // Default implementations for methods
    316   var defaults = {
    317     find: function(scope) {
    318       return querySelectorAll(scope, "." + this.name);
    319     },
    320     renderError: function(el, err) {
    321       var $el = $(el);
    322 
    323       this.clearError(el);
    324 
    325       // Add all these error classes, as Shiny does
    326       var errClass = "shiny-output-error";
    327       if (err.type !== null) {
    328         // use the classes of the error condition as CSS class names
    329         errClass = errClass + " " + $.map(asArray(err.type), function(type) {
    330           return errClass + "-" + type;
    331         }).join(" ");
    332       }
    333       errClass = errClass + " htmlwidgets-error";
    334 
    335       // Is el inline or block? If inline or inline-block, just display:none it
    336       // and add an inline error.
    337       var display = $el.css("display");
    338       $el.data("restore-display-mode", display);
    339 
    340       if (display === "inline" || display === "inline-block") {
    341         $el.hide();
    342         if (err.message !== "") {
    343           var errorSpan = $("<span>").addClass(errClass);
    344           errorSpan.text(err.message);
    345           $el.after(errorSpan);
    346         }
    347       } else if (display === "block") {
    348         // If block, add an error just after the el, set visibility:none on the
    349         // el, and position the error to be on top of the el.
    350         // Mark it with a unique ID and CSS class so we can remove it later.
    351         $el.css("visibility", "hidden");
    352         if (err.message !== "") {
    353           var errorDiv = $("<div>").addClass(errClass).css("position", "absolute")
    354             .css("top", el.offsetTop)
    355             .css("left", el.offsetLeft)
    356             // setting width can push out the page size, forcing otherwise
    357             // unnecessary scrollbars to appear and making it impossible for
    358             // the element to shrink; so use max-width instead
    359             .css("maxWidth", el.offsetWidth)
    360             .css("height", el.offsetHeight);
    361           errorDiv.text(err.message);
    362           $el.after(errorDiv);
    363 
    364           // Really dumb way to keep the size/position of the error in sync with
    365           // the parent element as the window is resized or whatever.
    366           var intId = setInterval(function() {
    367             if (!errorDiv[0].parentElement) {
    368               clearInterval(intId);
    369               return;
    370             }
    371             errorDiv
    372               .css("top", el.offsetTop)
    373               .css("left", el.offsetLeft)
    374               .css("maxWidth", el.offsetWidth)
    375               .css("height", el.offsetHeight);
    376           }, 500);
    377         }
    378       }
    379     },
    380     clearError: function(el) {
    381       var $el = $(el);
    382       var display = $el.data("restore-display-mode");
    383       $el.data("restore-display-mode", null);
    384 
    385       if (display === "inline" || display === "inline-block") {
    386         if (display)
    387           $el.css("display", display);
    388         $(el.nextSibling).filter(".htmlwidgets-error").remove();
    389       } else if (display === "block"){
    390         $el.css("visibility", "inherit");
    391         $(el.nextSibling).filter(".htmlwidgets-error").remove();
    392       }
    393     },
    394     sizing: {}
    395   };
    396 
    397   // Called by widget bindings to register a new type of widget. The definition
    398   // object can contain the following properties:
    399   // - name (required) - A string indicating the binding name, which will be
    400   //   used by default as the CSS classname to look for.
    401   // - initialize (optional) - A function(el) that will be called once per
    402   //   widget element; if a value is returned, it will be passed as the third
    403   //   value to renderValue.
    404   // - renderValue (required) - A function(el, data, initValue) that will be
    405   //   called with data. Static contexts will cause this to be called once per
    406   //   element; Shiny apps will cause this to be called multiple times per
    407   //   element, as the data changes.
    408   window.HTMLWidgets.widget = function(definition) {
    409     if (!definition.name) {
    410       throw new Error("Widget must have a name");
    411     }
    412     if (!definition.type) {
    413       throw new Error("Widget must have a type");
    414     }
    415     // Currently we only support output widgets
    416     if (definition.type !== "output") {
    417       throw new Error("Unrecognized widget type '" + definition.type + "'");
    418     }
    419     // TODO: Verify that .name is a valid CSS classname
    420 
    421     // Support new-style instance-bound definitions. Old-style class-bound
    422     // definitions have one widget "object" per widget per type/class of
    423     // widget; the renderValue and resize methods on such widget objects
    424     // take el and instance arguments, because the widget object can't
    425     // store them. New-style instance-bound definitions have one widget
    426     // object per widget instance; the definition that's passed in doesn't
    427     // provide renderValue or resize methods at all, just the single method
    428     //   factory(el, width, height)
    429     // which returns an object that has renderValue(x) and resize(w, h).
    430     // This enables a far more natural programming style for the widget
    431     // author, who can store per-instance state using either OO-style
    432     // instance fields or functional-style closure variables (I guess this
    433     // is in contrast to what can only be called C-style pseudo-OO which is
    434     // what we required before).
    435     if (definition.factory) {
    436       definition = createLegacyDefinitionAdapter(definition);
    437     }
    438 
    439     if (!definition.renderValue) {
    440       throw new Error("Widget must have a renderValue function");
    441     }
    442 
    443     // For static rendering (non-Shiny), use a simple widget registration
    444     // scheme. We also use this scheme for Shiny apps/documents that also
    445     // contain static widgets.
    446     window.HTMLWidgets.widgets = window.HTMLWidgets.widgets || [];
    447     // Merge defaults into the definition; don't mutate the original definition.
    448     var staticBinding = extend({}, defaults, definition);
    449     overrideMethod(staticBinding, "find", function(superfunc) {
    450       return function(scope) {
    451         var results = superfunc(scope);
    452         // Filter out Shiny outputs, we only want the static kind
    453         return filterByClass(results, "html-widget-output", false);
    454       };
    455     });
    456     window.HTMLWidgets.widgets.push(staticBinding);
    457 
    458     if (shinyMode) {
    459       // Shiny is running. Register the definition with an output binding.
    460       // The definition itself will not be the output binding, instead
    461       // we will make an output binding object that delegates to the
    462       // definition. This is because we foolishly used the same method
    463       // name (renderValue) for htmlwidgets definition and Shiny bindings
    464       // but they actually have quite different semantics (the Shiny
    465       // bindings receive data that includes lots of metadata that it
    466       // strips off before calling htmlwidgets renderValue). We can't
    467       // just ignore the difference because in some widgets it's helpful
    468       // to call this.renderValue() from inside of resize(), and if
    469       // we're not delegating, then that call will go to the Shiny
    470       // version instead of the htmlwidgets version.
    471 
    472       // Merge defaults with definition, without mutating either.
    473       var bindingDef = extend({}, defaults, definition);
    474 
    475       // This object will be our actual Shiny binding.
    476       var shinyBinding = new Shiny.OutputBinding();
    477 
    478       // With a few exceptions, we'll want to simply use the bindingDef's
    479       // version of methods if they are available, otherwise fall back to
    480       // Shiny's defaults. NOTE: If Shiny's output bindings gain additional
    481       // methods in the future, and we want them to be overrideable by
    482       // HTMLWidget binding definitions, then we'll need to add them to this
    483       // list.
    484       delegateMethod(shinyBinding, bindingDef, "getId");
    485       delegateMethod(shinyBinding, bindingDef, "onValueChange");
    486       delegateMethod(shinyBinding, bindingDef, "onValueError");
    487       delegateMethod(shinyBinding, bindingDef, "renderError");
    488       delegateMethod(shinyBinding, bindingDef, "clearError");
    489       delegateMethod(shinyBinding, bindingDef, "showProgress");
    490 
    491       // The find, renderValue, and resize are handled differently, because we
    492       // want to actually decorate the behavior of the bindingDef methods.
    493 
    494       shinyBinding.find = function(scope) {
    495         var results = bindingDef.find(scope);
    496 
    497         // Only return elements that are Shiny outputs, not static ones
    498         var dynamicResults = results.filter(".html-widget-output");
    499 
    500         // It's possible that whatever caused Shiny to think there might be
    501         // new dynamic outputs, also caused there to be new static outputs.
    502         // Since there might be lots of different htmlwidgets bindings, we
    503         // schedule execution for later--no need to staticRender multiple
    504         // times.
    505         if (results.length !== dynamicResults.length)
    506           scheduleStaticRender();
    507 
    508         return dynamicResults;
    509       };
    510 
    511       // Wrap renderValue to handle initialization, which unfortunately isn't
    512       // supported natively by Shiny at the time of this writing.
    513 
    514       shinyBinding.renderValue = function(el, data) {
    515         Shiny.renderDependencies(data.deps);
    516         // Resolve strings marked as javascript literals to objects
    517         if (!(data.evals instanceof Array)) data.evals = [data.evals];
    518         for (var i = 0; data.evals && i < data.evals.length; i++) {
    519           window.HTMLWidgets.evaluateStringMember(data.x, data.evals[i]);
    520         }
    521         if (!bindingDef.renderOnNullValue) {
    522           if (data.x === null) {
    523             el.style.visibility = "hidden";
    524             return;
    525           } else {
    526             el.style.visibility = "inherit";
    527           }
    528         }
    529         if (!elementData(el, "initialized")) {
    530           initSizing(el);
    531 
    532           elementData(el, "initialized", true);
    533           if (bindingDef.initialize) {
    534             var rect = el.getBoundingClientRect();
    535             var result = bindingDef.initialize(el, rect.width, rect.height);
    536             elementData(el, "init_result", result);
    537           }
    538         }
    539         bindingDef.renderValue(el, data.x, elementData(el, "init_result"));
    540         evalAndRun(data.jsHooks.render, elementData(el, "init_result"), [el, data.x]);
    541       };
    542 
    543       // Only override resize if bindingDef implements it
    544       if (bindingDef.resize) {
    545         shinyBinding.resize = function(el, width, height) {
    546           // Shiny can call resize before initialize/renderValue have been
    547           // called, which doesn't make sense for widgets.
    548           if (elementData(el, "initialized")) {
    549             bindingDef.resize(el, width, height, elementData(el, "init_result"));
    550           }
    551         };
    552       }
    553 
    554       Shiny.outputBindings.register(shinyBinding, bindingDef.name);
    555     }
    556   };
    557 
    558   var scheduleStaticRenderTimerId = null;
    559   function scheduleStaticRender() {
    560     if (!scheduleStaticRenderTimerId) {
    561       scheduleStaticRenderTimerId = setTimeout(function() {
    562         scheduleStaticRenderTimerId = null;
    563         window.HTMLWidgets.staticRender();
    564       }, 1);
    565     }
    566   }
    567 
    568   // Render static widgets after the document finishes loading
    569   // Statically render all elements that are of this widget's class
    570   window.HTMLWidgets.staticRender = function() {
    571     var bindings = window.HTMLWidgets.widgets || [];
    572     forEach(bindings, function(binding) {
    573       var matches = binding.find(document.documentElement);
    574       forEach(matches, function(el) {
    575         var sizeObj = initSizing(el, binding);
    576 
    577         var getSize = function(el) {
    578           if (sizeObj) {
    579             return {w: sizeObj.getWidth(), h: sizeObj.getHeight()}
    580           } else {
    581             var rect = el.getBoundingClientRect();
    582             return {w: rect.width, h: rect.height}
    583           }
    584         };
    585 
    586         if (hasClass(el, "html-widget-static-bound"))
    587           return;
    588         el.className = el.className + " html-widget-static-bound";
    589 
    590         var initResult;
    591         if (binding.initialize) {
    592           var size = getSize(el);
    593           initResult = binding.initialize(el, size.w, size.h);
    594           elementData(el, "init_result", initResult);
    595         }
    596 
    597         if (binding.resize) {
    598           var lastSize = getSize(el);
    599           var resizeHandler = function(e) {
    600             var size = getSize(el);
    601             if (size.w === 0 && size.h === 0)
    602               return;
    603             if (size.w === lastSize.w && size.h === lastSize.h)
    604               return;
    605             lastSize = size;
    606             binding.resize(el, size.w, size.h, initResult);
    607           };
    608 
    609           on(window, "resize", resizeHandler);
    610 
    611           // This is needed for cases where we're running in a Shiny
    612           // app, but the widget itself is not a Shiny output, but
    613           // rather a simple static widget. One example of this is
    614           // an rmarkdown document that has runtime:shiny and widget
    615           // that isn't in a render function. Shiny only knows to
    616           // call resize handlers for Shiny outputs, not for static
    617           // widgets, so we do it ourselves.
    618           if (window.jQuery) {
    619             window.jQuery(document).on(
    620               "shown.htmlwidgets shown.bs.tab.htmlwidgets shown.bs.collapse.htmlwidgets",
    621               resizeHandler
    622             );
    623             window.jQuery(document).on(
    624               "hidden.htmlwidgets hidden.bs.tab.htmlwidgets hidden.bs.collapse.htmlwidgets",
    625               resizeHandler
    626             );
    627           }
    628 
    629           // This is needed for the specific case of ioslides, which
    630           // flips slides between display:none and display:block.
    631           // Ideally we would not have to have ioslide-specific code
    632           // here, but rather have ioslides raise a generic event,
    633           // but the rmarkdown package just went to CRAN so the
    634           // window to getting that fixed may be long.
    635           if (window.addEventListener) {
    636             // It's OK to limit this to window.addEventListener
    637             // browsers because ioslides itself only supports
    638             // such browsers.
    639             on(document, "slideenter", resizeHandler);
    640             on(document, "slideleave", resizeHandler);
    641           }
    642         }
    643 
    644         var scriptData = document.querySelector("script[data-for='" + el.id + "'][type='application/json']");
    645         if (scriptData) {
    646           var data = JSON.parse(scriptData.textContent || scriptData.text);
    647           // Resolve strings marked as javascript literals to objects
    648           if (!(data.evals instanceof Array)) data.evals = [data.evals];
    649           for (var k = 0; data.evals && k < data.evals.length; k++) {
    650             window.HTMLWidgets.evaluateStringMember(data.x, data.evals[k]);
    651           }
    652           binding.renderValue(el, data.x, initResult);
    653           evalAndRun(data.jsHooks.render, initResult, [el, data.x]);
    654         }
    655       });
    656     });
    657 
    658     invokePostRenderHandlers();
    659   }
    660 
    661 
    662   function has_jQuery3() {
    663     if (!window.jQuery) {
    664       return false;
    665     }
    666     var $version = window.jQuery.fn.jquery;
    667     var $major_version = parseInt($version.split(".")[0]);
    668     return $major_version >= 3;
    669   }
    670 
    671   /*
    672   / Shiny 1.4 bumped jQuery from 1.x to 3.x which means jQuery's
    673   / on-ready handler (i.e., $(fn)) is now asyncronous (i.e., it now
    674   / really means $(setTimeout(fn)).
    675   / https://jquery.com/upgrade-guide/3.0/#breaking-change-document-ready-handlers-are-now-asynchronous
    676   /
    677   / Since Shiny uses $() to schedule initShiny, shiny>=1.4 calls initShiny
    678   / one tick later than it did before, which means staticRender() is
    679   / called renderValue() earlier than (advanced) widget authors might be expecting.
    680   / https://github.com/rstudio/shiny/issues/2630
    681   /
    682   / For a concrete example, leaflet has some methods (e.g., updateBounds)
    683   / which reference Shiny methods registered in initShiny (e.g., setInputValue).
    684   / Since leaflet is privy to this life-cycle, it knows to use setTimeout() to
    685   / delay execution of those methods (until Shiny methods are ready)
    686   / https://github.com/rstudio/leaflet/blob/18ec981/javascript/src/index.js#L266-L268
    687   /
    688   / Ideally widget authors wouldn't need to use this setTimeout() hack that
    689   / leaflet uses to call Shiny methods on a staticRender(). In the long run,
    690   / the logic initShiny should be broken up so that method registration happens
    691   / right away, but binding happens later.
    692   */
    693   function maybeStaticRenderLater() {
    694     if (shinyMode && has_jQuery3()) {
    695       window.jQuery(window.HTMLWidgets.staticRender);
    696     } else {
    697       window.HTMLWidgets.staticRender();
    698     }
    699   }
    700 
    701   if (document.addEventListener) {
    702     document.addEventListener("DOMContentLoaded", function() {
    703       document.removeEventListener("DOMContentLoaded", arguments.callee, false);
    704       maybeStaticRenderLater();
    705     }, false);
    706   } else if (document.attachEvent) {
    707     document.attachEvent("onreadystatechange", function() {
    708       if (document.readyState === "complete") {
    709         document.detachEvent("onreadystatechange", arguments.callee);
    710         maybeStaticRenderLater();
    711       }
    712     });
    713   }
    714 
    715 
    716   window.HTMLWidgets.getAttachmentUrl = function(depname, key) {
    717     // If no key, default to the first item
    718     if (typeof(key) === "undefined")
    719       key = 1;
    720 
    721     var link = document.getElementById(depname + "-" + key + "-attachment");
    722     if (!link) {
    723       throw new Error("Attachment " + depname + "/" + key + " not found in document");
    724     }
    725     return link.getAttribute("href");
    726   };
    727 
    728   window.HTMLWidgets.dataframeToD3 = function(df) {
    729     var names = [];
    730     var length;
    731     for (var name in df) {
    732         if (df.hasOwnProperty(name))
    733             names.push(name);
    734         if (typeof(df[name]) !== "object" || typeof(df[name].length) === "undefined") {
    735             throw new Error("All fields must be arrays");
    736         } else if (typeof(length) !== "undefined" && length !== df[name].length) {
    737             throw new Error("All fields must be arrays of the same length");
    738         }
    739         length = df[name].length;
    740     }
    741     var results = [];
    742     var item;
    743     for (var row = 0; row < length; row++) {
    744         item = {};
    745         for (var col = 0; col < names.length; col++) {
    746             item[names[col]] = df[names[col]][row];
    747         }
    748         results.push(item);
    749     }
    750     return results;
    751   };
    752 
    753   window.HTMLWidgets.transposeArray2D = function(array) {
    754       if (array.length === 0) return array;
    755       var newArray = array[0].map(function(col, i) {
    756           return array.map(function(row) {
    757               return row[i]
    758           })
    759       });
    760       return newArray;
    761   };
    762   // Split value at splitChar, but allow splitChar to be escaped
    763   // using escapeChar. Any other characters escaped by escapeChar
    764   // will be included as usual (including escapeChar itself).
    765   function splitWithEscape(value, splitChar, escapeChar) {
    766     var results = [];
    767     var escapeMode = false;
    768     var currentResult = "";
    769     for (var pos = 0; pos < value.length; pos++) {
    770       if (!escapeMode) {
    771         if (value[pos] === splitChar) {
    772           results.push(currentResult);
    773           currentResult = "";
    774         } else if (value[pos] === escapeChar) {
    775           escapeMode = true;
    776         } else {
    777           currentResult += value[pos];
    778         }
    779       } else {
    780         currentResult += value[pos];
    781         escapeMode = false;
    782       }
    783     }
    784     if (currentResult !== "") {
    785       results.push(currentResult);
    786     }
    787     return results;
    788   }
    789   // Function authored by Yihui/JJ Allaire
    790   window.HTMLWidgets.evaluateStringMember = function(o, member) {
    791     var parts = splitWithEscape(member, '.', '\\');
    792     for (var i = 0, l = parts.length; i < l; i++) {
    793       var part = parts[i];
    794       // part may be a character or 'numeric' member name
    795       if (o !== null && typeof o === "object" && part in o) {
    796         if (i == (l - 1)) { // if we are at the end of the line then evalulate
    797           if (typeof o[part] === "string")
    798             o[part] = tryEval(o[part]);
    799         } else { // otherwise continue to next embedded object
    800           o = o[part];
    801         }
    802       }
    803     }
    804   };
    805 
    806   // Retrieve the HTMLWidget instance (i.e. the return value of an
    807   // HTMLWidget binding's initialize() or factory() function)
    808   // associated with an element, or null if none.
    809   window.HTMLWidgets.getInstance = function(el) {
    810     return elementData(el, "init_result");
    811   };
    812 
    813   // Finds the first element in the scope that matches the selector,
    814   // and returns the HTMLWidget instance (i.e. the return value of
    815   // an HTMLWidget binding's initialize() or factory() function)
    816   // associated with that element, if any. If no element matches the
    817   // selector, or the first matching element has no HTMLWidget
    818   // instance associated with it, then null is returned.
    819   //
    820   // The scope argument is optional, and defaults to window.document.
    821   window.HTMLWidgets.find = function(scope, selector) {
    822     if (arguments.length == 1) {
    823       selector = scope;
    824       scope = document;
    825     }
    826 
    827     var el = scope.querySelector(selector);
    828     if (el === null) {
    829       return null;
    830     } else {
    831       return window.HTMLWidgets.getInstance(el);
    832     }
    833   };
    834 
    835   // Finds all elements in the scope that match the selector, and
    836   // returns the HTMLWidget instances (i.e. the return values of
    837   // an HTMLWidget binding's initialize() or factory() function)
    838   // associated with the elements, in an array. If elements that
    839   // match the selector don't have an associated HTMLWidget
    840   // instance, the returned array will contain nulls.
    841   //
    842   // The scope argument is optional, and defaults to window.document.
    843   window.HTMLWidgets.findAll = function(scope, selector) {
    844     if (arguments.length == 1) {
    845       selector = scope;
    846       scope = document;
    847     }
    848 
    849     var nodes = scope.querySelectorAll(selector);
    850     var results = [];
    851     for (var i = 0; i < nodes.length; i++) {
    852       results.push(window.HTMLWidgets.getInstance(nodes[i]));
    853     }
    854     return results;
    855   };
    856 
    857   var postRenderHandlers = [];
    858   function invokePostRenderHandlers() {
    859     while (postRenderHandlers.length) {
    860       var handler = postRenderHandlers.shift();
    861       if (handler) {
    862         handler();
    863       }
    864     }
    865   }
    866 
    867   // Register the given callback function to be invoked after the
    868   // next time static widgets are rendered.
    869   window.HTMLWidgets.addPostRenderHandler = function(callback) {
    870     postRenderHandlers.push(callback);
    871   };
    872 
    873   // Takes a new-style instance-bound definition, and returns an
    874   // old-style class-bound definition. This saves us from having
    875   // to rewrite all the logic in this file to accomodate both
    876   // types of definitions.
    877   function createLegacyDefinitionAdapter(defn) {
    878     var result = {
    879       name: defn.name,
    880       type: defn.type,
    881       initialize: function(el, width, height) {
    882         return defn.factory(el, width, height);
    883       },
    884       renderValue: function(el, x, instance) {
    885         return instance.renderValue(x);
    886       },
    887       resize: function(el, width, height, instance) {
    888         return instance.resize(width, height);
    889       }
    890     };
    891 
    892     if (defn.find)
    893       result.find = defn.find;
    894     if (defn.renderError)
    895       result.renderError = defn.renderError;
    896     if (defn.clearError)
    897       result.clearError = defn.clearError;
    898 
    899     return result;
    900   }
    901 })();