├── .gitignore
├── LICENSE.txt
├── README.md
├── bookmarklet.html
├── css
└── style.css
├── dev1.html
├── dev2.html
├── dev3.html
├── dist
├── fixto.js
└── fixto.min.js
├── grunt.js
├── index.html
├── libs
├── jasmine-2.0.0
│ ├── boot.js
│ ├── console.js
│ ├── jasmine-html.js
│ ├── jasmine.css
│ ├── jasmine.js
│ └── jasmine_favicon.png
├── jquery-loader.js
├── jquery
│ └── jquery.js
└── qunit
│ ├── qunit.css
│ └── qunit.js
├── modernizr-2.5.3.min.js
├── package.json
├── robots.txt
├── src
└── fixto.js
├── test
├── fixto.html
├── fixto_test.js
├── mimic-node.html
└── mimic-node_test.js
└── tests
└── SpecRunner.html
/.gitignore:
--------------------------------------------------------------------------------
1 | *.DS_STORE
2 | node_modules
3 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Burak Barakaci
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, 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,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # fixto
2 |
3 | A jQuery plugin for sticky positioning. Fix containers to the viewport relative to an ancestor. To see it in action you can see the [demo page][demo] or development pages [development page 1][dev1], [development page 2][dev2], [development page 3][dev3].
4 |
5 | [demo]: http://bbarakaci.github.com/fixto
6 | [dev1]: http://bbarakaci.github.com/fixto/dev1.html
7 | [dev2]: http://bbarakaci.github.com/fixto/dev2.html
8 | [dev3]: http://bbarakaci.github.com/fixto/dev3.html
9 |
10 | - [Features](#features)
11 | - [Browser support](#browser-support)
12 | - [Getting Started](#getting-started)
13 | - [Usage](#usage)
14 | - [Styling](#styling)
15 | - [Options](#options)
16 | - [Public Methods](#public-methods)
17 | - [Known issues](#known-issues)
18 | - [Release notes](#release-notes)
19 | - [Position sticky caveats](#position-sticky-caveats)
20 |
21 | ## Features
22 | - Responsive
23 | - Handles multiple instances
24 | - Start, stop, destroy
25 | - Sensitive to viewport height
26 | - Handles positioning context created by transformed ancestors.
27 | - Uses native position sticky when available
28 |
29 | ## Browser support
30 |
31 | Modern browsers, ie8+ are supported.
32 |
33 | ## Getting Started
34 | Download the [production version][min] or the [development version][max].
35 |
36 | [min]: https://raw.github.com/bbarakaci/fixto/master/dist/fixto.min.js
37 | [max]: https://raw.github.com/bbarakaci/fixto/master/dist/fixto.js
38 |
39 | Add jQuery
40 |
41 |
42 |
43 | Add fixTo
44 |
45 |
46 |
47 | ## Usage
48 |
49 | Use it with jQuery
50 |
51 | $('#nav').fixTo('body');
52 |
53 | You can fix multiple containers to multiple ancestors. Make sure your selectors match exactly.
54 |
55 | $('.sticky').fixTo('.sticky-holder');
56 |
57 | Passing options
58 |
59 | $('#left-banner').fixTo('#left-column', {
60 | className : 'my-class-name',
61 | zIndex: 10,
62 | mind: '#header',
63 | top: 20
64 | });
65 |
66 | Instantiate without jQuery:
67 |
68 | var sticky = fixto.fixTo(domElementToFix, domElementToBeFixed, options);
69 |
70 | ## Styling
71 |
72 | When the container is fixed, it will receive the class name `fixto-fixed`. You may use this class or you may pass any other class name as an option. This feature will not work when native postion sticky is used. See [known issues](#known-issues)
73 |
74 | ## Options
75 |
76 | ### className (String)
77 |
78 | See above example.
79 |
80 | ### zIndex (Number)
81 |
82 | Although you can set z-index with css, it is possible to pass a `zIndex` option. See above example.
83 |
84 | ### mind (selector)
85 |
86 | When you have other fixed containers on a page, pass those containers as mind option to prevent overlapping.
87 |
88 | Example
89 |
90 | $('#header').fixTo('body');
91 |
92 | $('#left-banner').fixTo('#left-column', {
93 | mind: '#header'
94 | });
95 |
96 | Selector can be in any form that jQuery can handle. You can pass multiple elements.
97 |
98 | ### top (Number)
99 |
100 | In pixels. Fixed element will preserve a gap on its top.
101 |
102 | ### useNativeSticky (Boolean)
103 | Fixto will use native position sticky when supported by the browser. Set this option to `false` to disable.
104 |
105 | This option can not be overwritten after initialization. If you need to do so, you can destroy the instance and create a new one.
106 |
107 | While fixto can fix a container to any of its ancestors, native sticky will fix it to the nearest containing block, which is generally its parent.
108 |
109 | There is no native way to know if the container is sticked, so it will not receive the css class name.
110 |
111 | Native sticky will perform very well as all the work is done by the browser. Without native sticky you will notice delayed response and undesired effects on IOS as all the javascript execuion is halted on scroll. Setting `-webkit-transform: translate(0)` to parent container will eliminate undesired effect, delayed response will remain.
112 |
113 | ### mindBottomPadding (Boolean)
114 | Allows for scrolling through the parent's bottom padding. Defaults to `true`, making fixed element stop when parent's bottom padding is reached. Won't work with native "sticky" implementation.
115 |
116 | ### mindViewport (Boolean)
117 | If set to true, the viewport height will be taken into consideration. If viewport height is shorter than the sticky element, it will not stick not to render any content inaccesible. This option won't work with native position sticky.
118 |
119 | ## Public Methods
120 |
121 | Following methods can be called directly on the instance or with jQuery.
122 |
123 | ### refresh
124 |
125 | Fixto tries to adapt to the layout during scroll and window resize. In case the layout changes dynamically at any time, use refresh to force fixto to adapt.
126 |
127 | instance.refresh();
128 |
129 | jQuery:
130 |
131 | $('#nav').fixTo('refresh');
132 |
133 | ### setOptions(options)
134 |
135 | Resets the options. Fixto will refresh itself automatically after this method is used.
136 |
137 | instance.setOptions({
138 | top: 10
139 | });
140 |
141 | jQuery:
142 |
143 | $('#nav').fixTo('setOptions', {
144 | top: 10
145 | });
146 |
147 | ### destroy
148 |
149 | Destroys the instance:
150 |
151 | instance.destroy();
152 |
153 | jQuery:
154 |
155 | $('#nav').fixTo('destroy');
156 |
157 | ### stop
158 |
159 | Stops the instances behavior without destroying the instance.
160 |
161 | instance.stop();
162 |
163 | jQuery:
164 |
165 | $('#nav').fixTo('stop');
166 |
167 | ### start
168 |
169 | Starts the instances behavior.
170 |
171 | instance.start();
172 |
173 | jQuery:
174 |
175 | $('#nav').fixTo('start');
176 |
177 | ## Known issues
178 |
179 | - Doesn't work on elements having `margin:auto`. You will need an additional wrapper around the element. This is because webkit differs from other browsers about reporting the computed margin values.
180 | - There is flickering on Safari when there is a transformed parent and if native sticky positioning is not used. Still couldn't resolve that. Safari already supports native sticky positioning so you will observe this issue only if you disable native sticky support.
181 | - Some features are not implemented for the native position sticky version. Fixto normally does a lot of calculations during scroll. These calculations are not done when using native position sticky. Although it is possible to implement these features by doing same calculations, for now it is decided not to do so, for performance reasons and for the sake of obeying to the natural development of position sticky. Finally, one day, we will not need to use fixto anymore.
182 |
183 | Or we will choose the other path. We will decide to implement the features we need to have fixto as a plugin that does its own magic. Please open an issue if you would like to see additional features when running on native sticky mode. If there is an issue, just participate with your +1. It will help us to decide for the future.
184 |
185 | ## Release notes
186 | ### 0.5.0
187 | - Sensitivity to the viewport is now an optional feature.
188 | - Added MIT license.
189 |
190 | ### 0.4.0
191 | - Added "mindBottomPadding" option, to make including parent's bottom padding optional when calculating max bottom position of the fixed element
192 |
193 | ### 0.3.1
194 | - Bugfix #15
195 |
196 | ### 0.3.0
197 | - Use native position sticky when supported.
198 | - Enabled for touch devices
199 | - Margin top of the fixed element will be ignored at fixed state, to be consistent with native sticky. This might break your existing layout if you used margin top on the target container with previous versions of fixto.
200 | - Top option added
201 | - useNativeSticky option added
202 | - setOptions method added
203 | - refresh method added
204 | - No need to use it on document ready. Feature detection will be done during first instantiation.
205 |
206 | ### 0.2.0
207 | - Ancestors with css transform rules does not break the functionality anymore.
208 | - Needs to be used only during or after document ready
209 |
210 | ## Position sticky caveats
211 | You might be wondering why position sticky does not work for your layout.
212 |
213 | Position sticky won’t work when:
214 |
215 | - the positioned element has an ancestor with an unintended overflow value of “auto”, “hidden” or “scroll”.
216 | - the positioned element has an ancestor with a computed height of zero, means, all the elements inside the container ran out of flow (floated or absolutely positioned) thus its height is 0. Clear properly if you have floated elements, but you can’t use overflow on the parent to clear, because of the rule above.
217 |
--------------------------------------------------------------------------------
/bookmarklet.html:
--------------------------------------------------------------------------------
1 |
8 | fixTo
--------------------------------------------------------------------------------
/css/style.css:
--------------------------------------------------------------------------------
1 | /* HTML5 Boilerplate */
2 |
3 | article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { display: block; }
4 | audio, canvas, video { display: inline-block; *display: inline; *zoom: 1; }
5 | audio:not([controls]) { display: none; }
6 | [hidden] { display: none; }
7 |
8 | html { font-size: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
9 | html, button, input, select, textarea { font-family: sans-serif; color: #222; }
10 | body { margin: 0; font-size: 1em; line-height: 1.4; }
11 |
12 | ::-moz-selection { background: #fe57a1; color: #fff; text-shadow: none; }
13 | ::selection { background: #fe57a1; color: #fff; text-shadow: none; }
14 |
15 | a { color: #00e; }
16 | a:visited { color: #551a8b; }
17 | a:hover { color: #06e; }
18 | a:focus { outline: thin dotted; }
19 | a:hover, a:active { outline: 0; }
20 | abbr[title] { border-bottom: 1px dotted; }
21 | b, strong { font-weight: bold; }
22 | blockquote { margin: 1em 40px; }
23 | dfn { font-style: italic; }
24 | hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; }
25 | ins { background: #ff9; color: #000; text-decoration: none; }
26 | mark { background: #ff0; color: #000; font-style: italic; font-weight: bold; }
27 | pre, code, kbd, samp { font-family: monospace, serif; _font-family: 'courier new', monospace; font-size: 1em; }
28 | pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; }
29 |
30 | q { quotes: none; }
31 | q:before, q:after { content: ""; content: none; }
32 | small { font-size: 85%; }
33 | sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; }
34 | sup { top: -0.5em; }
35 | sub { bottom: -0.25em; }
36 |
37 | ul, ol { margin: 1em 0; padding: 0 0 0 40px; }
38 | dd { margin: 0 0 0 40px; }
39 | nav ul, nav ol { list-style: none; list-style-image: none; margin: 0; padding: 0; }
40 |
41 | img { border: 0; -ms-interpolation-mode: bicubic; vertical-align: middle; }
42 | svg:not(:root) { overflow: hidden; }
43 | figure { margin: 0; }
44 |
45 | form { margin: 0; }
46 | fieldset { border: 0; margin: 0; padding: 0; }
47 |
48 | label { cursor: pointer; }
49 | legend { border: 0; *margin-left: -7px; padding: 0; white-space: normal; }
50 | button, input, select, textarea { font-size: 100%; margin: 0; vertical-align: baseline; *vertical-align: middle; }
51 | button, input { line-height: normal; }
52 | button, input[type="button"], input[type="reset"], input[type="submit"] { cursor: pointer; -webkit-appearance: button; *overflow: visible; }
53 | button[disabled], input[disabled] { cursor: default; }
54 | input[type="checkbox"], input[type="radio"] { box-sizing: border-box; padding: 0; *width: 13px; *height: 13px; }
55 | input[type="search"] { -webkit-appearance: textfield; -moz-box-sizing: content-box; -webkit-box-sizing: content-box; box-sizing: content-box; }
56 | input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; }
57 | button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; }
58 | textarea { overflow: auto; vertical-align: top; resize: vertical; }
59 | input:valid, textarea:valid { }
60 | input:invalid, textarea:invalid { background-color: #f0dddd; }
61 |
62 | table { border-collapse: collapse; border-spacing: 0; }
63 | td { vertical-align: top; }
64 |
65 | .chromeframe { margin: 0.2em 0; background: #ccc; color: black; padding: 0.2em 0; }
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | @media only screen and (min-width: 35em) {
76 |
77 |
78 | }
79 |
80 | .ir { display: block; border: 0; text-indent: -999em; overflow: hidden; background-color: transparent; background-repeat: no-repeat; text-align: left; direction: ltr; *line-height: 0; }
81 | .ir br { display: none; }
82 | .hidden { display: none !important; visibility: hidden; }
83 | .visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; }
84 | .visuallyhidden.focusable:active, .visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; }
85 | .invisible { visibility: hidden; }
86 | .clearfix:before, .clearfix:after { content: ""; display: table; }
87 | .clearfix:after { clear: both; }
88 | .clearfix { *zoom: 1; }
89 |
90 | @media print {
91 | * { background: transparent !important; color: black !important; box-shadow:none !important; text-shadow: none !important; filter:none !important; -ms-filter: none !important; }
92 | a, a:visited { text-decoration: underline; }
93 | a[href]:after { content: " (" attr(href) ")"; }
94 | abbr[title]:after { content: " (" attr(title) ")"; }
95 | .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; }
96 | pre, blockquote { border: 1px solid #999; page-break-inside: avoid; }
97 | thead { display: table-header-group; }
98 | tr, img { page-break-inside: avoid; }
99 | img { max-width: 100% !important; }
100 | @page { margin: 0.5cm; }
101 | p, h2, h3 { orphans: 3; widows: 3; }
102 | h2, h3 { page-break-after: avoid; }
103 | }
104 |
--------------------------------------------------------------------------------
/dev1.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce nulla ligula, egestas sit amet malesuada vel, iaculis at mi. Donec vestibulum nunc at neque pretium et egestas erat scelerisque. Pellentesque hendrerit egestas eros, non mattis arcu viverra vitae. Praesent tincidunt magna at urna ultrices consequat. Phasellus cursus rutrum lectus non dapibus. Curabitur lorem lorem, blandit vitae vehicula eget, sagittis vitae dolor. Integer sed ipsum sed elit facilisis commodo quis a urna. Mauris scelerisque nibh in metus tempor tristique. Fusce commodo sagittis ultrices. Pellentesque sit amet fermentum orci. Maecenas sollicitudin arcu non felis mollis ac laoreet augue mattis. Morbi condimentum turpis eu ipsum dapibus et euismod risus dapibus. Mauris mauris nisl, rutrum eu ultrices vel, cursus at massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
52 |
53 |
54 |
55 |
56 | Sticky has margins
57 |
58 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce nulla ligula, egestas sit amet malesuada vel, iaculis at mi. Donec vestibulum nunc at neque pretium et egestas erat scelerisque. Pellentesque hendrerit egestas eros, non mattis arcu viverra vitae. Praesent tincidunt magna at urna ultrices consequat. Phasellus cursus rutrum lectus non dapibus. Curabitur lorem lorem, blandit vitae vehicula eget, sagittis vitae dolor. Integer sed ipsum sed elit facilisis commodo quis a urna. Mauris scelerisque nibh in metus tempor tristique. Fusce commodo sagittis ultrices. Pellentesque sit amet fermentum orci. Maecenas sollicitudin arcu non felis mollis ac laoreet augue mattis. Morbi condimentum turpis eu ipsum dapibus et euismod risus dapibus. Mauris mauris nisl, rutrum eu ultrices vel, cursus at massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
59 |
60 |
61 |
62 | Sticky has borders
63 |
64 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce nulla ligula, egestas sit amet malesuada vel, iaculis at mi. Donec vestibulum nunc at neque pretium et egestas erat scelerisque. Pellentesque hendrerit egestas eros, non mattis arcu viverra vitae. Praesent tincidunt magna at urna ultrices consequat. Phasellus cursus rutrum lectus non dapibus. Curabitur lorem lorem, blandit vitae vehicula eget, sagittis vitae dolor. Integer sed ipsum sed elit facilisis commodo quis a urna. Mauris scelerisque nibh in metus tempor tristique. Fusce commodo sagittis ultrices. Pellentesque sit amet fermentum orci. Maecenas sollicitudin arcu non felis mollis ac laoreet augue mattis. Morbi condimentum turpis eu ipsum dapibus et euismod risus dapibus. Mauris mauris nisl, rutrum eu ultrices vel, cursus at massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
65 |
66 |
67 |
68 |
69 | Sticky floats left.
70 |
71 |
72 |
73 |
74 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce nulla ligula, egestas sit amet malesuada vel, iaculis at mi. Donec vestibulum nunc at neque pretium et egestas erat scelerisque. Pellentesque hendrerit egestas eros, non mattis arcu viverra vitae. Praesent tincidunt magna at urna ultrices consequat. Phasellus cursus rutrum lectus non dapibus. Curabitur lorem lorem, blandit vitae vehicula eget, sagittis vitae dolor. Integer sed ipsum sed elit facilisis commodo quis a urna. Mauris scelerisque nibh in metus tempor tristique. Fusce commodo sagittis ultrices. Pellentesque sit amet fermentum orci. Maecenas sollicitudin arcu non felis mollis ac laoreet augue mattis. Morbi condimentum turpis eu ipsum dapibus et euismod risus dapibus. Mauris mauris nisl, rutrum eu ultrices vel, cursus at massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
75 |
76 |
77 |
78 |
79 |
80 | Sticky floats right
81 |
82 |
83 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce nulla ligula, egestas sit amet malesuada vel, iaculis at mi. Donec vestibulum nunc at neque pretium et egestas erat scelerisque. Pellentesque hendrerit egestas eros, non mattis arcu viverra vitae. Praesent tincidunt magna at urna ultrices consequat. Phasellus cursus rutrum lectus non dapibus. Curabitur lorem lorem, blandit vitae vehicula eget, sagittis vitae dolor. Integer sed ipsum sed elit facilisis commodo quis a urna. Mauris scelerisque nibh in metus tempor tristique. Fusce commodo sagittis ultrices. Pellentesque sit amet fermentum orci. Maecenas sollicitudin arcu non felis mollis ac laoreet augue mattis. Morbi condimentum turpis eu ipsum dapibus et euismod risus dapibus. Mauris mauris nisl, rutrum eu ultrices vel, cursus at massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
84 |
85 |
86 |
87 |
88 |
89 | Sticky is tall. It will not be fixed if viewport is shorter, when mindViewport option set to true. (Only if native position sticky is disabled.)
90 |
91 |
92 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce nulla ligula, egestas sit amet malesuada vel, iaculis at mi. Donec vestibulum nunc at neque pretium et egestas erat scelerisque. Pellentesque hendrerit egestas eros, non mattis arcu viverra vitae. Praesent tincidunt magna at urna ultrices consequat. Phasellus cursus rutrum lectus non dapibus. Curabitur lorem lorem, blandit vitae vehicula eget, sagittis vitae dolor. Integer sed ipsum sed elit facilisis commodo quis a urna. Mauris scelerisque nibh in metus tempor tristique. Fusce commodo sagittis ultrices. Pellentesque sit amet fermentum orci. Maecenas sollicitudin arcu non felis mollis ac laoreet augue mattis. Morbi condimentum turpis eu ipsum dapibus et euismod risus dapibus. Mauris mauris nisl, rutrum eu ultrices vel, cursus at massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
93 |
94 |
95 |
96 |
97 | Parent's bottom padding is not respected due to disabled "mindBottomPadding" option. (Only if native position sticky is disabled.)
98 |
99 |
100 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce nulla ligula, egestas sit amet malesuada vel, iaculis at mi. Donec vestibulum nunc at neque pretium et egestas erat scelerisque. Pellentesque hendrerit egestas eros, non mattis arcu viverra vitae. Praesent tincidunt magna at urna ultrices consequat. Phasellus cursus rutrum lectus non dapibus. Curabitur lorem lorem, blandit vitae vehicula eget, sagittis vitae dolor. Integer sed ipsum sed elit facilisis commodo quis a urna. Mauris scelerisque nibh in metus tempor tristique. Fusce commodo sagittis ultrices. Pellentesque sit amet fermentum orci. Maecenas sollicitudin arcu non felis mollis ac laoreet augue mattis. Morbi condimentum turpis eu ipsum dapibus et euismod risus dapibus. Mauris mauris nisl, rutrum eu ultrices vel, cursus at massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
32 |
33 |
34 |
35 |
36 |
37 |
38 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/dist/fixto.js:
--------------------------------------------------------------------------------
1 | /*! fixto - v0.5.0 - 2016-06-16
2 | * http://github.com/bbarakaci/fixto/*/
3 |
4 |
5 | var fixto = (function ($, window, document) {
6 |
7 | // Start Computed Style. Please do not modify this module here. Modify it from its own repo. See address below.
8 |
9 | /*! Computed Style - v0.1.0 - 2012-07-19
10 | * https://github.com/bbarakaci/computed-style
11 | * Copyright (c) 2012 Burak Barakaci; Licensed MIT */
12 | var computedStyle = (function() {
13 | var computedStyle = {
14 | getAll : function(element){
15 | return document.defaultView.getComputedStyle(element);
16 | },
17 | get : function(element, name){
18 | return this.getAll(element)[name];
19 | },
20 | toFloat : function(value){
21 | return parseFloat(value, 10) || 0;
22 | },
23 | getFloat : function(element,name){
24 | return this.toFloat(this.get(element, name));
25 | },
26 | _getAllCurrentStyle : function(element) {
27 | return element.currentStyle;
28 | }
29 | };
30 |
31 | if (document.documentElement.currentStyle) {
32 | computedStyle.getAll = computedStyle._getAllCurrentStyle;
33 | }
34 |
35 | return computedStyle;
36 |
37 | }());
38 |
39 | // End Computed Style. Modify whatever you want to.
40 |
41 | var mimicNode = (function(){
42 | /*
43 | Class Mimic Node
44 | Dependency : Computed Style
45 | Tries to mimick a dom node taking his styles, dimensions. May go to his repo if gets mature.
46 | */
47 |
48 | function MimicNode(element) {
49 | this.element = element;
50 | this.replacer = document.createElement('div');
51 | this.replacer.style.visibility = 'hidden';
52 | this.hide();
53 | element.parentNode.insertBefore(this.replacer, element);
54 | }
55 |
56 | MimicNode.prototype = {
57 | replace : function(){
58 | var rst = this.replacer.style;
59 | var styles = computedStyle.getAll(this.element);
60 |
61 | // rst.width = computedStyle.width(this.element) + 'px';
62 | // rst.height = this.element.offsetHeight + 'px';
63 |
64 | // Setting offsetWidth
65 | rst.width = this._width();
66 | rst.height = this._height();
67 |
68 | // Adopt margins
69 | rst.marginTop = styles.marginTop;
70 | rst.marginBottom = styles.marginBottom;
71 | rst.marginLeft = styles.marginLeft;
72 | rst.marginRight = styles.marginRight;
73 |
74 | // Adopt positioning
75 | rst.cssFloat = styles.cssFloat;
76 | rst.styleFloat = styles.styleFloat; //ie8;
77 | rst.position = styles.position;
78 | rst.top = styles.top;
79 | rst.right = styles.right;
80 | rst.bottom = styles.bottom;
81 | rst.left = styles.left;
82 | // rst.borderStyle = styles.borderStyle;
83 |
84 | rst.display = styles.display;
85 |
86 | },
87 |
88 | hide: function () {
89 | this.replacer.style.display = 'none';
90 | },
91 |
92 | _width : function(){
93 | return this.element.getBoundingClientRect().width + 'px';
94 | },
95 |
96 | _widthOffset : function(){
97 | return this.element.offsetWidth + 'px';
98 | },
99 |
100 | _height : function(){
101 | return this.element.getBoundingClientRect().height + 'px';
102 | },
103 |
104 | _heightOffset : function(){
105 | return this.element.offsetHeight + 'px';
106 | },
107 |
108 | destroy: function () {
109 | $(this.replacer).remove();
110 |
111 | // set properties to null to break references
112 | for (var prop in this) {
113 | if (this.hasOwnProperty(prop)) {
114 | this[prop] = null;
115 | }
116 | }
117 | }
118 | };
119 |
120 | var bcr = document.documentElement.getBoundingClientRect();
121 | if(!bcr.width){
122 | MimicNode.prototype._width = MimicNode.prototype._widthOffset;
123 | MimicNode.prototype._height = MimicNode.prototype._heightOffset;
124 | }
125 |
126 | return {
127 | MimicNode:MimicNode,
128 | computedStyle:computedStyle
129 | };
130 | }());
131 |
132 | // Class handles vendor prefixes
133 | function Prefix() {
134 | // Cached vendor will be stored when it is detected
135 | this._vendor = null;
136 |
137 | //this._dummy = document.createElement('div');
138 | }
139 |
140 | Prefix.prototype = {
141 |
142 | _vendors: {
143 | webkit: { cssPrefix: '-webkit-', jsPrefix: 'Webkit'},
144 | moz: { cssPrefix: '-moz-', jsPrefix: 'Moz'},
145 | ms: { cssPrefix: '-ms-', jsPrefix: 'ms'},
146 | opera: { cssPrefix: '-o-', jsPrefix: 'O'}
147 | },
148 |
149 | _prefixJsProperty: function(vendor, prop) {
150 | return vendor.jsPrefix + prop[0].toUpperCase() + prop.substr(1);
151 | },
152 |
153 | _prefixValue: function(vendor, value) {
154 | return vendor.cssPrefix + value;
155 | },
156 |
157 | _valueSupported: function(prop, value, dummy) {
158 | // IE8 will throw Illegal Argument when you attempt to set a not supported value.
159 | try {
160 | dummy.style[prop] = value;
161 | return dummy.style[prop] === value;
162 | }
163 | catch(er) {
164 | return false;
165 | }
166 | },
167 |
168 | /**
169 | * Returns true if the property is supported
170 | * @param {string} prop Property name
171 | * @returns {boolean}
172 | */
173 | propertySupported: function(prop) {
174 | // Supported property will return either inine style value or an empty string.
175 | // Undefined means property is not supported.
176 | return document.documentElement.style[prop] !== undefined;
177 | },
178 |
179 | /**
180 | * Returns prefixed property name for js usage
181 | * @param {string} prop Property name
182 | * @returns {string|null}
183 | */
184 | getJsProperty: function(prop) {
185 | // Try native property name first.
186 | if(this.propertySupported(prop)) {
187 | return prop;
188 | }
189 |
190 | // Prefix it if we know the vendor already
191 | if(this._vendor) {
192 | return this._prefixJsProperty(this._vendor, prop);
193 | }
194 |
195 | // We don't know the vendor, try all the possibilities
196 | var prefixed;
197 | for(var vendor in this._vendors) {
198 | prefixed = this._prefixJsProperty(this._vendors[vendor], prop);
199 | if(this.propertySupported(prefixed)) {
200 | // Vendor detected. Cache it.
201 | this._vendor = this._vendors[vendor];
202 | return prefixed;
203 | }
204 | }
205 |
206 | // Nothing worked
207 | return null;
208 | },
209 |
210 | /**
211 | * Returns supported css value for css property. Could be used to check support or get prefixed value string.
212 | * @param {string} prop Property
213 | * @param {string} value Value name
214 | * @returns {string|null}
215 | */
216 | getCssValue: function(prop, value) {
217 | // Create dummy element to test value
218 | var dummy = document.createElement('div');
219 |
220 | // Get supported property name
221 | var jsProperty = this.getJsProperty(prop);
222 |
223 | // Try unprefixed value
224 | if(this._valueSupported(jsProperty, value, dummy)) {
225 | return value;
226 | }
227 |
228 | var prefixedValue;
229 |
230 | // If we know the vendor already try prefixed value
231 | if(this._vendor) {
232 | prefixedValue = this._prefixValue(this._vendor, value);
233 | if(this._valueSupported(jsProperty, prefixedValue, dummy)) {
234 | return prefixedValue;
235 | }
236 | }
237 |
238 | // Try all vendors
239 | for(var vendor in this._vendors) {
240 | prefixedValue = this._prefixValue(this._vendors[vendor], value);
241 | if(this._valueSupported(jsProperty, prefixedValue, dummy)) {
242 | // Vendor detected. Cache it.
243 | this._vendor = this._vendors[vendor];
244 | return prefixedValue;
245 | }
246 | }
247 | // No support for value
248 | return null;
249 | }
250 | };
251 |
252 | var prefix = new Prefix();
253 |
254 | // We will need this frequently. Lets have it as a global until we encapsulate properly.
255 | var transformJsProperty = prefix.getJsProperty('transform');
256 |
257 | // Will hold if browser creates a positioning context for fixed elements.
258 | var fixedPositioningContext;
259 |
260 | // Checks if browser creates a positioning context for fixed elements.
261 | // Transform rule will create a positioning context on browsers who follow the spec.
262 | // Ie for example will fix it according to documentElement
263 | // TODO: Other css rules also effects. perspective creates at chrome but not in firefox. transform-style preserve3d effects.
264 | function checkFixedPositioningContextSupport() {
265 | var support = false;
266 | var parent = document.createElement('div');
267 | var child = document.createElement('div');
268 | parent.appendChild(child);
269 | parent.style[transformJsProperty] = 'translate(0)';
270 | // Make sure there is space on top of parent
271 | parent.style.marginTop = '10px';
272 | parent.style.visibility = 'hidden';
273 | child.style.position = 'fixed';
274 | child.style.top = 0;
275 | document.body.appendChild(parent);
276 | var rect = child.getBoundingClientRect();
277 | // If offset top is greater than 0 meand transformed element created a positioning context.
278 | if(rect.top > 0) {
279 | support = true;
280 | }
281 | // Remove dummy content
282 | document.body.removeChild(parent);
283 | return support;
284 | }
285 |
286 | // It will return null if position sticky is not supported
287 | var nativeStickyValue = prefix.getCssValue('position', 'sticky');
288 |
289 | // It will return null if position fixed is not supported
290 | var fixedPositionValue = prefix.getCssValue('position', 'fixed');
291 |
292 | // Dirty business
293 | var ie = navigator.appName === 'Microsoft Internet Explorer';
294 | var ieversion;
295 |
296 | if(ie){
297 | ieversion = parseFloat(navigator.appVersion.split("MSIE")[1]);
298 | }
299 |
300 | function FixTo(child, parent, options) {
301 | this.child = child;
302 | this._$child = $(child);
303 | this.parent = parent;
304 | this.options = {
305 | className: 'fixto-fixed',
306 | top: 0,
307 | mindViewport: false
308 | };
309 | this._setOptions(options);
310 | }
311 |
312 | FixTo.prototype = {
313 | // Returns the total outerHeight of the elements passed to mind option. Will return 0 if none.
314 | _mindtop: function () {
315 | var top = 0;
316 | if(this._$mind) {
317 | var el;
318 | var rect;
319 | var height;
320 | for(var i=0, l=this._$mind.length; i this._parentBottom || this._scrollTop < (this._fullOffset('offsetTop', this._ghostNode) - this.options.top - this._mindtop())) {
446 | this._unfix();
447 | return;
448 | }
449 | this._adjust();
450 | }
451 | },
452 |
453 | _shouldFix: function() {
454 | if (this._scrollTop < this._parentBottom && this._scrollTop > (this._fullOffset('offsetTop', this.child) - this.options.top - this._mindtop())) {
455 | if (this.options.mindViewport && !this._isViewportAvailable()) {
456 | return false;
457 | }
458 | return true;
459 | }
460 | },
461 |
462 | _isViewportAvailable: function() {
463 | var childStyles = computedStyle.getAll(this.child);
464 | return this._viewportHeight > (this.child.offsetHeight + computedStyle.toFloat(childStyles.marginTop) + computedStyle.toFloat(childStyles.marginBottom));
465 | },
466 |
467 | _adjust: function _adjust() {
468 | var top = 0;
469 | var mindTop = this._mindtop();
470 | var diff = 0;
471 | var childStyles = computedStyle.getAll(this.child);
472 | var context = null;
473 |
474 | if(fixedPositioningContext) {
475 | // Get positioning context.
476 | context = this._getContext();
477 | if(context) {
478 | // There is a positioning context. Top should be according to the context.
479 | top = Math.abs(context.getBoundingClientRect().top);
480 | }
481 | }
482 |
483 | diff = (this._parentBottom - this._scrollTop) - (this.child.offsetHeight + computedStyle.toFloat(childStyles.marginBottom) + mindTop + this.options.top);
484 |
485 | if(diff>0) {
486 | diff = 0;
487 | }
488 |
489 | this.child.style.top = (diff + mindTop + top + this.options.top) - computedStyle.toFloat(childStyles.marginTop) + 'px';
490 | },
491 |
492 | // Calculate cumulative offset of the element.
493 | // Optionally according to context
494 | _fullOffset: function _fullOffset(offsetName, elm, context) {
495 | var offset = elm[offsetName];
496 | var offsetParent = elm.offsetParent;
497 |
498 | // Add offset of the ascendent tree until we reach to the document root or to the given context
499 | while (offsetParent !== null && offsetParent !== context) {
500 | offset = offset + offsetParent[offsetName];
501 | offsetParent = offsetParent.offsetParent;
502 | }
503 |
504 | return offset;
505 | },
506 |
507 | // Get positioning context of the element.
508 | // We know that the closest parent that a transform rule applied will create a positioning context.
509 | _getContext: function() {
510 | var parent;
511 | var element = this.child;
512 | var context = null;
513 | var styles;
514 |
515 | // Climb up the treee until reaching the context
516 | while(!context) {
517 | parent = element.parentNode;
518 | if(parent === document.documentElement) {
519 | return null;
520 | }
521 |
522 | styles = computedStyle.getAll(parent);
523 | // Element has a transform rule
524 | if(styles[transformJsProperty] !== 'none') {
525 | context = parent;
526 | break;
527 | }
528 | element = parent;
529 | }
530 | return context;
531 | },
532 |
533 | _fix: function _fix() {
534 | var child = this.child;
535 | var childStyle = child.style;
536 | var childStyles = computedStyle.getAll(child);
537 | var left = child.getBoundingClientRect().left;
538 | var width = childStyles.width;
539 |
540 | this._saveStyles();
541 |
542 | if(document.documentElement.currentStyle){
543 | // Function for ie<9. When hasLayout is not triggered in ie7, he will report currentStyle as auto, clientWidth as 0. Thus using offsetWidth.
544 | // Opera also falls here
545 | width = (child.offsetWidth) - (computedStyle.toFloat(childStyles.paddingLeft) + computedStyle.toFloat(childStyles.paddingRight) + computedStyle.toFloat(childStyles.borderLeftWidth) + computedStyle.toFloat(childStyles.borderRightWidth)) + 'px';
546 | }
547 |
548 | // Ie still fixes the container according to the viewport.
549 | if(fixedPositioningContext) {
550 | var context = this._getContext();
551 | if(context) {
552 | // There is a positioning context. Left should be according to the context.
553 | left = child.getBoundingClientRect().left - context.getBoundingClientRect().left;
554 | }
555 | }
556 |
557 | this._replacer.replace();
558 |
559 | childStyle.left = (left - computedStyle.toFloat(childStyles.marginLeft)) + 'px';
560 | childStyle.width = width;
561 |
562 | childStyle.position = 'fixed';
563 | childStyle.top = this._mindtop() + this.options.top - computedStyle.toFloat(childStyles.marginTop) + 'px';
564 | this._$child.addClass(this.options.className);
565 | this.fixed = true;
566 | },
567 |
568 | _unfix: function _unfix() {
569 | var childStyle = this.child.style;
570 | this._replacer.hide();
571 | childStyle.position = this._childOriginalPosition;
572 | childStyle.top = this._childOriginalTop;
573 | childStyle.width = this._childOriginalWidth;
574 | childStyle.left = this._childOriginalLeft;
575 | this._$child.removeClass(this.options.className);
576 | this.fixed = false;
577 | },
578 |
579 | _saveStyles: function(){
580 | var childStyle = this.child.style;
581 | this._childOriginalPosition = childStyle.position;
582 | this._childOriginalTop = childStyle.top;
583 | this._childOriginalWidth = childStyle.width;
584 | this._childOriginalLeft = childStyle.left;
585 | },
586 |
587 | _onresize: function () {
588 | this.refresh();
589 | },
590 |
591 | _saveViewportHeight: function () {
592 | // ie8 doesn't support innerHeight
593 | this._viewportHeight = window.innerHeight || document.documentElement.clientHeight;
594 | },
595 |
596 | _stop: function() {
597 | // Unfix the container immediately.
598 | this._unfix();
599 | // remove event listeners
600 | $(window).unbind('scroll', this._proxied_onscroll);
601 | $(this._toresize).unbind('resize', this._proxied_onresize);
602 | },
603 |
604 | _start: function() {
605 | // Trigger onscroll to have the effect immediately.
606 | this._onscroll();
607 |
608 | // Attach event listeners
609 | $(window).bind('scroll', this._proxied_onscroll);
610 | $(this._toresize).bind('resize', this._proxied_onresize);
611 | },
612 |
613 | _destroy: function() {
614 | // Destroy mimic node instance
615 | this._replacer.destroy();
616 | },
617 |
618 | refresh: function() {
619 | this._saveViewportHeight();
620 | this._unfix();
621 | this._onscroll();
622 | }
623 | });
624 |
625 | function NativeSticky(child, parent, options) {
626 | FixTo.call(this, child, parent, options);
627 | this.start();
628 | }
629 |
630 | NativeSticky.prototype = new FixTo();
631 |
632 | $.extend(NativeSticky.prototype, {
633 | _start: function() {
634 |
635 | var childStyles = computedStyle.getAll(this.child);
636 |
637 | this._childOriginalPosition = childStyles.position;
638 | this._childOriginalTop = childStyles.top;
639 |
640 | this.child.style.position = nativeStickyValue;
641 | this.refresh();
642 | },
643 |
644 | _stop: function() {
645 | this.child.style.position = this._childOriginalPosition;
646 | this.child.style.top = this._childOriginalTop;
647 | },
648 |
649 | refresh: function() {
650 | this.child.style.top = this._mindtop() + this.options.top + 'px';
651 | }
652 | });
653 |
654 |
655 |
656 | var fixTo = function fixTo(childElement, parentElement, options) {
657 | if((nativeStickyValue && !options) || (nativeStickyValue && options && options.useNativeSticky !== false)) {
658 | // Position sticky supported and user did not disabled the usage of it.
659 | return new NativeSticky(childElement, parentElement, options);
660 | }
661 | else if(fixedPositionValue) {
662 | // Position fixed supported
663 |
664 | if(fixedPositioningContext===undefined) {
665 | // We don't know yet if browser creates fixed positioning contexts. Check it.
666 | fixedPositioningContext = checkFixedPositioningContextSupport();
667 | }
668 |
669 | return new FixToContainer(childElement, parentElement, options);
670 | }
671 | else {
672 | return 'Neither fixed nor sticky positioning supported';
673 | }
674 | };
675 |
676 | /*
677 | No support for ie lt 8
678 | */
679 |
680 | if(ieversion<8){
681 | fixTo = function(){
682 | return 'not supported';
683 | };
684 | }
685 |
686 | // Let it be a jQuery Plugin
687 | $.fn.fixTo = function (targetSelector, options) {
688 |
689 | var $targets = $(targetSelector);
690 |
691 | var i = 0;
692 | return this.each(function () {
693 |
694 | // Check the data of the element.
695 | var instance = $(this).data('fixto-instance');
696 |
697 | // If the element is not bound to an instance, create the instance and save it to elements data.
698 | if(!instance) {
699 | $(this).data('fixto-instance', fixTo(this, $targets[i], options));
700 | }
701 | else {
702 | // If we already have the instance here, expect that targetSelector parameter will be a string
703 | // equal to a public methods name. Run the method on the instance without checking if
704 | // it exists or it is a public method or not. Cause nasty errors when necessary.
705 | var method = targetSelector;
706 | instance[method].call(instance, options);
707 | }
708 | i++;
709 | });
710 | };
711 |
712 | /*
713 | Expose
714 | */
715 |
716 | return {
717 | FixToContainer: FixToContainer,
718 | fixTo: fixTo,
719 | computedStyle:computedStyle,
720 | mimicNode:mimicNode
721 | };
722 |
723 |
724 | }(window.jQuery, window, document));
--------------------------------------------------------------------------------
/dist/fixto.min.js:
--------------------------------------------------------------------------------
1 | /*! fixto - v0.5.0 - 2016-06-16
2 | * http://github.com/bbarakaci/fixto/*/
3 | var fixto=function(e,t,n){function s(){this._vendor=null}function f(){var e=!1,t=n.createElement("div"),r=n.createElement("div");t.appendChild(r),t.style[u]="translate(0)",t.style.marginTop="10px",t.style.visibility="hidden",r.style.position="fixed",r.style.top=0,n.body.appendChild(t);var i=r.getBoundingClientRect();return i.top>0&&(e=!0),n.body.removeChild(t),e}function d(t,n,r){this.child=t,this._$child=e(t),this.parent=n,this.options={className:"fixto-fixed",top:0,mindViewport:!1},this._setOptions(r)}function v(e,t,n){d.call(this,e,t,n),this._replacer=new i.MimicNode(e),this._ghostNode=this._replacer.replacer,this._saveStyles(),this._saveViewportHeight(),this._proxied_onscroll=this._bind(this._onscroll,this),this._proxied_onresize=this._bind(this._onresize,this),this.start()}function m(e,t,n){d.call(this,e,t,n),this.start()}var r=function(){var e={getAll:function(e){return n.defaultView.getComputedStyle(e)},get:function(e,t){return this.getAll(e)[t]},toFloat:function(e){return parseFloat(e,10)||0},getFloat:function(e,t){return this.toFloat(this.get(e,t))},_getAllCurrentStyle:function(e){return e.currentStyle}};return n.documentElement.currentStyle&&(e.getAll=e._getAllCurrentStyle),e}(),i=function(){function t(e){this.element=e,this.replacer=n.createElement("div"),this.replacer.style.visibility="hidden",this.hide(),e.parentNode.insertBefore(this.replacer,e)}t.prototype={replace:function(){var e=this.replacer.style,t=r.getAll(this.element);e.width=this._width(),e.height=this._height(),e.marginTop=t.marginTop,e.marginBottom=t.marginBottom,e.marginLeft=t.marginLeft,e.marginRight=t.marginRight,e.cssFloat=t.cssFloat,e.styleFloat=t.styleFloat,e.position=t.position,e.top=t.top,e.right=t.right,e.bottom=t.bottom,e.left=t.left,e.display=t.display},hide:function(){this.replacer.style.display="none"},_width:function(){return this.element.getBoundingClientRect().width+"px"},_widthOffset:function(){return this.element.offsetWidth+"px"},_height:function(){return this.element.getBoundingClientRect().height+"px"},_heightOffset:function(){return this.element.offsetHeight+"px"},destroy:function(){e(this.replacer).remove();for(var t in this)this.hasOwnProperty(t)&&(this[t]=null)}};var i=n.documentElement.getBoundingClientRect();return i.width||(t.prototype._width=t.prototype._widthOffset,t.prototype._height=t.prototype._heightOffset),{MimicNode:t,computedStyle:r}}();s.prototype={_vendors:{webkit:{cssPrefix:"-webkit-",jsPrefix:"Webkit"},moz:{cssPrefix:"-moz-",jsPrefix:"Moz"},ms:{cssPrefix:"-ms-",jsPrefix:"ms"},opera:{cssPrefix:"-o-",jsPrefix:"O"}},_prefixJsProperty:function(e,t){return e.jsPrefix+t[0].toUpperCase()+t.substr(1)},_prefixValue:function(e,t){return e.cssPrefix+t},_valueSupported:function(e,t,n){try{return n.style[e]=t,n.style[e]===t}catch(r){return!1}},propertySupported:function(e){return n.documentElement.style[e]!==undefined},getJsProperty:function(e){if(this.propertySupported(e))return e;if(this._vendor)return this._prefixJsProperty(this._vendor,e);var t;for(var n in this._vendors){t=this._prefixJsProperty(this._vendors[n],e);if(this.propertySupported(t))return this._vendor=this._vendors[n],t}return null},getCssValue:function(e,t){var r=n.createElement("div"),i=this.getJsProperty(e);if(this._valueSupported(i,t,r))return t;var s;if(this._vendor){s=this._prefixValue(this._vendor,t);if(this._valueSupported(i,s,r))return s}for(var o in this._vendors){s=this._prefixValue(this._vendors[o],t);if(this._valueSupported(i,s,r))return this._vendor=this._vendors[o],s}return null}};var o=new s,u=o.getJsProperty("transform"),a,l=o.getCssValue("position","sticky"),c=o.getCssValue("position","fixed"),h=navigator.appName==="Microsoft Internet Explorer",p;h&&(p=parseFloat(navigator.appVersion.split("MSIE")[1])),d.prototype={_mindtop:function(){var e=0;if(this._$mind){var t,n,i;for(var s=0,o=this._$mind.length;sthis._parentBottom||this._scrollTopthis._fullOffset("offsetTop",this.child)-this.options.top-this._mindtop())return this.options.mindViewport&&!this._isViewportAvailable()?!1:!0},_isViewportAvailable:function(){var e=r.getAll(this.child);return this._viewportHeight>this.child.offsetHeight+r.toFloat(e.marginTop)+r.toFloat(e.marginBottom)},_adjust:function(){var t=0,n=this._mindtop(),i=0,s=r.getAll(this.child),o=null;a&&(o=this._getContext(),o&&(t=Math.abs(o.getBoundingClientRect().top))),i=this._parentBottom-this._scrollTop-(this.child.offsetHeight+r.toFloat(s.marginBottom)+n+this.options.top),i>0&&(i=0),this.child.style.top=i+n+t+this.options.top-r.toFloat(s.marginTop)+"px"},_fullOffset:function(t,n,r){var i=n[t],s=n.offsetParent;while(s!==null&&s!==r)i+=s[t],s=s.offsetParent;return i},_getContext:function(){var e,t=this.child,i=null,s;while(!i){e=t.parentNode;if(e===n.documentElement)return null;s=r.getAll(e);if(s[u]!=="none"){i=e;break}t=e}return i},_fix:function(){var t=this.child,i=t.style,s=r.getAll(t),o=t.getBoundingClientRect().left,u=s.width;this._saveStyles(),n.documentElement.currentStyle&&(u=t.offsetWidth-(r.toFloat(s.paddingLeft)+r.toFloat(s.paddingRight)+r.toFloat(s.borderLeftWidth)+r.toFloat(s.borderRightWidth))+"px");if(a){var f=this._getContext();f&&(o=t.getBoundingClientRect().left-f.getBoundingClientRect().left)}this._replacer.replace(),i.left=o-r.toFloat(s.marginLeft)+"px",i.width=u,i.position="fixed",i.top=this._mindtop()+this.options.top-r.toFloat(s.marginTop)+"px",this._$child.addClass(this.options.className),this.fixed=!0},_unfix:function(){var t=this.child.style;this._replacer.hide(),t.position=this._childOriginalPosition,t.top=this._childOriginalTop,t.width=this._childOriginalWidth,t.left=this._childOriginalLeft,this._$child.removeClass(this.options.className),this.fixed=!1},_saveStyles:function(){var e=this.child.style;this._childOriginalPosition=e.position,this._childOriginalTop=e.top,this._childOriginalWidth=e.width,this._childOriginalLeft=e.left},_onresize:function(){this.refresh()},_saveViewportHeight:function(){this._viewportHeight=t.innerHeight||n.documentElement.clientHeight},_stop:function(){this._unfix(),e(t).unbind("scroll",this._proxied_onscroll),e(this._toresize).unbind("resize",this._proxied_onresize)},_start:function(){this._onscroll(),e(t).bind("scroll",this._proxied_onscroll),e(this._toresize).bind("resize",this._proxied_onresize)},_destroy:function(){this._replacer.destroy()},refresh:function(){this._saveViewportHeight(),this._unfix(),this._onscroll()}}),m.prototype=new d,e.extend(m.prototype,{_start:function(){var e=r.getAll(this.child);this._childOriginalPosition=e.position,this._childOriginalTop=e.top,this.child.style.position=l,this.refresh()},_stop:function(){this.child.style.position=this._childOriginalPosition,this.child.style.top=this._childOriginalTop},refresh:function(){this.child.style.top=this._mindtop()+this.options.top+"px"}});var g=function(t,n,r){return l&&!r||l&&r&&r.useNativeSticky!==!1?new m(t,n,r):c?(a===undefined&&(a=f()),new v(t,n,r)):"Neither fixed nor sticky positioning supported"};return p<8&&(g=function(){return"not supported"}),e.fn.fixTo=function(t,n){var r=e(t),i=0;return this.each(function(){var s=e(this).data("fixto-instance");if(!s)e(this).data("fixto-instance",g(this,r[i],n));else{var o=t;s[o].call(s,n)}i++})},{FixToContainer:v,fixTo:g,computedStyle:r,mimicNode:i}}(window.jQuery,window,document);
--------------------------------------------------------------------------------
/grunt.js:
--------------------------------------------------------------------------------
1 | /*global module:false*/
2 | module.exports = function(grunt) {
3 |
4 | // Project configuration.
5 | grunt.initConfig({
6 | meta: {
7 | version: grunt.file.readJSON('package.json').version,
8 | banner: '/*! fixto - v<%= meta.version %> - ' +
9 | '<%= grunt.template.today("yyyy-mm-dd") %>\n' +
10 | '* http://github.com/bbarakaci/fixto/' +
11 | '*/'
12 | },
13 | lint: {
14 | files: ['grunt.js', 'src/**/*.js', 'test/**/*.js']
15 | },
16 | qunit: {
17 | files: ['test/**/*.html']
18 | },
19 | concat: {
20 | dist: {
21 | src: ['', ''],
22 | dest: 'dist/fixto.js'
23 | }
24 | },
25 | min: {
26 | dist: {
27 | src: ['', ''],
28 | dest: 'dist/fixto.min.js'
29 | }
30 | },
31 | watch: {
32 | files: '',
33 | tasks: 'lint qunit'
34 | },
35 | jshint: {
36 | options: {
37 | curly: true,
38 | eqeqeq: true,
39 | immed: true,
40 | latedef: true,
41 | newcap: true,
42 | noarg: true,
43 | sub: true,
44 | undef: true,
45 | boss: true,
46 | eqnull: true,
47 | browser: true
48 | },
49 | globals: {}
50 | },
51 | uglify: {}
52 | });
53 |
54 | // Default task.
55 | grunt.registerTask('default', 'lint qunit');
56 | grunt.registerTask('make', 'lint qunit concat min');
57 |
58 | };
59 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | fixto, a jQuery plugin for sticky positioning
10 |
11 |
12 |
13 |
14 |
15 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
fixto
158 |
A jQuery plugin for sticky positioning
159 |
Fix containers to the viewport relative to an ancestor
160 |
Scroll down please
161 |
162 |
163 |
174 |
175 |
176 |
188 |
189 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In vitae lacus lacus, sit amet vulputate tortor. Nam gravida dignissim feugiat. Mauris mattis cursus molestie. Ut tincidunt sapien id orci ornare vulputate. Phasellus scelerisque adipiscing euismod. Etiam euismod vulputate est, et fermentum nulla porta quis. Aenean id eros odio. Sed vehicula lorem dui. Proin in nunc purus, id rutrum ipsum. Nam tortor ante, ultricies nec egestas sed, laoreet eu libero.
190 |
Quisque quis turpis at mi venenatis varius et at mauris. Cras placerat felis a lacus viverra tempor. Vivamus et justo tellus. Cras consectetur mollis cursus. Integer vitae nibh sit amet tortor mattis aliquet. In elit turpis, dictum sit amet lobortis eget, pharetra ut erat. Donec commodo, ipsum ut vehicula gravida, est sem aliquet ante, vel rhoncus mi mi id sem. Cras mattis nisi id ante pretium tempus. Aenean non sem sit amet justo consequat malesuada. Integer eget iaculis sem. Curabitur condimentum massa at mi vestibulum ultrices. Donec sed sapien sit amet nunc cursus pharetra a sit amet augue. Sed eget metus at justo faucibus egestas sit amet at augue. Fusce ac leo magna, et placerat nibh.
191 |
192 |
193 |
194 |
195 |
207 |
208 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In vitae lacus lacus, sit amet vulputate tortor. Nam gravida dignissim feugiat. Mauris mattis cursus molestie. Ut tincidunt sapien id orci ornare vulputate. Phasellus scelerisque adipiscing euismod. Etiam euismod vulputate est, et fermentum nulla porta quis. Aenean id eros odio. Sed vehicula lorem dui. Proin in nunc purus, id rutrum ipsum. Nam tortor ante, ultricies nec egestas sed, laoreet eu libero.
209 |
Quisque quis turpis at mi venenatis varius et at mauris. Cras placerat felis a lacus viverra tempor. Vivamus et justo tellus. Cras consectetur mollis cursus. Integer vitae nibh sit amet tortor mattis aliquet. In elit turpis, dictum sit amet lobortis eget, pharetra ut erat. Donec commodo, ipsum ut vehicula gravida, est sem aliquet ante, vel rhoncus mi mi id sem. Cras mattis nisi id ante pretium tempus. Aenean non sem sit amet justo consequat malesuada. Integer eget iaculis sem. Curabitur condimentum massa at mi vestibulum ultrices. Donec sed sapien sit amet nunc cursus pharetra a sit amet augue. Sed eget metus at justo faucibus egestas sit amet at augue. Fusce ac leo magna, et placerat nibh.
210 |
211 |
212 |
213 |
225 |
226 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In vitae lacus lacus, sit amet vulputate tortor. Nam gravida dignissim feugiat. Mauris mattis cursus molestie. Ut tincidunt sapien id orci ornare vulputate. Phasellus scelerisque adipiscing euismod. Etiam euismod vulputate est, et fermentum nulla porta quis. Aenean id eros odio. Sed vehicula lorem dui. Proin in nunc purus, id rutrum ipsum. Nam tortor ante, ultricies nec egestas sed, laoreet eu libero.
227 |
Quisque quis turpis at mi venenatis varius et at mauris. Cras placerat felis a lacus viverra tempor. Vivamus et justo tellus. Cras consectetur mollis cursus. Integer vitae nibh sit amet tortor mattis aliquet. In elit turpis, dictum sit amet lobortis eget, pharetra ut erat. Donec commodo, ipsum ut vehicula gravida, est sem aliquet ante, vel rhoncus mi mi id sem. Cras mattis nisi id ante pretium tempus. Aenean non sem sit amet justo consequat malesuada. Integer eget iaculis sem. Curabitur condimentum massa at mi vestibulum ultrices. Donec sed sapien sit amet nunc cursus pharetra a sit amet augue. Sed eget metus at justo faucibus egestas sit amet at augue. Fusce ac leo magna, et placerat nibh.
228 |
229 |
230 |
231 |
243 |
244 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In vitae lacus lacus, sit amet vulputate tortor. Nam gravida dignissim feugiat. Mauris mattis cursus molestie. Ut tincidunt sapien id orci ornare vulputate. Phasellus scelerisque adipiscing euismod. Etiam euismod vulputate est, et fermentum nulla porta quis. Aenean id eros odio. Sed vehicula lorem dui. Proin in nunc purus, id rutrum ipsum. Nam tortor ante, ultricies nec egestas sed, laoreet eu libero.
245 |
Quisque quis turpis at mi venenatis varius et at mauris. Cras placerat felis a lacus viverra tempor. Vivamus et justo tellus. Cras consectetur mollis cursus. Integer vitae nibh sit amet tortor mattis aliquet. In elit turpis, dictum sit amet lobortis eget, pharetra ut erat. Donec commodo, ipsum ut vehicula gravida, est sem aliquet ante, vel rhoncus mi mi id sem. Cras mattis nisi id ante pretium tempus. Aenean non sem sit amet justo consequat malesuada. Integer eget iaculis sem. Curabitur condimentum massa at mi vestibulum ultrices. Donec sed sapien sit amet nunc cursus pharetra a sit amet augue. Sed eget metus at justo faucibus egestas sit amet at augue. Fusce ac leo magna, et placerat nibh.
246 |
247 |
248 |
249 |
261 |
262 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In vitae lacus lacus, sit amet vulputate tortor. Nam gravida dignissim feugiat. Mauris mattis cursus molestie. Ut tincidunt sapien id orci ornare vulputate. Phasellus scelerisque adipiscing euismod. Etiam euismod vulputate est, et fermentum nulla porta quis. Aenean id eros odio. Sed vehicula lorem dui. Proin in nunc purus, id rutrum ipsum. Nam tortor ante, ultricies nec egestas sed, laoreet eu libero.
263 |
Quisque quis turpis at mi venenatis varius et at mauris. Cras placerat felis a lacus viverra tempor. Vivamus et justo tellus. Cras consectetur mollis cursus. Integer vitae nibh sit amet tortor mattis aliquet. In elit turpis, dictum sit amet lobortis eget, pharetra ut erat. Donec commodo, ipsum ut vehicula gravida, est sem aliquet ante, vel rhoncus mi mi id sem. Cras mattis nisi id ante pretium tempus. Aenean non sem sit amet justo consequat malesuada. Integer eget iaculis sem. Curabitur condimentum massa at mi vestibulum ultrices. Donec sed sapien sit amet nunc cursus pharetra a sit amet augue. Sed eget metus at justo faucibus egestas sit amet at augue. Fusce ac leo magna, et placerat nibh.
264 |
265 |
266 |
267 |
268 |
269 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
281 |
282 |
289 |
290 |
--------------------------------------------------------------------------------
/libs/jasmine-2.0.0/boot.js:
--------------------------------------------------------------------------------
1 | /**
2 | Starting with version 2.0, this file "boots" Jasmine, performing all of the necessary initialization before executing the loaded environment and all of a project's specs. This file should be loaded after `jasmine.js`, but before any project source files or spec files are loaded. Thus this file can also be used to customize Jasmine for a project.
3 |
4 | If a project is using Jasmine via the standalone distribution, this file can be customized directly. If a project is using Jasmine via the [Ruby gem][jasmine-gem], this file can be copied into the support directory via `jasmine copy_boot_js`. Other environments (e.g., Python) will have different mechanisms.
5 |
6 | The location of `boot.js` can be specified and/or overridden in `jasmine.yml`.
7 |
8 | [jasmine-gem]: http://github.com/pivotal/jasmine-gem
9 | */
10 |
11 | (function() {
12 |
13 | /**
14 | * ## Require & Instantiate
15 | *
16 | * Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference.
17 | */
18 | window.jasmine = jasmineRequire.core(jasmineRequire);
19 |
20 | /**
21 | * Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference.
22 | */
23 | jasmineRequire.html(jasmine);
24 |
25 | /**
26 | * Create the Jasmine environment. This is used to run all specs in a project.
27 | */
28 | var env = jasmine.getEnv();
29 |
30 | /**
31 | * ## The Global Interface
32 | *
33 | * Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged.
34 | */
35 | var jasmineInterface = {
36 | describe: function(description, specDefinitions) {
37 | return env.describe(description, specDefinitions);
38 | },
39 |
40 | xdescribe: function(description, specDefinitions) {
41 | return env.xdescribe(description, specDefinitions);
42 | },
43 |
44 | it: function(desc, func) {
45 | return env.it(desc, func);
46 | },
47 |
48 | xit: function(desc, func) {
49 | return env.xit(desc, func);
50 | },
51 |
52 | beforeEach: function(beforeEachFunction) {
53 | return env.beforeEach(beforeEachFunction);
54 | },
55 |
56 | afterEach: function(afterEachFunction) {
57 | return env.afterEach(afterEachFunction);
58 | },
59 |
60 | expect: function(actual) {
61 | return env.expect(actual);
62 | },
63 |
64 | pending: function() {
65 | return env.pending();
66 | },
67 |
68 | spyOn: function(obj, methodName) {
69 | return env.spyOn(obj, methodName);
70 | },
71 |
72 | jsApiReporter: new jasmine.JsApiReporter({
73 | timer: new jasmine.Timer()
74 | })
75 | };
76 |
77 | /**
78 | * Add all of the Jasmine global/public interface to the proper global, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`.
79 | */
80 | if (typeof window == "undefined" && typeof exports == "object") {
81 | extend(exports, jasmineInterface);
82 | } else {
83 | extend(window, jasmineInterface);
84 | }
85 |
86 | /**
87 | * Expose the interface for adding custom equality testers.
88 | */
89 | jasmine.addCustomEqualityTester = function(tester) {
90 | env.addCustomEqualityTester(tester);
91 | };
92 |
93 | /**
94 | * Expose the interface for adding custom expectation matchers
95 | */
96 | jasmine.addMatchers = function(matchers) {
97 | return env.addMatchers(matchers);
98 | };
99 |
100 | /**
101 | * Expose the mock interface for the JavaScript timeout functions
102 | */
103 | jasmine.clock = function() {
104 | return env.clock;
105 | };
106 |
107 | /**
108 | * ## Runner Parameters
109 | *
110 | * More browser specific code - wrap the query string in an object and to allow for getting/setting parameters from the runner user interface.
111 | */
112 |
113 | var queryString = new jasmine.QueryString({
114 | getWindowLocation: function() { return window.location; }
115 | });
116 |
117 | var catchingExceptions = queryString.getParam("catch");
118 | env.catchExceptions(typeof catchingExceptions === "undefined" ? true : catchingExceptions);
119 |
120 | /**
121 | * ## Reporters
122 | * The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any).
123 | */
124 | var htmlReporter = new jasmine.HtmlReporter({
125 | env: env,
126 | onRaiseExceptionsClick: function() { queryString.setParam("catch", !env.catchingExceptions()); },
127 | getContainer: function() { return document.body; },
128 | createElement: function() { return document.createElement.apply(document, arguments); },
129 | createTextNode: function() { return document.createTextNode.apply(document, arguments); },
130 | timer: new jasmine.Timer()
131 | });
132 |
133 | /**
134 | * The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript.
135 | */
136 | env.addReporter(jasmineInterface.jsApiReporter);
137 | env.addReporter(htmlReporter);
138 |
139 | /**
140 | * Filter which specs will be run by matching the start of the full name against the `spec` query param.
141 | */
142 | var specFilter = new jasmine.HtmlSpecFilter({
143 | filterString: function() { return queryString.getParam("spec"); }
144 | });
145 |
146 | env.specFilter = function(spec) {
147 | return specFilter.matches(spec.getFullName());
148 | };
149 |
150 | /**
151 | * Setting up timing functions to be able to be overridden. Certain browsers (Safari, IE 8, phantomjs) require this hack.
152 | */
153 | window.setTimeout = window.setTimeout;
154 | window.setInterval = window.setInterval;
155 | window.clearTimeout = window.clearTimeout;
156 | window.clearInterval = window.clearInterval;
157 |
158 | /**
159 | * ## Execution
160 | *
161 | * Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded.
162 | */
163 | var currentWindowOnload = window.onload;
164 |
165 | window.onload = function() {
166 | if (currentWindowOnload) {
167 | currentWindowOnload();
168 | }
169 | htmlReporter.initialize();
170 | env.execute();
171 | };
172 |
173 | /**
174 | * Helper function for readability above.
175 | */
176 | function extend(destination, source) {
177 | for (var property in source) destination[property] = source[property];
178 | return destination;
179 | }
180 |
181 | }());
182 |
--------------------------------------------------------------------------------
/libs/jasmine-2.0.0/console.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2008-2013 Pivotal Labs
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining
5 | a copy of this software and associated documentation files (the
6 | "Software"), to deal in the Software without restriction, including
7 | without limitation the rights to use, copy, modify, merge, publish,
8 | distribute, sublicense, and/or sell copies of the Software, and to
9 | permit persons to whom the Software is furnished to do so, subject to
10 | the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | */
23 | function getJasmineRequireObj() {
24 | if (typeof module !== "undefined" && module.exports) {
25 | return exports;
26 | } else {
27 | window.jasmineRequire = window.jasmineRequire || {};
28 | return window.jasmineRequire;
29 | }
30 | }
31 |
32 | getJasmineRequireObj().console = function(jRequire, j$) {
33 | j$.ConsoleReporter = jRequire.ConsoleReporter();
34 | };
35 |
36 | getJasmineRequireObj().ConsoleReporter = function() {
37 |
38 | var noopTimer = {
39 | start: function(){},
40 | elapsed: function(){ return 0; }
41 | };
42 |
43 | function ConsoleReporter(options) {
44 | var print = options.print,
45 | showColors = options.showColors || false,
46 | onComplete = options.onComplete || function() {},
47 | timer = options.timer || noopTimer,
48 | specCount,
49 | failureCount,
50 | failedSpecs = [],
51 | pendingCount,
52 | ansi = {
53 | green: '\x1B[32m',
54 | red: '\x1B[31m',
55 | yellow: '\x1B[33m',
56 | none: '\x1B[0m'
57 | };
58 |
59 | this.jasmineStarted = function() {
60 | specCount = 0;
61 | failureCount = 0;
62 | pendingCount = 0;
63 | print("Started");
64 | printNewline();
65 | timer.start();
66 | };
67 |
68 | this.jasmineDone = function() {
69 | printNewline();
70 | for (var i = 0; i < failedSpecs.length; i++) {
71 | specFailureDetails(failedSpecs[i]);
72 | }
73 |
74 | printNewline();
75 | var specCounts = specCount + " " + plural("spec", specCount) + ", " +
76 | failureCount + " " + plural("failure", failureCount);
77 |
78 | if (pendingCount) {
79 | specCounts += ", " + pendingCount + " pending " + plural("spec", pendingCount);
80 | }
81 |
82 | print(specCounts);
83 |
84 | printNewline();
85 | var seconds = timer.elapsed() / 1000;
86 | print("Finished in " + seconds + " " + plural("second", seconds));
87 |
88 | printNewline();
89 |
90 | onComplete(failureCount === 0);
91 | };
92 |
93 | this.specDone = function(result) {
94 | specCount++;
95 |
96 | if (result.status == "pending") {
97 | pendingCount++;
98 | print(colored("yellow", "*"));
99 | return;
100 | }
101 |
102 | if (result.status == "passed") {
103 | print(colored("green", '.'));
104 | return;
105 | }
106 |
107 | if (result.status == "failed") {
108 | failureCount++;
109 | failedSpecs.push(result);
110 | print(colored("red", 'F'));
111 | }
112 | };
113 |
114 | return this;
115 |
116 | function printNewline() {
117 | print("\n");
118 | }
119 |
120 | function colored(color, str) {
121 | return showColors ? (ansi[color] + str + ansi.none) : str;
122 | }
123 |
124 | function plural(str, count) {
125 | return count == 1 ? str : str + "s";
126 | }
127 |
128 | function repeat(thing, times) {
129 | var arr = [];
130 | for (var i = 0; i < times; i++) {
131 | arr.push(thing);
132 | }
133 | return arr;
134 | }
135 |
136 | function indent(str, spaces) {
137 | var lines = (str || '').split("\n");
138 | var newArr = [];
139 | for (var i = 0; i < lines.length; i++) {
140 | newArr.push(repeat(" ", spaces).join("") + lines[i]);
141 | }
142 | return newArr.join("\n");
143 | }
144 |
145 | function specFailureDetails(result) {
146 | printNewline();
147 | print(result.fullName);
148 |
149 | for (var i = 0; i < result.failedExpectations.length; i++) {
150 | var failedExpectation = result.failedExpectations[i];
151 | printNewline();
152 | print(indent(failedExpectation.stack, 2));
153 | }
154 |
155 | printNewline();
156 | }
157 | }
158 |
159 | return ConsoleReporter;
160 | };
161 |
--------------------------------------------------------------------------------
/libs/jasmine-2.0.0/jasmine-html.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2008-2013 Pivotal Labs
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining
5 | a copy of this software and associated documentation files (the
6 | "Software"), to deal in the Software without restriction, including
7 | without limitation the rights to use, copy, modify, merge, publish,
8 | distribute, sublicense, and/or sell copies of the Software, and to
9 | permit persons to whom the Software is furnished to do so, subject to
10 | the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | */
23 | jasmineRequire.html = function(j$) {
24 | j$.ResultsNode = jasmineRequire.ResultsNode();
25 | j$.HtmlReporter = jasmineRequire.HtmlReporter(j$);
26 | j$.QueryString = jasmineRequire.QueryString();
27 | j$.HtmlSpecFilter = jasmineRequire.HtmlSpecFilter();
28 | };
29 |
30 | jasmineRequire.HtmlReporter = function(j$) {
31 |
32 | var noopTimer = {
33 | start: function() {},
34 | elapsed: function() { return 0; }
35 | };
36 |
37 | function HtmlReporter(options) {
38 | var env = options.env || {},
39 | getContainer = options.getContainer,
40 | createElement = options.createElement,
41 | createTextNode = options.createTextNode,
42 | onRaiseExceptionsClick = options.onRaiseExceptionsClick || function() {},
43 | timer = options.timer || noopTimer,
44 | results = [],
45 | specsExecuted = 0,
46 | failureCount = 0,
47 | pendingSpecCount = 0,
48 | htmlReporterMain,
49 | symbols;
50 |
51 | this.initialize = function() {
52 | htmlReporterMain = createDom("div", {className: "html-reporter"},
53 | createDom("div", {className: "banner"},
54 | createDom("span", {className: "title"}, "Jasmine"),
55 | createDom("span", {className: "version"}, j$.version)
56 | ),
57 | createDom("ul", {className: "symbol-summary"}),
58 | createDom("div", {className: "alert"}),
59 | createDom("div", {className: "results"},
60 | createDom("div", {className: "failures"})
61 | )
62 | );
63 | getContainer().appendChild(htmlReporterMain);
64 |
65 | symbols = find(".symbol-summary");
66 | };
67 |
68 | var totalSpecsDefined;
69 | this.jasmineStarted = function(options) {
70 | totalSpecsDefined = options.totalSpecsDefined || 0;
71 | timer.start();
72 | };
73 |
74 | var summary = createDom("div", {className: "summary"});
75 |
76 | var topResults = new j$.ResultsNode({}, "", null),
77 | currentParent = topResults;
78 |
79 | this.suiteStarted = function(result) {
80 | currentParent.addChild(result, "suite");
81 | currentParent = currentParent.last();
82 | };
83 |
84 | this.suiteDone = function(result) {
85 | if (currentParent == topResults) {
86 | return;
87 | }
88 |
89 | currentParent = currentParent.parent;
90 | };
91 |
92 | this.specStarted = function(result) {
93 | currentParent.addChild(result, "spec");
94 | };
95 |
96 | var failures = [];
97 | this.specDone = function(result) {
98 | if (result.status != "disabled") {
99 | specsExecuted++;
100 | }
101 |
102 | symbols.appendChild(createDom("li", {
103 | className: result.status,
104 | id: "spec_" + result.id,
105 | title: result.fullName
106 | }
107 | ));
108 |
109 | if (result.status == "failed") {
110 | failureCount++;
111 |
112 | var failure =
113 | createDom("div", {className: "spec-detail failed"},
114 | createDom("div", {className: "description"},
115 | createDom("a", {title: result.fullName, href: specHref(result)}, result.fullName)
116 | ),
117 | createDom("div", {className: "messages"})
118 | );
119 | var messages = failure.childNodes[1];
120 |
121 | for (var i = 0; i < result.failedExpectations.length; i++) {
122 | var expectation = result.failedExpectations[i];
123 | messages.appendChild(createDom("div", {className: "result-message"}, expectation.message));
124 | messages.appendChild(createDom("div", {className: "stack-trace"}, expectation.stack));
125 | }
126 |
127 | failures.push(failure);
128 | }
129 |
130 | if (result.status == "pending") {
131 | pendingSpecCount++;
132 | }
133 | };
134 |
135 | this.jasmineDone = function() {
136 | var banner = find(".banner");
137 | banner.appendChild(createDom("span", {className: "duration"}, "finished in " + timer.elapsed() / 1000 + "s"));
138 |
139 | var alert = find(".alert");
140 |
141 | alert.appendChild(createDom("span", { className: "exceptions" },
142 | createDom("label", { className: "label", 'for': "raise-exceptions" }, "raise exceptions"),
143 | createDom("input", {
144 | className: "raise",
145 | id: "raise-exceptions",
146 | type: "checkbox"
147 | })
148 | ));
149 | var checkbox = find("input");
150 |
151 | checkbox.checked = !env.catchingExceptions();
152 | checkbox.onclick = onRaiseExceptionsClick;
153 |
154 | if (specsExecuted < totalSpecsDefined) {
155 | var skippedMessage = "Ran " + specsExecuted + " of " + totalSpecsDefined + " specs - run all";
156 | alert.appendChild(
157 | createDom("span", {className: "bar skipped"},
158 | createDom("a", {href: "?", title: "Run all specs"}, skippedMessage)
159 | )
160 | );
161 | }
162 | var statusBarMessage = "" + pluralize("spec", specsExecuted) + ", " + pluralize("failure", failureCount);
163 | if (pendingSpecCount) { statusBarMessage += ", " + pluralize("pending spec", pendingSpecCount); }
164 |
165 | var statusBarClassName = "bar " + ((failureCount > 0) ? "failed" : "passed");
166 | alert.appendChild(createDom("span", {className: statusBarClassName}, statusBarMessage));
167 |
168 | var results = find(".results");
169 | results.appendChild(summary);
170 |
171 | summaryList(topResults, summary);
172 |
173 | function summaryList(resultsTree, domParent) {
174 | var specListNode;
175 | for (var i = 0; i < resultsTree.children.length; i++) {
176 | var resultNode = resultsTree.children[i];
177 | if (resultNode.type == "suite") {
178 | var suiteListNode = createDom("ul", {className: "suite", id: "suite-" + resultNode.result.id},
179 | createDom("li", {className: "suite-detail"},
180 | createDom("a", {href: specHref(resultNode.result)}, resultNode.result.description)
181 | )
182 | );
183 |
184 | summaryList(resultNode, suiteListNode);
185 | domParent.appendChild(suiteListNode);
186 | }
187 | if (resultNode.type == "spec") {
188 | if (domParent.getAttribute("class") != "specs") {
189 | specListNode = createDom("ul", {className: "specs"});
190 | domParent.appendChild(specListNode);
191 | }
192 | specListNode.appendChild(
193 | createDom("li", {
194 | className: resultNode.result.status,
195 | id: "spec-" + resultNode.result.id
196 | },
197 | createDom("a", {href: specHref(resultNode.result)}, resultNode.result.description)
198 | )
199 | );
200 | }
201 | }
202 | }
203 |
204 | if (failures.length) {
205 | alert.appendChild(
206 | createDom('span', {className: "menu bar spec-list"},
207 | createDom("span", {}, "Spec List | "),
208 | createDom('a', {className: "failures-menu", href: "#"}, "Failures")));
209 | alert.appendChild(
210 | createDom('span', {className: "menu bar failure-list"},
211 | createDom('a', {className: "spec-list-menu", href: "#"}, "Spec List"),
212 | createDom("span", {}, " | Failures ")));
213 |
214 | find(".failures-menu").onclick = function() {
215 | setMenuModeTo('failure-list');
216 | };
217 | find(".spec-list-menu").onclick = function() {
218 | setMenuModeTo('spec-list');
219 | };
220 |
221 | setMenuModeTo('failure-list');
222 |
223 | var failureNode = find(".failures");
224 | for (var i = 0; i < failures.length; i++) {
225 | failureNode.appendChild(failures[i]);
226 | }
227 | }
228 | };
229 |
230 | return this;
231 |
232 | function find(selector) {
233 | return getContainer().querySelector(selector);
234 | }
235 |
236 | function createDom(type, attrs, childrenVarArgs) {
237 | var el = createElement(type);
238 |
239 | for (var i = 2; i < arguments.length; i++) {
240 | var child = arguments[i];
241 |
242 | if (typeof child === 'string') {
243 | el.appendChild(createTextNode(child));
244 | } else {
245 | if (child) {
246 | el.appendChild(child);
247 | }
248 | }
249 | }
250 |
251 | for (var attr in attrs) {
252 | if (attr == "className") {
253 | el[attr] = attrs[attr];
254 | } else {
255 | el.setAttribute(attr, attrs[attr]);
256 | }
257 | }
258 |
259 | return el;
260 | }
261 |
262 | function pluralize(singular, count) {
263 | var word = (count == 1 ? singular : singular + "s");
264 |
265 | return "" + count + " " + word;
266 | }
267 |
268 | function specHref(result) {
269 | return "?spec=" + encodeURIComponent(result.fullName);
270 | }
271 |
272 | function setMenuModeTo(mode) {
273 | htmlReporterMain.setAttribute("class", "html-reporter " + mode);
274 | }
275 | }
276 |
277 | return HtmlReporter;
278 | };
279 |
280 | jasmineRequire.HtmlSpecFilter = function() {
281 | function HtmlSpecFilter(options) {
282 | var filterString = options && options.filterString() && options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
283 | var filterPattern = new RegExp(filterString);
284 |
285 | this.matches = function(specName) {
286 | return filterPattern.test(specName);
287 | };
288 | }
289 |
290 | return HtmlSpecFilter;
291 | };
292 |
293 | jasmineRequire.ResultsNode = function() {
294 | function ResultsNode(result, type, parent) {
295 | this.result = result;
296 | this.type = type;
297 | this.parent = parent;
298 |
299 | this.children = [];
300 |
301 | this.addChild = function(result, type) {
302 | this.children.push(new ResultsNode(result, type, this));
303 | };
304 |
305 | this.last = function() {
306 | return this.children[this.children.length - 1];
307 | };
308 | }
309 |
310 | return ResultsNode;
311 | };
312 |
313 | jasmineRequire.QueryString = function() {
314 | function QueryString(options) {
315 |
316 | this.setParam = function(key, value) {
317 | var paramMap = queryStringToParamMap();
318 | paramMap[key] = value;
319 | options.getWindowLocation().search = toQueryString(paramMap);
320 | };
321 |
322 | this.getParam = function(key) {
323 | return queryStringToParamMap()[key];
324 | };
325 |
326 | return this;
327 |
328 | function toQueryString(paramMap) {
329 | var qStrPairs = [];
330 | for (var prop in paramMap) {
331 | qStrPairs.push(encodeURIComponent(prop) + "=" + encodeURIComponent(paramMap[prop]));
332 | }
333 | return "?" + qStrPairs.join('&');
334 | }
335 |
336 | function queryStringToParamMap() {
337 | var paramStr = options.getWindowLocation().search.substring(1),
338 | params = [],
339 | paramMap = {};
340 |
341 | if (paramStr.length > 0) {
342 | params = paramStr.split('&');
343 | for (var i = 0; i < params.length; i++) {
344 | var p = params[i].split('=');
345 | var value = decodeURIComponent(p[1]);
346 | if (value === "true" || value === "false") {
347 | value = JSON.parse(value);
348 | }
349 | paramMap[decodeURIComponent(p[0])] = value;
350 | }
351 | }
352 |
353 | return paramMap;
354 | }
355 |
356 | }
357 |
358 | return QueryString;
359 | };
360 |
--------------------------------------------------------------------------------
/libs/jasmine-2.0.0/jasmine.css:
--------------------------------------------------------------------------------
1 | body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; }
2 |
3 | .html-reporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; }
4 | .html-reporter a { text-decoration: none; }
5 | .html-reporter a:hover { text-decoration: underline; }
6 | .html-reporter p, .html-reporter h1, .html-reporter h2, .html-reporter h3, .html-reporter h4, .html-reporter h5, .html-reporter h6 { margin: 0; line-height: 14px; }
7 | .html-reporter .banner, .html-reporter .symbol-summary, .html-reporter .summary, .html-reporter .result-message, .html-reporter .spec .description, .html-reporter .spec-detail .description, .html-reporter .alert .bar, .html-reporter .stack-trace { padding-left: 9px; padding-right: 9px; }
8 | .html-reporter .banner .version { margin-left: 14px; }
9 | .html-reporter #jasmine_content { position: fixed; right: 100%; }
10 | .html-reporter .version { color: #aaaaaa; }
11 | .html-reporter .banner { margin-top: 14px; }
12 | .html-reporter .duration { color: #aaaaaa; float: right; }
13 | .html-reporter .symbol-summary { overflow: hidden; *zoom: 1; margin: 14px 0; }
14 | .html-reporter .symbol-summary li { display: inline-block; height: 8px; width: 14px; font-size: 16px; }
15 | .html-reporter .symbol-summary li.passed { font-size: 14px; }
16 | .html-reporter .symbol-summary li.passed:before { color: #5e7d00; content: "\02022"; }
17 | .html-reporter .symbol-summary li.failed { line-height: 9px; }
18 | .html-reporter .symbol-summary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; }
19 | .html-reporter .symbol-summary li.disabled { font-size: 14px; }
20 | .html-reporter .symbol-summary li.disabled:before { color: #bababa; content: "\02022"; }
21 | .html-reporter .symbol-summary li.pending { line-height: 17px; }
22 | .html-reporter .symbol-summary li.pending:before { color: #ba9d37; content: "*"; }
23 | .html-reporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; }
24 | .html-reporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
25 | .html-reporter .bar.failed { background-color: #b03911; }
26 | .html-reporter .bar.passed { background-color: #a6b779; }
27 | .html-reporter .bar.skipped { background-color: #bababa; }
28 | .html-reporter .bar.menu { background-color: #fff; color: #aaaaaa; }
29 | .html-reporter .bar.menu a { color: #333333; }
30 | .html-reporter .bar a { color: white; }
31 | .html-reporter.spec-list .bar.menu.failure-list, .html-reporter.spec-list .results .failures { display: none; }
32 | .html-reporter.failure-list .bar.menu.spec-list, .html-reporter.failure-list .summary { display: none; }
33 | .html-reporter .running-alert { background-color: #666666; }
34 | .html-reporter .results { margin-top: 14px; }
35 | .html-reporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; }
36 | .html-reporter.showDetails .summaryMenuItem:hover { text-decoration: underline; }
37 | .html-reporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; }
38 | .html-reporter.showDetails .summary { display: none; }
39 | .html-reporter.showDetails #details { display: block; }
40 | .html-reporter .summaryMenuItem { font-weight: bold; text-decoration: underline; }
41 | .html-reporter .summary { margin-top: 14px; }
42 | .html-reporter .summary ul { list-style-type: none; margin-left: 14px; padding-top: 0; padding-left: 0; }
43 | .html-reporter .summary ul.suite { margin-top: 7px; margin-bottom: 7px; }
44 | .html-reporter .summary li.passed a { color: #5e7d00; }
45 | .html-reporter .summary li.failed a { color: #b03911; }
46 | .html-reporter .summary li.pending a { color: #ba9d37; }
47 | .html-reporter .description + .suite { margin-top: 0; }
48 | .html-reporter .suite { margin-top: 14px; }
49 | .html-reporter .suite a { color: #333333; }
50 | .html-reporter .failures .spec-detail { margin-bottom: 28px; }
51 | .html-reporter .failures .spec-detail .description { background-color: #b03911; }
52 | .html-reporter .failures .spec-detail .description a { color: white; }
53 | .html-reporter .result-message { padding-top: 14px; color: #333333; white-space: pre; }
54 | .html-reporter .result-message span.result { display: block; }
55 | .html-reporter .stack-trace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; }
56 |
--------------------------------------------------------------------------------
/libs/jasmine-2.0.0/jasmine_favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbarakaci/fixto/2ef69350091d4dadc9f170a8496cba64f07aa73c/libs/jasmine-2.0.0/jasmine_favicon.png
--------------------------------------------------------------------------------
/libs/jquery-loader.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | // Get any jquery=___ param from the query string.
3 | var jqversion = location.search.match(/[?&]jquery=(.*?)(?=&|$)/);
4 | var path;
5 | if (jqversion) {
6 | // A version was specified, load that version from code.jquery.com.
7 | path = 'http://code.jquery.com/jquery-' + jqversion[1] + '.js';
8 | } else {
9 | // No version was specified, load the local version.
10 | path = '../libs/jquery/jquery.js';
11 | }
12 | // This is the only time I'll ever use document.write, I promise!
13 | document.write('');
14 | }());
15 |
--------------------------------------------------------------------------------
/libs/qunit/qunit.css:
--------------------------------------------------------------------------------
1 | /**
2 | * QUnit v1.4.0 - A JavaScript Unit Testing Framework
3 | *
4 | * http://docs.jquery.com/QUnit
5 | *
6 | * Copyright (c) 2012 John Resig, Jörn Zaefferer
7 | * Dual licensed under the MIT (MIT-LICENSE.txt)
8 | * or GPL (GPL-LICENSE.txt) licenses.
9 | */
10 |
11 | /** Font Family and Sizes */
12 |
13 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
14 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
15 | }
16 |
17 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
18 | #qunit-tests { font-size: smaller; }
19 |
20 |
21 | /** Resets */
22 |
23 | #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
24 | margin: 0;
25 | padding: 0;
26 | }
27 |
28 |
29 | /** Header */
30 |
31 | #qunit-header {
32 | padding: 0.5em 0 0.5em 1em;
33 |
34 | color: #8699a4;
35 | background-color: #0d3349;
36 |
37 | font-size: 1.5em;
38 | line-height: 1em;
39 | font-weight: normal;
40 |
41 | border-radius: 15px 15px 0 0;
42 | -moz-border-radius: 15px 15px 0 0;
43 | -webkit-border-top-right-radius: 15px;
44 | -webkit-border-top-left-radius: 15px;
45 | }
46 |
47 | #qunit-header a {
48 | text-decoration: none;
49 | color: #c2ccd1;
50 | }
51 |
52 | #qunit-header a:hover,
53 | #qunit-header a:focus {
54 | color: #fff;
55 | }
56 |
57 | #qunit-header label {
58 | display: inline-block;
59 | }
60 |
61 | #qunit-banner {
62 | height: 5px;
63 | }
64 |
65 | #qunit-testrunner-toolbar {
66 | padding: 0.5em 0 0.5em 2em;
67 | color: #5E740B;
68 | background-color: #eee;
69 | }
70 |
71 | #qunit-userAgent {
72 | padding: 0.5em 0 0.5em 2.5em;
73 | background-color: #2b81af;
74 | color: #fff;
75 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
76 | }
77 |
78 |
79 | /** Tests: Pass/Fail */
80 |
81 | #qunit-tests {
82 | list-style-position: inside;
83 | }
84 |
85 | #qunit-tests li {
86 | padding: 0.4em 0.5em 0.4em 2.5em;
87 | border-bottom: 1px solid #fff;
88 | list-style-position: inside;
89 | }
90 |
91 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
92 | display: none;
93 | }
94 |
95 | #qunit-tests li strong {
96 | cursor: pointer;
97 | }
98 |
99 | #qunit-tests li a {
100 | padding: 0.5em;
101 | color: #c2ccd1;
102 | text-decoration: none;
103 | }
104 | #qunit-tests li a:hover,
105 | #qunit-tests li a:focus {
106 | color: #000;
107 | }
108 |
109 | #qunit-tests ol {
110 | margin-top: 0.5em;
111 | padding: 0.5em;
112 |
113 | background-color: #fff;
114 |
115 | border-radius: 15px;
116 | -moz-border-radius: 15px;
117 | -webkit-border-radius: 15px;
118 |
119 | box-shadow: inset 0px 2px 13px #999;
120 | -moz-box-shadow: inset 0px 2px 13px #999;
121 | -webkit-box-shadow: inset 0px 2px 13px #999;
122 | }
123 |
124 | #qunit-tests table {
125 | border-collapse: collapse;
126 | margin-top: .2em;
127 | }
128 |
129 | #qunit-tests th {
130 | text-align: right;
131 | vertical-align: top;
132 | padding: 0 .5em 0 0;
133 | }
134 |
135 | #qunit-tests td {
136 | vertical-align: top;
137 | }
138 |
139 | #qunit-tests pre {
140 | margin: 0;
141 | white-space: pre-wrap;
142 | word-wrap: break-word;
143 | }
144 |
145 | #qunit-tests del {
146 | background-color: #e0f2be;
147 | color: #374e0c;
148 | text-decoration: none;
149 | }
150 |
151 | #qunit-tests ins {
152 | background-color: #ffcaca;
153 | color: #500;
154 | text-decoration: none;
155 | }
156 |
157 | /*** Test Counts */
158 |
159 | #qunit-tests b.counts { color: black; }
160 | #qunit-tests b.passed { color: #5E740B; }
161 | #qunit-tests b.failed { color: #710909; }
162 |
163 | #qunit-tests li li {
164 | margin: 0.5em;
165 | padding: 0.4em 0.5em 0.4em 0.5em;
166 | background-color: #fff;
167 | border-bottom: none;
168 | list-style-position: inside;
169 | }
170 |
171 | /*** Passing Styles */
172 |
173 | #qunit-tests li li.pass {
174 | color: #5E740B;
175 | background-color: #fff;
176 | border-left: 26px solid #C6E746;
177 | }
178 |
179 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
180 | #qunit-tests .pass .test-name { color: #366097; }
181 |
182 | #qunit-tests .pass .test-actual,
183 | #qunit-tests .pass .test-expected { color: #999999; }
184 |
185 | #qunit-banner.qunit-pass { background-color: #C6E746; }
186 |
187 | /*** Failing Styles */
188 |
189 | #qunit-tests li li.fail {
190 | color: #710909;
191 | background-color: #fff;
192 | border-left: 26px solid #EE5757;
193 | white-space: pre;
194 | }
195 |
196 | #qunit-tests > li:last-child {
197 | border-radius: 0 0 15px 15px;
198 | -moz-border-radius: 0 0 15px 15px;
199 | -webkit-border-bottom-right-radius: 15px;
200 | -webkit-border-bottom-left-radius: 15px;
201 | }
202 |
203 | #qunit-tests .fail { color: #000000; background-color: #EE5757; }
204 | #qunit-tests .fail .test-name,
205 | #qunit-tests .fail .module-name { color: #000000; }
206 |
207 | #qunit-tests .fail .test-actual { color: #EE5757; }
208 | #qunit-tests .fail .test-expected { color: green; }
209 |
210 | #qunit-banner.qunit-fail { background-color: #EE5757; }
211 |
212 |
213 | /** Result */
214 |
215 | #qunit-testresult {
216 | padding: 0.5em 0.5em 0.5em 2.5em;
217 |
218 | color: #2b81af;
219 | background-color: #D2E0E6;
220 |
221 | border-bottom: 1px solid white;
222 | }
223 |
224 | /** Fixture */
225 |
226 | #qunit-fixture {
227 | position: absolute;
228 | top: -10000px;
229 | left: -10000px;
230 | width: 1000px;
231 | height: 1000px;
232 | }
233 |
--------------------------------------------------------------------------------
/libs/qunit/qunit.js:
--------------------------------------------------------------------------------
1 | /**
2 | * QUnit v1.4.0 - A JavaScript Unit Testing Framework
3 | *
4 | * http://docs.jquery.com/QUnit
5 | *
6 | * Copyright (c) 2012 John Resig, Jörn Zaefferer
7 | * Dual licensed under the MIT (MIT-LICENSE.txt)
8 | * or GPL (GPL-LICENSE.txt) licenses.
9 | */
10 |
11 | (function(window) {
12 |
13 | var defined = {
14 | setTimeout: typeof window.setTimeout !== "undefined",
15 | sessionStorage: (function() {
16 | var x = "qunit-test-string";
17 | try {
18 | sessionStorage.setItem(x, x);
19 | sessionStorage.removeItem(x);
20 | return true;
21 | } catch(e) {
22 | return false;
23 | }
24 | }())
25 | };
26 |
27 | var testId = 0,
28 | toString = Object.prototype.toString,
29 | hasOwn = Object.prototype.hasOwnProperty;
30 |
31 | var Test = function(name, testName, expected, async, callback) {
32 | this.name = name;
33 | this.testName = testName;
34 | this.expected = expected;
35 | this.async = async;
36 | this.callback = callback;
37 | this.assertions = [];
38 | };
39 | Test.prototype = {
40 | init: function() {
41 | var tests = id("qunit-tests");
42 | if (tests) {
43 | var b = document.createElement("strong");
44 | b.innerHTML = "Running " + this.name;
45 | var li = document.createElement("li");
46 | li.appendChild( b );
47 | li.className = "running";
48 | li.id = this.id = "test-output" + testId++;
49 | tests.appendChild( li );
50 | }
51 | },
52 | setup: function() {
53 | if (this.module != config.previousModule) {
54 | if ( config.previousModule ) {
55 | runLoggingCallbacks('moduleDone', QUnit, {
56 | name: config.previousModule,
57 | failed: config.moduleStats.bad,
58 | passed: config.moduleStats.all - config.moduleStats.bad,
59 | total: config.moduleStats.all
60 | } );
61 | }
62 | config.previousModule = this.module;
63 | config.moduleStats = { all: 0, bad: 0 };
64 | runLoggingCallbacks( 'moduleStart', QUnit, {
65 | name: this.module
66 | } );
67 | } else if (config.autorun) {
68 | runLoggingCallbacks( 'moduleStart', QUnit, {
69 | name: this.module
70 | } );
71 | }
72 |
73 | config.current = this;
74 | this.testEnvironment = extend({
75 | setup: function() {},
76 | teardown: function() {}
77 | }, this.moduleTestEnvironment);
78 |
79 | runLoggingCallbacks( 'testStart', QUnit, {
80 | name: this.testName,
81 | module: this.module
82 | });
83 |
84 | // allow utility functions to access the current test environment
85 | // TODO why??
86 | QUnit.current_testEnvironment = this.testEnvironment;
87 |
88 | if ( !config.pollution ) {
89 | saveGlobal();
90 | }
91 | if ( config.notrycatch ) {
92 | this.testEnvironment.setup.call(this.testEnvironment);
93 | return;
94 | }
95 | try {
96 | this.testEnvironment.setup.call(this.testEnvironment);
97 | } catch(e) {
98 | QUnit.pushFailure( "Setup failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) );
99 | }
100 | },
101 | run: function() {
102 | config.current = this;
103 | if ( this.async ) {
104 | QUnit.stop();
105 | }
106 |
107 | if ( config.notrycatch ) {
108 | this.callback.call(this.testEnvironment);
109 | return;
110 | }
111 | try {
112 | this.callback.call(this.testEnvironment);
113 | } catch(e) {
114 | QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + ": " + e.message, extractStacktrace( e, 1 ) );
115 | // else next test will carry the responsibility
116 | saveGlobal();
117 |
118 | // Restart the tests if they're blocking
119 | if ( config.blocking ) {
120 | QUnit.start();
121 | }
122 | }
123 | },
124 | teardown: function() {
125 | config.current = this;
126 | if ( config.notrycatch ) {
127 | this.testEnvironment.teardown.call(this.testEnvironment);
128 | return;
129 | } else {
130 | try {
131 | this.testEnvironment.teardown.call(this.testEnvironment);
132 | } catch(e) {
133 | QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) );
134 | }
135 | }
136 | checkPollution();
137 | },
138 | finish: function() {
139 | config.current = this;
140 | if ( this.expected != null && this.expected != this.assertions.length ) {
141 | QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" );
142 | } else if ( this.expected == null && !this.assertions.length ) {
143 | QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions." );
144 | }
145 |
146 | var good = 0, bad = 0,
147 | li, i,
148 | tests = id("qunit-tests");
149 |
150 | config.stats.all += this.assertions.length;
151 | config.moduleStats.all += this.assertions.length;
152 |
153 | if ( tests ) {
154 | var ol = document.createElement("ol");
155 |
156 | for ( i = 0; i < this.assertions.length; i++ ) {
157 | var assertion = this.assertions[i];
158 |
159 | li = document.createElement("li");
160 | li.className = assertion.result ? "pass" : "fail";
161 | li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed");
162 | ol.appendChild( li );
163 |
164 | if ( assertion.result ) {
165 | good++;
166 | } else {
167 | bad++;
168 | config.stats.bad++;
169 | config.moduleStats.bad++;
170 | }
171 | }
172 |
173 | // store result when possible
174 | if ( QUnit.config.reorder && defined.sessionStorage ) {
175 | if (bad) {
176 | sessionStorage.setItem("qunit-test-" + this.module + "-" + this.testName, bad);
177 | } else {
178 | sessionStorage.removeItem("qunit-test-" + this.module + "-" + this.testName);
179 | }
180 | }
181 |
182 | if (bad === 0) {
183 | ol.style.display = "none";
184 | }
185 |
186 | var b = document.createElement("strong");
187 | b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")";
188 |
189 | var a = document.createElement("a");
190 | a.innerHTML = "Rerun";
191 | a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
192 |
193 | addEvent(b, "click", function() {
194 | var next = b.nextSibling.nextSibling,
195 | display = next.style.display;
196 | next.style.display = display === "none" ? "block" : "none";
197 | });
198 |
199 | addEvent(b, "dblclick", function(e) {
200 | var target = e && e.target ? e.target : window.event.srcElement;
201 | if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
202 | target = target.parentNode;
203 | }
204 | if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
205 | window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
206 | }
207 | });
208 |
209 | li = id(this.id);
210 | li.className = bad ? "fail" : "pass";
211 | li.removeChild( li.firstChild );
212 | li.appendChild( b );
213 | li.appendChild( a );
214 | li.appendChild( ol );
215 |
216 | } else {
217 | for ( i = 0; i < this.assertions.length; i++ ) {
218 | if ( !this.assertions[i].result ) {
219 | bad++;
220 | config.stats.bad++;
221 | config.moduleStats.bad++;
222 | }
223 | }
224 | }
225 |
226 | QUnit.reset();
227 |
228 | runLoggingCallbacks( 'testDone', QUnit, {
229 | name: this.testName,
230 | module: this.module,
231 | failed: bad,
232 | passed: this.assertions.length - bad,
233 | total: this.assertions.length
234 | } );
235 | },
236 |
237 | queue: function() {
238 | var test = this;
239 | synchronize(function() {
240 | test.init();
241 | });
242 | function run() {
243 | // each of these can by async
244 | synchronize(function() {
245 | test.setup();
246 | });
247 | synchronize(function() {
248 | test.run();
249 | });
250 | synchronize(function() {
251 | test.teardown();
252 | });
253 | synchronize(function() {
254 | test.finish();
255 | });
256 | }
257 | // defer when previous test run passed, if storage is available
258 | var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-test-" + this.module + "-" + this.testName);
259 | if (bad) {
260 | run();
261 | } else {
262 | synchronize(run, true);
263 | }
264 | }
265 |
266 | };
267 |
268 | var QUnit = {
269 |
270 | // call on start of module test to prepend name to all tests
271 | module: function(name, testEnvironment) {
272 | config.currentModule = name;
273 | config.currentModuleTestEnviroment = testEnvironment;
274 | },
275 |
276 | asyncTest: function(testName, expected, callback) {
277 | if ( arguments.length === 2 ) {
278 | callback = expected;
279 | expected = null;
280 | }
281 |
282 | QUnit.test(testName, expected, callback, true);
283 | },
284 |
285 | test: function(testName, expected, callback, async) {
286 | var name = '' + escapeInnerText(testName) + '';
287 |
288 | if ( arguments.length === 2 ) {
289 | callback = expected;
290 | expected = null;
291 | }
292 |
293 | if ( config.currentModule ) {
294 | name = '' + config.currentModule + ": " + name;
295 | }
296 |
297 | if ( !validTest(config.currentModule + ": " + testName) ) {
298 | return;
299 | }
300 |
301 | var test = new Test(name, testName, expected, async, callback);
302 | test.module = config.currentModule;
303 | test.moduleTestEnvironment = config.currentModuleTestEnviroment;
304 | test.queue();
305 | },
306 |
307 | // Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
308 | expect: function(asserts) {
309 | config.current.expected = asserts;
310 | },
311 |
312 | // Asserts true.
313 | // @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
314 | ok: function(result, msg) {
315 | if (!config.current) {
316 | throw new Error("ok() assertion outside test context, was " + sourceFromStacktrace(2));
317 | }
318 | result = !!result;
319 | var details = {
320 | result: result,
321 | message: msg
322 | };
323 | msg = escapeInnerText(msg || (result ? "okay" : "failed"));
324 | if ( !result ) {
325 | var source = sourceFromStacktrace(2);
326 | if (source) {
327 | details.source = source;
328 | msg += '
Source:
' + escapeInnerText(source) + '
';
329 | }
330 | }
331 | runLoggingCallbacks( 'log', QUnit, details );
332 | config.current.assertions.push({
333 | result: result,
334 | message: msg
335 | });
336 | },
337 |
338 | // Checks that the first two arguments are equal, with an optional message. Prints out both actual and expected values.
339 | // @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." );
340 | equal: function(actual, expected, message) {
341 | QUnit.push(expected == actual, actual, expected, message);
342 | },
343 |
344 | notEqual: function(actual, expected, message) {
345 | QUnit.push(expected != actual, actual, expected, message);
346 | },
347 |
348 | deepEqual: function(actual, expected, message) {
349 | QUnit.push(QUnit.equiv(actual, expected), actual, expected, message);
350 | },
351 |
352 | notDeepEqual: function(actual, expected, message) {
353 | QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message);
354 | },
355 |
356 | strictEqual: function(actual, expected, message) {
357 | QUnit.push(expected === actual, actual, expected, message);
358 | },
359 |
360 | notStrictEqual: function(actual, expected, message) {
361 | QUnit.push(expected !== actual, actual, expected, message);
362 | },
363 |
364 | raises: function(block, expected, message) {
365 | var actual, ok = false;
366 |
367 | if (typeof expected === 'string') {
368 | message = expected;
369 | expected = null;
370 | }
371 |
372 | try {
373 | block();
374 | } catch (e) {
375 | actual = e;
376 | }
377 |
378 | if (actual) {
379 | // we don't want to validate thrown error
380 | if (!expected) {
381 | ok = true;
382 | // expected is a regexp
383 | } else if (QUnit.objectType(expected) === "regexp") {
384 | ok = expected.test(actual);
385 | // expected is a constructor
386 | } else if (actual instanceof expected) {
387 | ok = true;
388 | // expected is a validation function which returns true is validation passed
389 | } else if (expected.call({}, actual) === true) {
390 | ok = true;
391 | }
392 | }
393 |
394 | QUnit.ok(ok, message);
395 | },
396 |
397 | start: function(count) {
398 | config.semaphore -= count || 1;
399 | if (config.semaphore > 0) {
400 | // don't start until equal number of stop-calls
401 | return;
402 | }
403 | if (config.semaphore < 0) {
404 | // ignore if start is called more often then stop
405 | config.semaphore = 0;
406 | }
407 | // A slight delay, to avoid any current callbacks
408 | if ( defined.setTimeout ) {
409 | window.setTimeout(function() {
410 | if (config.semaphore > 0) {
411 | return;
412 | }
413 | if ( config.timeout ) {
414 | clearTimeout(config.timeout);
415 | }
416 |
417 | config.blocking = false;
418 | process(true);
419 | }, 13);
420 | } else {
421 | config.blocking = false;
422 | process(true);
423 | }
424 | },
425 |
426 | stop: function(count) {
427 | config.semaphore += count || 1;
428 | config.blocking = true;
429 |
430 | if ( config.testTimeout && defined.setTimeout ) {
431 | clearTimeout(config.timeout);
432 | config.timeout = window.setTimeout(function() {
433 | QUnit.ok( false, "Test timed out" );
434 | config.semaphore = 1;
435 | QUnit.start();
436 | }, config.testTimeout);
437 | }
438 | }
439 | };
440 |
441 | //We want access to the constructor's prototype
442 | (function() {
443 | function F(){}
444 | F.prototype = QUnit;
445 | QUnit = new F();
446 | //Make F QUnit's constructor so that we can add to the prototype later
447 | QUnit.constructor = F;
448 | }());
449 |
450 | // deprecated; still export them to window to provide clear error messages
451 | // next step: remove entirely
452 | QUnit.equals = function() {
453 | QUnit.push(false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead");
454 | };
455 | QUnit.same = function() {
456 | QUnit.push(false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead");
457 | };
458 |
459 | // Maintain internal state
460 | var config = {
461 | // The queue of tests to run
462 | queue: [],
463 |
464 | // block until document ready
465 | blocking: true,
466 |
467 | // when enabled, show only failing tests
468 | // gets persisted through sessionStorage and can be changed in UI via checkbox
469 | hidepassed: false,
470 |
471 | // by default, run previously failed tests first
472 | // very useful in combination with "Hide passed tests" checked
473 | reorder: true,
474 |
475 | // by default, modify document.title when suite is done
476 | altertitle: true,
477 |
478 | urlConfig: ['noglobals', 'notrycatch'],
479 |
480 | //logging callback queues
481 | begin: [],
482 | done: [],
483 | log: [],
484 | testStart: [],
485 | testDone: [],
486 | moduleStart: [],
487 | moduleDone: []
488 | };
489 |
490 | // Load paramaters
491 | (function() {
492 | var location = window.location || { search: "", protocol: "file:" },
493 | params = location.search.slice( 1 ).split( "&" ),
494 | length = params.length,
495 | urlParams = {},
496 | current;
497 |
498 | if ( params[ 0 ] ) {
499 | for ( var i = 0; i < length; i++ ) {
500 | current = params[ i ].split( "=" );
501 | current[ 0 ] = decodeURIComponent( current[ 0 ] );
502 | // allow just a key to turn on a flag, e.g., test.html?noglobals
503 | current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
504 | urlParams[ current[ 0 ] ] = current[ 1 ];
505 | }
506 | }
507 |
508 | QUnit.urlParams = urlParams;
509 | config.filter = urlParams.filter;
510 |
511 | // Figure out if we're running the tests from a server or not
512 | QUnit.isLocal = location.protocol === 'file:';
513 | }());
514 |
515 | // Expose the API as global variables, unless an 'exports'
516 | // object exists, in that case we assume we're in CommonJS - export everything at the end
517 | if ( typeof exports === "undefined" || typeof require === "undefined" ) {
518 | extend(window, QUnit);
519 | window.QUnit = QUnit;
520 | }
521 |
522 | // define these after exposing globals to keep them in these QUnit namespace only
523 | extend(QUnit, {
524 | config: config,
525 |
526 | // Initialize the configuration options
527 | init: function() {
528 | extend(config, {
529 | stats: { all: 0, bad: 0 },
530 | moduleStats: { all: 0, bad: 0 },
531 | started: +new Date(),
532 | updateRate: 1000,
533 | blocking: false,
534 | autostart: true,
535 | autorun: false,
536 | filter: "",
537 | queue: [],
538 | semaphore: 0
539 | });
540 |
541 | var qunit = id( "qunit" );
542 | if ( qunit ) {
543 | qunit.innerHTML =
544 | '
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce nulla ligula, egestas sit amet malesuada vel, iaculis at mi. Donec vestibulum nunc at neque pretium et egestas erat scelerisque. Pellentesque hendrerit egestas eros, non mattis arcu viverra vitae. Praesent tincidunt magna at urna ultrices consequat. Phasellus cursus rutrum lectus non dapibus. Curabitur lorem lorem, blandit vitae vehicula eget, sagittis vitae dolor. Integer sed ipsum sed elit facilisis commodo quis a urna. Mauris scelerisque nibh in metus tempor tristique. Fusce commodo sagittis ultrices. Pellentesque sit amet fermentum orci. Maecenas sollicitudin arcu non felis mollis ac laoreet augue mattis. Morbi condimentum turpis eu ipsum dapibus et euismod risus dapibus. Mauris mauris nisl, rutrum eu ultrices vel, cursus at massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit.