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