├── .gitignore ├── Gruntfile.js ├── MIT-LICENSE.txt ├── README.md ├── bower.json ├── changelog.md ├── demo ├── demo.html ├── jquery-ui.custom.min.css ├── jquery-ui.custom.min.js ├── jquery.min.js ├── raphael-min.js └── raphael.sketchpad.js ├── doc ├── divs.png ├── drag-n-drop.md ├── interpolation_stepCount.png ├── key-combo.md ├── key-sequence.md └── simulate.md ├── libs ├── bililiteRange.js └── jquery.simulate.js ├── package.json ├── simulate-ext.jquery.json ├── src ├── jquery.simulate.drag-n-drop.js ├── jquery.simulate.ext.js ├── jquery.simulate.key-combo.js └── jquery.simulate.key-sequence.js └── tests ├── .jshintrc ├── drag-n-drop.js ├── iframe.html ├── key-combo.js ├── key-sequence.js ├── qunit-close-enough.js ├── qunit.css ├── qunit.js ├── testInfrastructure.js └── tests.html /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | dist 3 | !.gitignore 4 | !.jshintrc 5 | /node_modules 6 | /bower_components 7 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /*global module:false require:true*/ 2 | module.exports = function(grunt) { 3 | "use strict"; 4 | 5 | var fs = require('fs'); 6 | 7 | grunt.loadNpmTasks('grunt-contrib-jshint'); 8 | grunt.loadNpmTasks('grunt-contrib-qunit'); 9 | grunt.loadNpmTasks('grunt-contrib-concat'); 10 | 11 | // Project configuration. 12 | grunt.initConfig({ 13 | pkg: grunt.file.readJSON("package.json"), 14 | meta: { 15 | banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + 16 | '<%= grunt.template.today("yyyy-mm-dd") %>' + "\n" + 17 | '<%= pkg.homepage ? " * " + pkg.homepage + "\\n" : "" %>' + 18 | ' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.contributors[0].name %>;' + 19 | ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %>\n' + 20 | ' */' 21 | }, 22 | 23 | jshint: { 24 | options: { 25 | "-W106": true, // Ignore warning "Identifier 'ext_disableQuirkDetection' is not in camel case." 26 | globals: { 27 | jQuery: true, 28 | "$": true, 29 | bililiteRange: true 30 | }, 31 | }, 32 | tests: { 33 | options: { 34 | jshintrc: true 35 | }, 36 | src: ['tests/drag-n-drop.js', 'tests/key-combo.js', 'tests/key-sequence.js', 'tests/testInfrastructure.js'] 37 | }, 38 | src: ['src/*.js', 'libs/jquery.simulate.js'], 39 | grunt: 'Gruntfile.js', 40 | }, 41 | 42 | qunit: { 43 | files: ['tests/tests.html'] 44 | }, 45 | 46 | concat: { 47 | 'ext-only': { 48 | src: ['src/jquery.simulate.ext.js', 'src/jquery.simulate.drag-n-drop.js', 'src/jquery.simulate.key-sequence.js', 'src/jquery.simulate.key-combo.js'], 49 | dest: 'dist/jquery.simulate.ext.<%= pkg.version %>.js' 50 | }, 51 | complete: { 52 | src: ['libs/bililiteRange.js', 'libs/jquery.simulate.js', 'src/jquery.simulate.ext.js', 'src/jquery.simulate.drag-n-drop.js', 'src/jquery.simulate.key-sequence.js', 'src/jquery.simulate.key-combo.js'], 53 | dest: 'dist/jquery.simulate.ext.<%= pkg.version %>.complete.js' 54 | } 55 | }, 56 | 57 | /* uglify task is not working correctly at the moment (strips away copyright notices) 58 | * therefore, we cannot use it 59 | 60 | uglify: { 61 | 'ext-only': { 62 | options: { 63 | banner: '<%= meta.banner %>' 64 | }, 65 | src: ['<%= concat["ext-only"].dest %>'], 66 | dest: 'dist/jquery.simulate.ext.<%= pkg.version %>.min.js' 67 | }, 68 | complete: { 69 | options: { 70 | preserveComments: function(node, comment) { 71 | if (comment.value.search(/Copyright/i) != -1) 72 | return true; 73 | else 74 | return false; 75 | } 76 | }, 77 | src: ['<%= concat["complete"].dest %>'], 78 | dest: 'dist/jquery.simulate.ext.<%= pkg.version %>.complete.min.js' 79 | } 80 | }, 81 | */ 82 | 83 | "multi-banner-min": { 84 | complete: { 85 | src: ['', 'libs/jquery.simulate.js', '', '<%= concat["ext-only"].dest %>'], 86 | dest: 'dist/jquery.simulate.ext.<%= pkg.version %>.complete.min.js' 87 | } 88 | } 89 | 90 | }); 91 | 92 | 93 | // Default task. 94 | grunt.registerTask('default', ['jshint', 'qunit', 'concat']); 95 | 96 | }; 97 | -------------------------------------------------------------------------------- /MIT-LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Jochen Ulrich (jochenulrich@t-online.de) 2 | http://github.com/j-ulrich/jquery-simulate-ext 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Project Status: Abandoned](https://img.shields.io/badge/project%20status-abandoned-red.svg)](https://github.com/j-ulrich/jquery-simulate-ext/issues/39) 2 | **NOTE:** jQuery Simulate Extended is not actively developed anymore. It may still work but chances are high that some features are broken in recent browsers. For more information, see [issue #39](https://github.com/j-ulrich/jquery-simulate-ext/issues/39). 3 | 4 | 5 | jQuery Simulate Extended Plugin 1.3.0 6 | ===================================== 7 | 8 | The jQuery Simulate Extended plugin (a.k.a. jquery-simulate-ext) provides methods for simulating complex 9 | user interactions based on the [jQuery.simulate()](https://github.com/jquery/jquery-simulate) plugin. 10 | The plugin provides simulation of: 11 | 12 | * Drag & Drop 13 | * Key Sequences 14 | * Key Combinations 15 | 16 | Additionally, the extended plugin includes documentation and fixes for the jQuery simulate plugin itself. 17 | 18 | #### Table of Contents #### 19 | - [Usage](#usage) 20 | - [Example](#example) 21 | - [Demos](#demos) 22 | - [Documentation](#documentation) 23 | - [Requirements](#requirements) 24 | - [Compatibility](#compatibility) 25 | - [Quirk Detection](#quirk-detection) 26 | - [Licensing](#licensing) 27 | 28 | 29 | Usage 30 | ----- 31 | To use the jquery-simulate-ext plugin, you need to include (in the given order): 32 | 33 | 1. `bililiteRange.js` 34 | [if you want to use `jquery.simulate.key-sequence.js` or `jquery.simulate.key-combo.js`] 35 | 1. `jquery-x.y.z.js` 36 | 1. `jquery.simulate.js` 37 | 1. `jquery.simulate.ext.js` 38 | 1. `jquery.simulate.drag-n-drop.js` [if you want to simulate drag & drop] 39 | 1. `jquery.simulate.key-sequence.js` [if you want to simulate key sequences] 40 | 1. `jquery.simulate.key-combo.js` [if you want to simulate key combos] 41 | 42 | The simulations are executed by calling the `.simulate()` function on a jQuery object. The simulation 43 | is then executed on all elements in the collection of the jQuery object (unless otherwise noted). 44 | 45 | - Synopsis: `.simulate(type, options)` 46 | - Parameters: 47 | * __type__ _{String}_: The type of the interaction to be simulated. 48 | * __options__ _{Object}_: An option object containing the options for the action to be simulated. 49 | 50 | The types of simulated actions are: 51 | 52 | - From the jquery-simulate plugin: 53 | - Mouse Events: `"mousemove"`, `"mousedown"`, `"mouseup"`, `"click"`, `dblclick"`, 54 | `"mouseover"`, `"mouseout"`, `"mouseenter"`, `"mouseleave"`, `"contextmenu"` 55 | - Key Events: `"keydown"`, `"keyup"`, `"keypress"` 56 | - `"focus"` 57 | - `"blur"` 58 | - From the jquery-simulate-ext plugin: 59 | - Drag & Drop: `"drag-n-drop"`, `"drag"`, `"drop"` 60 | - `"key-sequence"` 61 | - `"key-combo"` 62 | 63 | #### Example: #### 64 | ```javascript 65 | $('input[name="testInput"]').simulate("key-sequence", {sequence: "asdf"}); 66 | ``` 67 | 68 | #### Demos: #### 69 | The [demos folder](https://github.com/j-ulrich/jquery-simulate-ext/tree/master/demo) contains a 70 | demonstration of most of the features of the simulate extended plugins. 71 | 72 | Live demos can be found at jsFiddle and JS Bin where you can also play around with the plugin: 73 | 74 | - http://jsfiddle.net/Ignitor/Psjhf/embedded/result/ ([jsFiddle](http://jsfiddle.net/Ignitor/Psjhf/)) 75 | - http://jsbin.com/inalax/25/edit#live ([JS Bin](http://jsbin.com/inalax/25/edit)) 76 | 77 | 78 | Documentation 79 | ------------- 80 | The options and events for the different interactions are described in the files in the [doc folder](https://github.com/j-ulrich/jquery-simulate-ext/tree/master/doc): 81 | * [Mouse Events](https://github.com/j-ulrich/jquery-simulate-ext/tree/master/doc/simulate.md) 82 | * [Key Events](https://github.com/j-ulrich/jquery-simulate-ext/tree/master/doc/simulate.md) 83 | * [Focus/Blur](https://github.com/j-ulrich/jquery-simulate-ext/tree/master/doc/simulate.md) 84 | * [Drag & Drop](https://github.com/j-ulrich/jquery-simulate-ext/tree/master/doc/drag-n-drop.md) 85 | * [Key Sequence](https://github.com/j-ulrich/jquery-simulate-ext/tree/master/doc/key-sequence.md) 86 | * [Key Combination](https://github.com/j-ulrich/jquery-simulate-ext/tree/master/doc/key-combo.md) 87 | 88 | ### Global Options: ### 89 | Options recognized by all jquery-simulate-ext plugins: 90 | 91 | * __eventProps__ _{Object}_: Defines custom properties which will be attached to the simulated events. 92 | __Note:__ Trying to define default properties of events (e.g. `type`, `bubbles`, `altKey`, etc.) using this option 93 | might not work since those properties are typically read-only. 94 | __Note:__ The `dispatchEvent()` function of all major browsers will remove custom properties from the event. 95 | Therefore, the [`jquery.simulate.js`](https://github.com/j-ulrich/jquery-simulate-ext/tree/master/libs/jquery.simulate.js) 96 | from the jquery-simulate-ext repository contains an option to use `jQuery.trigger()` instead of the 97 | native `dispatchEvent()`. This causes that the simulated event will only trigger event handlers attached using the same 98 | jQuery instance and not handlers attached using [`addEventListener()`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget.addEventListener) 99 | or using another versions of jQuery (see http://bugs.jquery.com/ticket/11047 for more information), but it's the only way 100 | to allow the custom properties to be used in the event handlers. To activate this option, define `jQueryTrigger: true` 101 | in the `eventProps` option object. For example: 102 | 103 | ```javascript 104 | $('#mySimulateTarget').simulate('key-sequence', { 105 | sequence: "my simulated text", 106 | eventProps: { 107 | jQueryTrigger: true, 108 | simulatedEvent: true 109 | } 110 | }); 111 | ``` 112 | __Tip:__ As the example shows, this allows to flag the simulated events, which allows to 113 | distinguish the simulated events from real events in the event handlers. 114 | See [issue #12](https://github.com/j-ulrich/jquery-simulate-ext/issues/12) for more information. 115 | 116 | 117 | Requirements 118 | ------------ 119 | The plugin requires 120 | * [jQuery 1.7.0+](http://jquery.com) 121 | * [jQuery Simulate](https://github.com/jquery/jquery-ui/blob/master/tests/jquery.simulate.js) 122 | __Note:__ With the [current version](https://github.com/jquery/jquery-ui/blob/485ca7192ac57d018b8ce4f03e7dec6e694a53b7/tests/jquery.simulate.js) 123 | of `jquery.simulate.js`, not all features of jquery-simulate-ext work correctly (see for example 124 | [Drag & Drop within iframes](https://github.com/j-ulrich/jquery-simulate-ext/tree/master/doc/drag-n-drop.md#iframes)). 125 | Therefore, the jquery-simulate-ext repository contains a fixed version of `jquery.simulate.js` at 126 | [`libs/jquery.simulate.js`](https://github.com/j-ulrich/jquery-simulate-ext/tree/master/libs/jquery.simulate.js). 127 | * [bililiteRange](http://bililite.com/blog/2011/01/17/cross-browser-text-ranges-and-selections) for 128 | the key-sequence and key-combo plugins 129 | 130 | Compatibility 131 | ------------ 132 | The plugins have been successfully tested with jQuery 1.7.2, 1.10.2, 2.1.0 and jQuery Simulate 133 | [@485ca7192a](https://github.com/jquery/jquery-ui/blob/485ca7192ac57d018b8ce4f03e7dec6e694a53b7/tests/jquery.simulate.js), 134 | [@25938de206](https://github.com/jquery/jquery-simulate/blob/25938de20622a6c127a7082bd751f6d2f88eabd4/jquery.simulate.js). 135 | However, they should be compatible with other/future versions as well. 136 | 137 | ### Quirk Detection ### 138 | There are some issues with bililiteRange and some browsers. To workaround these issues, jquery-simulate-ext 139 | performs some quirk detections when the document is ready. Those quirk detections also contain temporary DOM manipulations. 140 | If you don't want those DOM manipulations to take place, you can disable the quirk detection by setting the flag 141 | `ext_disableQuirkDetection` in the `jQuery.simulate` object **after** `jquery.simulate.js` has been loaded but **before** 142 | any jquery-simulate-ext plugin is loaded. For example: 143 | ```html 144 | 145 | 146 | 147 | 148 | 149 | 150 | ``` 151 | For more information, see [issue #9](https://github.com/j-ulrich/jquery-simulate-ext/issues/9). 152 | 153 | Licensing 154 | --------- 155 | Copyright © 2014 Jochen Ulrich 156 | https://github.com/j-ulrich/jquery-simulate-ext 157 | 158 | Licensed under the [MIT license](http://opensource.org/licenses/MIT). 159 | 160 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-simulate-ext", 3 | "description": "jQuery Simulate - extended. Simulating complex user interaction based on the jQuery Simulate plugin.", 4 | "version": "1.3.0", 5 | "keywords": [ 6 | "jquery", 7 | "simulate", 8 | "key", 9 | "mouse", 10 | "press", 11 | "move", 12 | "drag", 13 | "drop", 14 | "user-interaction" 15 | ], 16 | "authors": [ 17 | { 18 | "name": "Jochen Ulrich", 19 | "email": "jochenulrich@t-online.de", 20 | "homepage": "https://github.com/j-ulrich" 21 | } 22 | ], 23 | "main": [ 24 | "libs/bililiteRange.js", 25 | "libs/jquery.simulate.js", 26 | "src/jquery.simulate.ext.js", 27 | "src/jquery.simulate.drag-n-drop.js", 28 | "src/jquery.simulate.key-sequence.js", 29 | "src/jquery.simulate.key-combo.js" 30 | ], 31 | "ignore": [ 32 | "**/.*", 33 | "*.md", 34 | "demo", 35 | "doc", 36 | "tests", 37 | "libs/jquery-*.js", 38 | "bower.json", 39 | "grunt.js", 40 | "package.json", 41 | "simulate-ext.jquery.json" 42 | ], 43 | "dependencies": { 44 | "jquery": ">=1.7.0" 45 | }, 46 | "devDependencies": { 47 | "qunit": "~1.14.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | jquery-simulate-ext Changelog 2 | ============================= 3 | 4 | Version 1.3.0 - (Released: 2014-03-07) 5 | ------------- 6 | * Adds the `eventParams` option to allow custom data in the simulated events (see #12). 7 | * Adds bower.json and registers the package at bower. 8 | * Registers the package at npm. 9 | * Corrects jQuery naming conventions ($varible). 10 | * Updates demo to jQuery UI 1.10.3 to make it work with jQuery 1.10.2. 11 | * Improves documentation here and there. 12 | * Logs a warning message before running the test suite to warn about mouse movement during testing. 13 | * Updated grunt file to grunt 0.4.x. 14 | * __key-combo__: Adds support of special keys (`"left-arrow"`, etc.). 15 | * __jquery.simulate.js__: Adds the `jQueryTrigger` option to use `jQuery.trigger()` instead of `disptachEvent()`. 16 | 17 | Version 1.2.0 - (Released: 2013-09-03) 18 | ------------- 19 | * Adds flag `jQuery.simulate.ext_disableQuirkDetection` to disable quirk detections. 20 | * Allows enabling/disabling specific quirk fixes manually by settings the corresponding 21 | flag in the `jQuery.simulate.prototype.quirks` object. 22 | 23 | Version 1.1.6 - (Released: 2013-08-15) 24 | ------------- 25 | * Updates to the latest version of jquery.simulate.js to achieve compatibility with jQuery 1.9.x 26 | and above. 27 | * Replaces libs/jquery-1.7.2.js with libs/jquery-1.10.2.js and updated demo accordingly. 28 | 29 | Version 1.1.5 - (Released: 2013-05-22) 30 | ------------- 31 | * Layout (CSS) improvements in the demo. 32 | * __key-sequence__: Adds a workaround for the bug that spaces are moved to the end 33 | of the sequence when simulating on a non-input with delay on certain browsers 34 | (Webkit-based browsers). 35 | 36 | Version 1.1.4 - (Released: 2013-01-30) 37 | ------------- 38 | * Extends the documentation for simulating drag-n-drops within iframes. 39 | * Registers the plugin at plugins.jquery.com. 40 | 41 | 42 | Version 1.1.3 - (Released: 2013-01-17) 43 | ------------- 44 | * Updates the jQuery package manifest file. 45 | 46 | 47 | Version 1.1.2 - (Released: 2012-10-11) 48 | ------------- 49 | * The build script now produces two dist versions: the "normal" version contains the jQuery simulate 50 | extended plugins and the "complete" version also includes jQuery simulate. 51 | 52 | 53 | Version 1.1.1 - (Released: 2012-10-08) 54 | ------------- 55 | * Adds grunt.js build system. 56 | * Source code cleanup. 57 | 58 | 59 | Version 1.1 - (Released: 2012-09-12) 60 | ----------- 61 | * __drag-n-drop__: Adds support for simulation of drag-n-drop within child-iframes 62 | * __drag-n-drop__: `mousemove` events are now triggered on the element at the position of the event instead of 63 | the dragged element (for exceptions, see [doc/drag-n-drop.md#events](https://github.com/j-ulrich/jquery-simulate-ext/tree/master/doc/drag-n-drop.md#events)) -------------------------------------------------------------------------------- /demo/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | jquery-simulate-ext Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 113 | 181 | 182 | 183 |
184 |
185 | 186 |
187 |

Key-Combo Simulation:

188 | 189 | 190 |
191 |
192 | Custom Combo: 193 |
194 |
195 |

Key-Sequence Simulation:

196 | 197 | 198 |
199 |
200 | Delay: ms 201 |
202 |
203 | Custom Sequence: 204 |
205 |
206 |
207 |

Drag'n'Drop Simulation:

208 |
209 | Note:
Avoid moving the mouse during interpolated drag'n'drop simulations. 210 |
211 |
212 |
Interpolation:
213 | :
217 | : ms
221 | Shaky: pixels 222 |
223 |
224 |

Slider:

225 |
226 |
Current slider value:
227 |
228 |

Press 'd' to simulate a drop while dragging.

229 | 230 | 231 | 232 |
233 |
234 | 235 | 236 | 237 |
238 |
239 |
240 |

Drawing:

241 | 242 |
243 |
244 |
245 | 246 | 247 | 248 |
249 |
250 | dx: , dy: 251 |
252 |
253 |
254 |
255 |
256 |
257 | Event Log: 258 | 259 |
260 |
261 | Note: mousemove events are logged only during drag simulation to avoid spamming 262 |
263 |
264 | MouseMove Position: 265 |
266 |
267 | 387 | 388 | -------------------------------------------------------------------------------- /demo/jquery-ui.custom.min.css: -------------------------------------------------------------------------------- 1 | /*! jQuery UI - v1.10.3 - 2013-12-15 2 | * http://jqueryui.com 3 | * Includes: jquery.ui.core.css, jquery.ui.slider.css, jquery.ui.theme.css 4 | * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Trebuchet%20MS%2CTahoma%2CVerdana%2CArial%2Csans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=f6a828&bgTextureHeader=gloss_wave&bgImgOpacityHeader=35&borderColorHeader=e78f08&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=highlight_soft&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=glass&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=1c94c4&iconColorDefault=ef8c08&bgColorHover=fdf5ce&bgTextureHover=glass&bgImgOpacityHover=100&borderColorHover=fbcb09&fcHover=c77405&iconColorHover=ef8c08&bgColorActive=ffffff&bgTextureActive=glass&bgImgOpacityActive=65&borderColorActive=fbd850&fcActive=eb8f00&iconColorActive=ef8c08&bgColorHighlight=ffe45c&bgTextureHighlight=highlight_soft&bgImgOpacityHighlight=75&borderColorHighlight=fed22f&fcHighlight=363636&iconColorHighlight=228ef1&bgColorError=b81900&bgTextureError=diagonals_thick&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=diagonals_thick&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=flat&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px 5 | * Copyright 2013 jQuery Foundation and other contributors; Licensed MIT */ 6 | 7 | .ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{min-height:0}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-slider{position:relative;text-align:left}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0}.ui-slider.ui-state-disabled .ui-slider-handle,.ui-slider.ui-state-disabled .ui-slider-range{filter:inherit}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{left:0}.ui-slider-horizontal .ui-slider-range-max{right:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{left:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0}.ui-widget{font-family:Trebuchet MS,Tahoma,Verdana,Arial,sans-serif;font-size:1.1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Trebuchet MS,Tahoma,Verdana,Arial,sans-serif;font-size:1em}.ui-widget-content{border:1px solid #ddd;background:#eee 50% top repeat-x;color:#333}.ui-widget-content a{color:#333}.ui-widget-header{border:1px solid #e78f08;background:#f6a828 50% 50% repeat-x;color:#fff;font-weight:bold}.ui-widget-header a{color:#fff}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #ccc;background:#f6f6f6 50% 50% repeat-x;font-weight:bold;color:#1c94c4}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#1c94c4;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #fbcb09;background:#fdf5ce 50% 50% repeat-x;font-weight:bold;color:#c77405}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited{color:#c77405;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #fbd850;background:#fff 50% 50% repeat-x;font-weight:bold;color:#eb8f00}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#eb8f00;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #fed22f;background:#ffe45c 50% top repeat-x;color:#363636}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#363636}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #cd0a0a;background:#b81900 50% 50% repeat;color:#fff}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#fff}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#fff}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{}.ui-widget-header .ui-icon{}.ui-state-default .ui-icon{}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{}.ui-state-active .ui-icon{}.ui-state-highlight .ui-icon{}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{}.ui-icon-blank{background-position:16px 16px}.ui-icon-carat-1-n{background-position:0 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-64px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-64px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:0 -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:4px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:4px}.ui-widget-overlay{background:#666 50% 50% repeat;opacity:.5;filter:Alpha(Opacity=50)}.ui-widget-shadow{margin:-5px 0 0 -5px;padding:5px;background:#000 50% 50% repeat-x;opacity:.2;filter:Alpha(Opacity=20);border-radius:5px} -------------------------------------------------------------------------------- /demo/jquery-ui.custom.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery UI - v1.10.3 - 2013-12-15 2 | * http://jqueryui.com 3 | * Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.slider.js 4 | * Copyright 2013 jQuery Foundation and other contributors; Licensed MIT */ 5 | 6 | (function(e,t){function i(t,i){var s,n,r,o=t.nodeName.toLowerCase();return"area"===o?(s=t.parentNode,n=s.name,t.href&&n&&"map"===s.nodeName.toLowerCase()?(r=e("img[usemap=#"+n+"]")[0],!!r&&a(r)):!1):(/input|select|textarea|button|object/.test(o)?!t.disabled:"a"===o?t.href||i:i)&&a(t)}function a(t){return e.expr.filters.visible(t)&&!e(t).parents().addBack().filter(function(){return"hidden"===e.css(this,"visibility")}).length}var s=0,n=/^ui-id-\d+$/;e.ui=e.ui||{},e.extend(e.ui,{version:"1.10.3",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({focus:function(t){return function(i,a){return"number"==typeof i?this.each(function(){var t=this;setTimeout(function(){e(t).focus(),a&&a.call(t)},i)}):t.apply(this,arguments)}}(e.fn.focus),scrollParent:function(){var t;return t=e.ui.ie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(e.css(this,"position"))&&/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0),/fixed/.test(this.css("position"))||!t.length?e(document):t},zIndex:function(i){if(i!==t)return this.css("zIndex",i);if(this.length)for(var a,s,n=e(this[0]);n.length&&n[0]!==document;){if(a=n.css("position"),("absolute"===a||"relative"===a||"fixed"===a)&&(s=parseInt(n.css("zIndex"),10),!isNaN(s)&&0!==s))return s;n=n.parent()}return 0},uniqueId:function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++s)})},removeUniqueId:function(){return this.each(function(){n.test(this.id)&&e(this).removeAttr("id")})}}),e.extend(e.expr[":"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(i){return!!e.data(i,t)}}):function(t,i,a){return!!e.data(t,a[3])},focusable:function(t){return i(t,!isNaN(e.attr(t,"tabindex")))},tabbable:function(t){var a=e.attr(t,"tabindex"),s=isNaN(a);return(s||a>=0)&&i(t,!s)}}),e("").outerWidth(1).jquery||e.each(["Width","Height"],function(i,a){function s(t,i,a,s){return e.each(n,function(){i-=parseFloat(e.css(t,"padding"+this))||0,a&&(i-=parseFloat(e.css(t,"border"+this+"Width"))||0),s&&(i-=parseFloat(e.css(t,"margin"+this))||0)}),i}var n="Width"===a?["Left","Right"]:["Top","Bottom"],r=a.toLowerCase(),o={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn["inner"+a]=function(i){return i===t?o["inner"+a].call(this):this.each(function(){e(this).css(r,s(this,i)+"px")})},e.fn["outer"+a]=function(t,i){return"number"!=typeof t?o["outer"+a].call(this,t):this.each(function(){e(this).css(r,s(this,t,!0,i)+"px")})}}),e.fn.addBack||(e.fn.addBack=function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}),e("").data("a-b","a").removeData("a-b").data("a-b")&&(e.fn.removeData=function(t){return function(i){return arguments.length?t.call(this,e.camelCase(i)):t.call(this)}}(e.fn.removeData)),e.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()),e.support.selectstart="onselectstart"in document.createElement("div"),e.fn.extend({disableSelection:function(){return this.bind((e.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(e){e.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),e.extend(e.ui,{plugin:{add:function(t,i,a){var s,n=e.ui[t].prototype;for(s in a)n.plugins[s]=n.plugins[s]||[],n.plugins[s].push([i,a[s]])},call:function(e,t,i){var a,s=e.plugins[t];if(s&&e.element[0].parentNode&&11!==e.element[0].parentNode.nodeType)for(a=0;s.length>a;a++)e.options[s[a][0]]&&s[a][1].apply(e.element,i)}},hasScroll:function(t,i){if("hidden"===e(t).css("overflow"))return!1;var a=i&&"left"===i?"scrollLeft":"scrollTop",s=!1;return t[a]>0?!0:(t[a]=1,s=t[a]>0,t[a]=0,s)}})})(jQuery);(function(e,t){var i=0,s=Array.prototype.slice,a=e.cleanData;e.cleanData=function(t){for(var i,s=0;null!=(i=t[s]);s++)try{e(i).triggerHandler("remove")}catch(n){}a(t)},e.widget=function(i,s,a){var n,r,o,h,l={},u=i.split(".")[0];i=i.split(".")[1],n=u+"-"+i,a||(a=s,s=e.Widget),e.expr[":"][n.toLowerCase()]=function(t){return!!e.data(t,n)},e[u]=e[u]||{},r=e[u][i],o=e[u][i]=function(e,i){return this._createWidget?(arguments.length&&this._createWidget(e,i),t):new o(e,i)},e.extend(o,r,{version:a.version,_proto:e.extend({},a),_childConstructors:[]}),h=new s,h.options=e.widget.extend({},h.options),e.each(a,function(i,a){return e.isFunction(a)?(l[i]=function(){var e=function(){return s.prototype[i].apply(this,arguments)},t=function(e){return s.prototype[i].apply(this,e)};return function(){var i,s=this._super,n=this._superApply;return this._super=e,this._superApply=t,i=a.apply(this,arguments),this._super=s,this._superApply=n,i}}(),t):(l[i]=a,t)}),o.prototype=e.widget.extend(h,{widgetEventPrefix:r?h.widgetEventPrefix:i},l,{constructor:o,namespace:u,widgetName:i,widgetFullName:n}),r?(e.each(r._childConstructors,function(t,i){var s=i.prototype;e.widget(s.namespace+"."+s.widgetName,o,i._proto)}),delete r._childConstructors):s._childConstructors.push(o),e.widget.bridge(i,o)},e.widget.extend=function(i){for(var a,n,r=s.call(arguments,1),o=0,h=r.length;h>o;o++)for(a in r[o])n=r[o][a],r[o].hasOwnProperty(a)&&n!==t&&(i[a]=e.isPlainObject(n)?e.isPlainObject(i[a])?e.widget.extend({},i[a],n):e.widget.extend({},n):n);return i},e.widget.bridge=function(i,a){var n=a.prototype.widgetFullName||i;e.fn[i]=function(r){var o="string"==typeof r,h=s.call(arguments,1),l=this;return r=!o&&h.length?e.widget.extend.apply(null,[r].concat(h)):r,o?this.each(function(){var s,a=e.data(this,n);return a?e.isFunction(a[r])&&"_"!==r.charAt(0)?(s=a[r].apply(a,h),s!==a&&s!==t?(l=s&&s.jquery?l.pushStack(s.get()):s,!1):t):e.error("no such method '"+r+"' for "+i+" widget instance"):e.error("cannot call methods on "+i+" prior to initialization; "+"attempted to call method '"+r+"'")}):this.each(function(){var t=e.data(this,n);t?t.option(r||{})._init():e.data(this,n,new a(r,this))}),l}},e.Widget=function(){},e.Widget._childConstructors=[],e.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
",options:{disabled:!1,create:null},_createWidget:function(t,s){s=e(s||this.defaultElement||this)[0],this.element=e(s),this.uuid=i++,this.eventNamespace="."+this.widgetName+this.uuid,this.options=e.widget.extend({},this.options,this._getCreateOptions(),t),this.bindings=e(),this.hoverable=e(),this.focusable=e(),s!==this&&(e.data(s,this.widgetFullName,this),this._on(!0,this.element,{remove:function(e){e.target===s&&this.destroy()}}),this.document=e(s.style?s.ownerDocument:s.document||s),this.window=e(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:e.noop,_getCreateEventData:e.noop,_create:e.noop,_init:e.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(e.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:e.noop,widget:function(){return this.element},option:function(i,s){var a,n,r,o=i;if(0===arguments.length)return e.widget.extend({},this.options);if("string"==typeof i)if(o={},a=i.split("."),i=a.shift(),a.length){for(n=o[i]=e.widget.extend({},this.options[i]),r=0;a.length-1>r;r++)n[a[r]]=n[a[r]]||{},n=n[a[r]];if(i=a.pop(),s===t)return n[i]===t?null:n[i];n[i]=s}else{if(s===t)return this.options[i]===t?null:this.options[i];o[i]=s}return this._setOptions(o),this},_setOptions:function(e){var t;for(t in e)this._setOption(t,e[t]);return this},_setOption:function(e,t){return this.options[e]=t,"disabled"===e&&(this.widget().toggleClass(this.widgetFullName+"-disabled ui-state-disabled",!!t).attr("aria-disabled",t),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")),this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_on:function(i,s,a){var n,r=this;"boolean"!=typeof i&&(a=s,s=i,i=!1),a?(s=n=e(s),this.bindings=this.bindings.add(s)):(a=s,s=this.element,n=this.widget()),e.each(a,function(a,o){function h(){return i||r.options.disabled!==!0&&!e(this).hasClass("ui-state-disabled")?("string"==typeof o?r[o]:o).apply(r,arguments):t}"string"!=typeof o&&(h.guid=o.guid=o.guid||h.guid||e.guid++);var l=a.match(/^(\w+)\s*(.*)$/),u=l[1]+r.eventNamespace,c=l[2];c?n.delegate(c,u,h):s.bind(u,h)})},_off:function(e,t){t=(t||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.unbind(t).undelegate(t)},_delay:function(e,t){function i(){return("string"==typeof e?s[e]:e).apply(s,arguments)}var s=this;return setTimeout(i,t||0)},_hoverable:function(t){this.hoverable=this.hoverable.add(t),this._on(t,{mouseenter:function(t){e(t.currentTarget).addClass("ui-state-hover")},mouseleave:function(t){e(t.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(t){this.focusable=this.focusable.add(t),this._on(t,{focusin:function(t){e(t.currentTarget).addClass("ui-state-focus")},focusout:function(t){e(t.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(t,i,s){var a,n,r=this.options[t];if(s=s||{},i=e.Event(i),i.type=(t===this.widgetEventPrefix?t:this.widgetEventPrefix+t).toLowerCase(),i.target=this.element[0],n=i.originalEvent)for(a in n)a in i||(i[a]=n[a]);return this.element.trigger(i,s),!(e.isFunction(r)&&r.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},e.each({show:"fadeIn",hide:"fadeOut"},function(t,i){e.Widget.prototype["_"+t]=function(s,a,n){"string"==typeof a&&(a={effect:a});var r,o=a?a===!0||"number"==typeof a?i:a.effect||i:t;a=a||{},"number"==typeof a&&(a={duration:a}),r=!e.isEmptyObject(a),a.complete=n,a.delay&&s.delay(a.delay),r&&e.effects&&e.effects.effect[o]?s[t](a):o!==t&&s[o]?s[o](a.duration,a.easing,n):s.queue(function(i){e(this)[t](),n&&n.call(s[0]),i()})}})})(jQuery);(function(e){var t=!1;e(document).mouseup(function(){t=!1}),e.widget("ui.mouse",{version:"1.10.3",options:{cancel:"input,textarea,button,select,option",distance:1,delay:0},_mouseInit:function(){var t=this;this.element.bind("mousedown."+this.widgetName,function(e){return t._mouseDown(e)}).bind("click."+this.widgetName,function(i){return!0===e.data(i.target,t.widgetName+".preventClickEvent")?(e.removeData(i.target,t.widgetName+".preventClickEvent"),i.stopImmediatePropagation(),!1):undefined}),this.started=!1},_mouseDestroy:function(){this.element.unbind("."+this.widgetName),this._mouseMoveDelegate&&e(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(i){if(!t){this._mouseStarted&&this._mouseUp(i),this._mouseDownEvent=i;var s=this,a=1===i.which,n="string"==typeof this.options.cancel&&i.target.nodeName?e(i.target).closest(this.options.cancel).length:!1;return a&&!n&&this._mouseCapture(i)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){s.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(i)&&this._mouseDelayMet(i)&&(this._mouseStarted=this._mouseStart(i)!==!1,!this._mouseStarted)?(i.preventDefault(),!0):(!0===e.data(i.target,this.widgetName+".preventClickEvent")&&e.removeData(i.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(e){return s._mouseMove(e)},this._mouseUpDelegate=function(e){return s._mouseUp(e)},e(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate),i.preventDefault(),t=!0,!0)):!0}},_mouseMove:function(t){return e.ui.ie&&(!document.documentMode||9>document.documentMode)&&!t.button?this._mouseUp(t):this._mouseStarted?(this._mouseDrag(t),t.preventDefault()):(this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,t)!==!1,this._mouseStarted?this._mouseDrag(t):this._mouseUp(t)),!this._mouseStarted)},_mouseUp:function(t){return e(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,t.target===this._mouseDownEvent.target&&e.data(t.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(t)),!1},_mouseDistanceMet:function(e){return Math.max(Math.abs(this._mouseDownEvent.pageX-e.pageX),Math.abs(this._mouseDownEvent.pageY-e.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}})})(jQuery);(function(e){var t=5;e.widget("ui.slider",e.ui.mouse,{version:"1.10.3",widgetEventPrefix:"slide",options:{animate:!1,distance:0,max:100,min:0,orientation:"horizontal",range:!1,step:1,value:0,values:null,change:null,slide:null,start:null,stop:null},_create:function(){this._keySliding=!1,this._mouseSliding=!1,this._animateOff=!0,this._handleIndex=null,this._detectOrientation(),this._mouseInit(),this.element.addClass("ui-slider ui-slider-"+this.orientation+" ui-widget"+" ui-widget-content"+" ui-corner-all"),this._refresh(),this._setOption("disabled",this.options.disabled),this._animateOff=!1},_refresh:function(){this._createRange(),this._createHandles(),this._setupEvents(),this._refreshValue()},_createHandles:function(){var t,i,s=this.options,a=this.element.find(".ui-slider-handle").addClass("ui-state-default ui-corner-all"),n="",r=[];for(i=s.values&&s.values.length||1,a.length>i&&(a.slice(i).remove(),a=a.slice(0,i)),t=a.length;i>t;t++)r.push(n);this.handles=a.add(e(r.join("")).appendTo(this.element)),this.handle=this.handles.eq(0),this.handles.each(function(t){e(this).data("ui-slider-handle-index",t)})},_createRange:function(){var t=this.options,i="";t.range?(t.range===!0&&(t.values?t.values.length&&2!==t.values.length?t.values=[t.values[0],t.values[0]]:e.isArray(t.values)&&(t.values=t.values.slice(0)):t.values=[this._valueMin(),this._valueMin()]),this.range&&this.range.length?this.range.removeClass("ui-slider-range-min ui-slider-range-max").css({left:"",bottom:""}):(this.range=e("
").appendTo(this.element),i="ui-slider-range ui-widget-header ui-corner-all"),this.range.addClass(i+("min"===t.range||"max"===t.range?" ui-slider-range-"+t.range:""))):this.range=e([])},_setupEvents:function(){var e=this.handles.add(this.range).filter("a");this._off(e),this._on(e,this._handleEvents),this._hoverable(e),this._focusable(e)},_destroy:function(){this.handles.remove(),this.range.remove(),this.element.removeClass("ui-slider ui-slider-horizontal ui-slider-vertical ui-widget ui-widget-content ui-corner-all"),this._mouseDestroy()},_mouseCapture:function(t){var i,s,a,n,r,o,h,l,u=this,c=this.options;return c.disabled?!1:(this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()},this.elementOffset=this.element.offset(),i={x:t.pageX,y:t.pageY},s=this._normValueFromMouse(i),a=this._valueMax()-this._valueMin()+1,this.handles.each(function(t){var i=Math.abs(s-u.values(t));(a>i||a===i&&(t===u._lastChangedValue||u.values(t)===c.min))&&(a=i,n=e(this),r=t)}),o=this._start(t,r),o===!1?!1:(this._mouseSliding=!0,this._handleIndex=r,n.addClass("ui-state-active").focus(),h=n.offset(),l=!e(t.target).parents().addBack().is(".ui-slider-handle"),this._clickOffset=l?{left:0,top:0}:{left:t.pageX-h.left-n.width()/2,top:t.pageY-h.top-n.height()/2-(parseInt(n.css("borderTopWidth"),10)||0)-(parseInt(n.css("borderBottomWidth"),10)||0)+(parseInt(n.css("marginTop"),10)||0)},this.handles.hasClass("ui-state-hover")||this._slide(t,r,s),this._animateOff=!0,!0))},_mouseStart:function(){return!0},_mouseDrag:function(e){var t={x:e.pageX,y:e.pageY},i=this._normValueFromMouse(t);return this._slide(e,this._handleIndex,i),!1},_mouseStop:function(e){return this.handles.removeClass("ui-state-active"),this._mouseSliding=!1,this._stop(e,this._handleIndex),this._change(e,this._handleIndex),this._handleIndex=null,this._clickOffset=null,this._animateOff=!1,!1},_detectOrientation:function(){this.orientation="vertical"===this.options.orientation?"vertical":"horizontal"},_normValueFromMouse:function(e){var t,i,s,a,n;return"horizontal"===this.orientation?(t=this.elementSize.width,i=e.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)):(t=this.elementSize.height,i=e.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)),s=i/t,s>1&&(s=1),0>s&&(s=0),"vertical"===this.orientation&&(s=1-s),a=this._valueMax()-this._valueMin(),n=this._valueMin()+s*a,this._trimAlignValue(n)},_start:function(e,t){var i={handle:this.handles[t],value:this.value()};return this.options.values&&this.options.values.length&&(i.value=this.values(t),i.values=this.values()),this._trigger("start",e,i)},_slide:function(e,t,i){var s,a,n;this.options.values&&this.options.values.length?(s=this.values(t?0:1),2===this.options.values.length&&this.options.range===!0&&(0===t&&i>s||1===t&&s>i)&&(i=s),i!==this.values(t)&&(a=this.values(),a[t]=i,n=this._trigger("slide",e,{handle:this.handles[t],value:i,values:a}),s=this.values(t?0:1),n!==!1&&this.values(t,i,!0))):i!==this.value()&&(n=this._trigger("slide",e,{handle:this.handles[t],value:i}),n!==!1&&this.value(i))},_stop:function(e,t){var i={handle:this.handles[t],value:this.value()};this.options.values&&this.options.values.length&&(i.value=this.values(t),i.values=this.values()),this._trigger("stop",e,i)},_change:function(e,t){if(!this._keySliding&&!this._mouseSliding){var i={handle:this.handles[t],value:this.value()};this.options.values&&this.options.values.length&&(i.value=this.values(t),i.values=this.values()),this._lastChangedValue=t,this._trigger("change",e,i)}},value:function(e){return arguments.length?(this.options.value=this._trimAlignValue(e),this._refreshValue(),this._change(null,0),undefined):this._value()},values:function(t,i){var s,a,n;if(arguments.length>1)return this.options.values[t]=this._trimAlignValue(i),this._refreshValue(),this._change(null,t),undefined;if(!arguments.length)return this._values();if(!e.isArray(arguments[0]))return this.options.values&&this.options.values.length?this._values(t):this.value();for(s=this.options.values,a=arguments[0],n=0;s.length>n;n+=1)s[n]=this._trimAlignValue(a[n]),this._change(null,n);this._refreshValue()},_setOption:function(t,i){var s,a=0;switch("range"===t&&this.options.range===!0&&("min"===i?(this.options.value=this._values(0),this.options.values=null):"max"===i&&(this.options.value=this._values(this.options.values.length-1),this.options.values=null)),e.isArray(this.options.values)&&(a=this.options.values.length),e.Widget.prototype._setOption.apply(this,arguments),t){case"orientation":this._detectOrientation(),this.element.removeClass("ui-slider-horizontal ui-slider-vertical").addClass("ui-slider-"+this.orientation),this._refreshValue();break;case"value":this._animateOff=!0,this._refreshValue(),this._change(null,0),this._animateOff=!1;break;case"values":for(this._animateOff=!0,this._refreshValue(),s=0;a>s;s+=1)this._change(null,s);this._animateOff=!1;break;case"min":case"max":this._animateOff=!0,this._refreshValue(),this._animateOff=!1;break;case"range":this._animateOff=!0,this._refresh(),this._animateOff=!1}},_value:function(){var e=this.options.value;return e=this._trimAlignValue(e)},_values:function(e){var t,i,s;if(arguments.length)return t=this.options.values[e],t=this._trimAlignValue(t);if(this.options.values&&this.options.values.length){for(i=this.options.values.slice(),s=0;i.length>s;s+=1)i[s]=this._trimAlignValue(i[s]);return i}return[]},_trimAlignValue:function(e){if(this._valueMin()>=e)return this._valueMin();if(e>=this._valueMax())return this._valueMax();var t=this.options.step>0?this.options.step:1,i=(e-this._valueMin())%t,s=e-i;return 2*Math.abs(i)>=t&&(s+=i>0?t:-t),parseFloat(s.toFixed(5))},_valueMin:function(){return this.options.min},_valueMax:function(){return this.options.max},_refreshValue:function(){var t,i,s,a,n,r=this.options.range,o=this.options,h=this,l=this._animateOff?!1:o.animate,u={};this.options.values&&this.options.values.length?this.handles.each(function(s){i=100*((h.values(s)-h._valueMin())/(h._valueMax()-h._valueMin())),u["horizontal"===h.orientation?"left":"bottom"]=i+"%",e(this).stop(1,1)[l?"animate":"css"](u,o.animate),h.options.range===!0&&("horizontal"===h.orientation?(0===s&&h.range.stop(1,1)[l?"animate":"css"]({left:i+"%"},o.animate),1===s&&h.range[l?"animate":"css"]({width:i-t+"%"},{queue:!1,duration:o.animate})):(0===s&&h.range.stop(1,1)[l?"animate":"css"]({bottom:i+"%"},o.animate),1===s&&h.range[l?"animate":"css"]({height:i-t+"%"},{queue:!1,duration:o.animate}))),t=i}):(s=this.value(),a=this._valueMin(),n=this._valueMax(),i=n!==a?100*((s-a)/(n-a)):0,u["horizontal"===this.orientation?"left":"bottom"]=i+"%",this.handle.stop(1,1)[l?"animate":"css"](u,o.animate),"min"===r&&"horizontal"===this.orientation&&this.range.stop(1,1)[l?"animate":"css"]({width:i+"%"},o.animate),"max"===r&&"horizontal"===this.orientation&&this.range[l?"animate":"css"]({width:100-i+"%"},{queue:!1,duration:o.animate}),"min"===r&&"vertical"===this.orientation&&this.range.stop(1,1)[l?"animate":"css"]({height:i+"%"},o.animate),"max"===r&&"vertical"===this.orientation&&this.range[l?"animate":"css"]({height:100-i+"%"},{queue:!1,duration:o.animate}))},_handleEvents:{keydown:function(i){var s,a,n,r,o=e(i.target).data("ui-slider-handle-index");switch(i.keyCode){case e.ui.keyCode.HOME:case e.ui.keyCode.END:case e.ui.keyCode.PAGE_UP:case e.ui.keyCode.PAGE_DOWN:case e.ui.keyCode.UP:case e.ui.keyCode.RIGHT:case e.ui.keyCode.DOWN:case e.ui.keyCode.LEFT:if(i.preventDefault(),!this._keySliding&&(this._keySliding=!0,e(i.target).addClass("ui-state-active"),s=this._start(i,o),s===!1))return}switch(r=this.options.step,a=n=this.options.values&&this.options.values.length?this.values(o):this.value(),i.keyCode){case e.ui.keyCode.HOME:n=this._valueMin();break;case e.ui.keyCode.END:n=this._valueMax();break;case e.ui.keyCode.PAGE_UP:n=this._trimAlignValue(a+(this._valueMax()-this._valueMin())/t);break;case e.ui.keyCode.PAGE_DOWN:n=this._trimAlignValue(a-(this._valueMax()-this._valueMin())/t);break;case e.ui.keyCode.UP:case e.ui.keyCode.RIGHT:if(a===this._valueMax())return;n=this._trimAlignValue(a+r);break;case e.ui.keyCode.DOWN:case e.ui.keyCode.LEFT:if(a===this._valueMin())return;n=this._trimAlignValue(a-r)}this._slide(i,o,n)},click:function(e){e.preventDefault()},keyup:function(t){var i=e(t.target).data("ui-slider-handle-index");this._keySliding&&(this._keySliding=!1,this._stop(t,i),this._change(t,i),e(t.target).removeClass("ui-state-active"))}}})})(jQuery); -------------------------------------------------------------------------------- /doc/divs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j-ulrich/jquery-simulate-ext/dc912f0ab97737c416599e41cf9735ea1a00236c/doc/divs.png -------------------------------------------------------------------------------- /doc/drag-n-drop.md: -------------------------------------------------------------------------------- 1 | jQuery Simulate Extensions: Drag & Drop 2 | ======================================== 3 | 4 | The drag & drop plugin allows simulation of drags and drops. 5 | 6 | The plugin simulates the mouse events for dragging and dropping. It can be used to simulate complex 7 | dragging routes by performing multiple, successive drag simulations (the plugin continues to move 8 | the object around until a drop is simulated or until a drag is simulated on another object). 9 | 10 | #### Table of Contents #### 11 | - [Usage](#usage) 12 | - [Options](#options) 13 | - [drag](#drag) 14 | - [drop](#drop) 15 | - [drag-n-drop](#drag-n-drop) 16 | - [Events](#events) 17 | - [iframes](#iframes) 18 | 19 | Usage 20 | ----- 21 | The `.simulate()` type parameter to simulate drags is `"drag"`, for drops it is `"drop"` and to simulate 22 | a complete drag & drop, it is `"drag-n-drop"`: 23 | - `.simulate("drag", options)` 24 | - `.simulate("drop", options)` 25 | - `.simulate("drag-n-drop", options)` 26 | 27 | The drags and drops are simulated for the first element in the jQuery set of matched elements since there 28 | can be only one drag at a time. If there is an active drag on one element and a drag is simulated on another 29 | element, the former drag is ended (i.e. a drop is simulated) before the new drag begins. 30 | 31 | Information about the currently active drag can be retrieved using `$.simulate.activeDrag()`. It 32 | returns an object with the following structure: 33 | ```javascript 34 | { 35 | dragElement: target, // The element which is dragged. 36 | dragStart: { x: x, y: y }, // The position where the drag started. In case of multiple, 37 | // successive drags, this is the original start position, i.e. 38 | // the start position of the first drag that occurred. 39 | dragDistance: { x: dx, y: dy } // The distance of the drag. In case of multiple, successive drags, 40 | // this is the total distance, i.e. the sum of all drag distances 41 | // that already took place. 42 | // In other words: the current drag ended/will end at dragStart + dragDistance 43 | } 44 | ``` 45 | 46 | In case of a `"drag"` and a `"drag-n-drop"`, the element on which `.simulate()` is called is the 47 | element which will be dragged, i.e. where the drag begins by simulating a `mousedown` event. 48 | In case of a `"drop"`, the element on which `.simulate()` is called is the element where the drop occurs, 49 | i.e. where the drag ends by simulating a `mouseup` event. If that element is currently dragged or if it 50 | is `document`, the drop will take place at the end position of the current drag without further moving/dragging. 51 | All the mouse events will take place on the center of the corresponding element. 52 | 53 | #### Examples: #### 54 | ```javascript 55 | // Simple drag-n-drop 56 | $('#draggableDiv').simulate("drag-n-drop", {dx: 50}); 57 | 58 | // Multiple drags, then drop 59 | $('#draggableDiv').simulate("drag", {dx: 50}); 60 | $('#draggableDiv').simulate("drag", {dragTarget: otherDiv}); 61 | $('#draggableDiv').simulate("drop"); 62 | ``` 63 | 64 | Options 65 | ------- 66 | #### `drag`: #### 67 | * __dx__ _{Numeric}_: Distance to be dragged in x-direction in pixels. Default: `0` 68 | * __dy__ _{Numeric}_: Distance to be dragged in y-direction in pixels. Default: `0` 69 | * __dragTarget__ _{DOM Element}_: Alternatively to specifying the distance to be dragged using `dx` and 70 | `dy`, you can specify a DOM element where the dragging should end. The drag will end on the center 71 | of the given element. When `dragTarget` is specified, `dx` and `dy` are ignored. Default: `undefined` 72 | * __clickToDrag__ _{Boolean}_: Defines whether the plugin should simulate a whole mouse click instead of 73 | just a `mousedown` event to start the drag. Should be use in combination with the `clickToDrop` 74 | option of the `drop` simulation. Default: `false` 75 | * __interpolation__ _{Object}_: Defines the properties for an interpolated drag, i.e. a drag where multiple 76 | `mousemove` events are generated between the start and the end of the drag. Interpolation allows 77 | to simulate a more human-like drag. For the interpolation option to work, it needs to define either 78 | `stepWidth` or `stepCount`. If both are given, `stepCount` is ignored. The recognized properties 79 | for the interpolation option object are: 80 | * __stepCount__ _{Numeric}_: Defines the number of steps (interpolation points) to be generated 81 | between the start and the end of the drag. The width between two interpolation points will 82 | be calculated based on the total distance of the drag. Default: `0` 83 | __Note:__ The number of generated `mousemove` events will be one higher as the `stepCount`. 84 | the following picture illustrates this: 85 | 86 | ![The `mousemove` takes place between start, interpolation steps and end.](https://github.com/j-ulrich/jquery-simulate-ext/raw/master/doc/interpolation_stepCount.png) 87 | 88 | * __stepWidth__ _{Numeric}_: Defines the width in pixels between two interpolation points. 89 | When `stepWidth` is given, `stepCount` will be ignored. The number of interpolation points 90 | will be calculated based on the total distance of the drag. Default: `0` 91 | * __duration__ _{Numeric}_: Defines the total duration for the simulation of the drag. When using this 92 | option, the delay between two interpolation steps will be calculated based on the number of steps. 93 | Default: `0` 94 | * __stepDelay__ _{Numeric}_: Defines the delay in milliseconds between two interpolation steps. 95 | When `stepDelay` is greater 0, `duration` will be ignored. Default: `0` 96 | * __shaky__ _{Numeric|Boolean}_: Allows simulation of a shaky drag. With a shaky drag, the interpolation 97 | points do not lie perfectly on the line between the start and end point. This makes the drag 98 | more human-like. The value of the option can either be the number of pixels which the events 99 | may differ from the exact position (the number applies to both x and y direction; the number is 100 | treated as a maximum value, i.e. the actual variation is generated randomly in the range `[0;shaky]` 101 | for each interpolation step) or it can be a boolean where `true` is equal to a value of `1` (pixel) 102 | and `false` is equal to a value of `0`. Default: `false` 103 | * __callback__ _{Function}_: Callback function which is executed as soon as the simulation of the drag 104 | is finished. 105 | 106 | __Note:__ If interpolation is used and either `stepDelay` or `duration` is greater 0, the simulation of the 107 | events takes place asynchronously, i.e. the call to `.simulate()` returns immediately. To detect when 108 | the simulation is finished, use either the `callback` option or trigger on the `simulate-drag` event 109 | (see [below](#events)). 110 | 111 | #### `drop`: #### 112 | * __clickToDrag__ _{Boolean}_: Defines whether the plugin should simulate a whole mouse click instead of 113 | just a `mouseup` event to end the drag. Should be use in combination with the `clickToDrag` option of 114 | the `drag` simulation. Default: `false` 115 | * __callback__ _{Function}_: Callback function which is executed as soon as the simulation of the drop 116 | is finished. 117 | 118 | #### `drag-n-drop`: #### 119 | The `drag-n-drop` simulation accepts all options of both the `drag` and `drop` simulations. However, 120 | the `callback` option behaves like the one from the `drop` simulation and there is one additional option: 121 | * __dropTarget__ _{DOM Element}_: Additionally to the `dragTarget` option (or `dx` and `dy` options) to 122 | define an end position of the drag, the `dropTarget` allows to define an element on whose 123 | center the drop will be simulated. Basically, this produces the same behavior like this: 124 | ``` 125 | $(dragElement).simulate('drag', {dragTarget: dragTarget}); 126 | $(dropTarget).simulate('drop'); 127 | ``` 128 | When `dropTarget` is `undefined`, the drop will occur at the end of the drag. Default: `undefined` 129 | 130 | Events 131 | ------ 132 | The simulation of drag and drop generates the following events: 133 | 134 | - drag: 135 | * `mousedown` (target: dragged element; pageX/Y: center of the dragged element) 136 | * if the option `clickToDrag` is used, then: 137 | * `mouseup` (target: dragged element; pageX/Y: center of the dragged element) 138 | * `click` (target: dragged element; pageX/Y: center of the dragged element) 139 | * one or more `mousemove` events (depending on the `interpolation` options) 140 | * `simulate-drag` (target: dragged element) 141 | - for each successive drag on the same element: 142 | * one or more `mousemove` events (depending on the `interpolation` options) 143 | * `simulate-drag` (target: dragged element) 144 | - drop: 145 | * if the element on which the drop is simulated (called "drop element" here) is not the currently dragged element or `document`, then: 146 | * `mousemove` (target: dragged element if one exists, else drop element; pageX/Y: center of the drop element) 147 | * if the option `clickToDrop` is used, then: 148 | * `mousedown` (target: dragged element if one exists, else drop element; pageX/Y: same as the last, simulated `mousemove` event) 149 | * `mouseup` (target: dragged element if one exists, else drop element; pageX/Y: same as the last, simulated `mousemove` event) 150 | * if the option `clickToDrop` is used, then: 151 | * `click` (target: dragged element if one exists, else drop element; pageX/Y: same as the last, simulated `mousemove` event) 152 | * `simulate-drop` (target: dragged element if one exists, else drop element) 153 | 154 | It should be noted that the target of the simulated events can differ from the target of a real 155 | drag & drop: 156 | * The target of the drag start events (`mousedown`, `mouseup`, `click`) is always the element on which the drag is simulated. 157 | With a real drag, the target would be the topmost, visible element at the position of the `mousedown`. 158 | To see the difference, imagine the following example of two `div` elements: 159 | 160 | ![Two nested divs. The inner div covers the center of the outer div.](https://github.com/j-ulrich/jquery-simulate-ext/raw/master/doc/divs.png) 161 | 162 | When a drag is _simulated_ on the `#outerDiv`, the events will target `#outerDiv` while a real drag 163 | on the center of the `#outerDiv` (black cross in the image) would target the `#innerDiv`. The 164 | way it is implemented makes the behavior of drag simulations more predictable (you explicitly name the 165 | element to be dragged and don't get surprised that another element is dragged because it covers the 166 | center of the element you expect to be dragged). 167 | If you explicitly want to simulate a drag on the element at a given position, retrieve the element 168 | using `document.elementFromPoint()` and simulate the drag on that element. 169 | * In some browsers (Chrome, Firefox, Safari?), the target of the `mousemove` event is "different 170 | from a real drag" if the drag is outside of the viewport of the browser (which is basically not possible 171 | to achieve with a real drag). The reason is that in those browsers, the function `document.elementFromPoint()` 172 | returns `null` when the position is outside of the viewport. In such a case, the events are triggered 173 | on the dragged element. However, this should be a rare use case. 174 | * The same applies to the target of the drop events if the center of the drop target (or the end 175 | of the drag) lies outside of the viewport. In such a case, the drop is simulated on either the dragged 176 | element (if there is a drag going on) or the element on which the drop is simulated. Again, this should 177 | be a rare use case. 178 | 179 | 180 | #### Example: #### 181 | The call 182 | ```javascript 183 | $('#myDiv').simulate("drag-n-drop", { 184 | dx: -71, 185 | dy: 71, 186 | interpolation: { 187 | stepCount: 2 188 | } 189 | }); 190 | ```` 191 | generates the following events (assuming the center of `#myDiv` is at `(299, 1229)`): 192 | ``` 193 | mousedown (which: 1) 194 | mousemove (pageX: 275, pageY: 1253) 195 | mousemove (pageX: 252, pageY: 1276) 196 | mousemove (pageX: 228, pageY: 1300) 197 | simulate-drag 198 | mouseup (which: 1) 199 | click (which: 1) 200 | simulate-drop 201 | ``` 202 | 203 | iframes 204 | ------- 205 | __Note:__ With the [current version](https://github.com/jquery/jquery-ui/blob/485ca7192ac57d018b8ce4f03e7dec6e694a53b7/tests/jquery.simulate.js) 206 | of `jquery.simulate.js`, drag & drop simulation within child-iframes does not work correctly when the parent page is scrolled. 207 | Therefore, the jquery-simulate-ext repository contains a fixed version of `jquery.simulate.js` at 208 | [`libs/jquery.simulate.js`](https://github.com/j-ulrich/jquery-simulate-ext/tree/master/libs/jquery.simulate.js). 209 | 210 | The plugin supports simulation of drag & drop within child-iframes since version 1.1. For the simulation to work, 211 | it is important that the element in the jQuery object is an element from within the iframe, e.g.: 212 | 213 | ```javascript 214 | $( window.frames[0].document.getElementById("elementWithinIFrame") ).simulate("drag-n-drop", {dx: 50}); 215 | ``` 216 | 217 | However, the plugin does __not__ support drag & drop *between elements from different frames*. The element receiving the 218 | drop events will always be from the same document like the dragged element. If you want to drag & drop 219 | between different frames, you have to bind to the drop events (`mouseup` etc.) in the source iframe and 220 | reproduce them in the target iframe manually. 221 | 222 | To trigger on the simulation end events (`simulate-drag` and `simulate-drop`) when simulating on an element 223 | within an iframe, it is necessary to use the same jQuery object which performs the simulation. 224 | For example, consider a parent document which contains a jQuery object called `$p` and an iframe 225 | containing a jQuery object called `$i`. If a drag is simulated on an element within the iframe using 226 | `$p` and we want to trigger on the `simulate-drag` event, we need to use `$p` on the element within 227 | the iframe (or an element above in the DOM of the iframe): 228 | 229 | ```javascript 230 | // Will work: 231 | $p( elementWithinIFrame ).on("simulate-drag", myTrigger); 232 | // Trigger on an element above in the hierarchy like this: 233 | $p( window.frames[0].document ).on("simulate-drag", myTrigger); 234 | 235 | // NOT working: 236 | $i( elementWithinIFrame ).on("simulate-drag", myTrigger); 237 | // NOT working either: 238 | $i( window.frames[0].document ).on("simulate-drag", myTrigger); 239 | // NOT working because the event will not bubble out of the iframe: 240 | $p( document ).on("simulate-drag", myTrigger); 241 | ``` 242 | In other words: the iframe will not see that the interaction was simulated. 243 | 244 | -------------------------------------------------------------------------------- /doc/interpolation_stepCount.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j-ulrich/jquery-simulate-ext/dc912f0ab97737c416599e41cf9735ea1a00236c/doc/interpolation_stepCount.png -------------------------------------------------------------------------------- /doc/key-combo.md: -------------------------------------------------------------------------------- 1 | jQuery Simulate Extensions: Key Combinations 2 | ============================================ 3 | 4 | The key combinations plugin allows simulation of simultaneous key presses, i.e. key combinations. 5 | Typical examples are `"ctrl+c"`, `"ctrl+v"` or `"shift+a"`. 6 | 7 | The plugin simulates the key events for the combination and even inserts the characters into elements 8 | where appropriate (e.g. when the combo is `"shift+a"`, it inserts an `A`). 9 | 10 | __Note:__ The insertion of characters works on **all** elements (not only on input and textarea elements). 11 | In case of elements other than input and textarea, the insertion will change the content of the 12 | element using the `textContent` or `innerText` property. 13 | 14 | __Note:__ The key-combo plugin requires the key-sequence plugin and bililiteRange.js (can be found in the [libs](https://github.com/j-ulrich/jquery-simulate-ext/tree/master/libs) folder of this repository). 15 | 16 | #### Table of Contents #### 17 | - [Usage](#usage) 18 | - [Options](#options) 19 | - [Combo Syntax](#combo-syntax) 20 | - [Events](#events) 21 | 22 | Usage 23 | ----- 24 | The `.simulate()` type parameter to simulate key combinations is `"key-combo"`: `.simulate("key-combo", options)` 25 | 26 | #### Example: #### 27 | ```javascript 28 | $('input[name="testInput"]').simulate("key-combo", {combo: "ctrl+shift+a"}); 29 | ``` 30 | 31 | Options 32 | ------- 33 | * __combo__ _{String}_: A string describing the combination to be simulated. See [below](#combo-syntax) 34 | for a description of the syntax of the combo string. 35 | * __eventsOnly__ _{Boolean}_: Defines whether the characters shall be inserted into the element. If `true`, 36 | the characters will not be inserted into the element and the plugin will only simulate the events 37 | for the key presses. Default: `false` 38 | 39 | Combo Syntax 40 | ------------ 41 | The syntax of the combo string is simple: the keys to be pressed are concatenated with plus characters (`+`) 42 | in between. The key presses are simulated in the order they appear within the string. 43 | For example: `"ctrl+alt+a+b+c"` 44 | 45 | There is no need to escape the plus character if you want to write a combo which includes the plus as a key. 46 | For example: `"ctrl++"` is the syntax for the combo of control and plus. 47 | 48 | The plugin is case-sensitive which means that the `which` property of the `keypress` events is different 49 | for lowercase characters (e.g.: `"a"` gives a value of `97` while `"A"` gives a value of `65`). `keydown` 50 | and `keyup` events are not affected by the case-sensitivity, i.e. they always contain the keycode of 51 | the uppercase character. The only exception from this behavior is when the combo contains the `"shift"` 52 | modifier. In that case, the `which` property always contains the charCode of the uppercase character. 53 | 54 | As already seen in the examples above, the plugin supports some modifier keys: 55 | - `"ctrl"` or `"control"`: Control key 56 | - `"alt"`: Alt key (or option key) 57 | - `"shift"`: Shift key 58 | - `"meta"` or `"command"`: Command key on Apple keyboards 59 | 60 | Since version 1.3.0, the plugin also supports some special keys: 61 | - the arrow keys: 62 | - `"left-arrow"` 63 | - `"right-arrow"` 64 | - `"up-arrow"` 65 | - `"down-arrow"` 66 | - the function keys: `"F1"`, ..., `"F12"` 67 | - other special keys: 68 | - `"enter"` 69 | - `"tab"` or `"tabulator"` 70 | - `"esc"` or `"escape"` 71 | - `"backspace"` 72 | - `"insert"` 73 | - `"delete"` 74 | - `"home"` 75 | - `"end"` 76 | - `"page-up"` 77 | - `"page-down"` 78 | 79 | 80 | **Tip:** In contrast to "normal" key, the modifier keys and the special keys are case insensitive and 81 | dashes can be replaced with underscores. Therefore, `"ctrl+left-arrow"` is the same as 82 | `"Ctrl+Left-Arrow"` and `"CTRL+LEFT_ARROW"` and so on. 83 | 84 | Events 85 | ------ 86 | The plugin generates the following events to simulate the key combination: 87 | 88 | 1. It generates one `keydown` event for every key in the combo. 89 | 2. If the key corresponding to the last `keydown` event is a printable character, the plugin generates 90 | one `keypress` event for that key. 91 | 3. It generates one `keyup` event for every key in the combo. 92 | 93 | This simulated behavior differs from the native behavior that some browsers show. For example, 94 | InternetExplorer and Chrome do not generate keypress events for native key combos that contain the 95 | control or alt (or meta?) modifier. However, unifying the event generation in the plugin makes the 96 | plugin easier to maintain and should generally not be a problem since it's just additional `keypress` 97 | events that are generated. 98 | 99 | Although the event generation partly differs from the native behavior, the values of the `keyCode`, 100 | `charCode` and `which` properties of the events should be equal to the corresponding values of 101 | native (i.e. non-simulated) events for all browsers. 102 | 103 | #### Example: #### 104 | The plugin generates the following events for the combo `"ctrl+alt+a+f"`: 105 | 106 | InternetExplorer and Opera: 107 | ``` 108 | keydown (which: 17, keyCode: 17, charCode: undefined, modifiers: ctrl) // ctrl 109 | keydown (which: 18, keyCode: 18, charCode: undefined, modifiers: alt+ctrl) // alt 110 | keydown (which: 65, keyCode: 65, charCode: undefined, modifiers: alt+ctrl) // a 111 | keypress (which: 97, keyCode: 97, charCode: undefined, modifiers: alt+ctrl) // a 112 | keydown (which: 70, keyCode: 70, charCode: undefined, modifiers: alt+ctrl) // f 113 | keypress (which: 102, keyCode: 102, charCode: undefined, modifiers: alt+ctrl) // f 114 | keyup (which: 70, keyCode: 70, charCode: undefined, modifiers: alt+ctrl) // f 115 | keyup (which: 65, keyCode: 65, charCode: undefined, modifiers: alt+ctrl) // a 116 | keyup (which: 18, keyCode: 18, charCode: undefined, modifiers: ctrl) // alt 117 | keyup (which: 17, keyCode: 17, charCode: undefined) // ctrl 118 | ``` 119 | 120 | Firefox: 121 | ``` 122 | keydown (which: 17, keyCode: 17, charCode: 0, modifiers: ctrl) // ctrl 123 | keydown (which: 18, keyCode: 18, charCode: 0, modifiers: alt+ctrl) // alt 124 | keydown (which: 65, keyCode: 65, charCode: 0, modifiers: alt+ctrl) // a 125 | keypress (which: 97, keyCode: 97, charCode: 97, modifiers: alt+ctrl) // a 126 | keydown (which: 70, keyCode: 70, charCode: 0, modifiers: alt+ctrl) // f 127 | keypress (which: 102, keyCode: 102, charCode: 102, modifiers: alt+ctrl) // f 128 | keyup (which: 70, keyCode: 70, charCode: 0, modifiers: alt+ctrl) // f 129 | keyup (which: 65, keyCode: 65, charCode: 0, modifiers: alt+ctrl) // a 130 | keyup (which: 18, keyCode: 18, charCode: 0, modifiers: ctrl) // alt 131 | keyup (which: 17, keyCode: 17, charCode: 0) // ctrl 132 | ``` 133 | 134 | Chrome: 135 | ``` 136 | keydown (which: 17, keyCode: 17, charCode: undefined, modifiers: ctrl) // ctrl 137 | keydown (which: 18, keyCode: 18, charCode: undefined, modifiers: alt+ctrl) // alt 138 | keydown (which: 65, keyCode: 65, charCode: undefined, modifiers: alt+ctrl) // a 139 | keypress (which: 97, keyCode: 97, charCode: 97, modifiers: alt+ctrl) // a 140 | keydown (which: 70, keyCode: 70, charCode: undefined, modifiers: alt+ctrl) // f 141 | keypress (which: 102, keyCode: 102, charCode: 102, modifiers: alt+ctrl) // f 142 | keyup (which: 70, keyCode: 70, charCode: undefined, modifiers: alt+ctrl) // f 143 | keyup (which: 65, keyCode: 65, charCode: undefined, modifiers: alt+ctrl) // a 144 | keyup (which: 18, keyCode: 18, charCode: undefined, modifiers: ctrl) // alt 145 | keyup (which: 17, keyCode: 17, charCode: undefined) // ctrl 146 | ``` -------------------------------------------------------------------------------- /doc/key-sequence.md: -------------------------------------------------------------------------------- 1 | jQuery Simulate Extensions: Key Sequences 2 | ========================================= 3 | 4 | The key sequences plugin allows simulation of successive key presses similar to typing text. 5 | 6 | The plugin simulates the key events for the text and even inserts the characters into elements. 7 | 8 | __Note:__ The insertion of characters works on **all** elements (not only on input and textarea elements). 9 | In case of elements other than input and textarea, the insertion will change the content of the 10 | element using the `textContent` or `innerText` property. 11 | 12 | The plugin is based on the sendkeys plugin by Daniel Wachsstock (http://bililite.com/blog/2008/08/20/the-fnsendkeys-plugin/). 13 | 14 | __Note:__ The key-sequence plugin requires bililiteRange.js (can be found in the [libs](https://github.com/j-ulrich/jquery-simulate-ext/tree/master/libs) folder of this repository). 15 | 16 | #### Table of Contents #### 17 | - [Usage](#usage) 18 | - [Options](#options) 19 | - [Special Sequences](#special-sequences) 20 | - [Events](#events) 21 | - [Special Characters and keyCodes](#special-characters-and-keycodes) 22 | - [Tips & Tricks](#tips--tricks) 23 | 24 | 25 | Usage 26 | ----- 27 | The `.simulate()` type parameter to simulate key sequences is `"key-sequence"`: `.simulate("key-sequence", options)` 28 | 29 | #### Example: #### 30 | ```javascript 31 | $('input[name="testInput"]').simulate("key-sequence", {sequence: "This is a test!"}); 32 | ``` 33 | 34 | Options 35 | ------- 36 | * __sequence__ _{String}_: The key sequence (text) to be simulated. 37 | * __delay__ _{Numeric}_: Delay between the key presses in milliseconds. If delay is greater 0, the key presses 38 | are simulated asynchronously, i.e. the call to `.simulate()` returns immediately. To detect when the 39 | simulation is finished, either use the `simulate-keySequence` event (see [below](#events)) or 40 | provide a `callback` (see below). Default: `0` 41 | * __triggerKeyEvents__ _{Boolean}_: Defines whether the plugin generates events for the key presses. If 42 | `false`, the plugin only inserts the characters into the target element. Default: `true` 43 | * __callback__ _{Function}_: Callback function which is executed as soon as the simulation of the key presses 44 | is finished. 45 | 46 | Special Sequences 47 | ----------------- 48 | Despite of the printable characters, the plugin supports a set of special sequences to manipulate the 49 | content of the element: 50 | 51 | * `{backspace}`: Deletes the currently selected characters or the character in front of the selection cursor. 52 | * `{del}`: Deletes the currently selected characters or the character behind the selection cursor. 53 | * `{rightarrow}`: Moves the selection cursor one character to the right. Doesn't work in InternetExplorer. 54 | Doesn't work correctly in Opera on Windows (cannot be used to move to the next line). 55 | * `{leftarrow}`: Moves the selection cursor one character to the left. Doesn't work in InternetExplorer. 56 | * `{selectall}`: Selects all characters. 57 | * `{enter}`: Inserts a line break. Doesn't work correctly in Opera on Windows (the line break is 58 | inserted behind the sequence). 59 | * `{{}`: Inserts a literal `{` 60 | 61 | Except for the `{selectall}` sequence, the special sequences also generate `keydown` and `keyup` 62 | events for the keys corresponding to the special sequence. The `{enter}` sequence also generates a `keypress` 63 | event. 64 | 65 | #### Example: #### 66 | The sequence `"as{leftarrow}{del}bc"` results in the insertion of the text `"abc"` and generates 67 | the following events: 68 | 69 | ``` 70 | keydown (which: 65) // a 71 | keypress (which: 97) // a 72 | keyup (which: 65) // a 73 | keydown (which: 83) // s 74 | keypress (which: 115) // s 75 | keyup (which: 83) // s 76 | keydown (which: 37) // {leftarrow} 77 | keyup (which: 37) // {leftarrow} 78 | keydown (which: 46) // {del} 79 | keyup (which: 46) // {del} 80 | keydown (which: 66) // b 81 | keypress (which: 98) // b 82 | keyup (which: 66) // b 83 | keydown (which: 67) // c 84 | keypress (which: 99) // c 85 | keyup (which: 67) // c 86 | ``` 87 | 88 | Events 89 | ------ 90 | For every key in the sequence (exceptions for special sequences see [above](#special-sequences)), 91 | the plugin generates the following events: 92 | 93 | 1. `keydown` 94 | 2. `keypress` if the key is printable 95 | 3. `keyup` 96 | 97 | The values of the `keyCode`, `charCode` and `which` properties of the events should be equal to 98 | the corresponding values of native (i.e. non-simulated) events for all browsers. 99 | As a rule of thumb: `keyCode` and `which` are equal for all events. For `keypress` events, 100 | they are the ASCII code of the character. For `keydown` and `keyup` events, they are a "key code" 101 | which differs among browsers. See [below](#special-characters-and-keycodes) for more details. 102 | 103 | Additionally, the plugin generates a `simulate-keySequence` event as soon as the simulation of the 104 | key sequence is finished. This `simulate-keySequence` has a property called `sequence` which 105 | contains the key sequence that has been simulated. 106 | 107 | #### Example: #### 108 | The plugin generates the following events for the sequence `"Test!"` (the `keyCode` and `charCode` 109 | properties have been omitted): 110 | 111 | ``` 112 | keydown (which: 84) // T 113 | keypress (which: 84) // T 114 | keyup (which: 84) // T 115 | keydown (which: 69) // e 116 | keypress (which: 101) // e 117 | keyup (which: 69) // e 118 | keydown (which: 83) // s 119 | keypress (which: 115) // s 120 | keyup (which: 83) // s 121 | keydown (which: 84) // t 122 | keypress (which: 116) // t 123 | keyup (which: 84) // t 124 | keydown (which: 49) // ! 125 | keypress (which: 33) // ! 126 | keyup (which: 49) // ! 127 | simulate-keySequence (sequence: "Test!") 128 | ``` 129 | 130 | Special Characters and keyCodes 131 | ------------------------------- 132 | The values of the `keyCode` and `which` properties for `keyup` and `keydown` events are not 133 | consistent across browsers. For the alphanumeric characters and for "meta" keys 134 | (Control, Space, Esc, leftarrow, ...), the values are consistent but the values differ for special 135 | characters. 136 | To keep the plugin simple, the current implementation assumes a US keyboard, it uses the same key 137 | codes like InternetExplorer for all browsers and the support of special characters is limited to 138 | the most common ones (see [the code](https://github.com/j-ulrich/jquery-simulate-ext/tree/master/src/jquery.simulate.key-sequence.js#L172-211) 139 | for the complete list). However, this might change in future versions. 140 | For more information about this topic see http://unixpapa.com/js/key.html. 141 | 142 | Tips & Tricks 143 | ------------- 144 | 145 | #### Simulating a text cursor #### 146 | When simulating a key-sequence in a non-input element (e.g. a `div` or `p` element), it is possible 147 | to simulate a text cursor like this: 148 | ```javascript 149 | $('div#myDiv').simulate("key-sequence", {sequence: "|{leftarrow}This is a test!{del}", delay: 100}); 150 | ``` 151 | -------------------------------------------------------------------------------- /doc/simulate.md: -------------------------------------------------------------------------------- 1 | jQuery Simulate 2 | =============== 3 | 4 | The jQuery simulate plugin allows to simulate user interaction using mouse and key events. 5 | 6 | #### Table of Contents #### 7 | - [Usage](#usage) 8 | - [Options](#options) 9 | - [Key Events](#key-events) 10 | - [Mouse Events](#mouse-events) 11 | 12 | Usage 13 | ----- 14 | The simulations are executed by calling the `.simulate()` function on a jQuery object. The simulation 15 | is then executed on all elements in the collection of the jQuery object. 16 | 17 | - Synopsis: `.simulate(type, options)` 18 | - Parameters: 19 | * __type__ _{String}_: The type of the interaction to be simulated. 20 | * __options__ _{Object}_: An option object containing the options for the action to be simulated. 21 | Default: `undefined` 22 | 23 | The possible types of simulated actions are: 24 | 25 | - From the simulate plugin: 26 | - Mouse Events: `"mousemove"`, `"mousedown"`, `"mouseup"`, `"click"`, `dblclick"`, 27 | `"mouseover"`, `"mouseout"`, `"mouseenter"`, `"mouseleave"`, `"contextmenu"` 28 | - Key Events: `"keydown"`, `"keyup"`, `"keypress"` 29 | - `"focus"` 30 | - `"blur"` 31 | - From the simulate-ext plugins: 32 | - Drag & Drop: `"drag-n-drop"`, `"drag"`, `"drop"` 33 | - `"key-sequence"` 34 | - `"key-combo"` 35 | 36 | Some of the simulated actions also trigger default actions (`"click"`, `"focus"` and `"blur"`) while 37 | the others do not (e.g. neither `"keypress"` nor `"keyup"` will insert a character into an input or 38 | textarea element). It also depends on the used browser whether the default action will be triggered. 39 | However, at least for the character insertions, the `key-sequence` plugin from simulate-ext provides 40 | simulation of key presses including insertion of characters. 41 | 42 | Options 43 | ------- 44 | For the options of the simulate-ext plugins, see other files in the [doc folder](https://github.com/j-ulrich/jquery-simulate-ext/tree/master/doc). 45 | 46 | #### Key Events: #### 47 | For more information about the options, see https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent. 48 | * __bubbles__ _{Boolean}_: Defines whether the event bubbles up the DOM tree. Default: `true` 49 | * __cancelable__ _{Boolean}_: Defines whether the default action of the event can be canceled. Default: `true` 50 | * __keyCode__ _{Numeric}_: The key code of the key to be pressed. The key code depends on the browser 51 | being used. Default: `0` 52 | * __charCode__ _{Numeric}_: The character code of the key to be pressed. This property is required 53 | for some combinations of browser and key event. Default: `undefined` 54 | 55 | __Note:__ For more information about the `keyCode` and `charCode` properties, see http://unixpapa.com/js/key.html. 56 | 57 | * __ctrlKey__ _{Boolean}_: Defines whether the control modifier should be marked as pressed. Default: `false` 58 | * __shiftKey__ _{Boolean}_: Defines whether the shift modifier should be marked as pressed. Default: `false` 59 | * __altKey__ _{Boolean}_: Defines whether the alt modifier should be marked as pressed. Default: `false` 60 | * __metaKey__ _{Boolean}_: Defines whether the meta modifier should be marked as pressed. Default: `false` 61 | 62 | __Note:__ If you want to simulate a real key combination (i.e. including the simulation of the key presses 63 | of the modifier keys), you should use the `key-combo` plugin instead. 64 | 65 | 66 | #### Mouse Events: #### 67 | For more information about the options, see https://developer.mozilla.org/en-US/docs/DOM/MouseEvent. 68 | * __bubbles__ _{Boolean}_: Defines whether the event bubbles up the DOM tree. Default: `true` 69 | * __cancelable__ _{Boolean}_: Defines whether the default action of the event can be canceled. 70 | Default: `true` for all events except `mousemove`, for `mousemove` it's `false` 71 | * __clientX__ _{Numeric}_: The x position where the event is simulated, relative to the upper left corner 72 | of the browser viewport. Default: `1` 73 | * __clientY__ _{Numeric}_: The y position where the event is simulated, relative to the upper left corner 74 | of the browser viewport. Default: `1` 75 | * __button__ _{Numeric}_: The mouse button which should be marked as pressed. Default: `0` (i.e. left mouse button) 76 | * __ctrlKey__ _{Boolean}_: Defines whether the control modifier should be marked as pressed. Default: `false` 77 | * __shiftKey__ _{Boolean}_: Defines whether the shift modifier should be marked as pressed. Default: `false` 78 | * __altKey__ _{Boolean}_: Defines whether the alt modifier should be marked as pressed. Default: `false` 79 | * __metaKey__ _{Boolean}_: Defines whether the meta modifier should be marked as pressed. Default: `false` 80 | -------------------------------------------------------------------------------- /libs/bililiteRange.js: -------------------------------------------------------------------------------- 1 | // Cross-broswer implementation of text ranges and selections 2 | // documentation: http://bililite.com/blog/2011/01/17/cross-browser-text-ranges-and-selections/ 3 | // Version: 1.1 4 | // Copyright (c) 2010 Daniel Wachsstock 5 | // MIT license: 6 | // Permission is hereby granted, free of charge, to any person 7 | // obtaining a copy of this software and associated documentation 8 | // files (the "Software"), to deal in the Software without 9 | // restriction, including without limitation the rights to use, 10 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the 12 | // Software is furnished to do so, subject to the following 13 | // conditions: 14 | 15 | // The above copyright notice and this permission notice shall be 16 | // included in all copies or substantial portions of the Software. 17 | 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | // OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | (function(){ 28 | 29 | bililiteRange = function(el, debug){ 30 | var ret; 31 | if (debug){ 32 | ret = new NothingRange(); // Easier to force it to use the no-selection type than to try to find an old browser 33 | }else if (document.selection){ 34 | // Internet Explorer 35 | ret = new IERange(); 36 | }else if (window.getSelection && el.setSelectionRange){ 37 | // Standards. Element is an input or textarea 38 | ret = new InputRange(); 39 | }else if (window.getSelection){ 40 | // Standards, with any other kind of element 41 | ret = new W3CRange(); 42 | }else{ 43 | // doesn't support selection 44 | ret = new NothingRange(); 45 | } 46 | ret._el = el; 47 | ret._textProp = textProp(el); 48 | ret._bounds = [0, ret.length()]; 49 | return ret; 50 | }; 51 | 52 | function textProp(el){ 53 | // returns the property that contains the text of the element 54 | if (typeof el.value != 'undefined') return 'value'; 55 | if (typeof el.text != 'undefined') return 'text'; 56 | if (typeof el.textContent != 'undefined') return 'textContent'; 57 | return 'innerText'; 58 | } 59 | 60 | // base class 61 | function Range(){} 62 | Range.prototype = { 63 | length: function() { 64 | return this._el[this._textProp].replace(/\r/g, '').length; // need to correct for IE's CrLf weirdness 65 | }, 66 | bounds: function(s){ 67 | if (s === 'all'){ 68 | this._bounds = [0, this.length()]; 69 | }else if (s === 'start'){ 70 | this._bounds = [0, 0]; 71 | }else if (s === 'end'){ 72 | this._bounds = [this.length(), this.length()]; 73 | }else if (s === 'selection'){ 74 | this.bounds ('all'); // first select the whole thing for constraining 75 | this._bounds = this._nativeSelection(); 76 | }else if (s){ 77 | this._bounds = s; // don't error check now; the element may change at any moment, so constrain it when we need it. 78 | }else{ 79 | var b = [ 80 | Math.max(0, Math.min (this.length(), this._bounds[0])), 81 | Math.max(0, Math.min (this.length(), this._bounds[1])) 82 | ]; 83 | return b; // need to constrain it to fit 84 | } 85 | return this; // allow for chaining 86 | }, 87 | select: function(){ 88 | this._nativeSelect(this._nativeRange(this.bounds())); 89 | return this; // allow for chaining 90 | }, 91 | text: function(text, select){ 92 | if (arguments.length){ 93 | this._nativeSetText(text, this._nativeRange(this.bounds())); 94 | if (select == 'start'){ 95 | this.bounds ([this._bounds[0], this._bounds[0]]); 96 | this.select(); 97 | }else if (select == 'end'){ 98 | this.bounds ([this._bounds[0]+text.length, this._bounds[0]+text.length]); 99 | this.select(); 100 | }else if (select == 'all'){ 101 | this.bounds ([this._bounds[0], this._bounds[0]+text.length]); 102 | this.select(); 103 | } 104 | return this; // allow for chaining 105 | }else{ 106 | return this._nativeGetText(this._nativeRange(this.bounds())); 107 | } 108 | }, 109 | insertEOL: function (){ 110 | this._nativeEOL(); 111 | this._bounds = [this._bounds[0]+1, this._bounds[0]+1]; // move past the EOL marker 112 | return this; 113 | } 114 | }; 115 | 116 | 117 | function IERange(){} 118 | IERange.prototype = new Range(); 119 | IERange.prototype._nativeRange = function (bounds){ 120 | var rng; 121 | if (this._el.tagName == 'INPUT'){ 122 | // IE 8 is very inconsistent; textareas have createTextRange but it doesn't work 123 | rng = this._el.createTextRange(); 124 | }else{ 125 | rng = document.body.createTextRange (); 126 | rng.moveToElementText(this._el); 127 | } 128 | if (bounds){ 129 | if (bounds[1] < 0) bounds[1] = 0; // IE tends to run elements out of bounds 130 | if (bounds[0] > this.length()) bounds[0] = this.length(); 131 | if (bounds[1] < rng.text.replace(/\r/g, '').length){ // correct for IE's CrLf wierdness 132 | // block-display elements have an invisible, uncounted end of element marker, so we move an extra one and use the current length of the range 133 | rng.moveEnd ('character', -1); 134 | rng.moveEnd ('character', bounds[1]-rng.text.replace(/\r/g, '').length); 135 | } 136 | if (bounds[0] > 0) rng.moveStart('character', bounds[0]); 137 | } 138 | return rng; 139 | }; 140 | IERange.prototype._nativeSelect = function (rng){ 141 | rng.select(); 142 | }; 143 | IERange.prototype._nativeSelection = function (){ 144 | // returns [start, end] for the selection constrained to be in element 145 | var rng = this._nativeRange(); // range of the element to constrain to 146 | var len = this.length(); 147 | if (document.selection.type != 'Text') return [len, len]; // append to the end 148 | var sel = document.selection.createRange(); 149 | try{ 150 | return [ 151 | iestart(sel, rng), 152 | ieend (sel, rng) 153 | ]; 154 | }catch (e){ 155 | // IE gets upset sometimes about comparing text to input elements, but the selections cannot overlap, so make a best guess 156 | return (sel.parentElement().sourceIndex < this._el.sourceIndex) ? [0,0] : [len, len]; 157 | } 158 | }; 159 | IERange.prototype._nativeGetText = function (rng){ 160 | return rng.text.replace(/\r/g, ''); // correct for IE's CrLf weirdness 161 | }; 162 | IERange.prototype._nativeSetText = function (text, rng){ 163 | rng.text = text; 164 | }; 165 | IERange.prototype._nativeEOL = function(){ 166 | if (typeof this._el.value != 'undefined'){ 167 | this.text('\n'); // for input and textarea, insert it straight 168 | }else{ 169 | this._nativeRange(this.bounds()).pasteHTML('
'); 170 | } 171 | }; 172 | // IE internals 173 | function iestart(rng, constraint){ 174 | // returns the position (in character) of the start of rng within constraint. If it's not in constraint, returns 0 if it's before, length if it's after 175 | var len = constraint.text.replace(/\r/g, '').length; // correct for IE's CrLf wierdness 176 | if (rng.compareEndPoints ('StartToStart', constraint) <= 0) return 0; // at or before the beginning 177 | if (rng.compareEndPoints ('StartToEnd', constraint) >= 0) return len; 178 | for (var i = 0; rng.compareEndPoints ('StartToStart', constraint) > 0; ++i, rng.moveStart('character', -1)); 179 | return i; 180 | } 181 | function ieend (rng, constraint){ 182 | // returns the position (in character) of the end of rng within constraint. If it's not in constraint, returns 0 if it's before, length if it's after 183 | var len = constraint.text.replace(/\r/g, '').length; // correct for IE's CrLf wierdness 184 | if (rng.compareEndPoints ('EndToEnd', constraint) >= 0) return len; // at or after the end 185 | if (rng.compareEndPoints ('EndToStart', constraint) <= 0) return 0; 186 | for (var i = 0; rng.compareEndPoints ('EndToStart', constraint) > 0; ++i, rng.moveEnd('character', -1)); 187 | return i; 188 | } 189 | 190 | // an input element in a standards document. "Native Range" is just the bounds array 191 | function InputRange(){} 192 | InputRange.prototype = new Range(); 193 | InputRange.prototype._nativeRange = function(bounds) { 194 | return bounds || [0, this.length()]; 195 | }; 196 | InputRange.prototype._nativeSelect = function (rng){ 197 | this._el.setSelectionRange(rng[0], rng[1]); 198 | }; 199 | InputRange.prototype._nativeSelection = function(){ 200 | return [this._el.selectionStart, this._el.selectionEnd]; 201 | }; 202 | InputRange.prototype._nativeGetText = function(rng){ 203 | return this._el.value.substring(rng[0], rng[1]); 204 | }; 205 | InputRange.prototype._nativeSetText = function(text, rng){ 206 | var val = this._el.value; 207 | this._el.value = val.substring(0, rng[0]) + text + val.substring(rng[1]); 208 | }; 209 | InputRange.prototype._nativeEOL = function(){ 210 | this.text('\n'); 211 | }; 212 | 213 | function W3CRange(){} 214 | W3CRange.prototype = new Range(); 215 | W3CRange.prototype._nativeRange = function (bounds){ 216 | var rng = document.createRange(); 217 | rng.selectNodeContents(this._el); 218 | if (bounds){ 219 | w3cmoveBoundary (rng, bounds[0], true, this._el); 220 | rng.collapse (true); 221 | w3cmoveBoundary (rng, bounds[1]-bounds[0], false, this._el); 222 | } 223 | return rng; 224 | }; 225 | W3CRange.prototype._nativeSelect = function (rng){ 226 | window.getSelection().removeAllRanges(); 227 | window.getSelection().addRange (rng); 228 | }; 229 | W3CRange.prototype._nativeSelection = function (){ 230 | // returns [start, end] for the selection constrained to be in element 231 | var rng = this._nativeRange(); // range of the element to constrain to 232 | if (window.getSelection().rangeCount == 0) return [this.length(), this.length()]; // append to the end 233 | var sel = window.getSelection().getRangeAt(0); 234 | return [ 235 | w3cstart(sel, rng), 236 | w3cend (sel, rng) 237 | ]; 238 | }; 239 | W3CRange.prototype._nativeGetText = function (rng){ 240 | return rng.toString(); 241 | }; 242 | W3CRange.prototype._nativeSetText = function (text, rng){ 243 | rng.deleteContents(); 244 | rng.insertNode (document.createTextNode(text)); 245 | this._el.normalize(); // merge the text with the surrounding text 246 | }; 247 | W3CRange.prototype._nativeEOL = function(){ 248 | var rng = this._nativeRange(this.bounds()); 249 | rng.deleteContents(); 250 | var br = document.createElement('br'); 251 | br.setAttribute ('_moz_dirty', ''); // for Firefox 252 | rng.insertNode (br); 253 | rng.insertNode (document.createTextNode('\n')); 254 | rng.collapse (false); 255 | }; 256 | // W3C internals 257 | function nextnode (node, root){ 258 | // in-order traversal 259 | // we've already visited node, so get kids then siblings 260 | if (node.firstChild) return node.firstChild; 261 | if (node.nextSibling) return node.nextSibling; 262 | if (node===root) return null; 263 | while (node.parentNode){ 264 | // get uncles 265 | node = node.parentNode; 266 | if (node == root) return null; 267 | if (node.nextSibling) return node.nextSibling; 268 | } 269 | return null; 270 | } 271 | function w3cmoveBoundary (rng, n, bStart, el){ 272 | // move the boundary (bStart == true ? start : end) n characters forward, up to the end of element el. Forward only! 273 | // if the start is moved after the end, then an exception is raised 274 | if (n <= 0) return; 275 | var node = rng[bStart ? 'startContainer' : 'endContainer']; 276 | if (node.nodeType == 3){ 277 | // we may be starting somewhere into the text 278 | n += rng[bStart ? 'startOffset' : 'endOffset']; 279 | } 280 | while (node){ 281 | if (node.nodeType == 3){ 282 | if (n <= node.nodeValue.length){ 283 | rng[bStart ? 'setStart' : 'setEnd'](node, n); 284 | // special case: if we end next to a
, include that node. 285 | if (n == node.nodeValue.length){ 286 | // skip past zero-length text nodes 287 | for (var next = nextnode (node, el); next && next.nodeType==3 && next.nodeValue.length == 0; next = nextnode(next, el)){ 288 | rng[bStart ? 'setStartAfter' : 'setEndAfter'](next); 289 | } 290 | if (next && next.nodeType == 1 && next.nodeName == "BR") rng[bStart ? 'setStartAfter' : 'setEndAfter'](next); 291 | } 292 | return; 293 | }else{ 294 | rng[bStart ? 'setStartAfter' : 'setEndAfter'](node); // skip past this one 295 | n -= node.nodeValue.length; // and eat these characters 296 | } 297 | } 298 | node = nextnode (node, el); 299 | } 300 | } 301 | var START_TO_START = 0; // from the w3c definitions 302 | var START_TO_END = 1; 303 | var END_TO_END = 2; 304 | var END_TO_START = 3; 305 | // from the Mozilla documentation, for range.compareBoundaryPoints(how, sourceRange) 306 | // -1, 0, or 1, indicating whether the corresponding boundary-point of range is respectively before, equal to, or after the corresponding boundary-point of sourceRange. 307 | // * Range.END_TO_END compares the end boundary-point of sourceRange to the end boundary-point of range. 308 | // * Range.END_TO_START compares the end boundary-point of sourceRange to the start boundary-point of range. 309 | // * Range.START_TO_END compares the start boundary-point of sourceRange to the end boundary-point of range. 310 | // * Range.START_TO_START compares the start boundary-point of sourceRange to the start boundary-point of range. 311 | function w3cstart(rng, constraint){ 312 | if (rng.compareBoundaryPoints (START_TO_START, constraint) <= 0) return 0; // at or before the beginning 313 | if (rng.compareBoundaryPoints (END_TO_START, constraint) >= 0) return constraint.toString().length; 314 | rng = rng.cloneRange(); // don't change the original 315 | rng.setEnd (constraint.endContainer, constraint.endOffset); // they now end at the same place 316 | return constraint.toString().length - rng.toString().length; 317 | } 318 | function w3cend (rng, constraint){ 319 | if (rng.compareBoundaryPoints (END_TO_END, constraint) >= 0) return constraint.toString().length; // at or after the end 320 | if (rng.compareBoundaryPoints (START_TO_END, constraint) <= 0) return 0; 321 | rng = rng.cloneRange(); // don't change the original 322 | rng.setStart (constraint.startContainer, constraint.startOffset); // they now start at the same place 323 | return rng.toString().length; 324 | } 325 | 326 | function NothingRange(){} 327 | NothingRange.prototype = new Range(); 328 | NothingRange.prototype._nativeRange = function(bounds) { 329 | return bounds || [0,this.length()]; 330 | }; 331 | NothingRange.prototype._nativeSelect = function (rng){ // do nothing 332 | }; 333 | NothingRange.prototype._nativeSelection = function(){ 334 | return [0,0]; 335 | }; 336 | NothingRange.prototype._nativeGetText = function (rng){ 337 | return this._el[this._textProp].substring(rng[0], rng[1]); 338 | }; 339 | NothingRange.prototype._nativeSetText = function (text, rng){ 340 | var val = this._el[this._textProp]; 341 | this._el[this._textProp] = val.substring(0, rng[0]) + text + val.substring(rng[1]); 342 | }; 343 | NothingRange.prototype._nativeEOL = function(){ 344 | this.text('\n'); 345 | }; 346 | 347 | })(); -------------------------------------------------------------------------------- /libs/jquery.simulate.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Simulate v0.0.1 - simulate browser mouse and keyboard events 3 | * https://github.com/jquery/jquery-simulate 4 | * 5 | * Copyright 2012 jQuery Foundation and other contributors 6 | * Released under the MIT license. 7 | * http://jquery.org/license 8 | * 9 | * Date: Sun Dec 9 12:15:33 2012 -0500 10 | */ 11 | 12 | ;(function( $, undefined ) { 13 | "use strict"; 14 | 15 | var rkeyEvent = /^key/, 16 | rmouseEvent = /^(?:mouse|contextmenu)|click/, 17 | rdocument = /\[object (?:HTML)?Document\]/; 18 | 19 | function isDocument(ele) { 20 | return rdocument.test(Object.prototype.toString.call(ele)); 21 | } 22 | 23 | function windowOfDocument(doc) { 24 | for (var i=0; i < window.frames.length; i+=1) { 25 | if (window.frames[i] && window.frames[i].document === doc) { 26 | return window.frames[i]; 27 | } 28 | } 29 | return window; 30 | } 31 | 32 | $.fn.simulate = function( type, options ) { 33 | return this.each(function() { 34 | new $.simulate( this, type, options ); 35 | }); 36 | }; 37 | 38 | $.simulate = function( elem, type, options ) { 39 | var method = $.camelCase( "simulate-" + type ); 40 | 41 | this.target = elem; 42 | this.options = options || {}; 43 | 44 | if ( this[ method ] ) { 45 | this[ method ](); 46 | } else { 47 | this.simulateEvent( elem, type, this.options ); 48 | } 49 | }; 50 | 51 | $.extend( $.simulate, { 52 | 53 | keyCode: { 54 | BACKSPACE: 8, 55 | COMMA: 188, 56 | DELETE: 46, 57 | DOWN: 40, 58 | END: 35, 59 | ENTER: 13, 60 | ESCAPE: 27, 61 | HOME: 36, 62 | LEFT: 37, 63 | NUMPAD_ADD: 107, 64 | NUMPAD_DECIMAL: 110, 65 | NUMPAD_DIVIDE: 111, 66 | NUMPAD_ENTER: 108, 67 | NUMPAD_MULTIPLY: 106, 68 | NUMPAD_SUBTRACT: 109, 69 | PAGE_DOWN: 34, 70 | PAGE_UP: 33, 71 | PERIOD: 190, 72 | RIGHT: 39, 73 | SPACE: 32, 74 | TAB: 9, 75 | UP: 38 76 | }, 77 | 78 | buttonCode: { 79 | LEFT: 0, 80 | MIDDLE: 1, 81 | RIGHT: 2 82 | } 83 | }); 84 | 85 | $.extend( $.simulate.prototype, { 86 | 87 | simulateEvent: function( elem, type, options ) { 88 | var event = this.createEvent( type, options ); 89 | this.dispatchEvent( elem, type, event, options ); 90 | }, 91 | 92 | createEvent: function( type, options ) { 93 | if ( rkeyEvent.test( type ) ) { 94 | return this.keyEvent( type, options ); 95 | } 96 | 97 | if ( rmouseEvent.test( type ) ) { 98 | return this.mouseEvent( type, options ); 99 | } 100 | }, 101 | 102 | mouseEvent: function( type, options ) { 103 | var event, 104 | eventDoc, 105 | doc = isDocument(this.target)? this.target : (this.target.ownerDocument || document), 106 | docEle, 107 | body; 108 | 109 | 110 | options = $.extend({ 111 | bubbles: true, 112 | cancelable: (type !== "mousemove"), 113 | view: windowOfDocument(doc), 114 | detail: 0, 115 | screenX: 0, 116 | screenY: 0, 117 | clientX: 1, 118 | clientY: 1, 119 | ctrlKey: false, 120 | altKey: false, 121 | shiftKey: false, 122 | metaKey: false, 123 | button: 0, 124 | relatedTarget: undefined 125 | }, options ); 126 | 127 | 128 | 129 | if ( doc.createEvent ) { 130 | event = doc.createEvent( "MouseEvents" ); 131 | event.initMouseEvent( type, options.bubbles, options.cancelable, 132 | options.view, options.detail, 133 | options.screenX, options.screenY, options.clientX, options.clientY, 134 | options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 135 | options.button, options.relatedTarget || doc.body.parentNode ); 136 | 137 | // IE 9+ creates events with pageX and pageY set to 0. 138 | // Trying to modify the properties throws an error, 139 | // so we define getters to return the correct values. 140 | if ( event.pageX === 0 && event.pageY === 0 && Object.defineProperty ) { 141 | eventDoc = isDocument(event.relatedTarget)? event.relatedTarget : (event.relatedTarget.ownerDocument || document); 142 | docEle = eventDoc.documentElement; 143 | body = eventDoc.body; 144 | 145 | Object.defineProperty( event, "pageX", { 146 | get: function() { 147 | return options.clientX + 148 | ( docEle && docEle.scrollLeft || body && body.scrollLeft || 0 ) - 149 | ( docEle && docEle.clientLeft || body && body.clientLeft || 0 ); 150 | } 151 | }); 152 | Object.defineProperty( event, "pageY", { 153 | get: function() { 154 | return options.clientY + 155 | ( docEle && docEle.scrollTop || body && body.scrollTop || 0 ) - 156 | ( docEle && docEle.clientTop || body && body.clientTop || 0 ); 157 | } 158 | }); 159 | } 160 | } else if ( doc.createEventObject ) { 161 | event = doc.createEventObject(); 162 | $.extend( event, options ); 163 | // standards event.button uses constants defined here: http://msdn.microsoft.com/en-us/library/ie/ff974877(v=vs.85).aspx 164 | // old IE event.button uses constants defined here: http://msdn.microsoft.com/en-us/library/ie/ms533544(v=vs.85).aspx 165 | // so we actually need to map the standard back to oldIE 166 | event.button = { 167 | 0: 1, 168 | 1: 4, 169 | 2: 2 170 | }[ event.button ] || event.button; 171 | } 172 | 173 | return event; 174 | }, 175 | 176 | keyEvent: function( type, options ) { 177 | var event, doc; 178 | options = $.extend({ 179 | bubbles: true, 180 | cancelable: true, 181 | view: windowOfDocument(doc), 182 | ctrlKey: false, 183 | altKey: false, 184 | shiftKey: false, 185 | metaKey: false, 186 | keyCode: 0, 187 | charCode: undefined 188 | }, options ); 189 | 190 | doc = isDocument(this.target)? this.target : (this.target.ownerDocument || document); 191 | if ( doc.createEvent ) { 192 | try { 193 | event = doc.createEvent( "KeyEvents" ); 194 | event.initKeyEvent( type, options.bubbles, options.cancelable, options.view, 195 | options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 196 | options.keyCode, options.charCode ); 197 | // initKeyEvent throws an exception in WebKit 198 | // see: http://stackoverflow.com/questions/6406784/initkeyevent-keypress-only-works-in-firefox-need-a-cross-browser-solution 199 | // and also https://bugs.webkit.org/show_bug.cgi?id=13368 200 | // fall back to a generic event until we decide to implement initKeyboardEvent 201 | } catch( err ) { 202 | event = doc.createEvent( "Events" ); 203 | event.initEvent( type, options.bubbles, options.cancelable ); 204 | $.extend( event, { 205 | view: options.view, 206 | ctrlKey: options.ctrlKey, 207 | altKey: options.altKey, 208 | shiftKey: options.shiftKey, 209 | metaKey: options.metaKey, 210 | keyCode: options.keyCode, 211 | charCode: options.charCode 212 | }); 213 | } 214 | } else if ( doc.createEventObject ) { 215 | event = doc.createEventObject(); 216 | $.extend( event, options ); 217 | } 218 | 219 | if ( !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() ) || (({}).toString.call( window.opera ) === "[object Opera]") ) { 220 | event.keyCode = (options.charCode > 0) ? options.charCode : options.keyCode; 221 | event.charCode = undefined; 222 | } 223 | 224 | return event; 225 | }, 226 | 227 | dispatchEvent: function( elem, type, event, options ) { 228 | if (options.jQueryTrigger === true) { 229 | $(elem).trigger($.extend({}, event, options, {type: type})); 230 | } 231 | else if ( elem.dispatchEvent ) { 232 | elem.dispatchEvent( event ); 233 | } else if ( elem.fireEvent ) { 234 | elem.fireEvent( "on" + type, event ); 235 | } 236 | }, 237 | 238 | simulateFocus: function() { 239 | var focusinEvent, 240 | triggered = false, 241 | $element = $( this.target ); 242 | 243 | function trigger() { 244 | triggered = true; 245 | } 246 | 247 | $element.bind( "focus", trigger ); 248 | $element[ 0 ].focus(); 249 | 250 | if ( !triggered ) { 251 | focusinEvent = $.Event( "focusin" ); 252 | focusinEvent.preventDefault(); 253 | $element.trigger( focusinEvent ); 254 | $element.triggerHandler( "focus" ); 255 | } 256 | $element.unbind( "focus", trigger ); 257 | }, 258 | 259 | simulateBlur: function() { 260 | var focusoutEvent, 261 | triggered = false, 262 | $element = $( this.target ); 263 | 264 | function trigger() { 265 | triggered = true; 266 | } 267 | 268 | $element.bind( "blur", trigger ); 269 | $element[ 0 ].blur(); 270 | 271 | // blur events are async in IE 272 | setTimeout(function() { 273 | // IE won't let the blur occur if the window is inactive 274 | if ( $element[ 0 ].ownerDocument.activeElement === $element[ 0 ] ) { 275 | $element[ 0 ].ownerDocument.body.focus(); 276 | } 277 | 278 | // Firefox won't trigger events if the window is inactive 279 | // IE doesn't trigger events if we had to manually focus the body 280 | if ( !triggered ) { 281 | focusoutEvent = $.Event( "focusout" ); 282 | focusoutEvent.preventDefault(); 283 | $element.trigger( focusoutEvent ); 284 | $element.triggerHandler( "blur" ); 285 | } 286 | $element.unbind( "blur", trigger ); 287 | }, 1 ); 288 | } 289 | }); 290 | 291 | 292 | 293 | /** complex events **/ 294 | 295 | function findCenter( elem ) { 296 | var offset, 297 | $document, 298 | $elem = $( elem ); 299 | 300 | if ( isDocument($elem[0]) ) { 301 | $document = $elem; 302 | offset = { left: 0, top: 0 }; 303 | } 304 | else { 305 | $document = $( $elem[0].ownerDocument || document ); 306 | offset = $elem.offset(); 307 | } 308 | 309 | return { 310 | x: offset.left + $elem.outerWidth() / 2 - $document.scrollLeft(), 311 | y: offset.top + $elem.outerHeight() / 2 - $document.scrollTop() 312 | }; 313 | } 314 | 315 | function findCorner( elem ) { 316 | var offset, 317 | $document, 318 | $elem = $( elem ); 319 | 320 | if ( isDocument($elem[0]) ) { 321 | $document = $elem; 322 | offset = { left: 0, top: 0 }; 323 | } 324 | else { 325 | $document = $( $elem[0].ownerDocument || document ); 326 | offset = $elem.offset(); 327 | } 328 | 329 | return { 330 | x: offset.left - document.scrollLeft(), 331 | y: offset.top - document.scrollTop() 332 | }; 333 | } 334 | 335 | $.extend( $.simulate.prototype, { 336 | simulateDrag: function() { 337 | var i = 0, 338 | target = this.target, 339 | options = this.options, 340 | center = options.handle === "corner" ? findCorner( target ) : findCenter( target ), 341 | x = Math.floor( center.x ), 342 | y = Math.floor( center.y ), 343 | coord = { clientX: x, clientY: y }, 344 | dx = options.dx || ( options.x !== undefined ? options.x - x : 0 ), 345 | dy = options.dy || ( options.y !== undefined ? options.y - y : 0 ), 346 | moves = options.moves || 3; 347 | 348 | this.simulateEvent( target, "mousedown", coord ); 349 | 350 | for ( ; i < moves ; i++ ) { 351 | x += dx / moves; 352 | y += dy / moves; 353 | 354 | coord = { 355 | clientX: Math.round( x ), 356 | clientY: Math.round( y ) 357 | }; 358 | 359 | this.simulateEvent( target.ownerDocument, "mousemove", coord ); 360 | } 361 | 362 | if ( $.contains( document, target ) ) { 363 | this.simulateEvent( target, "mouseup", coord ); 364 | this.simulateEvent( target, "click", coord ); 365 | } else { 366 | this.simulateEvent( document, "mouseup", coord ); 367 | } 368 | } 369 | }); 370 | 371 | })( jQuery ); 372 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-simulate-ext", 3 | "description": "jQuery Simulate - extended. Simulating complex user interaction based on the jQuery Simulate plugin.", 4 | "version": "1.3.0", 5 | "keywords": [ 6 | "jquery", 7 | "simulate", 8 | "key", 9 | "mouse", 10 | "press", 11 | "move", 12 | "drag", 13 | "drop", 14 | "user-interaction" 15 | ], 16 | "author": { 17 | "name": "Jochen Ulrich", 18 | "email": "jochenulrich@t-online.de", 19 | "web": "https://github.com/j-ulrich" 20 | }, 21 | "contributors": [ 22 | { 23 | "name": "Jochen Ulrich", 24 | "email": "jochenulrich@t-online.de", 25 | "web": "https://github.com/j-ulrich" 26 | } 27 | ], 28 | "bugs": "https://github.com/j-ulrich/jquery-simulate-ext/issues", 29 | "licenses": [ 30 | { 31 | "type": "MIT", 32 | "url": "http://github.com/j-ulrich/jquery-simulate-ext/blob/master/MIT-LICENSE.txt" 33 | } 34 | ], 35 | "repository": { 36 | "type": "git", 37 | "url": "https://github.com/j-ulrich/jquery-simulate-ext.git" 38 | }, 39 | "dependencies": {}, 40 | "homepage": "https://github.com/j-ulrich/jquery-simulate-ext", 41 | "scripts": { 42 | "test": "grunt qunit" 43 | }, 44 | "devDependencies": { 45 | "grunt": "~0.4.2", 46 | "grunt-contrib-concat": "~0.3.0", 47 | "grunt-contrib-jshint": "~0.7.2", 48 | "grunt-contrib-qunit": "~0.3.0" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /simulate-ext.jquery.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simulate-ext", 3 | "version": "1.3.0", 4 | "title": "jQuery Simulate Extended", 5 | "author": { 6 | "name": "Jochen Ulrich", 7 | "email": "jochenulrich@t-online.de", 8 | "url": "https://github.com/j-ulrich" 9 | }, 10 | "licenses": [ 11 | { 12 | "type": "MIT", 13 | "url": "https://github.com/j-ulrich/jquery-simulate-ext/blob/master/MIT-LICENSE.txt" 14 | } 15 | ], 16 | "dependencies": { 17 | "jquery": "~1.7" 18 | }, 19 | 20 | 21 | 22 | "description": "jQuery Simulate - Extended. Simulating complex user interaction based on the jQuery Simulate plugin.", 23 | "keywords": ["jquery", "simulate", "key", "mouse", "press", "move", "drag", "drop", "user-interaction"], 24 | "homepage": "http://j-ulrich.github.com/jquery-simulate-ext", 25 | "docs": "https://github.com/j-ulrich/jquery-simulate-ext/blob/master/README.md", 26 | "demo": "http://jsfiddle.net/Ignitor/Psjhf/embedded/result/", 27 | "bugs": "https://github.com/j-ulrich/jquery-simulate-ext/issues" 28 | } -------------------------------------------------------------------------------- /src/jquery.simulate.drag-n-drop.js: -------------------------------------------------------------------------------- 1 | /*jshint camelcase:true, plusplus:true, forin:true, noarg:true, noempty:true, eqeqeq:true, bitwise:true, strict:true, undef:true, unused:true, curly:true, browser:true, devel:true, maxerr:100, white:false, onevar:false */ 2 | /*global jQuery:true $:true */ 3 | 4 | /* jQuery Simulate Drag-n-Drop Plugin 1.3.0 5 | * http://github.com/j-ulrich/jquery-simulate-ext 6 | * 7 | * Copyright (c) 2014 Jochen Ulrich 8 | * Licensed under the MIT license (MIT-LICENSE.txt). 9 | */ 10 | 11 | ;(function($, undefined) { 12 | "use strict"; 13 | 14 | /* Overwrite the $.fn.simulate function to reduce the jQuery set to the first element for the 15 | * drag-n-drop interactions. 16 | */ 17 | $.fn.simulate = function( type, options ) { 18 | switch (type) { 19 | case "drag": 20 | case "drop": 21 | case "drag-n-drop": 22 | var ele = this.first(); 23 | new $.simulate( ele[0], type, options); 24 | return ele; 25 | default: 26 | return this.each(function() { 27 | new $.simulate( this, type, options ); 28 | }); 29 | } 30 | }; 31 | 32 | var now = Date.now || function() { return new Date().getTime(); }; 33 | 34 | var rdocument = /\[object (?:HTML)?Document\]/; 35 | /** 36 | * Tests whether an object is an (HTML) document object. 37 | * @param {DOM Element} elem - the object/element to be tested 38 | * @returns {Boolean} true if elem is an (HTML) document object. 39 | * @private 40 | * @author julrich 41 | * @since 1.1 42 | */ 43 | function isDocument( elem ) { 44 | return rdocument.test(Object.prototype.toString.call($(elem)[0])); 45 | } 46 | 47 | /** 48 | * Selects the first match from an array. 49 | * @param {Array} array - Array of objects to be be tested 50 | * @param {Function} check - Callback function that accepts one argument (which will be one element 51 | * from the array) and returns a boolean. 52 | * @returns {Boolean|null} the first element in array for which check returns true. 53 | * If none of the elements in array passes check, null is returned. 54 | * @private 55 | * @author julrich 56 | * @since 1.1 57 | */ 58 | function selectFirstMatch(array, check) { 59 | var i; 60 | if ($.isFunction(check)) { 61 | for (i=0; i < array.length; i+=1) { 62 | if (check(array[i])) { 63 | return array[i]; 64 | } 65 | } 66 | return null; 67 | } 68 | else { 69 | for (i=0; i < array.length; i+=1) { 70 | if (array[i]) { 71 | return array[i]; 72 | } 73 | } 74 | return null; 75 | } 76 | } 77 | 78 | // Based on the findCenter function from jquery.simulate.js 79 | /** 80 | * Calculates the position of the center of an DOM element. 81 | * @param {DOM Element} elem - the element whose center should be calculated. 82 | * @returns {Object} an object with the properties x and y 83 | * representing the position of the center of elem in page relative coordinates 84 | * (i.e. independent of any scrolling). 85 | * @private 86 | * @author julrich 87 | * @since 1.0 88 | */ 89 | function findCenter( elem ) { 90 | var offset, 91 | $elem = $( elem ); 92 | if ( isDocument($elem[0]) ) { 93 | offset = {left: 0, top: 0}; 94 | } 95 | else { 96 | offset = $elem.offset(); 97 | } 98 | 99 | return { 100 | x: offset.left + $elem.outerWidth() / 2, 101 | y: offset.top + $elem.outerHeight() / 2 102 | }; 103 | } 104 | 105 | /** 106 | * Converts page relative coordinates into client relative coordinates. 107 | * @param {Numeric|Object} x - Either the x coordinate of the page relative coordinates or 108 | * an object with the properties pageX and pageY representing page 109 | * relative coordinates. 110 | * @param {Numeric} [y] - If x is numeric (i.e. the x coordinate of page relative coordinates), 111 | * then this is the y coordinate. If x is an object, this parameter is skipped. 112 | * @param {DOM Document} [docRel] - Optional DOM document object used to calculate the client relative 113 | * coordinates. The page relative coordinates are interpreted as being relative to that document and 114 | * the scroll position of that document is used to calculate the client relative coordinates. 115 | * By default, document is used. 116 | * @returns {Object} an object representing the client relative coordinates corresponding to the 117 | * given page relative coordinates. The object either provides the properties x and 118 | * y when x and y were given as arguments, or clientX 119 | * and clientY when the parameter x was given as an object (see above). 120 | * @private 121 | * @author julrich 122 | * @since 1.0 123 | */ 124 | function pageToClientPos(x, y, docRel) { 125 | var $document; 126 | if ( isDocument(y) ) { 127 | $document = $(y); 128 | } else { 129 | $document = $(docRel || document); 130 | } 131 | 132 | if (typeof x === "number" && typeof y === "number") { 133 | return { 134 | x: x - $document.scrollLeft(), 135 | y: y - $document.scrollTop() 136 | }; 137 | } 138 | else if (typeof x === "object" && x.pageX && x.pageY) { 139 | return { 140 | clientX: x.pageX - $document.scrollLeft(), 141 | clientY: x.pageY - $document.scrollTop() 142 | }; 143 | } 144 | } 145 | 146 | /** 147 | * Browser-independent implementation of document.elementFromPoint(). 148 | * 149 | * When run for the first time on a scrolled page, this function performs a check on how 150 | * document.elementFromPoint() is implemented in the current browser. It stores 151 | * the results in two static variables so that the check can be skipped for successive calls. 152 | * 153 | * @param {Numeric|Object} x - Either the x coordinate of client relative coordinates or an object 154 | * with the properties x and y representing client relative coordinates. 155 | * @param {Numeric} [y] - If x is numeric (i.e. the x coordinate of client relative coordinates), 156 | * this is the y coordinate. If x is an object, this parameter is skipped. 157 | * @param {DOM Document} [docRel] - Optional DOM document object 158 | * @returns {DOM Element|Null} 159 | * @private 160 | * @author Nicolas Zeh (Basic idea), julrich 161 | * @see http://www.zehnet.de/2010/11/19/document-elementfrompoint-a-jquery-solution/ 162 | * @since 1.0 163 | */ 164 | function elementAtPosition(x, y, docRel) { 165 | var doc; 166 | if ( isDocument(y) ) { 167 | doc = y; 168 | } else { 169 | doc = docRel || document; 170 | } 171 | 172 | if(!doc.elementFromPoint) { 173 | return null; 174 | } 175 | 176 | var clientX = x, clientY = y; 177 | if (typeof x === "object" && (x.clientX || x.clientY)) { 178 | clientX = x.clientX || 0 ; 179 | clientY = x.clientY || 0; 180 | } 181 | 182 | if(elementAtPosition.prototype.check) 183 | { 184 | var sl, ele; 185 | if ((sl = $(doc).scrollTop()) >0) 186 | { 187 | ele = doc.elementFromPoint(0, sl + $(window).height() -1); 188 | if ( ele !== null && ele.tagName.toUpperCase() === 'HTML' ) { ele = null; } 189 | elementAtPosition.prototype.nativeUsesRelative = ( ele === null ); 190 | } 191 | else if((sl = $(doc).scrollLeft()) >0) 192 | { 193 | ele = doc.elementFromPoint(sl + $(window).width() -1, 0); 194 | if ( ele !== null && ele.tagName.toUpperCase() === 'HTML' ) { ele = null; } 195 | elementAtPosition.prototype.nativeUsesRelative = ( ele === null ); 196 | } 197 | elementAtPosition.prototype.check = (sl<=0); // Check was not meaningful because we were at scroll position 0 198 | } 199 | 200 | if(!elementAtPosition.prototype.nativeUsesRelative) 201 | { 202 | clientX += $(doc).scrollLeft(); 203 | clientY += $(doc).scrollTop(); 204 | } 205 | 206 | return doc.elementFromPoint(clientX,clientY); 207 | } 208 | // Default values for the check variables 209 | elementAtPosition.prototype.check = true; 210 | elementAtPosition.prototype.nativeUsesRelative = true; 211 | 212 | /** 213 | * Informs the rest of the world that the drag is finished. 214 | * @param {DOM Element} ele - The element which was dragged. 215 | * @param {Object} [options] - The drag options. 216 | * @fires simulate-drag 217 | * @private 218 | * @author julrich 219 | * @since 1.0 220 | */ 221 | function dragFinished(ele, options) { 222 | var opts = options || {}; 223 | $(ele).trigger({type: "simulate-drag"}); 224 | if ($.isFunction(opts.callback)) { 225 | opts.callback.apply(ele); 226 | } 227 | } 228 | 229 | /** 230 | * Generates a series of mousemove events for a drag. 231 | * @param {Object} self - The simulate object. 232 | * @param {DOM Element} ele - The element which is dragged. 233 | * @param {Object} start - The start coordinates of the drag, represented using the properties 234 | * x and y. 235 | * @param {Object} drag - The distance to be dragged, represented using the properties dx 236 | * and dy. 237 | * @param {Object} options - The drag options. Must have the property interpolation 238 | * containing the interpolation options (stepWidth, stepCount, etc.). 239 | * @requires eventTarget 240 | * @requires now() 241 | * @private 242 | * @author julrich 243 | * @since 1.0 244 | */ 245 | function interpolatedEvents(self, ele, start, drag, options) { 246 | var targetDoc = selectFirstMatch([ele, ele.ownerDocument], isDocument) || document, 247 | interpolOptions = options.interpolation, 248 | dragDistance = Math.sqrt(Math.pow(drag.dx,2) + Math.pow(drag.dy,2)), // sqrt(a^2 + b^2) 249 | stepWidth, stepCount, stepVector; 250 | 251 | if (interpolOptions.stepWidth) { 252 | stepWidth = parseInt(interpolOptions.stepWidth, 10); 253 | stepCount = Math.floor(dragDistance / stepWidth)-1; 254 | var stepScale = stepWidth / dragDistance; 255 | stepVector = {x: drag.dx*stepScale, y: drag.dy*stepScale }; 256 | } 257 | else { 258 | stepCount = parseInt(interpolOptions.stepCount, 10); 259 | stepWidth = dragDistance / (stepCount+1); 260 | stepVector = {x: drag.dx/(stepCount+1), y: drag.dy/(stepCount+1)}; 261 | } 262 | 263 | 264 | var coords = $.extend({},start); 265 | 266 | /** 267 | * Calculates the effective coordinates for one mousemove event and fires the event. 268 | * @requires eventTarget 269 | * @requires targetDoc 270 | * @requires coords 271 | * @requires stepVector 272 | * @requires interpolOptions 273 | * @fires mousemove 274 | * @inner 275 | * @author julrich 276 | * @since 1.0 277 | */ 278 | function interpolationStep() { 279 | coords.x += stepVector.x; 280 | coords.y += stepVector.y; 281 | var effectiveCoords = {pageX: coords.x, pageY: coords.y}; 282 | if (interpolOptions.shaky && (interpolOptions.shaky === true || !isNaN(parseInt(interpolOptions.shaky,10)) )) { 283 | var amplitude = (interpolOptions.shaky === true)? 1 : parseInt(interpolOptions.shaky,10); 284 | effectiveCoords.pageX += Math.floor(Math.random()*(2*amplitude+1)-amplitude); 285 | effectiveCoords.pageY += Math.floor(Math.random()*(2*amplitude+1)-amplitude); 286 | } 287 | var clientCoord = pageToClientPos(effectiveCoords, targetDoc), 288 | eventTarget = elementAtPosition(clientCoord, targetDoc) || ele; 289 | self.simulateEvent( eventTarget, "mousemove", {pageX: Math.round(effectiveCoords.pageX), pageY: Math.round(effectiveCoords.pageY)}); 290 | } 291 | 292 | 293 | var lastTime; 294 | 295 | /** 296 | * Performs one interpolation step (i.e. cares about firing the event) and then sleeps for 297 | * stepDelay milliseconds. 298 | * @requires lastTime 299 | * @requires stepDelay 300 | * @requires step 301 | * @requires ele 302 | * @requires eventTarget 303 | * @reuiqre targetDoc 304 | * @requires start 305 | * @requires drag 306 | * @requires now() 307 | * @inner 308 | * @author julrich 309 | * @since 1.0 310 | */ 311 | function stepAndSleep() { 312 | var timeElapsed = now() - lastTime; // Work-around for Firefox & IE "bug": setTimeout can fire before the timeout 313 | if (timeElapsed >= stepDelay) { 314 | if (step < stepCount) { 315 | interpolationStep(); 316 | step += 1; 317 | lastTime = now(); 318 | setTimeout(stepAndSleep, stepDelay); 319 | } 320 | else { 321 | var pageCoord = {pageX: Math.round(start.x+drag.dx), pageY: Math.round(start.y+drag.dy)}, 322 | clientCoord = pageToClientPos(pageCoord, targetDoc), 323 | eventTarget = elementAtPosition(clientCoord, targetDoc) || ele; 324 | self.simulateEvent( eventTarget, "mousemove", pageCoord); 325 | dragFinished(ele, options); 326 | } 327 | } 328 | else { 329 | setTimeout(stepAndSleep, stepDelay - timeElapsed); 330 | } 331 | 332 | } 333 | 334 | if ( (!interpolOptions.stepDelay && !interpolOptions.duration) || ((interpolOptions.stepDelay <= 0) && (interpolOptions.duration <= 0)) ) { 335 | // Trigger as fast as possible 336 | for (var i=0; i < stepCount; i+=1) { 337 | interpolationStep(); 338 | } 339 | var pageCoord = {pageX: Math.round(start.x+drag.dx), pageY: Math.round(start.y+drag.dy)}, 340 | clientCoord = pageToClientPos(pageCoord, targetDoc), 341 | eventTarget = elementAtPosition(clientCoord, targetDoc) || ele; 342 | self.simulateEvent( eventTarget, "mousemove", pageCoord); 343 | dragFinished(ele, options); 344 | } 345 | else { 346 | var stepDelay = parseInt(interpolOptions.stepDelay,10) || Math.ceil(parseInt(interpolOptions.duration,10) / (stepCount+1)); 347 | var step = 0; 348 | 349 | lastTime = now(); 350 | setTimeout(stepAndSleep, stepDelay); 351 | } 352 | 353 | } 354 | 355 | /** 356 | * @returns {Object|undefined} an object containing information about the currently active drag 357 | * or undefined when there is no active drag. 358 | * The returned object contains the following properties: 359 | *
    360 | *
  • dragElement: the dragged element
  • 361 | *
  • dragStart: object with the properties x and y 362 | * representing the page relative start coordinates of the drag
  • 363 | *
  • dragDistance: object with the properties x and y 364 | * representing the distance of the drag in x and y direction
  • 365 | *
366 | * @public 367 | * @author julrich 368 | * @since 1.0 369 | */ 370 | $.simulate.activeDrag = function() { 371 | if (!$.simulate._activeDrag) { 372 | return undefined; 373 | } 374 | return $.extend(true,{},$.simulate._activeDrag); 375 | }; 376 | 377 | $.extend( $.simulate.prototype, 378 | 379 | /** 380 | * @lends $.simulate.prototype 381 | */ 382 | { 383 | 384 | 385 | /** 386 | * Simulates a drag. 387 | * 388 | * @see https://github.com/j-ulrich/jquery-simulate-ext/blob/master/doc/drag-n-drop.md 389 | * @public 390 | * @author julrich 391 | * @since 1.0 392 | */ 393 | simulateDrag: function() { 394 | var self = this, 395 | ele = self.target, 396 | options = $.extend({ 397 | dx: 0, 398 | dy: 0, 399 | dragTarget: undefined, 400 | clickToDrag: false, 401 | eventProps: {}, 402 | interpolation: { 403 | stepWidth: 0, 404 | stepCount: 0, 405 | stepDelay: 0, 406 | duration: 0, 407 | shaky: false 408 | }, 409 | callback: undefined 410 | }, this.options); 411 | 412 | var start, 413 | continueDrag = ($.simulate._activeDrag && $.simulate._activeDrag.dragElement === ele); 414 | 415 | if (continueDrag) { 416 | start = $.simulate._activeDrag.dragStart; 417 | } 418 | else { 419 | start = findCenter( ele ); 420 | } 421 | 422 | var x = Math.round( start.x ), 423 | y = Math.round( start.y ), 424 | coord = { pageX: x, pageY: y }, 425 | dx, 426 | dy; 427 | 428 | if (options.dragTarget) { 429 | var end = findCenter(options.dragTarget); 430 | dx = Math.round(end.x - start.x); 431 | dy = Math.round(end.y - start.y); 432 | } 433 | else { 434 | dx = options.dx || 0; 435 | dy = options.dy || 0; 436 | } 437 | 438 | if (continueDrag) { 439 | // We just continue to move the dragged element 440 | $.simulate._activeDrag.dragDistance.x += dx; 441 | $.simulate._activeDrag.dragDistance.y += dy; 442 | coord = { pageX: Math.round(x + $.simulate._activeDrag.dragDistance.x) , pageY: Math.round(y + $.simulate._activeDrag.dragDistance.y) }; 443 | } 444 | else { 445 | if ($.simulate._activeDrag) { 446 | // Drop before starting a new drag 447 | $($.simulate._activeDrag.dragElement).simulate( "drop" ); 448 | } 449 | 450 | // We start a new drag 451 | $.extend(options.eventProps, coord); 452 | self.simulateEvent( ele, "mousedown", options.eventProps ); 453 | if (options.clickToDrag === true) { 454 | self.simulateEvent( ele, "mouseup", options.eventProps ); 455 | self.simulateEvent( ele, "click", options.eventProps ); 456 | } 457 | $(ele).add(ele.ownerDocument).one('mouseup', function() { 458 | $.simulate._activeDrag = undefined; 459 | }); 460 | 461 | $.extend($.simulate, { 462 | _activeDrag: { 463 | dragElement: ele, 464 | dragStart: { x: x, y: y }, 465 | dragDistance: { x: dx, y: dy } 466 | } 467 | }); 468 | coord = { pageX: Math.round(x + dx), pageY: Math.round(y + dy) }; 469 | } 470 | 471 | if (dx !== 0 || dy !== 0) { 472 | 473 | if ( options.interpolation && (options.interpolation.stepCount || options.interpolation.stepWidth) ) { 474 | interpolatedEvents(self, ele, {x: x, y: y}, {dx: dx, dy: dy}, options); 475 | } 476 | else { 477 | var targetDoc = selectFirstMatch([ele, ele.ownerDocument], isDocument) || document, 478 | clientCoord = pageToClientPos(coord, targetDoc), 479 | eventTarget = elementAtPosition(clientCoord, targetDoc) || ele; 480 | 481 | $.extend(options.eventProps, coord); 482 | self.simulateEvent( eventTarget, "mousemove", options.eventProps ); 483 | dragFinished(ele, options); 484 | } 485 | } 486 | else { 487 | dragFinished(ele, options); 488 | } 489 | }, 490 | 491 | /** 492 | * Simulates a drop. 493 | * 494 | * @see https://github.com/j-ulrich/jquery-simulate-ext/blob/master/doc/drag-n-drop.md 495 | * @public 496 | * @author julrich 497 | * @since 1.0 498 | */ 499 | simulateDrop: function() { 500 | var self = this, 501 | ele = this.target, 502 | activeDrag = $.simulate._activeDrag, 503 | options = $.extend({ 504 | clickToDrop: false, 505 | eventProps: {}, 506 | callback: undefined 507 | }, self.options), 508 | moveBeforeDrop = true, 509 | center = findCenter( ele ), 510 | x = Math.round( center.x ), 511 | y = Math.round( center.y ), 512 | coord = { pageX: x, pageY: y }, 513 | targetDoc = ( (activeDrag)? selectFirstMatch([activeDrag.dragElement, activeDrag.dragElement.ownerDocument], isDocument) : selectFirstMatch([ele, ele.ownerDocument], isDocument) ) || document, 514 | clientCoord = pageToClientPos(coord, targetDoc), 515 | eventTarget = elementAtPosition(clientCoord, targetDoc); 516 | 517 | if (activeDrag && (activeDrag.dragElement === ele || isDocument(ele))) { 518 | // We already moved the mouse during the drag so we just simulate the drop on the end position 519 | x = Math.round(activeDrag.dragStart.x + activeDrag.dragDistance.x); 520 | y = Math.round(activeDrag.dragStart.y + activeDrag.dragDistance.y); 521 | coord = { pageX: x, pageY: y }; 522 | clientCoord = pageToClientPos(coord, targetDoc); 523 | eventTarget = elementAtPosition(clientCoord, targetDoc); 524 | moveBeforeDrop = false; 525 | } 526 | 527 | if (!eventTarget) { 528 | eventTarget = (activeDrag)? activeDrag.dragElement : ele; 529 | } 530 | 531 | $.extend(options.eventProps, coord); 532 | 533 | if (moveBeforeDrop === true) { 534 | // Else we assume the drop should happen on target, so we move there 535 | self.simulateEvent( eventTarget, "mousemove", options.eventProps ); 536 | } 537 | 538 | if (options.clickToDrop) { 539 | self.simulateEvent( eventTarget, "mousedown", options.eventProps ); 540 | } 541 | this.simulateEvent( eventTarget, "mouseup", options.eventProps ); 542 | if (options.clickToDrop) { 543 | self.simulateEvent( eventTarget, "click", options.eventProps ); 544 | } 545 | 546 | $.simulate._activeDrag = undefined; 547 | $(eventTarget).trigger({type: "simulate-drop"}); 548 | if ($.isFunction(options.callback)) { 549 | options.callback.apply(eventTarget); 550 | } 551 | }, 552 | 553 | /** 554 | * Simulates a drag followed by drop. 555 | * 556 | * @see https://github.com/j-ulrich/jquery-simulate-ext/blob/master/doc/drag-n-drop.md 557 | * @public 558 | * @author julrich 559 | * @since 1.0 560 | */ 561 | simulateDragNDrop: function() { 562 | var self = this, 563 | ele = this.target, 564 | options = $.extend({ 565 | dragTarget: undefined, 566 | dropTarget: undefined 567 | }, self.options), 568 | // If there is a dragTarget or dx/dy, then we drag there and simulate an independent drop on dropTarget or ele 569 | dropEle = ((options.dragTarget || options.dx || options.dy)? options.dropTarget : ele) || ele; 570 | /* 571 | dx = (options.dropTarget)? 0 : (options.dx || 0), 572 | dy = (options.dropTarget)? 0 : (options.dy || 0), 573 | dragDistance = { dx: dx, dy: dy }; 574 | 575 | $.extend(options, dragDistance); 576 | */ 577 | $(ele).simulate( "drag", $.extend({},options,{ 578 | // If there is no dragTarget, no dx and no dy, we drag onto the dropTarget directly 579 | dragTarget: options.dragTarget || ((options.dx || options.dy)?undefined:options.dropTarget), 580 | callback: function() { 581 | $(dropEle).simulate( "drop", options ); 582 | } 583 | })); 584 | 585 | } 586 | }); 587 | 588 | }(jQuery)); -------------------------------------------------------------------------------- /src/jquery.simulate.ext.js: -------------------------------------------------------------------------------- 1 | /*jshint camelcase:true, plusplus:true, forin:true, noarg:true, noempty:true, eqeqeq:true, bitwise:true, strict:true, undef:true, unused:true, curly:true, browser:true, devel:true, maxerr:100, white:false, onevar:false */ 2 | /*global jQuery:true $:true */ 3 | 4 | /* jQuery Simulate Extended Plugin 1.3.0 5 | * http://github.com/j-ulrich/jquery-simulate-ext 6 | * 7 | * Copyright (c) 2014 Jochen Ulrich 8 | * Licensed under the MIT license (MIT-LICENSE.txt). 9 | */ 10 | 11 | ;(function( $ ) { 12 | "use strict"; 13 | 14 | /* Overwrite the $.simulate.prototype.mouseEvent function 15 | * to convert pageX/Y to clientX/Y 16 | */ 17 | var originalMouseEvent = $.simulate.prototype.mouseEvent, 18 | rdocument = /\[object (?:HTML)?Document\]/; 19 | 20 | $.simulate.prototype.mouseEvent = function(type, options) { 21 | options = options || {}; 22 | if (options.pageX || options.pageY) { 23 | var doc = rdocument.test(Object.prototype.toString.call(this.target))? this.target : (this.target.ownerDocument || document); 24 | options.clientX = (options.pageX || 0) - $(doc).scrollLeft(); 25 | options.clientY = (options.pageY || 0) - $(doc).scrollTop(); 26 | } 27 | return originalMouseEvent.apply(this, [type, options]); 28 | }; 29 | 30 | 31 | })( jQuery ); 32 | -------------------------------------------------------------------------------- /src/jquery.simulate.key-combo.js: -------------------------------------------------------------------------------- 1 | /*jshint camelcase:true, plusplus:true, forin:true, noarg:true, noempty:true, eqeqeq:true, bitwise:true, strict:true, undef:true, unused:true, curly:true, browser:true, devel:true, maxerr:100, white:false, onevar:false */ 2 | /*global jQuery:true $:true */ 3 | 4 | /* jQuery Simulate Key-Combo Plugin 1.3.0 5 | * http://github.com/j-ulrich/jquery-simulate-ext 6 | * 7 | * Copyright (c) 2014 Jochen Ulrich 8 | * Licensed under the MIT license (MIT-LICENSE.txt). 9 | */ 10 | 11 | /** 12 | * 13 | * For details about key events, key codes, char codes etc. see http://unixpapa.com/js/key.html 14 | */ 15 | 16 | ;(function($,undefined) { 17 | "use strict"; 18 | 19 | /** 20 | * Key codes of special keys. 21 | * @private 22 | * @author julrich 23 | * @since 1.3.0 24 | */ 25 | var SpecialKeyCodes = { 26 | // Modifier Keys 27 | SHIFT: 16, 28 | CONTROL: 17, 29 | ALTERNATIVE: 18, 30 | META: 91, 31 | // Arrow Keys 32 | LEFT_ARROW: 37, 33 | UP_ARROW: 38, 34 | RIGHT_ARROW: 39, 35 | DOWN_ARROW: 40, 36 | // Function Keys 37 | F1: 112, 38 | F2: 113, 39 | F3: 114, 40 | F4: 115, 41 | F5: 116, 42 | F6: 117, 43 | F7: 118, 44 | F8: 119, 45 | F9: 120, 46 | F10: 121, 47 | F11: 122, 48 | F12: 123, 49 | // Other 50 | ENTER: 13, 51 | TABULATOR: 9, 52 | ESCAPE: 27, 53 | BACKSPACE: 8, 54 | INSERT: 45, 55 | DELETE: 46, 56 | HOME: 36, 57 | END: 35, 58 | PAGE_UP: 33, 59 | PAGE_DOWN: 34, 60 | 61 | }; 62 | 63 | // SpecialKeyCode aliases 64 | SpecialKeyCodes.CTRL = SpecialKeyCodes.CONTROL; 65 | SpecialKeyCodes.ALT = SpecialKeyCodes.ALTERNATIVE; 66 | SpecialKeyCodes.COMMAND = SpecialKeyCodes.META; 67 | SpecialKeyCodes.TAB = SpecialKeyCodes.TABULATOR; 68 | SpecialKeyCodes.ESC = SpecialKeyCodes.ESCAPE; 69 | 70 | 71 | $.extend( $.simulate.prototype, 72 | 73 | /** 74 | * @lends $.simulate.prototype 75 | */ 76 | { 77 | 78 | 79 | /** 80 | * Simulates simultaneous key presses 81 | * 82 | * @see https://github.com/j-ulrich/jquery-simulate-ext/blob/master/doc/key-combo.md 83 | * @public 84 | * @author julrich 85 | * @since 1.0 86 | */ 87 | simulateKeyCombo: function() { 88 | var $target = $(this.target), 89 | options = $.extend({ 90 | combo: "", 91 | eventProps: {}, 92 | eventsOnly: false 93 | }, this.options), 94 | combo = options.combo, 95 | comboSplit = combo.split(/(\+)/), 96 | plusExpected = false, 97 | holdKeys = [], 98 | i; 99 | 100 | if (combo.length === 0) { 101 | return; 102 | } 103 | 104 | // Remove empty parts 105 | comboSplit = $.grep(comboSplit, function(part) { 106 | return (part !== ""); 107 | }); 108 | 109 | for (i=0; i < comboSplit.length; i+=1) { 110 | var key = comboSplit[i], 111 | keyLowered = key.toLowerCase(), 112 | keySpecial = key.toUpperCase().replace('-','_'); 113 | 114 | if (plusExpected) { 115 | if (key !== "+") { 116 | throw 'Syntax error: expected "+"'; 117 | } 118 | else { 119 | plusExpected = false; 120 | } 121 | } 122 | else { 123 | var keyCode; 124 | if ( key.length > 1) { 125 | // Assume a special key 126 | keyCode = SpecialKeyCodes[keySpecial]; 127 | 128 | if (keyCode === undefined) { 129 | throw 'Syntax error: unknown special key "'+key+'" (forgot "+" between keys?)'; 130 | } 131 | 132 | switch (keyCode) { 133 | case SpecialKeyCodes.CONTROL: 134 | case SpecialKeyCodes.ALT: 135 | case SpecialKeyCodes.SHIFT: 136 | case SpecialKeyCodes.META: 137 | options.eventProps[keyLowered+"Key"] = true; 138 | break; 139 | } 140 | holdKeys.unshift(keyCode); 141 | options.eventProps.keyCode = keyCode; 142 | options.eventProps.which = keyCode; 143 | options.eventProps.charCode = 0; 144 | $target.simulate("keydown", options.eventProps); 145 | 146 | } 147 | else { 148 | // "Normal" key 149 | keyCode = $.simulate.prototype.simulateKeySequence.prototype.charToKeyCode(key); 150 | holdKeys.unshift(keyCode); 151 | options.eventProps.keyCode = keyCode; 152 | options.eventProps.which = keyCode; 153 | options.eventProps.charCode = undefined; 154 | $target.simulate("keydown", options.eventProps); 155 | if (options.eventProps.shiftKey) { 156 | key = key.toUpperCase(); 157 | } 158 | options.eventProps.keyCode = key.charCodeAt(0); 159 | options.eventProps.charCode = options.eventProps.keyCode; 160 | options.eventProps.which = options.eventProps.keyCode; 161 | $target.simulate("keypress", options.eventProps); 162 | if (options.eventsOnly !== true && !options.eventProps.ctrlKey && !options.eventProps.altKey && !options.eventProps.metaKey) { 163 | $target.simulate('key-sequence', {sequence: key, triggerKeyEvents: false}); 164 | } 165 | } 166 | 167 | plusExpected = true; 168 | } 169 | } 170 | 171 | if (!plusExpected) { 172 | throw 'Syntax error: expected key (trailing "+"?)'; 173 | } 174 | 175 | // Release keys 176 | options.eventProps.charCode = undefined; 177 | for (i=0; i < holdKeys.length; i+=1) { 178 | options.eventProps.keyCode = holdKeys[i]; 179 | options.eventProps.which = holdKeys[i]; 180 | switch (options.eventProps.keyCode) { 181 | case SpecialKeyCodes.ALT: options.eventProps.altKey = false; break; 182 | case SpecialKeyCodes.SHIFT: options.eventProps.shiftKey = false; break; 183 | case SpecialKeyCodes.CONTROL: options.eventProps.ctrlKey = false; break; 184 | case SpecialKeyCodes.META: options.eventProps.metaKey = false; break; 185 | default: 186 | break; 187 | } 188 | $target.simulate("keyup", options.eventProps); 189 | } 190 | } 191 | 192 | }); 193 | }(jQuery)); -------------------------------------------------------------------------------- /src/jquery.simulate.key-sequence.js: -------------------------------------------------------------------------------- 1 | /*jshint camelcase:true, plusplus:true, forin:true, noarg:true, noempty:true, eqeqeq:true, bitwise:true, strict:true, undef:true, unused:true, curly:true, browser:true, devel:true, maxerr:100, white:false, onevar:false */ 2 | /*global jQuery:true $:true bililiteRange:true */ 3 | 4 | /* jQuery Simulate Key-Sequence Plugin 1.3.0 5 | * http://github.com/j-ulrich/jquery-simulate-ext 6 | * 7 | * Copyright (c) 2014 Jochen Ulrich 8 | * Licensed under the MIT license (MIT-LICENSE.txt). 9 | * 10 | * The plugin is an extension and modification of the jQuery sendkeys plugin by Daniel Wachsstock. 11 | * Therefore, the original copyright notice and license follow below. 12 | */ 13 | 14 | // insert characters in a textarea or text input field 15 | // special characters are enclosed in {}; use {{} for the { character itself 16 | // documentation: http://bililite.com/blog/2008/08/20/the-fnsendkeys-plugin/ 17 | // Version: 2.0 18 | // Copyright (c) 2010 Daniel Wachsstock 19 | // MIT license: 20 | // Permission is hereby granted, free of charge, to any person 21 | // obtaining a copy of this software and associated documentation 22 | // files (the "Software"), to deal in the Software without 23 | // restriction, including without limitation the rights to use, 24 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | // copies of the Software, and to permit persons to whom the 26 | // Software is furnished to do so, subject to the following 27 | // conditions: 28 | // 29 | // The above copyright notice and this permission notice shall be 30 | // included in all copies or substantial portions of the Software. 31 | // 32 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 33 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 34 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 35 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 36 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 37 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 38 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 39 | // OTHER DEALINGS IN THE SOFTWARE. 40 | 41 | ;(function($, undefined){ 42 | "use strict"; 43 | 44 | $.simulate.prototype.quirks = $.simulate.prototype.quirks || {}; 45 | 46 | $.extend($.simulate.prototype.quirks, 47 | 48 | /** 49 | * @lends $.simulate.prototype.quirks 50 | */ 51 | { 52 | /** 53 | * When simulating with delay in non-input elements, 54 | * all spaces are simulated at the end of the sequence instead 55 | * of the correct position. 56 | * @see {@link https://github.com/j-ulrich/jquery-simulate-ext/issues/6|issues #6} 57 | */ 58 | delayedSpacesInNonInputGlitchToEnd: undefined 59 | 60 | }); 61 | 62 | $.extend($.simulate.prototype, 63 | 64 | /** 65 | * @lends $.simulate.prototype 66 | */ 67 | { 68 | 69 | /** 70 | * Simulates sequencial key strokes. 71 | * 72 | * @see https://github.com/j-ulrich/jquery-simulate-ext/blob/master/doc/key-sequence.md 73 | * @public 74 | * @author Daniel Wachsstock, julrich 75 | * @since 1.0 76 | */ 77 | simulateKeySequence: function() { 78 | var target = this.target, 79 | $target = $(target), 80 | opts = $.extend({ 81 | sequence: "", 82 | triggerKeyEvents: true, 83 | eventProps: {}, 84 | delay: 0, 85 | callback: undefined 86 | }, this.options), 87 | sequence = opts.sequence; 88 | 89 | opts.delay = parseInt(opts.delay,10); 90 | 91 | var localkeys = {}; 92 | 93 | // Fix for #6 (https://github.com/j-ulrich/jquery-simulate-ext/issues/6) 94 | if ($.simulate.prototype.quirks.delayedSpacesInNonInputGlitchToEnd && !$target.is('input,textarea')) { 95 | $.extend(localkeys, { 96 | ' ': function(rng, s, opts) { 97 | var internalOpts = $.extend({}, opts, { 98 | triggerKeyEvents: false, 99 | delay: 0, 100 | callback: undefined 101 | }); 102 | $.simulate.prototype.simulateKeySequence.defaults.simplechar(rng, '\xA0', internalOpts); 103 | $.simulate.prototype.simulateKeySequence.defaults['{leftarrow}'](rng, s, internalOpts); 104 | $.simulate.prototype.simulateKeySequence.defaults.simplechar(rng, s, opts); 105 | $.simulate.prototype.simulateKeySequence.defaults['{del}'](rng, s, internalOpts); 106 | } 107 | }); 108 | } 109 | 110 | $.extend(localkeys, opts, $target.data('simulate-keySequence')); // allow for element-specific key functions 111 | 112 | // most elements to not keep track of their selection when they lose focus, so we have to do it for them 113 | var rng = $.data (target, 'simulate-keySequence.selection'); 114 | if (!rng){ 115 | rng = bililiteRange(target).bounds('selection'); 116 | $.data(target, 'simulate-keySequence.selection', rng); 117 | $target.bind('mouseup.simulate-keySequence', function(){ 118 | // we have to update the saved range. The routines here update the bounds with each press, but actual keypresses and mouseclicks do not 119 | $.data(target, 'simulate-keySequence.selection').bounds('selection'); 120 | }).bind('keyup.simulate-keySequence', function(evt){ 121 | // restore the selection if we got here with a tab (a click should select what was clicked on) 122 | if (evt.which === 9){ 123 | // there's a flash of selection when we restore the focus, but I don't know how to avoid that. 124 | $.data(target, 'simulate-keySequence.selection').select(); 125 | }else{ 126 | $.data(target, 'simulate-keySequence.selection').bounds('selection'); 127 | } 128 | }); 129 | } 130 | $target.focus(); 131 | if (typeof sequence === 'undefined') { // no string, so we just set up the event handlers 132 | return; 133 | } 134 | sequence = sequence.replace(/\n/g, '{enter}'); // turn line feeds into explicit break insertions 135 | 136 | /** 137 | * Informs the rest of the world that the sequences is finished. 138 | * @fires simulate-keySequence 139 | * @requires target 140 | * @requires sequence 141 | * @requires opts 142 | * @inner 143 | * @author julrich 144 | * @since 1.0 145 | */ 146 | function sequenceFinished() { 147 | $target.trigger({type: 'simulate-keySequence', sequence: sequence}); 148 | if ($.isFunction(opts.callback)) { 149 | opts.callback.apply(target, [{ 150 | sequence: sequence 151 | }]); 152 | } 153 | } 154 | 155 | /** 156 | * Simulates the key stroke for one character (or special sequence) and sleeps for 157 | * opts.delay milliseconds. 158 | * @requires lastTime 159 | * @requires now() 160 | * @requires tokenRegExp 161 | * @requires opts 162 | * @requires rng 163 | * @inner 164 | * @author julrich 165 | * @since 1.0 166 | */ 167 | function processNextToken() { 168 | var timeElapsed = now() - lastTime; // Work-around for Firefox "bug": setTimeout can fire before the timeout 169 | if (timeElapsed >= opts.delay) { 170 | var match = tokenRegExp.exec(sequence); 171 | if ( match !== null ) { 172 | var s = match[0]; 173 | (localkeys[s] || $.simulate.prototype.simulateKeySequence.defaults[s] || $.simulate.prototype.simulateKeySequence.defaults.simplechar)(rng, s, opts); 174 | setTimeout(processNextToken, opts.delay); 175 | } 176 | else { 177 | sequenceFinished(); 178 | } 179 | lastTime = now(); 180 | } 181 | else { 182 | setTimeout(processNextToken, opts.delay - timeElapsed); 183 | } 184 | } 185 | 186 | if (!opts.delay || opts.delay <= 0) { 187 | // Run as fast as possible 188 | sequence.replace(/\{[^}]*\}|[^{]+/g, function(s){ 189 | (localkeys[s] || $.simulate.prototype.simulateKeySequence.defaults[s] || $.simulate.prototype.simulateKeySequence.defaults.simplechar)(rng, s, opts); 190 | }); 191 | sequenceFinished(); 192 | } 193 | else { 194 | var tokenRegExp = /\{[^}]*\}|[^{]/g; // This matches curly bracket expressions or single characters 195 | var now = Date.now || function() { return new Date().getTime(); }, 196 | lastTime = now(); 197 | 198 | processNextToken(); 199 | } 200 | 201 | } 202 | }); 203 | 204 | $.extend($.simulate.prototype.simulateKeySequence.prototype, 205 | 206 | /** 207 | * @lends $.simulate.prototype.simulateKeySequence.prototype 208 | */ 209 | { 210 | 211 | /** 212 | * Maps special character char codes to IE key codes (covers IE and Webkit) 213 | * @author julrich 214 | * @since 1.0 215 | */ 216 | IEKeyCodeTable: { 217 | 33: 49, // ! -> 1 218 | 64: 50, // @ -> 2 219 | 35: 51, // # -> 3 220 | 36: 52, // $ -> 4 221 | 37: 53, // % -> 5 222 | 94: 54, // ^ -> 6 223 | 38: 55, // & -> 7 224 | 42: 56, // * -> 8 225 | 40: 57, // ( -> 9 226 | 41: 48, // ) -> 0 227 | 228 | 59: 186, // ; -> 186 229 | 58: 186, // : -> 186 230 | 61: 187, // = -> 187 231 | 43: 187, // + -> 187 232 | 44: 188, // , -> 188 233 | 60: 188, // < -> 188 234 | 45: 189, // - -> 189 235 | 95: 189, // _ -> 189 236 | 46: 190, // . -> 190 237 | 62: 190, // > -> 190 238 | 47: 191, // / -> 191 239 | 63: 191, // ? -> 191 240 | 96: 192, // ` -> 192 241 | 126: 192, // ~ -> 192 242 | 91: 219, // [ -> 219 243 | 123: 219, // { -> 219 244 | 92: 220, // \ -> 220 245 | 124: 220, // | -> 220 246 | 93: 221, // ] -> 221 247 | 125: 221, // } -> 221 248 | 39: 222, // ' -> 222 249 | 34: 222 // " -> 222 250 | }, 251 | 252 | /** 253 | * Tries to convert character codes to key codes. 254 | * @param {Numeric} character - A character code 255 | * @returns {Numeric} The key code corresponding to the given character code, 256 | * based on the key code table of InternetExplorer. If no corresponding key code 257 | * could be found (which will be the case for all special characters except the common 258 | * ones), the character code itself is returned. However, keyCode === charCode 259 | * does not imply that no key code was found because some key codes are identical to the 260 | * character codes (e.g. for uppercase characters). 261 | * @requires $.simulate.prototype.simulateKeySequence.prototype.IEKeyCodeTable 262 | * @see $.simulate.prototype.simulateKeySequence.prototype.IEKeyCodeTable 263 | * @author julrich 264 | * @since 1.0 265 | */ 266 | charToKeyCode: function(character) { 267 | var specialKeyCodeTable = $.simulate.prototype.simulateKeySequence.prototype.IEKeyCodeTable; 268 | var charCode = character.charCodeAt(0); 269 | 270 | if (charCode >= 64 && charCode <= 90 || charCode >= 48 && charCode <= 57) { 271 | // A-Z and 0-9 272 | return charCode; 273 | } 274 | else if (charCode >= 97 && charCode <= 122) { 275 | // a-z -> A-Z 276 | return character.toUpperCase().charCodeAt(0); 277 | } 278 | else if (specialKeyCodeTable[charCode] !== undefined) { 279 | return specialKeyCodeTable[charCode]; 280 | } 281 | else { 282 | return charCode; 283 | } 284 | } 285 | }); 286 | 287 | // add the functions publicly so they can be overridden 288 | $.simulate.prototype.simulateKeySequence.defaults = { 289 | 290 | /** 291 | * Simulates key strokes of "normal" characters (i.e. non-special sequences). 292 | * @param {Object} rng - bililiteRange object of the simulation target element. 293 | * @param {String} s - String of (simple) characters to be simulated. 294 | * @param {Object} opts - The key-sequence options. 295 | * @author Daniel Wachsstock, julrich 296 | * @since 1.0 297 | */ 298 | simplechar: function (rng, s, opts){ 299 | rng.text(s, 'end'); 300 | if (opts.triggerKeyEvents) { 301 | for (var i =0; i < s.length; i += 1){ 302 | var charCode = s.charCodeAt(i); 303 | var keyCode = $.simulate.prototype.simulateKeySequence.prototype.charToKeyCode(s.charAt(i)); 304 | // a bit of cheating: rng._el is the element associated with rng. 305 | $(rng._el).simulate('keydown', $.extend({}, opts.eventProps, {keyCode: keyCode})); 306 | $(rng._el).simulate('keypress', $.extend({}, opts.eventProps,{keyCode: charCode, which: charCode, charCode: charCode})); 307 | $(rng._el).simulate('keyup', $.extend({}, opts.eventProps, {keyCode: keyCode})); 308 | } 309 | } 310 | }, 311 | 312 | /** 313 | * Simulates key strokes of a curly opening bracket. 314 | * @param {Object} rng - bililiteRange object of the simulation target element. 315 | * @param {String} s - Ignored. 316 | * @param {Object} opts - The key-sequence options. 317 | * @author Daniel Wachsstock, julrich 318 | * @since 1.0 319 | */ 320 | '{{}': function (rng, s, opts){ 321 | $.simulate.prototype.simulateKeySequence.defaults.simplechar(rng, '{', opts); 322 | }, 323 | 324 | /** 325 | * Simulates hitting the enter button. 326 | * @param {Object} rng - bililiteRange object of the simulation target element. 327 | * @param {String} s - Ignored. 328 | * @param {Object} opts - The key-sequence options. 329 | * @author Daniel Wachsstock, julrich 330 | * @since 1.0 331 | */ 332 | '{enter}': function (rng, s, opts){ 333 | rng.insertEOL(); 334 | rng.select(); 335 | if (opts.triggerKeyEvents === true) { 336 | $(rng._el).simulate('keydown', $.extend({}, opts.eventProps, {keyCode: 13})); 337 | $(rng._el).simulate('keypress', $.extend({}, opts.eventProps, {keyCode: 13, which: 13, charCode: 13})); 338 | $(rng._el).simulate('keyup', $.extend({}, opts.eventProps, {keyCode: 13})); 339 | } 340 | }, 341 | 342 | /** 343 | * Simulates hitting the backspace button. 344 | * @param {Object} rng - bililiteRange object of the simulation target element. 345 | * @param {String} s - Ignored. 346 | * @param {Object} opts - The key-sequence options. 347 | * @author Daniel Wachsstock, julrich 348 | * @since 1.0 349 | */ 350 | '{backspace}': function (rng, s, opts){ 351 | var b = rng.bounds(); 352 | if (b[0] === b[1]) { rng.bounds([b[0]-1, b[0]]); } // no characters selected; it's just an insertion point. Remove the previous character 353 | rng.text('', 'end'); // delete the characters and update the selection 354 | if (opts.triggerKeyEvents === true) { 355 | $(rng._el).simulate('keydown', $.extend({}, opts.eventProps, {keyCode: 8})); 356 | $(rng._el).simulate('keyup', $.extend({}, opts.eventProps, {keyCode: 8})); 357 | } 358 | }, 359 | 360 | /** 361 | * Simulates hitting the delete button. 362 | * @param {Object} rng - bililiteRange object of the simulation target element. 363 | * @param {String} s - Ignored. 364 | * @param {Object} opts - The key-sequence options. 365 | * @author Daniel Wachsstock, julrich 366 | * @since 1.0 367 | */ 368 | '{del}': function (rng, s, opts){ 369 | var b = rng.bounds(); 370 | if (b[0] === b[1]) { rng.bounds([b[0], b[0]+1]); } // no characters selected; it's just an insertion point. Remove the next character 371 | rng.text('', 'end'); // delete the characters and update the selection 372 | if (opts.triggerKeyEvents === true) { 373 | $(rng._el).simulate('keydown', $.extend({}, opts.eventProps, {keyCode: 46})); 374 | $(rng._el).simulate('keyup', $.extend({}, opts.eventProps, {keyCode: 46})); 375 | } 376 | }, 377 | 378 | /** 379 | * Simulates hitting the right arrow button. 380 | * @param {Object} rng - bililiteRange object of the simulation target element. 381 | * @param {String} s - Ignored. 382 | * @param {Object} opts - The key-sequence options. 383 | * @author Daniel Wachsstock, julrich 384 | * @since 1.0 385 | */ 386 | '{rightarrow}': function (rng, s, opts){ 387 | var b = rng.bounds(); 388 | if (b[0] === b[1]) { b[1] += 1; } // no characters selected; it's just an insertion point. Move to the right 389 | rng.bounds([b[1], b[1]]).select(); 390 | if (opts.triggerKeyEvents === true) { 391 | $(rng._el).simulate('keydown', $.extend({}, opts.eventProps, {keyCode: 39})); 392 | $(rng._el).simulate('keyup', $.extend({}, opts.eventProps, {keyCode: 39})); 393 | } 394 | }, 395 | 396 | /** 397 | * Simulates hitting the left arrow button. 398 | * @param {Object} rng - bililiteRange object of the simulation target element. 399 | * @param {String} s - Ignored. 400 | * @param {Object} opts - The key-sequence options. 401 | * @author Daniel Wachsstock, julrich 402 | * @since 1.0 403 | */ 404 | '{leftarrow}': function (rng, s, opts){ 405 | var b = rng.bounds(); 406 | if (b[0] === b[1]) { b[0] -= 1; } // no characters selected; it's just an insertion point. Move to the left 407 | rng.bounds([b[0], b[0]]).select(); 408 | if (opts.triggerKeyEvents === true) { 409 | $(rng._el).simulate('keydown', $.extend({}, opts.eventProps, {keyCode: 37})); 410 | $(rng._el).simulate('keyup', $.extend({}, opts.eventProps, {keyCode: 37})); 411 | } 412 | }, 413 | 414 | /** 415 | * Selects all characters in the target element. 416 | * @param {Object} rng - bililiteRange object of the simulation target element. 417 | * @author Daniel Wachsstock, julrich 418 | * @since 1.0 419 | */ 420 | '{selectall}' : function (rng){ 421 | rng.bounds('all').select(); 422 | } 423 | }; 424 | 425 | 426 | 427 | 428 | //####### Quirk detection ####### 429 | if ($.simulate.ext_disableQuirkDetection !== true) { // Fixes issue #9 (https://github.com/j-ulrich/jquery-simulate-ext/issues/9) 430 | $(document).ready(function() { 431 | // delayedSpacesInNonInputGlitchToEnd 432 | // See issues #6 (https://github.com/j-ulrich/jquery-simulate-ext/issues/6) 433 | /* Append a div to the document (bililiteRange needs the element to be in the document), simulate 434 | * a delayed sequence containing a space in the middle and check if the space moves to the end. 435 | */ 436 | var $testDiv = $('
').css({height: 1, width: 1, position: 'absolute', left: -1000, top: -1000}).appendTo('body'); 437 | $testDiv.simulate('key-sequence', {sequence: '\xA0 \xA0', delay:1, callback: function() { 438 | $.simulate.prototype.quirks.delayedSpacesInNonInputGlitchToEnd = ($testDiv.text() === '\xA0\xA0 '); 439 | $testDiv.remove(); 440 | }}); 441 | }); 442 | } 443 | 444 | })(jQuery); -------------------------------------------------------------------------------- /tests/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "camelcase": true, 4 | "plusplus": true, 5 | "forin": true, 6 | "noarg": true, 7 | "noempty": true, 8 | "eqeqeq": true, 9 | "bitwise": true, 10 | "strict": true, 11 | "undef": true, 12 | "unused": true, 13 | "curly": true, 14 | "browser": true, 15 | "devel": true, 16 | "white": false, 17 | "onevar": false, 18 | "-W106": true, 19 | 20 | "globals": { 21 | "jQuery": true, 22 | "$": true, 23 | "tests": true, 24 | "test": true, 25 | "module": true, 26 | "QUnit": true, 27 | "strictEqual": true, 28 | "ok": true, 29 | "equal": true, 30 | "deepEqual": true, 31 | "notStrictEqual": true, 32 | "notEqual": true, 33 | "stop": true, 34 | "start": true, 35 | "expect": true, 36 | "asyncTest": true 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /tests/iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
6 | 7 | -------------------------------------------------------------------------------- /tests/key-combo.js: -------------------------------------------------------------------------------- 1 | /*jshint camelcase:true, plusplus:true, forin:true, noarg:true, noempty:true, eqeqeq:true, bitwise:true, strict:true, undef:true, unused:true, curly:true, browser:true, devel:true, maxerr:100, white:false, onevar:false */ 2 | /*global jQuery:true $:true */ 3 | 4 | (function ($, undefined) { 5 | "use strict"; 6 | 7 | $(document).ready(function() { 8 | 9 | module("key-combo", { 10 | setup: function() { 11 | tests.testSetup(); 12 | $('#textInput').val(""); 13 | }, 14 | teardown: function() { 15 | tests.testTearDown(); 16 | } 17 | }); 18 | 19 | //####### Test Functions ####### 20 | test("simple combo", function() { 21 | var $testElement = $('#textInput'); 22 | 23 | tests.expectedEvents = [ 24 | /* a */ {type: "keydown", keyCode: 65}, {type: "keypress", which: "a".charCodeAt(0)}, 25 | /* S */ {type: "keydown", keyCode: 83}, {type: "keypress", which: "S".charCodeAt(0)}, 26 | /* d */ {type: "keydown", keyCode: 68}, {type: "keypress", which: "d".charCodeAt(0)}, 27 | /* F */ {type: "keydown", keyCode: 70}, {type: "keypress", which: "F".charCodeAt(0)}, 28 | /* F */ {type: "keyup", keyCode: 70}, 29 | /* d */ {type: "keyup", keyCode: 68}, 30 | /* S */ {type: "keyup", keyCode: 83}, 31 | /* a */ {type: "keyup", keyCode: 65} 32 | ]; 33 | 34 | $testElement.simulate("key-combo", {combo: "a+S+d+F"}); 35 | 36 | strictEqual($testElement.val(), "aSdF", "Verify result of sequence"); 37 | }); 38 | 39 | test("events only", function() { 40 | var $testElement = $('#textInput'); 41 | 42 | tests.expectedEvents = [ 43 | /* a */ {type: "keydown", keyCode: 65}, {type: "keypress", which: "a".charCodeAt(0)}, 44 | /* S */ {type: "keydown", keyCode: 83}, {type: "keypress", which: "S".charCodeAt(0)}, 45 | /* d */ {type: "keydown", keyCode: 68}, {type: "keypress", which: "d".charCodeAt(0)}, 46 | /* F */ {type: "keydown", keyCode: 70}, {type: "keypress", which: "F".charCodeAt(0)}, 47 | /* F */ {type: "keyup", keyCode: 70}, 48 | /* d */ {type: "keyup", keyCode: 68}, 49 | /* S */ {type: "keyup", keyCode: 83}, 50 | /* a */ {type: "keyup", keyCode: 65} 51 | ]; 52 | 53 | $testElement.simulate("key-combo", {combo: "a+S+d+F", eventsOnly: true}); 54 | 55 | strictEqual($testElement.val(), "", "Verify result of sequence"); 56 | }); 57 | 58 | test("modifiers", function() { 59 | var $testElement = $('#textInput'); 60 | 61 | tests.expectedEvents = [ 62 | /* ctrl */ {type: "keydown", keyCode: 17, ctrlKey: true, shiftKey: false, altKey: false, metaKey: false}, 63 | /* shift */ {type: "keydown", keyCode: 16, ctrlKey: true, shiftKey: true, altKey: false, metaKey: false}, 64 | /* alt */ {type: "keydown", keyCode: 18, ctrlKey: true, shiftKey: true, altKey: true, metaKey: false}, 65 | /* meta */ {type: "keydown", keyCode: 91, ctrlKey: true, shiftKey: true, altKey: true, metaKey: true}, 66 | /* a */ {type: "keydown", keyCode: 65, ctrlKey: true, shiftKey: true, altKey: true, metaKey: true}, {type: "keypress", which: 65, ctrlKey: true, shiftKey: true, altKey: true, metaKey: true}, 67 | /* a */ {type: "keyup", keyCode: 65, ctrlKey: true, shiftKey: true, altKey: true, metaKey: true}, 68 | /* meta */ {type: "keyup", keyCode: 91, ctrlKey: true, shiftKey: true, altKey: true, metaKey: false}, 69 | /* alt */ {type: "keyup", keyCode: 18, ctrlKey: true, shiftKey: true, altKey: false, metaKey: false}, 70 | /* shift */ {type: "keyup", keyCode: 16, ctrlKey: true, shiftKey: false, altKey: false, metaKey: false}, 71 | /* ctrl */ {type: "keyup", keyCode: 17, ctrlKey: false, shiftKey: false, altKey: false, metaKey: false} 72 | ]; 73 | 74 | $testElement.simulate("key-combo", {combo: "ctrl+shift+alt+meta+a"}); 75 | 76 | strictEqual($testElement.val(), "", "Verify result of sequence"); 77 | }); 78 | 79 | 80 | test("shift", function() { 81 | var $testElement = $('#textInput'); 82 | 83 | tests.expectedEvents = [ 84 | /* shift */ {type: "keydown", keyCode: 16, ctrlKey: false, shiftKey: true, altKey: false, metaKey: false}, 85 | /* a */ {type: "keydown", keyCode: 65, ctrlKey: false, shiftKey: true, altKey: false, metaKey: false}, {type: "keypress", which: "A".charCodeAt(0), ctrlKey: false, shiftKey: true, altKey: false, metaKey: false}, 86 | /* a */ {type: "keyup", keyCode: 65, ctrlKey: false, shiftKey: true, altKey: false, metaKey: false}, 87 | /* shift */ {type: "keyup", keyCode: 16, ctrlKey: false, shiftKey: false, altKey: false, metaKey: false} 88 | ]; 89 | 90 | $testElement.simulate("key-combo", {combo: "shift+a"}); 91 | 92 | strictEqual($testElement.val(), "A", "Verify result of sequence"); 93 | }); 94 | 95 | test("modifier without shift", function() { 96 | var $testElement = $('#textInput'); 97 | 98 | tests.expectedEvents = [ 99 | /* ctrl */ {type: "keydown", keyCode: 17, ctrlKey: true, shiftKey: false, altKey: false, metaKey: false}, 100 | /* a */ {type: "keydown", keyCode: 65, ctrlKey: true, shiftKey: false, altKey: false, metaKey: false}, {type: "keypress", which: "a".charCodeAt(0), ctrlKey: true, shiftKey: false, altKey: false, metaKey: false}, 101 | /* a */ {type: "keyup", keyCode: 65, ctrlKey: true, shiftKey: false, altKey: false, metaKey: false}, 102 | /* ctrl */ {type: "keyup", keyCode: 17, ctrlKey: false, shiftKey: false, altKey: false, metaKey: false} 103 | ]; 104 | 105 | $testElement.simulate("key-combo", {combo: "ctrl+a"}); 106 | 107 | strictEqual($testElement.val(), "", "Verify result of sequence"); 108 | }); 109 | 110 | test("special character combo", function() { 111 | var $testElement = $('#textInput'); 112 | 113 | tests.expectedEvents = [ 114 | /* ctrl */ {type: "keydown", keyCode: 17, ctrlKey: true, shiftKey: false, altKey: false, metaKey: false}, 115 | /* + */ {type: "keydown", keyCode: 187, ctrlKey: true, shiftKey: false, altKey: false, metaKey: false}, {type: "keypress", which: "+".charCodeAt(0), ctrlKey: true, shiftKey: false, altKey: false, metaKey: false}, 116 | /* + */ {type: "keyup", keyCode: 187, ctrlKey: true, shiftKey: false, altKey: false, metaKey: false}, 117 | /* ctrl */ {type: "keyup", keyCode: 17, ctrlKey: false, shiftKey: false, altKey: false, metaKey: false} 118 | ]; 119 | 120 | $testElement.simulate("key-combo", {combo: "ctrl++"}); 121 | 122 | strictEqual($testElement.val(), "", "Verify result of sequence"); 123 | }); 124 | 125 | test("special key combo", function() { 126 | var $testElement = $('#textInput'); 127 | 128 | tests.expectedEvents = [ 129 | /* ctrl */ {type: "keydown", keyCode: 17, ctrlKey: true, shiftKey: false, altKey: false, metaKey: false}, 130 | /* left-arrow */ {type: "keydown", keyCode: 37, ctrlKey: true, shiftKey: false, altKey: false, metaKey: false}, 131 | /* left-arrow */ {type: "keyup", keyCode: 37, ctrlKey: true, shiftKey: false, altKey: false, metaKey: false}, 132 | /* ctrl */ {type: "keyup", keyCode: 17, ctrlKey: false, shiftKey: false, altKey: false, metaKey: false} 133 | ]; 134 | 135 | $testElement.simulate("key-combo", {combo: "ctrl+left-arrow"}); 136 | 137 | strictEqual($testElement.val(), "", "Verify result of sequence"); 138 | }); 139 | 140 | 141 | test("simple combo with eventProps", function() { 142 | var $testElement = $('#textInput'), 143 | customProp = "test!"; 144 | 145 | tests.expectedEvents = [ 146 | /* a */ {type: "keydown", keyCode: 65, myCustomProp: customProp}, 147 | {type: "keypress", which: "a".charCodeAt(0), myCustomProp: customProp}, 148 | /* S */ {type: "keydown", keyCode: 83, myCustomProp: customProp}, 149 | {type: "keypress", which: "S".charCodeAt(0), myCustomProp: customProp}, 150 | /* d */ {type: "keydown", keyCode: 68, myCustomProp: customProp}, 151 | {type: "keypress", which: "d".charCodeAt(0), myCustomProp: customProp}, 152 | /* F */ {type: "keydown", keyCode: 70, myCustomProp: customProp}, 153 | {type: "keypress", which: "F".charCodeAt(0), myCustomProp: customProp}, 154 | /* F */ {type: "keyup", keyCode: 70, myCustomProp: customProp}, 155 | /* d */ {type: "keyup", keyCode: 68, myCustomProp: customProp}, 156 | /* S */ {type: "keyup", keyCode: 83, myCustomProp: customProp}, 157 | /* a */ {type: "keyup", keyCode: 65, myCustomProp: customProp} 158 | ]; 159 | 160 | $testElement.simulate("key-combo", {combo: "a+S+d+F", eventProps: {jQueryTrigger: true, myCustomProp: customProp}}); 161 | 162 | strictEqual($testElement.val(), "aSdF", "Verify result of sequence"); 163 | }); 164 | 165 | test("simple combo with eventProps without jQueryTrigger", function() { 166 | var $testElement = $('#textInput'), 167 | customProp = "test!"; 168 | 169 | tests.expectedEvents = [ 170 | /* a */ {type: "keydown", keyCode: 65, myCustomProp: undefined}, 171 | {type: "keypress", which: "a".charCodeAt(0), myCustomProp: undefined}, 172 | /* S */ {type: "keydown", keyCode: 83, myCustomProp: undefined}, 173 | {type: "keypress", which: "S".charCodeAt(0), myCustomProp: undefined}, 174 | /* d */ {type: "keydown", keyCode: 68, myCustomProp: undefined}, 175 | {type: "keypress", which: "d".charCodeAt(0), myCustomProp: undefined}, 176 | /* F */ {type: "keydown", keyCode: 70, myCustomProp: undefined}, 177 | {type: "keypress", which: "F".charCodeAt(0), myCustomProp: undefined}, 178 | /* F */ {type: "keyup", keyCode: 70, myCustomProp: undefined}, 179 | /* d */ {type: "keyup", keyCode: 68, myCustomProp: undefined}, 180 | /* S */ {type: "keyup", keyCode: 83, myCustomProp: undefined}, 181 | /* a */ {type: "keyup", keyCode: 65, myCustomProp: undefined} 182 | ]; 183 | 184 | $testElement.simulate("key-combo", {combo: "a+S+d+F", eventProps: {jQueryTrigger: false, myCustomProp: customProp}}); 185 | 186 | strictEqual($testElement.val(), "aSdF", "Verify result of sequence"); 187 | }); 188 | 189 | }); 190 | 191 | }(jQuery)); 192 | -------------------------------------------------------------------------------- /tests/key-sequence.js: -------------------------------------------------------------------------------- 1 | /*jshint camelcase:true, plusplus:true, forin:true, noarg:true, noempty:true, eqeqeq:true, bitwise:true, strict:true, undef:true, unused:true, curly:true, browser:true, devel:true, maxerr:100, white:false, onevar:false */ 2 | /*global jQuery:true $:true */ 3 | 4 | (function ($, undefined) { 5 | "use strict"; 6 | 7 | $(document).ready(function() { 8 | 9 | module("key-sequence", { 10 | setup: function() { 11 | tests.testSetup(); 12 | $(document).on("simulate-keySequence", '#qunit-fixture', tests.assertExpectedEvent); 13 | }, 14 | 15 | teardown: function() { 16 | $(document).off("simulate-keySequence", '#qunit-fixture'); 17 | tests.testTearDown(); 18 | } 19 | }); 20 | 21 | //####### Test Functions ####### 22 | test("simple sequence", function() { 23 | var $testElement = $('#textInput'); 24 | 25 | var testSequence = "fO0BaR1"; 26 | 27 | tests.expectedEvents = [ 28 | /* f */ {type: "keydown", keyCode: 70}, {type: "keypress", which: "f".charCodeAt(0)}, {type: "keyup", keyCode: 70}, 29 | /* O */ {type: "keydown", keyCode: 79}, {type: "keypress", which: "O".charCodeAt(0)}, {type: "keyup", keyCode: 79}, 30 | /* 0 */ {type: "keydown", keyCode: 48}, {type: "keypress", which: "0".charCodeAt(0)}, {type: "keyup", keyCode: 48}, 31 | /* B */ {type: "keydown", keyCode: 66}, {type: "keypress", which: "B".charCodeAt(0)}, {type: "keyup", keyCode: 66}, 32 | /* a */ {type: "keydown", keyCode: 65}, {type: "keypress", which: "a".charCodeAt(0)}, {type: "keyup", keyCode: 65}, 33 | /* R */ {type: "keydown", keyCode: 82}, {type: "keypress", which: "R".charCodeAt(0)}, {type: "keyup", keyCode: 82}, 34 | /* 1 */ {type: "keydown", keyCode: 49}, {type: "keypress", which: "1".charCodeAt(0)}, {type: "keyup", keyCode: 49}, 35 | {type: "simulate-keySequence", sequence: testSequence} 36 | ]; 37 | 38 | $testElement.simulate("key-sequence", {sequence: testSequence}); 39 | 40 | strictEqual($testElement.val(), testSequence, "Verify result of sequence"); 41 | }); 42 | 43 | test("sequence callback", function() { 44 | var $testElement = $('#textInput'); 45 | 46 | var testSequence = "test"; 47 | 48 | tests.expectedEvents = [ 49 | /* t */ {type: "keydown", keyCode: 84}, {type: "keypress", which: "t".charCodeAt(0)}, {type: "keyup", keyCode: 84}, 50 | /* e */ {type: "keydown", keyCode: 69}, {type: "keypress", which: "e".charCodeAt(0)}, {type: "keyup", keyCode: 69}, 51 | /* s */ {type: "keydown", keyCode: 83}, {type: "keypress", which: "s".charCodeAt(0)}, {type: "keyup", keyCode: 83}, 52 | /* t */ {type: "keydown", keyCode: 84}, {type: "keypress", which: "t".charCodeAt(0)}, {type: "keyup", keyCode: 84}, 53 | {type: "simulate-keySequence", sequence: testSequence}, 54 | {type: "callback"} 55 | ]; 56 | 57 | $testElement.simulate("key-sequence", {sequence: testSequence, callback: function() { 58 | tests.assertExpectedEvent({type: "callback"}); 59 | }}); 60 | 61 | }); 62 | 63 | 64 | test("no events", function() { 65 | var $testElement = $('#textInput'); 66 | 67 | var testSequence = "fO0BaR1"; 68 | 69 | tests.expectedEvents = [ 70 | {type: "simulate-keySequence", sequence: testSequence} 71 | ]; 72 | 73 | $testElement.simulate("key-sequence", {sequence: testSequence, triggerKeyEvents: false}); 74 | 75 | strictEqual($testElement.val(), testSequence, "Verify result of sequence"); 76 | }); 77 | 78 | test("special characters", function() { 79 | var $testElement = $('#textInput'); 80 | 81 | var testSequence = "_-.,;:+!?"; 82 | 83 | tests.expectedEvents = [ 84 | /* _ */ {type: "keydown", keyCode: 189}, {type: "keypress", which: "_".charCodeAt(0)}, {type: "keyup", keyCode: 189}, 85 | /* - */ {type: "keydown", keyCode: 189}, {type: "keypress", which: "-".charCodeAt(0)}, {type: "keyup", keyCode: 189}, 86 | /* . */ {type: "keydown", keyCode: 190}, {type: "keypress", which: ".".charCodeAt(0)}, {type: "keyup", keyCode: 190}, 87 | /* , */ {type: "keydown", keyCode: 188}, {type: "keypress", which: ",".charCodeAt(0)}, {type: "keyup", keyCode: 188}, 88 | /* ; */ {type: "keydown", keyCode: 186}, {type: "keypress", which: ";".charCodeAt(0)}, {type: "keyup", keyCode: 186}, 89 | /* : */ {type: "keydown", keyCode: 186}, {type: "keypress", which: ":".charCodeAt(0)}, {type: "keyup", keyCode: 186}, 90 | /* + */ {type: "keydown", keyCode: 187}, {type: "keypress", which: "+".charCodeAt(0)}, {type: "keyup", keyCode: 187}, 91 | /* ! */ {type: "keydown", keyCode: 49}, {type: "keypress", which: "!".charCodeAt(0)}, {type: "keyup", keyCode: 49}, 92 | /* ? */ {type: "keydown", keyCode: 191}, {type: "keypress", which: "?".charCodeAt(0)}, {type: "keyup", keyCode: 191}, 93 | {type: "simulate-keySequence", sequence: testSequence} 94 | ]; 95 | 96 | $testElement.simulate("key-sequence", {sequence: testSequence}); 97 | }); 98 | 99 | test("special sequences", function() { 100 | var $testElement = $('#textInput'); 101 | 102 | var testSequence = "as{selectall}{del}f{leftarrow}{{}b{rightarrow}{backspace}"; 103 | 104 | tests.expectedEvents = [ 105 | /* a */ {type: "keydown", keyCode: 65}, {type: "keypress", which: "a".charCodeAt(0)}, {type: "keyup", keyCode: 65}, 106 | /* s */ {type: "keydown", keyCode: 83}, {type: "keypress", which: "s".charCodeAt(0)}, {type: "keyup", keyCode: 83}, 107 | /* {del} */ {type: "keydown", keyCode: 46}, {type: "keyup", keyCode: 46}, 108 | /* f */ {type: "keydown", keyCode: 70}, {type: "keypress", which: "f".charCodeAt(0)}, {type: "keyup", keyCode: 70}, 109 | /* {leftarrow} */ {type: "keydown", keyCode: 37}, {type: "keyup", keyCode: 37}, 110 | /* { */ {type: "keydown", keyCode: 219}, {type: "keypress", which: "{".charCodeAt(0)}, {type: "keyup", keyCode: 219}, 111 | /* b */ {type: "keydown", keyCode: 66}, {type: "keypress", which: "b".charCodeAt(0)}, {type: "keyup", keyCode: 66}, 112 | /* {rightarrow} */ {type: "keydown", keyCode: 39}, {type: "keyup", keyCode: 39}, 113 | /* {backspace} */ {type: "keydown", keyCode: 8}, {type: "keyup", keyCode: 8}, 114 | {type: "simulate-keySequence", sequence: testSequence} 115 | ]; 116 | 117 | $testElement.simulate("key-sequence", {sequence: testSequence}); 118 | 119 | strictEqual($testElement.val(), "{b", "Verify result of sequence (this is known to fail in IE)"); 120 | }); 121 | 122 | test("line break", function() { 123 | var $testElement = $('#textInput'); 124 | 125 | var testSequence = "foo{enter}bar"; 126 | 127 | tests.expectedEvents = [ 128 | /* f */ {type: "keydown", keyCode: 70}, {type: "keypress", which: "f".charCodeAt(0)}, {type: "keyup", keyCode: 70}, 129 | /* o */ {type: "keydown", keyCode: 79}, {type: "keypress", which: "o".charCodeAt(0)}, {type: "keyup", keyCode: 79}, 130 | /* o */ {type: "keydown", keyCode: 79}, {type: "keypress", which: "o".charCodeAt(0)}, {type: "keyup", keyCode: 79}, 131 | /* {enter} */ {type: "keydown", keyCode: 13}, {type: "keypress", which: 13}, {type: "keyup", keyCode: 13}, 132 | /* b */ {type: "keydown", keyCode: 66}, {type: "keypress", which: "b".charCodeAt(0)}, {type: "keyup", keyCode: 66}, 133 | /* a */ {type: "keydown", keyCode: 65}, {type: "keypress", which: "a".charCodeAt(0)}, {type: "keyup", keyCode: 65}, 134 | /* r */ {type: "keydown", keyCode: 82}, {type: "keypress", which: "r".charCodeAt(0)}, {type: "keyup", keyCode: 82}, 135 | {type: "simulate-keySequence", sequence: testSequence} 136 | ]; 137 | 138 | $testElement.simulate("key-sequence", {sequence: testSequence}); 139 | 140 | strictEqual($testElement.val(), "foo\nbar", "Verify result of sequence (this is known to fail in Opera on Windows)"); 141 | }); 142 | 143 | test("delay", function() { 144 | var $testElement = $('#textInput'); 145 | 146 | var keyDelay = 100, 147 | testSequence = "test", 148 | lastEventOccurrence; 149 | 150 | tests.expectedEvents = [ 151 | /* t */ {type: "keydown", keyCode: 84}, {type: "keypress", which: "t".charCodeAt(0)}, {type: "keyup", keyCode: 84}, 152 | /* e */ {type: "keydown", keyCode: 69}, {type: "keypress", which: "e".charCodeAt(0)}, {type: "keyup", keyCode: 69}, 153 | /* s */ {type: "keydown", keyCode: 83}, {type: "keypress", which: "s".charCodeAt(0)}, {type: "keyup", keyCode: 83}, 154 | /* t */ {type: "keydown", keyCode: 84}, {type: "keypress", which: "t".charCodeAt(0)}, {type: "keyup", keyCode: 84}, 155 | {type: "simulate-keySequence", sequence: testSequence } 156 | ]; 157 | 158 | 159 | // Unbind "normal" assertExpectedEvent function and replace with a fuzzy variant of it 160 | function assertExpectedEventDelay(event) { 161 | var delay = Date.now() - lastEventOccurrence; 162 | ok(delay >= keyDelay-10, "Verify events occur delayed (delay: "+delay+")"); // keyDelay-10 means tolerance of 10ms 163 | for (var prop in tests.expectedEvents[0]) { 164 | if (tests.expectedEvents[0].hasOwnProperty(prop)) { 165 | strictEqual(event[prop], tests.expectedEvents[0][prop], "Comparing "+prop+" (expected: "+tests.expectedEvents[0][prop]+")"); 166 | } 167 | } 168 | if (event.type === tests.expectedEvents[0].type) { 169 | tests.expectedEvents.shift(); 170 | } 171 | lastEventOccurrence = Date.now(); 172 | } 173 | $(document).off("keydown", "#qunit-fixture", tests.assertExpectedEvent).on("keydown", "#qunit-fixture", assertExpectedEventDelay); 174 | 175 | stop(); 176 | lastEventOccurrence = Date.now()-keyDelay; // The first key simulated without delay 177 | 178 | $testElement.simulate("key-sequence", {sequence: testSequence, delay: keyDelay}); 179 | 180 | setTimeout(function() { 181 | start(); 182 | },(testSequence.length+2)*keyDelay); 183 | 184 | }); 185 | 186 | test("simple sequence with eventProps", function() { 187 | var $testElement = $('#textInput'); 188 | 189 | var testSequence = "test", 190 | customProp = "test!"; 191 | 192 | tests.expectedEvents = [ 193 | /* t */ {type: "keydown", keyCode: 84, myCustomProp: customProp}, 194 | {type: "keypress", which: "t".charCodeAt(0), myCustomProp: customProp}, 195 | {type: "keyup", keyCode: 84, myCustomProp: customProp}, 196 | /* e */ {type: "keydown", keyCode: 69, myCustomProp: customProp}, 197 | {type: "keypress", which: "e".charCodeAt(0), myCustomProp: customProp}, 198 | {type: "keyup", keyCode: 69, myCustomProp: customProp}, 199 | /* s */ {type: "keydown", keyCode: 83, myCustomProp: customProp}, 200 | {type: "keypress", which: "s".charCodeAt(0), myCustomProp: customProp}, 201 | {type: "keyup", keyCode: 83, myCustomProp: customProp}, 202 | /* t */ {type: "keydown", keyCode: 84, myCustomProp: customProp}, 203 | {type: "keypress", which: "t".charCodeAt(0), myCustomProp: customProp}, 204 | {type: "keyup", keyCode: 84, myCustomProp: customProp}, 205 | {type: "simulate-keySequence", sequence: testSequence} 206 | ]; 207 | 208 | $testElement.simulate("key-sequence", {sequence: testSequence, eventProps: {jQueryTrigger: true, myCustomProp: customProp}}); 209 | 210 | strictEqual($testElement.val(), testSequence, "Verify result of sequence"); 211 | }); 212 | 213 | 214 | test("simple sequence with eventProps without jQueryTrigger", function() { 215 | var $testElement = $('#textInput'); 216 | 217 | var testSequence = "test", 218 | customProp = "test!"; 219 | 220 | tests.expectedEvents = [ 221 | /* t */ {type: "keydown", keyCode: 84, myCustomProp: undefined}, 222 | {type: "keypress", which: "t".charCodeAt(0), myCustomProp: undefined}, 223 | {type: "keyup", keyCode: 84, myCustomProp: undefined}, 224 | /* e */ {type: "keydown", keyCode: 69, myCustomProp: undefined}, 225 | {type: "keypress", which: "e".charCodeAt(0), myCustomProp: undefined}, 226 | {type: "keyup", keyCode: 69, myCustomProp: undefined}, 227 | /* s */ {type: "keydown", keyCode: 83, myCustomProp: undefined}, 228 | {type: "keypress", which: "s".charCodeAt(0), myCustomProp: undefined}, 229 | {type: "keyup", keyCode: 83, myCustomProp: undefined}, 230 | /* t */ {type: "keydown", keyCode: 84, myCustomProp: undefined}, 231 | {type: "keypress", which: "t".charCodeAt(0), myCustomProp: undefined}, 232 | {type: "keyup", keyCode: 84, myCustomProp: undefined}, 233 | {type: "simulate-keySequence", sequence: testSequence} 234 | ]; 235 | 236 | $testElement.simulate("key-sequence", {sequence: testSequence, eventProps: {jQueryTrigger: false, myCustomProp: customProp}}); 237 | 238 | strictEqual($testElement.val(), testSequence, "Verify result of sequence"); 239 | }); 240 | 241 | 242 | // See issue #6 (https://github.com/j-ulrich/jquery-simulate-ext/issues/6) 243 | test("delay, spaces in sequence, non-input element", function() { 244 | var $testElement = $('#emptyDiv'); 245 | 246 | var keyDelay = 5, 247 | testSequence = "a b c"; 248 | 249 | tests.expectedEvents = [ 250 | /* a */ {type: "keydown", keyCode: 65}, {type: "keypress", which: "a".charCodeAt(0)}, {type: "keyup", keyCode: 65}, 251 | /* */ {type: "keydown", keyCode: 32}, {type: "keypress", which: " ".charCodeAt(0)}, {type: "keyup", keyCode: 32}, 252 | /* b */ {type: "keydown", keyCode: 66}, {type: "keypress", which: "b".charCodeAt(0)}, {type: "keyup", keyCode: 66}, 253 | /* */ {type: "keydown", keyCode: 32}, {type: "keypress", which: " ".charCodeAt(0)}, {type: "keyup", keyCode: 32}, 254 | /* c */ {type: "keydown", keyCode: 67}, {type: "keypress", which: "c".charCodeAt(0)}, {type: "keyup", keyCode: 67}, 255 | {type: "simulate-keySequence", sequence: testSequence } 256 | ]; 257 | 258 | stop(); 259 | $testElement.simulate("key-sequence", {sequence: testSequence, delay: keyDelay, 260 | callback: function() { 261 | strictEqual($testElement.text(), testSequence, "Verify result of sequence"); 262 | start(); 263 | } 264 | }); 265 | 266 | }); 267 | 268 | //####### Quirk Detection Tests ####### 269 | module("quirk-detection", { 270 | 271 | }); 272 | 273 | asyncTest("quirk detection", 1, function() { 274 | setTimeout(function() { 275 | var iFrameWin = window.frames[0], 276 | iFrameDoc = iFrameWin.document, 277 | scriptLoadTimeout = 100; 278 | 279 | $.Deferred(function(dfd) { 280 | var jquery = iFrameDoc.createElement('script'); 281 | jquery.src = "../bower_components/jquery/dist/jquery.min.js"; 282 | iFrameDoc.body.appendChild(jquery); 283 | setTimeout(dfd.resolve, scriptLoadTimeout); 284 | }).pipe(function() { 285 | return $.Deferred(function(dfd) { 286 | var bililiteRange = iFrameDoc.createElement('script'); 287 | bililiteRange.src = "../libs/bililiteRange.js"; 288 | iFrameDoc.body.appendChild(bililiteRange); 289 | setTimeout(dfd.resolve, scriptLoadTimeout); 290 | }); 291 | }).pipe(function() { 292 | return $.Deferred(function(dfd) { 293 | var jquerySimulate = iFrameDoc.createElement('script'); 294 | jquerySimulate.src = "../libs/jquery.simulate.js"; 295 | iFrameDoc.body.appendChild(jquerySimulate); 296 | setTimeout(dfd.resolve, scriptLoadTimeout); 297 | }); 298 | }).pipe(function() { 299 | return $.Deferred(function(dfd) { 300 | var simulateExt = iFrameDoc.createElement('script'); 301 | simulateExt.src = "../src/jquery.simulate.ext.js"; 302 | iFrameDoc.body.appendChild(simulateExt); 303 | setTimeout(dfd.resolve, scriptLoadTimeout); 304 | }); 305 | }).pipe(function() { 306 | return $.Deferred(function(dfd) { 307 | var simulateExtKeySequence = iFrameDoc.createElement('script'); 308 | simulateExtKeySequence.src = "../src/jquery.simulate.key-sequence.js"; 309 | iFrameDoc.body.appendChild(simulateExtKeySequence); 310 | setTimeout(dfd.resolve, scriptLoadTimeout); 311 | }); 312 | }).pipe(function() { 313 | 314 | notStrictEqual(window.frames[0].jQuery.simulate.prototype.quirks.delayedSpacesInNonInputGlitchToEnd, undefined, "Quirk detection was executed"); 315 | start(); 316 | }); 317 | 318 | }, 1000); 319 | }); 320 | 321 | // See issue #9 (https://github.com/j-ulrich/jquery-simulate-ext/issues/9) 322 | asyncTest("disable quirk detection", 1, function() { 323 | setTimeout(function() { 324 | var iFrameWin = window.frames[0], 325 | iFrameDoc = iFrameWin.document, 326 | scriptLoadTimeout = 100; 327 | 328 | $.Deferred(function(dfd) { 329 | var jquery = iFrameDoc.createElement('script'); 330 | jquery.src = "../bower_components/jquery/dist/jquery.min.js"; 331 | iFrameDoc.body.appendChild(jquery); 332 | setTimeout(dfd.resolve, scriptLoadTimeout); 333 | }).pipe(function() { 334 | return $.Deferred(function(dfd) { 335 | var bililiteRange = iFrameDoc.createElement('script'); 336 | bililiteRange.src = "../libs/bililiteRange.js"; 337 | iFrameDoc.body.appendChild(bililiteRange); 338 | setTimeout(dfd.resolve, scriptLoadTimeout); 339 | }); 340 | }).pipe(function() { 341 | return $.Deferred(function(dfd) { 342 | var jquerySimulate = iFrameDoc.createElement('script'); 343 | jquerySimulate.src = "../libs/jquery.simulate.js"; 344 | iFrameDoc.body.appendChild(jquerySimulate); 345 | setTimeout(dfd.resolve, scriptLoadTimeout); 346 | }); 347 | }).pipe(function() { 348 | window.frames[0].jQuery.simulate.ext_disableQuirkDetection = true; 349 | 350 | return $.Deferred(function(dfd) { 351 | var simulateExt = iFrameDoc.createElement('script'); 352 | simulateExt.src = "../src/jquery.simulate.ext.js"; 353 | iFrameDoc.body.appendChild(simulateExt); 354 | setTimeout(dfd.resolve, scriptLoadTimeout); 355 | }); 356 | }).pipe(function() { 357 | return $.Deferred(function(dfd) { 358 | var simulateExtKeySequence = iFrameDoc.createElement('script'); 359 | simulateExtKeySequence.src = "../src/jquery.simulate.key-sequence.js"; 360 | iFrameDoc.body.appendChild(simulateExtKeySequence); 361 | setTimeout(dfd.resolve, scriptLoadTimeout); 362 | }); 363 | }).pipe(function() { 364 | 365 | strictEqual(window.frames[0].jQuery.simulate.prototype.quirks.delayedSpacesInNonInputGlitchToEnd, undefined, "Quirk detection was disabled"); 366 | start(); 367 | }); 368 | }, 1000); 369 | }); 370 | 371 | }); 372 | 373 | }(jQuery)); -------------------------------------------------------------------------------- /tests/qunit-close-enough.js: -------------------------------------------------------------------------------- 1 | QUnit.extend( QUnit, { 2 | /** 3 | * Checks that the first two arguments are equal, or are numbers close enough to be considered equal 4 | * based on a specified maximum allowable difference. 5 | * 6 | * @example close(3.141, Math.PI, 0.001); 7 | * 8 | * @param Number actual 9 | * @param Number expected 10 | * @param Number maxDifference (the maximum inclusive difference allowed between the actual and expected numbers) 11 | * @param String message (optional) 12 | */ 13 | close: function(actual, expected, maxDifference, message) { 14 | var passes = (actual === expected) || Math.abs(actual - expected) <= maxDifference; 15 | QUnit.push(passes, actual, expected, message); 16 | }, 17 | 18 | /** 19 | * Checks that the first two arguments are numbers with differences greater than the specified 20 | * minimum difference. 21 | * 22 | * @example notClose(3.1, Math.PI, 0.001); 23 | * 24 | * @param Number actual 25 | * @param Number expected 26 | * @param Number minDifference (the minimum exclusive difference allowed between the actual and expected numbers) 27 | * @param String message (optional) 28 | */ 29 | notClose: function(actual, expected, minDifference, message) { 30 | QUnit.push(Math.abs(actual - expected) > minDifference, actual, expected, message); 31 | } 32 | }); -------------------------------------------------------------------------------- /tests/qunit.css: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit v1.9.0pre - A JavaScript Unit Testing Framework 3 | * 4 | * http://docs.jquery.com/QUnit 5 | * 6 | * Copyright (c) 2012 John Resig, Jörn Zaefferer 7 | * Dual licensed under the MIT (MIT-LICENSE.txt) 8 | * or GPL (GPL-LICENSE.txt) licenses. 9 | * Pulled Live from Git Sun Jul 1 07:45:01 UTC 2012 10 | * Last Commit: 1ebf587386dbbe259f363baa71eca20165cfed41 11 | */ 12 | 13 | /** Font Family and Sizes */ 14 | 15 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 16 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 17 | } 18 | 19 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 20 | #qunit-tests { font-size: smaller; } 21 | 22 | 23 | /** Resets */ 24 | 25 | #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { 26 | margin: 0; 27 | padding: 0; 28 | } 29 | 30 | 31 | /** Header */ 32 | 33 | #qunit-header { 34 | padding: 0.5em 0 0.5em 1em; 35 | 36 | color: #8699a4; 37 | background-color: #0d3349; 38 | 39 | font-size: 1.5em; 40 | line-height: 1em; 41 | font-weight: normal; 42 | 43 | border-radius: 5px 5px 0 0; 44 | -moz-border-radius: 5px 5px 0 0; 45 | -webkit-border-top-right-radius: 5px; 46 | -webkit-border-top-left-radius: 5px; 47 | } 48 | 49 | #qunit-header a { 50 | text-decoration: none; 51 | color: #c2ccd1; 52 | } 53 | 54 | #qunit-header a:hover, 55 | #qunit-header a:focus { 56 | color: #fff; 57 | } 58 | 59 | #qunit-testrunner-toolbar label { 60 | display: inline-block; 61 | padding: 0 .5em 0 .1em; 62 | } 63 | 64 | #qunit-banner { 65 | height: 5px; 66 | } 67 | 68 | #qunit-testrunner-toolbar { 69 | padding: 0.5em 0 0.5em 2em; 70 | color: #5E740B; 71 | background-color: #eee; 72 | } 73 | 74 | #qunit-userAgent { 75 | padding: 0.5em 0 0.5em 2.5em; 76 | background-color: #2b81af; 77 | color: #fff; 78 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 79 | } 80 | 81 | 82 | /** Tests: Pass/Fail */ 83 | 84 | #qunit-tests { 85 | list-style-position: inside; 86 | } 87 | 88 | #qunit-tests li { 89 | padding: 0.4em 0.5em 0.4em 2.5em; 90 | border-bottom: 1px solid #fff; 91 | list-style-position: inside; 92 | } 93 | 94 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 95 | display: none; 96 | } 97 | 98 | #qunit-tests li strong { 99 | cursor: pointer; 100 | } 101 | 102 | #qunit-tests li a { 103 | padding: 0.5em; 104 | color: #c2ccd1; 105 | text-decoration: none; 106 | } 107 | #qunit-tests li a:hover, 108 | #qunit-tests li a:focus { 109 | color: #000; 110 | } 111 | 112 | #qunit-tests ol { 113 | margin-top: 0.5em; 114 | padding: 0.5em; 115 | 116 | background-color: #fff; 117 | 118 | border-radius: 5px; 119 | -moz-border-radius: 5px; 120 | -webkit-border-radius: 5px; 121 | } 122 | 123 | #qunit-tests table { 124 | border-collapse: collapse; 125 | margin-top: .2em; 126 | } 127 | 128 | #qunit-tests th { 129 | text-align: right; 130 | vertical-align: top; 131 | padding: 0 .5em 0 0; 132 | } 133 | 134 | #qunit-tests td { 135 | vertical-align: top; 136 | } 137 | 138 | #qunit-tests pre { 139 | margin: 0; 140 | white-space: pre-wrap; 141 | word-wrap: break-word; 142 | } 143 | 144 | #qunit-tests del { 145 | background-color: #e0f2be; 146 | color: #374e0c; 147 | text-decoration: none; 148 | } 149 | 150 | #qunit-tests ins { 151 | background-color: #ffcaca; 152 | color: #500; 153 | text-decoration: none; 154 | } 155 | 156 | /*** Test Counts */ 157 | 158 | #qunit-tests b.counts { color: black; } 159 | #qunit-tests b.passed { color: #5E740B; } 160 | #qunit-tests b.failed { color: #710909; } 161 | 162 | #qunit-tests li li { 163 | padding: 5px; 164 | background-color: #fff; 165 | border-bottom: none; 166 | list-style-position: inside; 167 | } 168 | 169 | /*** Passing Styles */ 170 | 171 | #qunit-tests li li.pass { 172 | color: #3c510c; 173 | background-color: #fff; 174 | border-left: 10px solid #C6E746; 175 | } 176 | 177 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 178 | #qunit-tests .pass .test-name { color: #366097; } 179 | 180 | #qunit-tests .pass .test-actual, 181 | #qunit-tests .pass .test-expected { color: #999999; } 182 | 183 | #qunit-banner.qunit-pass { background-color: #C6E746; } 184 | 185 | /*** Failing Styles */ 186 | 187 | #qunit-tests li li.fail { 188 | color: #710909; 189 | background-color: #fff; 190 | border-left: 10px solid #EE5757; 191 | white-space: pre; 192 | } 193 | 194 | #qunit-tests > li:last-child { 195 | border-radius: 0 0 5px 5px; 196 | -moz-border-radius: 0 0 5px 5px; 197 | -webkit-border-bottom-right-radius: 5px; 198 | -webkit-border-bottom-left-radius: 5px; 199 | } 200 | 201 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 202 | #qunit-tests .fail .test-name, 203 | #qunit-tests .fail .module-name { color: #000000; } 204 | 205 | #qunit-tests .fail .test-actual { color: #EE5757; } 206 | #qunit-tests .fail .test-expected { color: green; } 207 | 208 | #qunit-banner.qunit-fail { background-color: #EE5757; } 209 | 210 | 211 | /** Result */ 212 | 213 | #qunit-testresult { 214 | padding: 0.5em 0.5em 0.5em 2.5em; 215 | 216 | color: #2b81af; 217 | background-color: #D2E0E6; 218 | 219 | border-bottom: 1px solid white; 220 | } 221 | #qunit-testresult .module-name { 222 | font-weight: bold; 223 | } 224 | 225 | /** Fixture */ 226 | 227 | #qunit-fixture { 228 | position: absolute; 229 | top: -10000px; 230 | left: -10000px; 231 | width: 1000px; 232 | height: 1000px; 233 | } 234 | -------------------------------------------------------------------------------- /tests/testInfrastructure.js: -------------------------------------------------------------------------------- 1 | /*jshint camelcase:true, plusplus:true, forin:true, noarg:true, noempty:true, eqeqeq:true, bitwise:true, strict:true, undef:true, unused:true, curly:true, browser:true, devel:true, maxerr:100, white:false, onevar:false */ 2 | /*global jQuery:true $:true */ 3 | 4 | (function ($, undefined) { 5 | "use strict"; 6 | 7 | //####### Test Infrastructure ####### 8 | // Global object 9 | window.tests = { 10 | expectedEvents: [], 11 | 12 | assertExpectedEvent: function(event) { 13 | if (tests.expectedEvents.length === 0) { 14 | ok(false, "Unexpected event: "+event.type); 15 | return; 16 | } 17 | for (var prop in tests.expectedEvents[0]) { 18 | if (tests.expectedEvents[0].hasOwnProperty(prop)) { 19 | strictEqual(event[prop], tests.expectedEvents[0][prop], "Comparing "+prop+" (expected: "+tests.expectedEvents[0][prop]+")"); 20 | } 21 | } 22 | if (event.type === tests.expectedEvents[0].type) { 23 | tests.expectedEvents.shift(); 24 | } 25 | }, 26 | 27 | testSetup: function() { 28 | tests.expectedEvents = []; 29 | $(document).on("keyup keydown keypress mousedown mouseup mousemove click", '#qunit-fixture', tests.assertExpectedEvent); 30 | }, 31 | 32 | testTearDown: function() { 33 | var event; 34 | $(document).off("keyup keydown keypress mousedown mouseup mousemove click", '#qunit-fixture'); 35 | while ( (event = tests.expectedEvents.shift()) !== undefined) { 36 | if (event.type) { 37 | ok(false, "Missing event: "+event.type); 38 | } 39 | else { 40 | ok(false, "Missing event: "+event); 41 | } 42 | } 43 | } 44 | }; 45 | 46 | 47 | }(jQuery)); -------------------------------------------------------------------------------- /tests/tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | jquery-simulate-ext Test Suite 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |

jquery-simulate-ext

24 |

25 |
26 |

27 |
    28 |
    29 | 30 |
    31 |
    32 |
    33 |
    34 | 35 |
    36 |
    37 | 39 |
    40 | 41 | --------------------------------------------------------------------------------