red 4.1.0 → 4.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +25 -0
- data/lib/red/version.rb +1 -1
- data/lib/source/redshift/accessors.rb +580 -0
- data/lib/source/redshift/browser.rb +150 -0
- data/lib/source/redshift/chainable.rb +75 -0
- data/lib/source/redshift/code_events.rb +204 -0
- data/lib/source/redshift/cookie.rb +142 -0
- data/lib/source/redshift/document.rb +216 -0
- data/lib/source/redshift/element.rb +417 -0
- data/lib/source/redshift/event.rb +312 -0
- data/lib/source/redshift/redshift.red +5 -0
- data/lib/source/redshift/request.rb +276 -0
- data/lib/source/redshift/selectors.rb +374 -0
- data/lib/source/redshift/situated.rb +328 -0
- data/lib/source/redshift/store.rb +48 -0
- data/lib/source/redshift/transform.rb +199 -0
- data/lib/source/redshift/tween.rb +66 -0
- data/lib/source/redshift/user_events.rb +215 -0
- data/lib/source/redshift/validator.rb +21 -0
- data/lib/source/redshift/window.rb +11 -0
- data/lib/source/redspec/index.html +11 -0
- data/lib/source/redspec/lib/red_spec/red_spec.red +525 -0
- data/lib/source/redspec/lib/stylesheets/specs.sass +290 -0
- metadata +27 -2
@@ -0,0 +1,374 @@
|
|
1
|
+
require 'browser'
|
2
|
+
|
3
|
+
`
|
4
|
+
Array.fromCollection = function(collection){
|
5
|
+
for (var i = 0, a = [], j = collection.length; i < j; i++){
|
6
|
+
a.push($E(collection[i]))
|
7
|
+
}
|
8
|
+
return a
|
9
|
+
};
|
10
|
+
// used in several places where element/selector comparison is used
|
11
|
+
// tells whether an element matches another element or selector
|
12
|
+
Element.prototype.match = function(selector){
|
13
|
+
if (!selector) return true;
|
14
|
+
var tagid = Selectors.Utils.parseTagAndID(selector);
|
15
|
+
var tag = tagid[0], id = tagid[1];
|
16
|
+
if (!Selectors.Filters.byID(this, id) || !Selectors.Filters.byTag(this, tag)) return false;
|
17
|
+
var parsed = Selectors.Utils.parseSelector(selector);
|
18
|
+
return (parsed) ? Selectors.Utils.filter(this, parsed, {}) : true;
|
19
|
+
};
|
20
|
+
|
21
|
+
// Provides polymorphic access to getElementById to Elements as well as documents, both of which
|
22
|
+
// can be passed into Selectors.Utils.search as location for searching for subelements.
|
23
|
+
Element.prototype.getElementById = function(id, nocash){
|
24
|
+
var el = this.ownerDocument.getElementById(id);
|
25
|
+
if (!el) return null;
|
26
|
+
for (var parent = el.parentNode; parent != this; parent = parent.parentNode){
|
27
|
+
if (!parent) return null;
|
28
|
+
}
|
29
|
+
return $E(el);
|
30
|
+
},
|
31
|
+
|
32
|
+
Element.Attributes = {
|
33
|
+
Props: {'html': 'innerHTML', 'class': 'className', 'for': 'htmlFor', 'text': (#{trident?}) ? 'innerText' : 'textContent'},
|
34
|
+
Bools: ['compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked', 'disabled', 'readonly', 'multiple', 'selected', 'noresize', 'defer'],
|
35
|
+
Camels: ['value', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan', 'frameBorder', 'maxLength', 'readOnly', 'rowSpan', 'tabIndex', 'useMap']
|
36
|
+
};
|
37
|
+
|
38
|
+
Element.prototype.getProperty = function(attribute){
|
39
|
+
var EA = Element.Attributes, key = EA.Props[attribute];
|
40
|
+
var value = (key) ? this[key] : this.getAttribute(attribute, 2);
|
41
|
+
return (EA.Bools[attribute]) ? !!value : (key) ? value : value || null;
|
42
|
+
},
|
43
|
+
|
44
|
+
String.prototype.contains = function(string, separator){
|
45
|
+
return (separator) ? (separator + this + separator).indexOf(separator + string + separator) > -1 : this.indexOf(string) > -1;
|
46
|
+
};
|
47
|
+
|
48
|
+
String.prototype.trim = function(){
|
49
|
+
return this.replace(/^\s+|\s+$/g, '');
|
50
|
+
};
|
51
|
+
|
52
|
+
var Selectors = {Cache: {nth: {}, parsed: {}}};
|
53
|
+
|
54
|
+
Selectors.RegExps = {
|
55
|
+
id: (/#([\\w-]+)/),
|
56
|
+
tag: (/^(\\w+|\\*)/),
|
57
|
+
quick: (/^(\\w+|\\*)$/),
|
58
|
+
splitter: (/\\s*([+>~\\s])\\s*([a-zA-Z#.*:\\[])/g),
|
59
|
+
combined: (/\\.([\\w-]+)|\\[(\\w+)(?:([!*^$~|]?=)(["']?)([^\\4]*?)\\4)?\\]|:([\\w-]+)(?:\\(["']?(.*?)?["']?\\)|$)/g)
|
60
|
+
};
|
61
|
+
|
62
|
+
Selectors.Utils = {
|
63
|
+
// added to replace $uid
|
64
|
+
// uses internal Red.id
|
65
|
+
object_uid: function(item){
|
66
|
+
return item.__id__||(item.__id__=Red.id++)
|
67
|
+
},
|
68
|
+
|
69
|
+
chk: function(item, uniques){
|
70
|
+
if (!uniques) return true;
|
71
|
+
var uid = Selectors.Utils.object_uid(item);
|
72
|
+
if (!uniques[uid]) return uniques[uid] = true;
|
73
|
+
return false;
|
74
|
+
},
|
75
|
+
|
76
|
+
parseNthArgument: function(argument){
|
77
|
+
if (Selectors.Cache.nth[argument]) return Selectors.Cache.nth[argument];
|
78
|
+
var parsed = argument.match(/^([+-]?\\d*)?([a-z]+)?([+-]?\\d*)?$/);
|
79
|
+
if (!parsed) return false;
|
80
|
+
var inta = parseInt(parsed[1]);
|
81
|
+
var a = (inta || inta === 0) ? inta : 1;
|
82
|
+
var special = parsed[2] || false;
|
83
|
+
var b = parseInt(parsed[3]) || 0;
|
84
|
+
if (a != 0){
|
85
|
+
b--;
|
86
|
+
while (b < 1) b += a;
|
87
|
+
while (b >= a) b -= a;
|
88
|
+
} else {
|
89
|
+
a = b;
|
90
|
+
special = 'index';
|
91
|
+
}
|
92
|
+
switch (special){
|
93
|
+
case 'n': parsed = {a: a, b: b, special: 'n'}; break;
|
94
|
+
case 'odd': parsed = {a: 2, b: 0, special: 'n'}; break;
|
95
|
+
case 'even': parsed = {a: 2, b: 1, special: 'n'}; break;
|
96
|
+
case 'first': parsed = {a: 0, special: 'index'}; break;
|
97
|
+
case 'last': parsed = {special: 'last-child'}; break;
|
98
|
+
case 'only': parsed = {special: 'only-child'}; break;
|
99
|
+
default: parsed = {a: (a - 1), special: 'index'};
|
100
|
+
}
|
101
|
+
|
102
|
+
return Selectors.Cache.nth[argument] = parsed;
|
103
|
+
},
|
104
|
+
|
105
|
+
parseSelector: function(selector){
|
106
|
+
if (Selectors.Cache.parsed[selector]) return Selectors.Cache.parsed[selector];
|
107
|
+
var m, parsed = {classes: [], pseudos: [], attributes: []};
|
108
|
+
while ((m = Selectors.RegExps.combined.exec(selector))){
|
109
|
+
var cn = m[1], an = m[2], ao = m[3], av = m[5], pn = m[6], pa = m[7];
|
110
|
+
if (cn){
|
111
|
+
parsed.classes.push(cn);
|
112
|
+
} else if (pn){
|
113
|
+
var parser = Selectors.Pseudo[pn];
|
114
|
+
|
115
|
+
if (parser) parsed.pseudos.push({parser: parser, argument: pa});
|
116
|
+
else parsed.attributes.push({name: pn, operator: '=', value: pa});
|
117
|
+
} else if (an){
|
118
|
+
parsed.attributes.push({name: an, operator: ao, value: av});
|
119
|
+
}
|
120
|
+
}
|
121
|
+
if (!parsed.classes.length) delete parsed.classes;
|
122
|
+
if (!parsed.attributes.length) delete parsed.attributes;
|
123
|
+
if (!parsed.pseudos.length) delete parsed.pseudos;
|
124
|
+
if (!parsed.classes && !parsed.attributes && !parsed.pseudos) parsed = null;
|
125
|
+
return Selectors.Cache.parsed[selector] = parsed;
|
126
|
+
},
|
127
|
+
|
128
|
+
parseTagAndID: function(selector){
|
129
|
+
var tag = selector.match(Selectors.RegExps.tag);
|
130
|
+
var id = selector.match(Selectors.RegExps.id);
|
131
|
+
return [(tag) ? tag[1] : '*', (id) ? id[1] : false];
|
132
|
+
},
|
133
|
+
|
134
|
+
filter: function(item, parsed, local){
|
135
|
+
var i;
|
136
|
+
if (parsed.classes){
|
137
|
+
for (i = parsed.classes.length; i--; i){
|
138
|
+
var cn = parsed.classes[i];
|
139
|
+
if (!Selectors.Filters.byClass(item, cn)) return false;
|
140
|
+
}
|
141
|
+
}
|
142
|
+
if (parsed.attributes){
|
143
|
+
for (i = parsed.attributes.length; i--; i){
|
144
|
+
var att = parsed.attributes[i];
|
145
|
+
if (!Selectors.Filters.byAttribute(item, att.name, att.operator, att.value)) return false;
|
146
|
+
}
|
147
|
+
}
|
148
|
+
if (parsed.pseudos){
|
149
|
+
for (i = parsed.pseudos.length; i--; i){
|
150
|
+
var psd = parsed.pseudos[i];
|
151
|
+
if (!Selectors.Filters.byPseudo(item, psd.parser, psd.argument, local)) return false;
|
152
|
+
}
|
153
|
+
}
|
154
|
+
return true;
|
155
|
+
},
|
156
|
+
|
157
|
+
getByTagAndID: function(ctx, tag, id){
|
158
|
+
if (id){
|
159
|
+
var item = (ctx.getElementById) ? ctx.getElementById(id, true) : Element.getElementById(ctx, id, true);
|
160
|
+
return (item && Selectors.Filters.byTag(item, tag)) ? [item] : [];
|
161
|
+
} else {
|
162
|
+
return ctx.getElementsByTagName(tag);
|
163
|
+
}
|
164
|
+
},
|
165
|
+
|
166
|
+
search: function(self, expression, local){
|
167
|
+
var splitters = [];
|
168
|
+
|
169
|
+
var selectors = expression.trim().replace(Selectors.RegExps.splitter, function(m0, m1, m2){
|
170
|
+
splitters.push(m1);
|
171
|
+
return ':)' + m2;
|
172
|
+
}).split(':)');
|
173
|
+
|
174
|
+
|
175
|
+
var items, filtered, item;
|
176
|
+
for (var i = 0, l = selectors.length; i < l; i++){
|
177
|
+
var selector = selectors[i];
|
178
|
+
|
179
|
+
if (i == 0 && Selectors.RegExps.quick.test(selector)){
|
180
|
+
items = self.getElementsByTagName(selector);
|
181
|
+
continue;
|
182
|
+
}
|
183
|
+
|
184
|
+
var splitter = splitters[i - 1];
|
185
|
+
|
186
|
+
var tagid = Selectors.Utils.parseTagAndID(selector);
|
187
|
+
var tag = tagid[0], id = tagid[1];
|
188
|
+
if (i == 0){
|
189
|
+
items = Selectors.Utils.getByTagAndID(self, tag, id);
|
190
|
+
} else {
|
191
|
+
var uniques = {}, found = [];
|
192
|
+
for (var j = 0, k = items.length; j < k; j++) found = Selectors.Getters[splitter](found, items[j], tag, id, uniques);
|
193
|
+
items = found;
|
194
|
+
}
|
195
|
+
|
196
|
+
var parsed = Selectors.Utils.parseSelector(selector);
|
197
|
+
if (parsed){
|
198
|
+
filtered = [];
|
199
|
+
for (var m = 0, n = items.length; m < n; m++){
|
200
|
+
item = items[m];
|
201
|
+
if (Selectors.Utils.filter(item, parsed, local)) filtered.push(item);
|
202
|
+
}
|
203
|
+
items = filtered;
|
204
|
+
}
|
205
|
+
|
206
|
+
}
|
207
|
+
return items;
|
208
|
+
|
209
|
+
}
|
210
|
+
|
211
|
+
};
|
212
|
+
|
213
|
+
Selectors.Getters = {
|
214
|
+
|
215
|
+
' ': function(found, self, tag, id, uniques){
|
216
|
+
var items = Selectors.Utils.getByTagAndID(self, tag, id);
|
217
|
+
for (var i = 0, l = items.length; i < l; i++){
|
218
|
+
var item = items[i];
|
219
|
+
if (Selectors.Utils.chk(item, uniques)) found.push(item);
|
220
|
+
}
|
221
|
+
return found;
|
222
|
+
},
|
223
|
+
|
224
|
+
'>': function(found, self, tag, id, uniques){
|
225
|
+
var children = Selectors.Utils.getByTagAndID(self, tag, id);
|
226
|
+
for (var i = 0, l = children.length; i < l; i++){
|
227
|
+
var child = children[i];
|
228
|
+
if (child.parentNode == self && Selectors.Utils.chk(child, uniques)) found.push(child);
|
229
|
+
}
|
230
|
+
return found;
|
231
|
+
},
|
232
|
+
|
233
|
+
'+': function(found, self, tag, id, uniques){
|
234
|
+
while ((self = self.nextSibling)){
|
235
|
+
if (self.nodeType == 1){
|
236
|
+
if (Selectors.Utils.chk(self, uniques) && Selectors.Filters.byTag(self, tag) && Selectors.Filters.byID(self, id)) found.push(self);
|
237
|
+
break;
|
238
|
+
}
|
239
|
+
}
|
240
|
+
return found;
|
241
|
+
},
|
242
|
+
|
243
|
+
'~': function(found, self, tag, id, uniques){
|
244
|
+
|
245
|
+
while ((self = self.nextSibling)){
|
246
|
+
if (self.nodeType == 1){
|
247
|
+
if (!Selectors.Utils.chk(self, uniques)) break;
|
248
|
+
if (Selectors.Filters.byTag(self, tag) && Selectors.Filters.byID(self, id)) found.push(self);
|
249
|
+
}
|
250
|
+
}
|
251
|
+
return found;
|
252
|
+
}
|
253
|
+
|
254
|
+
};
|
255
|
+
|
256
|
+
Selectors.Filters = {
|
257
|
+
|
258
|
+
byTag: function(self, tag){
|
259
|
+
return (tag == '*' || (self.tagName && self.tagName.toLowerCase() == tag));
|
260
|
+
},
|
261
|
+
|
262
|
+
byID: function(self, id){
|
263
|
+
return (!id || (self.id && self.id == id));
|
264
|
+
},
|
265
|
+
|
266
|
+
byClass: function(self, klass){
|
267
|
+
return (self.className && self.className.contains(klass, ' '));
|
268
|
+
},
|
269
|
+
|
270
|
+
byPseudo: function(self, parser, argument, local){
|
271
|
+
return parser.call(self, argument, local);
|
272
|
+
},
|
273
|
+
|
274
|
+
byAttribute: function(self, name, operator, value){
|
275
|
+
var result = Element.prototype.getProperty.call(self, name);
|
276
|
+
if (!result) return false;
|
277
|
+
if (!operator || value == undefined) return true;
|
278
|
+
switch (operator){
|
279
|
+
case '=': return (result == value);
|
280
|
+
case '*=': return (result.contains(value));
|
281
|
+
case '^=': return (result.substr(0, value.length) == value);
|
282
|
+
case '$=': return (result.substr(result.length - value.length) == value);
|
283
|
+
case '!=': return (result != value);
|
284
|
+
case '~=': return result.contains(value, ' ');
|
285
|
+
case '|=': return result.contains(value, '-');
|
286
|
+
}
|
287
|
+
return false;
|
288
|
+
}
|
289
|
+
|
290
|
+
};
|
291
|
+
|
292
|
+
Selectors.Pseudo = {
|
293
|
+
|
294
|
+
// w3c pseudo selectors
|
295
|
+
|
296
|
+
empty: function(){
|
297
|
+
return !(this.innerText || this.textContent || '').length;
|
298
|
+
},
|
299
|
+
|
300
|
+
not: function(selector){
|
301
|
+
return !Element.match(this, selector);
|
302
|
+
},
|
303
|
+
|
304
|
+
contains: function(text){
|
305
|
+
return (this.innerText || this.textContent || '').contains(text);
|
306
|
+
},
|
307
|
+
|
308
|
+
'first-child': function(){
|
309
|
+
return Selectors.Pseudo.index.call(this, 0);
|
310
|
+
},
|
311
|
+
|
312
|
+
'last-child': function(){
|
313
|
+
var element = this;
|
314
|
+
while ((element = element.nextSibling)){
|
315
|
+
if (element.nodeType == 1) return false;
|
316
|
+
}
|
317
|
+
return true;
|
318
|
+
},
|
319
|
+
|
320
|
+
'only-child': function(){
|
321
|
+
var prev = this;
|
322
|
+
while ((prev = prev.previousSibling)){
|
323
|
+
if (prev.nodeType == 1) return false;
|
324
|
+
}
|
325
|
+
var next = this;
|
326
|
+
while ((next = next.nextSibling)){
|
327
|
+
if (next.nodeType == 1) return false;
|
328
|
+
}
|
329
|
+
return true;
|
330
|
+
},
|
331
|
+
|
332
|
+
'nth-child': function(argument, local){
|
333
|
+
argument = (argument == undefined) ? 'n' : argument;
|
334
|
+
var parsed = Selectors.Utils.parseNthArgument(argument);
|
335
|
+
if (parsed.special != 'n') return Selectors.Pseudo[parsed.special].call(this, parsed.a, local);
|
336
|
+
var count = 0;
|
337
|
+
local.positions = local.positions || {};
|
338
|
+
var uid = Selectors.Utils.object_uid(this);
|
339
|
+
if (!local.positions[uid]){
|
340
|
+
var self = this;
|
341
|
+
while ((self = self.previousSibling)){
|
342
|
+
if (self.nodeType != 1) continue;
|
343
|
+
count ++;
|
344
|
+
var position = local.positions[Selectors.Utils.object_uid(self)];
|
345
|
+
if (position != undefined){
|
346
|
+
count = position + count;
|
347
|
+
break;
|
348
|
+
}
|
349
|
+
}
|
350
|
+
local.positions[uid] = count;
|
351
|
+
}
|
352
|
+
return (local.positions[uid] % parsed.a == parsed.b);
|
353
|
+
},
|
354
|
+
|
355
|
+
// custom pseudo selectors
|
356
|
+
|
357
|
+
index: function(index){
|
358
|
+
var element = this, count = 0;
|
359
|
+
while ((element = element.previousSibling)){
|
360
|
+
if (element.nodeType == 1 && ++count > index) return false;
|
361
|
+
}
|
362
|
+
return (count == index);
|
363
|
+
},
|
364
|
+
|
365
|
+
even: function(argument, local){
|
366
|
+
return Selectors.Pseudo['nth-child'].call(this, '2n+1', local);
|
367
|
+
},
|
368
|
+
|
369
|
+
odd: function(argument, local){
|
370
|
+
return Selectors.Pseudo['nth-child'].call(this, '2n', local);
|
371
|
+
}
|
372
|
+
|
373
|
+
};
|
374
|
+
`
|
@@ -0,0 +1,328 @@
|
|
1
|
+
require 'element'
|
2
|
+
require 'window'
|
3
|
+
|
4
|
+
# Classes mixing in <tt>Situated</tt> and its submodules gain the ability
|
5
|
+
# to provide locational and dimensional data about their visual DOM
|
6
|
+
# representation within the browser.
|
7
|
+
#
|
8
|
+
module Situated
|
9
|
+
`window.styleString=function(el,prop){if(el.currentStyle){return el.currentStyle[prop.replace(/[_-]\\D/g, function(match){return match.charAt(1).toUpperCase();})];};var computed=document.defaultView.getComputedStyle(el,null);return(computed?computed.getPropertyValue([prop.replace(/[A-Z]/g, function(match){return('-'+match.charAt(0).toLowerCase());})]):null);}`
|
10
|
+
module PositionAndSize
|
11
|
+
|
12
|
+
# call-seq:
|
13
|
+
# situated.height -> integer
|
14
|
+
# returns the height of the _situated_ in pixels as an integer.
|
15
|
+
# height is the amount of vertical space the content requires
|
16
|
+
# plus top padding, bottom padding, top border, and bottom border, if any
|
17
|
+
#
|
18
|
+
#
|
19
|
+
# For example,
|
20
|
+
# situated = Document['#situated']
|
21
|
+
# where _situated_ is an element whose content is 200px tall
|
22
|
+
# with a top padding of 20px, bottom padding of 10px
|
23
|
+
# and a top border of 1px
|
24
|
+
#
|
25
|
+
# situated.height #=> 231 (200 + 20 + 10 + 1)
|
26
|
+
#
|
27
|
+
def height
|
28
|
+
self.size[:y]
|
29
|
+
end
|
30
|
+
|
31
|
+
# call-seq:
|
32
|
+
# situated.width -> integer
|
33
|
+
# returns the width of the _situated_ in pixels as an integer
|
34
|
+
# width is the amount of horizontal space the content requires
|
35
|
+
# plus left padding, right padding, left border, and right border, if any
|
36
|
+
#
|
37
|
+
# For example,
|
38
|
+
# situated = Document['#situated']
|
39
|
+
# where _situated_ is an element whose content is 200px wide
|
40
|
+
# with a left padding of 20px, right padding of 10px
|
41
|
+
# and a left border of 1px
|
42
|
+
#
|
43
|
+
# situated.width #=> 231 (200 + 20 + 10 + 1)
|
44
|
+
#
|
45
|
+
def width
|
46
|
+
self.size[:x]
|
47
|
+
end
|
48
|
+
|
49
|
+
def scroll_top
|
50
|
+
self.scroll[:y]
|
51
|
+
end
|
52
|
+
|
53
|
+
def scroll_left
|
54
|
+
self.scroll[:x]
|
55
|
+
end
|
56
|
+
|
57
|
+
def scroll_height
|
58
|
+
self.scroll_size[:y]
|
59
|
+
end
|
60
|
+
|
61
|
+
def scroll_width
|
62
|
+
self.scroll_size[:x]
|
63
|
+
end
|
64
|
+
|
65
|
+
def top
|
66
|
+
self.position[:x]
|
67
|
+
end
|
68
|
+
|
69
|
+
def left
|
70
|
+
self.position[:y]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
module Element
|
75
|
+
include PositionAndSize
|
76
|
+
|
77
|
+
def size
|
78
|
+
return self.window.size if self.is_body?
|
79
|
+
return {:x => `#{self}.__native__.offsetWidth`, :y => `#{self}.__native__.offsetHeight`}
|
80
|
+
end
|
81
|
+
|
82
|
+
# call-seq:
|
83
|
+
# situated.scroll -> hash
|
84
|
+
# returns a hash containing keys <tt>:x<tt> and <tt>:y<tt> representing the
|
85
|
+
# the distance that a _situated_ has been scrolled
|
86
|
+
# originating at the top left corner of the _situated_
|
87
|
+
#
|
88
|
+
# For example if a _situated_ has been scrolled 10px left and 5px down
|
89
|
+
# situated.scroll #=> {:x => 10, :y => 5}
|
90
|
+
def scroll
|
91
|
+
return self.window.scroll if self.is_body?
|
92
|
+
return {:x => `#{self}.__native__.scrollLeft`, :y => `#{self}.__native__.scrollTop`}
|
93
|
+
end
|
94
|
+
|
95
|
+
def scrolls
|
96
|
+
`var element = this.__native__, position = {x : 0, y : 0};
|
97
|
+
while (element && !c$Situated.c$Utilities.m$is_body_bool(element)){
|
98
|
+
position.x += element.scrollLeft;
|
99
|
+
position.y += element.scrollTop;
|
100
|
+
element = element.parentNode;
|
101
|
+
}`
|
102
|
+
return {:x => `position.x`, :y => `position.y`}
|
103
|
+
end
|
104
|
+
|
105
|
+
# call-seq:
|
106
|
+
# situated.scroll_size -> hash
|
107
|
+
# returns a hash containing keys <tt>:x<tt> and <tt>:y<tt> representing the
|
108
|
+
# the size that a _situated_ included potential scrollable area.
|
109
|
+
#
|
110
|
+
# For example if a _situated_ has been a visible width of 100px and visible height of 50px
|
111
|
+
# and 100 additional pixels of horizontal unseen content that can be scrolled to uncover
|
112
|
+
#
|
113
|
+
# situated.scroll_size #=> {:x => 200, :y => 50}
|
114
|
+
#
|
115
|
+
def scroll_size
|
116
|
+
return self.window.scroll_size if self.is_body?
|
117
|
+
return {:x => `#{self}.__native__.scrollWidth`, :y => `#{self}.__native__.scrollHeight`}
|
118
|
+
end
|
119
|
+
|
120
|
+
# call-seq:
|
121
|
+
# situated.scroll_to(x,y) -> situated
|
122
|
+
#
|
123
|
+
# Scrolls the _situated_ to the position referenced by the coordinates <tt>x</tt> and <tt>y</tt>
|
124
|
+
# which are pixel dimensions measured from the top left corner of hte _situated_
|
125
|
+
#
|
126
|
+
# Will scroll to the limit of one dimension if a cooridnate for that dimension
|
127
|
+
# is larger than the size element
|
128
|
+
#
|
129
|
+
# No scrolling in a direction will occur if scrolling in that direction would have no effect.
|
130
|
+
#
|
131
|
+
# Examples:
|
132
|
+
#
|
133
|
+
# Given a _situated_ that is 200px wide and 500px long and has
|
134
|
+
# only 150px of horizontal dimension visible at any time and
|
135
|
+
# only 200px of vertical dimension visible at any time
|
136
|
+
#
|
137
|
+
# situated.scroll_to(10,10)
|
138
|
+
# will scroll the visible area 10 pixels from the left and 10pixels from the top
|
139
|
+
#
|
140
|
+
# scroll.scroll_to(200,0) will scroll the _situated_ to the maximum left scrolling possible
|
141
|
+
# and return the _situated_ to the original height position.
|
142
|
+
#
|
143
|
+
def scroll_to(x,y)
|
144
|
+
if self.is_body?
|
145
|
+
self.window.scroll_to(x, y)
|
146
|
+
else
|
147
|
+
`this.__native__.scrollLeft = x`
|
148
|
+
`this.__native__.scrollTop = y`
|
149
|
+
end
|
150
|
+
return self
|
151
|
+
end
|
152
|
+
|
153
|
+
# call-seq:
|
154
|
+
# situated.offset_parent -> element
|
155
|
+
#
|
156
|
+
# returns the parent element that provides the visual offset from
|
157
|
+
# the top left corner of the viewport
|
158
|
+
#
|
159
|
+
# This is typically the closest statically position element or <tt>body</tt>
|
160
|
+
def offset_parent
|
161
|
+
element = self
|
162
|
+
return nil if element.is_body?
|
163
|
+
|
164
|
+
# All engines except trident have a native offsetParent property
|
165
|
+
`var e = #{element}.__native__.offsetParent`
|
166
|
+
return `$E(#{element}.__native__.offsetParent)` unless trident?
|
167
|
+
|
168
|
+
# For trident we walk the DOM until we have a static positioned element or reach the body
|
169
|
+
while ((element = `$E(#{element}.__native__.parentNode)`) && !element.is_body?) do
|
170
|
+
return element unless element.styles[:position] == 'static'
|
171
|
+
end
|
172
|
+
|
173
|
+
return nil
|
174
|
+
end
|
175
|
+
|
176
|
+
def offsets
|
177
|
+
`var native = this.__native__`
|
178
|
+
if trident?
|
179
|
+
`var bound = native.getBoundingClientRect()`
|
180
|
+
`var html = this.m$document.__native__.documentElement`
|
181
|
+
return {
|
182
|
+
:x => `bound.left + html.scrollLeft - html.clientLeft`,
|
183
|
+
:y => `bound.top + html.scrollTop - html.clientTop`
|
184
|
+
}
|
185
|
+
end
|
186
|
+
|
187
|
+
`var position = {x: 0, y: 0}`
|
188
|
+
return {:x => `position.x`, :y => `position.y`} if self.is_body?
|
189
|
+
`
|
190
|
+
|
191
|
+
element = native
|
192
|
+
while (element && !c$Situated.c$Utilities.m$is_body_bool(element)){
|
193
|
+
position.x += element.offsetLeft;
|
194
|
+
position.y += element.offsetTop;
|
195
|
+
|
196
|
+
if (#{gecko?}){
|
197
|
+
if (!c$Situated.c$Utilities.m$border_box(element)){
|
198
|
+
position.x += c$Situated.c$Utilities.m$left_border(element);
|
199
|
+
position.y += c$Situated.c$Utilities.m$top_border(element);
|
200
|
+
}
|
201
|
+
var parent = element.parentNode;
|
202
|
+
if (parent && window.styleString(parent, 'overflow') != 'visible'){
|
203
|
+
position.x += c$Situated.c$Utilities.m$left_border(parent);
|
204
|
+
position.y += c$Situated.c$Utilities.m$top_border(parent);
|
205
|
+
}
|
206
|
+
} else if (element != this && #{webkit?}){
|
207
|
+
position.x += c$Situated.c$Utilities.m$left_border(element);
|
208
|
+
position.y += c$Situated.c$Utilities.m$top_border(element);
|
209
|
+
}
|
210
|
+
|
211
|
+
element = element.offsetParent;
|
212
|
+
}
|
213
|
+
|
214
|
+
if (#{gecko?} && !c$Situated.c$Utilities.m$border_box(native)){
|
215
|
+
position.x -= c$Situated.c$Utilities.m$left_border(native);
|
216
|
+
position.y -= c$Situated.c$Utilities.m$top_border(native);
|
217
|
+
}
|
218
|
+
`
|
219
|
+
return {:x => `position.x`, :y => `position.y`}
|
220
|
+
end
|
221
|
+
|
222
|
+
def position_at(x,y)
|
223
|
+
native = `this.__native__`
|
224
|
+
h = {:left => x - Situated::Utilities.styleNumber(native, `'margin-left'`), :top => y - Situated::Utilities.styleNumber(native, `'margin-top'`), :position => 'absolute'}
|
225
|
+
self.set_styles(h)
|
226
|
+
end
|
227
|
+
|
228
|
+
def position(relative_to = nil)
|
229
|
+
# if (isBody(this)) return {x: 0, y: 0};
|
230
|
+
offset = self.offsets
|
231
|
+
scroll = self.scrolls
|
232
|
+
position = {:x => offset[:x] - scroll[:x], :y => offset[:y] - scroll[:y]}
|
233
|
+
relative_position = {:x => 0, :y => 0}
|
234
|
+
# relativePosition = (relative_to && (relative_to = $(relative_to)) ? relative_to.position : {x: 0, y: 0};
|
235
|
+
a = {:x => position[:x] - relative_position[:x], :y => position[:y] - relative_position[:y]}
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
module Viewport
|
240
|
+
include PositionAndSize
|
241
|
+
|
242
|
+
def size
|
243
|
+
win = self.window
|
244
|
+
return {:x => `#{win}.__native__.innerWidth`, :y => `#{win}.__native__.innerHeight`} if (presto? || webkit?)
|
245
|
+
doc = Situated::Utilities.native_compat_element(self)
|
246
|
+
return {:x => `#{doc}.__native__.clientWidth`, :y => `#{doc}.__native__.clientHeight`}
|
247
|
+
end
|
248
|
+
|
249
|
+
def scroll
|
250
|
+
win = self.window
|
251
|
+
doc = Situated::Utilities.native_compat_element(self)
|
252
|
+
return {:x => `#{win}.__native__.pageXOffset` || `#{doc}.__native__.scrollLeft`, :y => `#{win}.__native__pageYOffset` || `#{doc}.__native__.scrollTop`}
|
253
|
+
end
|
254
|
+
|
255
|
+
def scroll_size
|
256
|
+
doc = Situated::Utilities.native_compat_element(self)
|
257
|
+
min = self.size
|
258
|
+
return {:x => `Math.max(#{doc}.__native__.scrollWidth, #{min[:x]})`, :y => `Math.max(#{doc}.__native__.scrollHeight,#{ min[:y]})`}
|
259
|
+
end
|
260
|
+
|
261
|
+
# call-seq:
|
262
|
+
# viewport.position -> hash
|
263
|
+
# returns the position of the viewport, which is always {:x => 0, :y => 0}
|
264
|
+
#
|
265
|
+
def position
|
266
|
+
return {:x => 0, :y => 0}
|
267
|
+
end
|
268
|
+
|
269
|
+
# call-seq:
|
270
|
+
# viewport.position -> hash
|
271
|
+
# returns the coordinates of the viewport in pixesl as integers
|
272
|
+
# within a hash with the keys
|
273
|
+
# :top
|
274
|
+
# :left
|
275
|
+
# :bottom
|
276
|
+
# :right
|
277
|
+
# :height
|
278
|
+
# :width
|
279
|
+
#
|
280
|
+
# For example, a _viewport_ that is 500px wide and 800px tall will return
|
281
|
+
# viewport.coordinates
|
282
|
+
# {:top => 0,
|
283
|
+
# :left => 0,
|
284
|
+
# :bottom => 800,
|
285
|
+
# :right => 500,
|
286
|
+
# :height => 800,
|
287
|
+
# :width => 500 }
|
288
|
+
#
|
289
|
+
def coordinates
|
290
|
+
size = self.size
|
291
|
+
return {:top => 0, :left => 0, :bottom => size[:y], :right => size[:x], :height => size[:y], :width => size[:x]}
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
module Utilities
|
296
|
+
def self.is_body?(element)
|
297
|
+
`(/^(?:body|html)$/i).test(element.tagName)`
|
298
|
+
end
|
299
|
+
|
300
|
+
def self.styleNumber(native_element, style)
|
301
|
+
`parseInt(window.styleString(native_element, style)) || 0`
|
302
|
+
end
|
303
|
+
|
304
|
+
def self.border_box(element)
|
305
|
+
`window.styleString(element, '-moz-box-sizing') == 'border-box'`
|
306
|
+
end
|
307
|
+
|
308
|
+
def self.top_border(element)
|
309
|
+
`c$Situated.c$Utilities.m$styleNumber(element, 'border-top-width')`
|
310
|
+
end
|
311
|
+
|
312
|
+
def self.left_border(element)
|
313
|
+
`c$Situated.c$Utilities.m$styleNumber(element, 'border-left-width')`
|
314
|
+
end
|
315
|
+
|
316
|
+
def self.native_compat_element(element)
|
317
|
+
`var doc = #{element.document}.__native__`
|
318
|
+
`$E((!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body)`
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
class Element
|
324
|
+
include Situated::Element
|
325
|
+
end
|
326
|
+
|
327
|
+
Document.extend(Situated::Viewport)
|
328
|
+
Window.extend(Situated::Viewport)
|