function initShiny() { var shinyapp = exports.shinyapp = new ShinyApp(); function bindOutputs(scope) { if (scope === undefined) scope = document; scope = $(scope); var bindings = outputBindings.getBindings(); for (var i = 0; i < bindings.length; i++) { var binding = bindings[i].binding; var matches = binding.find(scope) || []; for (var j = 0; j < matches.length; j++) { var el = matches[j]; var id = binding.getId(el); // Check if ID is falsy if (!id) continue; var $el = $(el); if ($el.hasClass('shiny-bound-output')) { // Already bound; can happen with nested uiOutput (bindAll // gets called on two ancestors) continue; } var bindingAdapter = new OutputBindingAdapter(el, binding); shinyapp.bindOutput(id, bindingAdapter); $el.data('shiny-output-binding', bindingAdapter); $el.addClass('shiny-bound-output'); $el.trigger({ type: 'shiny:bound', binding: binding, bindingType: 'output' }); } } // Send later in case DOM layout isn't final yet. setTimeout(sendImageSize, 0); setTimeout(sendOutputHiddenState, 0); } function unbindOutputs(scope) { if (scope === undefined) scope = document; var outputs = $(scope).find('.shiny-bound-output'); for (var i = 0; i < outputs.length; i++) { var $el = $(outputs[i]); var bindingAdapter = $el.data('shiny-output-binding'); if (!bindingAdapter) continue; var id = bindingAdapter.binding.getId(outputs[i]); shinyapp.unbindOutput(id, bindingAdapter); $el.removeClass('shiny-bound-output'); $el.removeData('shiny-output-binding'); $el.trigger({ type: 'shiny:unbound', binding: bindingAdapter.binding, bindingType: 'output' }); } setTimeout(sendOutputHiddenState, 0); } var inputBatchSender = new InputBatchSender(shinyapp); var inputsNoResend = new InputNoResendDecorator(inputBatchSender); var inputsEvent = new InputEventDecorator(inputsNoResend); var inputsRate = new InputRateDecorator(inputsEvent); var inputsDefer = new InputDeferDecorator(inputsEvent); // By default, use rate decorator var inputs = inputsRate; $('input[type="submit"], button[type="submit"]').each(function() { // If there is a submit button on the page, use defer decorator inputs = inputsDefer; $(this).click(function(event) { event.preventDefault(); inputsDefer.submit(); }); }); exports.onInputChange = function(name, value) { inputs.setInput(name, value); }; var boundInputs = {}; function valueChangeCallback(binding, el, allowDeferred) { var id = binding.getId(el); if (id) { var value = binding.getValue(el); var type = binding.getType(el); if (type) id = id + ":" + type; inputs.setInput(id, value, !allowDeferred); } } function bindInputs(scope) { if (scope === undefined) scope = document; var bindings = inputBindings.getBindings(); var currentValues = {}; for (var i = 0; i < bindings.length; i++) { var binding = bindings[i].binding; var matches = binding.find(scope) || []; for (var j = 0; j < matches.length; j++) { var el = matches[j]; var id = binding.getId(el); // Check if ID is falsy, or if already bound if (!id || boundInputs[id]) continue; var type = binding.getType(el); var effectiveId = type ? id + ":" + type : id; currentValues[effectiveId] = binding.getValue(el); /*jshint loopfunc:true*/ var thisCallback = (function() { var thisBinding = binding; var thisEl = el; return function(allowDeferred) { valueChangeCallback(thisBinding, thisEl, allowDeferred); }; })(); binding.subscribe(el, thisCallback); $(el).data('shiny-input-binding', binding); $(el).addClass('shiny-bound-input'); var ratePolicy = binding.getRatePolicy(el); if (ratePolicy !== null) { inputsRate.setRatePolicy( effectiveId, ratePolicy.policy, ratePolicy.delay); } boundInputs[id] = { binding: binding, node: el }; $(el).trigger({ type: 'shiny:bound', binding: binding, bindingType: 'input' }); if (shinyapp.isConnected()) { valueChangeCallback(binding, el, false); } } } return currentValues; } function unbindInputs(scope) { if (scope === undefined) scope = document; var inputs = $(scope).find('.shiny-bound-input'); for (var i = 0; i < inputs.length; i++) { var el = inputs[i]; var binding = $(el).data('shiny-input-binding'); if (!binding) continue; var id = binding.getId(el); $(el).removeClass('shiny-bound-input'); delete boundInputs[id]; binding.unsubscribe(el); $(el).trigger({ type: 'shiny:unbound', binding: binding, bindingType: 'input' }); } } function _bindAll(scope) { bindOutputs(scope); return bindInputs(scope); } function unbindAll(scope) { unbindInputs(scope); unbindOutputs(scope); } exports.bindAll = function(scope) { // _bindAll alone returns initial values, it doesn't send them to the // server. export.bindAll needs to send the values to the server, so we // wrap _bindAll in a closure that does that. var currentValues = _bindAll(scope); $.each(currentValues, function(name, value) { inputs.setInput(name, value); }); }; exports.unbindAll = unbindAll; // Calls .initialize() for all of the input objects in all input bindings, // in the given scope. function initializeInputs(scope) { if (scope === undefined) scope = document; var bindings = inputBindings.getBindings(); // Iterate over all bindings for (var i = 0; i < bindings.length; i++) { var binding = bindings[i].binding; var inputObjects = binding.find(scope) || []; // Iterate over all input objects for this binding for (var j = 0; j < inputObjects.length; j++) { binding.initialize(inputObjects[j]); } } } exports.initializeInputs = initializeInputs; // Initialize all input objects in the document, before binding initializeInputs(document); var initialValues = _bindAll(document); // The server needs to know the size of each image and plot output element, // in case it is auto-sizing $('.shiny-image-output, .shiny-plot-output').each(function() { if (this.offsetWidth !== 0 || this.offsetHeight !== 0) { initialValues['.clientdata_output_' + this.id + '_width'] = this.offsetWidth; initialValues['.clientdata_output_' + this.id + '_height'] = this.offsetHeight; } }); function doSendImageSize() { $('.shiny-image-output, .shiny-plot-output').each(function() { if (this.offsetWidth !== 0 || this.offsetHeight !== 0) { inputs.setInput('.clientdata_output_' + this.id + '_width', this.offsetWidth); inputs.setInput('.clientdata_output_' + this.id + '_height', this.offsetHeight); } }); $('.shiny-bound-output').each(function() { var $this = $(this), binding = $this.data('shiny-output-binding'); $this.trigger({ type: 'shiny:visualchange', visible: !isHidden(this), binding: binding }); binding.onResize(); }); } var sendImageSizeDebouncer = new Debouncer(null, doSendImageSize, 0); function sendImageSize() { sendImageSizeDebouncer.normalCall(); } // Make sure sendImageSize actually gets called before the inputBatchSender // sends data to the server. inputBatchSender.lastChanceCallback.push(function() { if (sendImageSizeDebouncer.isPending()) sendImageSizeDebouncer.immediateCall(); }); // Return true if the object or one of its ancestors in the DOM tree has // style='display:none'; otherwise return false. function isHidden(obj) { // null means we've hit the top of the tree. If width or height is // non-zero, then we know that no ancestor has display:none. if (obj === null || obj.offsetWidth !== 0 || obj.offsetHeight !== 0) { return false; } else if (getStyle(obj, 'display') === 'none') { return true; } else { return(isHidden(obj.parentNode)); } } var lastKnownVisibleOutputs = {}; // Set initial state of outputs to hidden, if needed $('.shiny-bound-output').each(function() { if (isHidden(this)) { initialValues['.clientdata_output_' + this.id + '_hidden'] = true; } else { lastKnownVisibleOutputs[this.id] = true; initialValues['.clientdata_output_' + this.id + '_hidden'] = false; } }); // Send update when hidden state changes function doSendOutputHiddenState() { var visibleOutputs = {}; $('.shiny-bound-output').each(function() { delete lastKnownVisibleOutputs[this.id]; // Assume that the object is hidden when width and height are 0 var hidden = isHidden(this), evt = { type: 'shiny:visualchange', visible: !hidden }; if (hidden) { inputs.setInput('.clientdata_output_' + this.id + '_hidden', true); } else { visibleOutputs[this.id] = true; inputs.setInput('.clientdata_output_' + this.id + '_hidden', false); } var $this = $(this); evt.binding = $this.data('shiny-output-binding'); $this.trigger(evt); }); // Anything left in lastKnownVisibleOutputs is orphaned for (var name in lastKnownVisibleOutputs) { if (lastKnownVisibleOutputs.hasOwnProperty(name)) inputs.setInput('.clientdata_output_' + name + '_hidden', true); } // Update the visible outputs for next time lastKnownVisibleOutputs = visibleOutputs; } // sendOutputHiddenState gets called each time DOM elements are shown or // hidden. This can be in the hundreds or thousands of times at startup. // We'll debounce it, so that we do the actual work once per tick. var sendOutputHiddenStateDebouncer = new Debouncer(null, doSendOutputHiddenState, 0); function sendOutputHiddenState() { sendOutputHiddenStateDebouncer.normalCall(); } // We need to make sure doSendOutputHiddenState actually gets called before // the inputBatchSender sends data to the server. The lastChanceCallback // here does that - if the debouncer has a pending call, flush it. inputBatchSender.lastChanceCallback.push(function() { if (sendOutputHiddenStateDebouncer.isPending()) sendOutputHiddenStateDebouncer.immediateCall(); }); // Given a namespace and a handler function, return a function that invokes // the handler only when e's namespace matches. For example, if the // namespace is "bs", it would match when e.namespace is "bs" or "bs.tab". // If the namespace is "bs.tab", it would match for "bs.tab", but not "bs". function filterEventsByNamespace(namespace, handler) { namespace = namespace.split("."); return function(e) { var eventNamespace = e.namespace.split("."); // If any of the namespace strings aren't present in this event, quit. for (var i=0; i