├── .editorconfig ├── .gitignore ├── .travis.yml ├── .yardopts ├── CHANGELOG.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── config.ru ├── examples └── rake │ ├── .gitignore │ ├── Gemfile │ ├── README.md │ ├── Rakefile │ ├── app │ └── application.rb │ └── index.html ├── lib ├── opal-jquery.rb └── opal │ ├── jquery.rb │ └── jquery │ ├── constants.rb │ ├── document.rb │ ├── element.rb │ ├── event.rb │ ├── http.rb │ ├── kernel.rb │ ├── local_storage.rb │ ├── rspec.rb │ ├── version.rb │ └── window.rb ├── opal-jquery.gemspec └── spec-opal ├── document_spec.rb ├── element ├── after_spec.rb ├── animations_spec.rb ├── append_spec.rb ├── append_to_spec.rb ├── at_spec.rb ├── attributes_spec.rb ├── before_spec.rb ├── class_name_spec.rb ├── css_spec.rb ├── display_spec.rb ├── expose_spec.rb ├── height_width_spec.rb ├── inspect_spec.rb ├── iterable_spec.rb ├── length_spec.rb ├── method_missing_spec.rb ├── prepend_spec.rb ├── to_s_spec.rb └── traversing_spec.rb ├── element_spec.rb ├── event_spec.rb ├── fixtures ├── simple.txt └── user.json ├── http_spec.rb ├── jquery ├── index.html.erb ├── index3.html.erb ├── jquery-1.8.3.js └── jquery-3.0.0.js ├── kernel_spec.rb ├── local_storage_spec.rb ├── spec_helper.rb └── zepto ├── index.html.erb └── zepto.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.gem 3 | Gemfile.lock 4 | build 5 | gh-pages 6 | /.bundle 7 | .yardoc 8 | /doc 9 | .ruby-version 10 | .ruby-gemset 11 | .gladiator 12 | .gladiator-scratchfile 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | sudo: false 3 | 4 | fast_finish: true 5 | script: bundle exec rake $RUN 6 | 7 | matrix: 8 | include: 9 | - rvm: 2.4.2 10 | env: OPAL_VERSION='master' 11 | - rvm: 2.3.4 12 | env: OPAL_VERSION='~> 0.10.5' 13 | - rvm: 2.1.10 14 | env: RACK_VERSION='< 2.0' 15 | - rvm: 2.0.0 16 | env: RACK_VERSION='< 2.0' 17 | 18 | # We use 2.1.9 to differentiate the rvm version 19 | # as travis doesn't allow failures by env. 20 | - rvm: 2.1.9 21 | env: RUN=zepto PATH=".:$PATH" RACK_VERSION='< 2.0' 22 | 23 | allow_failures: 24 | - rvm: 2.1.9 # zepto 25 | 26 | 27 | cache: 28 | bundler: true 29 | directories: 30 | - node_modules 31 | 32 | before_install: 33 | - gem update --system 34 | - "export PATH=$PWD/travis_phantomjs/phantomjs-2.1.1-linux-x86_64/bin:$PATH" 35 | - "if [ $(phantomjs --version) != '2.1.1' ]; then rm -rf $PWD/travis_phantomjs; mkdir -p $PWD/travis_phantomjs; fi" 36 | - "if [ $(phantomjs --version) != '2.1.1' ]; then wget https://assets.membergetmember.co/software/phantomjs-2.1.1-linux-x86_64.tar.bz2 -O $PWD/travis_phantomjs/phantomjs-2.1.1-linux-x86_64.tar.bz2; fi" 37 | - "if [ $(phantomjs --version) != '2.1.1' ]; then tar -xvf $PWD/travis_phantomjs/phantomjs-2.1.1-linux-x86_64.tar.bz2 -C $PWD/travis_phantomjs; fi" 38 | - "phantomjs --version" 39 | 40 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --markup markdown 2 | - 3 | CHANGELOG.md 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.5.2](https://github.com/opal/opal-jquery/compare/v0.5.1...v0.5.2) 2024-07-31 2 | 3 | * Fix issue with incorrectly auto-setting content-type as 'application/json' when payload is null (#122) 4 | 5 | ## [0.5.1](https://github.com/opal/opal-jquery/compare/v0.5.0...v0.5.1) 2024-01-08 6 | 7 | * Added `# backtick_javascript: true` where needed to satisfy new Opal requirement (#121) 8 | 9 | ## [0.5.0](https://github.com/opal/opal-jquery/compare/v0.4.6...v0.5.0) 2024-01-05 10 | 11 | * Added `Event#location` (#114) 12 | * Add `Event#touch_count`; access each point via an optional `index` on `Event#touch_x`/`Event#touch_y` (#117) 13 | * Add setting to prevent payload automatic processing (#103) **breaking** 14 | * Remove untaint method for Ruby 3.2 compatibility (#119) **breaking** 15 | 16 | ## [0.4.6](https://github.com/opal/opal-jquery/compare/v0.4.5...v0.4.6) 2021-10-11 17 | 18 | * Initialize @@__isReady (#115) 19 | 20 | * Added `Element#select` (#111) 21 | 22 | ## [0.4.5](https://github.com/opal/opal-jquery/compare/v0.4.4...v0.4.5) 2021-07-28 23 | 24 | * Expose `Element#replace_all` and `Element#replace_with` (#110) 25 | 26 | * Open the opal dependency to any 1.x 27 | 28 | ## [0.4.4](https://github.com/opal/opal-jquery/compare/v0.4.3...v0.4.4) 2019-07-01 29 | 30 | * Use `::Native::Wrapper` where available 31 | 32 | * Allow Opal 1.0 in gemspec 33 | 34 | ## [0.4.3](https://github.com/opal/opal-jquery/compare/v0.4.2...v0.4.3) 2018-09-07 35 | 36 | * Add `Element#==` as an alias of `.is()` 37 | 38 | * Add `Element#method_missing` to allow not yet wrapped methods and plugins to be accessed with zero setup 39 | 40 | * Avoid `||` in JS-land because it would consider some values as falsy (e.g. `""` and `0`). **breaking** 41 | 42 | * Call `Element#prop` via `Native.call` to get the right semantics around `nil` vs. `undefined` **breaking** 43 | 44 | * Expose `Element#click` 45 | 46 | * Fix semantics of `Element#attr` to better reflect jQuery's **breaking** 47 | 48 | * Skip sending a callback to `Element#animate` if no block is given 49 | 50 | * Let `Element#data` return a usable Ruby object (`Array`/`Hash`) instead of a native one **breaking** 51 | 52 | * Don't wrap events with `Event.new` if no args are provided or the event is not a native object to increase performance in `Element#on` and `Element#one` 53 | 54 | * Rename the internal property holding the callback wrapper in `Element#on` and `Element#one` from `._jq_wrap` to `.$$jqwrap` to avoid polluting instance variables and following the custom of Opal's core classes 55 | 56 | * Fix `Element#value`, `Element#height` and `Element#width` to perform the `||` at ruby level to avoid overwriting values that are *falsy* in JavaScript with `nil` **breaking** 57 | 58 | * Add `Element#==` as an alias to jQuery's `.is()` 59 | 60 | * Add `Element#method_missing` and `Element#respond_to_missing?` to forward calls to native plugins 61 | 62 | * Add `HTTP#inspect` with a basic summary 63 | 64 | * Updated specs to also use jQuery 3 65 | 66 | * Allow Opal v0.11.0 67 | 68 | ## [0.4.2](https://github.com/opal/opal-jquery/compare/v0.4.1...v0.4.2) 2016-07-04 69 | 70 | * Allow Opal v0.10.0 71 | 72 | ## [0.4.1](https://github.com/opal/opal-jquery/compare/v0.4.0...v0.4.1) 2015-11-02 73 | 74 | * Updated specs to use jQuery 1.8 (that was initially released in 2012) 75 | 76 | * `Element.parse` now relies on `$.parseHTML` in conjunction with `$` enforcing actual HTML parsing. 77 | 78 | * Added `Document.ready` that returns a promise for `Document.ready?` 79 | 80 | * `Document.ready?` now works even after the document is loaded (unlike jQuery) 81 | 82 | ## [0.4.0](https://github.com/opal/opal-jquery/compare/v0.3.0...v0.4.0) 2015-07-17 83 | 84 | * `Element#[]=` now removes the attribute when the assigned value is nil. 85 | 86 | * `Element#attr` now better follows jQuery's behaviour by checking the number of arguments passed. Also it's now just a proxy to `Element#[]` and `Element#[]=` 87 | 88 | * `Element#[]` now returns `nil` only for attributes that are missing, better following jQuery behaviour (which is to return `undefined` on such attributes). `Element#has_attribute?` has been updated accordingly. 89 | 90 | * Add `Element#prepend` method. 91 | 92 | ## 0.3.0 2015-02-03 93 | 94 | * Move all files under `opal/jquery` require namespace, rather than 95 | current `opal-jquery` require paths. 96 | 97 | * Add `Browser::Window` class, and make `::Window` an instance of it. 98 | 99 | * Make `Document` include `Browser::DocumentMethods` which is a simple 100 | module to define custom methods for `Document`. 101 | 102 | * Cleanup HTTP implementation. 103 | 104 | * `Element#[]` and `Element#attr` now return `nil` for empty attributes, 105 | instead of returning an empty string. 106 | 107 | * Add `HTTP.setup` and `HTTP.setup=` to access `$.ajaxSetup` 108 | 109 | * Add PATCH and HEAD support to `HTTP` 110 | 111 | * Let `Element` accept previously defined `JQUERY_CLASS` and `JQUERY_SELECTOR` 112 | for environments such as node-webkit where `$` can't be found in the global object. 113 | 114 | * Add Promise support to `HTTP` get/post/put/delete methods. Also remove 115 | `HTTP#callback` and `#errback` methods. 116 | 117 | ## 0.2.0 2014-03-12 118 | 119 | * Add `Document.body` and `Document.head` shortcut to element instances. 120 | 121 | * Add `Event` methods: `prevented?`, `prevent`, `stopped?` and `stop` to 122 | replace longer javascript names. 123 | 124 | * Add `LocalStorage` implementation. 125 | 126 | * Fix `Element#data()` to return `nil` for an undefined data attribute 127 | instead of null. 128 | 129 | * Expose `#detach` method on `Element`. 130 | 131 | ## 0.1.2 2013-12-01 132 | 133 | * Support setting html content through `Element#html()`. 134 | 135 | * Add `Element` methods: `#get`, `#attr` and `#prop` by aliasing them to 136 | jquery implementations. 137 | 138 | ## 0.1.1 2013-11-03 139 | 140 | * Require `native` from stdlib for `HTTP` to use. 141 | 142 | ## 0.1.0 2013-11-03 143 | 144 | * Add `Window` and `$window` alias. 145 | 146 | * Support `Zepto` as well as `jQuery`. 147 | 148 | * `Event` is now a wrapper around native event from dom listeners. 149 | 150 | ## 0.0.9 2013-06-15 151 | 152 | * Revert earlier commit, and use `$document` as reference to jquery 153 | wrapped `document`. 154 | 155 | * Introduce Element.document as wrapped document element 156 | 157 | * Depreceate $document.title and $document.title=. 158 | 159 | ## 0.0.8 2013-05-02 160 | 161 | * Depreceate Document in favor of $document global. 162 | 163 | * Add HTTP.delete() for creating DELETE request. 164 | 165 | ## 0.0.7 2013-02-20 166 | 167 | * Add Element#method\_missing which forwards missing calls to try and call 168 | method as a native jquery function/plugin. 169 | 170 | * Depreceate Document finder methods (Document.find, Document[]). The finder 171 | methods on Element now replace them. Updated specs to suit. 172 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gemspec 3 | 4 | case (opal_version = ENV['OPAL_VERSION']) 5 | when 'master' 6 | gem 'opal', github: 'opal/opal', branch: 'master' 7 | gem 'opal-sprockets', github: 'opal/opal-sprockets' 8 | gem 'opal-rspec', github: 'opal/opal-rspec', branch: 'master', submodules: true 9 | when nil 10 | gem 'opal' # let bundler pick a version 11 | else 12 | gem 'opal', opal_version 13 | end 14 | 15 | # gem 'opal-rspec', github: 'opal/opal-rspec', submodules: true 16 | # gem 'opal-rspec', path: '../opal-rspec' 17 | gem 'rack', ENV['RACK_VERSION'] || '> 0' 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013 by Adam Beynon 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Opal jQuery 2 | 3 | *jQuery wrapper for Opal* 4 | 5 | [![Build Status](http://img.shields.io/travis/opal/opal-jquery/master.svg)](http://travis-ci.org/opal/opal-jquery) 6 | 7 | **opal-jquery** provides DOM access to opal by wrapping jQuery (or zepto) 8 | and providing a nice ruby syntax for dealing with jQuery instances. 9 | 10 | 11 | 12 | 13 | 14 | 15 | ## Installation 16 | 17 | Install opal-jquery from RubyGems: 18 | 19 | ``` 20 | $ gem install opal-jquery 21 | ``` 22 | 23 | Or include it in your Gemfile for Bundler: 24 | 25 | ```ruby 26 | gem 'opal-jquery' 27 | ``` 28 | 29 | 30 | 31 | 32 | ## Running Specs 33 | 34 | Get the dependencies: 35 | 36 | $ bundle install 37 | 38 | 39 | ### Browser 40 | 41 | You can run the specs in any web browser, by running the `config.ru` rack file: 42 | 43 | $ bundle exec rackup 44 | 45 | And then visiting `http://localhost:9292` in any web browser. 46 | 47 | 48 | ### Phantomjs 49 | 50 | You will need phantomjs to run the specs outside the browser. It can 51 | be downloaded at [http://phantomjs.org/download.html](http://phantomjs.org/download.html) 52 | 53 | On osx you can install through homebrew 54 | 55 | $ brew update; brew install phantomjs 56 | 57 | Run the tests inside a phantom.js runner: 58 | 59 | $ bundle exec rake 60 | 61 | 62 | ### Zepto 63 | 64 | opal-jquery also supports zepto. To run specs for zepto use the rake task: 65 | 66 | $ bundle exec rake zepto 67 | 68 | 69 | 70 | 71 | ## Getting Started 72 | 73 | 74 | ### Usage 75 | 76 | `opal-jquery` can now be easily added to your opal application sources using a 77 | standard require: 78 | 79 | ```ruby 80 | # app/application.rb 81 | require 'opal' 82 | require 'jquery' 83 | require 'opal-jquery' 84 | 85 | alert "Hello from jquery + opal" 86 | ``` 87 | 88 | > **Note**: this file requires two important dependencies, `jquery` and `opal-jquery`. 89 | > You need to bring your own `jquery.js` file as the gem does not include one. If 90 | > you are using the asset pipeline with rails, then this should be available 91 | > already, otherwise download a copy and place it into `app/` or whichever directory 92 | > you are compiling assets from. You can alternatively require a zepto instance. 93 | 94 | The `#alert` method is provided by `opal-jquery`. If the message displays, then 95 | `jquery` support should be working. 96 | 97 | 98 | ### How does opal-jquery work 99 | 100 | `opal-jquery` provides an `Element` class, whose instances are toll-free 101 | bridged instances of jquery objects. Just like ruby arrays are just javascript 102 | arrays, `Element` instances are just jquery objects. This makes interaction 103 | with jquery plugins much easier. 104 | 105 | Also, `Element` will try to bridge with Zepto if it cannot find jQuery loaded, 106 | making it ideal for mobile applications as well. 107 | 108 | 109 | 110 | 111 | ## Interacting with the DOM 112 | 113 | 114 | ### Finding Elements 115 | 116 | opal-jquery provides the `Element` class, which can be used to find elements in 117 | the current document: 118 | 119 | ```ruby 120 | Element.find('#header') 121 | ``` 122 | 123 | `Element.find` is aliased to `Element[]`: 124 | 125 | ```ruby 126 | Element['.my-class'] 127 | ``` 128 | 129 | These methods acts just like `$('selector')`, and can use any jQuery 130 | compatible selector: 131 | 132 | ```ruby 133 | Element.find('#navigation li:last') 134 | ``` 135 | 136 | The result is just a jQuery instance, which is toll-free bridged to 137 | instances of the `Element` class in ruby: 138 | 139 | ```ruby 140 | Element.find('.foo').class 141 | # => Element 142 | ``` 143 | 144 | Instances of `Element` also have the `#find` method available for 145 | finding elements within the scope of each DOM node represented by 146 | the instance: 147 | 148 | ```ruby 149 | el = Element.find('#header') 150 | el.find '.foo' 151 | # => # 152 | ``` 153 | 154 | 155 | ### Running code on document ready 156 | 157 | Just like jQuery, opal-jquery requires the document to be ready to 158 | be able to fully interact with the page. Any top level access should 159 | use the `ready?` method: 160 | 161 | ```ruby 162 | Document.ready? do 163 | alert "document is ready to go!" 164 | end 165 | ``` 166 | 167 | or the equivilent `Document.ready` promise which is useful when combined with other promises: 168 | 169 | ```ruby 170 | Document.ready.then do |ready| 171 | alert "Page is ready to use!" 172 | end 173 | ``` 174 | 175 | Notice the use of the `Kernel#alert` method. 176 | 177 | 178 | ### Event handling 179 | 180 | The `Element#on` method is used to attach event handlers to elements: 181 | 182 | ```ruby 183 | Element.find('#header').on :click do 184 | puts "The header was clicked!" 185 | end 186 | ``` 187 | 188 | Selectors can also be passed as a second argument to handle events 189 | on certain children: 190 | 191 | ```ruby 192 | Element.find('#header').on(:click, '.foo') do 193 | puts "An element with a 'foo' class was clicked" 194 | end 195 | ``` 196 | 197 | An `Event` instance is optionally passed to block handlers as well, 198 | which is toll-free bridged to jquery events: 199 | 200 | ```ruby 201 | Element.find('#my_link').on(:click) do |evt| 202 | evt.stop_propagation 203 | puts "stopped the event!" 204 | end 205 | ``` 206 | 207 | You can access the element which triggered the event by `#current_target`. 208 | 209 | ```ruby 210 | Document.on :click do |evt| 211 | puts "clicked on: #{evt.current_target}" 212 | end 213 | ``` 214 | 215 | 216 | ### CSS styles and classnames 217 | 218 | The various jQuery methods are available on `Element` instances: 219 | 220 | ```ruby 221 | foo = Element.find('.foo') 222 | 223 | foo.add_class 'blue' 224 | foo.remove_class 'foo' 225 | foo.toggle_class 'selected' 226 | ``` 227 | 228 | There are also added convenience methods for opal-jquery: 229 | 230 | ```ruby 231 | foo = Element.find('#header') 232 | 233 | foo.class_name 234 | # => 'red lorry' 235 | 236 | foo.class_name = 'yellow house' 237 | 238 | foo.class_name 239 | # => 'yellow house' 240 | ``` 241 | 242 | `Element#css` also exists for getting/setting css styles: 243 | 244 | ```ruby 245 | el = Element.find('#container') 246 | el.css 'color', 'blue' 247 | el.css 'color' 248 | # => 'blue' 249 | ``` 250 | 251 | 252 | 253 | 254 | ## HTTP/AJAX requests 255 | 256 | jQuery's Ajax implementation is also wrapped in the top level HTTP 257 | class. 258 | 259 | ```ruby 260 | HTTP.get("/users/1.json") do |response| 261 | puts response.body 262 | # => "{\"name\": \"Adam Beynon\"}" 263 | end 264 | ``` 265 | 266 | The block passed to this method is used as the handler when the request 267 | succeeds, as well as when it fails. To determine whether the request 268 | was successful, use the `ok?` method: 269 | 270 | ```ruby 271 | HTTP.get("/users/2.json") do |response| 272 | if response.ok? 273 | alert "successful!" 274 | else 275 | alert "request failed :(" 276 | end 277 | end 278 | ``` 279 | 280 | It is also possible to use a different handler for each case: 281 | 282 | ```ruby 283 | request = HTTP.get("/users/3.json") 284 | 285 | request.callback { 286 | puts "Request worked!" 287 | } 288 | 289 | request.errback { 290 | puts "Request didn't work :(" 291 | } 292 | ``` 293 | 294 | The request is actually triggered inside the `HTTP.get` method, but due 295 | to the async nature of the request, the callback and errback handlers can 296 | be added anytime before the request returns. 297 | 298 | 299 | ### Handling responses 300 | 301 | Web apps deal with JSON responses quite frequently, so there is a useful 302 | `#json` helper method to get the JSON content from a request: 303 | 304 | ```ruby 305 | HTTP.get("/users.json") do |response| 306 | puts response.body 307 | puts response.json 308 | end 309 | 310 | # => "[{\"name\": \"Adam\"},{\"name\": \"Ben\"}]" 311 | # => [{"name" => "Adam"}, {"name" => "Ben"}] 312 | ``` 313 | 314 | The `#body` method will always return the raw response string. 315 | 316 | If an error is encountered, then the `#status_code` method will hold the 317 | specific error code from the underlying request: 318 | 319 | ```ruby 320 | request = HTTP.get("/users/3.json") 321 | 322 | request.callback { puts "it worked!" } 323 | 324 | request.errback { |response| 325 | puts "failed with status #{response.status_code}" 326 | } 327 | ``` 328 | 329 | 330 | ### Supplying an XHR method 331 | 332 | To supply an XHR callback include a lambda with the `xhr` option: 333 | 334 | ```ruby 335 | update_progress = lambda do 336 | xhr = `new window.XMLHttpRequest()` 337 | update_progress = lambda do |evt| 338 | # update your progress here 339 | end 340 | `xhr.upload.addEventListener("progress", update_progress, false)` 341 | xhr 342 | end 343 | 344 | cloud_xfer = HTTP.put "http://my.cloud.storage/location", xhr: update_progress, ... etc ... 345 | ``` 346 | 347 | 348 | 349 | 350 | ## Usage of JQuery plugins 351 | Extra plugins used for JQuery aren't available to ruby code by default, you will have to `expose` these functions to opal-jquery. 352 | 353 | ```ruby 354 | Element.expose :cool_plugin 355 | ``` 356 | 357 | Arguments to a `exposed` function will be passed as they are, so these arguments will have to be passed as native JS instead of ruby code. A conversion to native JS can be done with the `.to_n` method. 358 | 359 | ```ruby 360 | Element.expose :cool_plugin 361 | 362 | el = Element['.html_element'] 363 | el.cool_plugin({argument: 'value', argument1: 1000}.to_n) 364 | ``` 365 | 366 | 367 | ## License 368 | 369 | (The MIT License) 370 | 371 | Copyright (C) 2013 by Adam Beynon 372 | 373 | Permission is hereby granted, free of charge, to any person obtaining a copy 374 | of this software and associated documentation files (the "Software"), to deal 375 | in the Software without restriction, including without limitation the rights 376 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 377 | copies of the Software, and to permit persons to whom the Software is 378 | furnished to do so, subject to the following conditions: 379 | 380 | The above copyright notice and this permission notice shall be included in 381 | all copies or substantial portions of the Software. 382 | 383 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 384 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 385 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 386 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 387 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 388 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 389 | THE SOFTWARE. 390 | 391 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler.require 3 | Bundler::GemHelper.install_tasks 4 | 5 | ENV['RUNNER'] = 'chrome' 6 | 7 | require 'opal/rspec/rake_task' 8 | Opal::RSpec::RakeTask.new(:default) do |server, task| 9 | server.index_path = 'spec-opal/jquery/index.html.erb' 10 | task.default_path = 'spec-opal' 11 | end 12 | 13 | Opal::RSpec::RakeTask.new(:jquery3) do |server, task| 14 | server.index_path = 'spec-opal/jquery/index3.html.erb' 15 | task.default_path = 'spec-opal' 16 | end 17 | 18 | Opal::RSpec::RakeTask.new(:zepto) do |server, task| 19 | server.index_path = 'spec-opal/zepto/index.html.erb' 20 | task.default_path = 'spec-opal' 21 | end 22 | 23 | desc "Build build/opal-jquery.js" 24 | task :dist do 25 | require 'fileutils' 26 | FileUtils.mkdir_p 'build' 27 | 28 | src = Opal::Builder.build('opal-jquery') 29 | min = uglify src 30 | gzp = gzip min 31 | 32 | File.open('build/opal-jquery.js', 'w+') do |out| 33 | out << src 34 | end 35 | 36 | puts "development: #{src.size}, minified: #{min.size}, gzipped: #{gzp.size}" 37 | end 38 | 39 | # Used for uglifying source to minify 40 | def uglify(str) 41 | IO.popen('uglifyjs', 'r+') do |i| 42 | i.puts str 43 | i.close_write 44 | return i.read 45 | end 46 | rescue Errno::ENOENT 47 | $stderr.puts '"uglifyjs" command not found (install with: "npm install -g uglify-js")' 48 | nil 49 | end 50 | 51 | # Gzip code to check file size 52 | def gzip(str) 53 | IO.popen('gzip -f', 'r+') do |i| 54 | i.puts str 55 | i.close_write 56 | return i.read 57 | end 58 | rescue Errno::ENOENT 59 | $stderr.puts '"gzip" command not found, it is required to produce the .gz version' 60 | nil 61 | end 62 | 63 | 64 | namespace :doc do 65 | doc_repo = Pathname(ENV['DOC_REPO'] || 'gh-pages') 66 | doc_base = doc_repo.join('doc') 67 | current_git_release = -> { `git rev-parse --abbrev-ref HEAD`.chomp } 68 | # template_option = "--template opal --template-path #{doc_repo.join('yard-templates')}" 69 | template_option = "" 70 | 71 | directory doc_repo.to_s do 72 | remote = ENV['DOC_REPO_REMOTE'] || '.' 73 | sh 'git', 'clone', '-b', 'gh-pages', '--', remote, doc_repo.to_s 74 | end 75 | 76 | # To generate docs that live on http://opalrb.org/opal-jquery/ use the 77 | # `rake doc` task 78 | # 79 | # DOC_REPO_REMOTE=https://github.com/opal/opal-jquery.git bundle exec rake doc 80 | # open gh-pages/index.html 81 | task :default => doc_repo.to_s do 82 | git = current_git_release.call 83 | name = 'api' 84 | glob = 'opal/**/*.rb' 85 | command = "yard doc #{glob} #{template_option} "\ 86 | "--readme opal/README.md -o gh-pages/doc/#{git}/#{name}" 87 | puts command; system command 88 | end 89 | 90 | # To generate api docs on rubygems: http://www.rubydoc.info/gems/opal-jquery/0.4.2 91 | # yard --main README.md --markup markdown --github 92 | # open doc/index.html 93 | end 94 | 95 | task :doc => 'doc:default' 96 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler.require 3 | 4 | require 'opal/rspec' 5 | require 'opal/rspec/sprockets_environment' 6 | 7 | sprockets_env = Opal::RSpec::SprocketsEnvironment.new 8 | run Opal::Server.new(sprockets: sprockets_env) { |s| 9 | s.main = 'opal/rspec/sprockets_runner' 10 | sprockets_env.add_spec_paths_to_sprockets 11 | s.debug = false 12 | } 13 | -------------------------------------------------------------------------------- /examples/rake/.gitignore: -------------------------------------------------------------------------------- 1 | application.js 2 | -------------------------------------------------------------------------------- /examples/rake/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'opal-jquery', :path => '../..' 4 | gem 'opal', '0.3.44' 5 | -------------------------------------------------------------------------------- /examples/rake/README.md: -------------------------------------------------------------------------------- 1 | Install dependencies: 2 | 3 | $ bundle install 4 | 5 | Build `application.js` with: 6 | 7 | $ bundle exec rake build 8 | 9 | -------------------------------------------------------------------------------- /examples/rake/Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler.require 3 | 4 | desc "Build to application.js" 5 | task :build do 6 | File.open("application.js", "w+") do |out| 7 | env = Opal::Environment.new 8 | env.append_path "app" 9 | out << env["application"].to_s 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /examples/rake/app/application.rb: -------------------------------------------------------------------------------- 1 | require 'opal' 2 | require 'opal-jquery' 3 | 4 | Document.ready? do 5 | Element.find('#foo').text = "Opal is loaded" 6 | end 7 | -------------------------------------------------------------------------------- /examples/rake/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | opal-jquery example 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /lib/opal-jquery.rb: -------------------------------------------------------------------------------- 1 | require 'opal/jquery' 2 | -------------------------------------------------------------------------------- /lib/opal/jquery.rb: -------------------------------------------------------------------------------- 1 | if RUBY_ENGINE == 'opal' 2 | require 'opal/jquery/window' 3 | require 'opal/jquery/document' 4 | require 'opal/jquery/element' 5 | require 'opal/jquery/event' 6 | require 'opal/jquery/http' 7 | require 'opal/jquery/kernel' 8 | else 9 | require 'opal' 10 | require 'opal/jquery/version' 11 | 12 | Opal.append_path File.expand_path('../..', __FILE__) 13 | end 14 | -------------------------------------------------------------------------------- /lib/opal/jquery/constants.rb: -------------------------------------------------------------------------------- 1 | # backtick_javascript: true 2 | 3 | require 'native' 4 | 5 | unless defined?(JQUERY_CLASS) 6 | case 7 | when `!!Opal.global.jQuery` 8 | JQUERY_CLASS = JQUERY_SELECTOR = `Opal.global.jQuery` 9 | when `!!Opal.global.Zepto` 10 | JQUERY_SELECTOR = `Opal.global.Zepto` 11 | JQUERY_CLASS = `Opal.global.Zepto.zepto.Z` 12 | else 13 | raise NameError, "Can't find jQuery or Zepto. jQuery must be included before opal-jquery" 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/opal/jquery/document.rb: -------------------------------------------------------------------------------- 1 | # backtick_javascript: true 2 | 3 | require 'opal/jquery/constants' 4 | require 'opal/jquery/element' 5 | 6 | module Browser 7 | # {Document} includes these methods to extend {Element}. 8 | # 9 | # Generally, you will want to use the {::Document} top level instance of 10 | # {Element}. 11 | # 12 | # ## Usage 13 | # 14 | # A useful method on {Document} is the {#ready?} method, which can be used to 15 | # run a block once the document is ready. This is equivalent to passing a 16 | # function to the `jQuery` constructor. Unlike jQuery it will work correctly 17 | # even if called *after* the document is already loaded. 18 | # 19 | # Document.ready? do 20 | # puts "Page is ready to use!" 21 | # end 22 | # 23 | # Just like jQuery, multiple blocks may be passed to {#ready?}. 24 | # 25 | # Document.ready (without the question mark) returns the equivilent promise. 26 | # Like other promises it can be combined using the when and then methods. 27 | # 28 | # Document.ready.then do |ready| 29 | # puts "Page is ready to use!" 30 | # end 31 | # 32 | # ### Document head and body elements 33 | # 34 | # Every document has atleast two elements: a `head` and `body`. For 35 | # convenience, these are both exposed as {#head} and {#body} respectively, 36 | # and are just instances of {Element}. 37 | # 38 | # puts Document.head 39 | # puts Document.body 40 | # 41 | # # => #]> 42 | # # => #]> 43 | # 44 | # ### Events 45 | # 46 | # {Document} instances also have {#on}, {#off} and {#trigger} methods for 47 | # handling events. These all just delegate to their respective methods on 48 | # {Element}, using `document` as the context. 49 | # 50 | # Document.on :click do |evt| 51 | # puts "someone clicked somewhere in the document" 52 | # end 53 | # 54 | module DocumentMethods 55 | @@__isReady = false 56 | 57 | `var $ = #{JQUERY_SELECTOR.to_n}` # cache $ for SPEED 58 | 59 | # Register a block to run once the document/page is ready. 60 | # will call the block if the document is already ready 61 | # 62 | # @example 63 | # Document.ready? do 64 | # puts "ready to go" 65 | # end 66 | # 67 | def ready?(&block) 68 | @@__isReady ? block.call : `$(#{block})` if block_given? 69 | end 70 | 71 | # Return a promise that resolves when the document is ready. 72 | # 73 | # @example 74 | # Document.ready.then do |r| 75 | # puts "ready to go" 76 | # end 77 | # 78 | def ready 79 | promise = Promise.new 80 | Document.ready? { promise.resolve } 81 | promise 82 | end 83 | 84 | module_function :ready? 85 | 86 | ready? { @@__isReady = true } 87 | 88 | # Returns document title. 89 | # 90 | # @return [String] 91 | def title 92 | `document.title` 93 | end 94 | 95 | # Set document title. 96 | # 97 | # @param title [String] 98 | def title=(title) 99 | `document.title = title` 100 | end 101 | 102 | # {Element} instance wrapping `document.head`. 103 | # 104 | # @return [Element] 105 | def head 106 | Element.find `document.head` 107 | end 108 | 109 | # {Element} instance wrapping `document.body`. 110 | # 111 | # @return [Element] 112 | def body 113 | Element.find `document.body` 114 | end 115 | end 116 | end 117 | 118 | # Top level {Document} instance wrapping `window.document`. 119 | Document = Element.find(`document`) 120 | Document.extend Browser::DocumentMethods 121 | 122 | # TODO: this will be removed soon (here for compatibility) 123 | $document = Document 124 | -------------------------------------------------------------------------------- /lib/opal/jquery/element.rb: -------------------------------------------------------------------------------- 1 | # backtick_javascript: true 2 | 3 | require 'native' 4 | require 'opal/jquery/constants' 5 | 6 | # {Element} is a toll-free bridged class that maps to native jQuery instances. 7 | # 8 | # As {Element} maps to a jQuery object, it can be used to represent 0, 1, or 9 | # more actual DOM elements. {Element} exposes a more ruby-esqe interface to 10 | # jQuery. 11 | # 12 | # ## Usage 13 | # 14 | # {Element} instances can be created in a number of ways. 15 | # 16 | # ### Creating new Elements 17 | # 18 | # A new element can be created using the {Element.new} method. 19 | # 20 | # el = Element.new(:div) 21 | # el.id = "title" 22 | # p el 23 | # # => #]> 24 | # 25 | # This is a nicer version of creating a javascript element using 26 | # `document.createElement` and then wrapping it in a jquery object. 27 | # 28 | # ### Finding existing elements in dom 29 | # 30 | # Any valid jQuery selector expressions can be used with either {Element.find} 31 | # or {Element.[]}. 32 | # 33 | # foos = Element.find('.foo') 34 | # # => #]> 35 | # 36 | # links = Element['a'] 37 | # # => #, ]> 38 | # 39 | # Alternatively, {Element.id} can be used to find an element by its document 40 | # id (or nil is returned for no match). 41 | # 42 | # bar = Element.id 'bar' 43 | # # => Element or nil 44 | # 45 | # ### DOM Content from string 46 | # 47 | # Finally, an {Element} instance can be created by parsing a string of html 48 | # content. This will parse multiple elements, like jquery, if string content 49 | # contains them: 50 | # 51 | # Element.parse '
hello world
' 52 | # # => #]> 53 | # 54 | class Element < `#{JQUERY_CLASS.to_n}` 55 | `var $ = #{JQUERY_SELECTOR.to_n}` # cache $ for SPEED 56 | 57 | include Enumerable 58 | 59 | # Find elements by the given css selector. 60 | # 61 | # Returns an empty {Element} if no matching elements. 62 | # 63 | # @param selector [String] css selector 64 | # @return [Element] 65 | def self.find(selector) 66 | `$(#{selector})` 67 | end 68 | 69 | # Find elements by the given css selector. 70 | # 71 | # Returns an empty {Element} if no matching elements. 72 | # 73 | # @param selector [String] css selector 74 | # @return [Element] 75 | def self.[](selector) 76 | `$(#{selector})` 77 | end 78 | 79 | # Find an element by the given id. 80 | # 81 | # If no matching element, then `nil` will be returned. A matching element 82 | # becomes the sole element in the returned collection. 83 | # 84 | # @param id [String] dom element id 85 | # @return [Element, nil] 86 | def self.id(id) 87 | %x{ 88 | var el = document.getElementById(id); 89 | 90 | if (!el) { 91 | return nil; 92 | } 93 | 94 | return $(el); 95 | } 96 | end 97 | 98 | # Create a new dom element, wrapped as {Element} instance with the given 99 | # `tag` name. 100 | # 101 | # @param tag [String] valid html tag name 102 | # @return [Element] 103 | def self.new(tag = 'div') 104 | `$(document.createElement(tag))` 105 | end 106 | 107 | # Parse a string of html content into an {Element} instance. 108 | # 109 | # If no valid elements in string, then an empty collection will be returned. 110 | # 111 | # @param str [String] html content to parse 112 | # @return [Element] 113 | def self.parse(str) 114 | `$.parseHTML ? $($.parseHTML(str)) : $(str)` 115 | end 116 | 117 | # Expose jQuery plugins to become available in ruby code. By default, 118 | # jQuery methods or plugins must be manually exposed as ruby methods. 119 | # This method simply creates an aliasing ruby method to call the original 120 | # javascript function. 121 | # 122 | # @example 123 | # # Expose bootstraps jQuery `modal` function 124 | # Element.expose :modal 125 | # 126 | # Element.find('.my-modal').modal 127 | # 128 | # @param methods [String, Symbol] all methods to expose to ruby 129 | # @return nil 130 | def self.expose(*methods) 131 | methods.each do |method| 132 | alias_native method 133 | end 134 | end 135 | 136 | # @return The original css selector used to create {Element} 137 | attr_reader :selector 138 | 139 | # @!method after(content) 140 | # 141 | # Inserts the given `content` after each element in this set of elements. 142 | # This method can accept either another {Element}, or a string. 143 | # 144 | # @param content [String, Element] string or element to insert 145 | alias_native :after 146 | 147 | # @!method before(content) 148 | # 149 | # Insert the given `content` before each element in this set of elements. 150 | # The given `content` can be either an {Element}, or a string. 151 | # 152 | # @param content [String, Element] string or element to insert 153 | alias_native :before 154 | 155 | # @!method parent(selector = nil) 156 | # 157 | # Returns a new {Element} set with the parents of each element in this 158 | # collection. An optional `selector` argument can be used to filter the 159 | # results to match the given selector. Result may be empty. 160 | # 161 | # @param selector [String] optional filter 162 | # @return [Element] 163 | alias_native :parent 164 | 165 | # @!method parents(selector = nil) 166 | # 167 | # Returns a new {Element} set with all parents of each element in this 168 | # collection. An optional `selector` may be provided to filter the 169 | # selection. Resulting collection may be empty. 170 | # 171 | # @example Without filtering collection 172 | # Element.find('#foo').parents 173 | # # => #, , ]> 174 | # 175 | # @example Using a filter 176 | # Element.find('#foo').parents('div') 177 | # # => #] 178 | # 179 | # @param selector [String] optional filter 180 | # @return [Element] 181 | alias_native :parents 182 | 183 | # @!method prev(selector = nil) 184 | alias_native :prev 185 | 186 | # @!method remove(selector = nil) 187 | alias_native :remove 188 | 189 | # @!method hide(duration = 400) 190 | alias_native :hide 191 | 192 | # @!method show(duration = 400) 193 | alias_native :show 194 | 195 | # @!method toggle(duration = 400) 196 | alias_native :toggle 197 | 198 | # @!method children(selector = nil) 199 | alias_native :children 200 | 201 | # @!method blur 202 | alias_native :blur 203 | 204 | # @!method closest(selector) 205 | alias_native :closest 206 | 207 | # @!method detach(selector = nil) 208 | alias_native :detach 209 | 210 | # @!method focus 211 | alias_native :focus 212 | 213 | # @!method find(selector) 214 | alias_native :find 215 | 216 | # @!method next(selector = nil) 217 | alias_native :next 218 | 219 | # @!method siblings(selector = nil) 220 | alias_native :siblings 221 | 222 | # @!method text(text = nil) 223 | # 224 | # Get or set the text content of each element in this collection. Setting 225 | # the content is provided as a compatibility method for jquery. Instead 226 | # {#text=} should be used for setting text content. 227 | # 228 | # If no `text` content is provided, then the text content of this element 229 | # will be returned. 230 | # 231 | # @see #text= 232 | # @param text [String] text content to set 233 | # @return [String] 234 | alias_native :text 235 | 236 | # @!method trigger(event) 237 | # 238 | # Trigger an event on this element. The given `event` specifies the event 239 | # type. 240 | # 241 | # @param event [String, Symbol] event type 242 | alias_native :trigger 243 | 244 | # @!method append(content) 245 | # 246 | # @param content [String, Element] 247 | alias_native :append 248 | 249 | # @!method prepend(content) 250 | # 251 | # @param content [String, Element] 252 | alias_native :prepend 253 | 254 | # @!method serialize 255 | alias_native :serialize 256 | 257 | # @!method is(selector) 258 | # @return [true, false] 259 | alias_native :is 260 | 261 | # @!method filter(selector) 262 | # @param selector [String] 263 | # @return [Element] 264 | alias_native :filter 265 | 266 | # @!method not(selector) 267 | # @param selector [String] 268 | # @return [Element] 269 | alias_native :not 270 | 271 | # @!method last 272 | # 273 | # Returns a new {Element} instance containing the last element in this 274 | # current set. 275 | # 276 | # @return [Element] 277 | alias_native :last 278 | 279 | # @!method wrap(wrapper) 280 | # @param wrapper [String, Element] html content, selector or element 281 | # @return [Element] 282 | alias_native :wrap 283 | 284 | # @!method stop 285 | # 286 | # Stop any currently running animations on element. 287 | alias_native :stop 288 | 289 | # @!method clone 290 | # 291 | # Clone all elements inside this collection, and return as a new instance. 292 | # @return [Element] 293 | alias_native :clone 294 | 295 | # @!method empty 296 | # 297 | # Remove all child nodes from each element in this collection. 298 | alias_native :empty 299 | 300 | # @!method get 301 | alias_native :get 302 | 303 | # @!method prop(name, value = undefined) 304 | # 305 | # Get or set the property `name` on each element in collection. 306 | def prop(*args) 307 | Native.call(self, :prop, *args) 308 | end 309 | 310 | alias succ next 311 | alias << append 312 | 313 | # @!method add_class(class_name) 314 | alias_native :add_class, :addClass 315 | 316 | # @!method append_to(element) 317 | alias_native :append_to, :appendTo 318 | 319 | # @!method has_class?(class_name) 320 | alias_native :has_class?, :hasClass 321 | 322 | # @!method html=(content) 323 | # 324 | # Set the html content of each element in this collection to the passed 325 | # content. Content can either be a string or another {Element}. 326 | # 327 | # @param content [String, Element] 328 | alias_native :html=, :html 329 | 330 | # @!method index(selector_or_element = nil) 331 | alias_native :index 332 | 333 | # @!method is?(selector) 334 | alias_native :is?, :is 335 | 336 | # @!method remove_attr(attr) 337 | alias_native :remove_attr, :removeAttr 338 | 339 | # @!method remove_class(class_name) 340 | alias_native :remove_class, :removeClass 341 | 342 | # @!method replace_all(target) 343 | alias_native :replace_all, :replaceAll 344 | 345 | # @!method replace_with(new_content) 346 | alias_native :replace_with, :replaceWith 347 | 348 | # @!method select() 349 | alias_native :select 350 | 351 | # @!method submit() 352 | alias_native :submit 353 | 354 | # @!method click() 355 | alias_native :click 356 | 357 | # @!method text=(text) 358 | # 359 | # Set text content of each element in this collection. 360 | # 361 | # @see #text 362 | # @param text [String] 363 | alias_native :text=, :text 364 | 365 | # @!method toggle_class 366 | alias_native :toggle_class, :toggleClass 367 | 368 | # @!method value=(value) 369 | alias_native :value=, :val 370 | 371 | # @!method scroll_top=(value) 372 | alias_native :scroll_top=, :scrollTop 373 | 374 | # @!method scroll_top 375 | alias_native :scroll_top, :scrollTop 376 | 377 | # @!method scroll_left=(value) 378 | alias_native :scroll_left=, :scrollLeft 379 | 380 | # @!method scroll_left 381 | alias_native :scroll_left, :scrollLeft 382 | 383 | # @!method remove_attribute(attr) 384 | alias_native :remove_attribute, :removeAttr 385 | 386 | # @!method slide_down(duration = 400) 387 | alias_native :slide_down, :slideDown 388 | 389 | # @!method slide_up(duration = 400) 390 | alias_native :slide_up, :slideUp 391 | 392 | # @!method slide_toggle(duration = 400) 393 | alias_native :slide_toggle, :slideToggle 394 | 395 | # @!method fade_toggle(duration = 400) 396 | alias_native :fade_toggle, :fadeToggle 397 | 398 | # @!method height=(value) 399 | alias_native :height=, :height 400 | 401 | # @!method width=(value) 402 | alias_native :width=, :width 403 | 404 | # @!method outer_width(include_margin = false) 405 | alias_native :outer_width, :outerWidth 406 | 407 | # @!method outer_height(include_margin = false) 408 | alias_native :outer_height, :outerHeight 409 | 410 | def to_n 411 | self 412 | end 413 | 414 | def [](name) 415 | %x{ 416 | var value = self.attr(name); 417 | if(value === undefined) return nil; 418 | return value; 419 | } 420 | end 421 | 422 | # Set the given attribute `attr` on each element in this collection. 423 | # 424 | # @see http://api.jquery.com/attr/ 425 | def []=(name, value) 426 | `return self.removeAttr(name)` if value.nil? 427 | `self.attr(name, value)` 428 | end 429 | 430 | def attr(*args) 431 | %x{ 432 | var size = args.length; 433 | switch (size) { 434 | case 1: 435 | var result = self.attr(args[0]); 436 | return( (result == null) ? nil : result ); 437 | break; 438 | case 2: 439 | return self.attr(args[0], args[1]); 440 | break; 441 | default: 442 | #{raise ArgumentError, '#attr only accepts 1 or 2 arguments'} 443 | } 444 | } 445 | end 446 | 447 | def has_attribute?(name) 448 | `self.attr(name) !== undefined` 449 | end 450 | 451 | def append_to_body 452 | `self.appendTo(document.body)` 453 | end 454 | 455 | def append_to_head 456 | `self.appendTo(document.head)` 457 | end 458 | 459 | # Returns the element at the given index as a new {Element} instance. 460 | # Negative indexes can be used and are counted from the end. If the 461 | # given index is outside the range then `nil` is returned. 462 | # 463 | # @param index [Integer] index 464 | # @return [Element, nil] 465 | def at(index) 466 | %x{ 467 | var length = self.length; 468 | 469 | if (index < 0) { 470 | index += length; 471 | } 472 | 473 | if (index < 0 || index >= length) { 474 | return nil; 475 | } 476 | 477 | return $(self[index]); 478 | } 479 | end 480 | 481 | # Returns the CSS class name of the firt element in self collection. 482 | # If the collection is empty then an empty string is returned. Only 483 | # the class name of the first element will ever be returned. 484 | # 485 | # @return [String] 486 | def class_name 487 | %x{ 488 | var first = self[0]; 489 | return (first && first.className) || ""; 490 | } 491 | end 492 | 493 | # Sets the CSS class name of every element in self collection to the 494 | # given string. self does not append the class names, it replaces 495 | # the entire current class name. 496 | # 497 | # @param name [String] class name to set 498 | def class_name=(name) 499 | %x{ 500 | for (var i = 0, length = self.length; i < length; i++) { 501 | self[i].className = name; 502 | } 503 | } 504 | self 505 | end 506 | 507 | # Get or set css properties on each element in self collection. If 508 | # only the `name` is given, then that css property name is read from 509 | # the first element in the collection and returned. If the `value` 510 | # property is also given then the given css property is set to the 511 | # given value for each of the elements in self collection. The 512 | # property can also be a hash of properties and values. 513 | def css(name, value=nil) 514 | if value.nil? && name.is_a?(String) 515 | return `self.css(name)` 516 | else 517 | name.is_a?(Hash) ? `self.css(#{name.to_n})` : `self.css(name, value)` 518 | end 519 | self 520 | end 521 | 522 | # Set css values over time to create animations. The first parameter is a 523 | # set of css properties and values to animate to. The first parameter 524 | # also accepts a special :speed value to set animation speed. If a block 525 | # is given, the block is run as a callback when the animation finishes. 526 | def animate(params, &block) 527 | speed = params.has_key?(:speed) ? params.delete(:speed) : 400 528 | if block_given? 529 | `self.animate(#{params.to_n}, #{speed}, block)` 530 | else 531 | `self.animate(#{params.to_n}, #{speed})` 532 | end 533 | end 534 | 535 | def data(*args) 536 | %x{ 537 | var result = self.data.apply(self, args); 538 | if ( 539 | (typeof(result) === 'object') && !(result instanceof #{JQUERY_CLASS}) 540 | ) { 541 | result = #{ JSON.from_object `result` }; 542 | } 543 | return result == null ? nil : result; 544 | } 545 | end 546 | 547 | # Start a visual effect (e.g. fadeIn, fadeOut, …) passing its name. 548 | # Underscored style is automatically converted (e.g. `effect(:fade_in)`). 549 | # Also accepts additional arguments and a block for the finished callback. 550 | def effect(name, *args, &block) 551 | name = name.gsub(/_\w/) { |match| match[1].upcase } 552 | args = args.map { |a| a.to_n if a.respond_to? :to_n }.compact 553 | args << `function() { #{block.call if block_given?} }` 554 | `self[#{name}].apply(self, #{args})` 555 | end 556 | 557 | def visible? 558 | `self.is(':visible')` 559 | end 560 | 561 | def offset 562 | Native(`self.offset()`) 563 | end 564 | 565 | def each 566 | `for (var i = 0, length = self.length; i < length; i++) {` 567 | yield `$(self[i])` 568 | `}` 569 | self 570 | end 571 | 572 | def first 573 | `self.length ? self.first() : nil` 574 | end 575 | 576 | def html(content = undefined) 577 | %x{ 578 | if (content != null) { 579 | return self.html(content); 580 | } 581 | 582 | return self.html() || ''; 583 | } 584 | end 585 | 586 | def id 587 | %x{ 588 | var first = self[0]; 589 | return (first && first.id) || ""; 590 | } 591 | end 592 | 593 | def id=(id) 594 | %x{ 595 | var first = self[0]; 596 | 597 | if (first) { 598 | first.id = id; 599 | } 600 | 601 | return self; 602 | } 603 | end 604 | 605 | def tag_name 606 | `self.length > 0 ? self[0].tagName.toLowerCase() : #{nil}` 607 | end 608 | 609 | def inspect 610 | %x{ 611 | if (self[0] === document) return '#' 612 | else if (self[0] === window ) return '#' 613 | 614 | var val, el, str, result = []; 615 | 616 | for (var i = 0, length = self.length; i < length; i++) { 617 | el = self[i]; 618 | if (!el.tagName) { return '#'); 626 | } 627 | 628 | return '#'; 629 | } 630 | end 631 | 632 | def to_s 633 | %x{ 634 | var val, el, result = []; 635 | 636 | for (var i = 0, length = self.length; i < length; i++) { 637 | el = self[i]; 638 | 639 | result.push(el.outerHTML) 640 | } 641 | 642 | return result.join(', '); 643 | } 644 | end 645 | 646 | # Returns the number of elements in this collection. May be zero. 647 | # @return [Integer] 648 | def length 649 | `self.length` 650 | end 651 | 652 | # Returns `true` if this collection has 1 or more elements, `false` 653 | # otherwise. 654 | # 655 | # @return [true, false] 656 | def any? 657 | `self.length > 0` 658 | end 659 | 660 | # Returns `true` if this collection contains no elements, `false` otherwise. 661 | # 662 | # @return [true, false] 663 | def empty? 664 | `self.length === 0` 665 | end 666 | 667 | alias empty? none? 668 | 669 | def on(name, sel = nil, &block) 670 | %x{ 671 | var has_args = #{block.arity} !== 0; 672 | 673 | var wrapper = function() { 674 | for(var args = new Array(arguments.length), i = 0, ii = args.length; i < ii; i++) { 675 | args[i] = arguments[i]; 676 | } 677 | 678 | // Use preventDefault as a canary for native events 679 | if (has_args && args[0].preventDefault) { 680 | args[0] = #{Event.new `args[0]`}; 681 | } 682 | 683 | return block.apply(null, args); 684 | }; 685 | 686 | block.$$jqwrap = wrapper; 687 | 688 | if (sel == nil) { 689 | self.on(name, wrapper); 690 | } 691 | else { 692 | self.on(name, sel, wrapper); 693 | } 694 | } 695 | 696 | block 697 | end 698 | 699 | def one(name, sel = nil, &block) 700 | %x{ 701 | var has_args = #{block.arity} !== 0; 702 | 703 | var wrapper = function() { 704 | for(var args = new Array(arguments.length), i = 0, ii = args.length; i < ii; i++) { 705 | args[i] = arguments[i]; 706 | } 707 | 708 | // Use preventDefault as a canary for native events 709 | if (has_args && args[0].preventDefault) { 710 | args[0] = #{Event.new `args[0]`}; 711 | } 712 | 713 | return block.apply(null, args); 714 | }; 715 | 716 | block.$$jqwrap = wrapper; 717 | 718 | if (sel == nil) { 719 | self.one(name, wrapper); 720 | } 721 | else { 722 | self.one(name, sel, wrapper); 723 | } 724 | } 725 | 726 | block 727 | end 728 | 729 | def off(name, sel, block = nil) 730 | %x{ 731 | if (sel == null) { 732 | return self.off(name); 733 | } 734 | else if (block === nil) { 735 | return self.off(name, sel.$$jqwrap); 736 | } 737 | else { 738 | return self.off(name, sel, block.$$jqwrap); 739 | } 740 | } 741 | end 742 | 743 | # Serializes a form into an Array of Hash objects. 744 | # 745 | # @return [Array] 746 | def serialize_array 747 | `self.serializeArray()`.map { |e| Hash.new(e) } 748 | end 749 | 750 | alias size length 751 | 752 | def value 753 | `self.val()` || "" 754 | end 755 | 756 | def height 757 | `self.height()` || nil 758 | end 759 | 760 | def width 761 | `self.width()` || nil 762 | end 763 | 764 | def position 765 | Native(`self.position()`) 766 | end 767 | 768 | def ==(other) 769 | `self.is(other)` 770 | end 771 | 772 | def respond_to_missing?(name, _) 773 | %x{ 774 | var method = self[#{name}]; 775 | if (typeof(method) === 'function') { 776 | return true; 777 | } else { 778 | return #{super}; 779 | } 780 | } 781 | end 782 | 783 | def method_missing(name, *args, &block) 784 | args << block if block_given? 785 | 786 | %x{ 787 | var method = self[#{name}]; 788 | if (typeof(method) === 'function') { 789 | return method.apply(self, #{args.to_n}); 790 | } else { 791 | return #{super}; 792 | } 793 | } 794 | end 795 | end 796 | -------------------------------------------------------------------------------- /lib/opal/jquery/event.rb: -------------------------------------------------------------------------------- 1 | # backtick_javascript: true 2 | 3 | require 'opal/jquery/constants' 4 | 5 | # {Event} wraps native jQuery events into a ruby api. Instances of events 6 | # can be accessed by {#to_n}. 7 | # 8 | # {Event} instances should not be created directly, as they are usually 9 | # created by one of the dom event handlers in {Element}. 10 | # 11 | # element.on :click do |event| 12 | # puts event 13 | # end 14 | # 15 | # # => # 16 | # 17 | # ## Usage 18 | # 19 | # {Event} exposes a slightly different API than jQuery, as {Event} tries to 20 | # add some more ruby flavour to the object. 21 | # 22 | # ### Accessing element triggering event 23 | # 24 | # Unlike jQuery, the context of an event handler is not set to the triggering 25 | # element. Instead, the element triggering the event can be accessed from the 26 | # {Event} instance. 27 | # 28 | # #### Current Target 29 | # 30 | # To access the current element in the bubbling phase, {#element} or 31 | # {#current_target} can be used which is the same as `currentTarget` or `this` 32 | # from jQuery. 33 | # 34 | # element.on :click do |event| 35 | # puts "element clicked: #{event.element} 36 | # end 37 | # 38 | # # => "element clicked: #]> 39 | # 40 | # #### Target 41 | # 42 | # The {#target} of an event is the actual dom element that triggered the event, 43 | # and this will be the same element through all phases of event bubbling. This 44 | # is the same as the `event.target` jQuery property. 45 | # 46 | # element.on :click do |event| 47 | # puts "actual element: #{event.target}" 48 | # end 49 | # 50 | # # => "actual element: #]> 51 | # 52 | # ### Controlling Event Bubbling 53 | # 54 | # Propagation and default behaviour can be controlled on events using {#prevent} 55 | # and {#stop}, which will prevent the browser default and stop event propagation 56 | # respectively. 57 | # 58 | # element.on :click do |event| 59 | # event.prevent # prevent browser default 60 | # event.stop # stop event propagation 61 | # end 62 | # 63 | # If you want to trigger both methods, which is usually the case, then {#kill} 64 | # can be used as a shorthand. 65 | # 66 | # element.on :click do |event| 67 | # event.kill 68 | # puts event.prevented? 69 | # puts event.stopped? 70 | # end 71 | # 72 | # # => true 73 | # # => true 74 | # 75 | class Event 76 | `var $ = #{JQUERY_SELECTOR.to_n}` # cache $ for SPEED 77 | 78 | # @private 79 | # @param native [JSObject] native jquery/javascript event 80 | def initialize(native) 81 | @native = native 82 | end 83 | 84 | # Returns native javascript event created by jQuery. 85 | # 86 | # @return [JSObject] 87 | def to_n 88 | @native 89 | end 90 | 91 | def [](name) 92 | `#@native[name]` 93 | end 94 | 95 | def type 96 | `#@native.type` 97 | end 98 | 99 | # Returns the current element in the bubbling cycle of the event. This is 100 | # not the same as the actual dom event that triggered the event, but is 101 | # usually the context element the event was registered with, or the target 102 | # of the css selector used in newer event styles. 103 | # 104 | # @return [Element] 105 | def element 106 | `$(#@native.currentTarget)` 107 | end 108 | 109 | alias current_target element 110 | 111 | # Returns the actual element that triggered the dom event. 112 | # 113 | # @return [Element] 114 | def target 115 | `$(#@native.target)` 116 | end 117 | 118 | # Returns `true` if this event has had its default browser behaviour 119 | # prevented, `false` otherwise. 120 | # 121 | # @return [Boolean] 122 | def prevented? 123 | `#@native.isDefaultPrevented()` 124 | end 125 | 126 | # Prevent this event from triggering its default browser behaviour. 127 | def prevent 128 | `#@native.preventDefault()` 129 | end 130 | 131 | # Returns `true` if the propagation/bubbling of this event has been stopped, 132 | # `false` otherwise. 133 | # 134 | # @return [Boolean] 135 | def stopped? 136 | `#@native.isPropagationStopped()` 137 | end 138 | 139 | # Stop further propagaion of this event. 140 | def stop 141 | `#@native.stopPropagation()` 142 | end 143 | 144 | def stop_immediate 145 | `#@native.stopImmediatePropagation()` 146 | end 147 | 148 | # Stops propagation and prevents default action. 149 | # 150 | # @see {#prevent} 151 | # @see {#stop} 152 | def kill 153 | stop 154 | prevent 155 | end 156 | 157 | ## 158 | # Keyboard/Mouse/Touch 159 | 160 | def page_x 161 | `#@native.pageX` 162 | end 163 | 164 | def page_y 165 | `#@native.pageY` 166 | end 167 | 168 | def touch_count 169 | `#@native.originalEvent.touches.length` 170 | end 171 | 172 | def touch_x(index = 0) 173 | `#@native.originalEvent.touches[#{index}].pageX` if index < touch_count 174 | end 175 | 176 | def touch_y(index = 0) 177 | `#@native.originalEvent.touches[#{index}].pageY` if index < touch_count 178 | end 179 | 180 | def location 181 | `#@native.originalEvent.location` 182 | end 183 | 184 | def ctrl_key 185 | `#@native.ctrlKey` 186 | end 187 | 188 | def meta_key 189 | `#@native.metaKey` 190 | end 191 | 192 | def alt_key 193 | `#@native.altKey` 194 | end 195 | 196 | def shift_key 197 | `#@native.shiftKey` 198 | end 199 | 200 | def key_code 201 | `#@native.keyCode` 202 | end 203 | 204 | def which 205 | `#@native.which` 206 | end 207 | 208 | # @deprecated These will be removed soon 209 | alias default_prevented? prevented? 210 | alias prevent_default prevent 211 | alias propagation_stopped? stopped? 212 | alias stop_propagation stop 213 | alias stop_immediate_propagation stop_immediate 214 | 215 | end 216 | -------------------------------------------------------------------------------- /lib/opal/jquery/http.rb: -------------------------------------------------------------------------------- 1 | # backtick_javascript: true 2 | 3 | require 'json' 4 | require 'native' 5 | require 'promise' 6 | require 'opal/jquery/constants' 7 | 8 | # {HTTP} is used to perform a `XMLHttpRequest` in ruby. It is a simple wrapper 9 | # around jQuerys' `$.ajax` call. `XMLHttpRequest` is not wrapped directly as 10 | # jquery provides some cross browser fixes. 11 | # 12 | # # Making requests 13 | # 14 | # To create a simple request, {HTTP} exposes class level methods to specify 15 | # the HTTP action you wish to perform. Each action accepts the url for the 16 | # request, as well as optional arguments passed as a hash: 17 | # 18 | # HTTP.get("/users/1.json") 19 | # HTTP.post("/users", payload: data) 20 | # 21 | # The supported `HTTP` actions are: 22 | # 23 | # * {HTTP.get} 24 | # * {HTTP.post} 25 | # * {HTTP.put} 26 | # * {HTTP.delete} 27 | # * {HTTP.patch} 28 | # * {HTTP.head} 29 | # 30 | # # Handling responses 31 | # 32 | # Responses can be handled using either a simple block callback, or using a 33 | # {Promise} returned by the request. 34 | # 35 | # ## Using a block 36 | # 37 | # All HTTP action methods accept a block which can be used as a simple 38 | # handler for the request. The block will be called for both successful as well 39 | # as unsuccessful requests. 40 | # 41 | # HTTP.get("/users/1") do |request| 42 | # puts "the request has completed!" 43 | # end 44 | # 45 | # This `request` object will simply be the instance of the {HTTP} class which 46 | # wraps the native `XMLHttpRequest`. {HTTP#ok?} can be used to quickly determine 47 | # if the request was successful. 48 | # 49 | # HTTP.get("/users/1") do |request| 50 | # if request.ok? 51 | # puts "request was success" 52 | # else 53 | # puts "something went wrong with request" 54 | # end 55 | # end 56 | # 57 | # The {HTTP} instance will always be the only object passed to the block. 58 | # 59 | # ## Using a Promise 60 | # 61 | # If no block is given to one of the action methods, then a {Promise} is 62 | # returned instead. See the standard library for more information on Promises. 63 | # 64 | # HTTP.get("/users/1").then do |req| 65 | # puts "response ok!" 66 | # end.fail do |req| 67 | # puts "response was not ok" 68 | # end 69 | # 70 | # When using a {Promise}, both success and failure handlers will be passed the 71 | # {HTTP} instance. 72 | # 73 | # # Accessing Response Data 74 | # 75 | # All data returned from an HTTP request can be accessed via the {HTTP} object 76 | # passed into the block or promise handlers. 77 | # 78 | # - {#ok?} - returns `true` or `false`, if request was a success (or not). 79 | # - {#body} - returns the raw text response of the request 80 | # - {#status_code} - returns the raw {HTTP} status code as integer 81 | # - {#json} - tries to convert the body response into a JSON object 82 | class HTTP 83 | `var $ = #{JQUERY_SELECTOR.to_n}` # cache $ for SPEED 84 | 85 | # All valid {HTTP} action methods this class accepts. 86 | # 87 | # @see HTTP.get 88 | # @see HTTP.post 89 | # @see HTTP.put 90 | # @see HTTP.delete 91 | # @see HTTP.patch 92 | # @see HTTP.head 93 | ACTIONS = %w[get post put delete patch head] 94 | 95 | # @!method self.get(url, options = {}, &block) 96 | # 97 | # Create a {HTTP} `get` request. 98 | # 99 | # @example 100 | # HTTP.get("/foo") do |req| 101 | # puts "got data: #{req.data}" 102 | # end 103 | # 104 | # @param url [String] url for request 105 | # @param options [Hash] any request options 106 | # @yield [self] optional block to handle response 107 | # @return [Promise, nil] optionally returns a promise 108 | 109 | # @!method self.post(url, options = {}, &block) 110 | # 111 | # Create a {HTTP} `post` request. Post data can be supplied using the 112 | # `payload` options. Usually this will be a hash which will get serialized 113 | # into a native javascript object. 114 | # 115 | # @example 116 | # HTTP.post("/bar", payload: data) do |req| 117 | # puts "got response" 118 | # end 119 | # 120 | # @param url [String] url for request 121 | # @param options [Hash] optional request options 122 | # @yield [self] optional block to yield for response 123 | # @return [Promise, nil] returns a {Promise} unless block given 124 | 125 | # @!method self.put(url, options = {}, &block) 126 | 127 | # @!method self.delete(url, options = {}, &block) 128 | 129 | # @!method self.patch(url, options = {}, &block) 130 | 131 | # @!method self.head(url, options = {}, &block) 132 | 133 | ACTIONS.each do |action| 134 | define_singleton_method(action) do |url, options = {}, &block| 135 | new.send(action, url, options, block) 136 | end 137 | 138 | define_method(action) do |url, options = {}, &block| 139 | send(action, url, options, block) 140 | end 141 | end 142 | 143 | def self.setup 144 | Hash.new(`$.ajaxSetup()`) 145 | end 146 | 147 | def self.setup= settings 148 | `$.ajaxSetup(#{settings.to_n})` 149 | end 150 | 151 | attr_reader :body, :error_message, :method, :status_code, :url, :xhr 152 | 153 | def initialize 154 | @settings = {} 155 | @ok = true 156 | end 157 | 158 | def send(method, url, options, block) 159 | @method = method 160 | @url = url 161 | @payload = options.delete :payload 162 | @handler = block 163 | 164 | @settings.update options 165 | 166 | settings, payload = @settings.to_n, @payload.to_n 167 | 168 | %x{ 169 | if (typeof(payload) === 'string' || settings.processData === false) { 170 | settings.data = payload; 171 | } 172 | else if (payload != null) { 173 | settings.data = JSON.stringify(payload); 174 | settings.contentType = 'application/json'; 175 | } 176 | 177 | settings.url = #@url; 178 | settings.type = #{@method.upcase}; 179 | 180 | settings.success = function(data, status, xhr) { 181 | return #{ succeed `data`, `status`, `xhr` }; 182 | }; 183 | 184 | settings.error = function(xhr, status, error) { 185 | return #{ fail `xhr`, `status`, `error` }; 186 | }; 187 | 188 | $.ajax(settings); 189 | } 190 | 191 | @handler ? self : promise 192 | end 193 | 194 | # Parses the http response body through json. If the response is not 195 | # valid JSON then an error will very likely be thrown. 196 | # 197 | # @example Getting JSON content 198 | # HTTP.get("api.json") do |response| 199 | # puts response.json 200 | # end 201 | # 202 | # # => {"key" => 1, "bar" => 2, ... } 203 | # 204 | # @return [Hash, Array] returns the parsed json 205 | def json 206 | @json ||= JSON.parse(@body) 207 | end 208 | 209 | # Returns true if the request succeeded, false otherwise. 210 | # 211 | # @example 212 | # HTTP.get("/some/url") do |response| 213 | # if response.ok? 214 | # alert "Yay!" 215 | # else 216 | # alert "Aww :(" 217 | # end 218 | # 219 | # @return [true, false] true if request was successful 220 | def ok? 221 | @ok 222 | end 223 | 224 | # Returns the value of the specified response header. 225 | # 226 | # @param key [String] name of the header to get 227 | # @return [String] value of the header 228 | # @return [nil] if the header +key+ was not in the response 229 | def get_header(key) 230 | %x{ 231 | var value = #@xhr.getResponseHeader(#{key}); 232 | return (value === null) ? nil : value; 233 | } 234 | end 235 | 236 | def inspect 237 | "#" 238 | end 239 | 240 | private 241 | 242 | def promise 243 | return @promise if @promise 244 | 245 | @promise = Promise.new.tap { |promise| 246 | @handler = proc { |res| 247 | if res.ok? 248 | promise.resolve res 249 | else 250 | promise.reject res 251 | end 252 | } 253 | } 254 | end 255 | 256 | def succeed(data, status, xhr) 257 | %x{ 258 | #@body = data; 259 | #@xhr = xhr; 260 | #@status_code = xhr.status; 261 | 262 | if (typeof(data) === 'object') { 263 | #@json = #{ JSON.from_object `data` }; 264 | } 265 | } 266 | 267 | @handler.call self if @handler 268 | end 269 | 270 | def fail(xhr, status, error) 271 | %x{ 272 | #@body = xhr.responseText; 273 | #@xhr = xhr; 274 | #@status_code = xhr.status; 275 | } 276 | 277 | @ok = false 278 | @handler.call self if @handler 279 | end 280 | end 281 | -------------------------------------------------------------------------------- /lib/opal/jquery/kernel.rb: -------------------------------------------------------------------------------- 1 | # backtick_javascript: true 2 | 3 | module Kernel 4 | # Alert the given message using `window.alert()`. This is a blocking 5 | # method. 6 | # 7 | # @param msg [String] message to alert 8 | # @return [nil] 9 | def alert(msg) 10 | `alert(msg)` 11 | nil 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/opal/jquery/local_storage.rb: -------------------------------------------------------------------------------- 1 | # backtick_javascript: true 2 | 3 | module Browser 4 | # {Browser::LocalStorage} is a simple wrapper around `localStorage` in the 5 | # browser. 6 | # 7 | # Instead of using the class directly, the main instance {LocalStorage} 8 | # should be used instead. This class can be used to wrap an instance from 9 | # another window or iframe if required. 10 | # 11 | # ## Usage 12 | # 13 | # LocalStorage is not included by default when you require opal-jquery, so 14 | # you will need to require it explicitly in your code: 15 | # 16 | # require 'opal/jquery' 17 | # require 'opal/jquery/local_storage' 18 | # 19 | # puts LocalStorage 20 | # # => # 21 | # 22 | # ## Example Usage 23 | # 24 | # LocalStorage['foo'] = 'hello world' 25 | # 26 | # LocalStorage['foo'] # => "hello world" 27 | # LocalStorage['bar'] # => nil 28 | # 29 | # @see LocalStorage 30 | # 31 | class LocalStorage 32 | def initialize(storage) 33 | @storage = storage 34 | end 35 | 36 | # Set a value in storage. 37 | # 38 | # Values stored in {LocalStorage} will be stored as strings. To store any 39 | # other type of object, you will need to convert them to a string first, 40 | # and then convert them back from {#[]}. For this reason it is recommended 41 | # to only store {JSON} based objects in storage, so they can be easily 42 | # converted back and forth. 43 | # 44 | # @param key [String] string key 45 | # @param value [String, #to_s] string or explicitly converted object 46 | def []=(key, value) 47 | %x{ 48 | #@storage.setItem(key, value); 49 | return value; 50 | } 51 | end 52 | 53 | # Retrieve an object from {LocalStorage}. 54 | # 55 | # Only string values can be stored, so any object will be returned as a 56 | # string. You will need to handle any conversion back into a normal 57 | # object. {JSON.parse} could be used, for example, to parse back into 58 | # arrays or hashes. 59 | # 60 | # If a key is not present in the storage, then `nil` will be returned. 61 | # 62 | # @param key [String] key to lookup 63 | # @return [String, nil] 64 | def [](key) 65 | %x{ 66 | var value = #@storage.getItem(key); 67 | return value == null ? nil : value; 68 | } 69 | end 70 | 71 | # Removes a specific `key` from storage. If the key does not exist then 72 | # there is no side effect. 73 | # 74 | # @param key [String] key to remove 75 | def delete(key) 76 | `#@storage.removeItem(key)` 77 | end 78 | 79 | # Remove all key/values from storage 80 | def clear 81 | `#@storage.clear()` 82 | end 83 | end 84 | end 85 | 86 | # {LocalStorage} is the top level instance of {Browser::LocalStorage} that 87 | # wraps `window.localStorage`, aka the `localStorage` object available on 88 | # the main window. 89 | LocalStorage = Browser::LocalStorage.new(`window.localStorage`) 90 | -------------------------------------------------------------------------------- /lib/opal/jquery/rspec.rb: -------------------------------------------------------------------------------- 1 | module Browser 2 | # When testing (using rspec), this module provides some helper methods to 3 | # try and make testing a little easier. 4 | # 5 | # ## Usage 6 | # 7 | # Simply require this file somewher (usualy in `spec_helper.rb`): 8 | # 9 | # require 'application' 10 | # require 'opal-rspec' 11 | # require 'opal/jquery/rspec' 12 | # 13 | # Once required, the module is registered with `rspec` for all example 14 | # groups. 15 | # 16 | # ## Adding html to DOM 17 | # 18 | # It is often convenient to have some `HTML` code ready in the dom for 19 | # testing. This helper method adds the given html string. More so, the html 20 | # is then removed at the end of each test. This is cruicial as it ensures 21 | # that the specified html code is inserted before each test, therefore 22 | # ensuring that html code changed by a test will not affect any other tests 23 | # in the same scope. 24 | # 25 | # describe "Check DOM code" do 26 | # html '
' 27 | # 28 | # it "foo should exist" do 29 | # expect(Document['#foo']).to_not be_empty 30 | # end 31 | # end 32 | # 33 | module RSpecHelpers 34 | # Add some html code to the body tag ready for testing. This will 35 | # be added before each test, then removed after each test. It is 36 | # convenient for adding html setup quickly. The code is wrapped 37 | # inside a div, which is directly inside the body element. 38 | # 39 | # describe "DOM feature" do 40 | # html <<-HTML 41 | #
42 | # HTML 43 | # 44 | # it "foo should exist" do 45 | # Document["#foo"] 46 | # end 47 | # end 48 | # 49 | # @param [String] html_string html content to add 50 | def html(html_string='') 51 | html = %Q{
#{html_string}
} 52 | 53 | before do 54 | @_spec_html = Element.parse(html) 55 | @_spec_html.append_to_body 56 | end 57 | 58 | after { @_spec_html.remove } 59 | end 60 | end 61 | end 62 | 63 | RSpec.configure do |config| 64 | config.extend Browser::RSpecHelpers 65 | end 66 | -------------------------------------------------------------------------------- /lib/opal/jquery/version.rb: -------------------------------------------------------------------------------- 1 | module Opal 2 | module JQuery 3 | VERSION = '0.5.2' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/opal/jquery/window.rb: -------------------------------------------------------------------------------- 1 | # backtick_javascript: true 2 | 3 | require 'opal/jquery/element' 4 | 5 | module Browser 6 | # {Window} instances are {Native} objects used to wrap native window instances. 7 | # 8 | # Generally, you will want to use the top level {::Window} instance, which 9 | # wraps `window` from the main page. 10 | class Window 11 | # In more recent Opal versions Native::Wrapper should be used 12 | include defined?(Native::Wrapper) ? Native::Wrapper : Native 13 | 14 | # Returns this {Window} instance wrapped as an {Element}. Useful for 15 | # delegating jQuery events, which allows the use of `window` as target. 16 | # 17 | # @return [Element] 18 | def element 19 | @element ||= Element.find(`window`) 20 | end 21 | 22 | # @see Element#on 23 | def on(*args, &block) 24 | element.on(*args, &block) 25 | end 26 | 27 | # @see Element#off 28 | def off(*args, &block) 29 | element.off(*args, &block) 30 | end 31 | 32 | # @see Element#trigger 33 | def trigger(*args) 34 | element.trigger(*args) 35 | end 36 | end 37 | end 38 | 39 | # Top level {Browser::Window} instance. 40 | Window = Browser::Window.new(`window`) 41 | 42 | # TODO: this will be removed soon. 43 | $window = Window 44 | -------------------------------------------------------------------------------- /opal-jquery.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path('../lib/opal/jquery/version', __FILE__) 3 | 4 | Gem::Specification.new do |s| 5 | s.name = 'opal-jquery' 6 | s.version = Opal::JQuery::VERSION 7 | s.authors = ['Adam Beynon', 'Elia Schito', 'Andy Maleh'] 8 | s.email = 'elia@schito.me' 9 | s.homepage = 'https://github.com/opal/opal-jquery#readme' 10 | s.summary = 'Opal access to jQuery' 11 | s.description = 'Opal DOM library for jQuery (Use jQuery with Ruby code)' 12 | 13 | s.files = `git ls-files`.split("\n") 14 | s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) } 15 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 16 | s.require_paths = ['lib'] 17 | 18 | s.add_runtime_dependency 'opal', ['>= 0.10.0', '< 2.0'] 19 | s.add_development_dependency 'opal-rspec', ['>= 0.7', '< 2.0'] 20 | s.add_development_dependency 'opal-sprockets', ['>= 0.4.1', '< 2.0'] 21 | s.add_development_dependency 'yard' 22 | s.add_development_dependency 'rake' 23 | end 24 | -------------------------------------------------------------------------------- /spec-opal/document_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe 'Document' do 4 | subject { Document } 5 | 6 | describe "ready?" do 7 | it "accepts a block" do 8 | Document.ready? { 9 | puts `$.fn.jquery` 10 | } 11 | end 12 | 13 | it "accepts a block" do 14 | Document.ready? { } 15 | end 16 | end 17 | 18 | describe "ready" do 19 | p self.ancestors 20 | async "resolves when document is ready" do 21 | Document.ready.then do 22 | async { Document.ready.resolved?.should be_truthy } 23 | end 24 | end 25 | end 26 | 27 | describe "title" do 28 | it "gets the document title" do 29 | Document.title.should be_kind_of(String) 30 | end 31 | end 32 | 33 | describe "title=" do 34 | it "sets the document title" do 35 | old = Document.title 36 | Document.title = "foo" 37 | Document.title.should eq("foo") 38 | Document.title = old 39 | end 40 | end 41 | 42 | describe "head" do 43 | it "returns the head element as an Element instance" do 44 | expect(subject.head).to be_kind_of(Element) 45 | expect(subject.head.tag_name).to eq('head') 46 | end 47 | end 48 | 49 | describe "body" do 50 | it "returns the body element as an Element instance" do 51 | expect(subject.body).to be_kind_of(Element) 52 | expect(subject.body.tag_name).to eq('body') 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /spec-opal/element/after_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe 'Element#after' do 4 | html <<-HTML 5 |
6 |
7 |
8 |
9 | HTML 10 | 11 | it 'should insert the given html string after each element' do 12 | el = find '.after-spec-first' 13 | expect(el.size).to eq(2) 14 | 15 | el.after '

' 16 | 17 | expect(find('#foo').next.class_name).to eq('woosh') 18 | expect(find('#bar').next.class_name).to eq('woosh') 19 | end 20 | 21 | it 'should insert the given DOM element after this element' do 22 | find('#baz').after find('#some-header') 23 | expect(find('#baz').next.id).to eq('some-header') 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec-opal/element/animations_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe "Element animation methods" do 4 | html <<-HTML 5 |
6 | HTML 7 | 8 | describe "#animate" do 9 | ### HACKY 10 | # jQUery's animate method doesn't *always* finish on time 11 | # so the values are being compared using greater than 12 | 13 | async "should animate a set of properties and values" do 14 | foo = Element.find "#animate-foo" 15 | foo.animate :width => "200px" 16 | 17 | delay 0.4 do 18 | async { (foo.css("width").to_f > 199).should eq(true) } 19 | end 20 | end 21 | 22 | async "should allow you to set a speed in the params" do 23 | foo = Element.find "#animate-foo" 24 | foo.animate :width => "200px", :speed => 100 25 | 26 | delay 0.150 do 27 | async { (foo.css("width").to_f > 199).should eq(true) } 28 | end 29 | end 30 | 31 | async "should accept a block as a callback" do 32 | foo = Element.find "#animate-foo" 33 | foo.animate :width => "200px", :speed => 100 do 34 | foo.add_class "finished" 35 | end 36 | 37 | delay 0.405 do 38 | async { foo.class_name.should eq("finished") } 39 | end 40 | end 41 | end 42 | end 43 | 44 | RSpec.describe "Element effects methods" do 45 | html <<-HTML 46 |
47 | HTML 48 | 49 | describe "#fadeout / #fadein" do 50 | async "should fade the element out first" do 51 | foo = Element.find "#effects-foo" 52 | foo.effect(:fade_out) 53 | 54 | delay 1 do 55 | async { 56 | foo.css("display").should eq("none") 57 | foo.effect(:fade_in) 58 | } 59 | end 60 | end 61 | async "should fade the element back in" do 62 | delay 2 do 63 | async { Element["#effects-foo"].css("display").should eq("block") } 64 | end 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /spec-opal/element/append_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe "Element#append" do 4 | html <<-HTML 5 |
6 |
7 |
8 |
9 | HTML 10 | 11 | it "should insert the HTML string to the end of each element" do 12 | Element.find('.first-append').append '

' 13 | 14 | Element.find('#foo').children.class_name.should == "woosh" 15 | Element.find('#bar').children.class_name.should == "woosh" 16 | end 17 | 18 | it "should insert the given DOM node at the end of the element" do 19 | baz = Element.find('#baz') 20 | buz = Element.find('#buz') 21 | 22 | baz.children.size.should == 0 23 | baz.append buz 24 | 25 | baz.children.size.should == 1 26 | baz.children.id.should == "buz" 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec-opal/element/append_to_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe "Element#append_to" do 4 | html <<-HTML 5 |
6 |
7 |
8 | HTML 9 | 10 | it "should insert the receiver into the target element" do 11 | Element.find('#foo').children.size.should == 0 12 | 13 | Element.parse('
    ').append_to Element.find('#foo') 14 | Element.find('#foo').children.class_name.should == "kapow" 15 | 16 | Element.find('#bar').append_to Element.find('#baz') 17 | Element.find('#baz').children.id.should == "bar" 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec-opal/element/at_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe "Element#at" do 4 | html <<-HTML 5 |
    6 |
    7 |
    8 | HTML 9 | 10 | it "returns the element at the given index" do 11 | foos = Element.find '.foo' 12 | foos.length.should == 3 13 | 14 | foos.at(0).id.should == "blah" 15 | foos.at(1).id.should == "bleh" 16 | foos.at(2).id.should == "bluh" 17 | end 18 | 19 | it "counts from the last index for negative values" do 20 | foos = Element.find '.foo' 21 | 22 | foos.at(-1).id.should == "bluh" 23 | foos.at(-2).id.should == "bleh" 24 | foos.at(-3).id.should == "blah" 25 | end 26 | 27 | it "returns nil for indexes outside range" do 28 | foos = Element.find '.foo' 29 | 30 | foos.at(-4).should == nil 31 | foos.at(4).should == nil 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec-opal/element/attributes_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe Element do 4 | html <<-HTML 5 |
    6 |
    7 |
    8 | 9 |
    10 |
    11 |
    12 |
    13 |
    14 |
    15 |
    16 | 17 |
    18 |
    19 | 20 |
    Hey there
    21 |

    Erm

    22 | 23 |
    Hello
    24 |
    Hello as well
    25 | 26 |
    27 | 28 |
    29 |
    30 |
    31 | 32 |
    33 | 34 | 38 | 39 | 40 |
    41 | 42 | 43 | HTML 44 | 45 | describe '#[]' do 46 | it 'should retrieve the attr value from the element' do 47 | Element.find('#attr-foo')[:title].should == "Hello there!" 48 | end 49 | 50 | it 'should return nil for a missing attribute' do 51 | expect(Element.find('#attr-missing')['attr-missing-value']).to be_nil 52 | end 53 | 54 | it 'should return "" for an attribute with empty value' do 55 | expect(Element.find('#attr-empty')['attr-empty-value']).to eq("") 56 | 57 | # Not sure if this is browser dependant 58 | expect(Element.find('#attr-missing')['attr-auto-value']).to eq("") 59 | end 60 | end 61 | 62 | describe '#attr' do 63 | it 'returns attributes from elements' do 64 | expect(Element.find('#attr-foo').attr(:title)).to eq('Hello there!') 65 | end 66 | 67 | it 'should return nil for a missing attribute' do 68 | expect(Element.find('#attr-missing').attr('attr-missing-value')).to be_nil 69 | end 70 | 71 | it 'should return "" for an attribute with empty value' do 72 | expect(Element.find('#attr-empty').attr('attr-empty-value')).to eq("") 73 | 74 | # Not sure if this is browser dependant 75 | expect(Element.find('#attr-missing').attr('attr-auto-value')).to eq("") 76 | end 77 | end 78 | 79 | describe '#[]=' do 80 | it 'should set the attr value on the element' do 81 | woosh = Element.find '#attr-woosh' 82 | expect(woosh[:title]).to be_nil 83 | 84 | woosh[:title] = "Oranges" 85 | expect(woosh[:title]).to eq('Oranges') 86 | end 87 | 88 | it 'should replace the old value for the attribute' do 89 | kapow = Element.find '#attr-kapow' 90 | kapow[:title].should == "Apples" 91 | 92 | kapow[:title] = "Pineapple" 93 | kapow[:title].should == "Pineapple" 94 | end 95 | end 96 | 97 | describe "#add_class" do 98 | it "should add the given class name to the element" do 99 | foo = Element.find '#foo' 100 | expect(foo).to_not have_class('lemons') 101 | 102 | foo.add_class 'lemons' 103 | expect(foo).to have_class('lemons') 104 | end 105 | 106 | it "should not duplicate class names on an element" do 107 | bar = Element.find '#bar' 108 | expect(bar).to have_class('apples') 109 | 110 | bar.add_class 'apples' 111 | expect(bar.class_name).to eq('apples') 112 | end 113 | 114 | it "should return self" do 115 | baz = Element.find '#baz' 116 | expect(baz.add_class('oranges')).to eq(baz) 117 | end 118 | end 119 | 120 | describe '#has_class?' do 121 | it "should return true if the element has the given class" do 122 | expect(Element.find('#has-foo')).to have_class('apples') 123 | expect(Element.find('#has-bar')).to have_class('lemons') 124 | 125 | expect(Element.find('#has-foo')).to_not have_class('oranges') 126 | end 127 | end 128 | 129 | describe '#html' do 130 | it "should return the html content of the element" do 131 | Element.find('#html-foo').html.should == "Hey there" 132 | Element.find('#html-bar').html.downcase.should == "

    erm

    " 133 | end 134 | 135 | it "should only return html for first matched element" do 136 | Element.find('.html-bridge').html.should == "Hello" 137 | end 138 | 139 | it "should return empty string for empty set" do 140 | Element.find('.html-nothing-here').html.should == "" 141 | end 142 | end 143 | 144 | describe '#remove_class' do 145 | it "should have no effect on elements without class" do 146 | foo = Element.find '#remove-foo' 147 | foo.class_name.should == '' 148 | foo.remove_class 'blah' 149 | foo.class_name.should == '' 150 | end 151 | 152 | it "should remove the given class from the element" do 153 | bar = Element.find '#remove-bar' 154 | bar.remove_class "lemons" 155 | bar.class_name.should == '' 156 | 157 | baz = Element.find '#remove-baz' 158 | baz.remove_class 'lemons' 159 | baz.class_name.should == 'apples oranges' 160 | 161 | baz.remove_class 'apples' 162 | baz.class_name.should == 'oranges' 163 | 164 | buz = Element.find '#remove-buz' 165 | buz.remove_class 'mangos' 166 | buz.class_name.should == 'pineapples' 167 | 168 | buz.remove_class 'pineapples' 169 | buz.class_name.should == '' 170 | end 171 | 172 | it "should return self" do 173 | bleh = Element.find '#remove-bleh' 174 | bleh.remove_class('fruit').should equal(bleh) 175 | bleh.remove_class('hmmmm').should equal(bleh) 176 | end 177 | end 178 | 179 | describe '#toggle_class' do 180 | it 'adds the given class name to the element if not already present' do 181 | foo = Element.find('#foo') 182 | foo.has_class?('oranges').should eq(false) 183 | foo.toggle_class 'oranges' 184 | foo.has_class?('oranges').should eq(true) 185 | end 186 | 187 | it 'removes the class if the element already has it' do 188 | bar = Element.find('#bar') 189 | bar.has_class?('apples').should eq(true) 190 | bar.toggle_class 'apples' 191 | bar.has_class?('apples').should eq(false) 192 | end 193 | end 194 | 195 | describe "#value" do 196 | it "should return the selected value of select elements" do 197 | Element.find('#value-foo').value.should == "Hello" 198 | end 199 | 200 | it "should return the value of normal input fields" do 201 | Element.find('#value-bar').value.should == "Blah" 202 | end 203 | 204 | it "should return an empty string for elements with no value attr" do 205 | Element.find('#value-baz').value.should == "" 206 | end 207 | end 208 | 209 | describe "#value=" do 210 | it "should set the value of the element to the given value" do 211 | foo = Element.find '#value-woosh' 212 | foo.value.should == "" 213 | 214 | foo.value = "Hi" 215 | foo.value.should == "Hi" 216 | 217 | foo.value = "There" 218 | foo.value.should == "There" 219 | end 220 | end 221 | end 222 | -------------------------------------------------------------------------------- /spec-opal/element/before_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe "Element#before" do 4 | html <<-HTML 5 |
    6 |
    7 |
    8 |
    9 | HTML 10 | 11 | it "should insert the given html string before each element" do 12 | el = Element.find('.before-spec-first') 13 | el.size.should == 2 14 | 15 | el.before '

    ' 16 | 17 | Element.find('#foo').prev.class_name.should == "woosh" 18 | Element.find('#bar').prev.class_name.should == "woosh" 19 | end 20 | 21 | it "should insert the given DOM element before this element" do 22 | Element.find('#baz').before Element.find('#some-header') 23 | Element.find('#baz').prev.id.should == "some-header" 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec-opal/element/class_name_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe "Element#class_name" do 4 | html <<-HTML 5 |
    6 |
    7 |
    8 |
    9 | 10 |
    11 |
    12 | HTML 13 | 14 | it "should return the elements' class name" do 15 | Element.find('#foo').class_name.should == "whiskey" 16 | Element.find('#bar').class_name.should == "scotch brandy" 17 | end 18 | 19 | it "should return an empty string for element with no class name" do 20 | Element.find('#baz').class_name.should == "" 21 | Element.find('#buz').class_name.should == "" 22 | end 23 | 24 | it "should return class name for first element if more than 1 in set" do 25 | Element.find('.red').class_name.should == "red dark" 26 | end 27 | 28 | it "should return an empty string for instances with no elements" do 29 | Element.find('.no-elements').class_name.should == "" 30 | end 31 | end 32 | 33 | RSpec.describe "Element#class_name=" do 34 | before do 35 | @div = Element.parse <<-HTML 36 |
    37 |
    38 |
    39 | 40 |
    41 |
    42 |
    43 | HTML 44 | 45 | @div.append_to_body 46 | end 47 | 48 | after do 49 | @div.remove 50 | end 51 | 52 | it "should set the given class name on the element" do 53 | Element.find('#foo').class_name = "apples" 54 | Element.find('#foo').class_name.should == "apples" 55 | end 56 | 57 | it "should replace any existing class name" do 58 | bar = Element.find('#bar') 59 | bar.class_name.should == "oranges" 60 | 61 | bar.class_name = "lemons" 62 | bar.class_name.should == "lemons" 63 | end 64 | 65 | it "should set the class name on all elements in instance" do 66 | el = Element.find '.banana' 67 | el.length.should == 2 68 | 69 | el.class_name = "pop" 70 | 71 | Element.find('#baz').class_name.should == "pop" 72 | Element.find('#buz').class_name.should == "pop" 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /spec-opal/element/css_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe "Element#css" do 4 | html <<-HTML 5 |
    6 |
    7 |
    8 | HTML 9 | 10 | describe "with a given name" do 11 | it "returns the value of the CSS property for the given name" do 12 | Element.find('#foo').css('backgroundColor').should be_kind_of(String) 13 | end 14 | 15 | it "should return an empty string when no style property is defined for name" do 16 | Element.find('#foo').css('color').should be_kind_of(String) 17 | end 18 | end 19 | 20 | describe "with a name and value" do 21 | it "should set the CSS property to the given value" do 22 | Element.find('#bar').css('backgroundColor', 'blue') 23 | end 24 | 25 | it "returns self" do 26 | bar = Element.find('#bar') 27 | bar.css("background", "green").should equal(bar) 28 | end 29 | end 30 | 31 | describe "with a set of names and values" do 32 | it "should set the properties" do 33 | hash = Element.find("#hash") 34 | hash.css(:width => "100px", :height => "200px") 35 | hash.css("width").should be_kind_of(String) 36 | hash.css("height").should be_kind_of(String) 37 | end 38 | 39 | it "should return self" do 40 | hash = Element.find("#hash") 41 | hash.css(:border => "1px solid #000").should equal(hash) 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec-opal/element/display_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe "Element display methods" do 4 | html <<-HTML 5 |
    6 | 7 | HTML 8 | 9 | it "hides an element" do 10 | element = Element.find('#shown') 11 | element.css('display').should == 'block' 12 | element.hide 13 | element.css('display').should == 'none' 14 | end 15 | 16 | it "shows an element" do 17 | element = Element.find('#hidden') 18 | element.css('display').should == 'none' 19 | element.show 20 | element.css('display').should == 'block' 21 | end 22 | 23 | it "toggles on a hidden element" do 24 | element = Element.find('#hidden') 25 | element.css('display').should == 'none' 26 | element.toggle 27 | element.css('display').should == 'block' 28 | end 29 | 30 | it "toggles off a displayed element" do 31 | element = Element.find('#shown') 32 | element.css('display').should == 'block' 33 | element.toggle 34 | element.css('display').should == 'none' 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec-opal/element/expose_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe "Element#expose" do 4 | subject(:element) { Element.new } 5 | before do 6 | `$.fn.exposableMethod = function() {return 123}` 7 | `$.fn.exposableMethod2 = function() {return 12}` 8 | `$.fn.opal_specs_extension = function() {return "foo_bar_baz";};` 9 | `$.fn.opal_specs_args = function() {return Array.prototype.slice.call(arguments);};` 10 | Element.expose :opal_specs_extension, :opal_specs_args 11 | end 12 | 13 | after do 14 | `delete $.fn.exposableMethod; delete $.fn.$exposableMethod;` 15 | `delete $.fn.exposableMethod2; delete $.fn.$exposableMethod2;` 16 | `delete $.fn.opal_specs_extension; delete $.fn.$opal_specs_extension;` 17 | `delete $.fn.opal_specs_args; delete $.fn.$opal_specs_args;` 18 | end 19 | 20 | it "exposes jquery plugins by given name" do 21 | Element.new.opal_specs_extension.should eq("foo_bar_baz") 22 | end 23 | 24 | it "forwards any args onto native function" do 25 | Element.new.opal_specs_args(:foo, 42, false).should eq([:foo, 42, false]) 26 | end 27 | 28 | it "only forwards calls when a native method exists" do 29 | expect { 30 | Element.new.some_unknown_plugin 31 | }.to raise_error(Exception) 32 | end 33 | 34 | it 'exposes methods defined on $.fn' do 35 | expect(element).to respond_to(:exposableMethod) # via #respond_to_missing? 36 | expect(element.methods).not_to include(:exposableMethod) 37 | Element.expose :exposableMethod 38 | expect(element.methods).to include(:exposableMethod) 39 | expect(element).to respond_to(:exposableMethod) # via method missing 40 | expect(element.exposableMethod).to eq(123) 41 | end 42 | 43 | it 'work if exposing the same method multiple times' do 44 | Element.expose :exposableMethod 45 | Element.expose :exposableMethod 46 | expect(element.exposableMethod).to eq(123) 47 | 48 | Element.expose :exposableMethod, :exposableMethod 49 | expect(element.exposableMethod).to eq(123) 50 | end 51 | 52 | it 'work if exposing multiple methods' do 53 | Element.expose :exposableMethod, :exposableMethod2 54 | expect(element.exposableMethod).to eq(123) 55 | expect(element.exposableMethod2).to eq(12) 56 | end 57 | 58 | it 'does not call method_missing after the method is exposed' do 59 | pending "broken on opal < 0.11" if RUBY_ENGINE_VERSION.to_f < 0.11 60 | 61 | expect(element).to receive(:method_missing).once.with(:exposableMethod) 62 | def element.method_missing(name) 63 | "#{name} (via method_missing)" 64 | end 65 | 66 | expect(element.exposableMethod).to eq("exposableMethod (via method_missing)") 67 | Element.expose :exposableMethod 68 | 69 | expect(element).not_to receive(:method_missing).once 70 | element.exposableMethod 71 | end 72 | 73 | end 74 | -------------------------------------------------------------------------------- /spec-opal/element/height_width_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe "Element height and width" do 4 | html <<-HTML 5 |
    6 | HTML 7 | 8 | describe '#height' do 9 | it "should grab height of existing element" do 10 | elm = Element.id('dimensions') 11 | 12 | expect(elm.height).to eq(200) 13 | end 14 | 15 | it "should return nil if item does not exist" do 16 | elm = Element.find('#not-an-elm') 17 | 18 | expect(elm.height).to eq(nil) 19 | end 20 | end 21 | 22 | describe '#height=' do 23 | it "should allow us to set height" do 24 | elm = Element.id('dimensions') 25 | elm.height = 121 26 | 27 | expect(elm.height).to eq(121) 28 | end 29 | end 30 | 31 | describe '#width' do 32 | it "should grab width of existing element" do 33 | elm = Element.id('dimensions') 34 | 35 | expect(elm.width).to eq(100) 36 | end 37 | 38 | it "should return nil if item does not exist" do 39 | elm = Element.find('#not-an-elm') 40 | 41 | expect(elm.width).to eq(nil) 42 | end 43 | end 44 | 45 | describe '#width=' do 46 | it "should allow us to set width" do 47 | elm = Element.id('dimensions') 48 | elm.width = 121 49 | 50 | expect(elm.width).to eq(121) 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /spec-opal/element/inspect_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe "Element#inspect" do 4 | html <<-HTML 5 |
    6 |
    7 |

    8 | HTML 9 | 10 | it "returns a string representation of the elements" do 11 | Element.find('#foo').inspect.should == '#]>' 12 | Element.find('.bar').inspect.should == '#,

    ]>' 13 | end 14 | 15 | it "returns '[]' when called on empty element set" do 16 | Element.find('.inspect-spec-none').inspect.should == '#' 17 | end 18 | 19 | it "returns '[]' when called on empty element set" do 20 | Element.find('.inspect-spec-none').inspect.should == '#' 21 | end 22 | 23 | it "returns '[document]' when called on $(document)" do 24 | Element.find(`document`).inspect.should == '#' 25 | end 26 | 27 | it "returns '[window]' when called on $(window)" do 28 | Element.find(`window`).inspect.should == '#' 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec-opal/element/iterable_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe Element do 4 | html <<-HTML 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
    mariorossi
    paolobianchi
    16 | HTML 17 | 18 | describe '#each' do 19 | it "should change all td to pippa" do 20 | Element.find('table.players td').each do |el| 21 | el.html = "pippa" 22 | end 23 | 24 | Element.find('table.players td').first.html.should == 'pippa' 25 | end 26 | end 27 | 28 | describe '#map' do 29 | it "should change all td.surname as array of stirng" do 30 | lst = Element.find('table.players td.surname').map {|el| el.html } 31 | 32 | lst.should == ['rossi','bianchi'] 33 | end 34 | end 35 | 36 | describe "#to_a" do 37 | it "should return a list of class Array" do 38 | Element.find('table.players td.surname').to_a.class.should == Array 39 | end 40 | 41 | it "should check first and last element" do 42 | Element.find('table.players td.surname').to_a.first.html == "rossi" 43 | Element.find('table.players td.surname').to_a.last.html == "bianchi" 44 | end 45 | 46 | it "should get only element with class surname" do 47 | Element.find('table.players td').to_a.select {|el| el.has_class?('surname') }. 48 | map {|el| el.class }.uniq == ['surname'] 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /spec-opal/element/length_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe "Element#length" do 4 | it "should report the number of elements in the instance" do 5 | Element.new.length.should == 1 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec-opal/element/method_missing_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe "Element#method_missing" do 4 | context 'with missing property' do 5 | html %{

    } 6 | 7 | it 'fallsback on method_missing when a method is unknown' do 8 | expect(Element['#foo']).to respond_to(:offsetParent) 9 | expect(Element['#foo'].offsetParent).to eq(Element['body']) 10 | end 11 | end 12 | 13 | context 'jQuery plugin methods' do 14 | subject(:element) { Element.new } 15 | 16 | before do 17 | `$.fn.pluginMethod = function() {return 123}` 18 | end 19 | 20 | after do 21 | `delete $.fn.pluginMethod; delete $.fn.$pluginMethod;` 22 | end 23 | 24 | it 'calls method_missing' do 25 | expect(element).to receive(:method_missing).once.with(:pluginMethod) 26 | element.pluginMethod 27 | end 28 | 29 | it 'calls forwards to the plugin', :focus do 30 | pending "broken on opal < 0.11" if RUBY_ENGINE_VERSION.to_f < 0.11 31 | expect(element.pluginMethod).to eq(123) 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec-opal/element/prepend_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe 'Element#prepend' do 4 | html <<-HTML 5 |
    6 |
    7 |

    Something

    8 |
    9 | HTML 10 | 11 | it 'inserts the given html to beginning of element' do 12 | foo = Element.find '#foo' 13 | bar = Element.find '#bar' 14 | 15 | foo.prepend 'Bore Da' 16 | expect(foo.children.first.text).to eq 'Bore Da' 17 | 18 | bar.prepend 'Charlie' 19 | expect(bar.children.first.text).to eq 'Charlie' 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec-opal/element/to_s_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe "Element#to_s" do 4 | html <<-HTML 5 |
    hi
    6 |
    7 |

    8 | HTML 9 | 10 | it "returns a string representation of the elements" do 11 | Element.find('#foo').to_s.should == '

    hi
    ' 12 | Element.find('.bar').to_s.should == '
    ,

    ' 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec-opal/element/traversing_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe Element do 4 | before do 5 | @div = Element.parse <<-HTML 6 |
    7 |
    8 |
    9 |

    Hey

    10 |

    There

    11 |
    12 |
    13 |

    14 |
    15 |
    16 | HTML 17 | 18 | @div.append_to_body 19 | end 20 | 21 | after do 22 | @div.remove 23 | end 24 | 25 | describe '#children' do 26 | it "should return a new collection of all direct children of element" do 27 | Element.find('#foo').children.size.should == 0 28 | Element.find('#bar').children.size.should == 2 29 | end 30 | 31 | it "should only return direct children" do 32 | c = Element.find('#baz').children 33 | c.size.should == 1 34 | end 35 | end 36 | 37 | describe '#each' do 38 | it "should loop over each element passing element to block" do 39 | result = [] 40 | Element.find('.traversing-class').each do |e| 41 | result << e.id 42 | end 43 | 44 | result.should == ['foo', 'bar'] 45 | end 46 | 47 | it "should not call the block with an empty element set" do 48 | Element.find('.bad-each-class').each do 49 | raise "shouldn't get here" 50 | end 51 | end 52 | end 53 | 54 | describe '#find' do 55 | it "should match all elements within scope of receiver" do 56 | foo = Element.find('#traversing-spec') 57 | foo.find('.traversing-class').size.should == 2 58 | foo.find('.main-content-wrapper').size.should == 1 59 | end 60 | 61 | it "should return an empty collection if there are no matching elements" do 62 | bar = Element.find('#bar') 63 | bar.find('.some-non-existant-class').size.should == 0 64 | end 65 | end 66 | 67 | describe '#first' do 68 | it "should return the first element in the receiver" do 69 | Element.find('.traversing-class').first.id.should == 'foo' 70 | Element.find('#baz').first.id.should == 'baz' 71 | end 72 | 73 | it "should return nil when receiver has no elements" do 74 | Element.find('.some-random-class').first.should == nil 75 | end 76 | end 77 | end 78 | 79 | RSpec.describe "Element#next" do 80 | before do 81 | @div = Element.parse <<-HTML 82 |
    83 |
    84 |
    85 |
    86 | HTML 87 | 88 | @div.append_to_body 89 | end 90 | 91 | after do 92 | @div.remove 93 | end 94 | 95 | it "should return the next sibling" do 96 | Element.find('#foo').next.id.should == "bar" 97 | end 98 | 99 | it "should return an empty instance when no next element" do 100 | Element.find('#bar').next.size.should == 0 101 | end 102 | end 103 | 104 | RSpec.describe "Element#parent" do 105 | before do 106 | @div = Element.parse <<-HTML 107 |
    108 |
    109 |
    110 |
    111 |
    112 |
    113 | HTML 114 | 115 | @div.append_to_body 116 | end 117 | 118 | after do 119 | @div.remove 120 | end 121 | 122 | it "should return the parent of the element" do 123 | Element.find('#bar').parent.id.should == "foo" 124 | Element.find('#baz').parent.id.should == "bar" 125 | Element.find('#buz').parent.id.should == "bar" 126 | end 127 | end 128 | 129 | RSpec.describe "Element#succ" do 130 | before do 131 | @div = Element.parse <<-HTML 132 |
    133 |
    134 |
    135 |
    136 | HTML 137 | 138 | @div.append_to_body 139 | end 140 | 141 | after do 142 | @div.remove 143 | end 144 | 145 | it "should return the next sibling" do 146 | Element.find('#foo').succ.id.should == "bar" 147 | end 148 | 149 | it "should return an empty instance when no next element" do 150 | Element.find('#bar').succ.size.should == 0 151 | end 152 | end 153 | 154 | RSpec.describe "Element#siblings" do 155 | before do 156 | @div = Element.parse <<-HTML 157 |
    158 |
    159 |
    160 |
    161 |
    162 |
    163 |
    164 |
    165 |
    166 |
    167 | HTML 168 | 169 | @div.append_to_body 170 | end 171 | 172 | after do 173 | @div.remove 174 | end 175 | 176 | it "should return all siblings" do 177 | Element.find('#bar').siblings.size.should == 2 178 | Element.find('#bar').siblings.at(0).id.should == "foo" 179 | Element.find('#bar').siblings.at(1).id.should == "baz" 180 | end 181 | 182 | it "should return all siblings that match the selector" do 183 | Element.find('#bar').siblings('.special').size.should == 1 184 | Element.find('#bar').siblings('.special').at(0).id.should == "baz" 185 | end 186 | 187 | it "should return an empty instance when there are no siblings" do 188 | Element.find('#uno').siblings.size.should == 0 189 | end 190 | end 191 | -------------------------------------------------------------------------------- /spec-opal/element_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe Element do 4 | context 'events' do 5 | html <<-HTML 6 |
    7 |
    8 |
    9 |
    10 | HTML 11 | 12 | describe '#on' do 13 | it 'adds an event listener onto the elements' do 14 | count = 0 15 | foo = Element['#foo'] 16 | 17 | foo.on(:click) { count += 1 } 18 | count.should == 0 19 | foo.trigger(:click) 20 | count.should == 1 21 | foo.trigger(:click) 22 | count.should == 2 23 | foo.trigger(:mousedown) 24 | count.should == 2 25 | end 26 | 27 | it 'takes an optional second parameter to delegate events' do 28 | count = 0 29 | foo = Element['#foo'] 30 | bar = Element['#bar'] 31 | 32 | foo.on(:click, '#bar') { count += 1 } 33 | count.should == 0 34 | foo.trigger(:click) 35 | count.should == 0 36 | bar.trigger(:click) 37 | count.should == 1 38 | bar.trigger(:click) 39 | count.should == 2 40 | bar.trigger(:mousedown) 41 | count.should == 2 42 | end 43 | 44 | it 'can listen for non-browser events' do 45 | count = 0 46 | foo = Element['#foo'] 47 | 48 | foo.on('opal-is-mega-lolz') { count += 1 } 49 | count.should == 0 50 | foo.trigger('opal-is-mega-lolz') 51 | count.should == 1 52 | foo.trigger('opal-is-mega-lolz') 53 | count.should == 2 54 | end 55 | 56 | it 'returns the given handler' do 57 | handler = proc {} 58 | Element['#foo'].on(:click, &handler).should == handler 59 | end 60 | 61 | it 'has an Event instance passed to the handler' do 62 | foo = Element['#foo'] 63 | foo.on :click do |event| 64 | event.should be_kind_of(Event) 65 | end 66 | foo.trigger(:click) 67 | end 68 | 69 | it 'has an Event instance, plus any additional parameters passed to the handler' do 70 | foo = Element['#foo'] 71 | foo.on :bozo do |event, foo, bar, baz, buz| 72 | event.should be_kind_of(Event) 73 | foo.should == 'foo' 74 | bar.should == 'bar' 75 | baz.should == 'baz' 76 | buz.should == 'buz' 77 | end 78 | foo.trigger(:bozo, ['foo', 'bar', 'baz', 'buz']) 79 | end 80 | end 81 | 82 | describe '#off' do 83 | it 'removes event handlers that were added using #on' do 84 | count = 0 85 | foo = Element['#foo'] 86 | 87 | handler = foo.on(:click) { count += 1 } 88 | count.should == 0 89 | foo.trigger(:click) 90 | count.should == 1 91 | foo.off(:click, handler) 92 | count.should == 1 93 | foo.trigger(:click) 94 | count.should == 1 95 | end 96 | 97 | it 'removes event handlers added with a selector' do 98 | count = 0 99 | foo = Element['#foo'] 100 | bar = Element['#bar'] 101 | 102 | handler = foo.on(:click, '#bar') { count += 1 } 103 | count.should == 0 104 | bar.trigger(:click) 105 | count.should == 1 106 | foo.off(:click, '#bar', handler) 107 | count.should == 1 108 | bar.trigger(:click) 109 | count.should == 1 110 | end 111 | end 112 | end 113 | 114 | context 'selectors/parse' do 115 | html <<-HTML 116 |
    117 |
    118 |
    119 |
    120 |
    121 |
    122 | HTML 123 | 124 | describe ".[]" do 125 | it "should be able to find elements with given id" do 126 | Element['#foo'].class_name.should == "bar" 127 | Element['#foo'].size.should == 1 128 | end 129 | 130 | it "should be able to match any valid CSS selector" do 131 | Element['.woosh'].should be_kind_of(Element) 132 | Element['.woosh'].size.should == 2 133 | end 134 | 135 | it "should return an empty Elements instance when not matching any elements" do 136 | dom = Element['.some-non-existing-class'] 137 | 138 | dom.should be_kind_of(Element) 139 | dom.size.should == 0 140 | end 141 | 142 | it "should accept an HTML string and parse it into a Elements instance" do 143 | el = Element['
    '] 144 | 145 | el.should be_kind_of(Element) 146 | el.id.should == "foo-bar-baz" 147 | el.size.should == 1 148 | end 149 | end 150 | 151 | describe ".find" do 152 | it "should find all elements matching CSS selector" do 153 | foo = Element.find '.find-foo' 154 | foo.should be_kind_of(Element) 155 | foo.length.should == 2 156 | 157 | bar = Element.find '.find-bar' 158 | bar.should be_kind_of(Element) 159 | bar.length.should == 1 160 | end 161 | 162 | it "should return an empty Element instance with length 0 when no matching" do 163 | baz = Element.find '.find-baz' 164 | baz.should be_kind_of(Element) 165 | baz.length.should == 0 166 | end 167 | end 168 | 169 | describe ".not" do 170 | it "should subtract from a set of elements" do 171 | divs = Element['#foo, .woosh'] 172 | divs.should be_kind_of(Element) 173 | divs.size.should == 3 174 | 175 | subtracted = divs.not('#foo') 176 | subtracted.should be_kind_of(Element) 177 | subtracted.length.should == 2 178 | end 179 | end 180 | 181 | describe '.id' do 182 | it "should return a new instance with the element with given id" do 183 | Element.id('foo').should be_kind_of(Element) 184 | Element.id('foo').id.should == 'foo' 185 | end 186 | 187 | it "should return nil if no element could be found" do 188 | Element.id('bad-element-id').should be_nil 189 | end 190 | end 191 | 192 | describe '.parse' do 193 | it "should return a new instance with parsed element as single element" do 194 | foo = Element.parse '
    ' 195 | foo.id.should == 'foo' 196 | foo.class_name.should == 'bar' 197 | end 198 | end 199 | 200 | end 201 | 202 | describe "#data" do 203 | html <<-HTML 204 |
    205 |
    206 | HTML 207 | 208 | it "sets a data attribute" do 209 | foo = Element.id('data-foo') 210 | foo.data 'bar', 'baz' 211 | expect(foo.data('bar')).to eq('baz') 212 | end 213 | 214 | it "can retrieve a data attribute" do 215 | expect(Element.id('data-ford').data('authur')).to eq('dent') 216 | end 217 | 218 | it "can retrieve all data attributes" do 219 | expect(Element.id('data-ford').data).to eq( 220 | 'authur' => 'dent', 'baz' => 'bar' 221 | ) 222 | end 223 | 224 | it "returns nil for an undefined data attribute" do 225 | expect(Element.id('data-ford').data('not-here')).to be_nil 226 | end 227 | end 228 | 229 | describe "#html" do 230 | html <<-HTML 231 |
    bar
    232 | HTML 233 | 234 | it "retrieves the inner html content for the element" do 235 | expect(Element.id('foo').html).to include('bar') 236 | end 237 | 238 | it "can be used to set inner html of element by passing string" do 239 | foo = Element.id 'foo' 240 | foo.html "different content" 241 | 242 | expect(foo.html).to_not include('bar') 243 | expect(foo.html).to include('different content') 244 | end 245 | end 246 | 247 | describe '#prop' do 248 | it 'converts nil to null' do 249 | checkbox = Element.new(:input).attr(:type, :checkbox) 250 | 251 | checkbox.prop(:checked, nil) 252 | expect(checkbox.prop(:checked)).to be false 253 | end 254 | end 255 | 256 | describe '#==' do 257 | it 'uses .is()' do 258 | expect(Element['body']).to eq(Element['body']) 259 | end 260 | end 261 | end 262 | -------------------------------------------------------------------------------- /spec-opal/event_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe Event do 4 | html <<-HTML 5 |
    6 |
    7 |
    8 |
    9 |
    10 |
    11 | HTML 12 | 13 | it '#current_target returns the current element in the bubbling' do 14 | foo = Element['#foo'] 15 | bar = Element['#bar'] 16 | result = [] 17 | 18 | foo.on(:click) { |e| result << e.current_target.id } 19 | bar.on(:click) { |e| result << e.current_target.id } 20 | 21 | foo.trigger(:click) 22 | result.should == ['foo'] 23 | result = [] 24 | 25 | bar.trigger(:click) 26 | result.should == ['bar', 'foo'] 27 | end 28 | 29 | it '#type returns the type of event' do 30 | type = nil 31 | foo = Element['#foo'] 32 | 33 | foo.on(:click) { |e| type = e.type } 34 | foo.on(:mousedown) { |e| type = e.type } 35 | foo.on(:opal_random) { |e| type = e.type } 36 | 37 | foo.trigger(:click) 38 | type.should == :click 39 | 40 | foo.trigger(:mousedown) 41 | type.should == :mousedown 42 | 43 | foo.trigger(:opal_random) 44 | type.should == :opal_random 45 | end 46 | 47 | it '#target returns a JQuery wrapper around the element that triggered the event' do 48 | foo = Element['#foo'] 49 | bar = Element['#bar'] 50 | target = nil 51 | 52 | foo.on(:click) { |e| target = e.target.id } 53 | 54 | foo.trigger(:click) 55 | target.should == 'foo' 56 | 57 | bar.trigger(:click) 58 | target.should == 'bar' 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /spec-opal/fixtures/simple.txt: -------------------------------------------------------------------------------- 1 | hey -------------------------------------------------------------------------------- /spec-opal/fixtures/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Adam", 3 | "age": 26 4 | } -------------------------------------------------------------------------------- /spec-opal/http_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe HTTP do 4 | let(:good_url) { '/spec/fixtures/simple.txt' } 5 | let(:json_url) { '/spec/fixtures/user.json' } 6 | let(:bad_url) { '/spec/fixtures/does_not_exist.txt' } 7 | 8 | describe ".setup" do 9 | it 'presents the $.ajaxSetup() object as a Hash' do 10 | expect(HTTP.setup).to be_a Hash 11 | end 12 | end 13 | 14 | describe ".get" do 15 | describe "with a block" do 16 | it "returns the http object instance" do 17 | expect(HTTP.get(good_url) {}).to be_a HTTP 18 | end 19 | 20 | async "block gets called on success" do 21 | HTTP.get(good_url) do |response| 22 | async { expect(response).to be_ok } 23 | end 24 | end 25 | 26 | async "block gets called on failure" do 27 | HTTP.get(bad_url) do |response| 28 | async { expect(response).to_not be_ok } 29 | end 30 | end 31 | end 32 | 33 | describe "without a block" do 34 | it "returns a promise" do 35 | expect(HTTP.get(good_url)).to be_a Promise 36 | end 37 | 38 | async "returns a promise which accepts a then-block for successful response" do 39 | HTTP.get(good_url).then do |response| 40 | async { expect(response).to be_ok } 41 | end 42 | end 43 | 44 | async "returns a promise which accepts a fail-block for failing response" do 45 | HTTP.get(bad_url).fail do |response| 46 | async { expect(response).to_not be_ok } 47 | end 48 | end 49 | end 50 | end 51 | 52 | describe '#body' do 53 | async 'returns the response body as a string' do 54 | HTTP.get(good_url) do |response| 55 | async { expect(response.body).to eq('hey') } 56 | end 57 | end 58 | end 59 | 60 | describe '#json' do 61 | async 'returns the json converted into native ruby objects' do 62 | HTTP.get(json_url) do |response| 63 | async { expect(response.json).to eq({ 'name' => 'Adam', 'age' => 26 }) } 64 | end 65 | end 66 | end 67 | 68 | describe '#ok?' do 69 | async 'returns true when the request was a sucess' do 70 | HTTP.get(good_url) do |response| 71 | async { expect(response).to be_ok } 72 | end 73 | end 74 | 75 | async 'returns false when the request failed' do 76 | HTTP.get(bad_url) do |response| 77 | async { expect(response).to_not be_ok } 78 | end 79 | end 80 | end 81 | 82 | describe '#get_header' do 83 | async 'returns the header value' do 84 | HTTP.get(good_url) do |response| 85 | async { expect(response.get_header 'Content-Type').to eq 'text/plain' } 86 | end 87 | end 88 | 89 | async 'returns nil' do 90 | HTTP.get(good_url) do |response| 91 | async { expect(response.get_header 'Does-Not-Exist').to be nil } 92 | end 93 | end 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /spec-opal/jquery/index.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%= javascript_include_tag 'jquery/jquery-1.8.3' %> 8 | <%= javascript_include_tag @server.main %> 9 | 10 | 11 | -------------------------------------------------------------------------------- /spec-opal/jquery/index3.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%= javascript_include_tag 'jquery/jquery-3.0.0' %> 8 | <%= javascript_include_tag @server.main %> 9 | 10 | 11 | -------------------------------------------------------------------------------- /spec-opal/kernel_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe 'Kernel#alert' do 4 | it 'returns nil' do 5 | begin 6 | original_alert = `window.alert` 7 | message = nil 8 | `window.alert = function(string) { message = string; }` 9 | Kernel.alert('a message').should be_nil 10 | expect(message).to eq('a message') 11 | ensure 12 | `window.alert = #{original_alert}` 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec-opal/local_storage_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'opal/jquery/local_storage' 3 | 4 | RSpec.describe LocalStorage do 5 | before { subject.clear } 6 | 7 | it "returns nil for undefined values" do 8 | expect(subject['foo']).to be_nil 9 | end 10 | 11 | it "should be able to create items" do 12 | subject['foo'] = 'Ford Prefect' 13 | expect(subject['foo']).to eq('Ford Prefect') 14 | end 15 | 16 | it "should be able to delete items" do 17 | subject['name'] = 'Arthur' 18 | subject.delete 'name' 19 | 20 | expect(subject['name']).to be_nil 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec-opal/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'jquery/jquery-1.8.3' 2 | require 'opal-rspec' 3 | require 'opal/jquery' 4 | require 'opal/jquery/rspec' 5 | 6 | module JQueryTestHelpers 7 | def find(selector) 8 | Element.find selector 9 | end 10 | end 11 | 12 | module SkipAsync 13 | def async(*args, &block) 14 | xit(*args, &block) 15 | end 16 | end 17 | 18 | RSpec.configure do |config| 19 | config.include JQueryTestHelpers 20 | config.extend SkipAsync 21 | config.formatter = :doc 22 | config.color = true 23 | end 24 | -------------------------------------------------------------------------------- /spec-opal/zepto/index.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%= javascript_include_tag 'zepto/zepto' %> 8 | <%= javascript_include_tag @server.main %> 9 | 10 | 11 | -------------------------------------------------------------------------------- /spec-opal/zepto/zepto.js: -------------------------------------------------------------------------------- 1 | /* Zepto v1.1.6 - zepto event ajax form ie - zeptojs.com/license */ 2 | var Zepto=function(){function L(t){return null==t?String(t):j[S.call(t)]||"object"}function Z(t){return"function"==L(t)}function _(t){return null!=t&&t==t.window}function $(t){return null!=t&&t.nodeType==t.DOCUMENT_NODE}function D(t){return"object"==L(t)}function M(t){return D(t)&&!_(t)&&Object.getPrototypeOf(t)==Object.prototype}function R(t){return"number"==typeof t.length}function k(t){return s.call(t,function(t){return null!=t})}function z(t){return t.length>0?n.fn.concat.apply([],t):t}function F(t){return t.replace(/::/g,"/").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/_/g,"-").toLowerCase()}function q(t){return t in f?f[t]:f[t]=new RegExp("(^|\\s)"+t+"(\\s|$)")}function H(t,e){return"number"!=typeof e||c[F(t)]?e:e+"px"}function I(t){var e,n;return u[t]||(e=a.createElement(t),a.body.appendChild(e),n=getComputedStyle(e,"").getPropertyValue("display"),e.parentNode.removeChild(e),"none"==n&&(n="block"),u[t]=n),u[t]}function V(t){return"children"in t?o.call(t.children):n.map(t.childNodes,function(t){return 1==t.nodeType?t:void 0})}function B(n,i,r){for(e in i)r&&(M(i[e])||A(i[e]))?(M(i[e])&&!M(n[e])&&(n[e]={}),A(i[e])&&!A(n[e])&&(n[e]=[]),B(n[e],i[e],r)):i[e]!==t&&(n[e]=i[e])}function U(t,e){return null==e?n(t):n(t).filter(e)}function J(t,e,n,i){return Z(e)?e.call(t,n,i):e}function X(t,e,n){null==n?t.removeAttribute(e):t.setAttribute(e,n)}function W(e,n){var i=e.className||"",r=i&&i.baseVal!==t;return n===t?r?i.baseVal:i:void(r?i.baseVal=n:e.className=n)}function Y(t){try{return t?"true"==t||("false"==t?!1:"null"==t?null:+t+""==t?+t:/^[\[\{]/.test(t)?n.parseJSON(t):t):t}catch(e){return t}}function G(t,e){e(t);for(var n=0,i=t.childNodes.length;i>n;n++)G(t.childNodes[n],e)}var t,e,n,i,C,N,r=[],o=r.slice,s=r.filter,a=window.document,u={},f={},c={"column-count":1,columns:1,"font-weight":1,"line-height":1,opacity:1,"z-index":1,zoom:1},l=/^\s*<(\w+|!)[^>]*>/,h=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,p=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,d=/^(?:body|html)$/i,m=/([A-Z])/g,g=["val","css","html","text","data","width","height","offset"],v=["after","prepend","before","append"],y=a.createElement("table"),x=a.createElement("tr"),b={tr:a.createElement("tbody"),tbody:y,thead:y,tfoot:y,td:x,th:x,"*":a.createElement("div")},w=/complete|loaded|interactive/,E=/^[\w-]*$/,j={},S=j.toString,T={},O=a.createElement("div"),P={tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},A=Array.isArray||function(t){return t instanceof Array};return T.matches=function(t,e){if(!e||!t||1!==t.nodeType)return!1;var n=t.webkitMatchesSelector||t.mozMatchesSelector||t.oMatchesSelector||t.matchesSelector;if(n)return n.call(t,e);var i,r=t.parentNode,o=!r;return o&&(r=O).appendChild(t),i=~T.qsa(r,e).indexOf(t),o&&O.removeChild(t),i},C=function(t){return t.replace(/-+(.)?/g,function(t,e){return e?e.toUpperCase():""})},N=function(t){return s.call(t,function(e,n){return t.indexOf(e)==n})},T.fragment=function(e,i,r){var s,u,f;return h.test(e)&&(s=n(a.createElement(RegExp.$1))),s||(e.replace&&(e=e.replace(p,"<$1>")),i===t&&(i=l.test(e)&&RegExp.$1),i in b||(i="*"),f=b[i],f.innerHTML=""+e,s=n.each(o.call(f.childNodes),function(){f.removeChild(this)})),M(r)&&(u=n(s),n.each(r,function(t,e){g.indexOf(t)>-1?u[t](e):u.attr(t,e)})),s},T.Z=function(t,e){return t=t||[],t.__proto__=n.fn,t.selector=e||"",t},T.isZ=function(t){return t instanceof T.Z},T.init=function(e,i){var r;if(!e)return T.Z();if("string"==typeof e)if(e=e.trim(),"<"==e[0]&&l.test(e))r=T.fragment(e,RegExp.$1,i),e=null;else{if(i!==t)return n(i).find(e);r=T.qsa(a,e)}else{if(Z(e))return n(a).ready(e);if(T.isZ(e))return e;if(A(e))r=k(e);else if(D(e))r=[e],e=null;else if(l.test(e))r=T.fragment(e.trim(),RegExp.$1,i),e=null;else{if(i!==t)return n(i).find(e);r=T.qsa(a,e)}}return T.Z(r,e)},n=function(t,e){return T.init(t,e)},n.extend=function(t){var e,n=o.call(arguments,1);return"boolean"==typeof t&&(e=t,t=n.shift()),n.forEach(function(n){B(t,n,e)}),t},T.qsa=function(t,e){var n,i="#"==e[0],r=!i&&"."==e[0],s=i||r?e.slice(1):e,a=E.test(s);return $(t)&&a&&i?(n=t.getElementById(s))?[n]:[]:1!==t.nodeType&&9!==t.nodeType?[]:o.call(a&&!i?r?t.getElementsByClassName(s):t.getElementsByTagName(e):t.querySelectorAll(e))},n.contains=a.documentElement.contains?function(t,e){return t!==e&&t.contains(e)}:function(t,e){for(;e&&(e=e.parentNode);)if(e===t)return!0;return!1},n.type=L,n.isFunction=Z,n.isWindow=_,n.isArray=A,n.isPlainObject=M,n.isEmptyObject=function(t){var e;for(e in t)return!1;return!0},n.inArray=function(t,e,n){return r.indexOf.call(e,t,n)},n.camelCase=C,n.trim=function(t){return null==t?"":String.prototype.trim.call(t)},n.uuid=0,n.support={},n.expr={},n.map=function(t,e){var n,r,o,i=[];if(R(t))for(r=0;r=0?e:e+this.length]},toArray:function(){return this.get()},size:function(){return this.length},remove:function(){return this.each(function(){null!=this.parentNode&&this.parentNode.removeChild(this)})},each:function(t){return r.every.call(this,function(e,n){return t.call(e,n,e)!==!1}),this},filter:function(t){return Z(t)?this.not(this.not(t)):n(s.call(this,function(e){return T.matches(e,t)}))},add:function(t,e){return n(N(this.concat(n(t,e))))},is:function(t){return this.length>0&&T.matches(this[0],t)},not:function(e){var i=[];if(Z(e)&&e.call!==t)this.each(function(t){e.call(this,t)||i.push(this)});else{var r="string"==typeof e?this.filter(e):R(e)&&Z(e.item)?o.call(e):n(e);this.forEach(function(t){r.indexOf(t)<0&&i.push(t)})}return n(i)},has:function(t){return this.filter(function(){return D(t)?n.contains(this,t):n(this).find(t).size()})},eq:function(t){return-1===t?this.slice(t):this.slice(t,+t+1)},first:function(){var t=this[0];return t&&!D(t)?t:n(t)},last:function(){var t=this[this.length-1];return t&&!D(t)?t:n(t)},find:function(t){var e,i=this;return e=t?"object"==typeof t?n(t).filter(function(){var t=this;return r.some.call(i,function(e){return n.contains(e,t)})}):1==this.length?n(T.qsa(this[0],t)):this.map(function(){return T.qsa(this,t)}):n()},closest:function(t,e){var i=this[0],r=!1;for("object"==typeof t&&(r=n(t));i&&!(r?r.indexOf(i)>=0:T.matches(i,t));)i=i!==e&&!$(i)&&i.parentNode;return n(i)},parents:function(t){for(var e=[],i=this;i.length>0;)i=n.map(i,function(t){return(t=t.parentNode)&&!$(t)&&e.indexOf(t)<0?(e.push(t),t):void 0});return U(e,t)},parent:function(t){return U(N(this.pluck("parentNode")),t)},children:function(t){return U(this.map(function(){return V(this)}),t)},contents:function(){return this.map(function(){return o.call(this.childNodes)})},siblings:function(t){return U(this.map(function(t,e){return s.call(V(e.parentNode),function(t){return t!==e})}),t)},empty:function(){return this.each(function(){this.innerHTML=""})},pluck:function(t){return n.map(this,function(e){return e[t]})},show:function(){return this.each(function(){"none"==this.style.display&&(this.style.display=""),"none"==getComputedStyle(this,"").getPropertyValue("display")&&(this.style.display=I(this.nodeName))})},replaceWith:function(t){return this.before(t).remove()},wrap:function(t){var e=Z(t);if(this[0]&&!e)var i=n(t).get(0),r=i.parentNode||this.length>1;return this.each(function(o){n(this).wrapAll(e?t.call(this,o):r?i.cloneNode(!0):i)})},wrapAll:function(t){if(this[0]){n(this[0]).before(t=n(t));for(var e;(e=t.children()).length;)t=e.first();n(t).append(this)}return this},wrapInner:function(t){var e=Z(t);return this.each(function(i){var r=n(this),o=r.contents(),s=e?t.call(this,i):t;o.length?o.wrapAll(s):r.append(s)})},unwrap:function(){return this.parent().each(function(){n(this).replaceWith(n(this).children())}),this},clone:function(){return this.map(function(){return this.cloneNode(!0)})},hide:function(){return this.css("display","none")},toggle:function(e){return this.each(function(){var i=n(this);(e===t?"none"==i.css("display"):e)?i.show():i.hide()})},prev:function(t){return n(this.pluck("previousElementSibling")).filter(t||"*")},next:function(t){return n(this.pluck("nextElementSibling")).filter(t||"*")},html:function(t){return 0 in arguments?this.each(function(e){var i=this.innerHTML;n(this).empty().append(J(this,t,e,i))}):0 in this?this[0].innerHTML:null},text:function(t){return 0 in arguments?this.each(function(e){var n=J(this,t,e,this.textContent);this.textContent=null==n?"":""+n}):0 in this?this[0].textContent:null},attr:function(n,i){var r;return"string"!=typeof n||1 in arguments?this.each(function(t){if(1===this.nodeType)if(D(n))for(e in n)X(this,e,n[e]);else X(this,n,J(this,i,t,this.getAttribute(n)))}):this.length&&1===this[0].nodeType?!(r=this[0].getAttribute(n))&&n in this[0]?this[0][n]:r:t},removeAttr:function(t){return this.each(function(){1===this.nodeType&&t.split(" ").forEach(function(t){X(this,t)},this)})},prop:function(t,e){return t=P[t]||t,1 in arguments?this.each(function(n){this[t]=J(this,e,n,this[t])}):this[0]&&this[0][t]},data:function(e,n){var i="data-"+e.replace(m,"-$1").toLowerCase(),r=1 in arguments?this.attr(i,n):this.attr(i);return null!==r?Y(r):t},val:function(t){return 0 in arguments?this.each(function(e){this.value=J(this,t,e,this.value)}):this[0]&&(this[0].multiple?n(this[0]).find("option").filter(function(){return this.selected}).pluck("value"):this[0].value)},offset:function(t){if(t)return this.each(function(e){var i=n(this),r=J(this,t,e,i.offset()),o=i.offsetParent().offset(),s={top:r.top-o.top,left:r.left-o.left};"static"==i.css("position")&&(s.position="relative"),i.css(s)});if(!this.length)return null;var e=this[0].getBoundingClientRect();return{left:e.left+window.pageXOffset,top:e.top+window.pageYOffset,width:Math.round(e.width),height:Math.round(e.height)}},css:function(t,i){if(arguments.length<2){var r,o=this[0];if(!o)return;if(r=getComputedStyle(o,""),"string"==typeof t)return o.style[C(t)]||r.getPropertyValue(t);if(A(t)){var s={};return n.each(t,function(t,e){s[e]=o.style[C(e)]||r.getPropertyValue(e)}),s}}var a="";if("string"==L(t))i||0===i?a=F(t)+":"+H(t,i):this.each(function(){this.style.removeProperty(F(t))});else for(e in t)t[e]||0===t[e]?a+=F(e)+":"+H(e,t[e])+";":this.each(function(){this.style.removeProperty(F(e))});return this.each(function(){this.style.cssText+=";"+a})},index:function(t){return t?this.indexOf(n(t)[0]):this.parent().children().indexOf(this[0])},hasClass:function(t){return t?r.some.call(this,function(t){return this.test(W(t))},q(t)):!1},addClass:function(t){return t?this.each(function(e){if("className"in this){i=[];var r=W(this),o=J(this,t,e,r);o.split(/\s+/g).forEach(function(t){n(this).hasClass(t)||i.push(t)},this),i.length&&W(this,r+(r?" ":"")+i.join(" "))}}):this},removeClass:function(e){return this.each(function(n){if("className"in this){if(e===t)return W(this,"");i=W(this),J(this,e,n,i).split(/\s+/g).forEach(function(t){i=i.replace(q(t)," ")}),W(this,i.trim())}})},toggleClass:function(e,i){return e?this.each(function(r){var o=n(this),s=J(this,e,r,W(this));s.split(/\s+/g).forEach(function(e){(i===t?!o.hasClass(e):i)?o.addClass(e):o.removeClass(e)})}):this},scrollTop:function(e){if(this.length){var n="scrollTop"in this[0];return e===t?n?this[0].scrollTop:this[0].pageYOffset:this.each(n?function(){this.scrollTop=e}:function(){this.scrollTo(this.scrollX,e)})}},scrollLeft:function(e){if(this.length){var n="scrollLeft"in this[0];return e===t?n?this[0].scrollLeft:this[0].pageXOffset:this.each(n?function(){this.scrollLeft=e}:function(){this.scrollTo(e,this.scrollY)})}},position:function(){if(this.length){var t=this[0],e=this.offsetParent(),i=this.offset(),r=d.test(e[0].nodeName)?{top:0,left:0}:e.offset();return i.top-=parseFloat(n(t).css("margin-top"))||0,i.left-=parseFloat(n(t).css("margin-left"))||0,r.top+=parseFloat(n(e[0]).css("border-top-width"))||0,r.left+=parseFloat(n(e[0]).css("border-left-width"))||0,{top:i.top-r.top,left:i.left-r.left}}},offsetParent:function(){return this.map(function(){for(var t=this.offsetParent||a.body;t&&!d.test(t.nodeName)&&"static"==n(t).css("position");)t=t.offsetParent;return t})}},n.fn.detach=n.fn.remove,["width","height"].forEach(function(e){var i=e.replace(/./,function(t){return t[0].toUpperCase()});n.fn[e]=function(r){var o,s=this[0];return r===t?_(s)?s["inner"+i]:$(s)?s.documentElement["scroll"+i]:(o=this.offset())&&o[e]:this.each(function(t){s=n(this),s.css(e,J(this,r,t,s[e]()))})}}),v.forEach(function(t,e){var i=e%2;n.fn[t]=function(){var t,o,r=n.map(arguments,function(e){return t=L(e),"object"==t||"array"==t||null==e?e:T.fragment(e)}),s=this.length>1;return r.length<1?this:this.each(function(t,u){o=i?u:u.parentNode,u=0==e?u.nextSibling:1==e?u.firstChild:2==e?u:null;var f=n.contains(a.documentElement,o);r.forEach(function(t){if(s)t=t.cloneNode(!0);else if(!o)return n(t).remove();o.insertBefore(t,u),f&&G(t,function(t){null==t.nodeName||"SCRIPT"!==t.nodeName.toUpperCase()||t.type&&"text/javascript"!==t.type||t.src||window.eval.call(window,t.innerHTML)})})})},n.fn[i?t+"To":"insert"+(e?"Before":"After")]=function(e){return n(e)[t](this),this}}),T.Z.prototype=n.fn,T.uniq=N,T.deserializeValue=Y,n.zepto=T,n}();window.Zepto=Zepto,void 0===window.$&&(window.$=Zepto),function(t){function l(t){return t._zid||(t._zid=e++)}function h(t,e,n,i){if(e=p(e),e.ns)var r=d(e.ns);return(s[l(t)]||[]).filter(function(t){return!(!t||e.e&&t.e!=e.e||e.ns&&!r.test(t.ns)||n&&l(t.fn)!==l(n)||i&&t.sel!=i)})}function p(t){var e=(""+t).split(".");return{e:e[0],ns:e.slice(1).sort().join(" ")}}function d(t){return new RegExp("(?:^| )"+t.replace(" "," .* ?")+"(?: |$)")}function m(t,e){return t.del&&!u&&t.e in f||!!e}function g(t){return c[t]||u&&f[t]||t}function v(e,i,r,o,a,u,f){var h=l(e),d=s[h]||(s[h]=[]);i.split(/\s/).forEach(function(i){if("ready"==i)return t(document).ready(r);var s=p(i);s.fn=r,s.sel=a,s.e in c&&(r=function(e){var n=e.relatedTarget;return!n||n!==this&&!t.contains(this,n)?s.fn.apply(this,arguments):void 0}),s.del=u;var l=u||r;s.proxy=function(t){if(t=j(t),!t.isImmediatePropagationStopped()){t.data=o;var i=l.apply(e,t._args==n?[t]:[t].concat(t._args));return i===!1&&(t.preventDefault(),t.stopPropagation()),i}},s.i=d.length,d.push(s),"addEventListener"in e&&e.addEventListener(g(s.e),s.proxy,m(s,f))})}function y(t,e,n,i,r){var o=l(t);(e||"").split(/\s/).forEach(function(e){h(t,e,n,i).forEach(function(e){delete s[o][e.i],"removeEventListener"in t&&t.removeEventListener(g(e.e),e.proxy,m(e,r))})})}function j(e,i){return(i||!e.isDefaultPrevented)&&(i||(i=e),t.each(E,function(t,n){var r=i[t];e[t]=function(){return this[n]=x,r&&r.apply(i,arguments)},e[n]=b}),(i.defaultPrevented!==n?i.defaultPrevented:"returnValue"in i?i.returnValue===!1:i.getPreventDefault&&i.getPreventDefault())&&(e.isDefaultPrevented=x)),e}function S(t){var e,i={originalEvent:t};for(e in t)w.test(e)||t[e]===n||(i[e]=t[e]);return j(i,t)}var n,e=1,i=Array.prototype.slice,r=t.isFunction,o=function(t){return"string"==typeof t},s={},a={},u="onfocusin"in window,f={focus:"focusin",blur:"focusout"},c={mouseenter:"mouseover",mouseleave:"mouseout"};a.click=a.mousedown=a.mouseup=a.mousemove="MouseEvents",t.event={add:v,remove:y},t.proxy=function(e,n){var s=2 in arguments&&i.call(arguments,2);if(r(e)){var a=function(){return e.apply(n,s?s.concat(i.call(arguments)):arguments)};return a._zid=l(e),a}if(o(n))return s?(s.unshift(e[n],e),t.proxy.apply(null,s)):t.proxy(e[n],e);throw new TypeError("expected function")},t.fn.bind=function(t,e,n){return this.on(t,e,n)},t.fn.unbind=function(t,e){return this.off(t,e)},t.fn.one=function(t,e,n,i){return this.on(t,e,n,i,1)};var x=function(){return!0},b=function(){return!1},w=/^([A-Z]|returnValue$|layer[XY]$)/,E={preventDefault:"isDefaultPrevented",stopImmediatePropagation:"isImmediatePropagationStopped",stopPropagation:"isPropagationStopped"};t.fn.delegate=function(t,e,n){return this.on(e,t,n)},t.fn.undelegate=function(t,e,n){return this.off(e,t,n)},t.fn.live=function(e,n){return t(document.body).delegate(this.selector,e,n),this},t.fn.die=function(e,n){return t(document.body).undelegate(this.selector,e,n),this},t.fn.on=function(e,s,a,u,f){var c,l,h=this;return e&&!o(e)?(t.each(e,function(t,e){h.on(t,s,a,e,f)}),h):(o(s)||r(u)||u===!1||(u=a,a=s,s=n),(r(a)||a===!1)&&(u=a,a=n),u===!1&&(u=b),h.each(function(n,r){f&&(c=function(t){return y(r,t.type,u),u.apply(this,arguments)}),s&&(l=function(e){var n,o=t(e.target).closest(s,r).get(0);return o&&o!==r?(n=t.extend(S(e),{currentTarget:o,liveFired:r}),(c||u).apply(o,[n].concat(i.call(arguments,1)))):void 0}),v(r,e,u,a,s,l||c)}))},t.fn.off=function(e,i,s){var a=this;return e&&!o(e)?(t.each(e,function(t,e){a.off(t,i,e)}),a):(o(i)||r(s)||s===!1||(s=i,i=n),s===!1&&(s=b),a.each(function(){y(this,e,s,i)}))},t.fn.trigger=function(e,n){return e=o(e)||t.isPlainObject(e)?t.Event(e):j(e),e._args=n,this.each(function(){e.type in f&&"function"==typeof this[e.type]?this[e.type]():"dispatchEvent"in this?this.dispatchEvent(e):t(this).triggerHandler(e,n)})},t.fn.triggerHandler=function(e,n){var i,r;return this.each(function(s,a){i=S(o(e)?t.Event(e):e),i._args=n,i.target=a,t.each(h(a,e.type||e),function(t,e){return r=e.proxy(i),i.isImmediatePropagationStopped()?!1:void 0})}),r},"focusin focusout focus blur load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select keydown keypress keyup error".split(" ").forEach(function(e){t.fn[e]=function(t){return 0 in arguments?this.bind(e,t):this.trigger(e)}}),t.Event=function(t,e){o(t)||(e=t,t=e.type);var n=document.createEvent(a[t]||"Events"),i=!0;if(e)for(var r in e)"bubbles"==r?i=!!e[r]:n[r]=e[r];return n.initEvent(t,i,!0),j(n)}}(Zepto),function(t){function h(e,n,i){var r=t.Event(n);return t(e).trigger(r,i),!r.isDefaultPrevented()}function p(t,e,i,r){return t.global?h(e||n,i,r):void 0}function d(e){e.global&&0===t.active++&&p(e,null,"ajaxStart")}function m(e){e.global&&!--t.active&&p(e,null,"ajaxStop")}function g(t,e){var n=e.context;return e.beforeSend.call(n,t,e)===!1||p(e,n,"ajaxBeforeSend",[t,e])===!1?!1:void p(e,n,"ajaxSend",[t,e])}function v(t,e,n,i){var r=n.context,o="success";n.success.call(r,t,o,e),i&&i.resolveWith(r,[t,o,e]),p(n,r,"ajaxSuccess",[e,n,t]),x(o,e,n)}function y(t,e,n,i,r){var o=i.context;i.error.call(o,n,e,t),r&&r.rejectWith(o,[n,e,t]),p(i,o,"ajaxError",[n,i,t||e]),x(e,n,i)}function x(t,e,n){var i=n.context;n.complete.call(i,e,t),p(n,i,"ajaxComplete",[e,n]),m(n)}function b(){}function w(t){return t&&(t=t.split(";",2)[0]),t&&(t==f?"html":t==u?"json":s.test(t)?"script":a.test(t)&&"xml")||"text"}function E(t,e){return""==e?t:(t+"&"+e).replace(/[&?]{1,2}/,"?")}function j(e){e.processData&&e.data&&"string"!=t.type(e.data)&&(e.data=t.param(e.data,e.traditional)),!e.data||e.type&&"GET"!=e.type.toUpperCase()||(e.url=E(e.url,e.data),e.data=void 0)}function S(e,n,i,r){return t.isFunction(n)&&(r=i,i=n,n=void 0),t.isFunction(i)||(r=i,i=void 0),{url:e,data:n,success:i,dataType:r}}function C(e,n,i,r){var o,s=t.isArray(n),a=t.isPlainObject(n);t.each(n,function(n,u){o=t.type(u),r&&(n=i?r:r+"["+(a||"object"==o||"array"==o?n:"")+"]"),!r&&s?e.add(u.name,u.value):"array"==o||!i&&"object"==o?C(e,u,i,n):e.add(n,u)})}var i,r,e=0,n=window.document,o=/)<[^<]*)*<\/script>/gi,s=/^(?:text|application)\/javascript/i,a=/^(?:text|application)\/xml/i,u="application/json",f="text/html",c=/^\s*$/,l=n.createElement("a");l.href=window.location.href,t.active=0,t.ajaxJSONP=function(i,r){if(!("type"in i))return t.ajax(i);var f,h,o=i.jsonpCallback,s=(t.isFunction(o)?o():o)||"jsonp"+ ++e,a=n.createElement("script"),u=window[s],c=function(e){t(a).triggerHandler("error",e||"abort")},l={abort:c};return r&&r.promise(l),t(a).on("load error",function(e,n){clearTimeout(h),t(a).off().remove(),"error"!=e.type&&f?v(f[0],l,i,r):y(null,n||"error",l,i,r),window[s]=u,f&&t.isFunction(u)&&u(f[0]),u=f=void 0}),g(l,i)===!1?(c("abort"),l):(window[s]=function(){f=arguments},a.src=i.url.replace(/\?(.+)=\?/,"?$1="+s),n.head.appendChild(a),i.timeout>0&&(h=setTimeout(function(){c("timeout")},i.timeout)),l)},t.ajaxSettings={type:"GET",beforeSend:b,success:b,error:b,complete:b,context:null,global:!0,xhr:function(){return new window.XMLHttpRequest},accepts:{script:"text/javascript, application/javascript, application/x-javascript",json:u,xml:"application/xml, text/xml",html:f,text:"text/plain"},crossDomain:!1,timeout:0,processData:!0,cache:!0},t.ajax=function(e){var a,o=t.extend({},e||{}),s=t.Deferred&&t.Deferred();for(i in t.ajaxSettings)void 0===o[i]&&(o[i]=t.ajaxSettings[i]);d(o),o.crossDomain||(a=n.createElement("a"),a.href=o.url,a.href=a.href,o.crossDomain=l.protocol+"//"+l.host!=a.protocol+"//"+a.host),o.url||(o.url=window.location.toString()),j(o);var u=o.dataType,f=/\?.+=\?/.test(o.url);if(f&&(u="jsonp"),o.cache!==!1&&(e&&e.cache===!0||"script"!=u&&"jsonp"!=u)||(o.url=E(o.url,"_="+Date.now())),"jsonp"==u)return f||(o.url=E(o.url,o.jsonp?o.jsonp+"=?":o.jsonp===!1?"":"callback=?")),t.ajaxJSONP(o,s);var C,h=o.accepts[u],p={},m=function(t,e){p[t.toLowerCase()]=[t,e]},x=/^([\w-]+:)\/\//.test(o.url)?RegExp.$1:window.location.protocol,S=o.xhr(),T=S.setRequestHeader;if(s&&s.promise(S),o.crossDomain||m("X-Requested-With","XMLHttpRequest"),m("Accept",h||"*/*"),(h=o.mimeType||h)&&(h.indexOf(",")>-1&&(h=h.split(",",2)[0]),S.overrideMimeType&&S.overrideMimeType(h)),(o.contentType||o.contentType!==!1&&o.data&&"GET"!=o.type.toUpperCase())&&m("Content-Type",o.contentType||"application/x-www-form-urlencoded"),o.headers)for(r in o.headers)m(r,o.headers[r]);if(S.setRequestHeader=m,S.onreadystatechange=function(){if(4==S.readyState){S.onreadystatechange=b,clearTimeout(C);var e,n=!1;if(S.status>=200&&S.status<300||304==S.status||0==S.status&&"file:"==x){u=u||w(o.mimeType||S.getResponseHeader("content-type")),e=S.responseText;try{"script"==u?(1,eval)(e):"xml"==u?e=S.responseXML:"json"==u&&(e=c.test(e)?null:t.parseJSON(e))}catch(i){n=i}n?y(n,"parsererror",S,o,s):v(e,S,o,s)}else y(S.statusText||null,S.status?"error":"abort",S,o,s)}},g(S,o)===!1)return S.abort(),y(null,"abort",S,o,s),S;if(o.xhrFields)for(r in o.xhrFields)S[r]=o.xhrFields[r];var N="async"in o?o.async:!0;S.open(o.type,o.url,N,o.username,o.password);for(r in p)T.apply(S,p[r]);return o.timeout>0&&(C=setTimeout(function(){S.onreadystatechange=b,S.abort(),y(null,"timeout",S,o,s)},o.timeout)),S.send(o.data?o.data:null),S},t.get=function(){return t.ajax(S.apply(null,arguments))},t.post=function(){var e=S.apply(null,arguments);return e.type="POST",t.ajax(e)},t.getJSON=function(){var e=S.apply(null,arguments);return e.dataType="json",t.ajax(e)},t.fn.load=function(e,n,i){if(!this.length)return this;var a,r=this,s=e.split(/\s/),u=S(e,n,i),f=u.success;return s.length>1&&(u.url=s[0],a=s[1]),u.success=function(e){r.html(a?t("
    ").html(e.replace(o,"")).find(a):e),f&&f.apply(r,arguments)},t.ajax(u),this};var T=encodeURIComponent;t.param=function(e,n){var i=[];return i.add=function(e,n){t.isFunction(n)&&(n=n()),null==n&&(n=""),this.push(T(e)+"="+T(n))},C(i,e,n),i.join("&").replace(/%20/g,"+")}}(Zepto),function(t){t.fn.serializeArray=function(){var e,n,i=[],r=function(t){return t.forEach?t.forEach(r):void i.push({name:e,value:t})};return this[0]&&t.each(this[0].elements,function(i,o){n=o.type,e=o.name,e&&"fieldset"!=o.nodeName.toLowerCase()&&!o.disabled&&"submit"!=n&&"reset"!=n&&"button"!=n&&"file"!=n&&("radio"!=n&&"checkbox"!=n||o.checked)&&r(t(o).val())}),i},t.fn.serialize=function(){var t=[];return this.serializeArray().forEach(function(e){t.push(encodeURIComponent(e.name)+"="+encodeURIComponent(e.value))}),t.join("&")},t.fn.submit=function(e){if(0 in arguments)this.bind("submit",e);else if(this.length){var n=t.Event("submit");this.eq(0).trigger(n),n.isDefaultPrevented()||this.get(0).submit()}return this}}(Zepto),function(t){"__proto__"in{}||t.extend(t.zepto,{Z:function(e,n){return e=e||[],t.extend(e,t.fn),e.selector=n||"",e.__Z=!0,e},isZ:function(e){return"array"===t.type(e)&&"__Z"in e}});try{getComputedStyle(void 0)}catch(e){var n=getComputedStyle;window.getComputedStyle=function(t){try{return n(t)}catch(e){return null}}}}(Zepto); 3 | --------------------------------------------------------------------------------