You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

htmlwidgets.js 32KB

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