1
0

htmlwidgets.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839
  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 = eval("(" + 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. function initSizing(el) {
  224. var sizing = sizingPolicy(el);
  225. if (!sizing)
  226. return;
  227. var cel = document.getElementById("htmlwidget_container");
  228. if (!cel)
  229. return;
  230. if (typeof(sizing.padding) !== "undefined") {
  231. document.body.style.margin = "0";
  232. document.body.style.padding = paddingToCss(unpackPadding(sizing.padding));
  233. }
  234. if (sizing.fill) {
  235. document.body.style.overflow = "hidden";
  236. document.body.style.width = "100%";
  237. document.body.style.height = "100%";
  238. document.documentElement.style.width = "100%";
  239. document.documentElement.style.height = "100%";
  240. if (cel) {
  241. cel.style.position = "absolute";
  242. var pad = unpackPadding(sizing.padding);
  243. cel.style.top = pad.top + "px";
  244. cel.style.right = pad.right + "px";
  245. cel.style.bottom = pad.bottom + "px";
  246. cel.style.left = pad.left + "px";
  247. el.style.width = "100%";
  248. el.style.height = "100%";
  249. }
  250. return {
  251. getWidth: function() { return cel.offsetWidth; },
  252. getHeight: function() { return cel.offsetHeight; }
  253. };
  254. } else {
  255. el.style.width = px(sizing.width);
  256. el.style.height = px(sizing.height);
  257. return {
  258. getWidth: function() { return el.offsetWidth; },
  259. getHeight: function() { return el.offsetHeight; }
  260. };
  261. }
  262. }
  263. // Default implementations for methods
  264. var defaults = {
  265. find: function(scope) {
  266. return querySelectorAll(scope, "." + this.name);
  267. },
  268. renderError: function(el, err) {
  269. var $el = $(el);
  270. this.clearError(el);
  271. // Add all these error classes, as Shiny does
  272. var errClass = "shiny-output-error";
  273. if (err.type !== null) {
  274. // use the classes of the error condition as CSS class names
  275. errClass = errClass + " " + $.map(asArray(err.type), function(type) {
  276. return errClass + "-" + type;
  277. }).join(" ");
  278. }
  279. errClass = errClass + " htmlwidgets-error";
  280. // Is el inline or block? If inline or inline-block, just display:none it
  281. // and add an inline error.
  282. var display = $el.css("display");
  283. $el.data("restore-display-mode", display);
  284. if (display === "inline" || display === "inline-block") {
  285. $el.hide();
  286. if (err.message !== "") {
  287. var errorSpan = $("<span>").addClass(errClass);
  288. errorSpan.text(err.message);
  289. $el.after(errorSpan);
  290. }
  291. } else if (display === "block") {
  292. // If block, add an error just after the el, set visibility:none on the
  293. // el, and position the error to be on top of the el.
  294. // Mark it with a unique ID and CSS class so we can remove it later.
  295. $el.css("visibility", "hidden");
  296. if (err.message !== "") {
  297. var errorDiv = $("<div>").addClass(errClass).css("position", "absolute")
  298. .css("top", el.offsetTop)
  299. .css("left", el.offsetLeft)
  300. // setting width can push out the page size, forcing otherwise
  301. // unnecessary scrollbars to appear and making it impossible for
  302. // the element to shrink; so use max-width instead
  303. .css("maxWidth", el.offsetWidth)
  304. .css("height", el.offsetHeight);
  305. errorDiv.text(err.message);
  306. $el.after(errorDiv);
  307. // Really dumb way to keep the size/position of the error in sync with
  308. // the parent element as the window is resized or whatever.
  309. var intId = setInterval(function() {
  310. if (!errorDiv[0].parentElement) {
  311. clearInterval(intId);
  312. return;
  313. }
  314. errorDiv
  315. .css("top", el.offsetTop)
  316. .css("left", el.offsetLeft)
  317. .css("maxWidth", el.offsetWidth)
  318. .css("height", el.offsetHeight);
  319. }, 500);
  320. }
  321. }
  322. },
  323. clearError: function(el) {
  324. var $el = $(el);
  325. var display = $el.data("restore-display-mode");
  326. $el.data("restore-display-mode", null);
  327. if (display === "inline" || display === "inline-block") {
  328. if (display)
  329. $el.css("display", display);
  330. $(el.nextSibling).filter(".htmlwidgets-error").remove();
  331. } else if (display === "block"){
  332. $el.css("visibility", "inherit");
  333. $(el.nextSibling).filter(".htmlwidgets-error").remove();
  334. }
  335. },
  336. sizing: {}
  337. };
  338. // Called by widget bindings to register a new type of widget. The definition
  339. // object can contain the following properties:
  340. // - name (required) - A string indicating the binding name, which will be
  341. // used by default as the CSS classname to look for.
  342. // - initialize (optional) - A function(el) that will be called once per
  343. // widget element; if a value is returned, it will be passed as the third
  344. // value to renderValue.
  345. // - renderValue (required) - A function(el, data, initValue) that will be
  346. // called with data. Static contexts will cause this to be called once per
  347. // element; Shiny apps will cause this to be called multiple times per
  348. // element, as the data changes.
  349. window.HTMLWidgets.widget = function(definition) {
  350. if (!definition.name) {
  351. throw new Error("Widget must have a name");
  352. }
  353. if (!definition.type) {
  354. throw new Error("Widget must have a type");
  355. }
  356. // Currently we only support output widgets
  357. if (definition.type !== "output") {
  358. throw new Error("Unrecognized widget type '" + definition.type + "'");
  359. }
  360. // TODO: Verify that .name is a valid CSS classname
  361. // Support new-style instance-bound definitions. Old-style class-bound
  362. // definitions have one widget "object" per widget per type/class of
  363. // widget; the renderValue and resize methods on such widget objects
  364. // take el and instance arguments, because the widget object can't
  365. // store them. New-style instance-bound definitions have one widget
  366. // object per widget instance; the definition that's passed in doesn't
  367. // provide renderValue or resize methods at all, just the single method
  368. // factory(el, width, height)
  369. // which returns an object that has renderValue(x) and resize(w, h).
  370. // This enables a far more natural programming style for the widget
  371. // author, who can store per-instance state using either OO-style
  372. // instance fields or functional-style closure variables (I guess this
  373. // is in contrast to what can only be called C-style pseudo-OO which is
  374. // what we required before).
  375. if (definition.factory) {
  376. definition = createLegacyDefinitionAdapter(definition);
  377. }
  378. if (!definition.renderValue) {
  379. throw new Error("Widget must have a renderValue function");
  380. }
  381. // For static rendering (non-Shiny), use a simple widget registration
  382. // scheme. We also use this scheme for Shiny apps/documents that also
  383. // contain static widgets.
  384. window.HTMLWidgets.widgets = window.HTMLWidgets.widgets || [];
  385. // Merge defaults into the definition; don't mutate the original definition.
  386. var staticBinding = extend({}, defaults, definition);
  387. overrideMethod(staticBinding, "find", function(superfunc) {
  388. return function(scope) {
  389. var results = superfunc(scope);
  390. // Filter out Shiny outputs, we only want the static kind
  391. return filterByClass(results, "html-widget-output", false);
  392. };
  393. });
  394. window.HTMLWidgets.widgets.push(staticBinding);
  395. if (shinyMode) {
  396. // Shiny is running. Register the definition with an output binding.
  397. // The definition itself will not be the output binding, instead
  398. // we will make an output binding object that delegates to the
  399. // definition. This is because we foolishly used the same method
  400. // name (renderValue) for htmlwidgets definition and Shiny bindings
  401. // but they actually have quite different semantics (the Shiny
  402. // bindings receive data that includes lots of metadata that it
  403. // strips off before calling htmlwidgets renderValue). We can't
  404. // just ignore the difference because in some widgets it's helpful
  405. // to call this.renderValue() from inside of resize(), and if
  406. // we're not delegating, then that call will go to the Shiny
  407. // version instead of the htmlwidgets version.
  408. // Merge defaults with definition, without mutating either.
  409. var bindingDef = extend({}, defaults, definition);
  410. // This object will be our actual Shiny binding.
  411. var shinyBinding = new Shiny.OutputBinding();
  412. // With a few exceptions, we'll want to simply use the bindingDef's
  413. // version of methods if they are available, otherwise fall back to
  414. // Shiny's defaults. NOTE: If Shiny's output bindings gain additional
  415. // methods in the future, and we want them to be overrideable by
  416. // HTMLWidget binding definitions, then we'll need to add them to this
  417. // list.
  418. delegateMethod(shinyBinding, bindingDef, "getId");
  419. delegateMethod(shinyBinding, bindingDef, "onValueChange");
  420. delegateMethod(shinyBinding, bindingDef, "onValueError");
  421. delegateMethod(shinyBinding, bindingDef, "renderError");
  422. delegateMethod(shinyBinding, bindingDef, "clearError");
  423. delegateMethod(shinyBinding, bindingDef, "showProgress");
  424. // The find, renderValue, and resize are handled differently, because we
  425. // want to actually decorate the behavior of the bindingDef methods.
  426. shinyBinding.find = function(scope) {
  427. var results = bindingDef.find(scope);
  428. // Only return elements that are Shiny outputs, not static ones
  429. var dynamicResults = results.filter(".html-widget-output");
  430. // It's possible that whatever caused Shiny to think there might be
  431. // new dynamic outputs, also caused there to be new static outputs.
  432. // Since there might be lots of different htmlwidgets bindings, we
  433. // schedule execution for later--no need to staticRender multiple
  434. // times.
  435. if (results.length !== dynamicResults.length)
  436. scheduleStaticRender();
  437. return dynamicResults;
  438. };
  439. // Wrap renderValue to handle initialization, which unfortunately isn't
  440. // supported natively by Shiny at the time of this writing.
  441. shinyBinding.renderValue = function(el, data) {
  442. Shiny.renderDependencies(data.deps);
  443. // Resolve strings marked as javascript literals to objects
  444. if (!(data.evals instanceof Array)) data.evals = [data.evals];
  445. for (var i = 0; data.evals && i < data.evals.length; i++) {
  446. window.HTMLWidgets.evaluateStringMember(data.x, data.evals[i]);
  447. }
  448. if (!bindingDef.renderOnNullValue) {
  449. if (data.x === null) {
  450. el.style.visibility = "hidden";
  451. return;
  452. } else {
  453. el.style.visibility = "inherit";
  454. }
  455. }
  456. if (!elementData(el, "initialized")) {
  457. initSizing(el);
  458. elementData(el, "initialized", true);
  459. if (bindingDef.initialize) {
  460. var result = bindingDef.initialize(el, el.offsetWidth,
  461. el.offsetHeight);
  462. elementData(el, "init_result", result);
  463. }
  464. }
  465. bindingDef.renderValue(el, data.x, elementData(el, "init_result"));
  466. evalAndRun(data.jsHooks.render, elementData(el, "init_result"), [el, data.x]);
  467. };
  468. // Only override resize if bindingDef implements it
  469. if (bindingDef.resize) {
  470. shinyBinding.resize = function(el, width, height) {
  471. // Shiny can call resize before initialize/renderValue have been
  472. // called, which doesn't make sense for widgets.
  473. if (elementData(el, "initialized")) {
  474. bindingDef.resize(el, width, height, elementData(el, "init_result"));
  475. }
  476. };
  477. }
  478. Shiny.outputBindings.register(shinyBinding, bindingDef.name);
  479. }
  480. };
  481. var scheduleStaticRenderTimerId = null;
  482. function scheduleStaticRender() {
  483. if (!scheduleStaticRenderTimerId) {
  484. scheduleStaticRenderTimerId = setTimeout(function() {
  485. scheduleStaticRenderTimerId = null;
  486. window.HTMLWidgets.staticRender();
  487. }, 1);
  488. }
  489. }
  490. // Render static widgets after the document finishes loading
  491. // Statically render all elements that are of this widget's class
  492. window.HTMLWidgets.staticRender = function() {
  493. var bindings = window.HTMLWidgets.widgets || [];
  494. forEach(bindings, function(binding) {
  495. var matches = binding.find(document.documentElement);
  496. forEach(matches, function(el) {
  497. var sizeObj = initSizing(el, binding);
  498. if (hasClass(el, "html-widget-static-bound"))
  499. return;
  500. el.className = el.className + " html-widget-static-bound";
  501. var initResult;
  502. if (binding.initialize) {
  503. initResult = binding.initialize(el,
  504. sizeObj ? sizeObj.getWidth() : el.offsetWidth,
  505. sizeObj ? sizeObj.getHeight() : el.offsetHeight
  506. );
  507. elementData(el, "init_result", initResult);
  508. }
  509. if (binding.resize) {
  510. var lastSize = {
  511. w: sizeObj ? sizeObj.getWidth() : el.offsetWidth,
  512. h: sizeObj ? sizeObj.getHeight() : el.offsetHeight
  513. };
  514. var resizeHandler = function(e) {
  515. var size = {
  516. w: sizeObj ? sizeObj.getWidth() : el.offsetWidth,
  517. h: sizeObj ? sizeObj.getHeight() : el.offsetHeight
  518. };
  519. if (size.w === 0 && size.h === 0)
  520. return;
  521. if (size.w === lastSize.w && size.h === lastSize.h)
  522. return;
  523. lastSize = size;
  524. binding.resize(el, size.w, size.h, initResult);
  525. };
  526. on(window, "resize", resizeHandler);
  527. // This is needed for cases where we're running in a Shiny
  528. // app, but the widget itself is not a Shiny output, but
  529. // rather a simple static widget. One example of this is
  530. // an rmarkdown document that has runtime:shiny and widget
  531. // that isn't in a render function. Shiny only knows to
  532. // call resize handlers for Shiny outputs, not for static
  533. // widgets, so we do it ourselves.
  534. if (window.jQuery) {
  535. window.jQuery(document).on(
  536. "shown.htmlwidgets shown.bs.tab.htmlwidgets shown.bs.collapse.htmlwidgets",
  537. resizeHandler
  538. );
  539. window.jQuery(document).on(
  540. "hidden.htmlwidgets hidden.bs.tab.htmlwidgets hidden.bs.collapse.htmlwidgets",
  541. resizeHandler
  542. );
  543. }
  544. // This is needed for the specific case of ioslides, which
  545. // flips slides between display:none and display:block.
  546. // Ideally we would not have to have ioslide-specific code
  547. // here, but rather have ioslides raise a generic event,
  548. // but the rmarkdown package just went to CRAN so the
  549. // window to getting that fixed may be long.
  550. if (window.addEventListener) {
  551. // It's OK to limit this to window.addEventListener
  552. // browsers because ioslides itself only supports
  553. // such browsers.
  554. on(document, "slideenter", resizeHandler);
  555. on(document, "slideleave", resizeHandler);
  556. }
  557. }
  558. var scriptData = document.querySelector("script[data-for='" + el.id + "'][type='application/json']");
  559. if (scriptData) {
  560. var data = JSON.parse(scriptData.textContent || scriptData.text);
  561. // Resolve strings marked as javascript literals to objects
  562. if (!(data.evals instanceof Array)) data.evals = [data.evals];
  563. for (var k = 0; data.evals && k < data.evals.length; k++) {
  564. window.HTMLWidgets.evaluateStringMember(data.x, data.evals[k]);
  565. }
  566. binding.renderValue(el, data.x, initResult);
  567. evalAndRun(data.jsHooks.render, initResult, [el, data.x]);
  568. }
  569. });
  570. });
  571. invokePostRenderHandlers();
  572. }
  573. // Wait until after the document has loaded to render the widgets.
  574. if (document.addEventListener) {
  575. document.addEventListener("DOMContentLoaded", function() {
  576. document.removeEventListener("DOMContentLoaded", arguments.callee, false);
  577. window.HTMLWidgets.staticRender();
  578. }, false);
  579. } else if (document.attachEvent) {
  580. document.attachEvent("onreadystatechange", function() {
  581. if (document.readyState === "complete") {
  582. document.detachEvent("onreadystatechange", arguments.callee);
  583. window.HTMLWidgets.staticRender();
  584. }
  585. });
  586. }
  587. window.HTMLWidgets.getAttachmentUrl = function(depname, key) {
  588. // If no key, default to the first item
  589. if (typeof(key) === "undefined")
  590. key = 1;
  591. var link = document.getElementById(depname + "-" + key + "-attachment");
  592. if (!link) {
  593. throw new Error("Attachment " + depname + "/" + key + " not found in document");
  594. }
  595. return link.getAttribute("href");
  596. };
  597. window.HTMLWidgets.dataframeToD3 = function(df) {
  598. var names = [];
  599. var length;
  600. for (var name in df) {
  601. if (df.hasOwnProperty(name))
  602. names.push(name);
  603. if (typeof(df[name]) !== "object" || typeof(df[name].length) === "undefined") {
  604. throw new Error("All fields must be arrays");
  605. } else if (typeof(length) !== "undefined" && length !== df[name].length) {
  606. throw new Error("All fields must be arrays of the same length");
  607. }
  608. length = df[name].length;
  609. }
  610. var results = [];
  611. var item;
  612. for (var row = 0; row < length; row++) {
  613. item = {};
  614. for (var col = 0; col < names.length; col++) {
  615. item[names[col]] = df[names[col]][row];
  616. }
  617. results.push(item);
  618. }
  619. return results;
  620. };
  621. window.HTMLWidgets.transposeArray2D = function(array) {
  622. if (array.length === 0) return array;
  623. var newArray = array[0].map(function(col, i) {
  624. return array.map(function(row) {
  625. return row[i]
  626. })
  627. });
  628. return newArray;
  629. };
  630. // Split value at splitChar, but allow splitChar to be escaped
  631. // using escapeChar. Any other characters escaped by escapeChar
  632. // will be included as usual (including escapeChar itself).
  633. function splitWithEscape(value, splitChar, escapeChar) {
  634. var results = [];
  635. var escapeMode = false;
  636. var currentResult = "";
  637. for (var pos = 0; pos < value.length; pos++) {
  638. if (!escapeMode) {
  639. if (value[pos] === splitChar) {
  640. results.push(currentResult);
  641. currentResult = "";
  642. } else if (value[pos] === escapeChar) {
  643. escapeMode = true;
  644. } else {
  645. currentResult += value[pos];
  646. }
  647. } else {
  648. currentResult += value[pos];
  649. escapeMode = false;
  650. }
  651. }
  652. if (currentResult !== "") {
  653. results.push(currentResult);
  654. }
  655. return results;
  656. }
  657. // Function authored by Yihui/JJ Allaire
  658. window.HTMLWidgets.evaluateStringMember = function(o, member) {
  659. var parts = splitWithEscape(member, '.', '\\');
  660. for (var i = 0, l = parts.length; i < l; i++) {
  661. var part = parts[i];
  662. // part may be a character or 'numeric' member name
  663. if (o !== null && typeof o === "object" && part in o) {
  664. if (i == (l - 1)) { // if we are at the end of the line then evalulate
  665. if (typeof o[part] === "string")
  666. o[part] = eval("(" + o[part] + ")");
  667. } else { // otherwise continue to next embedded object
  668. o = o[part];
  669. }
  670. }
  671. }
  672. };
  673. // Retrieve the HTMLWidget instance (i.e. the return value of an
  674. // HTMLWidget binding's initialize() or factory() function)
  675. // associated with an element, or null if none.
  676. window.HTMLWidgets.getInstance = function(el) {
  677. return elementData(el, "init_result");
  678. };
  679. // Finds the first element in the scope that matches the selector,
  680. // and returns the HTMLWidget instance (i.e. the return value of
  681. // an HTMLWidget binding's initialize() or factory() function)
  682. // associated with that element, if any. If no element matches the
  683. // selector, or the first matching element has no HTMLWidget
  684. // instance associated with it, then null is returned.
  685. //
  686. // The scope argument is optional, and defaults to window.document.
  687. window.HTMLWidgets.find = function(scope, selector) {
  688. if (arguments.length == 1) {
  689. selector = scope;
  690. scope = document;
  691. }
  692. var el = scope.querySelector(selector);
  693. if (el === null) {
  694. return null;
  695. } else {
  696. return window.HTMLWidgets.getInstance(el);
  697. }
  698. };
  699. // Finds all elements in the scope that match the selector, and
  700. // returns the HTMLWidget instances (i.e. the return values of
  701. // an HTMLWidget binding's initialize() or factory() function)
  702. // associated with the elements, in an array. If elements that
  703. // match the selector don't have an associated HTMLWidget
  704. // instance, the returned array will contain nulls.
  705. //
  706. // The scope argument is optional, and defaults to window.document.
  707. window.HTMLWidgets.findAll = function(scope, selector) {
  708. if (arguments.length == 1) {
  709. selector = scope;
  710. scope = document;
  711. }
  712. var nodes = scope.querySelectorAll(selector);
  713. var results = [];
  714. for (var i = 0; i < nodes.length; i++) {
  715. results.push(window.HTMLWidgets.getInstance(nodes[i]));
  716. }
  717. return results;
  718. };
  719. var postRenderHandlers = [];
  720. function invokePostRenderHandlers() {
  721. while (postRenderHandlers.length) {
  722. var handler = postRenderHandlers.shift();
  723. if (handler) {
  724. handler();
  725. }
  726. }
  727. }
  728. // Register the given callback function to be invoked after the
  729. // next time static widgets are rendered.
  730. window.HTMLWidgets.addPostRenderHandler = function(callback) {
  731. postRenderHandlers.push(callback);
  732. };
  733. // Takes a new-style instance-bound definition, and returns an
  734. // old-style class-bound definition. This saves us from having
  735. // to rewrite all the logic in this file to accomodate both
  736. // types of definitions.
  737. function createLegacyDefinitionAdapter(defn) {
  738. var result = {
  739. name: defn.name,
  740. type: defn.type,
  741. initialize: function(el, width, height) {
  742. return defn.factory(el, width, height);
  743. },
  744. renderValue: function(el, x, instance) {
  745. return instance.renderValue(x);
  746. },
  747. resize: function(el, width, height, instance) {
  748. return instance.resize(width, height);
  749. }
  750. };
  751. if (defn.find)
  752. result.find = defn.find;
  753. if (defn.renderError)
  754. result.renderError = defn.renderError;
  755. if (defn.clearError)
  756. result.clearError = defn.clearError;
  757. return result;
  758. }
  759. })();