├── .gitignore ├── LICENSE ├── README.md ├── Rakefile ├── docs ├── docco.css └── multimethod.html ├── index.js ├── multimethod-min.js ├── multimethod.js ├── package.json └── test ├── multimethod-tests.js ├── test.html └── vendor ├── jquery.js ├── qunit.css ├── qunit.js └── underscore.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Kris Jordan 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # What is multimethod.js? 2 | 3 | Multimethods are a functional programming control structure that allow you 4 | to dynamically build-up and manipulate the dispatching behavior of a 5 | polymorphic function. Inspired by clojure's multimethods, multimethod.js 6 | provides a functional alternative to classical, prototype based polymorphism. 7 | The multimethod.js library is 1kb minified, MIT licensed, and available on 8 | [GitHub](https://github.com/KrisJordan/multimethod-js). 9 | 10 | # Installation 11 | 12 | Install with `npm` for use in node.js based projects. 13 | 14 | npm install multimethod 15 | node 16 | > var multimethod = require('multimethod'); 17 | 18 | For in-browser use you will need to grab 19 | [underscore.js](http://documentcloud.github.com/underscore/) and multimethod.js: 20 | 21 | - underscore.js 22 | - Development: http://documentcloud.github.com/underscore/underscore.js 23 | - Minified: http://documentcloud.github.com/underscore/underscore-min.js 24 | - multimethod.js 25 | - Development: https://raw.github.com/KrisJordan/multimethod-js/master/multimethod.js 26 | - Minified: https://raw.github.com/KrisJordan/multimethod-js/master/multimethod-min.js 27 | 28 | # API 29 | 30 | - Constructor: `multimethod`( [fn | string] ): No arg constructor uses an 31 | identity function for `dispatch`. Single arg constructor is a shortcut for 32 | calling `dispatch` with the same argument. 33 | - `dispatch`(fn | string): Sets the `multimethod`'s `dispatch` function. String 34 | values are transformed into a pluck function which projects a single 35 | property value from the first argurment. 36 | - `when`(match, fn | value): Add a `method` to be invoked when the `dispatch` 37 | return value matches 'match'. If a non-function `value` is provided it will 38 | be returned directly. Calling `when` with the same `match` value twice will 39 | override the previously registered `method`. 40 | - `remove`(match): Remove a `method` by it's `match` value. 41 | - `default`(fn | value): Catch-all case when no `method` match is found. 42 | 43 | 44 | # Motivating Examples 45 | 46 | Let's use the node.js REPL to build a few multimethods and see what they are 47 | capable of doing. In this first example we'll create a mulimethod that 48 | calculates the area of shapes instantiated with object literals. 49 | 50 | ```javascript 51 | > var multimethod = require('multimethod'); 52 | > var area = multimethod() 53 | .dispatch(function(o) { 54 | return o.shape; 55 | }) 56 | .when("square", function(o) { 57 | return Math.pow(o.side, 2); 58 | }); 59 | > var aSquare = { "shape":"square", "side": 2 }; 60 | > area( aSquare ); 61 | 4 62 | 63 | > var aCircle = { "shape":"circle", "radius": 5 }; 64 | > area( aCircle ); 65 | undefined 66 | 67 | > area.default(function(o) { 68 | throw "Unknown shape: " + o.shape; 69 | }); 70 | > area( aCircle ); 71 | Unknown Shape: circle 72 | 73 | > area.when("circle", function(o) { 74 | return Math.PI * Math.pow(o.radius, 2); 75 | }); 76 | > area( aCircle ); 77 | 78.53981633974483 78 | > area( aSquare ); 79 | 4 80 | 81 | > area.remove("circle"); 82 | > area( aCircle ); 83 | Unknown Shape: circle 84 | ``` 85 | 86 | Notice how `dispatch` returns the value we'll match to a "method" registered 87 | with `when`. You can introduce, overwrite, and remove new methods dynamically at 88 | runtime. Fallback behavior can be established with a `default` function called 89 | when no methods match the dispatched value. 90 | 91 | A recursive Fibonacci function can be expressed naturally with a multimethod. 92 | 93 | ```javascript 94 | > var fib = multimethod() 95 | .when( 0, 0 ) 96 | .when( 1, 1 ) 97 | .default( function(n) { 98 | return fib(n-1) + fib(n-2); 99 | }); 100 | > fib(20); 101 | 6765 102 | ``` 103 | 104 | Notice, there is no `dispatch` specified. By default a multimethod will use 105 | the first argument it is invoked with to match the correct method to dispatch 106 | to. 107 | 108 | ```javascript 109 | > var hitPoints = multimethod() 110 | .dispatch(function(player){ return player.powerUp; }) 111 | .when( {"type":"star"} , Infinity) 112 | .default(5); 113 | 114 | > var starPower = { "type":"star" }, 115 | > mario = { "powerUp": starPower }; 116 | > hitPoints(mario); 117 | Infinity 118 | 119 | > mario.powerUp = null; 120 | > hitPoints(mario); 121 | 5 122 | 123 | > var godModeCheat = function() { return starPower; }; 124 | > hitPoints.dispatch(godModeCheat); 125 | > mario.powerUp; 126 | null 127 | > hitPoints(mario); 128 | Infinity 129 | ``` 130 | 131 | In this last example notice how we are matching against an object. Matching 132 | is done using deep equality so objects and arrays are valid method matching 133 | criteria. Also notice how we can completely override our dispatch 134 | function to change the logic with which a multimethod evaluates its arguments 135 | for dispatch, or, in this case, ignores them! 136 | 137 | # Multimethod Dispatch Algorithm Overview 138 | 139 | 1. User calls multimethod with argument `anArgument`. 140 | 2. Multimethod calls its `dispatch` function with `anArgument`. The returned 141 | value is stored in `dispatchValue`. 142 | 3. Multimethod iterates through each 'method' registered with `when` and 143 | performs an equality test on the `dispatchValue` and each method's match 144 | value. If a match is found, set `matchFunction` to the method's function 145 | and go to step 5. 146 | 4. If no method match found, set `matchFunction` to the multimethod's `default` 147 | function. 148 | 5. Multimethod calls `matchFunction` with `anArgument`. The returned value 149 | is returned to the user who called the multimethod. 150 | 151 | # Detailed Walkthrough 152 | 153 | ## The Basics 154 | 155 | A `multimethod` is instantiated with the `multimethod` function. 156 | 157 | ```javascript 158 | var stopLightColor = multimethod(); 159 | ``` 160 | 161 | A `multimethod` has methods. A `method` is has two parts, its match value 162 | and its implementation function. Methods are added using `when`. 163 | 164 | ```javascript 165 | stopLightColor.when("go", function() { return "green"; }) 166 | .when("stop", function() { return "red"; }); 167 | ``` 168 | 169 | You can call a `multimethod` just like any other function. It will dispatch 170 | based on the argument(s) passed in, invoke the matched `method`, and return 171 | the results of the `method` call. 172 | 173 | ```javascript 174 | console.log( stopLightColor("go") ); // "green" 175 | ``` 176 | 177 | When no method matches control can fallback to a `default` method. 178 | 179 | ```javascript 180 | stopLightColor.default( function() { return "unknown"; } ); 181 | console.log( stopLightColor("yield") ); // prints "unknown" 182 | ``` 183 | 184 | A `multimethod` can handle new cases dynamically at run time. 185 | 186 | ```javascript 187 | stopLightColor.when("yield", function() { return "yellow"; }); 188 | ``` 189 | 190 | There is a shorter way for a `method` to return a plain value. Rather than 191 | passing an implementation function to `when`, pass the value. 192 | 193 | ```javascript 194 | stopLightColor.when("yield", "yellow"); 195 | console.log( stopLightColor("yield") ); // prints "yellow" 196 | ``` 197 | 198 | A `method` can be removed dynamically at run time, too. 199 | 200 | ```javascript 201 | stopLightColor.remove("go"); 202 | console.log( stopLightColor("go") ); // prints "unknown" 203 | ``` 204 | 205 | ## Dispatch Function 206 | 207 | Each `multimethod` call first invokes a `dispatch` function whose return value 208 | is used to match the correct `method` to call. The `dispatch` function is 209 | passed the arguments the `multimethod` is invoked with and returns a value 210 | to match against. 211 | 212 | The default `dispatch` function is an identity function. 213 | The basic `stopLightColor` examples could have been 214 | created with an explicit `dispatch` function. 215 | 216 | ```javascript 217 | var stopLightColor = multimethod() 218 | .dispatch(function(state){ 219 | return state; 220 | }) 221 | .when('go', 'green'); 222 | console.log( stopLightColor('go') ); // green 223 | ``` 224 | 225 | The power of the `multimethod` paradigm is the ability to dispatch with a 226 | user-defined function. This gives a `multimethod` its "polymorphic" powers. 227 | Unlike classical, object-oriented polymorphism where the compiler dispatches 228 | based on the type hierarchy, a `multimethod` can dispatch on any criteria. 229 | 230 | ```javascript 231 | var contacts = [ 232 | {"name":"Jack", "service":"Twitter","handle": "@jack"}, 233 | {"name":"Diane","service":"Email", "address":"d@g.com"}, 234 | {"name":"John", "service":"Phone", "number": "919-919-9191"} 235 | ]; 236 | 237 | var sendMessage = multimethod() 238 | .dispatch(function(contact, msg) { 239 | return contact.service; 240 | }) 241 | .when("Twitter", function(contact, msg) { 242 | console.log("Tweet @"+contact.handle+":"+msg); 243 | }) 244 | .when("Email", function(contact, msg) { 245 | console.log("Emailing "+contact.address+":"+msg); 246 | }) 247 | .default(function(contact, msg) { 248 | console.log("Could not message " + contact.name); 249 | }); 250 | 251 | // Blast a message 252 | contacts.forEach( function(contact) { 253 | sendMessage(contact, "Hello, world."); 254 | }); 255 | ``` 256 | 257 | Plucking a single property from an object is so commonly used as a `dispatch` 258 | function, like in the example above, there is a shortcut for this pattern. 259 | The following `dispatch` call is equivalent to above. 260 | 261 | ```javascript 262 | sendMessage.dispatch( 'service' ); 263 | ``` 264 | 265 | A `multimethod`'s `dispatch` is usually specified when constructed. 266 | 267 | ```javascript 268 | var sendMessage = multimethod('service'); 269 | ``` 270 | 271 | Just like `method`s can be added and removed from a `multimethod` at 272 | run time, the `dispatch` function can also be redefined at run time. 273 | Ponder the implications of that for a minute. It is really powerful and 274 | really dangerous. Don't shoot your eye out. 275 | 276 | ## Deep Equality Matching 277 | 278 | A `method`'s match value is compared to `dispatch`'s return value 279 | using the underscore.js 280 | [`isEqual`](http://documentcloud.github.com/underscore/#isEqual) 281 | function. Deep equality `method` matching enables concise expressivity. 282 | Contrast this with a traditional `switch` statement that is 283 | limited by JavaScript's === equality behavior. 284 | 285 | ```javascript 286 | var greatPairs = multimethod() 287 | .when( ["Salt", "Pepper"], "Shakers" ) 288 | .when( [{"name":"Bonnie"}, {"name":"Clyde"}], "Robbers" ); 289 | console.log( greatPairs( ["Salt", "Pepper"] ) ); // Shakers 290 | ``` 291 | 292 | ## Related Work 293 | 294 | * Clojure's multimethod - http://clojure.org/multimethods 295 | * Pascal Coste Filtered Dispatch in Common Lisp - http://www.p-cos.net/documents/filtered-dispatch.pdf 296 | 297 | ## How-to Contribute 298 | 299 | * Submit bugs and feature requests on 300 | [GitHub Issues](https://github.com/KrisJordan/multimethod-js/issues) page. 301 | * Fork the repository and submit pull requests. Pull requests that update 302 | the test suite for coverage on changes will be brought in quickly. 303 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'closure-compiler' 3 | 4 | HEADER = /((^\s*\/\/.*\n)+)/ 5 | 6 | desc "Use the Closure Compiler to compress multimethod.js" 7 | task :build do 8 | source = File.read('multimethod.js') 9 | header = source.match(HEADER) 10 | min = Closure::Compiler.new.compress(source) 11 | File.open('multimethod-min.js', 'w') do |file| 12 | file.write header[1].squeeze(' ') + min 13 | end 14 | end 15 | 16 | desc "Build the docco documentation" 17 | task :doc do 18 | sh "docco multimethod.js" 19 | end 20 | -------------------------------------------------------------------------------- /docs/docco.css: -------------------------------------------------------------------------------- 1 | /*--------------------- Layout and Typography ----------------------------*/ 2 | body { 3 | font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; 4 | font-size: 15px; 5 | line-height: 22px; 6 | color: #252519; 7 | margin: 0; padding: 0; 8 | } 9 | a { 10 | color: #261a3b; 11 | } 12 | a:visited { 13 | color: #261a3b; 14 | } 15 | p { 16 | margin: 0 0 15px 0; 17 | } 18 | h1, h2, h3, h4, h5, h6 { 19 | margin: 0px 0 15px 0; 20 | } 21 | h1 { 22 | margin-top: 40px; 23 | } 24 | #container { 25 | position: relative; 26 | } 27 | #background { 28 | position: fixed; 29 | top: 0; left: 525px; right: 0; bottom: 0; 30 | background: #f5f5ff; 31 | border-left: 1px solid #e5e5ee; 32 | z-index: -1; 33 | } 34 | #jump_to, #jump_page { 35 | background: white; 36 | -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777; 37 | -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px; 38 | font: 10px Arial; 39 | text-transform: uppercase; 40 | cursor: pointer; 41 | text-align: right; 42 | } 43 | #jump_to, #jump_wrapper { 44 | position: fixed; 45 | right: 0; top: 0; 46 | padding: 5px 10px; 47 | } 48 | #jump_wrapper { 49 | padding: 0; 50 | display: none; 51 | } 52 | #jump_to:hover #jump_wrapper { 53 | display: block; 54 | } 55 | #jump_page { 56 | padding: 5px 0 3px; 57 | margin: 0 0 25px 25px; 58 | } 59 | #jump_page .source { 60 | display: block; 61 | padding: 5px 10px; 62 | text-decoration: none; 63 | border-top: 1px solid #eee; 64 | } 65 | #jump_page .source:hover { 66 | background: #f5f5ff; 67 | } 68 | #jump_page .source:first-child { 69 | } 70 | table td { 71 | border: 0; 72 | outline: 0; 73 | } 74 | td.docs, th.docs { 75 | max-width: 450px; 76 | min-width: 450px; 77 | min-height: 5px; 78 | padding: 10px 25px 1px 50px; 79 | overflow-x: hidden; 80 | vertical-align: top; 81 | text-align: left; 82 | } 83 | .docs pre { 84 | margin: 15px 0 15px; 85 | padding-left: 15px; 86 | } 87 | .docs p tt, .docs p code { 88 | background: #f8f8ff; 89 | border: 1px solid #dedede; 90 | font-size: 12px; 91 | padding: 0 0.2em; 92 | } 93 | .pilwrap { 94 | position: relative; 95 | } 96 | .pilcrow { 97 | font: 12px Arial; 98 | text-decoration: none; 99 | color: #454545; 100 | position: absolute; 101 | top: 3px; left: -20px; 102 | padding: 1px 2px; 103 | opacity: 0; 104 | -webkit-transition: opacity 0.2s linear; 105 | } 106 | td.docs:hover .pilcrow { 107 | opacity: 1; 108 | } 109 | td.code, th.code { 110 | padding: 14px 15px 16px 25px; 111 | width: 100%; 112 | vertical-align: top; 113 | background: #f5f5ff; 114 | border-left: 1px solid #e5e5ee; 115 | } 116 | pre, tt, code { 117 | font-size: 12px; line-height: 18px; 118 | font-family: Monaco, Consolas, "Lucida Console", monospace; 119 | margin: 0; padding: 0; 120 | } 121 | 122 | 123 | /*---------------------- Syntax Highlighting -----------------------------*/ 124 | td.linenos { background-color: #f0f0f0; padding-right: 10px; } 125 | span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } 126 | body .hll { background-color: #ffffcc } 127 | body .c { color: #408080; font-style: italic } /* Comment */ 128 | body .err { border: 1px solid #FF0000 } /* Error */ 129 | body .k { color: #954121 } /* Keyword */ 130 | body .o { color: #666666 } /* Operator */ 131 | body .cm { color: #408080; font-style: italic } /* Comment.Multiline */ 132 | body .cp { color: #BC7A00 } /* Comment.Preproc */ 133 | body .c1 { color: #408080; font-style: italic } /* Comment.Single */ 134 | body .cs { color: #408080; font-style: italic } /* Comment.Special */ 135 | body .gd { color: #A00000 } /* Generic.Deleted */ 136 | body .ge { font-style: italic } /* Generic.Emph */ 137 | body .gr { color: #FF0000 } /* Generic.Error */ 138 | body .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 139 | body .gi { color: #00A000 } /* Generic.Inserted */ 140 | body .go { color: #808080 } /* Generic.Output */ 141 | body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ 142 | body .gs { font-weight: bold } /* Generic.Strong */ 143 | body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 144 | body .gt { color: #0040D0 } /* Generic.Traceback */ 145 | body .kc { color: #954121 } /* Keyword.Constant */ 146 | body .kd { color: #954121; font-weight: bold } /* Keyword.Declaration */ 147 | body .kn { color: #954121; font-weight: bold } /* Keyword.Namespace */ 148 | body .kp { color: #954121 } /* Keyword.Pseudo */ 149 | body .kr { color: #954121; font-weight: bold } /* Keyword.Reserved */ 150 | body .kt { color: #B00040 } /* Keyword.Type */ 151 | body .m { color: #666666 } /* Literal.Number */ 152 | body .s { color: #219161 } /* Literal.String */ 153 | body .na { color: #7D9029 } /* Name.Attribute */ 154 | body .nb { color: #954121 } /* Name.Builtin */ 155 | body .nc { color: #0000FF; font-weight: bold } /* Name.Class */ 156 | body .no { color: #880000 } /* Name.Constant */ 157 | body .nd { color: #AA22FF } /* Name.Decorator */ 158 | body .ni { color: #999999; font-weight: bold } /* Name.Entity */ 159 | body .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ 160 | body .nf { color: #0000FF } /* Name.Function */ 161 | body .nl { color: #A0A000 } /* Name.Label */ 162 | body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ 163 | body .nt { color: #954121; font-weight: bold } /* Name.Tag */ 164 | body .nv { color: #19469D } /* Name.Variable */ 165 | body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ 166 | body .w { color: #bbbbbb } /* Text.Whitespace */ 167 | body .mf { color: #666666 } /* Literal.Number.Float */ 168 | body .mh { color: #666666 } /* Literal.Number.Hex */ 169 | body .mi { color: #666666 } /* Literal.Number.Integer */ 170 | body .mo { color: #666666 } /* Literal.Number.Oct */ 171 | body .sb { color: #219161 } /* Literal.String.Backtick */ 172 | body .sc { color: #219161 } /* Literal.String.Char */ 173 | body .sd { color: #219161; font-style: italic } /* Literal.String.Doc */ 174 | body .s2 { color: #219161 } /* Literal.String.Double */ 175 | body .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ 176 | body .sh { color: #219161 } /* Literal.String.Heredoc */ 177 | body .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ 178 | body .sx { color: #954121 } /* Literal.String.Other */ 179 | body .sr { color: #BB6688 } /* Literal.String.Regex */ 180 | body .s1 { color: #219161 } /* Literal.String.Single */ 181 | body .ss { color: #19469D } /* Literal.String.Symbol */ 182 | body .bp { color: #954121 } /* Name.Builtin.Pseudo */ 183 | body .vc { color: #19469D } /* Name.Variable.Class */ 184 | body .vg { color: #19469D } /* Name.Variable.Global */ 185 | body .vi { color: #19469D } /* Name.Variable.Instance */ 186 | body .il { color: #666666 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /docs/multimethod.html: -------------------------------------------------------------------------------- 1 | multimethod.js

multimethod.js

multimethod.js 0.1.0

2 | 3 |

(c) 2011 Kris Jordan

4 | 5 |

multimethod is freely distributable under the MIT license. 6 | For details and documentation: 7 | http://krisjordan.com/multimethod-js

(function() {

Multimethods are a functional programming control structure for dispatching 8 | function calls with user-defined criteria that can be changed at run time. 9 | Inspired by clojure's multimethods, multimethod.js provides an alternative to 10 | classical, prototype-chain based polymorphism.

Internal Utility Functions

No operation function used by default by default.

    var noop = function() {};

Identity dispatch function. Default value of dispatch.

    var identity = function(a) { return a; };

A method in multimethod is a (match value, function) pair stored in 11 | an array. indexOf takes a value and array of methods and returns the 12 | index of the method whose value is equal to the first argument. If no 13 | match is found, false is returned.

    var indexOf = function(value, methods) {
 14 |         for(var i in methods) {
 15 |             var matches  = methods[i][0];
 16 |             if(_(value).isEqual(matches)) {
 17 |                 return i;
 18 |             }
 19 |         }
 20 |         return false;
 21 |     }

Given a dispatch value and array of methods, return the function 22 | of the method whose match value corresponds to a dispatch value.

    var match = function(value, methods) {
 23 |         var index = indexOf(value, methods);
 24 |         if(index !== false) {
 25 |             return methods[index][1];
 26 |         } else {
 27 |             return false;
 28 |         }
 29 |     }

Simple, consistent helper that returns a native value or invokes a function 30 | and returns its return value. Used by when and default allowing 31 | short-hand notation for returning values rather than calling functions.

    var toValue = function(subject, args) {
 32 |         if(_.isFunction(subject)) {
 33 |             return subject.apply(this, args);
 34 |         } else {
 35 |             return subject;
 36 |         }
 37 |     };

Plucking a single property value from an object in dispatch is commonly 38 | used. The internal pluck function returns a function suitable for use 39 | by dispatch for just that purpose.

    var pluck = function(property) {
 40 |         return function(object) {
 41 |             return object[property];
 42 |         }
 43 |     };

Implementation

multimethod is a higher-order function that returns a closure with 44 | methods to control its behavior.

    var multimethod = function(dispatch) { 

Private Properties

_dispatch holds either a dispatch function or a string 45 | corresponding to the property name whose value will be plucked 46 | and used as the dispatch criteria.

        var _dispatch,

_methods is a an array of method arrays. A method is 47 | [ matchValue, implementation ].

            _methods   = [],

_default is the fallback method when a multimethod is called 48 | and matches no other method.

            _default   = noop;

The fundamental control flow of the multimethod is implemented 49 | in _lookup. First we invoke the dispatch function, this gives 50 | us our match criteria. Then we match a method based on the criteria 51 | or return the default method.

        var _lookup    = function() {
 52 |             var criteria    = _dispatch.apply(this, arguments),
 53 |                 method      = match(criteria, _methods);
 54 |             if(method !== false) {
 55 |                 return method;
 56 |             } else {
 57 |                 return _default;
 58 |             }
 59 |         };

The result of calling multimethod's "factory" function is this function.

        var returnFn  = function() {
 60 |             var method = _lookup.apply(this, arguments);
 61 |             return toValue.call(this, method, arguments);
 62 |         };

Member Methods / API

dispatch is the accessor to the multimethod's _dispatch function. 63 | When called with a string we create an anonymous pluck function as a 64 | shorthand.

        returnFn['dispatch'] = function(dispatch) {
 65 |             if(_.isFunction(dispatch)) {
 66 |                 _dispatch = dispatch;
 67 |             } else if(_.isString(dispatch)) {
 68 |                 _dispatch = pluck(dispatch);
 69 |             } else {
 70 |                 throw "dispatch requires a function or a string.";
 71 |             }
 72 |             return this;
 73 |         }

If multimethod is called/"constructed" with a dispatch value we go ahead and set 74 | it up here. Otherwise dispatch is the identity function.

        returnFn.dispatch(dispatch || identity);

when introduces new methods to a multimethod. If the 75 | matchValue has already been registered the new method will 76 | overwrite the old method.

        returnFn['when'] = function(matchValue, fn) {
 77 |             var index = indexOf(matchValue, _methods);
 78 |             if(index !== false) {
 79 |                 _methods[index] = [matchValue, fn];
 80 |             } else {
 81 |                 _methods.push([matchValue, fn]);
 82 |             }
 83 |             return this;
 84 |         }

remove will unregister a method based on matchValue

        returnFn['remove'] = function(matchValue) {
 85 |             var index = indexOf(matchValue, _methods);
 86 |             if(index !== false) {
 87 |                 _methods.splice(index, 1);
 88 |             }
 89 |             return this;
 90 |         }

default is an accessor to control the _default, fallback method 91 | that is called when no match is found when the multimethod is 92 | invoked and dispatched.

        returnFn['default'] = function(method) {
 93 |             _default = method;
 94 |             return this;
 95 |         }

Our multimethod instance/closure is fully setup now, return!

        return returnFn;
 96 |     };

The following snippet courtesy of underscore.js. 97 | Export multimethod to the window/exports namespace.

    if (typeof exports !== 'undefined') {
 98 |         if (typeof module !== 'undefined' && module.exports) {
 99 |             exports = module.exports = multimethod;
100 |             var _ = require('underscore');
101 |         }
102 |         exports.multimethod = multimethod;
103 |     } else if (typeof define === 'function' && define.amd) {
104 |         define('multimethod', function() {
105 |             return multimethod;
106 |         });
107 |     } else {
108 |         this['multimethod'] = multimethod;
109 |         var _ = this['_'];
110 |     }
111 | 
112 |     multimethod.version = '0.1.0';
113 | 
114 | }).call(this);
115 | 
116 | 
-------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./multimethod'); 2 | -------------------------------------------------------------------------------- /multimethod-min.js: -------------------------------------------------------------------------------- 1 | // multimethod.js 0.1.0 2 | // 3 | // (c) 2011 Kris Jordan 4 | // 5 | // Multimethod is freely distributable under the MIT license. 6 | // For details and documentation: 7 | // [http://krisjordan.com/multimethod-js](http://krisjordan.com/multimethod-js) 8 | (function(){var j=function(){},k=function(g){return g},h=function(g,c){for(var d in c){var b=c[d][0];if(f(g).isEqual(b))return d}return false},l=function(b){return function(c){return c[b]}},b=function(b){var c,d=[],i=j,m=function(){var a=c.apply(this,arguments),a=h(a,d),a=a!==false?d[a][1]:false;return a!==false?a:i},e=function(){var a=m.apply(this,arguments);return f.isFunction(a)?a.apply(this,arguments):a};e.dispatch=function(a){if(f.isFunction(a))c=a;else if(f.isString(a))c=l(a);else throw"dispatch requires a function or a string."; 9 | return this};e.dispatch(b||k);e.when=function(a,b){var c=h(a,d);c!==false?d[c]=[a,b]:d.push([a,b]);return this};e.remove=function(a){a=h(a,d);a!==false&&d.splice(a,1);return this};e["default"]=function(a){i=a;return this};return e};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports){exports=module.exports=b;var f=require("underscore")}exports.multimethod=b}else typeof define==="function"&&define.amd?define("multimethod",function(){return b}):(this.multimethod=b,f=this._); 10 | b.version="0.1.0"}).call(this); 11 | -------------------------------------------------------------------------------- /multimethod.js: -------------------------------------------------------------------------------- 1 | // multimethod.js 0.1.0 2 | // 3 | // (c) 2011 Kris Jordan 4 | // 5 | // `multimethod` is freely distributable under the MIT license. 6 | // For details and documentation: 7 | // [http://krisjordan.com/multimethod-js](http://krisjordan.com/multimethod-js) 8 | 9 | (function() { 10 | 11 | // Multimethods are a functional programming control structure for dispatching 12 | // function calls with user-defined criteria that can be changed at run time. 13 | // Inspired by clojure's multimethods, multimethod.js provides an alternative to 14 | // classical, prototype-chain based polymorphism. 15 | 16 | // ## Internal Utility Functions 17 | 18 | // No operation function used by default by `default`. 19 | var noop = function() {}; 20 | 21 | // Identity `dispatch` function. Default value of `dispatch`. 22 | var identity = function(a) { return a; }; 23 | 24 | // A `method` in `multimethod` is a (match value, function) pair stored in 25 | // an array. `indexOf` takes a value and array of methods and returns the 26 | // index of the method whose value is equal to the first argument. If no 27 | // match is found, false is returned. 28 | var indexOf = function(value, methods) { 29 | for(var i in methods) { 30 | var matches = methods[i][0]; 31 | if(_(value).isEqual(matches)) { 32 | return i; 33 | } 34 | } 35 | return false; 36 | } 37 | 38 | // Given a dispatch `value` and array of `method`s, return the function 39 | // of the `method` whose match value corresponds to a dispatch value. 40 | var match = function(value, methods) { 41 | var index = indexOf(value, methods); 42 | if(index !== false) { 43 | return methods[index][1]; 44 | } else { 45 | return false; 46 | } 47 | } 48 | 49 | // Simple, consistent helper that returns a native value or invokes a function 50 | // and returns its return value. Used by `when` and `default` allowing 51 | // short-hand notation for returning values rather than calling functions. 52 | var toValue = function(subject, args) { 53 | if(_.isFunction(subject)) { 54 | return subject.apply(this, args); 55 | } else { 56 | return subject; 57 | } 58 | }; 59 | 60 | // Plucking a single property value from an object in `dispatch` is commonly 61 | // used. The internal `pluck` function returns a function suitable for use 62 | // by `dispatch` for just that purpose. 63 | var pluck = function(property) { 64 | return function(object) { 65 | return object[property]; 66 | } 67 | }; 68 | 69 | 70 | // ## Implementation 71 | 72 | // `multimethod` is a higher-order function that returns a closure with 73 | // methods to control its behavior. 74 | var multimethod = function(dispatch) { 75 | 76 | // ### Private Properties 77 | 78 | // `_dispatch` holds either a dispatch function or a string 79 | // corresponding to the property name whose value will be plucked 80 | // and used as the `dispatch` criteria. 81 | var _dispatch, 82 | // `_methods` is a an array of `method` arrays. A `method` is 83 | // [ matchValue, implementation ]. 84 | _methods = [], 85 | // `_default` is the fallback method when a `multimethod` is called 86 | // and matches no other method. 87 | _default = noop; 88 | 89 | // The fundamental control flow of the `multimethod` is implemented 90 | // in `_lookup`. First we invoke the dispatch function, this gives 91 | // us our match criteria. Then we match a method based on the criteria 92 | // or return the default method. 93 | var _lookup = function() { 94 | var criteria = _dispatch.apply(this, arguments), 95 | method = match(criteria, _methods); 96 | if(method !== false) { 97 | return method; 98 | } else { 99 | return _default; 100 | } 101 | }; 102 | 103 | // The result of calling `multimethod`'s "factory" function is this function. 104 | var returnFn = function() { 105 | var method = _lookup.apply(this, arguments); 106 | return toValue.call(this, method, arguments); 107 | }; 108 | 109 | // ### Member Methods / API 110 | 111 | // `dispatch` is the accessor to the `multimethod`'s `_dispatch` function. 112 | // When called with a string we create an anonymous pluck function as a 113 | // shorthand. 114 | returnFn['dispatch'] = function(dispatch) { 115 | if(_.isFunction(dispatch)) { 116 | _dispatch = dispatch; 117 | } else if(_.isString(dispatch)) { 118 | _dispatch = pluck(dispatch); 119 | } else { 120 | throw "dispatch requires a function or a string."; 121 | } 122 | return this; 123 | } 124 | // If `multimethod` is called/"constructed" with a `dispatch` value we go ahead and set 125 | // it up here. Otherwise `dispatch` is the `identity` function. 126 | returnFn.dispatch(dispatch || identity); 127 | 128 | // `when` introduces new `method`s to a `multimethod`. If the 129 | // `matchValue` has already been registered the new method will 130 | // overwrite the old method. 131 | returnFn['when'] = function(matchValue, fn) { 132 | var index = indexOf(matchValue, _methods); 133 | if(index !== false) { 134 | _methods[index] = [matchValue, fn]; 135 | } else { 136 | _methods.push([matchValue, fn]); 137 | } 138 | return this; 139 | } 140 | 141 | // `remove` will unregister a `method` based on matchValue 142 | returnFn['remove'] = function(matchValue) { 143 | var index = indexOf(matchValue, _methods); 144 | if(index !== false) { 145 | _methods.splice(index, 1); 146 | } 147 | return this; 148 | } 149 | 150 | // `default` is an accessor to control the `_default`, fallback method 151 | // that is called when no match is found when the `multimethod` is 152 | // invoked and dispatched. 153 | returnFn['default'] = function(method) { 154 | _default = method; 155 | return this; 156 | } 157 | 158 | // Our `multimethod` instance/closure is fully setup now, return! 159 | return returnFn; 160 | }; 161 | 162 | // The following snippet courtesy of underscore.js. 163 | // Export `multimethod` to the window/exports namespace. 164 | if (typeof exports !== 'undefined') { 165 | if (typeof module !== 'undefined' && module.exports) { 166 | exports = module.exports = multimethod; 167 | var _ = require('underscore'); 168 | } 169 | exports.multimethod = multimethod; 170 | } else if (typeof define === 'function' && define.amd) { 171 | define('multimethod', function() { 172 | return multimethod; 173 | }); 174 | } else { 175 | this['multimethod'] = multimethod; 176 | var _ = this['_']; 177 | } 178 | 179 | multimethod.version = '0.1.0'; 180 | 181 | }).call(this); 182 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Kris Jordan (http://krisjordan.com)", 3 | "name": "multimethod", 4 | "description": "Multimethods for JavaScript", 5 | "version": "0.1.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/KrisJordan/multimethod-js.git" 9 | }, 10 | "engines": { 11 | "node": "~0.6.0" 12 | }, 13 | "dependencies": { 14 | "underscore": "1.2.1" 15 | }, 16 | "devDependencies": {} 17 | } 18 | -------------------------------------------------------------------------------- /test/multimethod-tests.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | module("Multimethods"); 4 | 5 | var helpers = { 6 | "plus1": function(n) { return n + 1; }, 7 | "sum": function(a,b) { return a + b; }, 8 | "product": function(a,b) { return a * b; } 9 | } 10 | 11 | test("identity default", function() { 12 | var mm = multimethod(); 13 | equals(mm(1), undefined); 14 | }); 15 | 16 | test("identity dispatch function", function() { 17 | var mm = multimethod().when(1,helpers.plus1); 18 | equals(mm(1),2); 19 | }); 20 | 21 | test("default dispatch function", function() { 22 | var mm = multimethod().default(helpers.plus1); 23 | equals(mm(1),2); 24 | }); 25 | 26 | test("when returns primitive values", function() { 27 | var mm = multimethod().when(1,2); 28 | equals(mm(1),2); 29 | 30 | var mm = multimethod().when(1,true); 31 | equals(mm(1),true); 32 | 33 | var mm = multimethod().when(1,"string"); 34 | equals(mm(1),"string"); 35 | }); 36 | 37 | test("when chains and selects correct value", function() { 38 | var mm = multimethod() 39 | .when(1,"one") 40 | .when(2,"two") 41 | .when(3,"three"); 42 | equals(mm(1),"one"); 43 | equals(mm(2),"two"); 44 | equals(mm(3),"three"); 45 | }); 46 | 47 | test("multiple arguments", function() { 48 | var mm = multimethod(function(a, b) { return [a, b]; }) 49 | .when([1,1], helpers.sum) 50 | .when([3,3], helpers.product); 51 | equals(mm(1,1),2); 52 | equals(mm(3,3),9); 53 | }); 54 | 55 | test("modify dispatch function", function() { 56 | var mm = multimethod() 57 | .dispatch(helpers.plus1) 58 | .when(2,helpers.plus1); 59 | equals(mm(1),2); 60 | }); 61 | 62 | test("override method", function() { 63 | var mm = multimethod() 64 | .when(1, 1) 65 | .when(1, 2); 66 | equals(mm(1),2); 67 | }); 68 | 69 | test("remove method", function() { 70 | var mm = multimethod() 71 | .when(1,1) 72 | .default(2) 73 | .remove(1); 74 | equals(mm(1),2); 75 | }); 76 | 77 | test("pluck string dispatch", function() { 78 | var bornOn = multimethod('type') 79 | .when('person',function(person) { return person.yearBorn; }) 80 | .when('car', function(car) { return car.yearBuilt; }) 81 | equals(bornOn({'type':'person','yearBorn':1985}), 1985); 82 | equals(bornOn({'type':'car','yearBuilt':2000}), 2000); 83 | }); 84 | 85 | test("pluck undefined property", function() { 86 | var pluckMM = multimethod('type') 87 | .default(1); 88 | equals(pluckMM({}),1); 89 | }); 90 | 91 | test("deep equality example", function() { 92 | var greatPairs = multimethod() 93 | .when( ["Salt", "Pepper"], "Shakers" ) 94 | .when( [ { "name": "Bonnie" }, { "name": "Clyde" } ], "Robbers" ) 95 | .default( "?" ); 96 | equals("?", greatPairs(["MJ", "Pippen"])); 97 | equals("Shakers", greatPairs(["Salt", "Pepper"])); 98 | equals("Robbers", greatPairs([ { "name": "Bonnie" }, { "name": "Clyde" } ])); 99 | }); 100 | 101 | test("calling dispatch throws", function() { 102 | try { 103 | var mm = multimethod().dispatch(1); 104 | equals(true, false); 105 | } catch(e) { 106 | equals(true, true); 107 | } 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /test/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Multimethod Test Suite 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |

Multimethod Test Suite

15 |

16 |

17 |
    18 |
    19 | 20 | 21 | -------------------------------------------------------------------------------- /test/vendor/jquery.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery JavaScript Library v1.3.2 3 | * http://jquery.com/ 4 | * 5 | * Copyright (c) 2009 John Resig 6 | * Dual licensed under the MIT and GPL licenses. 7 | * http://docs.jquery.com/License 8 | * 9 | * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) 10 | * Revision: 6246 11 | */ 12 | (function(){var l=this,g,y=l.jQuery,p=l.$,o=l.jQuery=l.$=function(E,F){return new o.fn.init(E,F)},D=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;o.fn=o.prototype={init:function(E,H){E=E||document;if(E.nodeType){this[0]=E;this.length=1;this.context=E;return this}if(typeof E==="string"){var G=D.exec(E);if(G&&(G[1]||!H)){if(G[1]){E=o.clean([G[1]],H)}else{var I=document.getElementById(G[3]);if(I&&I.id!=G[3]){return o().find(E)}var F=o(I||[]);F.context=document;F.selector=E;return F}}else{return o(H).find(E)}}else{if(o.isFunction(E)){return o(document).ready(E)}}if(E.selector&&E.context){this.selector=E.selector;this.context=E.context}return this.setArray(o.isArray(E)?E:o.makeArray(E))},selector:"",jquery:"1.3.2",size:function(){return this.length},get:function(E){return E===g?Array.prototype.slice.call(this):this[E]},pushStack:function(F,H,E){var G=o(F);G.prevObject=this;G.context=this.context;if(H==="find"){G.selector=this.selector+(this.selector?" ":"")+E}else{if(H){G.selector=this.selector+"."+H+"("+E+")"}}return G},setArray:function(E){this.length=0;Array.prototype.push.apply(this,E);return this},each:function(F,E){return o.each(this,F,E)},index:function(E){return o.inArray(E&&E.jquery?E[0]:E,this)},attr:function(F,H,G){var E=F;if(typeof F==="string"){if(H===g){return this[0]&&o[G||"attr"](this[0],F)}else{E={};E[F]=H}}return this.each(function(I){for(F in E){o.attr(G?this.style:this,F,o.prop(this,E[F],G,I,F))}})},css:function(E,F){if((E=="width"||E=="height")&&parseFloat(F)<0){F=g}return this.attr(E,F,"curCSS")},text:function(F){if(typeof F!=="object"&&F!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(F))}var E="";o.each(F||this,function(){o.each(this.childNodes,function(){if(this.nodeType!=8){E+=this.nodeType!=1?this.nodeValue:o.fn.text([this])}})});return E},wrapAll:function(E){if(this[0]){var F=o(E,this[0].ownerDocument).clone();if(this[0].parentNode){F.insertBefore(this[0])}F.map(function(){var G=this;while(G.firstChild){G=G.firstChild}return G}).append(this)}return this},wrapInner:function(E){return this.each(function(){o(this).contents().wrapAll(E)})},wrap:function(E){return this.each(function(){o(this).wrapAll(E)})},append:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.appendChild(E)}})},prepend:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.insertBefore(E,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this)})},after:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this.nextSibling)})},end:function(){return this.prevObject||o([])},push:[].push,sort:[].sort,splice:[].splice,find:function(E){if(this.length===1){var F=this.pushStack([],"find",E);F.length=0;o.find(E,this[0],F);return F}else{return this.pushStack(o.unique(o.map(this,function(G){return o.find(E,G)})),"find",E)}},clone:function(G){var E=this.map(function(){if(!o.support.noCloneEvent&&!o.isXMLDoc(this)){var I=this.outerHTML;if(!I){var J=this.ownerDocument.createElement("div");J.appendChild(this.cloneNode(true));I=J.innerHTML}return o.clean([I.replace(/ jQuery\d+="(?:\d+|null)"/g,"").replace(/^\s*/,"")])[0]}else{return this.cloneNode(true)}});if(G===true){var H=this.find("*").andSelf(),F=0;E.find("*").andSelf().each(function(){if(this.nodeName!==H[F].nodeName){return}var I=o.data(H[F],"events");for(var K in I){for(var J in I[K]){o.event.add(this,K,I[K][J],I[K][J].data)}}F++})}return E},filter:function(E){return this.pushStack(o.isFunction(E)&&o.grep(this,function(G,F){return E.call(G,F)})||o.multiFilter(E,o.grep(this,function(F){return F.nodeType===1})),"filter",E)},closest:function(E){var G=o.expr.match.POS.test(E)?o(E):null,F=0;return this.map(function(){var H=this;while(H&&H.ownerDocument){if(G?G.index(H)>-1:o(H).is(E)){o.data(H,"closest",F);return H}H=H.parentNode;F++}})},not:function(E){if(typeof E==="string"){if(f.test(E)){return this.pushStack(o.multiFilter(E,this,true),"not",E)}else{E=o.multiFilter(E,this)}}var F=E.length&&E[E.length-1]!==g&&!E.nodeType;return this.filter(function(){return F?o.inArray(this,E)<0:this!=E})},add:function(E){return this.pushStack(o.unique(o.merge(this.get(),typeof E==="string"?o(E):o.makeArray(E))))},is:function(E){return !!E&&o.multiFilter(E,this).length>0},hasClass:function(E){return !!E&&this.is("."+E)},val:function(K){if(K===g){var E=this[0];if(E){if(o.nodeName(E,"option")){return(E.attributes.value||{}).specified?E.value:E.text}if(o.nodeName(E,"select")){var I=E.selectedIndex,L=[],M=E.options,H=E.type=="select-one";if(I<0){return null}for(var F=H?I:0,J=H?I+1:M.length;F=0||o.inArray(this.name,K)>=0)}else{if(o.nodeName(this,"select")){var N=o.makeArray(K);o("option",this).each(function(){this.selected=(o.inArray(this.value,N)>=0||o.inArray(this.text,N)>=0)});if(!N.length){this.selectedIndex=-1}}else{this.value=K}}})},html:function(E){return E===g?(this[0]?this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g,""):null):this.empty().append(E)},replaceWith:function(E){return this.after(E).remove()},eq:function(E){return this.slice(E,+E+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(E){return this.pushStack(o.map(this,function(G,F){return E.call(G,F,G)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(J,M,L){if(this[0]){var I=(this[0].ownerDocument||this[0]).createDocumentFragment(),F=o.clean(J,(this[0].ownerDocument||this[0]),I),H=I.firstChild;if(H){for(var G=0,E=this.length;G1||G>0?I.cloneNode(true):I)}}if(F){o.each(F,z)}}return this;function K(N,O){return M&&o.nodeName(N,"table")&&o.nodeName(O,"tr")?(N.getElementsByTagName("tbody")[0]||N.appendChild(N.ownerDocument.createElement("tbody"))):N}}};o.fn.init.prototype=o.fn;function z(E,F){if(F.src){o.ajax({url:F.src,async:false,dataType:"script"})}else{o.globalEval(F.text||F.textContent||F.innerHTML||"")}if(F.parentNode){F.parentNode.removeChild(F)}}function e(){return +new Date}o.extend=o.fn.extend=function(){var J=arguments[0]||{},H=1,I=arguments.length,E=false,G;if(typeof J==="boolean"){E=J;J=arguments[1]||{};H=2}if(typeof J!=="object"&&!o.isFunction(J)){J={}}if(I==H){J=this;--H}for(;H-1}},swap:function(H,G,I){var E={};for(var F in G){E[F]=H.style[F];H.style[F]=G[F]}I.call(H);for(var F in G){H.style[F]=E[F]}},css:function(H,F,J,E){if(F=="width"||F=="height"){var L,G={position:"absolute",visibility:"hidden",display:"block"},K=F=="width"?["Left","Right"]:["Top","Bottom"];function I(){L=F=="width"?H.offsetWidth:H.offsetHeight;if(E==="border"){return}o.each(K,function(){if(!E){L-=parseFloat(o.curCSS(H,"padding"+this,true))||0}if(E==="margin"){L+=parseFloat(o.curCSS(H,"margin"+this,true))||0}else{L-=parseFloat(o.curCSS(H,"border"+this+"Width",true))||0}})}if(H.offsetWidth!==0){I()}else{o.swap(H,G,I)}return Math.max(0,Math.round(L))}return o.curCSS(H,F,J)},curCSS:function(I,F,G){var L,E=I.style;if(F=="opacity"&&!o.support.opacity){L=o.attr(E,"opacity");return L==""?"1":L}if(F.match(/float/i)){F=w}if(!G&&E&&E[F]){L=E[F]}else{if(q.getComputedStyle){if(F.match(/float/i)){F="float"}F=F.replace(/([A-Z])/g,"-$1").toLowerCase();var M=q.getComputedStyle(I,null);if(M){L=M.getPropertyValue(F)}if(F=="opacity"&&L==""){L="1"}}else{if(I.currentStyle){var J=F.replace(/\-(\w)/g,function(N,O){return O.toUpperCase()});L=I.currentStyle[F]||I.currentStyle[J];if(!/^\d+(px)?$/i.test(L)&&/^\d/.test(L)){var H=E.left,K=I.runtimeStyle.left;I.runtimeStyle.left=I.currentStyle.left;E.left=L||0;L=E.pixelLeft+"px";E.left=H;I.runtimeStyle.left=K}}}}return L},clean:function(F,K,I){K=K||document;if(typeof K.createElement==="undefined"){K=K.ownerDocument||K[0]&&K[0].ownerDocument||document}if(!I&&F.length===1&&typeof F[0]==="string"){var H=/^<(\w+)\s*\/?>$/.exec(F[0]);if(H){return[K.createElement(H[1])]}}var G=[],E=[],L=K.createElement("div");o.each(F,function(P,S){if(typeof S==="number"){S+=""}if(!S){return}if(typeof S==="string"){S=S.replace(/(<(\w+)[^>]*?)\/>/g,function(U,V,T){return T.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?U:V+">"});var O=S.replace(/^\s+/,"").substring(0,10).toLowerCase();var Q=!O.indexOf("",""]||!O.indexOf("",""]||O.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"","
    "]||!O.indexOf("",""]||(!O.indexOf("",""]||!O.indexOf("",""]||!o.support.htmlSerialize&&[1,"div
    ","
    "]||[0,"",""];L.innerHTML=Q[1]+S+Q[2];while(Q[0]--){L=L.lastChild}if(!o.support.tbody){var R=/"&&!R?L.childNodes:[];for(var M=N.length-1;M>=0;--M){if(o.nodeName(N[M],"tbody")&&!N[M].childNodes.length){N[M].parentNode.removeChild(N[M])}}}if(!o.support.leadingWhitespace&&/^\s/.test(S)){L.insertBefore(K.createTextNode(S.match(/^\s*/)[0]),L.firstChild)}S=o.makeArray(L.childNodes)}if(S.nodeType){G.push(S)}else{G=o.merge(G,S)}});if(I){for(var J=0;G[J];J++){if(o.nodeName(G[J],"script")&&(!G[J].type||G[J].type.toLowerCase()==="text/javascript")){E.push(G[J].parentNode?G[J].parentNode.removeChild(G[J]):G[J])}else{if(G[J].nodeType===1){G.splice.apply(G,[J+1,0].concat(o.makeArray(G[J].getElementsByTagName("script"))))}I.appendChild(G[J])}}return E}return G},attr:function(J,G,K){if(!J||J.nodeType==3||J.nodeType==8){return g}var H=!o.isXMLDoc(J),L=K!==g;G=H&&o.props[G]||G;if(J.tagName){var F=/href|src|style/.test(G);if(G=="selected"&&J.parentNode){J.parentNode.selectedIndex}if(G in J&&H&&!F){if(L){if(G=="type"&&o.nodeName(J,"input")&&J.parentNode){throw"type property can't be changed"}J[G]=K}if(o.nodeName(J,"form")&&J.getAttributeNode(G)){return J.getAttributeNode(G).nodeValue}if(G=="tabIndex"){var I=J.getAttributeNode("tabIndex");return I&&I.specified?I.value:J.nodeName.match(/(button|input|object|select|textarea)/i)?0:J.nodeName.match(/^(a|area)$/i)&&J.href?0:g}return J[G]}if(!o.support.style&&H&&G=="style"){return o.attr(J.style,"cssText",K)}if(L){J.setAttribute(G,""+K)}var E=!o.support.hrefNormalized&&H&&F?J.getAttribute(G,2):J.getAttribute(G);return E===null?g:E}if(!o.support.opacity&&G=="opacity"){if(L){J.zoom=1;J.filter=(J.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(K)+""=="NaN"?"":"alpha(opacity="+K*100+")")}return J.filter&&J.filter.indexOf("opacity=")>=0?(parseFloat(J.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}G=G.replace(/-([a-z])/ig,function(M,N){return N.toUpperCase()});if(L){J[G]=K}return J[G]},trim:function(E){return(E||"").replace(/^\s+|\s+$/g,"")},makeArray:function(G){var E=[];if(G!=null){var F=G.length;if(F==null||typeof G==="string"||o.isFunction(G)||G.setInterval){E[0]=G}else{while(F){E[--F]=G[F]}}}return E},inArray:function(G,H){for(var E=0,F=H.length;E0?this.clone(true):this).get();o.fn[F].apply(o(L[K]),I);J=J.concat(I)}return this.pushStack(J,E,G)}});o.each({removeAttr:function(E){o.attr(this,E,"");if(this.nodeType==1){this.removeAttribute(E)}},addClass:function(E){o.className.add(this,E)},removeClass:function(E){o.className.remove(this,E)},toggleClass:function(F,E){if(typeof E!=="boolean"){E=!o.className.has(this,F)}o.className[E?"add":"remove"](this,F)},remove:function(E){if(!E||o.filter(E,[this]).length){o("*",this).add([this]).each(function(){o.event.remove(this);o.removeData(this)});if(this.parentNode){this.parentNode.removeChild(this)}}},empty:function(){o(this).children().remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(E,F){o.fn[E]=function(){return this.each(F,arguments)}});function j(E,F){return E[0]&&parseInt(o.curCSS(E[0],F,true),10)||0}var h="jQuery"+e(),v=0,A={};o.extend({cache:{},data:function(F,E,G){F=F==l?A:F;var H=F[h];if(!H){H=F[h]=++v}if(E&&!o.cache[H]){o.cache[H]={}}if(G!==g){o.cache[H][E]=G}return E?o.cache[H][E]:H},removeData:function(F,E){F=F==l?A:F;var H=F[h];if(E){if(o.cache[H]){delete o.cache[H][E];E="";for(E in o.cache[H]){break}if(!E){o.removeData(F)}}}else{try{delete F[h]}catch(G){if(F.removeAttribute){F.removeAttribute(h)}}delete o.cache[H]}},queue:function(F,E,H){if(F){E=(E||"fx")+"queue";var G=o.data(F,E);if(!G||o.isArray(H)){G=o.data(F,E,o.makeArray(H))}else{if(H){G.push(H)}}}return G},dequeue:function(H,G){var E=o.queue(H,G),F=E.shift();if(!G||G==="fx"){F=E[0]}if(F!==g){F.call(H)}}});o.fn.extend({data:function(E,G){var H=E.split(".");H[1]=H[1]?"."+H[1]:"";if(G===g){var F=this.triggerHandler("getData"+H[1]+"!",[H[0]]);if(F===g&&this.length){F=o.data(this[0],E)}return F===g&&H[1]?this.data(H[0]):F}else{return this.trigger("setData"+H[1]+"!",[H[0],G]).each(function(){o.data(this,E,G)})}},removeData:function(E){return this.each(function(){o.removeData(this,E)})},queue:function(E,F){if(typeof E!=="string"){F=E;E="fx"}if(F===g){return o.queue(this[0],E)}return this.each(function(){var G=o.queue(this,E,F);if(E=="fx"&&G.length==1){G[0].call(this)}})},dequeue:function(E){return this.each(function(){o.dequeue(this,E)})}}); 13 | /* 14 | * Sizzle CSS Selector Engine - v0.9.3 15 | * Copyright 2009, The Dojo Foundation 16 | * Released under the MIT, BSD, and GPL Licenses. 17 | * More information: http://sizzlejs.com/ 18 | */ 19 | (function(){var R=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,L=0,H=Object.prototype.toString;var F=function(Y,U,ab,ac){ab=ab||[];U=U||document;if(U.nodeType!==1&&U.nodeType!==9){return[]}if(!Y||typeof Y!=="string"){return ab}var Z=[],W,af,ai,T,ad,V,X=true;R.lastIndex=0;while((W=R.exec(Y))!==null){Z.push(W[1]);if(W[2]){V=RegExp.rightContext;break}}if(Z.length>1&&M.exec(Y)){if(Z.length===2&&I.relative[Z[0]]){af=J(Z[0]+Z[1],U)}else{af=I.relative[Z[0]]?[U]:F(Z.shift(),U);while(Z.length){Y=Z.shift();if(I.relative[Y]){Y+=Z.shift()}af=J(Y,af)}}}else{var ae=ac?{expr:Z.pop(),set:E(ac)}:F.find(Z.pop(),Z.length===1&&U.parentNode?U.parentNode:U,Q(U));af=F.filter(ae.expr,ae.set);if(Z.length>0){ai=E(af)}else{X=false}while(Z.length){var ah=Z.pop(),ag=ah;if(!I.relative[ah]){ah=""}else{ag=Z.pop()}if(ag==null){ag=U}I.relative[ah](ai,ag,Q(U))}}if(!ai){ai=af}if(!ai){throw"Syntax error, unrecognized expression: "+(ah||Y)}if(H.call(ai)==="[object Array]"){if(!X){ab.push.apply(ab,ai)}else{if(U.nodeType===1){for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&(ai[aa]===true||ai[aa].nodeType===1&&K(U,ai[aa]))){ab.push(af[aa])}}}else{for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&ai[aa].nodeType===1){ab.push(af[aa])}}}}}else{E(ai,ab)}if(V){F(V,U,ab,ac);if(G){hasDuplicate=false;ab.sort(G);if(hasDuplicate){for(var aa=1;aa":function(Z,U,aa){var X=typeof U==="string";if(X&&!/\W/.test(U)){U=aa?U:U.toUpperCase();for(var V=0,T=Z.length;V=0)){if(!V){T.push(Y)}}else{if(V){U[X]=false}}}}return false},ID:function(T){return T[1].replace(/\\/g,"")},TAG:function(U,T){for(var V=0;T[V]===false;V++){}return T[V]&&Q(T[V])?U[1]:U[1].toUpperCase()},CHILD:function(T){if(T[1]=="nth"){var U=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(T[2]=="even"&&"2n"||T[2]=="odd"&&"2n+1"||!/\D/.test(T[2])&&"0n+"+T[2]||T[2]);T[2]=(U[1]+(U[2]||1))-0;T[3]=U[3]-0}T[0]=L++;return T},ATTR:function(X,U,V,T,Y,Z){var W=X[1].replace(/\\/g,"");if(!Z&&I.attrMap[W]){X[1]=I.attrMap[W]}if(X[2]==="~="){X[4]=" "+X[4]+" "}return X},PSEUDO:function(X,U,V,T,Y){if(X[1]==="not"){if(X[3].match(R).length>1||/^\w/.test(X[3])){X[3]=F(X[3],null,null,U)}else{var W=F.filter(X[3],U,V,true^Y);if(!V){T.push.apply(T,W)}return false}}else{if(I.match.POS.test(X[0])||I.match.CHILD.test(X[0])){return true}}return X},POS:function(T){T.unshift(true);return T}},filters:{enabled:function(T){return T.disabled===false&&T.type!=="hidden"},disabled:function(T){return T.disabled===true},checked:function(T){return T.checked===true},selected:function(T){T.parentNode.selectedIndex;return T.selected===true},parent:function(T){return !!T.firstChild},empty:function(T){return !T.firstChild},has:function(V,U,T){return !!F(T[3],V).length},header:function(T){return/h\d/i.test(T.nodeName)},text:function(T){return"text"===T.type},radio:function(T){return"radio"===T.type},checkbox:function(T){return"checkbox"===T.type},file:function(T){return"file"===T.type},password:function(T){return"password"===T.type},submit:function(T){return"submit"===T.type},image:function(T){return"image"===T.type},reset:function(T){return"reset"===T.type},button:function(T){return"button"===T.type||T.nodeName.toUpperCase()==="BUTTON"},input:function(T){return/input|select|textarea|button/i.test(T.nodeName)}},setFilters:{first:function(U,T){return T===0},last:function(V,U,T,W){return U===W.length-1},even:function(U,T){return T%2===0},odd:function(U,T){return T%2===1},lt:function(V,U,T){return UT[3]-0},nth:function(V,U,T){return T[3]-0==U},eq:function(V,U,T){return T[3]-0==U}},filter:{PSEUDO:function(Z,V,W,aa){var U=V[1],X=I.filters[U];if(X){return X(Z,W,V,aa)}else{if(U==="contains"){return(Z.textContent||Z.innerText||"").indexOf(V[3])>=0}else{if(U==="not"){var Y=V[3];for(var W=0,T=Y.length;W=0)}}},ID:function(U,T){return U.nodeType===1&&U.getAttribute("id")===T},TAG:function(U,T){return(T==="*"&&U.nodeType===1)||U.nodeName===T},CLASS:function(U,T){return(" "+(U.className||U.getAttribute("class"))+" ").indexOf(T)>-1},ATTR:function(Y,W){var V=W[1],T=I.attrHandle[V]?I.attrHandle[V](Y):Y[V]!=null?Y[V]:Y.getAttribute(V),Z=T+"",X=W[2],U=W[4];return T==null?X==="!=":X==="="?Z===U:X==="*="?Z.indexOf(U)>=0:X==="~="?(" "+Z+" ").indexOf(U)>=0:!U?Z&&T!==false:X==="!="?Z!=U:X==="^="?Z.indexOf(U)===0:X==="$="?Z.substr(Z.length-U.length)===U:X==="|="?Z===U||Z.substr(0,U.length+1)===U+"-":false},POS:function(X,U,V,Y){var T=U[2],W=I.setFilters[T];if(W){return W(X,V,U,Y)}}}};var M=I.match.POS;for(var O in I.match){I.match[O]=RegExp(I.match[O].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var E=function(U,T){U=Array.prototype.slice.call(U);if(T){T.push.apply(T,U);return T}return U};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(N){E=function(X,W){var U=W||[];if(H.call(X)==="[object Array]"){Array.prototype.push.apply(U,X)}else{if(typeof X.length==="number"){for(var V=0,T=X.length;V";var T=document.documentElement;T.insertBefore(U,T.firstChild);if(!!document.getElementById(V)){I.find.ID=function(X,Y,Z){if(typeof Y.getElementById!=="undefined"&&!Z){var W=Y.getElementById(X[1]);return W?W.id===X[1]||typeof W.getAttributeNode!=="undefined"&&W.getAttributeNode("id").nodeValue===X[1]?[W]:g:[]}};I.filter.ID=function(Y,W){var X=typeof Y.getAttributeNode!=="undefined"&&Y.getAttributeNode("id");return Y.nodeType===1&&X&&X.nodeValue===W}}T.removeChild(U)})();(function(){var T=document.createElement("div");T.appendChild(document.createComment(""));if(T.getElementsByTagName("*").length>0){I.find.TAG=function(U,Y){var X=Y.getElementsByTagName(U[1]);if(U[1]==="*"){var W=[];for(var V=0;X[V];V++){if(X[V].nodeType===1){W.push(X[V])}}X=W}return X}}T.innerHTML="";if(T.firstChild&&typeof T.firstChild.getAttribute!=="undefined"&&T.firstChild.getAttribute("href")!=="#"){I.attrHandle.href=function(U){return U.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var T=F,U=document.createElement("div");U.innerHTML="

    ";if(U.querySelectorAll&&U.querySelectorAll(".TEST").length===0){return}F=function(Y,X,V,W){X=X||document;if(!W&&X.nodeType===9&&!Q(X)){try{return E(X.querySelectorAll(Y),V)}catch(Z){}}return T(Y,X,V,W)};F.find=T.find;F.filter=T.filter;F.selectors=T.selectors;F.matches=T.matches})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){(function(){var T=document.createElement("div");T.innerHTML="
    ";if(T.getElementsByClassName("e").length===0){return}T.lastChild.className="e";if(T.getElementsByClassName("e").length===1){return}I.order.splice(1,0,"CLASS");I.find.CLASS=function(U,V,W){if(typeof V.getElementsByClassName!=="undefined"&&!W){return V.getElementsByClassName(U[1])}}})()}function P(U,Z,Y,ad,aa,ac){var ab=U=="previousSibling"&&!ac;for(var W=0,V=ad.length;W0){X=T;break}}}T=T[U]}ad[W]=X}}}var K=document.compareDocumentPosition?function(U,T){return U.compareDocumentPosition(T)&16}:function(U,T){return U!==T&&(U.contains?U.contains(T):true)};var Q=function(T){return T.nodeType===9&&T.documentElement.nodeName!=="HTML"||!!T.ownerDocument&&Q(T.ownerDocument)};var J=function(T,aa){var W=[],X="",Y,V=aa.nodeType?[aa]:aa;while((Y=I.match.PSEUDO.exec(T))){X+=Y[0];T=T.replace(I.match.PSEUDO,"")}T=I.relative[T]?T+"*":T;for(var Z=0,U=V.length;Z0||T.offsetHeight>0};F.selectors.filters.animated=function(T){return o.grep(o.timers,function(U){return T===U.elem}).length};o.multiFilter=function(V,T,U){if(U){V=":not("+V+")"}return F.matches(V,T)};o.dir=function(V,U){var T=[],W=V[U];while(W&&W!=document){if(W.nodeType==1){T.push(W)}W=W[U]}return T};o.nth=function(X,T,V,W){T=T||1;var U=0;for(;X;X=X[V]){if(X.nodeType==1&&++U==T){break}}return X};o.sibling=function(V,U){var T=[];for(;V;V=V.nextSibling){if(V.nodeType==1&&V!=U){T.push(V)}}return T};return;l.Sizzle=F})();o.event={add:function(I,F,H,K){if(I.nodeType==3||I.nodeType==8){return}if(I.setInterval&&I!=l){I=l}if(!H.guid){H.guid=this.guid++}if(K!==g){var G=H;H=this.proxy(G);H.data=K}var E=o.data(I,"events")||o.data(I,"events",{}),J=o.data(I,"handle")||o.data(I,"handle",function(){return typeof o!=="undefined"&&!o.event.triggered?o.event.handle.apply(arguments.callee.elem,arguments):g});J.elem=I;o.each(F.split(/\s+/),function(M,N){var O=N.split(".");N=O.shift();H.type=O.slice().sort().join(".");var L=E[N];if(o.event.specialAll[N]){o.event.specialAll[N].setup.call(I,K,O)}if(!L){L=E[N]={};if(!o.event.special[N]||o.event.special[N].setup.call(I,K,O)===false){if(I.addEventListener){I.addEventListener(N,J,false)}else{if(I.attachEvent){I.attachEvent("on"+N,J)}}}}L[H.guid]=H;o.event.global[N]=true});I=null},guid:1,global:{},remove:function(K,H,J){if(K.nodeType==3||K.nodeType==8){return}var G=o.data(K,"events"),F,E;if(G){if(H===g||(typeof H==="string"&&H.charAt(0)==".")){for(var I in G){this.remove(K,I+(H||""))}}else{if(H.type){J=H.handler;H=H.type}o.each(H.split(/\s+/),function(M,O){var Q=O.split(".");O=Q.shift();var N=RegExp("(^|\\.)"+Q.slice().sort().join(".*\\.")+"(\\.|$)");if(G[O]){if(J){delete G[O][J.guid]}else{for(var P in G[O]){if(N.test(G[O][P].type)){delete G[O][P]}}}if(o.event.specialAll[O]){o.event.specialAll[O].teardown.call(K,Q)}for(F in G[O]){break}if(!F){if(!o.event.special[O]||o.event.special[O].teardown.call(K,Q)===false){if(K.removeEventListener){K.removeEventListener(O,o.data(K,"handle"),false)}else{if(K.detachEvent){K.detachEvent("on"+O,o.data(K,"handle"))}}}F=null;delete G[O]}}})}for(F in G){break}if(!F){var L=o.data(K,"handle");if(L){L.elem=null}o.removeData(K,"events");o.removeData(K,"handle")}}},trigger:function(I,K,H,E){var G=I.type||I;if(!E){I=typeof I==="object"?I[h]?I:o.extend(o.Event(G),I):o.Event(G);if(G.indexOf("!")>=0){I.type=G=G.slice(0,-1);I.exclusive=true}if(!H){I.stopPropagation();if(this.global[G]){o.each(o.cache,function(){if(this.events&&this.events[G]){o.event.trigger(I,K,this.handle.elem)}})}}if(!H||H.nodeType==3||H.nodeType==8){return g}I.result=g;I.target=H;K=o.makeArray(K);K.unshift(I)}I.currentTarget=H;var J=o.data(H,"handle");if(J){J.apply(H,K)}if((!H[G]||(o.nodeName(H,"a")&&G=="click"))&&H["on"+G]&&H["on"+G].apply(H,K)===false){I.result=false}if(!E&&H[G]&&!I.isDefaultPrevented()&&!(o.nodeName(H,"a")&&G=="click")){this.triggered=true;try{H[G]()}catch(L){}}this.triggered=false;if(!I.isPropagationStopped()){var F=H.parentNode||H.ownerDocument;if(F){o.event.trigger(I,K,F,true)}}},handle:function(K){var J,E;K=arguments[0]=o.event.fix(K||l.event);K.currentTarget=this;var L=K.type.split(".");K.type=L.shift();J=!L.length&&!K.exclusive;var I=RegExp("(^|\\.)"+L.slice().sort().join(".*\\.")+"(\\.|$)");E=(o.data(this,"events")||{})[K.type];for(var G in E){var H=E[G];if(J||I.test(H.type)){K.handler=H;K.data=H.data;var F=H.apply(this,arguments);if(F!==g){K.result=F;if(F===false){K.preventDefault();K.stopPropagation()}}if(K.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(H){if(H[h]){return H}var F=H;H=o.Event(F);for(var G=this.props.length,J;G;){J=this.props[--G];H[J]=F[J]}if(!H.target){H.target=H.srcElement||document}if(H.target.nodeType==3){H.target=H.target.parentNode}if(!H.relatedTarget&&H.fromElement){H.relatedTarget=H.fromElement==H.target?H.toElement:H.fromElement}if(H.pageX==null&&H.clientX!=null){var I=document.documentElement,E=document.body;H.pageX=H.clientX+(I&&I.scrollLeft||E&&E.scrollLeft||0)-(I.clientLeft||0);H.pageY=H.clientY+(I&&I.scrollTop||E&&E.scrollTop||0)-(I.clientTop||0)}if(!H.which&&((H.charCode||H.charCode===0)?H.charCode:H.keyCode)){H.which=H.charCode||H.keyCode}if(!H.metaKey&&H.ctrlKey){H.metaKey=H.ctrlKey}if(!H.which&&H.button){H.which=(H.button&1?1:(H.button&2?3:(H.button&4?2:0)))}return H},proxy:function(F,E){E=E||function(){return F.apply(this,arguments)};E.guid=F.guid=F.guid||E.guid||this.guid++;return E},special:{ready:{setup:B,teardown:function(){}}},specialAll:{live:{setup:function(E,F){o.event.add(this,F[0],c)},teardown:function(G){if(G.length){var E=0,F=RegExp("(^|\\.)"+G[0]+"(\\.|$)");o.each((o.data(this,"events").live||{}),function(){if(F.test(this.type)){E++}});if(E<1){o.event.remove(this,G[0],c)}}}}}};o.Event=function(E){if(!this.preventDefault){return new o.Event(E)}if(E&&E.type){this.originalEvent=E;this.type=E.type}else{this.type=E}this.timeStamp=e();this[h]=true};function k(){return false}function u(){return true}o.Event.prototype={preventDefault:function(){this.isDefaultPrevented=u;var E=this.originalEvent;if(!E){return}if(E.preventDefault){E.preventDefault()}E.returnValue=false},stopPropagation:function(){this.isPropagationStopped=u;var E=this.originalEvent;if(!E){return}if(E.stopPropagation){E.stopPropagation()}E.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(F){var E=F.relatedTarget;while(E&&E!=this){try{E=E.parentNode}catch(G){E=this}}if(E!=this){F.type=F.data;o.event.handle.apply(this,arguments)}};o.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(F,E){o.event.special[E]={setup:function(){o.event.add(this,F,a,E)},teardown:function(){o.event.remove(this,F,a)}}});o.fn.extend({bind:function(F,G,E){return F=="unload"?this.one(F,G,E):this.each(function(){o.event.add(this,F,E||G,E&&G)})},one:function(G,H,F){var E=o.event.proxy(F||H,function(I){o(this).unbind(I,E);return(F||H).apply(this,arguments)});return this.each(function(){o.event.add(this,G,E,F&&H)})},unbind:function(F,E){return this.each(function(){o.event.remove(this,F,E)})},trigger:function(E,F){return this.each(function(){o.event.trigger(E,F,this)})},triggerHandler:function(E,G){if(this[0]){var F=o.Event(E);F.preventDefault();F.stopPropagation();o.event.trigger(F,G,this[0]);return F.result}},toggle:function(G){var E=arguments,F=1;while(F=0){var E=G.slice(I,G.length);G=G.slice(0,I)}var H="GET";if(J){if(o.isFunction(J)){K=J;J=null}else{if(typeof J==="object"){J=o.param(J);H="POST"}}}var F=this;o.ajax({url:G,type:H,dataType:"html",data:J,complete:function(M,L){if(L=="success"||L=="notmodified"){F.html(E?o("
    ").append(M.responseText.replace(//g,"")).find(E):M.responseText)}if(K){F.each(K,[M.responseText,L,M])}}});return this},serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?o.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password|search/i.test(this.type))}).map(function(E,F){var G=o(this).val();return G==null?null:o.isArray(G)?o.map(G,function(I,H){return{name:F.name,value:I}}):{name:F.name,value:G}}).get()}});o.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(E,F){o.fn[F]=function(G){return this.bind(F,G)}});var r=e();o.extend({get:function(E,G,H,F){if(o.isFunction(G)){H=G;G=null}return o.ajax({type:"GET",url:E,data:G,success:H,dataType:F})},getScript:function(E,F){return o.get(E,null,F,"script")},getJSON:function(E,F,G){return o.get(E,F,G,"json")},post:function(E,G,H,F){if(o.isFunction(G)){H=G;G={}}return o.ajax({type:"POST",url:E,data:G,success:H,dataType:F})},ajaxSetup:function(E){o.extend(o.ajaxSettings,E)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(M){M=o.extend(true,M,o.extend(true,{},o.ajaxSettings,M));var W,F=/=\?(&|$)/g,R,V,G=M.type.toUpperCase();if(M.data&&M.processData&&typeof M.data!=="string"){M.data=o.param(M.data)}if(M.dataType=="jsonp"){if(G=="GET"){if(!M.url.match(F)){M.url+=(M.url.match(/\?/)?"&":"?")+(M.jsonp||"callback")+"=?"}}else{if(!M.data||!M.data.match(F)){M.data=(M.data?M.data+"&":"")+(M.jsonp||"callback")+"=?"}}M.dataType="json"}if(M.dataType=="json"&&(M.data&&M.data.match(F)||M.url.match(F))){W="jsonp"+r++;if(M.data){M.data=(M.data+"").replace(F,"="+W+"$1")}M.url=M.url.replace(F,"="+W+"$1");M.dataType="script";l[W]=function(X){V=X;I();L();l[W]=g;try{delete l[W]}catch(Y){}if(H){H.removeChild(T)}}}if(M.dataType=="script"&&M.cache==null){M.cache=false}if(M.cache===false&&G=="GET"){var E=e();var U=M.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+E+"$2");M.url=U+((U==M.url)?(M.url.match(/\?/)?"&":"?")+"_="+E:"")}if(M.data&&G=="GET"){M.url+=(M.url.match(/\?/)?"&":"?")+M.data;M.data=null}if(M.global&&!o.active++){o.event.trigger("ajaxStart")}var Q=/^(\w+:)?\/\/([^\/?#]+)/.exec(M.url);if(M.dataType=="script"&&G=="GET"&&Q&&(Q[1]&&Q[1]!=location.protocol||Q[2]!=location.host)){var H=document.getElementsByTagName("head")[0];var T=document.createElement("script");T.src=M.url;if(M.scriptCharset){T.charset=M.scriptCharset}if(!W){var O=false;T.onload=T.onreadystatechange=function(){if(!O&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){O=true;I();L();T.onload=T.onreadystatechange=null;H.removeChild(T)}}}H.appendChild(T);return g}var K=false;var J=M.xhr();if(M.username){J.open(G,M.url,M.async,M.username,M.password)}else{J.open(G,M.url,M.async)}try{if(M.data){J.setRequestHeader("Content-Type",M.contentType)}if(M.ifModified){J.setRequestHeader("If-Modified-Since",o.lastModified[M.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}J.setRequestHeader("X-Requested-With","XMLHttpRequest");J.setRequestHeader("Accept",M.dataType&&M.accepts[M.dataType]?M.accepts[M.dataType]+", */*":M.accepts._default)}catch(S){}if(M.beforeSend&&M.beforeSend(J,M)===false){if(M.global&&!--o.active){o.event.trigger("ajaxStop")}J.abort();return false}if(M.global){o.event.trigger("ajaxSend",[J,M])}var N=function(X){if(J.readyState==0){if(P){clearInterval(P);P=null;if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}}else{if(!K&&J&&(J.readyState==4||X=="timeout")){K=true;if(P){clearInterval(P);P=null}R=X=="timeout"?"timeout":!o.httpSuccess(J)?"error":M.ifModified&&o.httpNotModified(J,M.url)?"notmodified":"success";if(R=="success"){try{V=o.httpData(J,M.dataType,M)}catch(Z){R="parsererror"}}if(R=="success"){var Y;try{Y=J.getResponseHeader("Last-Modified")}catch(Z){}if(M.ifModified&&Y){o.lastModified[M.url]=Y}if(!W){I()}}else{o.handleError(M,J,R)}L();if(X){J.abort()}if(M.async){J=null}}}};if(M.async){var P=setInterval(N,13);if(M.timeout>0){setTimeout(function(){if(J&&!K){N("timeout")}},M.timeout)}}try{J.send(M.data)}catch(S){o.handleError(M,J,null,S)}if(!M.async){N()}function I(){if(M.success){M.success(V,R)}if(M.global){o.event.trigger("ajaxSuccess",[J,M])}}function L(){if(M.complete){M.complete(J,R)}if(M.global){o.event.trigger("ajaxComplete",[J,M])}if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}return J},handleError:function(F,H,E,G){if(F.error){F.error(H,E,G)}if(F.global){o.event.trigger("ajaxError",[H,F,G])}},active:0,httpSuccess:function(F){try{return !F.status&&location.protocol=="file:"||(F.status>=200&&F.status<300)||F.status==304||F.status==1223}catch(E){}return false},httpNotModified:function(G,E){try{var H=G.getResponseHeader("Last-Modified");return G.status==304||H==o.lastModified[E]}catch(F){}return false},httpData:function(J,H,G){var F=J.getResponseHeader("content-type"),E=H=="xml"||!H&&F&&F.indexOf("xml")>=0,I=E?J.responseXML:J.responseText;if(E&&I.documentElement.tagName=="parsererror"){throw"parsererror"}if(G&&G.dataFilter){I=G.dataFilter(I,H)}if(typeof I==="string"){if(H=="script"){o.globalEval(I)}if(H=="json"){I=l["eval"]("("+I+")")}}return I},param:function(E){var G=[];function H(I,J){G[G.length]=encodeURIComponent(I)+"="+encodeURIComponent(J)}if(o.isArray(E)||E.jquery){o.each(E,function(){H(this.name,this.value)})}else{for(var F in E){if(o.isArray(E[F])){o.each(E[F],function(){H(F,this)})}else{H(F,o.isFunction(E[F])?E[F]():E[F])}}}return G.join("&").replace(/%20/g,"+")}});var m={},n,d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function t(F,E){var G={};o.each(d.concat.apply([],d.slice(0,E)),function(){G[this]=F});return G}o.fn.extend({show:function(J,L){if(J){return this.animate(t("show",3),J,L)}else{for(var H=0,F=this.length;H").appendTo("body");K=I.css("display");if(K==="none"){K="block"}I.remove();m[G]=K}o.data(this[H],"olddisplay",K)}}for(var H=0,F=this.length;H=0;H--){if(G[H].elem==this){if(E){G[H](true)}G.splice(H,1)}}});if(!E){this.dequeue()}return this}});o.each({slideDown:t("show",1),slideUp:t("hide",1),slideToggle:t("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(E,F){o.fn[E]=function(G,H){return this.animate(F,G,H)}});o.extend({speed:function(G,H,F){var E=typeof G==="object"?G:{complete:F||!F&&H||o.isFunction(G)&&G,duration:G,easing:F&&H||H&&!o.isFunction(H)&&H};E.duration=o.fx.off?0:typeof E.duration==="number"?E.duration:o.fx.speeds[E.duration]||o.fx.speeds._default;E.old=E.complete;E.complete=function(){if(E.queue!==false){o(this).dequeue()}if(o.isFunction(E.old)){E.old.call(this)}};return E},easing:{linear:function(G,H,E,F){return E+F*G},swing:function(G,H,E,F){return((-Math.cos(G*Math.PI)/2)+0.5)*F+E}},timers:[],fx:function(F,E,G){this.options=E;this.elem=F;this.prop=G;if(!E.orig){E.orig={}}}});o.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(o.fx.step[this.prop]||o.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(F){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var E=parseFloat(o.css(this.elem,this.prop,F));return E&&E>-10000?E:parseFloat(o.curCSS(this.elem,this.prop))||0},custom:function(I,H,G){this.startTime=e();this.start=I;this.end=H;this.unit=G||this.unit||"px";this.now=this.start;this.pos=this.state=0;var E=this;function F(J){return E.step(J)}F.elem=this.elem;if(F()&&o.timers.push(F)&&!n){n=setInterval(function(){var K=o.timers;for(var J=0;J=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var E=true;for(var F in this.options.curAnim){if(this.options.curAnim[F]!==true){E=false}}if(E){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(o.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){o(this.elem).hide()}if(this.options.hide||this.options.show){for(var I in this.options.curAnim){o.attr(this.elem.style,I,this.options.orig[I])}}this.options.complete.call(this.elem)}return false}else{var J=G-this.startTime;this.state=J/this.options.duration;this.pos=o.easing[this.options.easing||(o.easing.swing?"swing":"linear")](this.state,J,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};o.extend(o.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(E){o.attr(E.elem.style,"opacity",E.now)},_default:function(E){if(E.elem.style&&E.elem.style[E.prop]!=null){E.elem.style[E.prop]=E.now+E.unit}else{E.elem[E.prop]=E.now}}}});if(document.documentElement.getBoundingClientRect){o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}var G=this[0].getBoundingClientRect(),J=this[0].ownerDocument,F=J.body,E=J.documentElement,L=E.clientTop||F.clientTop||0,K=E.clientLeft||F.clientLeft||0,I=G.top+(self.pageYOffset||o.boxModel&&E.scrollTop||F.scrollTop)-L,H=G.left+(self.pageXOffset||o.boxModel&&E.scrollLeft||F.scrollLeft)-K;return{top:I,left:H}}}else{o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}o.offset.initialized||o.offset.initialize();var J=this[0],G=J.offsetParent,F=J,O=J.ownerDocument,M,H=O.documentElement,K=O.body,L=O.defaultView,E=L.getComputedStyle(J,null),N=J.offsetTop,I=J.offsetLeft;while((J=J.parentNode)&&J!==K&&J!==H){M=L.getComputedStyle(J,null);N-=J.scrollTop,I-=J.scrollLeft;if(J===G){N+=J.offsetTop,I+=J.offsetLeft;if(o.offset.doesNotAddBorder&&!(o.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(J.tagName))){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}F=G,G=J.offsetParent}if(o.offset.subtractsBorderForOverflowNotVisible&&M.overflow!=="visible"){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}E=M}if(E.position==="relative"||E.position==="static"){N+=K.offsetTop,I+=K.offsetLeft}if(E.position==="fixed"){N+=Math.max(H.scrollTop,K.scrollTop),I+=Math.max(H.scrollLeft,K.scrollLeft)}return{top:N,left:I}}}o.offset={initialize:function(){if(this.initialized){return}var L=document.body,F=document.createElement("div"),H,G,N,I,M,E,J=L.style.marginTop,K='
    ';M={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(E in M){F.style[E]=M[E]}F.innerHTML=K;L.insertBefore(F,L.firstChild);H=F.firstChild,G=H.firstChild,I=H.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(G.offsetTop!==5);this.doesAddBorderForTableAndCells=(I.offsetTop===5);H.style.overflow="hidden",H.style.position="relative";this.subtractsBorderForOverflowNotVisible=(G.offsetTop===-5);L.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(L.offsetTop===0);L.style.marginTop=J;L.removeChild(F);this.initialized=true},bodyOffset:function(E){o.offset.initialized||o.offset.initialize();var G=E.offsetTop,F=E.offsetLeft;if(o.offset.doesNotIncludeMarginInBodyOffset){G+=parseInt(o.curCSS(E,"marginTop",true),10)||0,F+=parseInt(o.curCSS(E,"marginLeft",true),10)||0}return{top:G,left:F}}};o.fn.extend({position:function(){var I=0,H=0,F;if(this[0]){var G=this.offsetParent(),J=this.offset(),E=/^body|html$/i.test(G[0].tagName)?{top:0,left:0}:G.offset();J.top-=j(this,"marginTop");J.left-=j(this,"marginLeft");E.top+=j(G,"borderTopWidth");E.left+=j(G,"borderLeftWidth");F={top:J.top-E.top,left:J.left-E.left}}return F},offsetParent:function(){var E=this[0].offsetParent||document.body;while(E&&(!/^body|html$/i.test(E.tagName)&&o.css(E,"position")=="static")){E=E.offsetParent}return o(E)}});o.each(["Left","Top"],function(F,E){var G="scroll"+E;o.fn[G]=function(H){if(!this[0]){return null}return H!==g?this.each(function(){this==l||this==document?l.scrollTo(!F?H:o(l).scrollLeft(),F?H:o(l).scrollTop()):this[G]=H}):this[0]==l||this[0]==document?self[F?"pageYOffset":"pageXOffset"]||o.boxModel&&document.documentElement[G]||document.body[G]:this[0][G]}});o.each(["Height","Width"],function(I,G){var E=I?"Left":"Top",H=I?"Right":"Bottom",F=G.toLowerCase();o.fn["inner"+G]=function(){return this[0]?o.css(this[0],F,false,"padding"):null};o.fn["outer"+G]=function(K){return this[0]?o.css(this[0],F,false,K?"margin":"border"):null};var J=G.toLowerCase();o.fn[J]=function(K){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+G]||document.body["client"+G]:this[0]==document?Math.max(document.documentElement["client"+G],document.body["scroll"+G],document.documentElement["scroll"+G],document.body["offset"+G],document.documentElement["offset"+G]):K===g?(this.length?o.css(this[0],J):null):this.css(J,typeof K==="string"?K:K+"px")}})})(); -------------------------------------------------------------------------------- /test/vendor/qunit.css: -------------------------------------------------------------------------------- 1 | /** Font Family and Sizes */ 2 | 3 | #qunit-tests, #qunit-header, .qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 4 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 5 | } 6 | 7 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 8 | #qunit-tests { font-size: smaller; } 9 | 10 | 11 | /** Resets */ 12 | 13 | #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { 14 | margin: 0; 15 | padding: 0; 16 | } 17 | 18 | 19 | /** Header */ 20 | 21 | #qunit-header, .qunit-header { 22 | padding: 0.5em 0 0.5em 1em; 23 | 24 | color: #8699a4; 25 | background-color: #0d3349; 26 | 27 | font-size: 1.5em; 28 | line-height: 1em; 29 | font-weight: normal; 30 | 31 | border-radius: 15px 15px 0 0; 32 | -moz-border-radius: 15px 15px 0 0; 33 | -webkit-border-top-right-radius: 15px; 34 | -webkit-border-top-left-radius: 15px; 35 | } 36 | 37 | #qunit-header a { 38 | text-decoration: none; 39 | color: #c2ccd1; 40 | } 41 | 42 | #qunit-header a:hover, 43 | #qunit-header a:focus { 44 | color: #fff; 45 | } 46 | 47 | #qunit-banner { 48 | height: 5px; 49 | } 50 | 51 | #qunit-testrunner-toolbar { 52 | padding: 0em 0 0.5em 2em; 53 | } 54 | 55 | #qunit-userAgent { 56 | padding: 0.5em 0 0.5em 2.5em; 57 | background-color: #2b81af; 58 | color: #fff; 59 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 60 | } 61 | 62 | 63 | /** Tests: Pass/Fail */ 64 | 65 | #qunit-tests { 66 | list-style-position: inside; 67 | } 68 | 69 | #qunit-tests li { 70 | padding: 0.4em 0.5em 0.4em 2.5em; 71 | border-bottom: 1px solid #fff; 72 | list-style-position: inside; 73 | } 74 | 75 | #qunit-tests li strong { 76 | cursor: pointer; 77 | } 78 | 79 | #qunit-tests ol { 80 | margin-top: 0.5em; 81 | padding: 0.5em; 82 | 83 | background-color: #fff; 84 | 85 | border-radius: 15px; 86 | -moz-border-radius: 15px; 87 | -webkit-border-radius: 15px; 88 | 89 | box-shadow: inset 0px 2px 13px #999; 90 | -moz-box-shadow: inset 0px 2px 13px #999; 91 | -webkit-box-shadow: inset 0px 2px 13px #999; 92 | } 93 | 94 | #qunit-tests table { 95 | border-collapse: collapse; 96 | margin-top: .2em; 97 | } 98 | 99 | #qunit-tests th { 100 | text-align: right; 101 | vertical-align: top; 102 | padding: 0 .5em 0 0; 103 | } 104 | 105 | #qunit-tests td { 106 | vertical-align: top; 107 | } 108 | 109 | #qunit-tests pre { 110 | margin: 0; 111 | white-space: pre-wrap; 112 | word-wrap: break-word; 113 | } 114 | 115 | #qunit-tests del { 116 | background-color: #e0f2be; 117 | color: #374e0c; 118 | text-decoration: none; 119 | } 120 | 121 | #qunit-tests ins { 122 | background-color: #ffcaca; 123 | color: #500; 124 | text-decoration: none; 125 | } 126 | 127 | /*** Test Counts */ 128 | 129 | #qunit-tests b.counts { color: black; } 130 | #qunit-tests b.passed { color: #5E740B; } 131 | #qunit-tests b.failed { color: #710909; } 132 | 133 | #qunit-tests li li { 134 | margin: 0.5em; 135 | padding: 0.4em 0.5em 0.4em 0.5em; 136 | background-color: #fff; 137 | border-bottom: none; 138 | list-style-position: inside; 139 | } 140 | 141 | /*** Passing Styles */ 142 | 143 | #qunit-tests li li.pass { 144 | color: #5E740B; 145 | background-color: #fff; 146 | border-left: 26px solid #C6E746; 147 | } 148 | 149 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 150 | #qunit-tests .pass .test-name { color: #366097; } 151 | 152 | #qunit-tests .pass .test-actual, 153 | #qunit-tests .pass .test-expected { color: #999999; } 154 | 155 | #qunit-banner.qunit-pass { background-color: #C6E746; } 156 | 157 | /*** Failing Styles */ 158 | 159 | #qunit-tests li li.fail { 160 | color: #710909; 161 | background-color: #fff; 162 | border-left: 26px solid #EE5757; 163 | } 164 | 165 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 166 | #qunit-tests .fail .test-name, 167 | #qunit-tests .fail .module-name { color: #000000; } 168 | 169 | #qunit-tests .fail .test-actual { color: #EE5757; } 170 | #qunit-tests .fail .test-expected { color: green; } 171 | 172 | #qunit-banner.qunit-fail, 173 | #qunit-testrunner-toolbar { background-color: #EE5757; } 174 | 175 | 176 | /** Footer */ 177 | 178 | #qunit-testresult { 179 | padding: 0.5em 0.5em 0.5em 2.5em; 180 | 181 | color: #2b81af; 182 | background-color: #D2E0E6; 183 | 184 | border-radius: 0 0 15px 15px; 185 | -moz-border-radius: 0 0 15px 15px; 186 | -webkit-border-bottom-right-radius: 15px; 187 | -webkit-border-bottom-left-radius: 15px; 188 | } 189 | 190 | /** Fixture */ 191 | 192 | #qunit-fixture { 193 | position: absolute; 194 | top: -10000px; 195 | left: -10000px; 196 | } -------------------------------------------------------------------------------- /test/vendor/underscore.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.2.1 2 | // (c) 2011 Jeremy Ashkenas, DocumentCloud Inc. 3 | // Underscore is freely distributable under the MIT license. 4 | // Portions of Underscore are inspired or borrowed from Prototype, 5 | // Oliver Steele's Functional, and John Resig's Micro-Templating. 6 | // For all details and documentation: 7 | // http://documentcloud.github.com/underscore 8 | 9 | (function() { 10 | 11 | // Baseline setup 12 | // -------------- 13 | 14 | // Establish the root object, `window` in the browser, or `global` on the server. 15 | var root = this; 16 | 17 | // Save the previous value of the `_` variable. 18 | var previousUnderscore = root._; 19 | 20 | // Establish the object that gets returned to break out of a loop iteration. 21 | var breaker = {}; 22 | 23 | // Save bytes in the minified (but not gzipped) version: 24 | var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; 25 | 26 | // Create quick reference variables for speed access to core prototypes. 27 | var slice = ArrayProto.slice, 28 | unshift = ArrayProto.unshift, 29 | toString = ObjProto.toString, 30 | hasOwnProperty = ObjProto.hasOwnProperty; 31 | 32 | // All **ECMAScript 5** native function implementations that we hope to use 33 | // are declared here. 34 | var 35 | nativeForEach = ArrayProto.forEach, 36 | nativeMap = ArrayProto.map, 37 | nativeReduce = ArrayProto.reduce, 38 | nativeReduceRight = ArrayProto.reduceRight, 39 | nativeFilter = ArrayProto.filter, 40 | nativeEvery = ArrayProto.every, 41 | nativeSome = ArrayProto.some, 42 | nativeIndexOf = ArrayProto.indexOf, 43 | nativeLastIndexOf = ArrayProto.lastIndexOf, 44 | nativeIsArray = Array.isArray, 45 | nativeKeys = Object.keys, 46 | nativeBind = FuncProto.bind; 47 | 48 | // Create a safe reference to the Underscore object for use below. 49 | var _ = function(obj) { return new wrapper(obj); }; 50 | 51 | // Export the Underscore object for **Node.js** and **"CommonJS"**, with 52 | // backwards-compatibility for the old `require()` API. If we're not in 53 | // CommonJS, add `_` to the global object. 54 | if (typeof exports !== 'undefined') { 55 | if (typeof module !== 'undefined' && module.exports) { 56 | exports = module.exports = _; 57 | } 58 | exports._ = _; 59 | } else if (typeof define === 'function' && define.amd) { 60 | // Register as a named module with AMD. 61 | define('underscore', function() { 62 | return _; 63 | }); 64 | } else { 65 | // Exported as a string, for Closure Compiler "advanced" mode. 66 | root['_'] = _; 67 | } 68 | 69 | // Current version. 70 | _.VERSION = '1.2.1'; 71 | 72 | // Collection Functions 73 | // -------------------- 74 | 75 | // The cornerstone, an `each` implementation, aka `forEach`. 76 | // Handles objects with the built-in `forEach`, arrays, and raw objects. 77 | // Delegates to **ECMAScript 5**'s native `forEach` if available. 78 | var each = _.each = _.forEach = function(obj, iterator, context) { 79 | if (obj == null) return; 80 | if (nativeForEach && obj.forEach === nativeForEach) { 81 | obj.forEach(iterator, context); 82 | } else if (obj.length === +obj.length) { 83 | for (var i = 0, l = obj.length; i < l; i++) { 84 | if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return; 85 | } 86 | } else { 87 | for (var key in obj) { 88 | if (hasOwnProperty.call(obj, key)) { 89 | if (iterator.call(context, obj[key], key, obj) === breaker) return; 90 | } 91 | } 92 | } 93 | }; 94 | 95 | // Return the results of applying the iterator to each element. 96 | // Delegates to **ECMAScript 5**'s native `map` if available. 97 | _.map = function(obj, iterator, context) { 98 | var results = []; 99 | if (obj == null) return results; 100 | if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); 101 | each(obj, function(value, index, list) { 102 | results[results.length] = iterator.call(context, value, index, list); 103 | }); 104 | return results; 105 | }; 106 | 107 | // **Reduce** builds up a single result from a list of values, aka `inject`, 108 | // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. 109 | _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { 110 | var initial = memo !== void 0; 111 | if (obj == null) obj = []; 112 | if (nativeReduce && obj.reduce === nativeReduce) { 113 | if (context) iterator = _.bind(iterator, context); 114 | return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); 115 | } 116 | each(obj, function(value, index, list) { 117 | if (!initial) { 118 | memo = value; 119 | initial = true; 120 | } else { 121 | memo = iterator.call(context, memo, value, index, list); 122 | } 123 | }); 124 | if (!initial) throw new TypeError("Reduce of empty array with no initial value"); 125 | return memo; 126 | }; 127 | 128 | // The right-associative version of reduce, also known as `foldr`. 129 | // Delegates to **ECMAScript 5**'s native `reduceRight` if available. 130 | _.reduceRight = _.foldr = function(obj, iterator, memo, context) { 131 | if (obj == null) obj = []; 132 | if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { 133 | if (context) iterator = _.bind(iterator, context); 134 | return memo !== void 0 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); 135 | } 136 | var reversed = (_.isArray(obj) ? obj.slice() : _.toArray(obj)).reverse(); 137 | return _.reduce(reversed, iterator, memo, context); 138 | }; 139 | 140 | // Return the first value which passes a truth test. Aliased as `detect`. 141 | _.find = _.detect = function(obj, iterator, context) { 142 | var result; 143 | any(obj, function(value, index, list) { 144 | if (iterator.call(context, value, index, list)) { 145 | result = value; 146 | return true; 147 | } 148 | }); 149 | return result; 150 | }; 151 | 152 | // Return all the elements that pass a truth test. 153 | // Delegates to **ECMAScript 5**'s native `filter` if available. 154 | // Aliased as `select`. 155 | _.filter = _.select = function(obj, iterator, context) { 156 | var results = []; 157 | if (obj == null) return results; 158 | if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); 159 | each(obj, function(value, index, list) { 160 | if (iterator.call(context, value, index, list)) results[results.length] = value; 161 | }); 162 | return results; 163 | }; 164 | 165 | // Return all the elements for which a truth test fails. 166 | _.reject = function(obj, iterator, context) { 167 | var results = []; 168 | if (obj == null) return results; 169 | each(obj, function(value, index, list) { 170 | if (!iterator.call(context, value, index, list)) results[results.length] = value; 171 | }); 172 | return results; 173 | }; 174 | 175 | // Determine whether all of the elements match a truth test. 176 | // Delegates to **ECMAScript 5**'s native `every` if available. 177 | // Aliased as `all`. 178 | _.every = _.all = function(obj, iterator, context) { 179 | var result = true; 180 | if (obj == null) return result; 181 | if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); 182 | each(obj, function(value, index, list) { 183 | if (!(result = result && iterator.call(context, value, index, list))) return breaker; 184 | }); 185 | return result; 186 | }; 187 | 188 | // Determine if at least one element in the object matches a truth test. 189 | // Delegates to **ECMAScript 5**'s native `some` if available. 190 | // Aliased as `any`. 191 | var any = _.some = _.any = function(obj, iterator, context) { 192 | iterator = iterator || _.identity; 193 | var result = false; 194 | if (obj == null) return result; 195 | if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); 196 | each(obj, function(value, index, list) { 197 | if (result |= iterator.call(context, value, index, list)) return breaker; 198 | }); 199 | return !!result; 200 | }; 201 | 202 | // Determine if a given value is included in the array or object using `===`. 203 | // Aliased as `contains`. 204 | _.include = _.contains = function(obj, target) { 205 | var found = false; 206 | if (obj == null) return found; 207 | if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; 208 | found = any(obj, function(value) { 209 | return value === target; 210 | }); 211 | return found; 212 | }; 213 | 214 | // Invoke a method (with arguments) on every item in a collection. 215 | _.invoke = function(obj, method) { 216 | var args = slice.call(arguments, 2); 217 | return _.map(obj, function(value) { 218 | return (method.call ? method || value : value[method]).apply(value, args); 219 | }); 220 | }; 221 | 222 | // Convenience version of a common use case of `map`: fetching a property. 223 | _.pluck = function(obj, key) { 224 | return _.map(obj, function(value){ return value[key]; }); 225 | }; 226 | 227 | // Return the maximum element or (element-based computation). 228 | _.max = function(obj, iterator, context) { 229 | if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj); 230 | if (!iterator && _.isEmpty(obj)) return -Infinity; 231 | var result = {computed : -Infinity}; 232 | each(obj, function(value, index, list) { 233 | var computed = iterator ? iterator.call(context, value, index, list) : value; 234 | computed >= result.computed && (result = {value : value, computed : computed}); 235 | }); 236 | return result.value; 237 | }; 238 | 239 | // Return the minimum element (or element-based computation). 240 | _.min = function(obj, iterator, context) { 241 | if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj); 242 | if (!iterator && _.isEmpty(obj)) return Infinity; 243 | var result = {computed : Infinity}; 244 | each(obj, function(value, index, list) { 245 | var computed = iterator ? iterator.call(context, value, index, list) : value; 246 | computed < result.computed && (result = {value : value, computed : computed}); 247 | }); 248 | return result.value; 249 | }; 250 | 251 | // Shuffle an array. 252 | _.shuffle = function(obj) { 253 | var shuffled = [], rand; 254 | each(obj, function(value, index, list) { 255 | if (index == 0) { 256 | shuffled[0] = value; 257 | } else { 258 | rand = Math.floor(Math.random() * (index + 1)); 259 | shuffled[index] = shuffled[rand]; 260 | shuffled[rand] = value; 261 | } 262 | }); 263 | return shuffled; 264 | }; 265 | 266 | // Sort the object's values by a criterion produced by an iterator. 267 | _.sortBy = function(obj, iterator, context) { 268 | return _.pluck(_.map(obj, function(value, index, list) { 269 | return { 270 | value : value, 271 | criteria : iterator.call(context, value, index, list) 272 | }; 273 | }).sort(function(left, right) { 274 | var a = left.criteria, b = right.criteria; 275 | return a < b ? -1 : a > b ? 1 : 0; 276 | }), 'value'); 277 | }; 278 | 279 | // Groups the object's values by a criterion. Pass either a string attribute 280 | // to group by, or a function that returns the criterion. 281 | _.groupBy = function(obj, val) { 282 | var result = {}; 283 | var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; }; 284 | each(obj, function(value, index) { 285 | var key = iterator(value, index); 286 | (result[key] || (result[key] = [])).push(value); 287 | }); 288 | return result; 289 | }; 290 | 291 | // Use a comparator function to figure out at what index an object should 292 | // be inserted so as to maintain order. Uses binary search. 293 | _.sortedIndex = function(array, obj, iterator) { 294 | iterator || (iterator = _.identity); 295 | var low = 0, high = array.length; 296 | while (low < high) { 297 | var mid = (low + high) >> 1; 298 | iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid; 299 | } 300 | return low; 301 | }; 302 | 303 | // Safely convert anything iterable into a real, live array. 304 | _.toArray = function(iterable) { 305 | if (!iterable) return []; 306 | if (iterable.toArray) return iterable.toArray(); 307 | if (_.isArray(iterable)) return slice.call(iterable); 308 | if (_.isArguments(iterable)) return slice.call(iterable); 309 | return _.values(iterable); 310 | }; 311 | 312 | // Return the number of elements in an object. 313 | _.size = function(obj) { 314 | return _.toArray(obj).length; 315 | }; 316 | 317 | // Array Functions 318 | // --------------- 319 | 320 | // Get the first element of an array. Passing **n** will return the first N 321 | // values in the array. Aliased as `head`. The **guard** check allows it to work 322 | // with `_.map`. 323 | _.first = _.head = function(array, n, guard) { 324 | return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; 325 | }; 326 | 327 | // Returns everything but the last entry of the array. Especcialy useful on 328 | // the arguments object. Passing **n** will return all the values in 329 | // the array, excluding the last N. The **guard** check allows it to work with 330 | // `_.map`. 331 | _.initial = function(array, n, guard) { 332 | return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); 333 | }; 334 | 335 | // Get the last element of an array. Passing **n** will return the last N 336 | // values in the array. The **guard** check allows it to work with `_.map`. 337 | _.last = function(array, n, guard) { 338 | return (n != null) && !guard ? slice.call(array, array.length - n) : array[array.length - 1]; 339 | }; 340 | 341 | // Returns everything but the first entry of the array. Aliased as `tail`. 342 | // Especially useful on the arguments object. Passing an **index** will return 343 | // the rest of the values in the array from that index onward. The **guard** 344 | // check allows it to work with `_.map`. 345 | _.rest = _.tail = function(array, index, guard) { 346 | return slice.call(array, (index == null) || guard ? 1 : index); 347 | }; 348 | 349 | // Trim out all falsy values from an array. 350 | _.compact = function(array) { 351 | return _.filter(array, function(value){ return !!value; }); 352 | }; 353 | 354 | // Return a completely flattened version of an array. 355 | _.flatten = function(array, shallow) { 356 | return _.reduce(array, function(memo, value) { 357 | if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value)); 358 | memo[memo.length] = value; 359 | return memo; 360 | }, []); 361 | }; 362 | 363 | // Return a version of the array that does not contain the specified value(s). 364 | _.without = function(array) { 365 | return _.difference(array, slice.call(arguments, 1)); 366 | }; 367 | 368 | // Produce a duplicate-free version of the array. If the array has already 369 | // been sorted, you have the option of using a faster algorithm. 370 | // Aliased as `unique`. 371 | _.uniq = _.unique = function(array, isSorted, iterator) { 372 | var initial = iterator ? _.map(array, iterator) : array; 373 | var result = []; 374 | _.reduce(initial, function(memo, el, i) { 375 | if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) { 376 | memo[memo.length] = el; 377 | result[result.length] = array[i]; 378 | } 379 | return memo; 380 | }, []); 381 | return result; 382 | }; 383 | 384 | // Produce an array that contains the union: each distinct element from all of 385 | // the passed-in arrays. 386 | _.union = function() { 387 | return _.uniq(_.flatten(arguments, true)); 388 | }; 389 | 390 | // Produce an array that contains every item shared between all the 391 | // passed-in arrays. (Aliased as "intersect" for back-compat.) 392 | _.intersection = _.intersect = function(array) { 393 | var rest = slice.call(arguments, 1); 394 | return _.filter(_.uniq(array), function(item) { 395 | return _.every(rest, function(other) { 396 | return _.indexOf(other, item) >= 0; 397 | }); 398 | }); 399 | }; 400 | 401 | // Take the difference between one array and another. 402 | // Only the elements present in just the first array will remain. 403 | _.difference = function(array, other) { 404 | return _.filter(array, function(value){ return !_.include(other, value); }); 405 | }; 406 | 407 | // Zip together multiple lists into a single array -- elements that share 408 | // an index go together. 409 | _.zip = function() { 410 | var args = slice.call(arguments); 411 | var length = _.max(_.pluck(args, 'length')); 412 | var results = new Array(length); 413 | for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i); 414 | return results; 415 | }; 416 | 417 | // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), 418 | // we need this function. Return the position of the first occurrence of an 419 | // item in an array, or -1 if the item is not included in the array. 420 | // Delegates to **ECMAScript 5**'s native `indexOf` if available. 421 | // If the array is large and already in sort order, pass `true` 422 | // for **isSorted** to use binary search. 423 | _.indexOf = function(array, item, isSorted) { 424 | if (array == null) return -1; 425 | var i, l; 426 | if (isSorted) { 427 | i = _.sortedIndex(array, item); 428 | return array[i] === item ? i : -1; 429 | } 430 | if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item); 431 | for (i = 0, l = array.length; i < l; i++) if (array[i] === item) return i; 432 | return -1; 433 | }; 434 | 435 | // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. 436 | _.lastIndexOf = function(array, item) { 437 | if (array == null) return -1; 438 | if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item); 439 | var i = array.length; 440 | while (i--) if (array[i] === item) return i; 441 | return -1; 442 | }; 443 | 444 | // Generate an integer Array containing an arithmetic progression. A port of 445 | // the native Python `range()` function. See 446 | // [the Python documentation](http://docs.python.org/library/functions.html#range). 447 | _.range = function(start, stop, step) { 448 | if (arguments.length <= 1) { 449 | stop = start || 0; 450 | start = 0; 451 | } 452 | step = arguments[2] || 1; 453 | 454 | var len = Math.max(Math.ceil((stop - start) / step), 0); 455 | var idx = 0; 456 | var range = new Array(len); 457 | 458 | while(idx < len) { 459 | range[idx++] = start; 460 | start += step; 461 | } 462 | 463 | return range; 464 | }; 465 | 466 | // Function (ahem) Functions 467 | // ------------------ 468 | 469 | // Reusable constructor function for prototype setting. 470 | var ctor = function(){}; 471 | 472 | // Create a function bound to a given object (assigning `this`, and arguments, 473 | // optionally). Binding with arguments is also known as `curry`. 474 | // Delegates to **ECMAScript 5**'s native `Function.bind` if available. 475 | // We check for `func.bind` first, to fail fast when `func` is undefined. 476 | _.bind = function bind(func, context) { 477 | var bound, args; 478 | if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); 479 | if (!_.isFunction(func)) throw new TypeError; 480 | args = slice.call(arguments, 2); 481 | return bound = function() { 482 | if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); 483 | ctor.prototype = func.prototype; 484 | var self = new ctor; 485 | var result = func.apply(self, args.concat(slice.call(arguments))); 486 | if (Object(result) === result) return result; 487 | return self; 488 | }; 489 | }; 490 | 491 | // Bind all of an object's methods to that object. Useful for ensuring that 492 | // all callbacks defined on an object belong to it. 493 | _.bindAll = function(obj) { 494 | var funcs = slice.call(arguments, 1); 495 | if (funcs.length == 0) funcs = _.functions(obj); 496 | each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); 497 | return obj; 498 | }; 499 | 500 | // Memoize an expensive function by storing its results. 501 | _.memoize = function(func, hasher) { 502 | var memo = {}; 503 | hasher || (hasher = _.identity); 504 | return function() { 505 | var key = hasher.apply(this, arguments); 506 | return hasOwnProperty.call(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); 507 | }; 508 | }; 509 | 510 | // Delays a function for the given number of milliseconds, and then calls 511 | // it with the arguments supplied. 512 | _.delay = function(func, wait) { 513 | var args = slice.call(arguments, 2); 514 | return setTimeout(function(){ return func.apply(func, args); }, wait); 515 | }; 516 | 517 | // Defers a function, scheduling it to run after the current call stack has 518 | // cleared. 519 | _.defer = function(func) { 520 | return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); 521 | }; 522 | 523 | // Returns a function, that, when invoked, will only be triggered at most once 524 | // during a given window of time. 525 | _.throttle = function(func, wait) { 526 | var context, args, timeout, throttling, more; 527 | var whenDone = _.debounce(function(){ more = throttling = false; }, wait); 528 | return function() { 529 | context = this; args = arguments; 530 | var later = function() { 531 | timeout = null; 532 | if (more) func.apply(context, args); 533 | whenDone(); 534 | }; 535 | if (!timeout) timeout = setTimeout(later, wait); 536 | if (throttling) { 537 | more = true; 538 | } else { 539 | func.apply(context, args); 540 | } 541 | whenDone(); 542 | throttling = true; 543 | }; 544 | }; 545 | 546 | // Returns a function, that, as long as it continues to be invoked, will not 547 | // be triggered. The function will be called after it stops being called for 548 | // N milliseconds. 549 | _.debounce = function(func, wait) { 550 | var timeout; 551 | return function() { 552 | var context = this, args = arguments; 553 | var later = function() { 554 | timeout = null; 555 | func.apply(context, args); 556 | }; 557 | clearTimeout(timeout); 558 | timeout = setTimeout(later, wait); 559 | }; 560 | }; 561 | 562 | // Returns a function that will be executed at most one time, no matter how 563 | // often you call it. Useful for lazy initialization. 564 | _.once = function(func) { 565 | var ran = false, memo; 566 | return function() { 567 | if (ran) return memo; 568 | ran = true; 569 | return memo = func.apply(this, arguments); 570 | }; 571 | }; 572 | 573 | // Returns the first function passed as an argument to the second, 574 | // allowing you to adjust arguments, run code before and after, and 575 | // conditionally execute the original function. 576 | _.wrap = function(func, wrapper) { 577 | return function() { 578 | var args = [func].concat(slice.call(arguments)); 579 | return wrapper.apply(this, args); 580 | }; 581 | }; 582 | 583 | // Returns a function that is the composition of a list of functions, each 584 | // consuming the return value of the function that follows. 585 | _.compose = function() { 586 | var funcs = slice.call(arguments); 587 | return function() { 588 | var args = slice.call(arguments); 589 | for (var i = funcs.length - 1; i >= 0; i--) { 590 | args = [funcs[i].apply(this, args)]; 591 | } 592 | return args[0]; 593 | }; 594 | }; 595 | 596 | // Returns a function that will only be executed after being called N times. 597 | _.after = function(times, func) { 598 | return function() { 599 | if (--times < 1) { return func.apply(this, arguments); } 600 | }; 601 | }; 602 | 603 | // Object Functions 604 | // ---------------- 605 | 606 | // Retrieve the names of an object's properties. 607 | // Delegates to **ECMAScript 5**'s native `Object.keys` 608 | _.keys = nativeKeys || function(obj) { 609 | if (obj !== Object(obj)) throw new TypeError('Invalid object'); 610 | var keys = []; 611 | for (var key in obj) if (hasOwnProperty.call(obj, key)) keys[keys.length] = key; 612 | return keys; 613 | }; 614 | 615 | // Retrieve the values of an object's properties. 616 | _.values = function(obj) { 617 | return _.map(obj, _.identity); 618 | }; 619 | 620 | // Return a sorted list of the function names available on the object. 621 | // Aliased as `methods` 622 | _.functions = _.methods = function(obj) { 623 | var names = []; 624 | for (var key in obj) { 625 | if (_.isFunction(obj[key])) names.push(key); 626 | } 627 | return names.sort(); 628 | }; 629 | 630 | // Extend a given object with all the properties in passed-in object(s). 631 | _.extend = function(obj) { 632 | each(slice.call(arguments, 1), function(source) { 633 | for (var prop in source) { 634 | if (source[prop] !== void 0) obj[prop] = source[prop]; 635 | } 636 | }); 637 | return obj; 638 | }; 639 | 640 | // Fill in a given object with default properties. 641 | _.defaults = function(obj) { 642 | each(slice.call(arguments, 1), function(source) { 643 | for (var prop in source) { 644 | if (obj[prop] == null) obj[prop] = source[prop]; 645 | } 646 | }); 647 | return obj; 648 | }; 649 | 650 | // Create a (shallow-cloned) duplicate of an object. 651 | _.clone = function(obj) { 652 | if (!_.isObject(obj)) return obj; 653 | return _.isArray(obj) ? obj.slice() : _.extend({}, obj); 654 | }; 655 | 656 | // Invokes interceptor with the obj, and then returns obj. 657 | // The primary purpose of this method is to "tap into" a method chain, in 658 | // order to perform operations on intermediate results within the chain. 659 | _.tap = function(obj, interceptor) { 660 | interceptor(obj); 661 | return obj; 662 | }; 663 | 664 | // Internal recursive comparison function. 665 | function eq(a, b, stack) { 666 | // Identical objects are equal. `0 === -0`, but they aren't identical. 667 | // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal. 668 | if (a === b) return a !== 0 || 1 / a == 1 / b; 669 | // A strict comparison is necessary because `null == undefined`. 670 | if ((a == null) || (b == null)) return a === b; 671 | // Unwrap any wrapped objects. 672 | if (a._chain) a = a._wrapped; 673 | if (b._chain) b = b._wrapped; 674 | // Invoke a custom `isEqual` method if one is provided. 675 | if (_.isFunction(a.isEqual)) return a.isEqual(b); 676 | if (_.isFunction(b.isEqual)) return b.isEqual(a); 677 | // Compare object types. 678 | var typeA = typeof a; 679 | if (typeA != typeof b) return false; 680 | // Optimization; ensure that both values are truthy or falsy. 681 | if (!a != !b) return false; 682 | // `NaN` values are equal. 683 | if (_.isNaN(a)) return _.isNaN(b); 684 | // Compare string objects by value. 685 | var isStringA = _.isString(a), isStringB = _.isString(b); 686 | if (isStringA || isStringB) return isStringA && isStringB && String(a) == String(b); 687 | // Compare number objects by value. 688 | var isNumberA = _.isNumber(a), isNumberB = _.isNumber(b); 689 | if (isNumberA || isNumberB) return isNumberA && isNumberB && +a == +b; 690 | // Compare boolean objects by value. The value of `true` is 1; the value of `false` is 0. 691 | var isBooleanA = _.isBoolean(a), isBooleanB = _.isBoolean(b); 692 | if (isBooleanA || isBooleanB) return isBooleanA && isBooleanB && +a == +b; 693 | // Compare dates by their millisecond values. 694 | var isDateA = _.isDate(a), isDateB = _.isDate(b); 695 | if (isDateA || isDateB) return isDateA && isDateB && a.getTime() == b.getTime(); 696 | // Compare RegExps by their source patterns and flags. 697 | var isRegExpA = _.isRegExp(a), isRegExpB = _.isRegExp(b); 698 | if (isRegExpA || isRegExpB) { 699 | // Ensure commutative equality for RegExps. 700 | return isRegExpA && isRegExpB && 701 | a.source == b.source && 702 | a.global == b.global && 703 | a.multiline == b.multiline && 704 | a.ignoreCase == b.ignoreCase; 705 | } 706 | // Ensure that both values are objects. 707 | if (typeA != 'object') return false; 708 | // Arrays or Arraylikes with different lengths are not equal. 709 | if (a.length !== b.length) return false; 710 | // Objects with different constructors are not equal. 711 | if (a.constructor !== b.constructor) return false; 712 | // Assume equality for cyclic structures. The algorithm for detecting cyclic 713 | // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. 714 | var length = stack.length; 715 | while (length--) { 716 | // Linear search. Performance is inversely proportional to the number of 717 | // unique nested structures. 718 | if (stack[length] == a) return true; 719 | } 720 | // Add the first object to the stack of traversed objects. 721 | stack.push(a); 722 | var size = 0, result = true; 723 | // Deep compare objects. 724 | for (var key in a) { 725 | if (hasOwnProperty.call(a, key)) { 726 | // Count the expected number of properties. 727 | size++; 728 | // Deep compare each member. 729 | if (!(result = hasOwnProperty.call(b, key) && eq(a[key], b[key], stack))) break; 730 | } 731 | } 732 | // Ensure that both objects contain the same number of properties. 733 | if (result) { 734 | for (key in b) { 735 | if (hasOwnProperty.call(b, key) && !(size--)) break; 736 | } 737 | result = !size; 738 | } 739 | // Remove the first object from the stack of traversed objects. 740 | stack.pop(); 741 | return result; 742 | } 743 | 744 | // Perform a deep comparison to check if two objects are equal. 745 | _.isEqual = function(a, b) { 746 | return eq(a, b, []); 747 | }; 748 | 749 | // Is a given array, string, or object empty? 750 | // An "empty" object has no enumerable own-properties. 751 | _.isEmpty = function(obj) { 752 | if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; 753 | for (var key in obj) if (hasOwnProperty.call(obj, key)) return false; 754 | return true; 755 | }; 756 | 757 | // Is a given value a DOM element? 758 | _.isElement = function(obj) { 759 | return !!(obj && obj.nodeType == 1); 760 | }; 761 | 762 | // Is a given value an array? 763 | // Delegates to ECMA5's native Array.isArray 764 | _.isArray = nativeIsArray || function(obj) { 765 | return toString.call(obj) == '[object Array]'; 766 | }; 767 | 768 | // Is a given variable an object? 769 | _.isObject = function(obj) { 770 | return obj === Object(obj); 771 | }; 772 | 773 | // Is a given variable an arguments object? 774 | if (toString.call(arguments) == '[object Arguments]') { 775 | _.isArguments = function(obj) { 776 | return toString.call(obj) == '[object Arguments]'; 777 | }; 778 | } else { 779 | _.isArguments = function(obj) { 780 | return !!(obj && hasOwnProperty.call(obj, 'callee')); 781 | }; 782 | } 783 | 784 | // Is a given value a function? 785 | _.isFunction = function(obj) { 786 | return toString.call(obj) == '[object Function]'; 787 | }; 788 | 789 | // Is a given value a string? 790 | _.isString = function(obj) { 791 | return toString.call(obj) == '[object String]'; 792 | }; 793 | 794 | // Is a given value a number? 795 | _.isNumber = function(obj) { 796 | return toString.call(obj) == '[object Number]'; 797 | }; 798 | 799 | // Is the given value `NaN`? 800 | _.isNaN = function(obj) { 801 | // `NaN` is the only value for which `===` is not reflexive. 802 | return obj !== obj; 803 | }; 804 | 805 | // Is a given value a boolean? 806 | _.isBoolean = function(obj) { 807 | return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; 808 | }; 809 | 810 | // Is a given value a date? 811 | _.isDate = function(obj) { 812 | return toString.call(obj) == '[object Date]'; 813 | }; 814 | 815 | // Is the given value a regular expression? 816 | _.isRegExp = function(obj) { 817 | return toString.call(obj) == '[object RegExp]'; 818 | }; 819 | 820 | // Is a given value equal to null? 821 | _.isNull = function(obj) { 822 | return obj === null; 823 | }; 824 | 825 | // Is a given variable undefined? 826 | _.isUndefined = function(obj) { 827 | return obj === void 0; 828 | }; 829 | 830 | // Utility Functions 831 | // ----------------- 832 | 833 | // Run Underscore.js in *noConflict* mode, returning the `_` variable to its 834 | // previous owner. Returns a reference to the Underscore object. 835 | _.noConflict = function() { 836 | root._ = previousUnderscore; 837 | return this; 838 | }; 839 | 840 | // Keep the identity function around for default iterators. 841 | _.identity = function(value) { 842 | return value; 843 | }; 844 | 845 | // Run a function **n** times. 846 | _.times = function (n, iterator, context) { 847 | for (var i = 0; i < n; i++) iterator.call(context, i); 848 | }; 849 | 850 | // Escape a string for HTML interpolation. 851 | _.escape = function(string) { 852 | return (''+string).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/'); 853 | }; 854 | 855 | // Add your own custom functions to the Underscore object, ensuring that 856 | // they're correctly added to the OOP wrapper as well. 857 | _.mixin = function(obj) { 858 | each(_.functions(obj), function(name){ 859 | addToWrapper(name, _[name] = obj[name]); 860 | }); 861 | }; 862 | 863 | // Generate a unique integer id (unique within the entire client session). 864 | // Useful for temporary DOM ids. 865 | var idCounter = 0; 866 | _.uniqueId = function(prefix) { 867 | var id = idCounter++; 868 | return prefix ? prefix + id : id; 869 | }; 870 | 871 | // By default, Underscore uses ERB-style template delimiters, change the 872 | // following template settings to use alternative delimiters. 873 | _.templateSettings = { 874 | evaluate : /<%([\s\S]+?)%>/g, 875 | interpolate : /<%=([\s\S]+?)%>/g, 876 | escape : /<%-([\s\S]+?)%>/g 877 | }; 878 | 879 | // JavaScript micro-templating, similar to John Resig's implementation. 880 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 881 | // and correctly escapes quotes within interpolated code. 882 | _.template = function(str, data) { 883 | var c = _.templateSettings; 884 | var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' + 885 | 'with(obj||{}){__p.push(\'' + 886 | str.replace(/\\/g, '\\\\') 887 | .replace(/'/g, "\\'") 888 | .replace(c.escape, function(match, code) { 889 | return "',_.escape(" + code.replace(/\\'/g, "'") + "),'"; 890 | }) 891 | .replace(c.interpolate, function(match, code) { 892 | return "'," + code.replace(/\\'/g, "'") + ",'"; 893 | }) 894 | .replace(c.evaluate || null, function(match, code) { 895 | return "');" + code.replace(/\\'/g, "'") 896 | .replace(/[\r\n\t]/g, ' ') + "__p.push('"; 897 | }) 898 | .replace(/\r/g, '\\r') 899 | .replace(/\n/g, '\\n') 900 | .replace(/\t/g, '\\t') 901 | + "');}return __p.join('');"; 902 | var func = new Function('obj', '_', tmpl); 903 | return data ? func(data, _) : function(data) { return func(data, _) }; 904 | }; 905 | 906 | // The OOP Wrapper 907 | // --------------- 908 | 909 | // If Underscore is called as a function, it returns a wrapped object that 910 | // can be used OO-style. This wrapper holds altered versions of all the 911 | // underscore functions. Wrapped objects may be chained. 912 | var wrapper = function(obj) { this._wrapped = obj; }; 913 | 914 | // Expose `wrapper.prototype` as `_.prototype` 915 | _.prototype = wrapper.prototype; 916 | 917 | // Helper function to continue chaining intermediate results. 918 | var result = function(obj, chain) { 919 | return chain ? _(obj).chain() : obj; 920 | }; 921 | 922 | // A method to easily add functions to the OOP wrapper. 923 | var addToWrapper = function(name, func) { 924 | wrapper.prototype[name] = function() { 925 | var args = slice.call(arguments); 926 | unshift.call(args, this._wrapped); 927 | return result(func.apply(_, args), this._chain); 928 | }; 929 | }; 930 | 931 | // Add all of the Underscore functions to the wrapper object. 932 | _.mixin(_); 933 | 934 | // Add all mutator Array functions to the wrapper. 935 | each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { 936 | var method = ArrayProto[name]; 937 | wrapper.prototype[name] = function() { 938 | method.apply(this._wrapped, arguments); 939 | return result(this._wrapped, this._chain); 940 | }; 941 | }); 942 | 943 | // Add all accessor Array functions to the wrapper. 944 | each(['concat', 'join', 'slice'], function(name) { 945 | var method = ArrayProto[name]; 946 | wrapper.prototype[name] = function() { 947 | return result(method.apply(this._wrapped, arguments), this._chain); 948 | }; 949 | }); 950 | 951 | // Start chaining a wrapped Underscore object. 952 | wrapper.prototype.chain = function() { 953 | this._chain = true; 954 | return this; 955 | }; 956 | 957 | // Extracts the result from a wrapped and chained object. 958 | wrapper.prototype.value = function() { 959 | return this._wrapped; 960 | }; 961 | 962 | }).call(this); 963 | --------------------------------------------------------------------------------