├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── demo ├── demo.css ├── demo.html └── sprite.png ├── karma.conf.js ├── package-lock.json ├── package.json ├── site ├── back_pattern.png ├── fork_me.png ├── index.html ├── prism.css ├── prism.js ├── sprite.png ├── src │ ├── jquery.collapse.js │ ├── jquery.collapse_cookie_storage.js │ └── jquery.collapse_storage.js └── style.css ├── src ├── jquery.collapse.js ├── jquery.collapse_cookie_storage.js └── jquery.collapse_storage.js ├── test ├── jquery.collapse.cookie_storage_test.coffee ├── jquery.collapse_storage_test.coffee └── jquery.collapse_test.coffee └── vendor ├── jquery-1.9.1.js └── json2.js /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.diff 3 | *.patch 4 | .DS_Store 5 | .settings 6 | spec/*_spec.js 7 | TODO 8 | node_modules 9 | coverage 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "stable" 4 | addons: 5 | chrome: stable 6 | script: 7 | - npm install 8 | - npm run test 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Daniel Stocks 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jQuery Collapse 2 | 3 | A lightweight and flexible jQuery plugin that allows you to collapse content. A feature also known as 'progressive disclosure'. 4 | 5 | jQuery Collapse is tested against the latest version of jQuery but requires at least jQuery 1.7.0. 6 | 7 | [![Build Status](https://travis-ci.org/danielstocks/jQuery-Collapse.png?branch=master)](https://travis-ci.org/danielstocks/jQuery-Collapse) 8 | [![Code Climate](https://codeclimate.com/github/danielstocks/jQuery-Collapse.png)](https://codeclimate.com/github/danielstocks/jQuery-Collapse) 9 | [![Coveralls](https://img.shields.io/coveralls/danielstocks/jQuery-Collapse/master.svg)](https://coveralls.io/github/danielstocks/jQuery-Collapse?branch=master) 10 | 11 | ## Features 12 | 13 | - [WAI ARIA](http://dev.opera.com/articles/view/introduction-to-wai-aria/) compliant 14 | - Lightweight (~1.2kb minified and gzipped) 15 | - Cross Browser compliant (Tested in >= IE6, Firefox, Safari, Chrome, Opera) 16 | - **Accordion** behaviour can be enabled. 17 | - **Persistence** to remember open sections on page reload! 18 | 19 | 20 | ### Demo 21 | 22 | A demo showcasing all the features of the plugin can be found at 'demo/demo.html' in this repository. 23 | 24 | ## Usage 25 | 26 | Load jQuery and the jQuery Collapse plugin into your document: 27 | 28 | ```html 29 | 30 | 31 | ``` 32 | 33 | Here's some sample HTML markup: 34 | 35 | ```html 36 |
37 |

Fruits

38 | 43 |

Info

44 |
45 |

You can use any container you like (in this case a div element)

46 |
47 |
48 | ``` 49 | 50 | That's it! The *data-collapse* attribute will automatically trigger the script. 51 | 52 | ### Open/Collapse section by default 53 | 54 | The standard behaviour is to collapse all the sections on page load. 55 | If you want to show a section to the user on page load you can 56 | achieve this by adding an 'open' class to the section heading 57 | 58 | ```html 59 |
60 |

I'm open by default

61 |

Yay

62 |
63 | ``` 64 | 65 | ### Open all sections 66 | 67 | You can open or close sections by utilizing events. Assume you have the following markup: 68 | 69 | ```html 70 |
71 |

Section 1

72 |

I'm first

73 |

Section 2

74 |

I'm second/p> 75 |

76 | ``` 77 | You can now trigger events on the elements you want to affect. For instance: 78 | 79 | ```js 80 | $("#test").trigger("open") // Open all sections 81 | $("#test").trigger("close") // Close all sections 82 | $("#test h2 a").first().trigger("open") // Open first section 83 | ``` 84 | 85 | For further information, please refer to the [events](#events) documentation. 86 | 87 | ## JavaScript usage 88 | 89 | If you'd rather omit the 'data-collapse' attribute in the HTML and load the plugin via jQuery, you can: 90 | 91 | ```js 92 | $("#demo").collapse({ 93 | // options... 94 | }); 95 | ``` 96 | 97 | If you don't want to use the jQuery ($) wrapper, you can also access the 98 | plugin with *vanilla* JavaScript: 99 | 100 | ```js 101 | new jQueryCollapse($("#demo"), { 102 | // options... 103 | }); 104 | ``` 105 | 106 | ### Using custom markup 107 | 108 | By default the plugin will look for groups of two elements. 109 | In real life™ your markup may vary and you'll need to customize how the 110 | plugin interprets it. For example 111 | 112 | ```html 113 |
114 |
115 |

Summary

116 |
details...
117 |
118 |
119 |

Summary

120 |
details...
121 |
122 |
123 | ``` 124 | 125 | In order for the plugin to understand the above markup, we can pass a 'query' 126 | option specifying where to find the header/summary element of a section: 127 | 128 | ```js 129 | new jQueryCollapse($("#demo"), { 130 | query: 'div h2' 131 | }); 132 | ``` 133 | 134 | #### External markup example 135 | 136 | You can also just use an arbitrary link on a page to collapse\expand a section: 137 | 138 | ```html 139 | Toggle section 140 |
141 |

Summary

142 |
details...
143 |
144 | ``` 145 | 146 | Then attach an event handler to your link and make use of jQuery Collapse events to toggle the section: 147 | 148 | ```js 149 | $("#toggle").click(function() { 150 | $(this.hash).trigger("toggle"); 151 | }); 152 | ``` 153 | 154 | #### Custom click query 155 | 156 | Sometimes you want to customize what element inside the collapse summary that should trigger the open/close action. Consider the following markup: 157 | 158 | ```html 159 |
160 |
161 | Google.com info 162 |
163 |
164 |

Find stuff on google

165 |
166 |
167 | Twitter.com info 168 |
169 |
170 |

Tweet stuff on twitter

171 |
172 |
173 | ``` 174 | 175 | Now use the clickQuery option to trigger the action only when the span is clicked 176 | 177 | ```js 178 | $("#custom-click-query").collapse({ 179 | clickQuery: "span.toggle" 180 | }); 181 | ``` 182 | 183 | 184 | ## Accordion 185 | 186 | To activate the accordion behaviour set 'accordion' as the value of the 'data-collapse' attribute: 187 | 188 | ```html 189 |
190 | ... 191 |
192 | ``` 193 | 194 | 195 | ## Persistence 196 | 197 | By default, if the user reloads the page all the sections will be closed. 198 | If you want previously collapsed sections to stay open you can add 'persist' to the data-collapse attribute: 199 | 200 | ```html 201 |
202 | ... 203 |
204 | ``` 205 | And include the storage module in your document *after* the other 206 | scripts. 207 | 208 | ```html 209 | 210 | ``` 211 | 212 | As in the example above, the target element (#demo) **will require an ID** in order for the 213 | persistence to work. 214 | 215 | You can combine the accordion and persistence options by adding 216 | both values to the data-collapse attribute: 217 | 218 | ```html 219 |
220 | ... 221 |
222 | ``` 223 | 224 | jQuery Collapse uses HTML5 localStorage if available, otherwise it 225 | will attempt to use cookies (read about IE support below). If that also fails, it will degrade 226 | to work but without any persistence. 227 | 228 | ### Internet Explorer =< 7 Support 229 | 230 | For IE 6-7 you'll need to include the cookie storage and JSON2 libraries 231 | for the cookie storage support to work properly: 232 | 233 | ```html 234 | 238 | ``` 239 | 240 | ## API Documentation 241 | 242 | Here are the exposed options and events that you can play around with 243 | using JavaScript. Enjoy. 244 | 245 | ### Options 246 | 247 | You can pass the following options when initializing 248 | the plugin with JavaScript. 249 | 250 | * **open** (function) : Custom function for opening section (default: function(){ this.show() }) 251 | * **close** (function) : Custom function for collapsing section (default: function(){ this.hide() }) 252 | * **accordion** (bool) : Enable accordion behaviour by setting this option to 'true' 253 | * **persist** (bool) : Enable persistence between page loads by setting this option to 'true' 254 | * **query** (string) : Please refer to to [using custom markup](#using-custom-markup) 255 | * **clickQuery** (string): Please refer to [custom click query](#custom-click-query) 256 | 257 | Example usage of options: 258 | 259 | ```js 260 | // Initializing collapse plugin 261 | // with custom open/close methods, 262 | // persistence plugin and accordion behaviour 263 | $("#demo").collapse({ 264 | open: function() { 265 | // The context of 'this' is applied to 266 | // the collapsed details in a jQuery wrapper 267 | this.slideDown(100); 268 | }, 269 | close: function() { 270 | this.slideUp(100); 271 | }, 272 | accordion: true, 273 | persist: true 274 | }); 275 | ``` 276 | 277 | ### Events 278 | 279 | #### Binding events 280 | 281 | You can listen for the **opened** and **closed** events on a collapsed collection. 282 | 283 | ```js 284 | 285 | $("#demo").bind("opened", function(e, section) { 286 | console.log(section, " was opened"); 287 | }); 288 | 289 | $("#demo").bind("closed", function(e, section) { 290 | console.log(section, " was closed"); 291 | }); 292 | ``` 293 | 294 | #### Triggering events 295 | 296 | You can manually trigger an **open**, **close** or **toggle** event to change the state of a section: 297 | 298 | ```js 299 | $("#demo").trigger("open") // open all sections 300 | $("#demo").trigger("close") // close all sections 301 | $("#demo h2 a").last().trigger("toggle") // toggle last section 302 | ``` 303 | 304 | When a section changes state, it will trigger either an "opened" or "closed" event in return, depending on it's new state. 305 | 306 | ### API methods 307 | 308 | If you're using vanilla JavaScript to instantiate the plugin, you'll get 309 | direct access to the **open**, **close** and **toggle** methods. 310 | 311 | ```js 312 | var demo = new jQueryCollapse($("#demo")); // Initializing plugin 313 | demo.open(); // Open all sections 314 | demo.close(); // Close all sections 315 | demo.open(0); // Open first section 316 | demo.open(1); // Open second section 317 | demo.close(0); // Close first section 318 | demo.toggle(1); // Toggle second section 319 | ``` 320 | 321 | ## Contributing 322 | 323 | Did you find a bug? Do you want to introduce a feature? Here's what to do (in the following order) 324 | 325 | * Clone this repository, and run `npm install` 326 | * Write a test case 327 | * Watch it fail (red light) 328 | * Fix bug / introduce feature 329 | * Watch it pass (green light) 330 | * Refactor / Perfectionize! 331 | * Submit a pull request on Github and wait patiently... 332 | * Rejoice! 333 | 334 | ### A note about testing 335 | 336 | To run the tests simply type "npm test". The [Karma](http://karma-runner.github.io/) test runner will open Chrome and Firefox and run the tests. 337 | 338 | The tests are written in a BDD fashion using CoffeeScript and can be found in the test directory. 339 | 340 | The test suite uses [Mocha](http://mochajs.org/) (test framework), [Chai](http://chaijs.com/) (exceptions) and [Sinon](http://sinonjs.org/) (stubs and mocks). 341 | -------------------------------------------------------------------------------- /demo/demo.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #ddd; 3 | margin: 0 auto; 4 | max-width: 1024px; 5 | font: normal 14px/1.2 'Helvetica Neue', 'Arial' 6 | } 7 | 8 | .fork { 9 | border:0; 10 | position:absolute; 11 | top:0; 12 | right:0; 13 | } 14 | 15 | .col { 16 | width: 320px; 17 | float: left; 18 | margin-right:32px; 19 | margin-bottom: 16px; 20 | } 21 | .c3 { 22 | margin-right:0; 23 | } 24 | 25 | h1 { 26 | border-bottom: 1px solid #333; 27 | font-size: 32px; 28 | color: #fff; 29 | padding-bottom: 12px; 30 | text-shadow: 0px 0px 2px rgba(0,0,0,0.6); 31 | } 32 | 33 | h2 { 34 | margin: 10px 0; 35 | color: #000; 36 | font-size: 18px; 37 | text-shadow: 1px 1px 2px #fff; 38 | } 39 | 40 | h3 { 41 | margin: 0; 42 | background-color: rgb(228,10,85); 43 | background-image: linear-gradient(bottom, rgb(228,10,85) 14%, rgb(255,36,111) 57%); 44 | background-image: -o-linear-gradient(bottom, rgb(228,10,85) 14%, rgb(255,36,111) 57%); 45 | background-image: -moz-linear-gradient(bottom, rgb(228,10,85) 14%, rgb(255,36,111) 57%); 46 | background-image: -webkit-linear-gradient(bottom, rgb(228,10,85) 14%, rgb(255,36,111) 57%); 47 | background-image: -ms-linear-gradient(bottom, rgb(228,10,85) 14%, rgb(255,36,111) 57%); 48 | } 49 | 50 | h3 a { 51 | background: url("sprite.png") 15px 13px no-repeat; 52 | display: block; 53 | padding: 10px; 54 | padding-left: 32px; 55 | margin: 0; 56 | color: #fff; 57 | text-decoration: none; 58 | font-weight: normal; 59 | border-bottom: 1px solid rgba(128, 10, 85, 0.5); 60 | text-shadow: 1px 1px 1px rgb(128,10,85); 61 | } 62 | h3:hover { background: rgb(228,10,85); } 63 | h3.open { background: rgb(255,70,120); } 64 | h3.open a { background-position: 13px -25px; } 65 | h3 + div { padding: 10px; } 66 | h2 + div, 67 | .example { 68 | background: #fff; 69 | overflow: hidden; 70 | border-radius: 3px; 71 | -moz-border-radius: 3px; 72 | -webkit-border-radius: 3px; 73 | margin-bottom: 20px; 74 | } 75 | 76 | /* Pre hide sections with JavaScript on 77 | --- */ 78 | h3+div { 79 | display: none; 80 | } 81 | 82 | /* CSS3 Animation example 83 | --- */ 84 | #css3-animated-example h3 + div { 85 | height: 0px; 86 | padding: 0px; 87 | overflow: hidden; 88 | background: #000; 89 | display: block!important; 90 | -webkit-transform: translateZ(0); 91 | -webkit-transition: all 0.3s ease; 92 | moz-transition: all 0.3s ease; 93 | -o-transition: all 0.3s ease; 94 | -ms-transition:all 0.3s ease; 95 | transition: all 0.3s ease; 96 | } 97 | #css3-animated-example .content { 98 | padding: 10px; 99 | } 100 | 101 | #css3-animated-example h3.open + div { 102 | height: auto; 103 | background: #aaffff; 104 | } 105 | 106 | /* Event example 107 | --- */ 108 | pre#event-log { 109 | background: #fafacc; 110 | padding: 10px; 111 | display: block; 112 | } 113 | 114 | /* Responsive design 115 | --- */ 116 | @media screen and (max-width: 1056px) { 117 | body { 118 | width: 672px; 119 | } 120 | .c2 { 121 | margin-right: 0; 122 | margin-bottom: 0; 123 | } 124 | } 125 | 126 | .test { background: #ccc; padding: 10px; border-bottom: 1px solid #aaa;} 127 | .test + div { background: #fff; padding: 10px; } 128 | 129 | @media 130 | only screen and (max-width: 704px), 131 | only screen and (-webkit-min-device-pixel-ratio : 1.5), 132 | only screen and (min-device-pixel-ratio : 1.5) { 133 | body { 134 | width: 320px; 135 | } 136 | .c1 { 137 | margin-right: 0; 138 | margin-bottom: 0; 139 | } 140 | h1 { 141 | font-size: 28px; 142 | text-shadow: 0px 0px 1px rgba(0,0,0,0.7); 143 | } 144 | } 145 | 146 | @media 147 | only screen and (-webkit-min-device-pixel-ratio : 1.5), 148 | only screen and (min-device-pixel-ratio : 1.5) { 149 | body { 150 | padding: 0 12px; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /demo/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | jQuery Collapse Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Fork me on GitHub 18 | 19 |

jQuery Collapse Demo

20 |
21 |

Default Example

22 |
23 |

Fruits

24 |
I like fruits. This link should work
25 |

Info

26 |
This is some information
27 |
28 | 29 |

Accordion Example

30 |
31 |

Accordions

32 |
Are fun and they make pleasent noises
33 |

Fruits

34 |
I like fruits
35 |

Info

36 |
This is some information
37 |

Yeah!!

38 |
eh
39 |
40 | 41 |

Persistence Example

42 |
43 |

These

44 |
Well hello there
45 |

Sections

46 |
yabayaba
47 |

Should be

48 |
might be.
49 |

Persistant!!

50 |
eh
51 |
52 |
53 | 54 |
55 | 56 |

Custom show & hide

57 |
58 |

Hello

59 |
60 |

Hello Sir.

61 |

I'm sliding

62 |
63 |

Anarachy in the UK

64 |
I like tea
65 |

Indeed

66 |
This is some information
67 |
68 | 78 | 79 | 80 | 81 |

w/ CSS3 Animations

82 |
83 |

Hello

84 |
85 |
86 |

This example simply sets a class attribute to the details and let's an 87 | external stylesheet toggle the collapsed state.

88 |

Hello Sir.

89 |

I'm sliding

90 |
91 |
92 |

Friend

93 |
94 |
95 |

This example simply sets a class attribute to the details and let's an 96 | external stylesheet toggle the collapsed state.

97 |

Hello Sir.

98 |
99 |
100 |

Foe

101 |
102 |
103 |

This example simply sets a class attribute to the details and let's an 104 | external stylesheet toggle the collapsed state.

105 |
106 |
107 |
108 | 121 | 122 | 123 | 124 | 125 |

Custom markup example

126 |
127 |
128 |

Hello

129 |
130 |
131 |

This example simply sets a class attribute to the details and let's an 132 | external stylesheet toggle the collapsed state.

133 |

Hello Sir.

134 |

I'm sliding

135 |
136 |
137 |
138 |
139 |

Friend

140 |
141 |
142 |

This example simply sets a class attribute to the details and let's an 143 | external stylesheet toggle the collapsed state.

144 |

Hello Sir.

145 |
146 |
147 |
148 |
149 |

Foe

150 |
151 |
152 |

This example simply sets a class attribute to the details and let's an 153 | external stylesheet toggle the collapsed state.

154 |
155 |
156 |
157 |
158 | 163 | 164 | 165 | 166 |
167 |
168 | Google.com info 169 |
170 |
171 |

Find stuff on google

172 |
173 |
174 | Twitter.com info 175 |
176 |
177 |

Tweet stuff on twitter

178 |
179 |
180 | 185 | 186 | 187 |
188 |
189 | 190 | 191 |

Binding & Triggering events

192 |
event log
193 |
194 |

Section 1

195 |
196 |

This example simply sets a class attribute to the details and let's an 197 | external stylesheet toggle the collapsed state.

198 |

Hello Sir.

199 |

I'm sliding

200 |
201 |

Section 2

202 |
203 |

This example simply sets a class attribute to the details and let's an 204 | external stylesheet toggle the collapsed state.

205 |

Hello Sir.

206 |
207 |

Section 3

208 |
209 |

This example simply sets a class attribute to the details and let's an 210 | external stylesheet toggle the collapsed state.

211 |
212 |
213 | 214 | 215 | 216 | 239 |

240 | 241 |

Open section by default

242 |
243 |

I'm open by default

244 |
Yay
245 |

I'm not open

246 |
booo :(
247 |
248 | 249 | 250 |

Nested markup example

251 |
252 |

Fruits and Vegetables

253 |
I like fruits. This link should work
254 |

Info

255 |
This is some information
256 |
257 | 258 |
259 | 260 | 261 | -------------------------------------------------------------------------------- /demo/sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielstocks/jQuery-Collapse/cafcce1ec71ad1fabc7302d7953ef4307db30d1b/demo/sprite.png -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Mon Oct 07 2013 10:25:42 GMT+0200 (CEST) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | // base path, that will be used to resolve files and exclude 7 | basePath: '', 8 | 9 | preprocessors: { 10 | '**/*.coffee': ['coffee'], 11 | '**/src/*.js': ['coverage'], 12 | }, 13 | 14 | // frameworks to use 15 | frameworks: ['mocha', 'sinon-chai'], 16 | 17 | // list of files / patterns to load in the browser 18 | files: ['vendor/*.js', 'src/*.js', 'test/*.coffee'], 19 | 20 | // list of files to exclude 21 | exclude: [], 22 | 23 | // test results reporter to use 24 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 25 | reporters: ['dots', 'coverage', 'coveralls'], 26 | 27 | coverageReporter: { 28 | reporters: [ 29 | // reporters not supporting the `file` property 30 | {type: 'html', subdir: 'report-html'}, 31 | {type: 'lcov', subdir: 'report-lcov'}, 32 | ], 33 | }, 34 | 35 | // web server port 36 | port: 9876, 37 | 38 | // enable / disable colors in the output (reporters and logs) 39 | colors: true, 40 | 41 | // level of logging 42 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 43 | logLevel: config.LOG_INFO, 44 | 45 | // enable / disable watching file and executing tests whenever any file changes 46 | autoWatch: true, 47 | 48 | // Start these browsers, currently available: 49 | // - Chrome 50 | // - ChromeCanary 51 | // - Firefox 52 | // - Opera 53 | // - Safari (only Mac) 54 | // - PhantomJS 55 | // - IE (only Windows) 56 | 57 | browsers: ['ChromeHeadlessNoSandbox'], 58 | 59 | customLaunchers: { 60 | ChromeHeadlessNoSandbox: { 61 | base: 'ChromeHeadless', 62 | flags: ['--no-sandbox'] 63 | } 64 | }, 65 | 66 | 67 | // If browser does not capture in given timeout [ms], kill it 68 | captureTimeout: 60000, 69 | 70 | // Continuous Integration mode 71 | // if true, it capture browsers, run tests and exit 72 | singleRun: false, 73 | }); 74 | }; 75 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-collapse", 3 | "version": "1.1.2", 4 | "description": "A lightweight (~1kb) jQuery plugin that enables expanding and collapsing content", 5 | "devDependencies": { 6 | "chai": "^4.2.0", 7 | "coveralls": "^3.0.9", 8 | "jquery": "^3.4.1", 9 | "karma": "^4.4.1", 10 | "karma-chrome-launcher": "^3.1.0", 11 | "karma-coffee-preprocessor": "^1.0.1", 12 | "karma-coverage": "^2.0.1", 13 | "karma-coveralls": "^2.1.0", 14 | "karma-mocha": "^1.3.0", 15 | "karma-sinon-chai": "^2.0.2", 16 | "mocha": "^7.0.1", 17 | "should": "^13.2.3", 18 | "sinon": "^8.1.1", 19 | "sinon-chai": "^3.4.0" 20 | }, 21 | "scripts": { 22 | "test": "./node_modules/karma/bin/karma start --single-run" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/danielstocks/jQuery-Collapse.git" 27 | }, 28 | "author": "Daniel Stocks", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/danielstocks/jQuery-Collapse/issues" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /site/back_pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielstocks/jQuery-Collapse/cafcce1ec71ad1fabc7302d7953ef4307db30d1b/site/back_pattern.png -------------------------------------------------------------------------------- /site/fork_me.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielstocks/jQuery-Collapse/cafcce1ec71ad1fabc7302d7953ef4307db30d1b/site/fork_me.png -------------------------------------------------------------------------------- /site/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | jQuery Collapse | webcloud 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | Fork me on GitHub 20 | 21 |
22 |
23 | webcloud / jQuery Collapse 24 |
25 |
26 | 27 | 28 | 29 | 30 | 34 |
35 | 39 |
40 |
41 |

Collapsible Content with JavaScript

42 |

This plugin provides you an accessible and lightweight solution to a widely adopted interface pattern known as progressive disclosure.

43 |

Read the full documentation for more info.

44 |

Basic Usage

45 |
<div id="demo" data-collapse="accordion persist">
 46 |   <h2>Fruits</h2>
 47 |   <ul>
 48 |     <li>Apple</li>
 49 |     <li>Pear</li>
 50 |     <li>Orange</li>
 51 |   </ul>
 52 |   <h2>Hint</h2>
 53 |   <div>
 54 |     <p>One fruit a day keeps the doctor away</p>
 55 |   </div>
 56 |   <h2>Third</h2>
 57 |   <p>Just a paragraph here</p>
 58 | </div>
 59 | 
60 |
61 |

Fruits

62 |
    63 |
  • Apple
  • 64 |
  • Pear
  • 65 |
  • Orange
  • 66 |
67 |

Saying

68 |
69 |

One fruit a day keeps the doctor away

70 |
71 |

Third

72 |

Just a paragraph here

73 |
74 |
75 | 85 |
86 |

Features

87 |
88 |

Persistence

89 |
90 |

Remembers open sections on page reload using either HTML5 localStorage or cookies!

91 |
92 |

Accordion

93 |
94 |

jQuery Collapse is packed with the classic 'accordion' behaviour that can be easily toggled on and off.

95 |
96 |

WAI-ARIA Compliance

97 |
98 |

The plugin has been designed with WAI-ARIA in mind which defines a way to make Web content and Web applications more accessible to people with disabilities.

99 |
100 |

Lightweight

101 |
102 |

jQuery-Collapse is designed to be lightweight with minimal bloat. It's only ~1kb when compiled and gzipped!

103 |
104 |

Cross Browser

105 |
106 |

Fully tested in IE6+, Firefox3+, Chrome5+, Safari4+, Opera 10+. Degrades gracefully in unsupported browsers

107 |
108 |
109 |
110 |
111 | 112 |

A few examples

113 |

To better understand how these examples work I recommend you to view the source. 114 | 115 | 116 |

117 |
118 |

Default Example

119 |
120 |

Fruits

121 |
I like fruits. This link should work
122 |

Info

123 |
This is some information
124 |
125 |

Accordion Example

126 |
127 |

Accordions

128 |
Are fun and they make pleasent noises
129 |

Fruits

130 |
I like fruits
131 |

Info

132 |
This is some information
133 |

Yeah!!

134 |
eh
135 |
136 | 137 |

Persistence Example

138 |
139 |

These

140 |
Well hello there
141 |

Sections

142 |
yabayaba
143 |

Should be

144 |
might be.
145 |

Persistant!!

146 |
eh
147 |
148 |
149 | 150 |
151 | 152 |

Custom show & hide

153 |
154 |

Hello

155 |
156 |

Hello Sir.

157 |

I'm sliding

158 |
159 |

Anarachy in the UK

160 |
I like tea
161 |

Indeed

162 |
This is some information
163 |
164 | 174 | 175 | 176 | 177 |

w/ CSS3 Animations

178 |
179 |

Hello

180 |
181 |
182 |

This example simply sets a class attribute to the details and let's an 183 | external stylesheet toggle the collapsed state.

184 |

Hello Sir.

185 |

I'm sliding

186 |
187 |
188 |

Friend

189 |
190 |
191 |

This example simply sets a class attribute to the details and let's an 192 | external stylesheet toggle the collapsed state.

193 |

Hello Sir.

194 |
195 |
196 |

Foe

197 |
198 |
199 |

This example simply sets a class attribute to the details and let's an 200 | external stylesheet toggle the collapsed state.

201 |
202 |
203 |
204 | 218 | 219 | 220 | 221 | 222 |

Custom markup example

223 |
224 |
225 |

Hello

226 |
227 |
228 |

This example simply sets a class attribute to the details and let's an 229 | external stylesheet toggle the collapsed state.

230 |

Hello Sir.

231 |

I'm sliding

232 |
233 |
234 |
235 |
236 |

Friend

237 |
238 |
239 |

This example simply sets a class attribute to the details and let's an 240 | external stylesheet toggle the collapsed state.

241 |

Hello Sir.

242 |
243 |
244 |
245 |
246 |

Foe

247 |
248 |
249 |

This example simply sets a class attribute to the details and let's an 250 | external stylesheet toggle the collapsed state.

251 |
252 |
253 |
254 |
255 | 260 | 261 |
262 | 263 |
264 | 265 | 266 |

Binding events

267 |
Event log
268 |
269 |

Section 1

270 |
271 |

This example simply sets a class attribute to the details and let's an 272 | external stylesheet toggle the collapsed state.

273 |

Hello Sir.

274 |

I'm sliding

275 |
276 |

Section 2

277 |
278 |

This example simply sets a class attribute to the details and let's an 279 | external stylesheet toggle the collapsed state.

280 |

Hello Sir.

281 |
282 |

Section 3

283 |
284 |

This example simply sets a class attribute to the details and let's an 285 | external stylesheet toggle the collapsed state.

286 |
287 |
288 | 289 |

Open section by default

290 |
291 |

I'm open by default

292 |
Yay
293 |

I'm not open

294 |
booo :(
295 |
296 | 297 | 309 | 310 | 311 |
312 |
313 | 314 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | -------------------------------------------------------------------------------- /site/prism.css: -------------------------------------------------------------------------------- 1 | /** 2 | * prism.js default theme for JavaScript, CSS and HTML 3 | * Based on dabblet (http://dabblet.com) 4 | * @author Lea Verou 5 | */ 6 | 7 | code[class*="language-"], 8 | pre[class*="language-"] { 9 | color: black; 10 | text-shadow: 0 1px white; 11 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 12 | direction: ltr; 13 | text-align: left; 14 | white-space: pre; 15 | word-spacing: normal; 16 | 17 | -moz-tab-size: 4; 18 | -o-tab-size: 4; 19 | tab-size: 4; 20 | 21 | -webkit-hyphens: none; 22 | -moz-hyphens: none; 23 | -ms-hyphens: none; 24 | hyphens: none; 25 | } 26 | 27 | /* Code blocks */ 28 | pre[class*="language-"] { 29 | padding: 1em; 30 | margin: .5em 0; 31 | overflow: auto; 32 | } 33 | 34 | :not(pre) > code[class*="language-"], 35 | pre[class*="language-"] { 36 | background: #fff; 37 | } 38 | 39 | /* Inline code */ 40 | :not(pre) > code[class*="language-"] { 41 | padding: .1em; 42 | border-radius: .3em; 43 | } 44 | 45 | .token.comment, 46 | .token.prolog, 47 | .token.doctype, 48 | .token.cdata { 49 | color: slategray; 50 | } 51 | 52 | .token.punctuation { 53 | color: #999; 54 | } 55 | 56 | .namespace { 57 | opacity: .7; 58 | } 59 | 60 | .token.property, 61 | .token.tag, 62 | .token.boolean, 63 | .token.number { 64 | color: #905; 65 | } 66 | 67 | .token.selector, 68 | .token.attr-name, 69 | .token.string { 70 | color: #690; 71 | } 72 | 73 | .token.operator, 74 | .token.entity, 75 | .token.url, 76 | .language-css .token.string, 77 | .style .token.string { 78 | color: #a67f59; 79 | background: hsla(0,0%,100%,.5); 80 | } 81 | 82 | .token.atrule, 83 | .token.attr-value, 84 | .token.keyword { 85 | color: #07a; 86 | } 87 | 88 | 89 | .token.regex, 90 | .token.important { 91 | color: #e90; 92 | } 93 | 94 | .token.important { 95 | font-weight: bold; 96 | } 97 | 98 | .token.entity { 99 | cursor: help; 100 | } 101 | -------------------------------------------------------------------------------- /site/prism.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Prism: Lightweight, robust, elegant syntax highlighting 3 | * MIT license http://www.opensource.org/licenses/mit-license.php/ 4 | * @author Lea Verou http://lea.verou.me 5 | */(function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=self.Prism={languages:{insertBefore:function(e,n,r,i){i=i||t.languages;var s=i[e],o={};for(var u in s)if(s.hasOwnProperty(u)){if(u==n)for(var a in r)r.hasOwnProperty(a)&&(o[a]=r[a]);o[u]=s[u]}return i[e]=o},DFS:function(e,n){for(var r in e){n.call(e,r,e[r]);Object.prototype.toString.call(e)==="[object Object]"&&t.languages.DFS(e[r],n)}}},highlightAll:function(e,n){var r=document.querySelectorAll('code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code');for(var i=0,s;s=r[i++];)t.highlightElement(s,e===!0,n)},highlightElement:function(r,i,s){var o,u,a=r;while(a&&!e.test(a.className))a=a.parentNode;if(a){o=(a.className.match(e)||[,""])[1];u=t.languages[o]}if(!u)return;r.className=r.className.replace(e,"").replace(/\s+/g," ")+" language-"+o;a=r.parentNode;/pre/i.test(a.nodeName)&&(a.className=a.className.replace(e,"").replace(/\s+/g," ")+" language-"+o);var f=r.textContent.trim();if(!f)return;f=f.replace(/&/g,"&").replace(//g,">").replace(/\u00a0/g," ");var l={element:r,language:o,grammar:u,code:f};t.hooks.run("before-highlight",l);if(i&&self.Worker){var c=new Worker(t.filename);c.onmessage=function(e){l.highlightedCode=n.stringify(JSON.parse(e.data));l.element.innerHTML=l.highlightedCode;s&&s.call(l.element);t.hooks.run("after-highlight",l)};c.postMessage(JSON.stringify({language:l.language,code:l.code}))}else{l.highlightedCode=t.highlight(l.code,l.grammar);l.element.innerHTML=l.highlightedCode;s&&s.call(r);t.hooks.run("after-highlight",l)}},highlight:function(e,r){return n.stringify(t.tokenize(e,r))},tokenize:function(e,n){var r=t.Token,i=[e],s=n.rest;if(s){for(var o in s)n[o]=s[o];delete n.rest}e:for(var o in n){if(!n.hasOwnProperty(o)||!n[o])continue;var u=n[o],a=u.inside,f=!!u.lookbehind||0;u=u.pattern||u;for(var l=0;le.length)break e;if(c instanceof r)continue;u.lastIndex=0;var h=u.exec(c);if(h){f&&(f=h[1].length);var p=h.index-1+f,h=h[0].slice(f),d=h.length,v=p+d,m=c.slice(0,p+1),g=c.slice(v+1),y=[l,1];m&&y.push(m);var b=new r(o,a?t.tokenize(h,a):h);y.push(b);g&&y.push(g);Array.prototype.splice.apply(i,y)}}}return i},hooks:{all:{},add:function(e,n){var r=t.hooks.all;r[e]=r[e]||[];r[e].push(n)},run:function(e,n){var r=t.hooks.all[e];if(!r||!r.length)return;for(var i=0,s;s=r[i++];)s(n)}}},n=t.Token=function(e,t){this.type=e;this.content=t};n.stringify=function(e){if(typeof e=="string")return e;if(Object.prototype.toString.call(e)=="[object Array]"){for(var r=0;r"+i.content+""};if(!self.document){self.addEventListener("message",function(e){var n=JSON.parse(e.data),r=n.language,i=n.code;self.postMessage(JSON.stringify(t.tokenize(i,t.languages[r])));self.close()},!1);return}var r=document.getElementsByTagName("script");r=r[r.length-1];if(r){t.filename=r.src;document.addEventListener&&!r.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)}})(); 6 | Prism.languages.markup={comment:/<!--[\w\W]*?--(>|>)/g,prolog:/<\?.+?\?>/,doctype:/<!DOCTYPE.+?>/,cdata:/<!\[CDATA\[[\w\W]+?]]>/i,tag:{pattern:/<\/?[\w:-]+\s*[\w\W]*?>/gi,inside:{tag:{pattern:/^<\/?[\w:-]+/i,inside:{punctuation:/^<\/?/,namespace:/^[\w-]+?:/}},"attr-value":{pattern:/=(('|")[\w\W]*?(\2)|[^\s>]+)/gi,inside:{punctuation:/=/g}},punctuation:/\/?>/g,"attr-name":{pattern:/[\w:-]+/g,inside:{namespace:/^[\w-]+?:/}}}},entity:/&#?[\da-z]{1,8};/gi};Prism.hooks.add("wrap",function(e){e.type==="entity"&&(e.attributes.title=e.content.replace(/&/,"&"))}); 7 | Prism.languages.css={comment:/\/\*[\w\W]*?\*\//g,atrule:/@[\w-]+?(\s+.+)?(?=\s*{|\s*;)/gi,url:/url\((["']?).*?\1\)/gi,selector:/[^\{\}\s][^\{\}]*(?=\s*\{)/g,property:/(\b|\B)[a-z-]+(?=\s*:)/ig,string:/("|')(\\?.)*?\1/g,important:/\B!important\b/gi,ignore:/&(lt|gt|amp);/gi,punctuation:/[\{\};:]/g};Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{style:{pattern:/(<|<)style[\w\W]*?(>|>)[\w\W]*?(<|<)\/style(>|>)/ig,inside:{tag:{pattern:/(<|<)style[\w\W]*?(>|>)|(<|<)\/style(>|>)/ig,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.css}}}); 8 | Prism.languages.javascript={comment:{pattern:/(^|[^\\])(\/\*[\w\W]*?\*\/|\/\/.*?(\r?\n|$))/g,lookbehind:!0},string:/("|')(\\?.)*?\1/g,regex:{pattern:/(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\r\n])+\/[gim]{0,3}(?=\s*($|[\r\n,.;})]))/g,lookbehind:!0},keyword:/\b(var|let|if|else|while|do|for|return|in|instanceof|function|new|with|typeof|try|catch|finally|null|break|continue)\b/g,"boolean":/\b(true|false)\b/g,number:/\b-?(0x)?\d*\.?\d+\b/g,operator:/[-+]{1,2}|!|=?<|=?>|={1,2}|(&){1,2}|\|?\||\?|\*|\//g,ignore:/&(lt|gt|amp);/gi,punctuation:/[{}[\];(),.:]/g};Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/(<|<)script[\w\W]*?(>|>)[\w\W]*?(<|<)\/script(>|>)/ig,inside:{tag:{pattern:/(<|<)script[\w\W]*?(>|>)|(<|<)\/script(>|>)/ig,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.javascript}}}); 9 | -------------------------------------------------------------------------------- /site/sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielstocks/jQuery-Collapse/cafcce1ec71ad1fabc7302d7953ef4307db30d1b/site/sprite.png -------------------------------------------------------------------------------- /site/src/jquery.collapse.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Collapse plugin for jQuery 3 | * -- 4 | * source: http://github.com/danielstocks/jQuery-Collapse/ 5 | * site: http://webcloud.se/jQuery-Collapse 6 | * 7 | * @author Daniel Stocks (http://webcloud.se) 8 | * Copyright 2013, Daniel Stocks 9 | * Released under the MIT, BSD, and GPL Licenses. 10 | */ 11 | 12 | (function($) { 13 | 14 | // Constructor 15 | function Collapse (el, options) { 16 | options = options || {}; 17 | var _this = this, 18 | query = options.query || "> :even"; 19 | 20 | $.extend(_this, { 21 | $el: el, 22 | options : options, 23 | sections: [], 24 | isAccordion : options.accordion || false, 25 | db : options.persist ? jQueryCollapseStorage(el[0].id) : false 26 | }); 27 | 28 | // Figure out what sections are open if storage is used 29 | _this.states = _this.db ? _this.db.read() : []; 30 | 31 | // For every pair of elements in given 32 | // element, create a section 33 | _this.$el.find(query).each(function() { 34 | var section = new Section($(this), _this); 35 | _this.sections.push(section); 36 | 37 | // Check current state of section 38 | var state = _this.states[section._index()]; 39 | if(state === 0) { 40 | section.$summary.removeClass("open"); 41 | } 42 | if(state === 1) { 43 | section.$summary.addClass("open"); 44 | } 45 | 46 | // Show or hide accordingly 47 | if(section.$summary.hasClass("open")) { 48 | section.open(true); 49 | } 50 | else { 51 | section.close(true); 52 | } 53 | }); 54 | 55 | // Capute ALL the clicks! 56 | (function(scope) { 57 | _this.$el.on("click", "[data-collapse-summary]", 58 | $.proxy(_this.handleClick, scope)); 59 | }(_this)); 60 | } 61 | 62 | Collapse.prototype = { 63 | handleClick: function(e) { 64 | e.preventDefault(); 65 | var sections = this.sections, 66 | l = sections.length; 67 | while(l--) { 68 | if($.contains(sections[l].$summary[0], e.target)) { 69 | sections[l].toggle(); 70 | break; 71 | } 72 | } 73 | }, 74 | open : function(eq) { 75 | if(isFinite(eq)) return this.sections[eq].open(); 76 | $.each(this.sections, function() { 77 | this.open(); 78 | }); 79 | }, 80 | close: function(eq) { 81 | if(isFinite(eq)) return this.sections[eq].close(); 82 | $.each(this.sections, function() { 83 | this.close(); 84 | }); 85 | } 86 | }; 87 | 88 | // Section constructor 89 | function Section($el, parent) { 90 | $.extend(this, { 91 | isOpen : false, 92 | $summary : $el 93 | .attr("data-collapse-summary", "") 94 | .wrapInner(''), 95 | $details : $el.next(), 96 | options: parent.options, 97 | parent: parent 98 | }); 99 | } 100 | 101 | Section.prototype = { 102 | toggle : function() { 103 | if(this.isOpen) this.close(); 104 | else this.open(); 105 | }, 106 | close: function(bypass) { 107 | this._changeState("close", bypass); 108 | }, 109 | open: function(bypass) { 110 | var _this = this; 111 | if(_this.options.accordion && !bypass) { 112 | $.each(_this.parent.sections, function() { 113 | this.close(); 114 | }); 115 | } 116 | _this._changeState("open", bypass); 117 | }, 118 | _index: function() { 119 | return $.inArray(this, this.parent.sections); 120 | }, 121 | _changeState: function(state, bypass) { 122 | 123 | var _this = this; 124 | _this.isOpen = state == "open"; 125 | if($.isFunction(_this.options[state]) && !bypass) { 126 | _this.options[state].apply(_this.$details); 127 | } else { 128 | if(_this.isOpen) _this.$details.show(); 129 | else _this.$details.hide(); 130 | } 131 | _this.$summary.removeClass("open close").addClass(state); 132 | _this.$details.attr("aria-hidden", state == "close"); 133 | _this.parent.$el.trigger(state, _this); 134 | if(_this.parent.db) { 135 | _this.parent.db.write(_this._index(), _this.isOpen); 136 | } 137 | } 138 | }; 139 | 140 | // Expose in jQuery API 141 | $.fn.extend({ 142 | collapse: function(options, scan) { 143 | var nodes = (scan) ? $("body").find("[data-collapse]") : $(this); 144 | return nodes.each(function() { 145 | var settings = (scan) ? {} : options, 146 | values = $(this).attr("data-collapse") || ""; 147 | $.each(values.split(" "), function(i,v) { 148 | if(v) settings[v] = true; 149 | }); 150 | new jQueryCollapse($(this), settings); 151 | }); 152 | } 153 | }); 154 | 155 | //jQuery DOM Ready 156 | $(function() { 157 | $.fn.collapse(false, true); 158 | }); 159 | 160 | // Expose constructor to 161 | // global namespace 162 | jQueryCollapse = Collapse; 163 | 164 | })(window.jQuery); 165 | -------------------------------------------------------------------------------- /site/src/jquery.collapse_cookie_storage.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Cookie Storage for jQuery Collapse 3 | * -- 4 | * source: http://github.com/danielstocks/jQuery-Collapse/ 5 | * site: http://webcloud.se/jQuery-Collapse 6 | * 7 | * @author Daniel Stocks (http://webcloud.se) 8 | * Copyright 2013, Daniel Stocks 9 | * Released under the MIT, BSD, and GPL Licenses. 10 | */ 11 | 12 | (function($) { 13 | 14 | var cookieStorage = { 15 | expires: function() { 16 | var now = new Date(); 17 | return now.setDate(now.getDate() + 1); 18 | }(), 19 | setItem: function(key, value) { 20 | document.cookie = key + '=' + value + '; expires=' + this.expires +'; path=/'; 21 | }, 22 | getItem: function(key) { 23 | key+= "="; 24 | var item = ""; 25 | $.each(document.cookie.split(';'), function(i, cookie) { 26 | while (cookie.charAt(0)==' ') cookie = cookie.substring(1,cookie.length); 27 | if(cookie.indexOf(key) === 0) { 28 | item = cookie.substring(key.length,cookie.length); 29 | } 30 | }); 31 | return item; 32 | } 33 | }; 34 | 35 | $.fn.collapse.cookieStorage = cookieStorage; 36 | 37 | })(jQuery); 38 | -------------------------------------------------------------------------------- /site/src/jquery.collapse_storage.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Storage for jQuery Collapse 3 | * -- 4 | * source: http://github.com/danielstocks/jQuery-Collapse/ 5 | * site: http://webcloud.se/jQuery-Collapse 6 | * 7 | * @author Daniel Stocks (http://webcloud.se) 8 | * Copyright 2013, Daniel Stocks 9 | * Released under the MIT, BSD, and GPL Licenses. 10 | */ 11 | 12 | (function($) { 13 | 14 | var STORAGE_KEY = "jQuery-Collapse"; 15 | 16 | function Storage(id) { 17 | var DB; 18 | try { 19 | DB = window.localStorage || $.fn.collapse.cookieStorage; 20 | } catch(e) { 21 | DB = false; 22 | } 23 | return DB ? new _Storage(id, DB) : false; 24 | } 25 | function _Storage(id, DB) { 26 | this.id = id; 27 | this.db = DB; 28 | this.data = []; 29 | } 30 | _Storage.prototype = { 31 | write: function(position, state) { 32 | var _this = this; 33 | _this.data[position] = state ? 1 : 0; 34 | // Pad out data array with zero values 35 | $.each(_this.data, function(i) { 36 | if(typeof _this.data[i] == 'undefined') { 37 | _this.data[i] = 0; 38 | } 39 | }); 40 | var obj = this.getDataObject(); 41 | obj[this.id] = this.data; 42 | this.db.setItem(STORAGE_KEY, JSON.stringify(obj)); 43 | }, 44 | read: function() { 45 | var obj = this.getDataObject(); 46 | return obj[this.id] || []; 47 | }, 48 | getDataObject: function() { 49 | var string = this.db.getItem(STORAGE_KEY); 50 | return string ? JSON.parse(string) : {}; 51 | } 52 | }; 53 | 54 | jQueryCollapseStorage = Storage; 55 | 56 | })(jQuery); 57 | -------------------------------------------------------------------------------- /site/style.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Asap:400,700); 2 | 3 | /* Base 4 | * ------------- */ 5 | body { 6 | margin: 0; 7 | padding: 20px; 8 | font: 14px/1.4 Georgia, serif; 9 | color: #444; 10 | background: url('back_pattern.png') fixed; 11 | } 12 | ul { 13 | list-style-type: circle; 14 | display: block; 15 | margin: 20px 0; 16 | } 17 | li { 18 | margin: 10px 20px; 19 | list-style-type: circle; 20 | } 21 | p { 22 | margin: 20px 0; 23 | } 24 | a { 25 | color: #39b2e5; 26 | } 27 | #header h1 { 28 | font: bold 72px/1 'Asap', sans-serif; 29 | float: right; 30 | letter-spacing: -3px; 31 | margin: 0; 32 | } 33 | h2 { 34 | font: 24px/28px 'Asap', sans-serif; 35 | line-height: 1.2; 36 | margin: 0; 37 | } 38 | h3 { 39 | font: 18px/24px 'Asap', sans-serif; 40 | margin: 0 0 -12px; 41 | } 42 | #content pre { 43 | font-size: 12px; 44 | padding: 10px; 45 | background: #fff; 46 | margin: 30px 0; 47 | display: block; 48 | border: 1px solid #ccc; 49 | border-width: 0 1px 1px 0; 50 | } 51 | 52 | /* Header 53 | * ------------- */ 54 | #wrap { 55 | width: 940px; 56 | margin: 80px auto 0; 57 | position: relative; 58 | } 59 | #wrap #header { 60 | border-top: 2px solid #333; 61 | position: relative; 62 | border-bottom: 1px solid #aaa; 63 | padding: 20px 0; 64 | display: block; 65 | overflow: hidden; 66 | margin-bottom: 20px; 67 | } 68 | #header h2 { 69 | font-size: 20px; 70 | font-family: Georgia, serif; 71 | margin: 0; 72 | width: 380px; 73 | position: absolute; 74 | bottom: 20px; 75 | } 76 | #crumb, 77 | #social { 78 | position: absolute; 79 | right: 0; 80 | top: -32px; 81 | } 82 | #crumb { 83 | right: auto; 84 | } 85 | 86 | /* Sections 87 | * ------------- */ 88 | section { 89 | width: 450px; 90 | float: left; 91 | margin: 20px 0; 92 | overflow: hidden; 93 | margin-left: 40px; 94 | } 95 | section:first-child { 96 | margin-left: 0; 97 | } 98 | 99 | /* Demo collapse 100 | * ------------- */ 101 | #demo { 102 | background: #b0e5b8; 103 | border: 1px solid #628066; 104 | border-width: 0 1px 0px 0; 105 | overflow: hidden; 106 | margin-bottom: 20px; 107 | } 108 | #demo h2 a { 109 | font-size: 18px; 110 | background: url('sprite.png') 15px 13px no-repeat; 111 | display: block; 112 | padding: 9px; 113 | padding-left: 32px; 114 | margin: 0; 115 | color: #628066; 116 | text-decoration: none; 117 | font-weight: normal; 118 | border-bottom: 1px solid #628066; 119 | } 120 | #demo h2.open a { 121 | background-position: 14px -24px; 122 | } 123 | #demo h2 + div, 124 | #demo h2 + ul, 125 | #demo h2 + p { 126 | padding: 10px; 127 | background: #fff; 128 | margin: 0; 129 | } 130 | 131 | /* Feature box 132 | * ------------- */ 133 | #features .box { 134 | background: #fff095; 135 | padding: 20px; 136 | border: 1px solid #bfb470; 137 | border-width: 0 1px 1px 0; 138 | margin-top: 10px; 139 | } 140 | #features p { 141 | margin-top: 10px; 142 | } 143 | #features h3 { 144 | margin: 0 0 5px; 145 | background: url('sprite.png') 2px 5px no-repeat; 146 | padding-left: 20px; 147 | } 148 | #features h3 a { 149 | color: #bfb470; 150 | text-decoration: none; 151 | } 152 | #features h3.open { 153 | background-position: 0px -34px; 154 | } 155 | 156 | /* Download area 157 | * ------------- */ 158 | .download { 159 | float: left; 160 | overflow: hidden; 161 | } 162 | .download small { 163 | margin: 5px 2px 0; 164 | text-align: right; 165 | display: block; 166 | color: #888; 167 | } 168 | .view-on-github { 169 | float: left; 170 | margin-left: 10px; 171 | line-height: 50px; 172 | } 173 | 174 | /* Button style (from twitter's Bootstrap) 175 | * ------------- */ 176 | .btn { 177 | font: normal 13px/18px 'Asap', sans-serif; 178 | text-decoration: none; 179 | display: inline-block; 180 | *display: inline; 181 | padding: 4px 10px 4px; 182 | margin-bottom: 0; 183 | *margin-left: 0.3em; 184 | font-size: 13px; 185 | line-height: 18px; 186 | *line-height: 20px; 187 | color: #333333; 188 | text-align: center; 189 | text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); 190 | vertical-align: middle; 191 | cursor: pointer; 192 | background-color: #f5f5f5; 193 | *background-color: #e6e6e6; 194 | background-image: -ms-linear-gradient(top, #ffffff, #e6e6e6); 195 | background-image: -webkit-gradient( 196 | linear, 197 | 0 0, 198 | 0 100%, 199 | from(#ffffff), 200 | to(#e6e6e6) 201 | ); 202 | background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6); 203 | background-image: -o-linear-gradient(top, #ffffff, #e6e6e6); 204 | background-image: linear-gradient(top, #ffffff, #e6e6e6); 205 | background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6); 206 | background-repeat: repeat-x; 207 | border: 1px solid #cccccc; 208 | *border: 0; 209 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); 210 | border-color: #e6e6e6 #e6e6e6 #bfbfbf; 211 | border-bottom-color: #b3b3b3; 212 | -webkit-border-radius: 4px; 213 | -moz-border-radius: 4px; 214 | border-radius: 4px; 215 | filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0); 216 | filter: progid:dximagetransform.microsoft.gradient(enabled=false); 217 | *zoom: 1; 218 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 219 | 0 1px 2px rgba(0, 0, 0, 0.05); 220 | -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 221 | 0 1px 2px rgba(0, 0, 0, 0.05); 222 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 223 | 0 1px 2px rgba(0, 0, 0, 0.05); 224 | text-shadow: 1px 1px 1px #fff; 225 | } 226 | .btn-large { 227 | padding: 14px 19px; 228 | font-size: 18px; 229 | line-height: normal; 230 | -webkit-border-radius: 5px; 231 | -moz-border-radius: 5px; 232 | border-radius: 5px; 233 | } 234 | 235 | .btn:hover { 236 | color: #333333; 237 | text-decoration: none; 238 | background-color: #e6e6e6; 239 | *background-color: #d9d9d9; 240 | /* Buttons in IE7 don't get borders, so darken on hover */ 241 | background-position: 0 -15px; 242 | -webkit-transition: background-position 0.1s linear; 243 | -moz-transition: background-position 0.1s linear; 244 | -ms-transition: background-position 0.1s linear; 245 | -o-transition: background-position 0.1s linear; 246 | transition: background-position 0.1s linear; 247 | } 248 | .btn:active { 249 | background-color: #e6e6e6; 250 | background-color: #d9d9d9 \9; 251 | background-image: none; 252 | outline: 0; 253 | -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 254 | 0 1px 2px rgba(0, 0, 0, 0.05); 255 | -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 256 | 0 1px 2px rgba(0, 0, 0, 0.05); 257 | box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); 258 | } 259 | 260 | .col { 261 | width: 292px; 262 | float: left; 263 | margin-right: 32px; 264 | margin-bottom: 16px; 265 | } 266 | .c3 { 267 | margin-right: 0; 268 | } 269 | 270 | #examples-header { 271 | clear: both; 272 | font: normal 48px/1.2 'Asap', sans-serif; 273 | border-bottom: 1px solid #ccc; 274 | padding: 0 0 5px; 275 | margin: 0 0 10px; 276 | text-align: center; 277 | } 278 | #examples-header span { 279 | color: #ccc; 280 | font-size: 20px; 281 | vertical-align: middle; 282 | } 283 | 284 | #examples-info { 285 | text-align: center; 286 | } 287 | #examples { 288 | margin: 0; 289 | width: 100%; 290 | } 291 | #examples h2 { 292 | margin: 10px 0; 293 | color: #000; 294 | font-size: 18px; 295 | text-shadow: 1px 1px 2px #fff; 296 | } 297 | 298 | #examples h3 { 299 | margin: 0; 300 | background-color: rgb(228, 10, 85); 301 | background-image: linear-gradient( 302 | bottom, 303 | rgb(228, 10, 85) 14%, 304 | rgb(255, 36, 111) 57% 305 | ); 306 | background-image: -o-linear-gradient( 307 | bottom, 308 | rgb(228, 10, 85) 14%, 309 | rgb(255, 36, 111) 57% 310 | ); 311 | background-image: -moz-linear-gradient( 312 | bottom, 313 | rgb(228, 10, 85) 14%, 314 | rgb(255, 36, 111) 57% 315 | ); 316 | background-image: -webkit-linear-gradient( 317 | bottom, 318 | rgb(228, 10, 85) 14%, 319 | rgb(255, 36, 111) 57% 320 | ); 321 | background-image: -ms-linear-gradient( 322 | bottom, 323 | rgb(228, 10, 85) 14%, 324 | rgb(255, 36, 111) 57% 325 | ); 326 | } 327 | 328 | #examples h3 a { 329 | background: url('sprite.png') 15px 13px no-repeat; 330 | display: block; 331 | padding: 10px; 332 | padding-left: 32px; 333 | margin: 0; 334 | color: #fff; 335 | text-decoration: none; 336 | font-weight: normal; 337 | border-bottom: 1px solid rgba(128, 10, 85, 0.5); 338 | text-shadow: 1px 1px 1px rgb(128, 10, 85); 339 | } 340 | #examples h3:hover { 341 | background: rgb(228, 10, 85); 342 | } 343 | #examples h3.open { 344 | background: rgb(255, 70, 120); 345 | } 346 | #examples h3.open a { 347 | background-position: 13px -25px; 348 | } 349 | #examples h3 + div { 350 | padding: 10px; 351 | } 352 | 353 | #examples h2 + div, 354 | #examples .example { 355 | background: #fff; 356 | overflow: hidden; 357 | border-radius: 3px; 358 | -moz-border-radius: 3px; 359 | -webkit-border-radius: 3px; 360 | margin-bottom: 20px; 361 | } 362 | 363 | .fork { 364 | border: 0; 365 | position: absolute; 366 | top: 0; 367 | right: 0; 368 | } 369 | 370 | #github-star { 371 | margin-left: 8px; 372 | } 373 | 374 | /* Pre hide sections with JavaScript on 375 | --- */ 376 | #examples h3 + div { 377 | display: none; 378 | } 379 | 380 | /* CSS3 Animation example 381 | --- */ 382 | #css3-animated-example h3 + div { 383 | height: 0px; 384 | padding: 0px; 385 | overflow: hidden; 386 | background: #000; 387 | display: block !important; 388 | -webkit-transform: translateZ(0); 389 | -webkit-transition: all 0.3s ease; 390 | -moz-transition: all 0.3s ease; 391 | -o-transition: all 0.3s ease; 392 | -ms-transition: all 0.3s ease; 393 | transition: all 0.3s ease; 394 | } 395 | #css3-animated-example .content { 396 | padding: 10px; 397 | } 398 | 399 | #css3-animated-example h3.open + div { 400 | height: auto; 401 | background: #aaffff; 402 | } 403 | 404 | #examples p { 405 | margin: 5px 0; 406 | } 407 | 408 | /* Event example 409 | --- */ 410 | pre#event-log { 411 | background: #fafacc; 412 | margin-top: 0; 413 | padding: 10px; 414 | display: block; 415 | } 416 | 417 | /* Responsive design 418 | --- */ 419 | @media screen and (max-width: 1056px) { 420 | .fork { 421 | display: none; 422 | } 423 | #header h1 { 424 | font-size: 70px; 425 | float: none; 426 | margin-bottom: 20px; 427 | } 428 | #header h2 { 429 | position: static; 430 | width: auto; 431 | } 432 | #info { 433 | margin: 0; 434 | } 435 | #links, 436 | #features { 437 | margin: 10px 0; 438 | } 439 | #examples-header { 440 | padding-top: 20px; 441 | font-size: 32px; 442 | } 443 | .col { 444 | width: 100%; 445 | margin-right: 0; 446 | } 447 | #github-star { 448 | display: none; 449 | } 450 | body { 451 | padding: 16px; 452 | padding-top: 0; 453 | } 454 | .col { 455 | width: 100%; 456 | margin-right: 0; 457 | } 458 | #wrap { 459 | width: auto; 460 | max-width: 620px; 461 | } 462 | section { 463 | width: 100%; 464 | } 465 | } 466 | -------------------------------------------------------------------------------- /src/jquery.collapse.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Collapse plugin for jQuery 3 | * -- 4 | * source: http://github.com/danielstocks/jQuery-Collapse/ 5 | * site: http://webcloud.se/jQuery-Collapse 6 | * 7 | * @author Daniel Stocks (http://webcloud.se) 8 | * Copyright 2013, Daniel Stocks 9 | * Released under the MIT, BSD, and GPL Licenses. 10 | */ 11 | 12 | (function($, exports) { 13 | 14 | // Constructor 15 | function Collapse (el, options) { 16 | options = options || {}; 17 | var _this = this, 18 | query = options.query || "> :even"; 19 | 20 | $.extend(_this, { 21 | $el: el, 22 | options : options, 23 | sections: [], 24 | isAccordion : options.accordion || false, 25 | db : options.persist ? jQueryCollapseStorage(el.get(0).id) : false 26 | }); 27 | 28 | // Figure out what sections are open if storage is used 29 | _this.states = _this.db ? _this.db.read() : []; 30 | 31 | // For every pair of elements in given 32 | // element, create a section 33 | _this.$el.find(query).each(function() { 34 | new jQueryCollapseSection($(this), _this); 35 | }); 36 | 37 | // Capute ALL the clicks! 38 | (function(scope) { 39 | _this.$el.on("click", "[data-collapse-summary] " + (scope.options.clickQuery || ""), 40 | $.proxy(_this.handleClick, scope)); 41 | 42 | _this.$el.bind("toggle close open", 43 | $.proxy(_this.handleEvent, scope)); 44 | 45 | }(_this)); 46 | } 47 | 48 | Collapse.prototype = { 49 | handleClick: function(e, state) { 50 | e.preventDefault(); 51 | state = state || "toggle"; 52 | var sections = this.sections, 53 | l = sections.length; 54 | while(l--) { 55 | if($.contains(sections[l].$summary[0], e.target)) { 56 | sections[l][state](); 57 | break; 58 | } 59 | } 60 | }, 61 | handleEvent: function(e) { 62 | if(e.target == this.$el.get(0)) return this[e.type](); 63 | this.handleClick(e, e.type); 64 | }, 65 | open: function(eq) { 66 | this._change("open", eq); 67 | }, 68 | close: function(eq) { 69 | this._change("close", eq); 70 | }, 71 | toggle: function(eq) { 72 | this._change("toggle", eq); 73 | }, 74 | _change: function(action, eq) { 75 | if(isFinite(eq)) return this.sections[eq][action](); 76 | $.each(this.sections, function(i, section) { 77 | section[action](); 78 | }); 79 | } 80 | }; 81 | 82 | // Section constructor 83 | function Section($el, parent) { 84 | 85 | if(!parent.options.clickQuery) $el.wrapInner(''); 86 | 87 | $.extend(this, { 88 | isOpen : false, 89 | $summary : $el.attr("data-collapse-summary",""), 90 | $details : $el.next(), 91 | options: parent.options, 92 | parent: parent 93 | }); 94 | parent.sections.push(this); 95 | 96 | // Check current state of section 97 | var state = parent.states[this._index()]; 98 | 99 | if(state === 0) { 100 | this.close(true); 101 | } 102 | else if(this.$summary.is(".open") || state === 1) { 103 | this.open(true); 104 | } else { 105 | this.close(true); 106 | } 107 | } 108 | 109 | Section.prototype = { 110 | toggle : function() { 111 | this.isOpen ? this.close() : this.open(); 112 | }, 113 | close: function(bypass) { 114 | this._changeState("close", bypass); 115 | }, 116 | open: function(bypass) { 117 | var _this = this; 118 | if(_this.options.accordion && !bypass) { 119 | $.each(_this.parent.sections, function(i, section) { 120 | section.close(); 121 | }); 122 | } 123 | _this._changeState("open", bypass); 124 | }, 125 | _index: function() { 126 | return $.inArray(this, this.parent.sections); 127 | }, 128 | _changeState: function(state, bypass) { 129 | 130 | var _this = this; 131 | _this.isOpen = state == "open"; 132 | if($.isFunction(_this.options[state]) && !bypass) { 133 | _this.options[state].apply(_this.$details); 134 | } else { 135 | _this.$details[_this.isOpen ? "show" : "hide"](); 136 | } 137 | 138 | _this.$summary.toggleClass("open", state !== "close"); 139 | _this.$details.attr("aria-hidden", state === "close"); 140 | _this.$summary.attr("aria-expanded", state === "open"); 141 | _this.$summary.trigger(state === "open" ? "opened" : "closed", _this); 142 | if(_this.parent.db) { 143 | _this.parent.db.write(_this._index(), _this.isOpen); 144 | } 145 | } 146 | }; 147 | 148 | // Expose in jQuery API 149 | $.fn.extend({ 150 | collapse: function(options, scan) { 151 | var nodes = (scan) ? $("body").find("[data-collapse]") : $(this); 152 | return nodes.each(function() { 153 | var settings = (scan) ? {} : options, 154 | values = $(this).attr("data-collapse") || ""; 155 | $.each(values.split(" "), function(i,v) { 156 | if(v) settings[v] = true; 157 | }); 158 | new Collapse($(this), settings); 159 | }); 160 | } 161 | }); 162 | 163 | // Expose constructor to 164 | // global namespace 165 | exports.jQueryCollapse = Collapse; 166 | exports.jQueryCollapseSection = Section; 167 | 168 | //jQuery DOM Ready 169 | $(function() { 170 | $.fn.collapse(false, true); 171 | }); 172 | 173 | })(window.jQuery, window); 174 | -------------------------------------------------------------------------------- /src/jquery.collapse_cookie_storage.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Cookie Storage for jQuery Collapse 3 | * -- 4 | * source: http://github.com/danielstocks/jQuery-Collapse/ 5 | * site: http://webcloud.se/jQuery-Collapse 6 | * 7 | * @author Daniel Stocks (http://webcloud.se) 8 | * Copyright 2013, Daniel Stocks 9 | * Released under the MIT, BSD, and GPL Licenses. 10 | */ 11 | 12 | (function($) { 13 | 14 | var cookieStorage = { 15 | expires: function() { 16 | var now = new Date(); 17 | return now.setDate(now.getDate() + 1); 18 | }(), 19 | setItem: function(key, value) { 20 | document.cookie = key + '=' + value + '; expires=' + this.expires +'; path=/'; 21 | }, 22 | getItem: function(key) { 23 | key+= "="; 24 | var item = ""; 25 | $.each(document.cookie.split(';'), function(i, cookie) { 26 | while (cookie.charAt(0)==' ') cookie = cookie.substring(1,cookie.length); 27 | if(cookie.indexOf(key) === 0) { 28 | item = cookie.substring(key.length,cookie.length); 29 | } 30 | }); 31 | return item; 32 | } 33 | }; 34 | 35 | $.fn.collapse.cookieStorage = cookieStorage; 36 | 37 | })(jQuery); 38 | -------------------------------------------------------------------------------- /src/jquery.collapse_storage.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Storage for jQuery Collapse 3 | * -- 4 | * source: http://github.com/danielstocks/jQuery-Collapse/ 5 | * site: http://webcloud.se/jQuery-Collapse 6 | * 7 | * @author Daniel Stocks (http://webcloud.se) 8 | * Copyright 2013, Daniel Stocks 9 | * Released under the MIT, BSD, and GPL Licenses. 10 | */ 11 | 12 | (function($) { 13 | 14 | var STORAGE_KEY = "jQuery-Collapse"; 15 | 16 | function Storage(id) { 17 | var DB; 18 | try { 19 | DB = window.localStorage || $.fn.collapse.cookieStorage; 20 | } catch(e) { 21 | DB = false; 22 | } 23 | return DB ? new _Storage(id, DB) : false; 24 | } 25 | 26 | function _Storage(id, DB) { 27 | this.id = id; 28 | this.db = DB; 29 | this.data = []; 30 | } 31 | 32 | window.jQueryCollapseStorage = Storage; 33 | 34 | _Storage.prototype = { 35 | write: function(position, state) { 36 | var _this = this; 37 | _this.data[position] = state ? 1 : 0; 38 | // Pad out data array with zero values 39 | $.each(_this.data, function(i) { 40 | if(typeof _this.data[i] == 'undefined') { 41 | _this.data[i] = 0; 42 | } 43 | }); 44 | var obj = this._getDataObject(); 45 | obj[this.id] = this.data; 46 | this.db.setItem(STORAGE_KEY, JSON.stringify(obj)); 47 | }, 48 | read: function() { 49 | var obj = this._getDataObject(); 50 | return obj[this.id] || []; 51 | }, 52 | _getDataObject: function() { 53 | var string = this.db.getItem(STORAGE_KEY); 54 | return string ? JSON.parse(string) : {}; 55 | } 56 | }; 57 | 58 | })(jQuery); 59 | -------------------------------------------------------------------------------- /test/jquery.collapse.cookie_storage_test.coffee: -------------------------------------------------------------------------------- 1 | describe 'Cookie Storage', -> 2 | 3 | before -> 4 | @cookieStorage = $.fn.collapse.cookieStorage 5 | @cookieStorage.setItem("test","holla") 6 | @cookieStorage.setItem("lol","ok") 7 | 8 | describe 'setItem', -> 9 | 10 | it 'sets a cookie', -> 11 | expect(document.cookie).to.eq "test=holla; lol=ok" 12 | 13 | describe 'getItem', -> 14 | 15 | before -> 16 | 17 | it 'gets the lol cookie', -> 18 | expect(@cookieStorage.getItem("lol")).to.eql "ok" 19 | 20 | it 'gets the test cookie', -> 21 | expect(@cookieStorage.getItem("test")).to.eql "holla" 22 | 23 | -------------------------------------------------------------------------------- /test/jquery.collapse_storage_test.coffee: -------------------------------------------------------------------------------- 1 | describe 'Storage', -> 2 | 3 | before -> 4 | @id = 'hello' 5 | @storage = new jQueryCollapseStorage(@id) 6 | 7 | describe 'Constructor', -> 8 | 9 | it 'should set an id', -> 10 | expect(@storage.id).to.eq @id 11 | 12 | it 'should set db to local storage', -> 13 | expect(@storage.db).to.eql window.localStorage 14 | 15 | it 'should set data to empty array', -> 16 | expect(@storage.data).to.eql [] 17 | 18 | describe 'write', -> 19 | 20 | before -> 21 | @storage.write(2, true) 22 | 23 | it 'should update storage data', -> 24 | expect(@storage.data).to.eql [0,0,1] 25 | 26 | it 'should set item on db', -> 27 | expect(@storage.db.getItem("jQuery-Collapse")).to.eq '{"hello":[0,0,1]}' 28 | 29 | describe 'read', -> 30 | 31 | it 'should read from db', -> 32 | value = @storage.read() 33 | expect(value).to.eql [0,0,1] 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /test/jquery.collapse_test.coffee: -------------------------------------------------------------------------------- 1 | describe 'Collapse', -> 2 | 3 | describe 'Constructor', -> 4 | 5 | describe 'without options', -> 6 | 7 | before -> 8 | @el = $(document.createElement('div')) 9 | @collapse = new jQueryCollapse @el 10 | 11 | it 'sets an empty options object', -> 12 | expect(@collapse.options).to.eql {} 13 | 14 | it 'sets an given element', -> 15 | expect(@collapse.$el).to.eq @el 16 | 17 | it 'has no sections', -> 18 | expect(@collapse.sections).to.eql [] 19 | 20 | it 'is not an accordion', -> 21 | expect(@collapse.isAccordion).to.eq false 22 | 23 | it 'is not persistant', -> 24 | expect(@collapse.db).to.eq false 25 | 26 | it 'has no states', -> 27 | expect(@collapse.states).to.eql [] 28 | 29 | describe 'with options', -> 30 | 31 | before -> 32 | @el = 33 | find : sinon.stub().returns($("
").add("
")) 34 | get : sinon.stub().returns(true) 35 | on : sinon.stub().returns(true) 36 | bind : sinon.stub().returns(true) 37 | 38 | @sectionStub = sinon.stub(window, 'jQueryCollapseSection') 39 | 40 | @dbMock = 41 | read: sinon.spy 42 | 43 | @storageStub = sinon.stub(window, 'jQueryCollapseStorage') 44 | .returns(@dbMock) 45 | 46 | @options = 47 | query: "div h2" 48 | accordion: true 49 | persist: true 50 | @collapse = new jQueryCollapse @el, @options 51 | 52 | after -> 53 | jQueryCollapseSection.restore() 54 | jQueryCollapseStorage.restore() 55 | 56 | it 'is an accordion', -> 57 | expect(@collapse.isAccordion).to.eq @options.accordion 58 | 59 | it 'is persistant', -> 60 | expect(@storageStub.calledOnce).to.be.ok 61 | 62 | it 'attempts to read db', -> 63 | expect(@dbMock.read).to.be.ok 64 | 65 | it 'creates a section for every query match', -> 66 | expect(@sectionStub.calledTwice).to.be.ok 67 | 68 | it 'captures ALL the clicks', -> 69 | expect(@el.on.calledWith('click', '[data-collapse-summary] ')).to.be.ok 70 | 71 | 72 | describe 'handleClick method', -> 73 | 74 | beforeEach -> 75 | 76 | summary = $("
") 77 | @collapse = new jQueryCollapse($(document.createElement('div'))) 78 | @collapse.sections = [ 79 | {toggle: sinon.spy(), $summary: $('
').append(summary)} 80 | ] 81 | @e = 82 | target : summary.get(0) 83 | preventDefault: sinon.spy() 84 | @collapse.handleClick(@e) 85 | 86 | it 'prevents default event behaviour', -> 87 | expect(@e.preventDefault.calledOnce).to.be.ok 88 | 89 | it 'toggles section if a summary was clicked', -> 90 | expect(@collapse.sections[0].toggle.calledOnce).to.be.ok 91 | 92 | 93 | describe 'open method', -> 94 | 95 | beforeEach -> 96 | @el = $(document.createElement('div')) 97 | @collapse = new jQueryCollapse @el 98 | @collapse.sections = [ 99 | {open: sinon.spy()} 100 | {open: sinon.spy()} 101 | ] 102 | 103 | it 'opens given section', -> 104 | @collapse.open(1) 105 | expect(@collapse.sections[0].open.called).not.to.be.ok 106 | expect(@collapse.sections[1].open.calledOnce).to.be.ok 107 | 108 | it 'opens all the sections', -> 109 | @collapse.open() 110 | expect(@collapse.sections[0].open.calledOnce).to.be.ok 111 | expect(@collapse.sections[1].open.calledOnce).to.be.ok 112 | 113 | describe 'close method', -> 114 | 115 | beforeEach -> 116 | @el = $(document.createElement('div')) 117 | @collapse = new jQueryCollapse @el 118 | @collapse.sections = [ 119 | {close: sinon.spy()} 120 | {close: sinon.spy()} 121 | ] 122 | 123 | it 'closes given section', -> 124 | @collapse.close(1) 125 | expect(@collapse.sections[0].close.called).not.to.be.ok 126 | expect(@collapse.sections[1].close.calledOnce).to.be.ok 127 | 128 | it 'closes all the sections', -> 129 | @collapse.close() 130 | expect(@collapse.sections[0].close.calledOnce).to.be.ok 131 | expect(@collapse.sections[1].close.calledOnce).to.be.ok 132 | 133 | describe 'Section', -> 134 | 135 | describe 'constructor', -> 136 | 137 | before -> 138 | @summary = $("
").addClass("summary") 139 | @details = $("
").addClass("details") 140 | @rootEl = $("
").append(@summary, @details) 141 | 142 | @parent = 143 | sections : [] 144 | states: [0,1,0] 145 | options: {} 146 | $el: @rootEl 147 | 148 | @section = new jQueryCollapseSection(@summary, @parent) 149 | 150 | describe 'defaults', -> 151 | 152 | it 'is not open', -> 153 | expect(@section.isOpen).to.eql false 154 | 155 | it 'sets a summary', -> 156 | expect(@section.$summary.is(@summary)).to.eql true 157 | 158 | it 'sets a data-collapse summary attribute on summry', -> 159 | expect(@section.$summary.data()).to.eql { collapseSummary: '' } 160 | 161 | it 'injects a link inside el', -> 162 | expect(@section.$summary.find("a").length).to.eql 1 163 | 164 | it 'finds and sets details', -> 165 | expect(@section.$details.get(0)).to.eql @details.get(0) 166 | 167 | it 'applies a parent', -> 168 | expect(@section.parent).to.eql @parent 169 | 170 | it 'applies parent options', -> 171 | expect(@section.options).to.eql @parent.options 172 | 173 | it 'pushes to parent sections', -> 174 | expect(@parent.sections[0]).to.eql @section 175 | 176 | describe 'state is 1', -> 177 | 178 | before -> 179 | sinon.stub(jQueryCollapseSection.prototype, '_index').returns(1) 180 | @open = sinon.stub(jQueryCollapseSection.prototype, 'open') 181 | @section = new jQueryCollapseSection(@summary, @parent) 182 | 183 | after -> 184 | jQueryCollapseSection.prototype._index.restore() 185 | jQueryCollapseSection.prototype.open.restore() 186 | 187 | it 'opens with a bypass', -> 188 | expect(@open.calledWith(true)).to.be.ok 189 | 190 | describe 'state is 0', -> 191 | 192 | before -> 193 | sinon.stub(jQueryCollapseSection.prototype, '_index').returns(0) 194 | @close = sinon.stub(jQueryCollapseSection.prototype, 'close') 195 | @section = new jQueryCollapseSection(@summary, @parent) 196 | 197 | after -> 198 | jQueryCollapseSection.prototype._index.restore() 199 | jQueryCollapseSection.prototype.close.restore() 200 | 201 | it 'closes with a bypass', -> 202 | expect(@close.calledWith(true)).to.be.ok 203 | 204 | describe 'section summary has .open class', -> 205 | 206 | before -> 207 | sinon.stub($.fn, 'is').withArgs('.open').returns(true) 208 | @open = sinon.stub(jQueryCollapseSection.prototype, 'open') 209 | new jQueryCollapseSection(@summary, @parent) 210 | 211 | after -> 212 | $.fn.is.restore() 213 | jQueryCollapseSection.prototype.open.restore() 214 | 215 | it 'opens with a bypass', -> 216 | expect(@open.calledWith(true)).to.be.ok 217 | -------------------------------------------------------------------------------- /vendor/json2.js: -------------------------------------------------------------------------------- 1 | /* 2 | json2.js 3 | 2011-10-19 4 | 5 | Public Domain. 6 | 7 | NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 8 | 9 | See http://www.JSON.org/js.html 10 | 11 | 12 | This code should be minified before deployment. 13 | See http://javascript.crockford.com/jsmin.html 14 | 15 | USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO 16 | NOT CONTROL. 17 | 18 | 19 | This file creates a global JSON object containing two methods: stringify 20 | and parse. 21 | 22 | JSON.stringify(value, replacer, space) 23 | value any JavaScript value, usually an object or array. 24 | 25 | replacer an optional parameter that determines how object 26 | values are stringified for objects. It can be a 27 | function or an array of strings. 28 | 29 | space an optional parameter that specifies the indentation 30 | of nested structures. If it is omitted, the text will 31 | be packed without extra whitespace. If it is a number, 32 | it will specify the number of spaces to indent at each 33 | level. If it is a string (such as '\t' or ' '), 34 | it contains the characters used to indent at each level. 35 | 36 | This method produces a JSON text from a JavaScript value. 37 | 38 | When an object value is found, if the object contains a toJSON 39 | method, its toJSON method will be called and the result will be 40 | stringified. A toJSON method does not serialize: it returns the 41 | value represented by the name/value pair that should be serialized, 42 | or undefined if nothing should be serialized. The toJSON method 43 | will be passed the key associated with the value, and this will be 44 | bound to the value 45 | 46 | For example, this would serialize Dates as ISO strings. 47 | 48 | Date.prototype.toJSON = function (key) { 49 | function f(n) { 50 | // Format integers to have at least two digits. 51 | return n < 10 ? '0' + n : n; 52 | } 53 | 54 | return this.getUTCFullYear() + '-' + 55 | f(this.getUTCMonth() + 1) + '-' + 56 | f(this.getUTCDate()) + 'T' + 57 | f(this.getUTCHours()) + ':' + 58 | f(this.getUTCMinutes()) + ':' + 59 | f(this.getUTCSeconds()) + 'Z'; 60 | }; 61 | 62 | You can provide an optional replacer method. It will be passed the 63 | key and value of each member, with this bound to the containing 64 | object. The value that is returned from your method will be 65 | serialized. If your method returns undefined, then the member will 66 | be excluded from the serialization. 67 | 68 | If the replacer parameter is an array of strings, then it will be 69 | used to select the members to be serialized. It filters the results 70 | such that only members with keys listed in the replacer array are 71 | stringified. 72 | 73 | Values that do not have JSON representations, such as undefined or 74 | functions, will not be serialized. Such values in objects will be 75 | dropped; in arrays they will be replaced with null. You can use 76 | a replacer function to replace those with JSON values. 77 | JSON.stringify(undefined) returns undefined. 78 | 79 | The optional space parameter produces a stringification of the 80 | value that is filled with line breaks and indentation to make it 81 | easier to read. 82 | 83 | If the space parameter is a non-empty string, then that string will 84 | be used for indentation. If the space parameter is a number, then 85 | the indentation will be that many spaces. 86 | 87 | Example: 88 | 89 | text = JSON.stringify(['e', {pluribus: 'unum'}]); 90 | // text is '["e",{"pluribus":"unum"}]' 91 | 92 | 93 | text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); 94 | // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' 95 | 96 | text = JSON.stringify([new Date()], function (key, value) { 97 | return this[key] instanceof Date ? 98 | 'Date(' + this[key] + ')' : value; 99 | }); 100 | // text is '["Date(---current time---)"]' 101 | 102 | 103 | JSON.parse(text, reviver) 104 | This method parses a JSON text to produce an object or array. 105 | It can throw a SyntaxError exception. 106 | 107 | The optional reviver parameter is a function that can filter and 108 | transform the results. It receives each of the keys and values, 109 | and its return value is used instead of the original value. 110 | If it returns what it received, then the structure is not modified. 111 | If it returns undefined then the member is deleted. 112 | 113 | Example: 114 | 115 | // Parse the text. Values that look like ISO date strings will 116 | // be converted to Date objects. 117 | 118 | myData = JSON.parse(text, function (key, value) { 119 | var a; 120 | if (typeof value === 'string') { 121 | a = 122 | /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 123 | if (a) { 124 | return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], 125 | +a[5], +a[6])); 126 | } 127 | } 128 | return value; 129 | }); 130 | 131 | myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { 132 | var d; 133 | if (typeof value === 'string' && 134 | value.slice(0, 5) === 'Date(' && 135 | value.slice(-1) === ')') { 136 | d = new Date(value.slice(5, -1)); 137 | if (d) { 138 | return d; 139 | } 140 | } 141 | return value; 142 | }); 143 | 144 | 145 | This is a reference implementation. You are free to copy, modify, or 146 | redistribute. 147 | */ 148 | 149 | /*jslint evil: true, regexp: true */ 150 | 151 | /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, 152 | call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, 153 | getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, 154 | lastIndex, length, parse, prototype, push, replace, slice, stringify, 155 | test, toJSON, toString, valueOf 156 | */ 157 | 158 | 159 | // Create a JSON object only if one does not already exist. We create the 160 | // methods in a closure to avoid creating global variables. 161 | 162 | var JSON; 163 | if (!JSON) { 164 | JSON = {}; 165 | } 166 | 167 | (function () { 168 | 'use strict'; 169 | 170 | function f(n) { 171 | // Format integers to have at least two digits. 172 | return n < 10 ? '0' + n : n; 173 | } 174 | 175 | if (typeof Date.prototype.toJSON !== 'function') { 176 | 177 | Date.prototype.toJSON = function (key) { 178 | 179 | return isFinite(this.valueOf()) 180 | ? this.getUTCFullYear() + '-' + 181 | f(this.getUTCMonth() + 1) + '-' + 182 | f(this.getUTCDate()) + 'T' + 183 | f(this.getUTCHours()) + ':' + 184 | f(this.getUTCMinutes()) + ':' + 185 | f(this.getUTCSeconds()) + 'Z' 186 | : null; 187 | }; 188 | 189 | String.prototype.toJSON = 190 | Number.prototype.toJSON = 191 | Boolean.prototype.toJSON = function (key) { 192 | return this.valueOf(); 193 | }; 194 | } 195 | 196 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 197 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 198 | gap, 199 | indent, 200 | meta = { // table of character substitutions 201 | '\b': '\\b', 202 | '\t': '\\t', 203 | '\n': '\\n', 204 | '\f': '\\f', 205 | '\r': '\\r', 206 | '"' : '\\"', 207 | '\\': '\\\\' 208 | }, 209 | rep; 210 | 211 | 212 | function quote(string) { 213 | 214 | // If the string contains no control characters, no quote characters, and no 215 | // backslash characters, then we can safely slap some quotes around it. 216 | // Otherwise we must also replace the offending characters with safe escape 217 | // sequences. 218 | 219 | escapable.lastIndex = 0; 220 | return escapable.test(string) ? '"' + string.replace(escapable, function (a) { 221 | var c = meta[a]; 222 | return typeof c === 'string' 223 | ? c 224 | : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 225 | }) + '"' : '"' + string + '"'; 226 | } 227 | 228 | 229 | function str(key, holder) { 230 | 231 | // Produce a string from holder[key]. 232 | 233 | var i, // The loop counter. 234 | k, // The member key. 235 | v, // The member value. 236 | length, 237 | mind = gap, 238 | partial, 239 | value = holder[key]; 240 | 241 | // If the value has a toJSON method, call it to obtain a replacement value. 242 | 243 | if (value && typeof value === 'object' && 244 | typeof value.toJSON === 'function') { 245 | value = value.toJSON(key); 246 | } 247 | 248 | // If we were called with a replacer function, then call the replacer to 249 | // obtain a replacement value. 250 | 251 | if (typeof rep === 'function') { 252 | value = rep.call(holder, key, value); 253 | } 254 | 255 | // What happens next depends on the value's type. 256 | 257 | switch (typeof value) { 258 | case 'string': 259 | return quote(value); 260 | 261 | case 'number': 262 | 263 | // JSON numbers must be finite. Encode non-finite numbers as null. 264 | 265 | return isFinite(value) ? String(value) : 'null'; 266 | 267 | case 'boolean': 268 | case 'null': 269 | 270 | // If the value is a boolean or null, convert it to a string. Note: 271 | // typeof null does not produce 'null'. The case is included here in 272 | // the remote chance that this gets fixed someday. 273 | 274 | return String(value); 275 | 276 | // If the type is 'object', we might be dealing with an object or an array or 277 | // null. 278 | 279 | case 'object': 280 | 281 | // Due to a specification blunder in ECMAScript, typeof null is 'object', 282 | // so watch out for that case. 283 | 284 | if (!value) { 285 | return 'null'; 286 | } 287 | 288 | // Make an array to hold the partial results of stringifying this object value. 289 | 290 | gap += indent; 291 | partial = []; 292 | 293 | // Is the value an array? 294 | 295 | if (Object.prototype.toString.apply(value) === '[object Array]') { 296 | 297 | // The value is an array. Stringify every element. Use null as a placeholder 298 | // for non-JSON values. 299 | 300 | length = value.length; 301 | for (i = 0; i < length; i += 1) { 302 | partial[i] = str(i, value) || 'null'; 303 | } 304 | 305 | // Join all of the elements together, separated with commas, and wrap them in 306 | // brackets. 307 | 308 | v = partial.length === 0 309 | ? '[]' 310 | : gap 311 | ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' 312 | : '[' + partial.join(',') + ']'; 313 | gap = mind; 314 | return v; 315 | } 316 | 317 | // If the replacer is an array, use it to select the members to be stringified. 318 | 319 | if (rep && typeof rep === 'object') { 320 | length = rep.length; 321 | for (i = 0; i < length; i += 1) { 322 | if (typeof rep[i] === 'string') { 323 | k = rep[i]; 324 | v = str(k, value); 325 | if (v) { 326 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 327 | } 328 | } 329 | } 330 | } else { 331 | 332 | // Otherwise, iterate through all of the keys in the object. 333 | 334 | for (k in value) { 335 | if (Object.prototype.hasOwnProperty.call(value, k)) { 336 | v = str(k, value); 337 | if (v) { 338 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 339 | } 340 | } 341 | } 342 | } 343 | 344 | // Join all of the member texts together, separated with commas, 345 | // and wrap them in braces. 346 | 347 | v = partial.length === 0 348 | ? '{}' 349 | : gap 350 | ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' 351 | : '{' + partial.join(',') + '}'; 352 | gap = mind; 353 | return v; 354 | } 355 | } 356 | 357 | // If the JSON object does not yet have a stringify method, give it one. 358 | 359 | if (typeof JSON.stringify !== 'function') { 360 | JSON.stringify = function (value, replacer, space) { 361 | 362 | // The stringify method takes a value and an optional replacer, and an optional 363 | // space parameter, and returns a JSON text. The replacer can be a function 364 | // that can replace values, or an array of strings that will select the keys. 365 | // A default replacer method can be provided. Use of the space parameter can 366 | // produce text that is more easily readable. 367 | 368 | var i; 369 | gap = ''; 370 | indent = ''; 371 | 372 | // If the space parameter is a number, make an indent string containing that 373 | // many spaces. 374 | 375 | if (typeof space === 'number') { 376 | for (i = 0; i < space; i += 1) { 377 | indent += ' '; 378 | } 379 | 380 | // If the space parameter is a string, it will be used as the indent string. 381 | 382 | } else if (typeof space === 'string') { 383 | indent = space; 384 | } 385 | 386 | // If there is a replacer, it must be a function or an array. 387 | // Otherwise, throw an error. 388 | 389 | rep = replacer; 390 | if (replacer && typeof replacer !== 'function' && 391 | (typeof replacer !== 'object' || 392 | typeof replacer.length !== 'number')) { 393 | throw new Error('JSON.stringify'); 394 | } 395 | 396 | // Make a fake root object containing our value under the key of ''. 397 | // Return the result of stringifying the value. 398 | 399 | return str('', {'': value}); 400 | }; 401 | } 402 | 403 | 404 | // If the JSON object does not yet have a parse method, give it one. 405 | 406 | if (typeof JSON.parse !== 'function') { 407 | JSON.parse = function (text, reviver) { 408 | 409 | // The parse method takes a text and an optional reviver function, and returns 410 | // a JavaScript value if the text is a valid JSON text. 411 | 412 | var j; 413 | 414 | function walk(holder, key) { 415 | 416 | // The walk method is used to recursively walk the resulting structure so 417 | // that modifications can be made. 418 | 419 | var k, v, value = holder[key]; 420 | if (value && typeof value === 'object') { 421 | for (k in value) { 422 | if (Object.prototype.hasOwnProperty.call(value, k)) { 423 | v = walk(value, k); 424 | if (v !== undefined) { 425 | value[k] = v; 426 | } else { 427 | delete value[k]; 428 | } 429 | } 430 | } 431 | } 432 | return reviver.call(holder, key, value); 433 | } 434 | 435 | 436 | // Parsing happens in four stages. In the first stage, we replace certain 437 | // Unicode characters with escape sequences. JavaScript handles many characters 438 | // incorrectly, either silently deleting them, or treating them as line endings. 439 | 440 | text = String(text); 441 | cx.lastIndex = 0; 442 | if (cx.test(text)) { 443 | text = text.replace(cx, function (a) { 444 | return '\\u' + 445 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 446 | }); 447 | } 448 | 449 | // In the second stage, we run the text against regular expressions that look 450 | // for non-JSON patterns. We are especially concerned with '()' and 'new' 451 | // because they can cause invocation, and '=' because it can cause mutation. 452 | // But just to be safe, we want to reject all unexpected forms. 453 | 454 | // We split the second stage into 4 regexp operations in order to work around 455 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 456 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 457 | // replace all simple value tokens with ']' characters. Third, we delete all 458 | // open brackets that follow a colon or comma or that begin the text. Finally, 459 | // we look to see that the remaining characters are only whitespace or ']' or 460 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 461 | 462 | if (/^[\],:{}\s]*$/ 463 | .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') 464 | .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') 465 | .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 466 | 467 | // In the third stage we use the eval function to compile the text into a 468 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity 469 | // in JavaScript: it can begin a block or an object literal. We wrap the text 470 | // in parens to eliminate the ambiguity. 471 | 472 | j = eval('(' + text + ')'); 473 | 474 | // In the optional fourth stage, we recursively walk the new structure, passing 475 | // each name/value pair to a reviver function for possible transformation. 476 | 477 | return typeof reviver === 'function' 478 | ? walk({'': j}, '') 479 | : j; 480 | } 481 | 482 | // If the text is not JSON parseable, then a SyntaxError is thrown. 483 | 484 | throw new SyntaxError('JSON.parse'); 485 | }; 486 | } 487 | }()); 488 | --------------------------------------------------------------------------------