[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 /* 2 YUI 3.17.2 (build 9c3c78e) 3 Copyright 2014 Yahoo! Inc. All rights reserved. 4 Licensed under the BSD License. 5 http://yuilibrary.com/license/ 6 */ 7 8 YUI.add('event-valuechange', function (Y, NAME) { 9 10 /** 11 Adds a synthetic `valuechange` event that fires when the `value` property of an 12 `<input>`, `<textarea>`, `<select>`, or `[contenteditable="true"]` node changes 13 as a result of a keystroke, mouse operation, or input method editor (IME) 14 input event. 15 16 Usage: 17 18 YUI().use('event-valuechange', function (Y) { 19 Y.one('#my-input').on('valuechange', function (e) { 20 Y.log('previous value: ' + e.prevVal); 21 Y.log('new value: ' + e.newVal); 22 }); 23 }); 24 25 @module event-valuechange 26 **/ 27 28 /** 29 Provides the implementation for the synthetic `valuechange` event. This class 30 isn't meant to be used directly, but is public to make monkeypatching possible. 31 32 Usage: 33 34 YUI().use('event-valuechange', function (Y) { 35 Y.one('#my-input').on('valuechange', function (e) { 36 Y.log('previous value: ' + e.prevVal); 37 Y.log('new value: ' + e.newVal); 38 }); 39 }); 40 41 @class ValueChange 42 @static 43 */ 44 45 var DATA_KEY = '_valuechange', 46 VALUE = 'value', 47 NODE_NAME = 'nodeName', 48 49 config, // defined at the end of this file 50 51 // Just a simple namespace to make methods overridable. 52 VC = { 53 // -- Static Constants ----------------------------------------------------- 54 55 /** 56 Interval (in milliseconds) at which to poll for changes to the value of an 57 element with one or more `valuechange` subscribers when the user is likely 58 to be interacting with it. 59 60 @property POLL_INTERVAL 61 @type Number 62 @default 50 63 @static 64 **/ 65 POLL_INTERVAL: 50, 66 67 /** 68 Timeout (in milliseconds) after which to stop polling when there hasn't been 69 any new activity (keypresses, mouse clicks, etc.) on an element. 70 71 @property TIMEOUT 72 @type Number 73 @default 10000 74 @static 75 **/ 76 TIMEOUT: 10000, 77 78 // -- Protected Static Methods --------------------------------------------- 79 80 /** 81 Called at an interval to poll for changes to the value of the specified 82 node. 83 84 @method _poll 85 @param {Node} node Node to poll. 86 87 @param {Object} options Options object. 88 @param {EventFacade} [options.e] Event facade of the event that 89 initiated the polling. 90 91 @protected 92 @static 93 **/ 94 _poll: function (node, options) { 95 var domNode = node._node, // performance cheat; getValue() is a big hit when polling 96 event = options.e, 97 vcData = node._data && node._data[DATA_KEY], // another perf cheat 98 stopped = 0, 99 facade, prevVal, newVal, nodeName, selectedOption, stopElement; 100 101 if (!(domNode && vcData)) { 102 Y.log('_poll: node #' + node.get('id') + ' disappeared; stopping polling and removing all notifiers.', 'warn', 'event-valuechange'); 103 VC._stopPolling(node); 104 return; 105 } 106 107 prevVal = vcData.prevVal; 108 nodeName = vcData.nodeName; 109 110 if (vcData.isEditable) { 111 // Use innerHTML for performance 112 newVal = domNode.innerHTML; 113 } else if (nodeName === 'input' || nodeName === 'textarea') { 114 // Use value property for performance 115 newVal = domNode.value; 116 } else if (nodeName === 'select') { 117 // Back-compatibility with IE6 <select> element values. 118 // Huge performance cheat to get past node.get('value'). 119 selectedOption = domNode.options[domNode.selectedIndex]; 120 newVal = selectedOption.value || selectedOption.text; 121 } 122 123 if (newVal !== prevVal) { 124 vcData.prevVal = newVal; 125 126 facade = { 127 _event : event, 128 currentTarget: (event && event.currentTarget) || node, 129 newVal : newVal, 130 prevVal : prevVal, 131 target : (event && event.target) || node 132 }; 133 134 Y.Object.some(vcData.notifiers, function (notifier) { 135 var evt = notifier.handle.evt, 136 newStopped; 137 138 // support e.stopPropagation() 139 if (stopped !== 1) { 140 notifier.fire(facade); 141 } else if (evt.el === stopElement) { 142 notifier.fire(facade); 143 } 144 145 newStopped = evt && evt._facade ? evt._facade.stopped : 0; 146 147 // need to consider the condition in which there are two 148 // listeners on the same element: 149 // listener 1 calls e.stopPropagation() 150 // listener 2 calls e.stopImmediatePropagation() 151 if (newStopped > stopped) { 152 stopped = newStopped; 153 154 if (stopped === 1) { 155 stopElement = evt.el; 156 } 157 } 158 159 // support e.stopImmediatePropagation() 160 if (stopped === 2) { 161 return true; 162 } 163 }); 164 165 VC._refreshTimeout(node); 166 } 167 }, 168 169 /** 170 Restarts the inactivity timeout for the specified node. 171 172 @method _refreshTimeout 173 @param {Node} node Node to refresh. 174 @param {SyntheticEvent.Notifier} notifier 175 @protected 176 @static 177 **/ 178 _refreshTimeout: function (node, notifier) { 179 // The node may have been destroyed, so check that it still exists 180 // before trying to get its data. Otherwise an error will occur. 181 if (!node._node) { 182 Y.log('_stopPolling: node disappeared', 'warn', 'event-valuechange'); 183 return; 184 } 185 186 var vcData = node.getData(DATA_KEY); 187 188 VC._stopTimeout(node); // avoid dupes 189 190 // If we don't see any changes within the timeout period (10 seconds by 191 // default), stop polling. 192 vcData.timeout = setTimeout(function () { 193 Y.log('timeout: #' + node.get('id'), 'info', 'event-valuechange'); 194 VC._stopPolling(node, notifier); 195 }, VC.TIMEOUT); 196 197 Y.log('_refreshTimeout: #' + node.get('id'), 'info', 'event-valuechange'); 198 }, 199 200 /** 201 Begins polling for changes to the `value` property of the specified node. If 202 polling is already underway for the specified node, it will not be restarted 203 unless the `force` option is `true` 204 205 @method _startPolling 206 @param {Node} node Node to watch. 207 @param {SyntheticEvent.Notifier} notifier 208 209 @param {Object} options Options object. 210 @param {EventFacade} [options.e] Event facade of the event that 211 initiated the polling. 212 @param {Boolean} [options.force=false] If `true`, polling will be 213 restarted even if we're already polling this node. 214 215 @protected 216 @static 217 **/ 218 _startPolling: function (node, notifier, options) { 219 var vcData, isEditable; 220 221 if (!node.test('input,textarea,select') && !(isEditable = VC._isEditable(node))) { 222 Y.log('_startPolling: aborting poll on #' + node.get('id') + ' -- not a detectable node', 'warn', 'event-valuechange'); 223 return; 224 } 225 226 vcData = node.getData(DATA_KEY); 227 228 if (!vcData) { 229 vcData = { 230 nodeName : node.get(NODE_NAME).toLowerCase(), 231 isEditable : isEditable, 232 prevVal : isEditable ? node.getDOMNode().innerHTML : node.get(VALUE) 233 }; 234 235 node.setData(DATA_KEY, vcData); 236 } 237 238 vcData.notifiers || (vcData.notifiers = {}); 239 240 // Don't bother continuing if we're already polling this node, unless 241 // `options.force` is true. 242 if (vcData.interval) { 243 if (options.force) { 244 VC._stopPolling(node, notifier); // restart polling, but avoid dupe polls 245 } else { 246 vcData.notifiers[Y.stamp(notifier)] = notifier; 247 return; 248 } 249 } 250 251 // Poll for changes to the node's value. We can't rely on keyboard 252 // events for this, since the value may change due to a mouse-initiated 253 // paste event, an IME input event, or for some other reason that 254 // doesn't trigger a key event. 255 vcData.notifiers[Y.stamp(notifier)] = notifier; 256 257 vcData.interval = setInterval(function () { 258 VC._poll(node, options); 259 }, VC.POLL_INTERVAL); 260 261 Y.log('_startPolling: #' + node.get('id'), 'info', 'event-valuechange'); 262 263 VC._refreshTimeout(node, notifier); 264 }, 265 266 /** 267 Stops polling for changes to the specified node's `value` attribute. 268 269 @method _stopPolling 270 @param {Node} node Node to stop polling on. 271 @param {SyntheticEvent.Notifier} [notifier] Notifier to remove from the 272 node. If not specified, all notifiers will be removed. 273 @protected 274 @static 275 **/ 276 _stopPolling: function (node, notifier) { 277 // The node may have been destroyed, so check that it still exists 278 // before trying to get its data. Otherwise an error will occur. 279 if (!node._node) { 280 Y.log('_stopPolling: node disappeared', 'info', 'event-valuechange'); 281 return; 282 } 283 284 var vcData = node.getData(DATA_KEY) || {}; 285 286 clearInterval(vcData.interval); 287 delete vcData.interval; 288 289 VC._stopTimeout(node); 290 291 if (notifier) { 292 vcData.notifiers && delete vcData.notifiers[Y.stamp(notifier)]; 293 } else { 294 vcData.notifiers = {}; 295 } 296 297 Y.log('_stopPolling: #' + node.get('id'), 'info', 'event-valuechange'); 298 }, 299 300 /** 301 Clears the inactivity timeout for the specified node, if any. 302 303 @method _stopTimeout 304 @param {Node} node 305 @protected 306 @static 307 **/ 308 _stopTimeout: function (node) { 309 var vcData = node.getData(DATA_KEY) || {}; 310 311 clearTimeout(vcData.timeout); 312 delete vcData.timeout; 313 }, 314 315 /** 316 Check to see if a node has editable content or not. 317 318 TODO: Add additional checks to get it to work for child nodes 319 that inherit "contenteditable" from parent nodes. This may be 320 too computationally intensive to be placed inside of the `_poll` 321 loop, however. 322 323 @method _isEditable 324 @param {Node} node 325 @protected 326 @static 327 **/ 328 _isEditable: function (node) { 329 // Performance cheat because this is used inside `_poll` 330 var domNode = node._node; 331 return domNode.contentEditable === 'true' || 332 domNode.contentEditable === ''; 333 }, 334 335 336 337 // -- Protected Static Event Handlers -------------------------------------- 338 339 /** 340 Stops polling when a node's blur event fires. 341 342 @method _onBlur 343 @param {EventFacade} e 344 @param {SyntheticEvent.Notifier} notifier 345 @protected 346 @static 347 **/ 348 _onBlur: function (e, notifier) { 349 VC._stopPolling(e.currentTarget, notifier); 350 }, 351 352 /** 353 Resets a node's history and starts polling when a focus event occurs. 354 355 @method _onFocus 356 @param {EventFacade} e 357 @param {SyntheticEvent.Notifier} notifier 358 @protected 359 @static 360 **/ 361 _onFocus: function (e, notifier) { 362 var node = e.currentTarget, 363 vcData = node.getData(DATA_KEY); 364 365 if (!vcData) { 366 vcData = { 367 isEditable : VC._isEditable(node), 368 nodeName : node.get(NODE_NAME).toLowerCase() 369 }; 370 node.setData(DATA_KEY, vcData); 371 } 372 373 vcData.prevVal = vcData.isEditable ? node.getDOMNode().innerHTML : node.get(VALUE); 374 375 VC._startPolling(node, notifier, {e: e}); 376 }, 377 378 /** 379 Starts polling when a node receives a keyDown event. 380 381 @method _onKeyDown 382 @param {EventFacade} e 383 @param {SyntheticEvent.Notifier} notifier 384 @protected 385 @static 386 **/ 387 _onKeyDown: function (e, notifier) { 388 VC._startPolling(e.currentTarget, notifier, {e: e}); 389 }, 390 391 /** 392 Starts polling when an IME-related keyUp event occurs on a node. 393 394 @method _onKeyUp 395 @param {EventFacade} e 396 @param {SyntheticEvent.Notifier} notifier 397 @protected 398 @static 399 **/ 400 _onKeyUp: function (e, notifier) { 401 // These charCodes indicate that an IME has started. We'll restart 402 // polling and give the IME up to 10 seconds (by default) to finish. 403 if (e.charCode === 229 || e.charCode === 197) { 404 VC._startPolling(e.currentTarget, notifier, { 405 e : e, 406 force: true 407 }); 408 } 409 }, 410 411 /** 412 Starts polling when a node receives a mouseDown event. 413 414 @method _onMouseDown 415 @param {EventFacade} e 416 @param {SyntheticEvent.Notifier} notifier 417 @protected 418 @static 419 **/ 420 _onMouseDown: function (e, notifier) { 421 VC._startPolling(e.currentTarget, notifier, {e: e}); 422 }, 423 424 /** 425 Called when the `valuechange` event receives a new subscriber. 426 427 Child nodes that aren't initially available when this subscription is 428 called will still fire the `valuechange` event after their data is 429 collected when the delegated `focus` event is captured. This includes 430 elements that haven't been inserted into the DOM yet, as well as 431 elements that aren't initially `contenteditable`. 432 433 @method _onSubscribe 434 @param {Node} node 435 @param {Subscription} sub 436 @param {SyntheticEvent.Notifier} notifier 437 @param {Function|String} [filter] Filter function or selector string. Only 438 provided for delegate subscriptions. 439 @protected 440 @static 441 **/ 442 _onSubscribe: function (node, sub, notifier, filter) { 443 var _valuechange, callbacks, isEditable, inputNodes, editableNodes; 444 445 callbacks = { 446 blur : VC._onBlur, 447 focus : VC._onFocus, 448 keydown : VC._onKeyDown, 449 keyup : VC._onKeyUp, 450 mousedown: VC._onMouseDown 451 }; 452 453 // Store a utility object on the notifier to hold stuff that needs to be 454 // passed around to trigger event handlers, polling handlers, etc. 455 _valuechange = notifier._valuechange = {}; 456 457 if (filter) { 458 // If a filter is provided, then this is a delegated subscription. 459 _valuechange.delegated = true; 460 461 // Add a function to the notifier that we can use to find all 462 // nodes that pass the delegate filter. 463 _valuechange.getNodes = function () { 464 inputNodes = node.all('input,textarea,select').filter(filter); 465 editableNodes = node.all('[contenteditable="true"],[contenteditable=""]').filter(filter); 466 467 return inputNodes.concat(editableNodes); 468 }; 469 470 // Store the initial values for each descendant of the container 471 // node that passes the delegate filter. 472 _valuechange.getNodes().each(function (child) { 473 if (!child.getData(DATA_KEY)) { 474 child.setData(DATA_KEY, { 475 nodeName : child.get(NODE_NAME).toLowerCase(), 476 isEditable : VC._isEditable(child), 477 prevVal : isEditable ? child.getDOMNode().innerHTML : child.get(VALUE) 478 }); 479 } 480 }); 481 482 notifier._handles = Y.delegate(callbacks, node, filter, null, 483 notifier); 484 } else { 485 isEditable = VC._isEditable(node); 486 // This is a normal (non-delegated) event subscription. 487 if (!node.test('input,textarea,select') && !isEditable) { 488 return; 489 } 490 491 if (!node.getData(DATA_KEY)) { 492 node.setData(DATA_KEY, { 493 nodeName : node.get(NODE_NAME).toLowerCase(), 494 isEditable : isEditable, 495 prevVal : isEditable ? node.getDOMNode().innerHTML : node.get(VALUE) 496 }); 497 } 498 499 notifier._handles = node.on(callbacks, null, null, notifier); 500 } 501 }, 502 503 /** 504 Called when the `valuechange` event loses a subscriber. 505 506 @method _onUnsubscribe 507 @param {Node} node 508 @param {Subscription} subscription 509 @param {SyntheticEvent.Notifier} notifier 510 @protected 511 @static 512 **/ 513 _onUnsubscribe: function (node, subscription, notifier) { 514 var _valuechange = notifier._valuechange; 515 516 notifier._handles && notifier._handles.detach(); 517 518 if (_valuechange.delegated) { 519 _valuechange.getNodes().each(function (child) { 520 VC._stopPolling(child, notifier); 521 }); 522 } else { 523 VC._stopPolling(node, notifier); 524 } 525 } 526 }; 527 528 /** 529 Synthetic event that fires when the `value` property of an `<input>`, 530 `<textarea>`, `<select>`, or `[contenteditable="true"]` node changes as a 531 result of a user-initiated keystroke, mouse operation, or input method 532 editor (IME) input event. 533 534 Unlike the `onchange` event, this event fires when the value actually changes 535 and not when the element loses focus. This event also reports IME and 536 multi-stroke input more reliably than `oninput` or the various key events across 537 browsers. 538 539 For performance reasons, only focused nodes are monitored for changes, so 540 programmatic value changes on nodes that don't have focus won't be detected. 541 542 @example 543 544 YUI().use('event-valuechange', function (Y) { 545 Y.one('#my-input').on('valuechange', function (e) { 546 Y.log('previous value: ' + e.prevVal); 547 Y.log('new value: ' + e.newVal); 548 }); 549 }); 550 551 @event valuechange 552 @param {String} prevVal Previous value prior to the latest change. 553 @param {String} newVal New value after the latest change. 554 @for YUI 555 **/ 556 557 config = { 558 detach: VC._onUnsubscribe, 559 on : VC._onSubscribe, 560 561 delegate : VC._onSubscribe, 562 detachDelegate: VC._onUnsubscribe, 563 564 publishConfig: { 565 emitFacade: true 566 } 567 }; 568 569 Y.Event.define('valuechange', config); 570 Y.Event.define('valueChange', config); // deprecated, but supported for backcompat 571 572 Y.ValueChange = VC; 573 574 575 }, '3.17.2', {"requires": ["event-focus", "event-synthetic"]});
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |