Changeset View
Changeset View
Standalone View
Standalone View
externals/javelin/src/ext/reactor/dom/RDOM.js
- This file was added.
| /** | |||||
| * Javelin Reactive functions to work with the DOM. | |||||
| * @provides javelin-reactor-dom | |||||
| * @requires javelin-dom | |||||
| * javelin-dynval | |||||
| * javelin-reactornode | |||||
| * javelin-install | |||||
| * javelin-util | |||||
| * @javelin | |||||
| */ | |||||
| JX.install('RDOM', { | |||||
| statics : { | |||||
| _time : null, | |||||
| /** | |||||
| * DynVal of the current time in milliseconds. | |||||
| */ | |||||
| time : function() { | |||||
| if (JX.RDOM._time === null) { | |||||
| var time = new JX.ReactorNode([], JX.id); | |||||
| window.setInterval(function() { | |||||
| time.forceSendValue(JX.now()); | |||||
| }, 100); | |||||
| JX.RDOM._time = new JX.DynVal(time, JX.now()); | |||||
| } | |||||
| return JX.RDOM._time; | |||||
| }, | |||||
| /** | |||||
| * Given a DynVal[String], return a DOM text node whose value tracks it. | |||||
| */ | |||||
| $DT : function(dyn_string) { | |||||
| var node = document.createTextNode(dyn_string.getValueNow()); | |||||
| dyn_string.transform(function(s) { node.data = s; }); | |||||
| return node; | |||||
| }, | |||||
| _recvEventPulses : function(node, event) { | |||||
| var reactor_node = new JX.ReactorNode([], JX.id); | |||||
| var no_path = null; | |||||
| JX.DOM.listen( | |||||
| node, | |||||
| event, | |||||
| no_path, | |||||
| JX.bind(reactor_node, reactor_node.forceSendValue) | |||||
| ); | |||||
| reactor_node.setGraphID(JX.DOM.uniqID(node)); | |||||
| return reactor_node; | |||||
| }, | |||||
| _recvChangePulses : function(node) { | |||||
| return JX.RDOM._recvEventPulses(node, 'change').transform(function() { | |||||
| return node.value; | |||||
| }); | |||||
| }, | |||||
| /** | |||||
| * Sets up a bidirectional DynVal for a node. | |||||
| * @param node :: DOM Node | |||||
| * @param inPulsesFn :: DOM Node -> ReactorNode | |||||
| * @param inDynValFn :: DOM Node -> ReactorNode -> DynVal | |||||
| * @param outFn :: ReactorNode -> DOM Node | |||||
| */ | |||||
| _bidi : function(node, inPulsesFn, inDynValFn, outFn) { | |||||
| var inPulses = inPulsesFn(node); | |||||
| var inDynVal = inDynValFn(node, inPulses); | |||||
| outFn(inDynVal.getChanges(), node); | |||||
| inDynVal.getChanges().listen(inPulses); | |||||
| return inDynVal; | |||||
| }, | |||||
| /** | |||||
| * ReactorNode[String] of the incoming values of a radio group. | |||||
| * @param Array of DOM elements, all the radio buttons in a group. | |||||
| */ | |||||
| _recvRadioPulses : function(buttons) { | |||||
| var ins = []; | |||||
| for (var ii = 0; ii < buttons.length; ii++) { | |||||
| ins.push(JX.RDOM._recvChangePulses(buttons[ii])); | |||||
| } | |||||
| return new JX.ReactorNode(ins, JX.id); | |||||
| }, | |||||
| /** | |||||
| * DynVal[String] of the incoming values of a radio group. | |||||
| * pulses is a ReactorNode[String] of the incoming values of the group | |||||
| */ | |||||
| _recvRadio : function(buttons, pulses) { | |||||
| var init = ''; | |||||
| for (var ii = 0; ii < buttons.length; ii++) { | |||||
| if (buttons[ii].checked) { | |||||
| init = buttons[ii].value; | |||||
| break; | |||||
| } | |||||
| } | |||||
| return new JX.DynVal(pulses, init); | |||||
| }, | |||||
| /** | |||||
| * Send the pulses from the ReactorNode[String] to the radio group. | |||||
| * Sending an invalid value will result in a log message in __DEV__. | |||||
| */ | |||||
| _sendRadioPulses : function(rnode, buttons) { | |||||
| return rnode.transform(function(val) { | |||||
| var found; | |||||
| if (__DEV__) { | |||||
| found = false; | |||||
| } | |||||
| for (var ii = 0; ii < buttons.length; ii++) { | |||||
| if (buttons[ii].value == val) { | |||||
| buttons[ii].checked = true; | |||||
| if (__DEV__) { | |||||
| found = true; | |||||
| } | |||||
| } | |||||
| } | |||||
| if (__DEV__) { | |||||
| if (!found) { | |||||
| throw new Error("Mismatched radio button value"); | |||||
| } | |||||
| } | |||||
| }); | |||||
| }, | |||||
| /** | |||||
| * Bidirectional DynVal[String] for a radio group. | |||||
| * Sending an invalid value will result in a log message in __DEV__. | |||||
| */ | |||||
| radio : function(input) { | |||||
| return JX.RDOM._bidi( | |||||
| input, | |||||
| JX.RDOM._recvRadioPulses, | |||||
| JX.RDOM._recvRadio, | |||||
| JX.RDOM._sendRadioPulses | |||||
| ); | |||||
| }, | |||||
| /** | |||||
| * ReactorNode[Boolean] of the values of the checkbox when it changes. | |||||
| */ | |||||
| _recvCheckboxPulses : function(checkbox) { | |||||
| return JX.RDOM._recvChangePulses(checkbox).transform(function(val) { | |||||
| return Boolean(val); | |||||
| }); | |||||
| }, | |||||
| /** | |||||
| * DynVal[Boolean] of the value of a checkbox. | |||||
| */ | |||||
| _recvCheckbox : function(checkbox, pulses) { | |||||
| return new JX.DynVal(pulses, Boolean(checkbox.checked)); | |||||
| }, | |||||
| /** | |||||
| * Send the pulses from the ReactorNode[Boolean] to the checkbox | |||||
| */ | |||||
| _sendCheckboxPulses : function(rnode, checkbox) { | |||||
| return rnode.transform(function(val) { | |||||
| if (__DEV__) { | |||||
| if (!(val === true || val === false)) { | |||||
| throw new Error("Send boolean values to checkboxes."); | |||||
| } | |||||
| } | |||||
| checkbox.checked = val; | |||||
| }); | |||||
| }, | |||||
| /** | |||||
| * Bidirectional DynVal[Boolean] for a checkbox. | |||||
| */ | |||||
| checkbox : function(input) { | |||||
| return JX.RDOM._bidi( | |||||
| input, | |||||
| JX.RDOM._recvCheckboxPulses, | |||||
| JX.RDOM._recvCheckbox, | |||||
| JX.RDOM._sendCheckboxPulses | |||||
| ); | |||||
| }, | |||||
| /** | |||||
| * ReactorNode[String] of the changing values of a text input. | |||||
| */ | |||||
| _recvInputPulses : function(input) { | |||||
| // This misses advanced changes like paste events. | |||||
| var live_changes = [ | |||||
| JX.RDOM._recvChangePulses(input), | |||||
| JX.RDOM._recvEventPulses(input, 'keyup'), | |||||
| JX.RDOM._recvEventPulses(input, 'keypress'), | |||||
| JX.RDOM._recvEventPulses(input, 'keydown') | |||||
| ]; | |||||
| return new JX.ReactorNode(live_changes, function() { | |||||
| return input.value; | |||||
| }); | |||||
| }, | |||||
| /** | |||||
| * DynVal[String] of the value of a text input. | |||||
| */ | |||||
| _recvInput : function(input, pulses) { | |||||
| return new JX.DynVal(pulses, input.value); | |||||
| }, | |||||
| /** | |||||
| * Send the pulses from the ReactorNode[String] to the input | |||||
| */ | |||||
| _sendInputPulses : function(rnode, input) { | |||||
| var result = rnode.transform(function(val) { | |||||
| input.value = val; | |||||
| }); | |||||
| result.setGraphID(JX.DOM.uniqID(input)); | |||||
| return result; | |||||
| }, | |||||
| /** | |||||
| * Bidirectional DynVal[String] for a text input. | |||||
| */ | |||||
| input : function(input) { | |||||
| return JX.RDOM._bidi( | |||||
| input, | |||||
| JX.RDOM._recvInputPulses, | |||||
| JX.RDOM._recvInput, | |||||
| JX.RDOM._sendInputPulses | |||||
| ); | |||||
| }, | |||||
| /** | |||||
| * ReactorNode[String] of the incoming changes in value of a select element. | |||||
| */ | |||||
| _recvSelectPulses : function(select) { | |||||
| return JX.RDOM._recvChangePulses(select); | |||||
| }, | |||||
| /** | |||||
| * DynVal[String] of the value of a select element. | |||||
| */ | |||||
| _recvSelect : function(select, pulses) { | |||||
| return new JX.DynVal(pulses, select.value); | |||||
| }, | |||||
| /** | |||||
| * Send the pulses from the ReactorNode[String] to the select. | |||||
| * Sending an invalid value will result in a log message in __DEV__. | |||||
| */ | |||||
| _sendSelectPulses : function(rnode, select) { | |||||
| return rnode.transform(function(val) { | |||||
| select.value = val; | |||||
| if (__DEV__) { | |||||
| if (select.value !== val) { | |||||
| throw new Error("Mismatched select value"); | |||||
| } | |||||
| } | |||||
| }); | |||||
| }, | |||||
| /** | |||||
| * Bidirectional DynVal[String] for the value of a select. | |||||
| */ | |||||
| select : function(select) { | |||||
| return JX.RDOM._bidi( | |||||
| select, | |||||
| JX.RDOM._recvSelectPulses, | |||||
| JX.RDOM._recvSelect, | |||||
| JX.RDOM._sendSelectPulses | |||||
| ); | |||||
| }, | |||||
| /** | |||||
| * ReactorNode[undefined] that fires when a button is clicked. | |||||
| */ | |||||
| clickPulses : function(button) { | |||||
| return JX.RDOM._recvEventPulses(button, 'click').transform(function() { | |||||
| return null; | |||||
| }); | |||||
| }, | |||||
| /** | |||||
| * ReactorNode[Boolean] of whether the mouse is over a target. | |||||
| */ | |||||
| _recvIsMouseOverPulses : function(target) { | |||||
| var mouseovers = JX.RDOM._recvEventPulses(target, 'mouseover').transform( | |||||
| function() { | |||||
| return true; | |||||
| }); | |||||
| var mouseouts = JX.RDOM._recvEventPulses(target, 'mouseout').transform( | |||||
| function() { | |||||
| return false; | |||||
| }); | |||||
| return new JX.ReactorNode([mouseovers, mouseouts], JX.id); | |||||
| }, | |||||
| /** | |||||
| * DynVal[Boolean] of whether the mouse is over a target. | |||||
| */ | |||||
| isMouseOver : function(target) { | |||||
| // Not worth it to initialize this properly. | |||||
| return new JX.DynVal(JX.RDOM._recvIsMouseOverPulses(target), false); | |||||
| }, | |||||
| /** | |||||
| * ReactorNode[Boolean] of whether an element has the focus. | |||||
| */ | |||||
| _recvHasFocusPulses : function(target) { | |||||
| var focuses = JX.RDOM._recvEventPulses(target, 'focus').transform( | |||||
| function() { | |||||
| return true; | |||||
| }); | |||||
| var blurs = JX.RDOM._recvEventPulses(target, 'blur').transform( | |||||
| function() { | |||||
| return false; | |||||
| }); | |||||
| return new JX.ReactorNode([focuses, blurs], JX.id); | |||||
| }, | |||||
| /** | |||||
| * DynVal[Boolean] of whether an element has the focus. | |||||
| */ | |||||
| _recvHasFocus : function(target) { | |||||
| var is_focused_now = (target === document.activeElement); | |||||
| return new JX.DynVal(JX.RDOM._recvHasFocusPulses(target), is_focused_now); | |||||
| }, | |||||
| _sendHasFocusPulses : function(rnode, target) { | |||||
| rnode.transform(function(should_focus) { | |||||
| if (should_focus) { | |||||
| target.focus(); | |||||
| } else { | |||||
| target.blur(); | |||||
| } | |||||
| return should_focus; | |||||
| }); | |||||
| }, | |||||
| /** | |||||
| * Bidirectional DynVal[Boolean] of whether an element has the focus. | |||||
| */ | |||||
| hasFocus : function(target) { | |||||
| return JX.RDOM._bidi( | |||||
| target, | |||||
| JX.RDOM._recvHasFocusPulses, | |||||
| JX.RDOM._recvHasFocus, | |||||
| JX.RDOM._sendHasFocusPulses | |||||
| ); | |||||
| }, | |||||
| /** | |||||
| * Send a CSS class from a DynVal to a node | |||||
| */ | |||||
| sendClass : function(dynval, node, className) { | |||||
| return dynval.transform(function(add) { | |||||
| JX.DOM.alterClass(node, className, add); | |||||
| }); | |||||
| }, | |||||
| /** | |||||
| * Dynamically attach a set of DynVals to a DOM node's properties as | |||||
| * specified by props. | |||||
| * props: {left: someDynVal, style: {backgroundColor: someOtherDynVal}} | |||||
| */ | |||||
| sendProps : function(node, props) { | |||||
| var dynvals = []; | |||||
| var keys = []; | |||||
| var style_keys = []; | |||||
| for (var key in props) { | |||||
| keys.push(key); | |||||
| if (key === 'style') { | |||||
| for (var style_key in props[key]) { | |||||
| style_keys.push(style_key); | |||||
| dynvals.push(props[key][style_key]); | |||||
| node.style[style_key] = props[key][style_key].getValueNow(); | |||||
| } | |||||
| } else { | |||||
| dynvals.push(props[key]); | |||||
| node[key] = props[key].getValueNow(); | |||||
| } | |||||
| } | |||||
| return JX.Reactor.lift(JX.bind(null, function(keys, style_keys, node) { | |||||
| var args = JX.$A(arguments).slice(3); | |||||
| for (var ii = 0; ii < args.length; ii++) { | |||||
| if (keys[ii] === 'style') { | |||||
| for (var jj = 0; jj < style_keys.length; jj++) { | |||||
| node.style[style_keys[jj]] = args[ii]; | |||||
| ii++; | |||||
| } | |||||
| ii--; | |||||
| } else { | |||||
| node[keys[ii]] = args[ii]; | |||||
| } | |||||
| } | |||||
| }, keys, style_keys, node), dynvals); | |||||
| } | |||||
| } | |||||
| }); | |||||