summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Crute <mcrute@gmail.com>2010-01-17 12:06:15 -0500
committerMike Crute <mcrute@gmail.com>2010-01-17 12:06:15 -0500
commit55974d21a824378b287e563bce4c32597060cfca (patch)
treeff8426efe10ff98189fee26583f061fb08f19a50
downloadsantoprene_designer_site-master.tar.bz2
santoprene_designer_site-master.tar.xz
santoprene_designer_site-master.zip
Initial importHEADmaster
-rw-r--r--Designer.tmproj278
-rwxr-xr-xbuild_system/cmpcss125
-rwxr-xr-xbuild_system/get_jsfiles.sed25
-rwxr-xr-xbuild_system/make.gzip130
-rwxr-xr-xbuild_system/yui_compressor.jarbin0 -> 837686 bytes
-rwxr-xr-xcgi-bin/card_table.pl371
-rwxr-xr-xcgi-bin/designer.sql15
-rwxr-xr-xcgi-bin/html/login.html41
-rwxr-xr-xcgi-bin/login.pl209
-rwxr-xr-xcgi-bin/sketchbook.pl142
-rwxr-xr-xcgi-bin/sketchbook_resolver.pl49
-rwxr-xr-xcgi-bin/sop_preview_proxy.pl28
-rwxr-xr-xcgi-bin/sop_proxy.pl18
-rwxr-xr-xdeploy71
-rwxr-xr-xdocroot/.htaccess46
-rwxr-xr-xdocroot/account_deleted.html23
-rwxr-xr-xdocroot/application.css463
-rwxr-xr-xdocroot/application.js67
-rwxr-xr-xdocroot/blank.gif5
-rwxr-xr-xdocroot/classes/application.js227
-rwxr-xr-xdocroot/classes/bezel.class.js154
-rwxr-xr-xdocroot/classes/card.class.js378
-rwxr-xr-xdocroot/classes/chip.class.js269
-rwxr-xr-xdocroot/classes/cookie.class.js70
-rwxr-xr-xdocroot/classes/decoder.module.js205
-rwxr-xr-xdocroot/classes/history.class.js208
-rwxr-xr-xdocroot/classes/layout.class.js51
-rwxr-xr-xdocroot/classes/layouts/layout.custom.class.js219
-rwxr-xr-xdocroot/classes/layouts/layout.error.class.js33
-rwxr-xr-xdocroot/classes/layouts/layout.primary.class.js411
-rwxr-xr-xdocroot/classes/layouts/layout.special.class.js43
-rwxr-xr-xdocroot/classes/overlay.class.js118
-rwxr-xr-xdocroot/classes/roundcorners.class.js60
-rwxr-xr-xdocroot/classes/sketchbook.class.js288
-rwxr-xr-xdocroot/classes/sme.namespace.js140
-rwxr-xr-xdocroot/classes/table.class.js315
-rwxr-xr-xdocroot/classes/utility.js199
-rwxr-xr-xdocroot/custom_content/2col.css68
-rwxr-xr-xdocroot/custom_content/2col.js170
-rwxr-xr-xdocroot/data/card_tables.js63
-rwxr-xr-xdocroot/data/persist_chips.js3
-rwxr-xr-xdocroot/data/strings.en.js122
-rwxr-xr-xdocroot/favicon.icobin0 -> 1150 bytes
-rwxr-xr-xdocroot/graphics/chip.psdbin0 -> 226411 bytes
-rwxr-xr-xdocroot/images/arrow_down.gifbin0 -> 65 bytes
-rwxr-xr-xdocroot/images/arrow_right_grey.gifbin0 -> 64 bytes
-rwxr-xr-xdocroot/images/blank.gifbin0 -> 49 bytes
-rwxr-xr-xdocroot/images/close.gifbin0 -> 66 bytes
-rwxr-xr-xdocroot/images/email.gifbin0 -> 65 bytes
-rwxr-xr-xdocroot/images/history.gifbin0 -> 66 bytes
-rwxr-xr-xdocroot/images/loader.gifbin0 -> 1877 bytes
-rwxr-xr-xdocroot/images/logo.gifbin0 -> 2066 bytes
-rwxr-xr-xdocroot/images/pill.gifbin0 -> 836 bytes
-rwxr-xr-xdocroot/images/plus.gifbin0 -> 64 bytes
-rwxr-xr-xdocroot/images/print.gifbin0 -> 64 bytes
-rwxr-xr-xdocroot/images/tagline.pngbin0 -> 52535 bytes
-rwxr-xr-xdocroot/images/trash_can.jpgbin0 -> 1286 bytes
-rwxr-xr-xdocroot/index.html45
-rwxr-xr-xdocroot/lib/firebug/errorIcon.pngbin0 -> 457 bytes
-rwxr-xr-xdocroot/lib/firebug/firebug.css209
-rwxr-xr-xdocroot/lib/firebug/firebug.html23
-rwxr-xr-xdocroot/lib/firebug/firebug.js672
-rwxr-xr-xdocroot/lib/firebug/firebugx.js10
-rwxr-xr-xdocroot/lib/firebug/infoIcon.pngbin0 -> 524 bytes
-rwxr-xr-xdocroot/lib/firebug/warningIcon.pngbin0 -> 516 bytes
-rwxr-xr-xdocroot/lib/prototype.js3277
-rwxr-xr-xdocroot/lib/scriptaculous/builder.js136
-rwxr-xr-xdocroot/lib/scriptaculous/controls.js875
-rwxr-xr-xdocroot/lib/scriptaculous/dragdrop.js970
-rwxr-xr-xdocroot/lib/scriptaculous/effects.js1094
-rwxr-xr-xdocroot/lib/scriptaculous/scriptaculous.js58
-rwxr-xr-xdocroot/lib/scriptaculous/slider.js277
-rwxr-xr-xdocroot/lib/scriptaculous/sound.js60
-rwxr-xr-xdocroot/lib/scriptaculous/unittest.js564
-rwxr-xr-xdocroot/lib/swfobject/swfobject.js233
-rwxr-xr-xdocroot/logged_out.html27
-rwxr-xr-xdocroot/pngbehavior.htc42
-rwxr-xr-xdocroot/specialcases.css16
-rwxr-xr-xmake135
-rwxr-xr-xsyncdev10
80 files changed, 14653 insertions, 0 deletions
diff --git a/Designer.tmproj b/Designer.tmproj
new file mode 100644
index 0000000..9997ff3
--- /dev/null
+++ b/Designer.tmproj
@@ -0,0 +1,278 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3<plist version="1.0">
4<dict>
5 <key>currentDocument</key>
6 <string>syncdev</string>
7 <key>documents</key>
8 <array>
9 <dict>
10 <key>expanded</key>
11 <true/>
12 <key>name</key>
13 <string>aes_designer</string>
14 <key>regexFolderFilter</key>
15 <string>!.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$</string>
16 <key>sourceDirectory</key>
17 <string></string>
18 </dict>
19 </array>
20 <key>fileHierarchyDrawerWidth</key>
21 <integer>246</integer>
22 <key>metaData</key>
23 <dict>
24 <key>docroot/application.js</key>
25 <dict>
26 <key>caret</key>
27 <dict>
28 <key>column</key>
29 <integer>0</integer>
30 <key>line</key>
31 <integer>0</integer>
32 </dict>
33 <key>firstVisibleColumn</key>
34 <integer>0</integer>
35 <key>firstVisibleLine</key>
36 <integer>3</integer>
37 </dict>
38 <key>docroot/classes/application.js</key>
39 <dict>
40 <key>caret</key>
41 <dict>
42 <key>column</key>
43 <integer>29</integer>
44 <key>line</key>
45 <integer>28</integer>
46 </dict>
47 <key>columnSelection</key>
48 <false/>
49 <key>firstVisibleColumn</key>
50 <integer>0</integer>
51 <key>firstVisibleLine</key>
52 <integer>0</integer>
53 <key>selectFrom</key>
54 <dict>
55 <key>column</key>
56 <integer>22</integer>
57 <key>line</key>
58 <integer>28</integer>
59 </dict>
60 <key>selectTo</key>
61 <dict>
62 <key>column</key>
63 <integer>29</integer>
64 <key>line</key>
65 <integer>28</integer>
66 </dict>
67 </dict>
68 <key>docroot/classes/card.class.js</key>
69 <dict>
70 <key>caret</key>
71 <dict>
72 <key>column</key>
73 <integer>6</integer>
74 <key>line</key>
75 <integer>272</integer>
76 </dict>
77 <key>firstVisibleColumn</key>
78 <integer>0</integer>
79 <key>firstVisibleLine</key>
80 <integer>46</integer>
81 </dict>
82 <key>docroot/classes/cookie.class.js</key>
83 <dict>
84 <key>caret</key>
85 <dict>
86 <key>column</key>
87 <integer>0</integer>
88 <key>line</key>
89 <integer>0</integer>
90 </dict>
91 <key>firstVisibleColumn</key>
92 <integer>0</integer>
93 <key>firstVisibleLine</key>
94 <integer>17</integer>
95 </dict>
96 <key>docroot/classes/history.class.js</key>
97 <dict>
98 <key>caret</key>
99 <dict>
100 <key>column</key>
101 <integer>0</integer>
102 <key>line</key>
103 <integer>0</integer>
104 </dict>
105 <key>firstVisibleColumn</key>
106 <integer>0</integer>
107 <key>firstVisibleLine</key>
108 <integer>136</integer>
109 </dict>
110 <key>docroot/classes/layouts/layout.primary.class.js</key>
111 <dict>
112 <key>caret</key>
113 <dict>
114 <key>column</key>
115 <integer>0</integer>
116 <key>line</key>
117 <integer>0</integer>
118 </dict>
119 <key>firstVisibleColumn</key>
120 <integer>0</integer>
121 <key>firstVisibleLine</key>
122 <integer>358</integer>
123 </dict>
124 <key>docroot/classes/overlay.class.js</key>
125 <dict>
126 <key>caret</key>
127 <dict>
128 <key>column</key>
129 <integer>0</integer>
130 <key>line</key>
131 <integer>0</integer>
132 </dict>
133 <key>firstVisibleColumn</key>
134 <integer>0</integer>
135 <key>firstVisibleLine</key>
136 <integer>65</integer>
137 </dict>
138 <key>docroot/classes/roundcorners.class.js</key>
139 <dict>
140 <key>caret</key>
141 <dict>
142 <key>column</key>
143 <integer>0</integer>
144 <key>line</key>
145 <integer>0</integer>
146 </dict>
147 <key>firstVisibleColumn</key>
148 <integer>0</integer>
149 <key>firstVisibleLine</key>
150 <integer>7</integer>
151 </dict>
152 <key>docroot/data/strings.en.js</key>
153 <dict>
154 <key>caret</key>
155 <dict>
156 <key>column</key>
157 <integer>3</integer>
158 <key>line</key>
159 <integer>64</integer>
160 </dict>
161 <key>firstVisibleColumn</key>
162 <integer>0</integer>
163 <key>firstVisibleLine</key>
164 <integer>0</integer>
165 </dict>
166 <key>docroot/specialcases.css</key>
167 <dict>
168 <key>caret</key>
169 <dict>
170 <key>column</key>
171 <integer>0</integer>
172 <key>line</key>
173 <integer>0</integer>
174 </dict>
175 <key>firstVisibleColumn</key>
176 <integer>0</integer>
177 <key>firstVisibleLine</key>
178 <integer>0</integer>
179 </dict>
180 <key>syncdev</key>
181 <dict>
182 <key>caret</key>
183 <dict>
184 <key>column</key>
185 <integer>22</integer>
186 <key>line</key>
187 <integer>9</integer>
188 </dict>
189 <key>firstVisibleColumn</key>
190 <integer>0</integer>
191 <key>firstVisibleLine</key>
192 <integer>0</integer>
193 </dict>
194 </dict>
195 <key>openDocuments</key>
196 <array>
197 <string>docroot/classes/cookie.class.js</string>
198 <string>docroot/classes/overlay.class.js</string>
199 <string>docroot/classes/roundcorners.class.js</string>
200 <string>docroot/classes/history.class.js</string>
201 <string>docroot/classes/card.class.js</string>
202 <string>docroot/classes/layouts/layout.primary.class.js</string>
203 <string>docroot/classes/application.js</string>
204 <string>docroot/application.js</string>
205 <string>docroot/data/strings.en.js</string>
206 <string>docroot/specialcases.css</string>
207 <string>syncdev</string>
208 </array>
209 <key>showFileHierarchyDrawer</key>
210 <false/>
211 <key>showFileHierarchyPanel</key>
212 <true/>
213 <key>treeState</key>
214 <dict>
215 <key>aes_designer</key>
216 <dict>
217 <key>isExpanded</key>
218 <true/>
219 <key>subItems</key>
220 <dict>
221 <key>build_system</key>
222 <dict>
223 <key>isExpanded</key>
224 <true/>
225 <key>subItems</key>
226 <dict/>
227 </dict>
228 <key>cgi-bin</key>
229 <dict>
230 <key>isExpanded</key>
231 <true/>
232 <key>subItems</key>
233 <dict/>
234 </dict>
235 <key>docroot</key>
236 <dict>
237 <key>isExpanded</key>
238 <true/>
239 <key>subItems</key>
240 <dict>
241 <key>classes</key>
242 <dict>
243 <key>isExpanded</key>
244 <true/>
245 <key>subItems</key>
246 <dict>
247 <key>layouts</key>
248 <dict>
249 <key>isExpanded</key>
250 <true/>
251 <key>subItems</key>
252 <dict/>
253 </dict>
254 </dict>
255 </dict>
256 <key>data</key>
257 <dict>
258 <key>isExpanded</key>
259 <true/>
260 <key>subItems</key>
261 <dict/>
262 </dict>
263 <key>lib</key>
264 <dict>
265 <key>isExpanded</key>
266 <true/>
267 <key>subItems</key>
268 <dict/>
269 </dict>
270 </dict>
271 </dict>
272 </dict>
273 </dict>
274 </dict>
275 <key>windowFrame</key>
276 <string>{{151, 48}, {1199, 830}}</string>
277</dict>
278</plist>
diff --git a/build_system/cmpcss b/build_system/cmpcss
new file mode 100755
index 0000000..d347a63
--- /dev/null
+++ b/build_system/cmpcss
@@ -0,0 +1,125 @@
1#!/usr/bin/python
2###############
3# cmpcss
4# Compress a CSS file in prepration for production use.
5#
6# AUTHOR
7# Michael Crute (mcrute@gmail.com)
8# DATE
9# 16 June 2006
10# VERSION
11# 1.0
12# PURPOSE
13# An implementation of a CSS compressor in Python. Written to
14# prepare development CSS to production ready CSS. Does the
15# following:
16# * Removes spaces intelligently
17# * Removes charset declarations
18# * Removes extra semicolons
19# * Flattens the file (tabs and line breaks)
20# USAGE
21# cmpcss site_dev.css > site_prod.css
22# LICENSE
23# You may copy and redistribute this program as you see fit, with no
24# restrictions.
25# WARRANTY
26# This program comes with NO warranty, real or implied. If it eats
27# your CSS, it's not my problem, you should have kept a backup.
28###############
29
30import sys
31import re
32#import string
33
34# The chewy caramel center of the program
35# Don't bitch about the short variable names, it makes it easy to read
36def cleanline(theLine):
37 # Kills line breaks, tabs, and double spaces
38 p = re.compile('(\n|\r|\t|\f|\v)+')
39 m = p.sub('',theLine)
40
41 # Kills double spaces
42 p = re.compile('( )+')
43 m = p.sub(' ',m)
44
45 # Removes last semicolon before }
46 p = re.compile('(; }|;})+')
47 m = p.sub('}',m)
48
49 # Removes space before {
50 p = re.compile('({ )+')
51 m = p.sub('{',m)
52
53 # Removes all comments
54 p = re.compile('/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/')
55 m = p.sub('',m)
56
57 # Strip off the Charset
58 p = re.compile('@CHARSET .*;')
59 m = p.sub('',m)
60
61 # Strip spaces before the {
62 p = re.compile(' {')
63 m = p.sub('{',m)
64
65 # Strip space after :
66 p = re.compile(': ')
67 m = p.sub(':',m)
68
69 # Strip space after ,
70 p = re.compile(', ')
71 m = p.sub(',',m)
72
73 # Strip space after ;
74 p = re.compile('; ')
75 m = p.sub(';',m)
76
77# May return to add color code compression
78# not a huge space saver so also not high priority
79# p = re.compile('.*#[0-9A-F]{6}.*')
80# cc = p.match(m)
81
82# if cc:
83# cc = compcolor(m)
84
85 return m
86
87#def compcolor(m):
88
89# Boring usage info for people too dumb to use the program
90def printusage():
91 print ''
92 print 'SoftGroup CSS Compressor'
93 print ''
94 print 'This is a compressor for CSS files.'
95 print 'As far as I know it doesn\'t break the standards'
96 print 'compliance of the file. If you can find a way'
97 print 'to make it break a file please email me.'
98 print ''
99 print 'Usage: cmpcss <filename>'
100 sys.exit()
101
102# The main routine
103if ( __name__ == "__main__" ):
104 if (len(sys.argv) <= 1):
105 printusage()
106
107 # Open the file for reading
108 try:
109 theFile = file(sys.argv[1], "r")
110 except IOError:
111 print 'I couldn\'t open your file.'
112 sys.exit()
113
114 # Initialize temp so it's not scoped to the for loop
115 temp = ''
116
117 # Loop through the file line by line and clean it
118 for line in theFile:
119 temp = temp + cleanline(line)
120
121 # Once more, clean the entire file string
122 print cleanline(temp)
123
124 # Cleanup after ourselves
125 theFile.close()
diff --git a/build_system/get_jsfiles.sed b/build_system/get_jsfiles.sed
new file mode 100755
index 0000000..09e7a6d
--- /dev/null
+++ b/build_system/get_jsfiles.sed
@@ -0,0 +1,25 @@
1#!/bin/sed -f
2#
3# SED script for parsing out include file names from
4# the application.js file.
5#
6
7# Remove single line comments first
8/^\s+\/\//d
9
10# Remove multi-line comments
11/^\s+\/\*.+/d
12/\/\*/,/\*\// {
13 s/.*//g
14}
15
16# Replace the require lines with just their contents
17s/this\.require\(["']([^"']*)["']\);/\1/
18
19# Remove all lines that don't end in js
20# assumes we are getting all of our JS files
21/js$/!d
22
23# Remove the spaces before and after the filename
24# assumes there are no spaces in the filename iteself
25s/\s*//
diff --git a/build_system/make.gzip b/build_system/make.gzip
new file mode 100755
index 0000000..a95ed04
--- /dev/null
+++ b/build_system/make.gzip
@@ -0,0 +1,130 @@
1#!/bin/bash
2#
3# ExxonMobil Designer Site Build Script
4# by Mike Crute, EYEMG (mcrute@eyemg.com)
5#
6# This is a pretty simple build script just export a
7# tag or the trunk from SVN and run ./make, running
8# make (w/out the ./) will not work. You can then
9# take the generated tarball and explode it on the
10# production server w/out any further configuration.
11#
12
13cd docroot
14
15# Create the build directory and copy over the cgi-bin
16mkdir -p build/{docroot,cgi-bin}
17cp -R ../cgi-bin/* build/cgi-bin/
18
19# Create a build date file
20echo "Designer Site Built `date +'%Y-%m-%d %H:%M:%S'` EST" > build/docroot/build.date
21echo "Code Version: `grep 'releaseVersion' classes/sme.namespace.js | cut -d"'" -f 2 `" >> build/docroot/build.date
22echo "Subversion Revision: `svn info | grep 'Revision' | awk '{ print $2 }'`" >> build/docroot/build.date
23
24# Remove stuff from the cgi-bin that does not belong in
25# production.
26rm build/cgi-bin/*.sql
27
28# Also copy over any files that don't need special processing
29cp -R images \
30 blank.gif \
31 favicon.ico \
32 custom_content \
33build/docroot/
34
35# Prepare the .htaccess file for production
36#
37# We use a sparse layout in development where each class is
38# in its own separate file but in production we use a solid
39# layout that is also gzipped so we have to un-comment the
40# gzip headers in our htaccess file.
41
42sed '
43 /### PRODUCTION ###/,/### END PRODUCTION ###/ {
44 /^###.*/d
45 s/#//g
46 }
47' .htaccess > build/docroot/.htaccess
48
49# Minify the PNG Behavior
50sed '
51 /\/\*/,/\*\// {
52 /.*/d;
53 }
54' pngbehavior.htc > build/docroot/pngbehavior.htc
55
56# Concatenate the Javascript files into a single library file
57#
58# application.js loads all the javascript files in the development
59# environment. When we go to production we need to parse out the
60# actual script names being loaded and cat them, in order, into
61# the final output application.js file.
62
63# GNU sed uses the -r flag for extended regular expressions,
64# Darwin uses the -E flag for regular expressions. If neither
65# of these hold true we might as well fail.
66if [[ `uname` == 'Linux' ]]; then
67 MYFILES=`sed -r -f ../build_system/get_jsfiles.sed application.js`
68elif [[ `uname` == 'Darwin' ]]; then
69 MYFILES=`sed -E -f ../build_system/get_jsfiles.sed application.js`
70else
71 print 'No valid sed command could be determined.'
72 exit 1
73fi
74
75for item in $MYFILES; do
76 cat $item >> build/docroot/application.js.in
77done
78
79# Remove development code from the application code
80# and append it to the libraries files
81sed '/^;;;.*/d' build/docroot/application.js.in >> build/docroot/application.js.out
82sed '/^\/\*;;;.*/d' build/docroot/application.js.out >> build/docroot/application.js
83rm build/docroot/application.js.in build/docroot/application.js.out
84
85# Minify index.html by removing leading spaces on each line
86# as well as the ID comment, blank lines, and any script tags
87# that appear in the <head> of the document.
88sed 's/^[[:space:]]*//; /^$/d; s/\n$//; /<!--/,/-->/ { /.*/d; }' index.html > build/docroot/index.html
89sed 's/^[[:space:]]*//; /^$/d; s/\n$//; /<!--/,/-->/ { /.*/d; }' logged_out.html > build/docroot/logged_out.html
90
91# Minify the application Javascript using the YUI Compressor
92java -jar ../build_system/yui_compressor.jar -o application.o build/docroot/application.js > /dev/null 2>&1
93mv application.o build/docroot/application.js
94
95# Minify the JSON data files
96mkdir -p build/docroot/data
97for item in `ls data/`; do
98 java -jar ../build_system/yui_compressor.jar -o build/docroot/data/$item data/$item > /dev/null 2>&1
99done
100
101# Can't minimize JSON, the semicolon at the end breaks everything
102cat data/card_tables.js > build/docroot/data/card_tables.js
103
104# Minify the application CSS using our custom compressor
105../build_system/cmpcss application.css > build/docroot/application.css
106../build_system/cmpcss specialcases.css > build/docroot/specialcases.css
107
108# Gzip all that should be gzipped. This really should be done server-side
109# but for now we work around it by statically compressing them at build time
110# and serving it with the correct headers in the .htaccess file
111gzip build/docroot/application.js
112gzip build/docroot/application.css
113gzip build/docroot/index.html
114
115mv build/docroot/application.js.gz build/docroot/application.js
116mv build/docroot/application.css.gz build/docroot/application.css
117mv build/docroot/index.html.gz build/docroot/index.html
118
119# Tar it all up in a development dump
120mv build aes_designer
121cd aes_designer
122
123# Removing the date since its really not needed
124tar -cjvf ../../aes_designer.tbz2 * > /dev/null 2>&1
125#tar -cjvf ../../aes_designer-`date +%Y%m%d_%H%M%S`.tbz2 * > /dev/null 2>&1
126
127
128cd ..
129
130rm -rf aes_designer/
diff --git a/build_system/yui_compressor.jar b/build_system/yui_compressor.jar
new file mode 100755
index 0000000..4efe293
--- /dev/null
+++ b/build_system/yui_compressor.jar
Binary files differ
diff --git a/cgi-bin/card_table.pl b/cgi-bin/card_table.pl
new file mode 100755
index 0000000..a4d26f6
--- /dev/null
+++ b/cgi-bin/card_table.pl
@@ -0,0 +1,371 @@
1#!/usr/bin/perl
2
3$|=1;
4
5srand;
6
7use strict;
8
9use Apache::Request;
10use Apache::Constants qw(REDIRECT);
11use Benchmark::Timer;
12use HTML::Template;
13use Bit::Vector;
14use Image::Magick;
15
16use Compose::local_lib;
17use Compose::db_connection;
18
19my $r = Apache::Request->new(Apache->request);
20$r->send_http_header('text/javascript');
21
22my $tt = new Benchmark::Timer;
23$tt->start('all');
24
25my $local_lib = new Compose::local_lib();
26
27my $dbh = new Compose::db_connection('localhost','aes','apache','webconnect');
28
29my $form;
30foreach my $key (sort $r->param) {
31 $form->{$key} = $local_lib->fix_spaces($r->param($key));
32}
33
34# table=home
35
36##################################################
37#
38#
39if ($r->method() eq "GET") {
40
41 if ($form->{'table'} eq "") {
42
43 my $qry = qq(select distinct select_0 from content_data where site_id=1 and form_id=48 and moderation_status=3 and date_0 <= NOW() );
44 my %tables = $dbh->queryDB($qry,'select_0');
45
46 print qq(
47[
48);
49 my @list=();
50 foreach my $table (sort { $a cmp $b } keys %tables) {
51 push @list,"\t\"$table\"";
52 }
53
54 print join(",\n",@list) . qq(
55]
56);
57
58
59
60 } elsif ($form->{'table'} ne "") {
61
62 my $qry = "";
63
64 my $ckb = "checkbox_1";
65 if ($form->{'table'} eq "intro") {
66 $ckb = "checkbox_0";
67 }
68
69 my $t = new Benchmark::Timer;
70
71 $form->{'num_cards'} = 15; # if ($form->{'num_cards'} eq "");
72 $form->{'w'} = 900 if ($form->{'w'} eq "" || $form->{'w'} < 900);
73 $form->{'h'} = 400 if ($form->{'h'} eq "" || $form->{'h'} < 400);
74 $form->{'max'} = 20 if ($form->{'max'} eq "");
75
76 # get cards
77 if ($form->{'table'} eq 'home') {
78 $qry = qq(
79 SELECT
80 content_id,
81 enc_content_id,
82 textfield_0 AS title,
83 select_0 AS category,
84 checkbox_4 AS protected,
85 image_1_width AS rotated_width,
86 image_1_height AS rotated_height,
87 image_2_width AS zdegree_width,
88 image_2_height AS zdegree_height,
89 CONCAT(clients.website.site_url,clients.website.publish_docroot,"/",human_dir,"/index.html") AS page_loc,
90 CONCAT(clients.website.server_docroot, clients.website.publish_docroot,"/",human_dir,"/orig/",image_1) AS publish_loc,
91 CONCAT(clients.website.site_url,clients.website.publish_docroot,"/",human_dir,"/orig/",image_1) AS rotated_image,
92 CONCAT(clients.website.site_url,clients.website.publish_docroot,"/",human_dir,"/orig/",image_2) AS zdegree_image
93 FROM
94 content_data,
95 clients.website
96 WHERE
97 clients.website.site_id = content_data.site_id AND
98 content_data.site_id = 1 AND
99 form_id = 48 AND
100 moderation_status >= 3 AND
101 date_0 <= NOW() AND
102 checkbox_0 = "Yes" AND
103 $ckb = "Yes" AND
104 checkbox_2 != "Yes" AND
105 !(textfield_5 is NULL or textfield_5 = "")
106 ORDER BY
107 last_modified_date
108 LIMIT
109 $form->{'num_cards'}
110 );
111 } else {
112 #$qry = qq( select content_id,enc_content_id,textfield_0 as title, select_0 as category, image_1_height as rotated_height, image_2_width as zdegree_width, image_2_height as zdegree_height, CONCAT(clients.website.site_url,clients.website.publish_docroot,"/",human_dir,"/index.html") as page_loc , CONCAT(clients.website.server_docroot, clients.website.publish_docroot,"/",human_dir,"/orig/",image_1) as publish_loc, CONCAT(clients.website.site_url,clients.website.publish_docroot,"/",human_dir,"/orig/",image_1) as rotated_image, CONCAT(clients.website.site_url,clients.website.publish_docroot,"/",human_dir,"/orig/",image_2) as zdegree_image from content_data,clients.website where clients.website.site_id=content_data.site_id and content_data.site_id=1 and form_id=48 and moderation_status >=3 and date_0 <= NOW() and select_0="$form->{'table'}" and $ckb="Yes" and !(textfield_5 is NULL or textfield_5 = "") and checkbox_2 != "Yes" order by last_modified_date limit $form->{'num_cards'}) ;
113
114 $qry = qq(
115 SELECT
116 content_id,
117 enc_content_id,
118 textfield_0 AS title,
119 select_0 AS category,
120 checkbox_4 AS protected,
121 image_1_width AS rotated_width,
122 image_1_height AS rotated_height,
123 image_2_width AS zdegree_width,
124 image_2_height AS zdegree_height,
125 CONCAT(clients.website.site_url,clients.website.publish_docroot,"/",human_dir,"/index.html") AS page_loc,
126 CONCAT(clients.website.server_docroot, clients.website.publish_docroot,"/",human_dir,"/orig/",image_1) AS publish_loc,
127 CONCAT(clients.website.site_url,clients.website.publish_docroot,"/",human_dir,"/orig/",image_1) AS rotated_image,
128 CONCAT(clients.website.site_url,clients.website.publish_docroot,"/",human_dir,"/orig/",image_2) AS zdegree_image
129 FROM
130 content_data,
131 clients.website
132 WHERE
133 clients.website.site_id = content_data.site_id AND
134 content_data.site_id = 1 AND
135 form_id = 48 AND
136 moderation_status >= 3 AND
137 date_0 <= NOW() AND
138 select_0 = "$form->{'table'}" AND
139 $ckb = "Yes" AND
140 checkbox_2 != "Yes" AND
141 !(textfield_5 IS NULL OR textfield_5 = "")
142 ORDER BY
143 last_modified_date
144 LIMIT
145 $form->{'num_cards'}
146 );
147
148 }
149
150 my %res = $dbh->queryRawDB($qry);
151
152 my @master_v = new Bit::Vector($form->{'w'},$form->{'h'});
153 my ($hei,$wid);
154 my @rowM=();
155
156 my $back_image;
157 if ($form->{'gen'} ne "") {
158 $back_image = Image::Magick->new(size=>"$form->{'w'} x $form->{'h'}");
159 $back_image->ReadImage('xc:white');
160 }
161
162 my ($idx,$x,$y);
163
164 print qq( [ \n);
165 my $iter = 0;
166 foreach $idx (sort {$a <=> $b} keys %res) {
167
168 my $protected = ($res{$idx}{'protected'} eq 'Yes') ? 'true' : 'false';
169
170 ($x,$y,$back_image) = &place_data(\@master_v,$form->{'w'},$form->{'h'},$res{$idx}{'zdegree_image'},$res{$idx}{'rotated_image'},$res{$idx}{'rotated_width'},$res{$idx}{'rotated_height'},$res{$idx}{'publish_loc'},$back_image,$form->{'gen'},$form->{'max'});
171
172 print qq(\t{
173 "cid" : "$res{$idx}{'enc_content_id'}",
174 "title" : "$res{$idx}{'title'}",
175 "category" : "$res{$idx}{'category'}",
176 "locked" : $protected,
177 "x" : "$x",
178 "y" : "$y",
179 "chip" : "$res{$idx}{'rotated_image'}"
180 });
181 print ",\n" if (++$iter != keys(%res));
182 }
183 print qq( \n ]);
184
185 if ($form->{'gen'} ne "") {
186 $back_image->Write("/usr/web/designer/docroot/test.png");
187 }
188
189 }
190
191}
192
193#$tt->stop('all');
194#print $tt->report() ." <br>";
195#print qq(<img src="/test.png">) if ($form->{'gen'});
196
197###########################################
198#
199
200sub place_data {
201
202 my $master_v = shift;
203 my $page_width = shift;
204 my $page_height = shift;
205 my $image_data_zdegree_image = shift;
206 my $image_data_rotated_image = shift;
207 my $image_data_rotated_width = shift;
208 my $image_data_rotated_height = shift;
209 my $image_data_publish_loc = shift;
210 my $back_image = shift;
211 my $gen_image = shift;
212 my $max_collide = shift;
213
214 my $done = 0;
215 my $attempts = 0;
216
217 my $t = new Benchmark::Timer;
218
219 my ($max_x,$max_y,$xpos,$ypos,$vector,$sdone,$start,$attempts,$sattempts,$min,$max,$collisions,$xrand,$yrand );
220
221 while ((!$done) && $attempts < 500) {
222
223 $xrand = int(rand($page_width-20-$image_data_rotated_width)+10);
224 $yrand = int(rand($page_height-20-$image_data_rotated_height)+10);
225
226 my $max_collide = int($image_data_rotated_width*$image_data_rotated_height*($max_collide/100));
227
228 $max_x = $xrand+$image_data_rotated_width;
229 $max_y = $yrand+$image_data_rotated_height;
230
231 $t->start('3.1');
232
233 $collisions = 0;
234
235 for ($ypos = $yrand;$ypos <= $max_y; $ypos++) {
236 $vector = @{$master_v}->[$ypos];
237
238 $sdone=0;
239 $start = $xrand;
240 $sattempts = 0;
241
242 while (!$sdone && $sattempts++ < 20) {
243 ($min,$max) = $vector->Interval_Scan_inc($start);
244
245 if ($max+1 >= $page_width) {
246 $sdone=1;
247 }
248
249 if ($min < $max_x && $max) {
250 if ($max > $max_x || $max+1 >= $page_width) {
251 $collisions += $max_x - $min;
252 $sdone = 1;
253 } else {
254 $collisions += $max - $min;
255 $start = $max+1;
256 }
257
258 } elsif ($min > $max_x || !$max || !$min) {
259 $sdone=1;
260 }
261 }
262 }
263
264 #$t->stop('3.1');
265 #print $t->report() . "<br>";
266
267
268 if ($collisions < $max_collide) {
269
270 # place image in master array
271
272 my ($xpos,$ypos);
273 my $max_x = $xrand+$image_data_rotated_width;
274 my $max_y = $yrand+$image_data_rotated_height;
275
276 #$t->start('5.1');
277
278 my $vector;
279 for ($ypos = $yrand;$ypos <= $max_y; $ypos++) {
280 $vector = @{$master_v}->[$ypos];
281 $vector->Interval_Fill($xrand,$max_x);
282 }
283
284 #$t->stop('5.1');
285 #print $t->report() . "<br>";
286
287 $done = 1;
288
289 if ($gen_image) {
290 my $card_image = Image::Magick->new();
291 $card_image->Read($image_data_publish_loc);
292 $back_image->Composite(image=>$card_image, x=>$xrand,y=>$yrand);
293 }
294
295 } else {
296
297 $attempts++;
298 }
299 }
300
301 #print "ATTEMPTS: $attempts <br>";
302
303 if ($attempts >= 499) {
304 #print "UNABLE TO PLACE IMAGE <br>";
305 }
306
307
308 return ($xrand,$yrand,$back_image);
309}
310
311##################################################
312#
313
314sub check_collisions {
315
316 my $master_v = shift;
317 my $width = shift;
318 my $height = shift;
319 my $xrand = shift;
320 my $yrand = shift;
321 my $max_collide = shift;
322 my $page_width = shift;
323
324
325 my $t = new Benchmark::Timer;
326
327 my $max_x = $xrand+$width;
328 my $max_y = $yrand+$height;
329
330 #$t->start('3.1');
331
332 my $btot = 0;
333
334 my ($xpos,$ypos,$vector,$sdone,$start,$attempts,$min,$max);
335
336 for ($ypos = $yrand;$ypos <= $max_y; $ypos++) {
337 $vector = @{$master_v}->[$ypos];
338
339 $sdone=0;
340 $start = $xrand;
341 $attempts = 0;
342
343 while (!$sdone && $attempts++ < 20) {
344 ($min,$max) = $vector->Interval_Scan_inc($start);
345
346 if ($max+1 >= $page_width) {
347 $sdone=1;
348 }
349
350 if ($min < $max_x && $max) {
351 if ($max > $max_x || $max+1 >= $page_width) {
352 $btot += $max_x - $min;
353 $sdone = 1;
354 } else {
355 $btot += $max - $min;
356 $start = $max+1;
357 }
358
359 } elsif ($min > $max_x || !$max || !$min) {
360 $sdone=1;
361 }
362 }
363 }
364
365 #$t->stop('3.1');
366 #print $t->report() . "<br>";
367
368 return $btot;
369
370}
371
diff --git a/cgi-bin/designer.sql b/cgi-bin/designer.sql
new file mode 100755
index 0000000..aef45ce
--- /dev/null
+++ b/cgi-bin/designer.sql
@@ -0,0 +1,15 @@
1drop database if exists designer;
2create database designer;
3
4use designer;
5
6
7
8create TABLE sketchbook (
9 id int not NULL auto_increment,
10 user_id int not NULL,
11 sketchbook_data text,
12 INDEX user_id(user_id),
13 PRIMARY KEY(id)
14);
15
diff --git a/cgi-bin/html/login.html b/cgi-bin/html/login.html
new file mode 100755
index 0000000..5fe2254
--- /dev/null
+++ b/cgi-bin/html/login.html
@@ -0,0 +1,41 @@
1<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >
3 <head>
4 <title>Please Log In!</title>
5 <script type="text/javascript" src="http://materialexperience.santoprene.com/siteflow2/scripts/designer.js"></script>
6
7 <style type="text/css">
8 @import url('http://www.santoprene.com/siteflow2/styles/designer.css');
9 @import url('http://materialexperience.santoprene.com/specialcases.css');
10 </style>
11 </head>
12
13 <body>
14 <h3>Please Log In!</h3>
15 <h4>You need to be registered and logged in to use certain features of the Material Experience Website.</h4>
16
17 <TMPL_VAR NAME="error">
18
19 <p>
20 If you are not yet registered, <a href="http://www.santoprene.com/cgi-bin/register/account.pl?template=designer_">
21 register here!</a> If you have forgotten your password,
22 <a href="http://www.santoprene.com/cgi-bin/lost_password.pl?tmpl=designer">we can e-mail it to you</a>.
23 </p>
24
25 <form action="/cgi-bin/login.pl" method="post">
26 <input type="hidden" name="redir" value="/cgi-bin/sketchbook.pl">
27
28 <p>
29 <label>Username:</label>
30 <input type="text" name="user" value="<TMPL_VAR NAME="user">" size="30" />
31 </p>
32 <p>
33 <label>Password:</label>
34 <input type="password" name="password" />
35 <a href="http://www.santoprene.com/cgi-bin/lost_password.pl?tmpl=designer">Forgot it?</a>
36 </p>
37
38 <p><input type="submit" value="Log In" /></p>
39 </form>
40 </body>
41</html>
diff --git a/cgi-bin/login.pl b/cgi-bin/login.pl
new file mode 100755
index 0000000..81e8bf7
--- /dev/null
+++ b/cgi-bin/login.pl
@@ -0,0 +1,209 @@
1#!/usr/bin/perl
2
3$|=1;
4
5use strict;
6
7use Apache::Request;
8use Apache::Constants qw(REDIRECT);
9use MIME::Base64 qw(encode_base64 decode_base64);
10use HTML::Template;
11
12use Compose::local_lib;
13use Compose::site_user_lib;
14
15$Apache::DBI::DEBUG=2;
16
17my $r = Apache::Request->new(Apache->request);
18#$r->send_http_header('text/html');
19
20my $dbh = new Compose::db_connection('localhost','aes','apache','webconnect');
21
22my $client_lib = new Compose::client_lib();
23my $local_lib = new Compose::local_lib($client_lib,0);
24
25my $client_id = 1;
26$client_lib->setup_client($client_id);
27
28
29my $site_user_lib = new Compose::site_user_lib($client_lib);
30
31$client_lib->{'dbh'}{'debug'} = 2;
32
33my ($form,$PASS);
34
35foreach my $key (sort $r->param) {
36 $form->{$key} = $local_lib->fix_spaces($r->param($key));
37 #print "$key: $form->{$key} <br>";
38}
39
40my %cookiejar = Apache::Cookie->new($r)->parse;
41my $newcookie = Apache::Cookie->new($r);
42
43#####################################################
44# Get the username and password from the cookie.
45
46unless ($cookiejar{'Site'} || ($form->{'user'} && $form->{'password'})) {
47 $r->send_http_header('text/html');
48
49 my $template = HTML::Template->new( filename => "html/login.html", path => [ "$client_lib->{'client'}->{'server_docroot'}" ], die_on_bad_params => 0);
50
51 $template->param('user' => $form->{'user'});
52
53 print $template->output();
54
55 exit(0);
56}
57
58my %cookie_hash;
59
60if ( $cookiejar{'Site'} ) {
61
62 my @values = $cookiejar{'Site'}->value;
63
64 for (my $i=0;$i<scalar(@values);$i+=2) {
65 $cookie_hash{$values[$i]} = $values[$i+1];
66 }
67} else {
68 $cookiejar{'Site'} = "";
69}
70
71my $errors = "";
72
73
74if ($form->{'user'} && $form->{'password'}) {
75
76 my $site_user = &get_user_info($form->{'user'},$dbh);
77
78 if (lc $site_user->{'user_name'} eq lc $form->{'user'}) {
79 if ($site_user->{'user_passwd'} eq $form->{'password'}) {
80 &bake_cookie($r,$client_lib,$newcookie,\%cookie_hash,$form,$site_user,$dbh);
81 exit(0);
82 } else {
83 $errors .= qq(The password you entered is incorrect. Please try again.<br>);
84 }
85
86 } else {
87 $errors .= qq(The user name $form->{'user'} does not exist.<br>);
88 }
89
90} elsif ($cookie_hash{'Site'}) {
91
92
93 my ($user, $password) = split /:/, decode_base64($cookie_hash{'Site'}), 2;
94
95 if ($user eq "" ) {
96 $errors .= qq($cookie_hash{'Site'} Cookie could not be read. <br>);
97
98 } else {
99
100 my $site_user = &get_user_info($user,$dbh);
101
102 if (defined $site_user->{'user_name'} && lc $site_user->{'user_name'} eq lc $user ) {
103
104 if ($site_user->{'user_passwd'} eq $password) {
105 &bake_cookie($r,$client_lib,$newcookie,\%cookie_hash,$form,$site_user,$dbh);
106 exit(0);
107 } else {
108 $errors .= qq(The password you entered is incorrect. Please try again.<br>);
109 }
110
111 } else {
112 $errors .= qq(The user name $form->{'user'} does not exist.<br>) if ($form->{'user'});
113 }
114 }
115}
116
117
118$r->send_http_header('text/html');
119
120my $template = HTML::Template->new( filename => "html/login.html", path => [ "$client_lib->{'client'}->{'server_docroot'}" ], die_on_bad_params => 0);
121
122$template->param('user' => $form->{'user'});
123$template->param('error' => "$errors");
124
125print $template->output();
126
127
128
129###################################
130
131sub bake_cookie {
132
133 my $r = shift;
134 my $client_lib = shift;
135 my $cookiejar = shift;
136 my $cookie_hash = shift;
137 my $form = shift;
138 my $site_user = shift;
139 my $dbh = shift;
140
141 if ( ($cookie_hash->{uri} =~ /login.pl/) || $cookie_hash->{uri} eq "") {
142 $cookie_hash->{uri} = "/";
143 }
144 $cookie_hash->{uri} = $form->{'redir'};
145
146
147 # We have some valid credientials, so set an authorization cookie.
148 my @values = (
149 uri => $cookie_hash->{uri},
150 Cookie => encode_base64(join ":", ($form->{'user'},$form->{'password'})),
151 );
152
153 my $c = $r->connection;
154 my $ip = $c->remote_ip;
155 my $ins = qq(insert into logins (id,username,last_name,first_name,login_date,ip_address) values (NULL,"$site_user->{'user_name'}","$site_user->{'last_name'}","$site_user->{'first_name'}",NOW(),"$ip"));
156 $dbh->updateDB($ins);
157
158
159 $cookiejar->name('Site');
160 $cookiejar->value(\@values);
161 $cookiejar->path('/');
162 $cookiejar->domain('.santoprene.com');
163 $cookiejar->bake;
164
165
166 $r->status(REDIRECT);
167 $r->headers_out->set(Location => $cookie_hash->{uri});
168 $r->send_http_header;
169
170
171
172}
173#######################
174
175sub get_user_info {
176
177 my $uid = shift;
178 my $dbh = shift;
179
180 my ($qry,$gqry,%user_info,%group_info);
181
182 %user_info=%group_info=();
183
184 ###########################
185 # Internet User
186
187
188 $qry = qq(select admin_user_info.*, DATE_FORMAT(created_on,'%c/%y') as format_created_on from admin_user_info where user_name="$uid" and ((registrant=1 and verified=1) or registrant=0) );
189
190 %user_info = $dbh->queryRawDB($qry);
191
192 my %USER_INFO;
193
194 foreach my $k (keys %{$user_info{'0'}}) {
195 $USER_INFO{$k} = $user_info{'0'}{$k};
196 }
197
198 $USER_INFO{'FULL_NAME'} = "$USER_INFO{'first_name'} " if ($USER_INFO{'first_name'} ne "");
199 $USER_INFO{'FULL_NAME'} .= "$USER_INFO{'last_name'} " if ($USER_INFO{'last_name'} ne "");
200
201 foreach my $group (keys %group_info) {
202 $USER_INFO{'group_info'}{$group_info{$group}{'group_id'}} = $group_info{$group};
203 $USER_INFO{'groups'}{$group} = 1;
204 }
205
206 return \%USER_INFO;
207}
208
209
diff --git a/cgi-bin/sketchbook.pl b/cgi-bin/sketchbook.pl
new file mode 100755
index 0000000..8103bd4
--- /dev/null
+++ b/cgi-bin/sketchbook.pl
@@ -0,0 +1,142 @@
1#!/usr/bin/perl
2
3$|=1;
4
5srand;
6
7use strict;
8
9use Apache::Request;
10use Apache::Constants qw(REDIRECT);
11use Benchmark::Timer;
12use HTML::Template;
13use MIME::Base64 qw(encode_base64 decode_base64);
14use Compose::local_lib;
15use Compose::db_connection;
16
17my $r = Apache::Request->new(Apache->request);
18
19my $local_lib = new Compose::local_lib();
20
21my $dbh_aes = new Compose::db_connection('localhost','aes','apache','webconnect');
22my $dbh = new Compose::db_connection('localhost','designer','apache','webconnect');
23
24my $form;
25foreach my $key (sort $r->param) {
26 $form->{$key} = $local_lib->fix_spaces($r->param($key));
27}
28
29my %cookiejar = Apache::Cookie->new($r)->parse;
30my $newcookie = Apache::Cookie->new($r);
31my ($user, $password, %user_info, $qry, %user_info, %cookie_hash);
32
33##################################################
34#
35unless ($cookiejar{'Site'}) {
36 print "Content-type: text/html\n";
37 print "Status: 403\n";
38 exit(0);
39##################################################
40#
41} elsif ( $cookiejar{'Site'} ) {
42
43 my @values = $cookiejar{'Site'}->value;
44
45 for (my $i=0;$i<scalar(@values);$i+=2) {
46 #print qq($values[$i] : $values[$i+1] <br>);
47 $cookie_hash{$values[$i]} = $values[$i+1];
48 }
49
50 ($user, $password) = split /:/, decode_base64($cookie_hash{'Cookie'}), 2;
51
52 $qry = qq(select * from admin_user_info where user_name="$user");
53
54 %user_info = $dbh_aes->queryRawDB($qry);
55
56 if ($user_info{'0'}{'id'} eq "") {
57 print "Content-type: text/html\n";
58 print "Status: 403\n";
59 exit(0);
60 }
61}
62
63
64
65##################################################
66#
67if ($r->method() eq "GET") {
68
69 $qry = qq(select * from sketchbook where user_id="$user_info{'0'}{'id'}");
70
71 my %data = $dbh->queryRawDB($qry);
72
73 if ($data{'0'}{'sketchbook_data'} eq "") {
74 if ($form->{'interactive'} ne "false") {
75 $r->send_http_header('text/html');
76 print qq{
77 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
78 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
79 <head>
80 <title>Logged In</title>
81 <style type="text/css">
82 \@import url('http://www.santoprene.com/siteflow2/styles/designer.css');
83 \@import url('http://materialexperience.santoprene.com/specialcases.css');
84 </style>
85 </head>
86
87 <body>
88 <h1>Logged In</h1>
89 <p>Thanks for logging in. You can close this card now.</p>
90 </body>
91 </html><div style="display: none">
92 };
93 } else {
94 print "Status: 404\n";
95 print "Content-type: text/html\n";
96 }
97
98 exit(0);
99 } else {
100 if ($form->{'interactive'} ne "false") {
101 $r->send_http_header('text/html');
102 print qq{
103 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
104 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
105 <head>
106 <title>Logged In</title>
107 <style type="text/css">
108 \@import url('http://www.santoprene.com/siteflow2/styles/designer.css');
109 \@import url('http://materialexperience.santoprene.com/specialcases.css');
110 </style>
111 </head>
112
113 <body>
114 <h1>Logged In</h1>
115 <p>Thanks for logging in. You can close this card now.</p>
116 </body>
117 </html><div style="display: none">
118 };
119 } else {
120 $r->send_http_header('text/javascript');
121 print "$data{'0'}{'sketchbook_data'}\n";
122 }
123 }
124
125##################################################
126#
127} else {
128
129 if ($form->{'sketchbook_data'} ne "") {
130 my $upd = qq(delete from sketchbook where user_id="$user_info{'0'}{'id'}");
131 $dbh->updateDB($upd);
132
133 $form->{'sketchbook_data'} =~ s/"/\\"/g;
134
135 my $upd = qq(insert into sketchbook (sketchbook_data,user_id) values ("$form->{'sketchbook_data'}","$user_info{'0'}{'id'}"));
136 my %data = $dbh->queryRawDB($upd);
137 }
138
139 print "Content-type: text/html\n\n";
140
141}
142
diff --git a/cgi-bin/sketchbook_resolver.pl b/cgi-bin/sketchbook_resolver.pl
new file mode 100755
index 0000000..cbf64be
--- /dev/null
+++ b/cgi-bin/sketchbook_resolver.pl
@@ -0,0 +1,49 @@
1#!/usr/bin/perl
2
3$|=1;
4
5use strict;
6use Apache::Request;
7use Apache::Constants qw(REDIRECT);
8use Compose::local_lib;
9use Compose::db_connection;
10
11my $r = Apache::Request->new(Apache->request);
12my $local_lib = new Compose::local_lib();
13my $dbh = new Compose::db_connection('localhost','aes','apache','webconnect');
14my $item = $local_lib->fix_spaces($r->param('card'));
15
16$r->send_http_header('text/javascript');
17
18if ($r->method() eq "GET") {
19 my %res = $dbh->queryRawDB(qq(
20 SELECT
21 enc_content_id,
22 textfield_0 AS title,
23 select_0 AS category,
24 CONCAT(clients.website.site_url,clients.website.publish_docroot,"/",human_dir,"/index.html") AS page_loc,
25 CONCAT(clients.website.site_url,clients.website.publish_docroot,"/",human_dir,"/orig/",image_1) AS rotated_image
26 FROM
27 content_data,
28 clients.website
29 WHERE
30 clients.website.site_id = content_data.site_id AND
31 content_data.site_id = 1 AND
32 form_id = 48 AND
33 moderation_status >= 3 AND
34 date_0 <= NOW() AND
35 enc_content_id = "$item" AND
36 !(textfield_5 is NULL or textfield_5 = "") AND
37 checkbox_2 != "Yes"
38 ));
39
40 print qq(
41\({
42 "cid" : "$res{0}{'enc_content_id'}",
43 "title" : "$res{0}{'title'}",
44 "category" : "$res{0}{'category'}",
45 "url" : "$res{0}{'page_loc'}",
46 "chip" : "$res{0}{'rotated_image'}"
47}\)
48 );
49} \ No newline at end of file
diff --git a/cgi-bin/sop_preview_proxy.pl b/cgi-bin/sop_preview_proxy.pl
new file mode 100755
index 0000000..0694e11
--- /dev/null
+++ b/cgi-bin/sop_preview_proxy.pl
@@ -0,0 +1,28 @@
1#!/usr/bin/perl
2
3#
4# Perl Based Same Origin Proxy
5#
6
7$|=1;
8
9srand;
10use strict;
11
12use Apache::Request;
13use LWP::Simple;
14use LWP::UserAgent;
15
16my $r = Apache::Request->new(Apache->request);
17$r->send_http_header('text/javascript');
18
19my @url = split(/site=/,$r->parsed_uri()->unparse());
20
21my $browser = LWP::UserAgent->new;
22$browser->credentials(
23 'admin.santoprene.com:80',
24 'Admin',
25 'DESIGNER_SOP_USER' => 'dsop4edit'
26);
27
28print $browser->get($url[1])->content;
diff --git a/cgi-bin/sop_proxy.pl b/cgi-bin/sop_proxy.pl
new file mode 100755
index 0000000..4a3b370
--- /dev/null
+++ b/cgi-bin/sop_proxy.pl
@@ -0,0 +1,18 @@
1#!/usr/bin/perl
2
3#
4# Perl Based Same Origin Proxy
5#
6
7$|=1;
8
9srand;
10use strict;
11
12use Apache::Request;
13use LWP::Simple;
14
15my $r = Apache::Request->new(Apache->request);
16$r->send_http_header('text/javascript');
17
18print get $r->param('site');
diff --git a/deploy b/deploy
new file mode 100755
index 0000000..d992fbc
--- /dev/null
+++ b/deploy
@@ -0,0 +1,71 @@
1#!/bin/bash
2#
3# ExxonMobil Designer Site Deploy Script
4# by Mike Crute, EYEMG (mcrute@eyemg.com)
5#
6# This is the deployment script for the AES designer site
7# it exists to script the last piece of building and deploying
8# the designer site.
9#
10
11if [ `hostname` == 'calvin.eyemg.com' ]; then
12 cd /usr/web/designer
13 rm -rf cgi-bin/ docroot/
14 tar -xvjf aes_designer.tbz2
15 echo "Code Deployed: `date +'%Y-%m-%d %H:%M:%S'` EST" >> docroot/build.date
16 rm deploy
17else
18 RED="\033[0;31m"
19 GREEN="\033[0;32m"
20 BROWN="\033[0;33m"
21 CYAN="\033[0;36m"
22 COLOR_CLEAR="\033[00m"
23
24 clear
25 echo -e "${BROWN}***************************************************"
26 echo -e "${BROWN}* *"
27 echo -e "${BROWN}*${COLOR_CLEAR} ${CYAN}AES Designer Site Deployment Script${COLOR_CLEAR} ${BROWN}*"
28 echo -e "${BROWN}* *"
29 echo -e "${BROWN}***************************************************"
30 echo -e "$COLOR_CLEAR"
31 echo -n "Your SSH Username: "
32 read myuser
33 echo ""
34
35 if [[ $myuser == '' ]]; then
36 echo -e "\n${RED}FAILED:${COLOR_CLEAR} Enter a username."
37 exit 1
38 fi
39
40 ./make
41
42 if [[ $? != 0 ]]; then
43 echo -e "\n${RED}FAILED:${COLOR_CLEAR} Make failed, wisely refusing to continue."
44 exit 1
45 fi
46
47 scp ./aes_designer.tbz2 $myuser@calvin.eyemg.com:/usr/web/designer
48
49 if [[ $? != 0 ]]; then
50 echo -e "\n${RED}FAILED:${COLOR_CLEAR} Failed to deploy code tarball."
51 exit 1
52 fi
53
54 scp ./deploy $myuser@calvin.eyemg.com:/usr/web/designer
55
56 if [[ $? != 0 ]]; then
57 echo -e "\n${RED}FAILED:${COLOR_CLEAR} Failed to deploy install script."
58 exit 1
59 fi
60
61 ssh $myuser@calvin.eyemg.com /usr/web/designer/deploy > /dev/null 2>&1
62
63 if [[ $? != 0 ]]; then
64 echo -e "\n${RED}FAILED:${COLOR_CLEAR} Remote command execution failed."
65 exit 1
66 fi
67
68 rm aes_designer.tbz2
69
70 echo -e "\n ${GREEN}SUCCESS:${COLOR_CLEAR} Go check and make sure!"
71fi
diff --git a/docroot/.htaccess b/docroot/.htaccess
new file mode 100755
index 0000000..70fc492
--- /dev/null
+++ b/docroot/.htaccess
@@ -0,0 +1,46 @@
1#
2# Material Experience - .htaccess File
3#
4# EYEMG - Interactive Media Group
5# Created by Mike Crute (mcrute@eyemg.com)
6# Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07
7#
8# Workarounds for missing mod_gzip and caching. This file
9# is pre-processed before the site goes into production
10# since we don't really gzip anything in development.
11#
12# WARNING!: Seconds of code marked with ### PRODUCTION###
13# are blocks un-commented by the build system, you can
14# add or remove them just be careful what you ask for.
15#
16
17# Cache Control Headers
18### PRODUCTION ###
19#ExpiresActive on
20#ExpiresDefault "access plus 1 week"
21### END PRODUCTION ###
22
23# Disable the ETag Header (per YSlow)
24#
25# I know that this probably doesn't mean anything for us
26# because we don't serve AES with a cluster but just for
27# the sake of getting straight As in YSlow we'll remove
28# them.
29FileEtag none
30
31### PRODUCTION ###
32#<Files application.js>
33# Header set Content-Encoding "gzip"
34#</Files>
35
36#<Files application.css>
37# Header set Content-Encoding "gzip"
38#</Files>
39### END PRODUCTION ###
40
41<Files index.html>
42 Header set Content-Type "text/html;charset=utf-8"
43### PRODUCTION ###
44# Header set Content-Encoding "gzip"
45### END PRODUCTION ###
46</Files>
diff --git a/docroot/account_deleted.html b/docroot/account_deleted.html
new file mode 100755
index 0000000..04af7cc
--- /dev/null
+++ b/docroot/account_deleted.html
@@ -0,0 +1,23 @@
1<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >
3 <head>
4 <title>Please Log In!</title>
5 <script type="text/javascript" src="http://materialexperience.santoprene.com/siteflow2/scripts/designer.js"></script>
6
7 <style type="text/css">
8 @import url('http://www.santoprene.com/siteflow2/styles/designer.css');
9 @import url('http://materialexperience.santoprene.com/specialcases.css');
10 </style>
11 </head>
12
13 <body>
14 <h3>Your account has been deleted!</h3>
15
16 <p>Your account information has been deleted and you are now logged out of Material Experience Website. <br />
17 You will have to register and log in again if you would like to gain access to certain areas of this Web site.</p>
18
19 <p>You can close this card to continue.</p>
20
21
22 </body>
23</html>
diff --git a/docroot/application.css b/docroot/application.css
new file mode 100755
index 0000000..cd0c540
--- /dev/null
+++ b/docroot/application.css
@@ -0,0 +1,463 @@
1/*
2 * Material Experience - Site Stylesheet
3 *
4 * EYEMG - Interactive Media Group
5 * Created by Mike Crute (mcrute@eyemg.com)
6 * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07
7 *
8 * Site stylesheet the covers most of the look and feel of the site.
9 * Some of the CSS is also in the application logic so if you can't
10 * find what your looking for here go there.
11 */
12
13/*
14 * General Application Styles
15 */
16html
17{
18 overflow: hidden;
19}
20
21body
22{
23 font: 10px Verdana,sans-serif;
24 margin: 0px;
25 overflow: hidden;
26}
27
28a
29{
30 color: blue;
31 text-decoration: underline;
32}
33
34a img
35{
36 border: 0px;
37}
38
39h1
40{
41 font-size: 1.7em;
42}
43
44h2
45{
46 font-size: 1.6em;
47}
48
49img
50{
51 /* The infamous PNG fix */
52 behavior: url('pngbehavior.htc');
53}
54
55/*
56 * Application Header Styles
57 */
58img#tagline
59{
60 position: absolute;
61 top: 0px;
62 right: 0px;
63}
64
65div#header, div#footer
66{
67 background: white;
68 height: 60px;
69 width: 100%;
70 position: relative;
71 z-index: 99;
72}
73
74div#header
75{
76 height: 80px;
77}
78
79p#version
80{
81 position: absolute;
82 bottom: 0px;
83 right: 7px;
84 z-index: 100;
85 color: red;
86}
87
88img#logo
89{
90 position: absolute;
91 z-index: 9999999999;
92 cursor: pointer;
93 padding: 10px 0px 0px 10px;
94}
95
96/*
97 * Table and Chip Element Styles
98 */
99div.appDecorator
100{
101 height: 21px;
102 width: 6px;
103}
104
105div.table
106{
107 position: absolute;
108 top: 60px;
109 left: 0px;
110 width: 100%;
111}
112
113img.chip
114{
115 position: absolute;
116 cursor: pointer;
117}
118
119/*
120 * Application Footer Styles
121 */
122p#copyright, div#footer p#copyright a
123{
124 color: rgb(190,190,190);
125 font-size: 10px;
126 margin-right: 0px;
127 text-transform: none;
128}
129
130div#footer p#copyright a
131{
132 text-decoration: underline;
133}
134
135p#copyright
136{
137 width: 100%;
138 display: block;
139 position: absolute;
140 bottom: 0px;
141 left: 0px;
142 text-align: center;
143 z-index: 100;
144}
145
146div#footer
147{
148 position: absolute;
149 bottom: -1px;
150 height: 40px;
151 text-align: center;
152 padding: 5px 0px 10px 0px;
153}
154
155div#footer img
156{
157 position: relative;
158 top: 6px;
159 margin-right: 5px;
160}
161
162div#footer a
163{
164 font-size: 1.2em;
165 text-transform: uppercase;
166 text-decoration: none;
167 margin-right: 25px;
168}
169
170/*
171 * History Menu Styles
172 */
173div.history
174{
175 position: absolute;
176 right: 20px;
177 top: 20px;
178 padding: 3px 10px;
179 cursor: pointer;
180 background: none;
181 text-align: right;
182}
183
184ul#history:hover, ul#history.hovered
185{
186 display: block;
187}
188
189ul#history
190{
191 position: absolute;
192 z-index: 100;
193 display: none;
194 margin: 0px;
195 padding: 10px 0px 0px 0px;
196 color: rgb(255, 255, 255);
197 text-decoration: none;
198 width: 200px;
199}
200
201ul#history li
202{
203 background: rgb(145, 145, 145);
204 list-style: none;
205 padding: 5px 10px;
206 margin: 0px;
207 border-bottom: 1px solid white;
208 text-align: left;
209 color: white;
210 text-decoration: none;
211}
212
213ul#history li a
214{
215 color: white;
216 text-decoration: none;
217 border: 0px;
218 display: block;
219 width: 100%;
220 height: 100%;
221}
222
223ul#history li:last-child
224{
225 border-bottom: 0px;
226}
227
228/*
229 * Rounded Corner Styles
230 */
231.round { display: block; }
232.round * { display:block; height: 1px; overflow: hidden; }
233.rcSlice_1 { margin: 0 10px; }
234.rcSlice_2 { margin: 0 8px; }
235.rcSlice_3 { margin: 0 6px; }
236.rcSlice_4 { margin: 0 5px; }
237.rcSlice_5 { margin: 0 4px; }
238.rcSlice_6 { margin: 0 3px; }
239.rcSlice_7 { margin: 0 3px; }
240.rcSlice_8 { margin: 0 2px; height: 1px; }
241.rcSlice_9 { margin: 0 2px; height: 2px; }
242.rcSlice_10 { margin: 0 1px; height: 3px; }
243
244/*
245 * Card Frame Styles (Card Class)
246 */
247img.commandbtn
248{
249 height: 11px;
250 width: 11px;
251 display: inline;
252 margin: 0px 2px;
253 cursor: pointer;
254}
255
256span.commandlabel
257{
258 height: auto;
259 z-index: 9;
260 display: inline;
261 position: relative;
262 top: -2px;
263 font-size: 1.1em;
264}
265
266span.commandlabel *
267{
268 height: auto;
269 display: inline;
270 font-size: 1.1em;
271}
272
273span.cardTitle
274{
275 font-size: 1.1em;
276}
277
278.cardTitle
279{
280 position: absolute;
281 color: white;
282 z-index: 1;
283 height: auto;
284 top: 4px;
285 left: 15px;
286}
287
288.buttonbox
289{
290 position: absolute;
291 right: 15px;
292 top: 6px;
293 z-index: 100;
294 height: auto;
295 color: white;
296 font-size: 0.8em;
297}
298
299/*
300 * Card Resource List Styles (Card Layout)
301 */
302.resourceList
303{
304 margin: 0px;
305 padding: 0px;
306}
307
308.resourceList li
309{
310 list-style: none;
311 padding-bottom: 7px;
312}
313
314/*
315 * Card Thumbnail Styles (Card Layout)
316 */
317.mediaThumb
318{
319 width: 50px;
320 height: 50px;
321 padding: 10px 10px 0px 0px;
322 cursor: pointer;
323}
324
325.thumbStrip
326{
327 text-align: center;
328}
329
330.thumbStrip img
331{
332 width: 50px;
333 height: 50px;
334 cursor: pointer;
335}
336
337/*
338 * Sketchbook Trash Can Styles
339 */
340#sbTrash
341{
342 position: absolute;
343 bottom: 20px;
344 right: 10px;
345}
346
347.trashHi
348{
349 background-color: lightyellow;
350}
351
352/*
353 * Skip Link Styles (Intro Card)
354 */
355.skiplink
356{
357 color: rgb(145, 145, 145);
358 text-decoration: none;
359 font-size: 1.1em;
360 position: relative;
361 z-index: 99;
362}
363
364.skipimg
365{
366 vertical-align: bottom;
367 margin-left: 3px;
368}
369
370.skipbox
371{
372 text-align: center;
373 margin-top: -50px;
374}
375
376/*
377 * Custom Content Styles
378 */
379ul#navigation
380{
381 margin: 0px;
382 padding: 0px;
383}
384
385ul#navigation li
386{
387 margin: 5px 30px 0px 0px;
388 list-style: none;
389 border: 1px solid white;
390}
391
392ul#navigation li a
393{
394 display: block;
395 text-decoration: none;
396 padding: 2px;
397 border: 3px solid black;
398}
399
400ul#navigation li ul li a
401{
402 margin: 0px -60px 0px -30px;
403}
404
405.selected
406{
407 background: black;
408 color: white;
409 padding: 5px;
410}
411/*
412.narrowCol, .wideCol
413{
414 float: left;
415 height: 460px;
416 overflow: hidden;
417}
418
419.narrowCol
420{
421 width: 290px;
422 margin-right: 20px;
423}
424
425.wideCol
426{
427 width: 569px;
428}
429*/
430
431/*
432 * Custom Content Color Styles
433 */
434ul#navigation li a
435{
436 color: white;
437 background: rgb(100, 100, 100);
438 border: 3px solid rgb(100, 100, 100);
439}
440
441ul#navigation li ul li a
442{
443 background: white;
444 color: rgb(100, 100, 100);
445}
446
447.selected
448{
449 background: #ccc;
450 color: #000;
451 padding: 0px;
452}
453
454span.glossterm
455{
456 text-transform: uppercase;
457}
458
459span.anchor
460{
461 text-transform: uppercase;
462 font-weight: bold;
463}
diff --git a/docroot/application.js b/docroot/application.js
new file mode 100755
index 0000000..22bf06e
--- /dev/null
+++ b/docroot/application.js
@@ -0,0 +1,67 @@
1/*
2 * Material Experience - Development Loader
3 *
4 * EYEMG - Interactive Media Group
5 * Created by Mike Crute (mcrute@eyemg.com)
6 * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07
7 *
8 * Loads the designer site in the development environment,
9 * this facilitates us having multiple class files. This
10 * file is pre-processed by the build system to pull
11 * out include files for building into the final site.
12 *
13 * Thus, the require statements are magic :-)
14 */
15
16var DesignerSite =
17{
18 Version: "$Revision$".match(/[0-9]+/),
19
20 require: function(libraryName)
21 {
22 document.write('<script type="text/javascript" src="' + libraryName + '"></script>');
23 },
24
25 load: function()
26 {
27 // Prototype Stuff
28 this.require("lib/prototype.js");
29
30 // Script.aculo.us Stuff
31 this.require("lib/scriptaculous/scriptaculous.js");
32 this.require("lib/scriptaculous/effects.js");
33 this.require("lib/scriptaculous/builder.js");
34 this.require("lib/scriptaculous/dragdrop.js");
35 this.require("lib/scriptaculous/slider.js");
36
37 // Other 3rd Party Libraries
38 this.require("lib/swfobject/swfobject.js");
39 this.require("classes/decoder.module.js");
40
41 // Application Code
42 this.require("classes/utility.js");
43 this.require("classes/chip.class.js");
44 this.require("classes/cookie.class.js");
45 this.require("classes/card.class.js");
46 this.require("classes/bezel.class.js");
47 this.require("classes/overlay.class.js");
48 this.require("classes/roundcorners.class.js");
49 this.require("classes/table.class.js");
50 this.require("classes/history.class.js");
51 this.require("classes/sketchbook.class.js");
52 this.require("classes/application.js");
53
54 // Layout Engines
55 this.require("classes/layout.class.js");
56 this.require("classes/layouts/layout.error.class.js");
57 this.require("classes/layouts/layout.primary.class.js");
58 this.require("classes/layouts/layout.special.class.js");
59 this.require("classes/layouts/layout.custom.class.js");
60
61 // Namespaces and Data
62 this.require("data/strings.en.js");
63 this.require("classes/sme.namespace.js");
64 }
65};
66
67DesignerSite.load(); \ No newline at end of file
diff --git a/docroot/blank.gif b/docroot/blank.gif
new file mode 100755
index 0000000..057b51b
--- /dev/null
+++ b/docroot/blank.gif
@@ -0,0 +1,5 @@
1XSym
20016
388e2976783fa96f9cad2c9a96c39fa58
4images/blank.gif
5 \ No newline at end of file
diff --git a/docroot/classes/application.js b/docroot/classes/application.js
new file mode 100755
index 0000000..d8776d8
--- /dev/null
+++ b/docroot/classes/application.js
@@ -0,0 +1,227 @@
1/*
2 * Material Experience - Main Application Code
3 *
4 * EYEMG - Interactive Media Group
5 * Created by Mike Crute (mcrute@eyemg.com)
6 * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07
7 *
8 * Core application code that is responsible for starting up the application
9 * and initializing the core objects.
10 */
11
12/*
13 * Register global actions for AJAX responders.
14 */
15Ajax.Responders.register(
16{
17 /*
18 * When an AJAX connection is created show the bezel that says
19 * "loading data".
20 */
21 onCreate: function()
22 {
23 if (!SME.AJAXBezel)
24 {
25 SME.AJAXBezel = new Bezel({ displayTime: 0, destroy: false }).show(Strings.loadingAnim);
26 }
27 else
28 {
29 SME.AJAXBezel.show(Strings.loadingAnim);
30 }
31 },
32
33 /*
34 * Each time a requester completes we check to see if it was the last
35 * one, if it is then we take down the loading bezel.
36 */
37 onComplete: function()
38 {
39 if (Ajax.activeRequestCount == 0)
40 {
41 SME.AJAXBezel.hide();
42 }
43 },
44
45 /*
46 * When something goes wrong with loading data we die.
47 */
48 onException: function(transport, exception)
49 {
50 if (SME.debug)
51 {
52 console.error(exception);
53 }
54
55 SME.AJAXBezel.hide();
56 new Bezel({ displayTime: 0 }).show(Strings.ajaxError);
57 }
58});
59
60/*
61 * Load the data for the card tables.
62 */
63function loadTables()
64{
65 new Ajax.Request(SME.url.tableList,
66 {
67 method: "get",
68
69 onSuccess: function(transport)
70 {
71 transport.responseText.evalJSON().each(function(data)
72 {
73 var windowDims = window.getDimensions();
74
75 var table = new CardTable(
76 {
77 color: data.color,
78 name: data.name,
79 id: data.tid,
80 decorate: data.decorate
81 });
82
83 // Push the table onto the global table cache (see the SME namespace
84 // for more information about the global table cache)
85 SME.tables.push(table);
86
87 // Subtracting the max chip width and height ensures that cards don't
88 // fall too far off the tables
89 table.loadChipData(SME.url.cardTables,
90 {
91 table: data.tid,
92 w: windowDims.width - (SME.sizes.chipMax.width / 2),
93 h: windowDims.height - (SME.sizes.chipMax.height / 2)
94 }, false);
95 });
96
97 // Initialize the sketchbook
98 SME.sketchbook = new Sketchbook();
99
100 // By default show the home table. When the history manager loads for the
101 // first time (after this step) it will load the right table from the URL
102 // if applicable. This just ensures that a table is always displayed.
103 CardTable.showTable("home");
104
105 // Show the tool box in the upper right
106 showToolBox();
107 }
108 });
109
110 // Start up the history manager
111 SME.history = new HistoryManager().pollEvents();
112}
113
114/*
115 * Show an intro card. This function will gracefully pass if there are no
116 * intro cards to be shown.
117 */
118function showIntro()
119{
120 new Ajax.Request(SME.url.introCards,
121 {
122 method: "get",
123
124 onSuccess: function(transport)
125 {
126 var data = transport.responseText.cleanJSON();
127
128 // If there is no intro card then just pass
129 if (data == "" || data == "\n")
130 {
131 return loadTables();
132 }
133 else
134 {
135 data = data.evalJSON();
136
137 // By default just show the first card in the
138 // feed
139 var myCard = data[0];
140
141 // If more than one card then pick one at random
142 // to display (per client requirements).
143 if (data.length > 1)
144 {
145 myCard = data[Math.floor(1 + (data.length - 1) * Math.random())];
146 }
147 }
148
149 var card = new Card(
150 {
151 color: SME.colors.grey,
152 title: '',
153 addExtraButtons: false,
154 contID: myCard,
155
156 onFadeComplete: function()
157 {
158 loadTables();
159 }
160 }).show();
161 }
162 });
163}
164
165/*
166 * Show the toolbox in the upper right side of the screen.
167 */
168function showToolBox()
169{
170 new Effect.Appear($$("div#header div.history")[0]);
171
172 // On mouseover of the history link show the dropdown
173 $$("div.history a.history")[0].observe("mouseover", function()
174 {
175 SME.history.getDropDown();
176 });
177
178 // Show the login screen when the login link is clicked
179 $("loginLink").observe("click", Sketchbook.showLoginScreen);
180
181 // Check the login when they first hit the page, saves people logging
182 // in again
183 Sketchbook.checkLogin();
184}
185
186/*
187 * Main program function, this starts up the interface and does various
188 * little fixups of interface elements.
189 */
190function main()
191{
192 if (SME.debug)
193 {
194 new Bezel({ displayTime: 5 }).show("Full Debug Mode is Enabled");
195 }
196
197 // Check the resolution at load and when the screen size changes
198 window.checkResolution();
199 Event.observe(window, "resize", window.checkResolution);
200
201 // Show the intro card or load the tables
202 if (SME.skipIntro || window.location.hash.length > 1)
203 {
204 loadTables();
205 }
206 else
207 {
208 showIntro();
209 }
210
211 // Per Bryan this should be a single year if 2007 otherwise it should
212 // be a date range starting on the year that the site was released.
213 if (new Date().getFullYear() > 2007)
214 {
215 $("copyright").innerHTML = $("copyright").innerHTML.replace(/####/, "2007-" +
216 new Date().getFullYear());
217 }
218 else
219 {
220 $("copyright").innerHTML = $("copyright").innerHTML.replace(/####/, "2007");
221 }
222}
223
224/*
225 * Start the program up when the window loads.
226 */
227Event.observe(window, "load", main); \ No newline at end of file
diff --git a/docroot/classes/bezel.class.js b/docroot/classes/bezel.class.js
new file mode 100755
index 0000000..d29ed4f
--- /dev/null
+++ b/docroot/classes/bezel.class.js
@@ -0,0 +1,154 @@
1/*
2 * Material Experience - Bezel Class
3 *
4 * EYEMG - Interactive Media Group
5 * Created by Mike Crute (mcrute@eyemg.com)
6 * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07
7 *
8 * Notification bezel class mimics the notification bezel in BBEdit
9 * (and to a lesser extent, OS X).
10 */
11
12var Bezel = Class.create();
13Object.extend(Bezel.prototype,
14{
15 /*
16 * Creates the bezel HTML elements and adds them to the document body.
17 */
18 initialize: function()
19 {
20 this.options = Object.extend(
21 {
22 background: "black", // Background color of the bezel
23 opacity: 0.8, // % Opacity of the bezel
24 fontSize: "2em", // Font Size
25 fadeTime: 2, // Fade Time
26 destroy: true, // Destroy bezel on fade out
27 displayTime: 1, // How long to display the bezel - 0 is "sticky"
28 onShow: Prototype.emptyFunction, // After show callback
29 afterHide: Prototype.emptyFunction // After hide callback
30 }, arguments[0] || {});
31
32 this.visible = false;
33
34 var Rounder = new RoundedCorners(this.options.background);
35
36 this.bezel = Element.extend(document.createElement("div"));
37 this.message = Element.extend(document.createElement("div"));
38
39 this.bezel.appendChild(Rounder.get(Rounder.directions.top));
40 this.bezel.appendChild(this.message);
41 this.bezel.appendChild(Rounder.get(Rounder.directions.bottom));
42
43 document.body.appendChild(this.bezel);
44
45 this.bezel.setStyle(
46 {
47 opacity: this.options.opacity,
48 zIndex: 9999999,
49 position: "absolute",
50 display: "none",
51 width: "auto",
52 cursor: "pointer"
53 });
54
55 this.message.setStyle(
56 {
57 background: this.options.background,
58 fontSize: this.options.fontSize,
59 color: "white",
60 padding: "0px 1em",
61 textAlign: "center"
62 });
63
64 // IE does not properly size the bezel so we must constrain it
65 if (Prototype.Browser.IE)
66 {
67 this.bezel.setStyle({ width: "50%" });
68 }
69
70 this.bezel.onclick = function()
71 {
72 this.hide();
73 }.bind(this);
74 },
75
76 /*
77 * Displays the notification bezel with the requested message.
78 */
79 show: function(message)
80 {
81 // Sets the bezel message
82 this.message.innerHTML = message;
83 this.visible = true;
84
85 // Center the bezel
86 var mysize = this.bezel.getDimensions();
87 mysize = window.calcCordsToCenter(mysize.height, mysize.width);
88
89 // Set the position of the bezel
90 this.bezel.setStyle(
91 {
92 top: mysize.top + "px",
93 left: mysize.left + "px"
94 });
95
96 this.bezel.show();
97
98 if (typeof this.options.onShow == "function")
99 {
100 this.options.onShow();
101 }
102
103 if (this.options.displayTime > 0)
104 {
105 new PeriodicalExecuter(function(executer)
106 {
107 this.hide();
108 executer.stop();
109 }.bind(this), this.options.displayTime);
110 }
111
112 return this;
113 },
114
115 /*
116 * Removes the bezel from the DOM.
117 */
118 destroy: function()
119 {
120 try
121 {
122 document.body.removeChild(this.bezel);
123 }
124 catch (e)
125 {
126 // Ignore errors
127 }
128 },
129
130 /*
131 * Fades out the notification bezel. If this is not a sticky bezel
132 * (displayTime > 0) then this function will be called automatically
133 * by the show routine.
134 */
135 hide: function()
136 {
137 new Effect.Fade(this.bezel,
138 {
139 duration: this.options.fadeTime,
140
141 afterFinish: function()
142 {
143 this.visible = false;
144
145 if (this.options.destroy)
146 {
147 this.destroy();
148 }
149
150 this.options.afterHide(this);
151 }.bind(this)
152 });
153 }
154}); \ No newline at end of file
diff --git a/docroot/classes/card.class.js b/docroot/classes/card.class.js
new file mode 100755
index 0000000..e135259
--- /dev/null
+++ b/docroot/classes/card.class.js
@@ -0,0 +1,378 @@
1/*
2 * Material Experience - Card Class
3 *
4 * EYEMG - Interactive Media Group
5 * Created by Mike Crute (mcrute@eyemg.com)
6 * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07
7 */
8
9var Card = Class.create();
10Object.extend(Card.prototype,
11{
12 /*
13 * Creates the card and appends it to the document. This does
14 * not set the content.
15 */
16 initialize: function()
17 {
18 this.options = Object.extend(
19 {
20 title: 'New Card', // Card Title
21 id: 'mycard', // Card ID
22 preview: false, // This is a preview card
23 addExtraButtons: true, // Add the extra buttons to non-system cards
24 contID: null, // Content ID of the card's contents
25 color: SME.colors.blue, // Card Color
26 width: SME.sizes.card.width, // Card Width
27 height: SME.sizes.card.height, // Card Height
28 onClose: Prototype.emptyFunction, // Card Close Callback
29 onFadeComplete: Prototype.emptyFunction, // Card Fade Completion Callback
30 onFadeOutFinish: Prototype.emptyFunction // Card Fade Out Completion Callback
31 }, arguments[0] || {});
32
33 // Keep track of our card chip
34 this.chip = this.options.chip;
35
36 // Buttons on the card frame
37 this.buttons = [];
38
39 // Create the document overlay but don't yet show it
40 this.overlay = new Overlay(
41 {
42 onClick: function()
43 {
44 this.hide();
45 }.bind(this)
46 });
47
48 this._createCard();
49
50 if (this.options.addExtraButtons)
51 {
52 this._addExtraButtons();
53 }
54
55 // Always add a close button
56 this.addCommandButton(Strings.closeCard,'images/close.gif', function()
57 {
58 this.hide();
59 }.bind(this));
60
61 this.setTitle(this.options.title);
62 },
63
64 /*
65 * Create the card DOM nodes.
66 */
67 _createCard: function()
68 {
69 // Get the rounded corner generator and the coordinates of the page center
70 var Rounder = new RoundedCorners(this.options.color);
71 var cardPos = window.calcCordsToCenter(this.options.height, this.options.width);
72 var slicebox = Rounder.get(Rounder.directions.top);
73
74 // Intro card will flash across the screen while card builds but
75 // before fading up if display is not set to none
76 this.card = Builder.node('div',
77 {
78 id: this.options.id,
79 style: 'display: none;',
80 className: 'card'
81 });
82
83 // Create the card header with title and button box
84 slicebox.appendChild(this.title = Builder.node('span', { className: 'cardTitle' }));
85 slicebox.appendChild(this.buttonbox = Builder.node('div', { className: 'buttonbox' },
86 this.buttonLabel = Builder.node('span', { className: 'commandlabel' })
87 ));
88
89 // Append the header and the card content area
90 this.card.appendChild(slicebox);
91 this.card.appendChild(Builder.node('div', { style: 'background: white; opacity: 100%;' },
92 this.cframe = Builder.node('div')
93 ));
94
95 // Append the bottom of the card frame and add the card to the document
96 this.card.appendChild(Rounder.get(Rounder.directions.bottom));
97 document.body.appendChild(this.card);
98
99 // Card frame styles
100 Element.extend(this.cframe).setStyle(
101 {
102 width: this.options.width - 11 + 'px',
103 height: this.options.height - 40 + 'px',
104 borderLeft: '5px solid ' + this.options.color,
105 borderRight: '5px solid ' + this.options.color,
106 borderTop: '10px solid ' + this.options.color,
107 position: 'relative',
108 overflow: 'hidden'
109 });
110
111 // Card styles
112 Element.extend(this.card).setStyle(
113 {
114 height: this.options.height + 'px',
115 width: this.options.width + 'px',
116 top: cardPos.top + 'px',
117 left: cardPos.left + 'px',
118 zIndex: 9001,
119 opacity: '100%',
120 display: 'none',
121 position: 'absolute'
122 });
123
124 // Make the card draggable
125 this.drag = new Draggable(this.card,
126 {
127 handle: 'round',
128 zindex: 9001,
129 starteffect: null,
130 endeffect: null
131 });
132 },
133
134 /*
135 * Add a command button to the top right of the card.
136 */
137 addCommandButton: function(title, icon, action)
138 {
139 var newButton = Builder.node('img', { className: 'commandbtn', src: icon });
140
141 // Show the label on mouseover
142 Event.observe(newButton, 'mouseover', function()
143 {
144 this.buttonLabel.innerHTML = title;
145 }.bindAsEventListener(this));
146
147 // Hide the label on mouse out
148 Event.observe(newButton, 'mouseout', function()
149 {
150 this.buttonLabel.innerHTML = '';
151 }.bindAsEventListener(this));
152
153 // Take the action specified when the button is clicked
154 Event.observe(newButton, 'click', function(event)
155 {
156 action(event);
157 });
158
159 // Add the button to the button box and cache it.
160 this.buttons[title] = this.buttonbox.appendChild(newButton);
161 },
162
163 /*
164 * Removes a command button from the card.
165 */
166 removeCommandButton: function(button)
167 {
168 this.buttonLabel.innerHTML = '';
169 this.buttonbox.removeChild(this.buttons[button]);
170 },
171
172 /*
173 * Sets the title of the card.
174 */
175 setTitle: function(title)
176 {
177 this.options.title = title;
178 this.title.innerHTML = title;
179 },
180
181 /*
182 * Disables the drag on the card and removes the card
183 * from the DOM.
184 */
185 destroy: function()
186 {
187 // Kill the drag to prevent probable memory leaks in IE
188 this.drag.destroy();
189
190 // Remove the card from the DOM
191 document.body.removeChild(this.card);
192
193 // Destroy the overlay
194 this.overlay.destroy();
195 },
196
197 /*
198 * Display the card.
199 */
200 show: function()
201 {
202 // Track the currently active card for the history manager
203 // FLAWED LOGIC: We can have multiple cards visible at the same
204 // time.
205 SME.currentCard = this;
206
207 // Get card positioning data
208 var cardPos = window.calcCordsToCenter(this.options.height, this.options.width);
209 var windSize = window.getDimensions();
210
211 // Position the card
212 this.card.setStyle(
213 {
214 top: cardPos.top + 'px',
215 left: cardPos.left + 'px'
216 });
217
218 this._autoSetLayout();
219
220 // Show the overlay first so it will fade in with the card
221 this.overlay.show();
222
223 // Fade the card in and run its fadeComplete function.
224 new Effect.Appear(this.card,
225 {
226 afterFinish: function()
227 {
228 this.options.onFadeComplete();
229 }.bind(this)
230 });
231 },
232
233 /*
234 * Automatically set the layout based on the content of a JSON feed
235 * if it is supplied.
236 */
237 _autoSetLayout: function()
238 {
239 // Fetch data only if this is not an intro card and a content
240 // id has been specified.
241 if (!this.options.contID)
242 {
243 return;
244 }
245
246 // Pull the correct data if this is a preview or a real
247 // card.
248 if (this.options.preview)
249 {
250 var theUrl = SME.url.cardPreview.evaluate({card: this.options.contID})
251 }
252 else
253 {
254 var theUrl = SME.url.cards.evaluate({card: this.options.contID});
255 }
256
257 new Ajax.Request(theUrl,
258 {
259 method: 'get',
260
261 onSuccess: function(transport)
262 {
263 try
264 {
265 var data = transport.responseText.cleanJSON().evalJSON();
266 this.setLayout(SME.engineMapping[data.template], data);
267 }
268 catch (exception)
269 {
270 if (SME.debug)
271 {
272 console.error(exception);
273 }
274
275 this.setLayout(Card.Layout.Errors, '');
276 }
277 }.bind(this)
278 });
279 },
280
281 /*
282 * Add extra buttons to a card. I moved this to its own private
283 * function because some cards, like system cards, don't need all
284 * those extra buttons.
285 */
286 _addExtraButtons: function()
287 {
288 this.addCommandButton(Strings.addToSketchbook,'images/plus.gif', function()
289 {
290 SME.sketchbook.addChip(this.options.contID);
291 }.bind(this));
292
293 this.addCommandButton(Strings.sendToFriend,'images/email.gif', function()
294 {
295 if ($('cardFlash'))
296 {
297 this.flashP = $('cardFlash').up();
298 this.flash = $('cardFlash').remove();
299 }
300
301 var card = new Card(
302 {
303 color: SME.colors.grey,
304 title: Strings.sendToFriend,
305 addExtraButtons: false,
306
307 onFadeComplete: function()
308 {
309 if (this.flash)
310 {
311 this.flashP.appendChild(this.flash);
312 this.flash = null;
313 }
314 }.bind(this)
315 });
316
317 card.setLayout(Card.Layout.Special,
318 {
319 url: SME.url.sendToFriend.evaluate(
320 {
321 durl: 'card' + encodeURI(this.options.contID),
322 title: encodeURI(this.options.title)
323 })
324 });
325
326 card.show();
327 }.bind(this));
328
329 this.addCommandButton(Strings.printCard, 'images/print.gif', function()
330 {
331 this.layoutEngine.print();
332 }.bind(this));
333
334 // Card history ON the card was poorly designed and implemented
335 // (yeah, I know) so I'm just removing it for now till I get
336 // some time to re-write it.
337 //
338 // TODO: Re-write this
339 this.addCommandButton(Strings.myHistory,'images/history.gif', function()
340 {
341 var center = window.calcCordsToCenter(SME.sizes.card.height, SME.sizes.card.width);
342 SME.history.getDropDown(center.left + 30, center.top + 13);
343 });
344 },
345
346 /*
347 * Hides the current card but does not remove it from the DOM.
348 */
349 hide: function()
350 {
351 // There is no current card so unset it
352 // FLAWED LOGIC: We can have multiple cards.
353 SME.currentCard = null;
354
355 // Fade out and subsequently destroy the card
356 new Effect.Fade(this.card,
357 {
358 afterFinish: function()
359 {
360 this.options.onFadeOutFinish();
361 this.destroy();
362 }.bind(this)
363 });
364
365 // FLAWED LOGIC: Multiple cards, see above.
366 SME.history.clearHash();
367 this.overlay.hide();
368 this.options.onClose();
369 },
370
371 /*
372 * Sets the layout of the card using a layout engine.
373 */
374 setLayout: function(layoutE, data)
375 {
376 this.layoutEngine = new layoutE(this.cframe, data, this).layout();
377 }
378});
diff --git a/docroot/classes/chip.class.js b/docroot/classes/chip.class.js
new file mode 100755
index 0000000..61b7bdb
--- /dev/null
+++ b/docroot/classes/chip.class.js
@@ -0,0 +1,269 @@
1/*
2 * Material Experience - Card Chip Class
3 *
4 * EYEMG - Interactive Media Group
5 * Created by Mike Crute (mcrute@eyemg.com)
6 * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07
7 */
8
9var CardChip = Class.create();
10Object.extend(CardChip.prototype,
11{
12 /*
13 * Initializes the chip class.
14 */
15 initialize: function()
16 {
17 this.options = Object.extend(
18 {
19 x: 0, // X coordinate of the chip
20 y: 0, // Y coordinate of the chip
21 locked: false, // Require login to view card
22 category: null, // Chip target category
23 contID: null, // Chip target content ID
24 title: null, // Chip target title
25 image: null, // Chip image
26 className: 'chip', // Chip CSS Class Name
27 animate: true // Animate chip onto table
28 }, arguments[0] || {});
29
30 this.dragged = false;
31
32 this._createChip();
33
34 return this;
35 },
36
37 /*
38 * Create the chip DOM nodes and attach events and drags as appropriate.
39 */
40 _createChip: function()
41 {
42 this.chip = Builder.node('img',
43 {
44 src: this.options.image,
45 className: this.options.className,
46 style: 'top: ' + this.options.y + 'px; left: ' + this.options.x + 'px',
47 title: this.options.title + ((this.options.locked) ? ' (protected)' : ''),
48 alt: this.options.title + ((this.options.locked) ? ' (protected)' : '')
49 });
50
51 // Each DOM node should have a link back to this class so we can
52 // later look at a DOM node which holds no real data about the
53 // object and come back here to get the data we need.
54 this.chip.classLink = this;
55
56 new Draggable(this.chip,
57 {
58 starteffect: null,
59 endeffect: null,
60
61 change: function()
62 {
63 // Set the dragged flag (see onClick for more information)
64 this.dragged = true;
65 }.bind(this),
66
67 onEnd: function(e)
68 {
69 // Ensures that cards stay on top of the stack after they are
70 // dropped. This is accomplished by determining the maximum
71 // z-index of the cards and applying max + 1 to the current
72 // chip.
73 //
74 // Note that we are modifying a variable used internally by
75 // Scriptaculous that is NOT part of the public API. The
76 // originalZ variable is used by Scriptaculous to record the
77 // z-index that it should return the draggable to, by
78 // incrementing this we effectively move the card up in the
79 // stack.
80 //
81 // Note that scriptaculous adds a z-index of 1000 when the drag
82 // starts and does not remove it till AFTER this function executes
83 // so we have to take off that extra 1000 to get the real z-index.
84 e.originalZ = $$('div#' + (SME.currentTable || 'home') + ' img.chip').max(function(x)
85 {
86 var z = parseInt(x.style.zIndex) || 0;
87 return (z >= 1000) ? z - 1000 : z;
88 }) + 1;
89
90 // Track the X and Y coordinates of the chip
91 e.element.classLink.options.x = e.element.style.left.split('px')[0];
92 e.element.classLink.options.y = e.element.style.top.split('px')[0];
93 }
94 });
95
96 Event.observe(this.chip, 'click', function()
97 {
98 this.onClick();
99 }.bindAsEventListener(this));
100
101 if (this.options.animate)
102 {
103 this._generateAnimation();
104 }
105 },
106
107 /*
108 * Handle clicks on the chip.
109 */
110 onClick: function()
111 {
112 // If we recently dragged this card then do nothing. This
113 // facilitates single click DND as well as single click
114 // activation.
115 if (this.dragged == true)
116 {
117 this.dragged = false;
118 return;
119 }
120
121 // If this is a locked chip and we aren't logged in then show
122 // a login screen
123 if (this.options.locked && !Sketchbook.loggedIn)
124 {
125 return Sketchbook.showLoginScreen();
126 }
127
128 // Disable the puff in IE because the PNG fix breaks it
129 if (!Prototype.Browser.IE)
130 {
131 new Effect.Puff(this.chip,
132 {
133 afterFinish: function()
134 {
135 new Effect.Appear(this.chip);
136 }.bind(this)
137 });
138 }
139
140 SME.history.registerEvent('card', this.options.title, this.options.contID);
141
142 var t = new Card(
143 {
144 color: CardTable.tableColor(this.options.category),
145 contID: this.options.contID,
146 title: this.options.title,
147 chip: this
148 });
149
150 t.show();
151 },
152
153 /*
154 * Actually move the chip onto the table. This function expects the
155 * original and new coordinates to be pre-generated and stored by
156 * another function.
157 */
158 animate: function()
159 {
160 new Effect.Morph(this.chip,
161 {
162 style:
163 {
164 top: this.newY + 'px',
165 left: this.newX + 'px'
166 }
167 });
168 },
169
170 /*
171 * Return data about the chip in an object that matches of the format
172 * of the card table JSON feed.
173 */
174 _serialize: function()
175 {
176 return {
177 cid : this.options.contID,
178 title : this.options.title,
179 category : this.options.category,
180 locked : this.options.locked,
181 x : this.options.x,
182 y : this.options.y,
183 chip : this.options.image
184 };
185 },
186
187 /*
188 * Generate random coordinates for and place the chip off the edge of
189 * the table to be animated in later on by a different function.
190 */
191 _generateAnimation: function()
192 {
193 var myRand = Math.floor(1 + (5 - 1) * Math.random());
194 var windSize = window.getDimensions();
195
196 this.newX = this.options.x;
197 this.newY = this.options.y;
198
199 this.chip.setStyle({ position: 'absolute' });
200
201 // Determine from which direction the cards will animate
202 switch (myRand)
203 {
204 case 1: // Top
205 this.chip.setStyle(
206 {
207 top: -(SME.sizes.chipMax.height) + 'px',
208 left: (windSize.width / 2) + 'px'
209 });
210 break;
211
212 case 2: // Right
213 this.chip.setStyle(
214 {
215 top: (windSize.height / 2) + 'px',
216 left: (SME.sizes.chipMax.width) + windSize.width + 'px'
217 });
218 break;
219
220 case 3: // Bottom
221 this.chip.setStyle(
222 {
223 top: (SME.sizes.chipMax.height) + windSize.height + 'px',
224 left: (windSize.width / 2) + 'px'
225 });
226 break;
227
228 default: // Left
229 this.chip.setStyle(
230 {
231 top: (windSize.height / 2) + 'px',
232 left: -(SME.sizes.chipMax.width + 40) + 'px'
233 });
234 break;
235 }
236 },
237
238 /*
239 * Add a chip DOM node to a table.
240 */
241 addTo: function(table)
242 {
243 table.appendChild(this.chip);
244 },
245
246 /*
247 * Remove the chip DOM node from the document.
248 */
249 destroy: function()
250 {
251 document.removeChild(this.chip);
252 },
253
254 /*
255 * Show the chip.
256 */
257 show: function()
258 {
259 this.chip.show();
260 },
261
262 /*
263 * Hide the chip.
264 */
265 hide: function()
266 {
267 this.chip.hide();
268 }
269}); \ No newline at end of file
diff --git a/docroot/classes/cookie.class.js b/docroot/classes/cookie.class.js
new file mode 100755
index 0000000..adf28f2
--- /dev/null
+++ b/docroot/classes/cookie.class.js
@@ -0,0 +1,70 @@
1/*
2 * Material Experience - Cookie Handling Class
3 *
4 * EYEMG - Interactive Media Group
5 * Created by Mike Crute (mcrute@eyemg.com) on 10/2/07
6 * Updated by Mike Crute (mcrute@eyemg.com) on 10/3/07
7 *
8 * Class to handle cookie CRUD. Code adapted from:
9 * http://www.quirksmode.org/js/cookies.html
10 */
11
12var Cookie = Object.extend(Class.create(),
13{
14 /*
15 * Creates a cookie.
16 */
17 create: function(name, value, days)
18 {
19 if (days)
20 {
21 var date = new Date().setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
22 var expires = "; expires=" + date.toGMTString();
23 }
24
25 document.cookie = name + "=" + value + (expires ? expires : '') + "; path=/";
26 },
27
28 /*
29 * Setting the expiration date of the cookie to -1 effectively deletes
30 * it.
31 */
32 erase: function(name)
33 {
34 createCookie(name,"",-1);
35 },
36
37 /*
38 * Reads a cookie and returns the value.
39 */
40 read: function(name)
41 {
42 var nameEQ = name + "=";
43 var ca = document.cookie.split(';');
44
45 for ( var i = 0; i < ca.length; i++ )
46 {
47 var c = ca[i];
48
49 while (c.charAt(0) == ' ')
50 {
51 c = c.substring(1,c.length);
52 }
53
54 if (c.indexOf(nameEQ) == 0)
55 {
56 return c.substring(nameEQ.length,c.length);
57 }
58 }
59
60 return null;
61 }
62});
63
64/*
65 * Shorthand for Cookie.read
66 */
67function $C(name)
68{
69 return Cookie.read(name);
70} \ No newline at end of file
diff --git a/docroot/classes/decoder.module.js b/docroot/classes/decoder.module.js
new file mode 100755
index 0000000..ae33059
--- /dev/null
+++ b/docroot/classes/decoder.module.js
@@ -0,0 +1,205 @@
1/*
2 * Material Experience - Encoder/Decoder Module
3 *
4 * EYEMG - Interactive Media Group
5 * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07
6 *
7 * Encoder/Decoder module for common encoding schemes like URL and
8 * base64. Code from: http://ostermiller.org/calc/encode.html
9 */
10var END_OF_INPUT = -1;
11
12var base64Chars = new Array(
13 'A','B','C','D','E','F','G','H',
14 'I','J','K','L','M','N','O','P',
15 'Q','R','S','T','U','V','W','X',
16 'Y','Z','a','b','c','d','e','f',
17 'g','h','i','j','k','l','m','n',
18 'o','p','q','r','s','t','u','v',
19 'w','x','y','z','0','1','2','3',
20 '4','5','6','7','8','9','+','/'
21);
22
23var reverseBase64Chars = new Array();
24var base64Str;
25var base64Count;
26
27for (var i=0; i < base64Chars.length; i++)
28{
29 reverseBase64Chars[base64Chars[i]] = i;
30}
31
32// -------------------------------------------------------------------------- \\
33
34function urlDecode(str)
35{
36 return unescape(str.replace(new RegExp('\\+','g'),' '));
37}
38
39function urlEncode(str)
40{
41 str = escape(str);
42 str = str.replace(new RegExp('\\+','g'),'%2B');
43
44 return str.replace(new RegExp('%20','g'),'+');
45}
46
47// -------------------------------------------------------------------------- \\
48
49function setBase64Str(str)
50{
51 base64Str = str;
52 base64Count = 0;
53}
54
55function readBase64()
56{
57 if (!base64Str)
58 {
59 return END_OF_INPUT;
60 }
61
62 if (base64Count >= base64Str.length)
63 {
64 return END_OF_INPUT;
65 }
66
67 var c = base64Str.charCodeAt(base64Count) & 0xff;
68
69 base64Count++;
70
71 return c;
72}
73
74function encodeBase64(str)
75{
76 setBase64Str(str);
77
78 var result = '';
79 var inBuffer = new Array(3);
80 var lineCount = 0;
81 var done = false;
82
83 while (!done && (inBuffer[0] = readBase64()) != END_OF_INPUT)
84 {
85 inBuffer[1] = readBase64();
86 inBuffer[2] = readBase64();
87 result += (base64Chars[ inBuffer[0] >> 2 ]);
88
89 if (inBuffer[1] != END_OF_INPUT)
90 {
91 result += (base64Chars [(( inBuffer[0] << 4 ) & 0x30) | (inBuffer[1] >> 4) ]);
92
93 if (inBuffer[2] != END_OF_INPUT)
94 {
95 result += (base64Chars [((inBuffer[1] << 2) & 0x3c) | (inBuffer[2] >> 6) ]);
96 result += (base64Chars [inBuffer[2] & 0x3F]);
97 }
98 else
99 {
100 result += (base64Chars [((inBuffer[1] << 2) & 0x3c)]);
101 result += ('=');
102 done = true;
103 }
104 }
105 else
106 {
107 result += (base64Chars [(( inBuffer[0] << 4 ) & 0x30)]);
108 result += ('=');
109 result += ('=');
110 done = true;
111 }
112
113 lineCount += 4;
114
115 if (lineCount >= 76)
116 {
117 result += ('\n');
118 lineCount = 0;
119 }
120 }
121
122 return result;
123}
124
125function readReverseBase64()
126{
127 if (!base64Str)
128 {
129 return END_OF_INPUT;
130 }
131
132 while (true)
133 {
134 if (base64Count >= base64Str.length)
135 {
136 return END_OF_INPUT;
137 }
138
139 var nextCharacter = base64Str.charAt(base64Count);
140 base64Count++;
141
142 if (reverseBase64Chars[nextCharacter])
143 {
144 return reverseBase64Chars[nextCharacter];
145 }
146
147 if (nextCharacter == 'A')
148 {
149 return 0;
150 }
151 }
152
153 return END_OF_INPUT;
154}
155
156function ntos(n)
157{
158 n = n.toString(16);
159
160 if (n.length == 1)
161 {
162 n = "0" + n;
163 }
164
165 n = "%" + n;
166
167 return unescape(n);
168}
169
170function decodeBase64(str)
171{
172 setBase64Str(str);
173
174 var result = "";
175 var inBuffer = new Array(4);
176 var done = false;
177
178 while (!done && (inBuffer[0] = readReverseBase64()) != END_OF_INPUT
179 && (inBuffer[1] = readReverseBase64()) != END_OF_INPUT)
180 {
181 inBuffer[2] = readReverseBase64();
182 inBuffer[3] = readReverseBase64();
183 result += ntos((((inBuffer[0] << 2) & 0xff)| inBuffer[1] >> 4));
184
185 if (inBuffer[2] != END_OF_INPUT)
186 {
187 result += ntos((((inBuffer[1] << 4) & 0xff)| inBuffer[2] >> 2));
188
189 if (inBuffer[3] != END_OF_INPUT)
190 {
191 result += ntos((((inBuffer[2] << 6) & 0xff) | inBuffer[3]));
192 }
193 else
194 {
195 done = true;
196 }
197 }
198 else
199 {
200 done = true;
201 }
202 }
203
204 return result;
205} \ No newline at end of file
diff --git a/docroot/classes/history.class.js b/docroot/classes/history.class.js
new file mode 100755
index 0000000..12005c3
--- /dev/null
+++ b/docroot/classes/history.class.js
@@ -0,0 +1,208 @@
1/*
2 * Material Experience - History Manager Class
3 *
4 * EYEMG - Interactive Media Group
5 * Created by Mike Crute (mcrute@eyemg.com)
6 * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07
7 */
8
9var HistoryManager = Class.create();
10Object.extend(HistoryManager.prototype,
11{
12 /*
13 * Sets up the history storage array and initializes the last
14 * event to nothing.
15 */
16 initialize: function()
17 {
18 this.historyStore = $H();
19 this.lastEvent = null;
20 },
21
22 /*
23 * Clears the window hash. Generally called when a card closes.
24 */
25 clearHash: function()
26 {
27 window.location.hash = '#';
28 },
29
30 /*
31 * Register an event with the history manager. Also updates the
32 * window hash to reflect the event. This is the only way to
33 * register an event with the history manager.
34 */
35 registerEvent: function(type, title, url)
36 {
37 window.location.hash = '#' + type + url;
38
39 // If you don't set this explicitly IE will show the hash as
40 // the document title.
41 document.title = Strings.appTitle;
42
43 this.lastEvent = '#' + type + url;
44 this.historyStore[type + url] = title;
45
46 return this;
47 },
48
49 /*
50 * Stops polling the URL for new events.
51 */
52 stopPolling: function()
53 {
54 this.poller.stop();
55
56 return this;
57 },
58
59 /*
60 * Starts polling the URL for new events and will execute the event
61 * handler if an event occurs.
62 */
63 pollEvents: function()
64 {
65 this.poller = new PeriodicalExecuter(function()
66 {
67 // Make sure there is a valid hash and it is NOT the card
68 // that we just loaded (prevent double-carding)
69 if (
70 this.lastEvent != window.location.hash &&
71 window.location.hash != '#' &&
72 window.location.hash != ''
73 ) {
74 this.lastEvent = window.location.hash;
75 this._handleEvents();
76 }
77 }.bind(this), 0.1);
78
79 return this;
80 },
81
82 /*
83 * Handles an event, this is called internally by the pollEvents function
84 * when a new event occurs and should never be called directly.
85 */
86 _handleEvents: function()
87 {
88 // Breaks down the event type and parameters from the hash
89 var evtTypes = /^(card|table|preview)/;
90 var hash = window.location.hash;
91
92 // When IE does the split it only returns one array element,
93 // the parameter, so we have to do a little magic to match
94 // the action too. All other browsers seem to work correctly.
95 var fullEvt = hash.substr(1, hash.length).split(evtTypes).without('');
96 var eventType = (fullEvt.length == 1) ? hash.substr(1, hash.length).match(evtTypes)[0] : fullEvt[0];
97 var eventParam = (fullEvt.length == 1) ? fullEvt : fullEvt[1];
98
99 // Hide the current card if there is one
100 if (SME.currentCard && SME.currentCard.hide)
101 {
102 SME.currentCard.hide();
103 }
104
105 switch (eventType)
106 {
107 case 'table':
108 CardTable.showTable(eventParam);
109 return;
110 break;
111
112 case 'card':
113 theURL = SME.url.cards.evaluate({card: eventParam});
114 break;
115
116 case 'preview':
117 theURL = SME.url.cardPreview.evaluate({card: eventParam});
118 this.stopPolling();
119 break;
120
121 default:
122 return;
123 break;
124 }
125
126 new Ajax.Request(theURL,
127 {
128 method: 'get',
129
130 onSuccess: function(transport)
131 {
132 var data = transport.responseText.cleanJSON().evalJSON();
133
134 var card = new Card(
135 {
136 color: CardTable.tableColor(data.type),
137 contID: eventParam,
138 title: data.title,
139 preview: (eventType == 'preview') ? true : false
140 });
141
142 this.registerEvent(eventType, data.title, eventParam);
143 card.show();
144 }.bind(this)
145 });
146 },
147
148
149 /*
150 * Creates and populates the history dropdown based on the
151 * events stored in the history storage array. Also handles
152 * showing, hiding and positioning that menu.
153 */
154 getDropDown: function(x, y)
155 {
156 var dropdown = $('history');
157
158 // If no x and y then position the menu for the my history
159 // dropdown from the tools menu.
160 x = x ? x : 20;
161 y = y ? y : 26;
162
163 // Remove and re-append the menu from the DOM, this prevents
164 // IE z-index bugs.
165 document.body.appendChild(dropdown.remove());
166 dropdown.innerHTML = '';
167
168 // Populate the menu
169 this.historyStore.each(function(item)
170 {
171 dropdown.appendChild(Builder.node('li', {}, Builder.node('a', { href: '#' + item.key }, item.value)));
172 });
173
174 // Initially show the menu
175 dropdown.addClassName('hovered');
176
177 // Must explicitly set left to nothing otherwise positioning
178 // is wrong.
179 dropdown.setStyle(
180 {
181 right: x + 'px',
182 top: y + 'px',
183 left: '',
184 zIndex: 9999
185 });
186
187 // Hide the menu when the user...
188 $H({
189 '#history a' : 'click', // Clicks on a link
190 'div.table' : 'mouseover', // Mouses-off onto a table
191 'p' : 'mouseover', // Mouses-off onto a paragraph
192 'iframe' : 'mouseover', // Mouses-off onto an iframe
193 'img' : 'mouseover' // Mouses-off onto an image
194 }).each(function(aitem)
195 {
196 $$(aitem.key).each(function(item)
197 {
198 item.observe(aitem.value, function()
199 {
200 if ($('history').hasClassName('hovered'))
201 {
202 $('history').removeClassName('hovered');
203 }
204 });
205 });
206 });
207 }
208}); \ No newline at end of file
diff --git a/docroot/classes/layout.class.js b/docroot/classes/layout.class.js
new file mode 100755
index 0000000..a088fbf
--- /dev/null
+++ b/docroot/classes/layout.class.js
@@ -0,0 +1,51 @@
1/*
2 * Material Experience - Card Layout Engines Class
3 *
4 * EYEMG - Interactive Media Group
5 * Created by Mike Crute (mcrute@eyemg.com)
6 * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07
7 */
8
9// Card.Layout.Errors = Class.create();
10// Object.extend(Object.extend(Card.Layout.Errors.prototype, Card.Layout.prototype),
11
12Card.Layout = Class.create();
13Object.extend(Card.Layout.prototype,
14{
15 /*
16 * Initialize the layout class
17 */
18 initialize: function(cframe, data, card)
19 {
20 this.cframe = cframe;
21 this.data = data;
22 this.card = card;
23 this.color = this.card.options.color;
24 this.hasResources = false;
25 },
26
27 /*
28 * Main function to layout the card.
29 */
30 layout: function()
31 {
32 throw "Not implemented here.";
33 },
34
35 /*
36 * Throw the card into a popup window for printing.
37 */
38 print: function()
39 {
40 throw "Not implemented here.";
41 },
42
43 /*
44 * Debug the card layout. Generally this should be used to spot out
45 * data issues. But it could also be used for other debugging purposes.
46 */
47 _debug: function()
48 {
49 throw "Not implemented here.";
50 }
51}); \ No newline at end of file
diff --git a/docroot/classes/layouts/layout.custom.class.js b/docroot/classes/layouts/layout.custom.class.js
new file mode 100755
index 0000000..d4aa463
--- /dev/null
+++ b/docroot/classes/layouts/layout.custom.class.js
@@ -0,0 +1,219 @@
1/*
2 * Material Experience - Card Layout Engines Class
3 *
4 * EYEMG - Interactive Media Group
5 * Created by Mike Crute (mcrute@eyemg.com)
6 * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07
7 */
8
9Card.Layout.Custom = Class.create();
10Object.extend(Object.extend(Card.Layout.Custom.prototype, Card.Layout.prototype),
11{
12 /*
13 * Initialize the layout class
14 */
15 initialize: function(cframe, data, card)
16 {
17 this.cframe = cframe;
18 this.data = data;
19 this.card = card;
20 this.color = this.card.options.color;
21 this.hasResources = false;
22 this.inited = false;
23 },
24
25 /*
26 * Create custom scroll bars for an HTML element.
27 */
28 scrollify: function(div, isiframe)
29 {
30 var div2 = null;
31
32 // Inherit the color from a link
33 var color = $$('ul#navigation li a')[0].getStyle('background-color');
34
35 if (isiframe)
36 {
37 div2 = div.contentWindow.document.documentElement;
38 }
39 else
40 {
41 div2 = div;
42 }
43
44 // Create the scroll container and draggable widget
45 this.cframe.appendChild(
46 this.scrollCont = Builder.node('div',
47 {
48 style: 'border-left: 1px solid ' + color + ';' +
49 'width: 1px; position: absolute; ' +
50 'height:' + div.offsetHeight + 'px; ' +
51 'top: 10px;' + 'left: ' + (div.offsetLeft + div.offsetWidth + 12) + 'px;'
52 },
53
54 this.scrollBar = Builder.node('img',
55 {
56 src: 'http://materialexperience.santoprene.com/images/pill.gif',
57 style: 'display: block; margin-left: -3px; cursor: move; ' +
58 'background: ' + color + '; padding: 0px;'
59 })
60 ));
61
62 // Create the scroller
63 this.scroller = new Control.Slider(this.scrollBar, this.scrollCont,
64 {
65 axis: 'vertical',
66 range: $R(0, div2.scrollHeight),
67
68 onSlide: function(value)
69 {
70 div2.scrollTop = Math.floor(value);
71 }.bind(this)
72 });
73
74 new PeriodicalExecuter(function()
75 {
76 // Hide the scroller if there isn't a need for it
77 if (div2.scrollHeight > 480)
78 {
79 this.scrollCont.show();
80 }
81 else
82 {
83 this.scrollCont.hide();
84 return;
85 }
86
87 // Update the scroller range when the contents change
88 this.scroller.range = $R(0, div2.scrollHeight);
89
90 // Move the scroller when the scrolled element changes
91 // (e.g. linking down into the page).
92 if (this.scroller.value != div2.scrollTop)
93 {
94 this.scroller.setValue(div2.scrollTop);
95 }
96 }.bind(this), 1);
97 },
98
99 initIframe: function(url)
100 {
101 $('wideCol').innerHTML = '<iframe ' +
102 'src = "' + url + '" ' +
103 'frameborder = "0" ' +
104 'scrolling = "auto" ' +
105 'width = "660" ' +
106 'height = "460" ' +
107 'id = "wide_content" ' +
108 'name = "wide_content" ' +
109 '></iframe>';
110 },
111
112 /*
113 * Main function to layout the card.
114 */
115 layout: function()
116 {
117 var htmlstr = '<div id="narrowCol" style="float: left; margin: 10px 0px 0px 20px; width: 200px; height: 460px; overflow: hidden;"><ul id="navigation">';
118
119 this.data.contentNarrow.each(function(item)
120 {
121 htmlstr += '<li><a target="wide_content" href="' + item.url + '">' + item.title + '</a>';
122
123 if (item.subitems)
124 {
125 var parentUrl = item.url;
126
127 htmlstr += '<ul>';
128
129 item.subitems.each(function(subitem)
130 {
131 htmlstr += '<li><a target="wide_content" href="' + parentUrl + '#' + subitem.url + '">' + subitem.title + '</a></li>';
132 }.bind(this));
133
134 htmlstr += '</ul></li>';
135 }
136 else
137 {
138 htmlstr += '</li>';
139 }
140 }.bind(this));
141
142 htmlstr += '</ul></div><div id="wideCol" style="width: 660px; height: 460px; float: right; margin: 10px 10px 0px 0px;"></div>';
143 this.cframe.innerHTML = htmlstr;
144
145 if (this.data.intro)
146 {
147 $('wideCol').innerHTML = this.data.intro;
148 }
149
150// this.scrollify($('wide_content'), true);
151 this.scrollify($('narrowCol'));
152
153 // Hide everything first
154 $$('ul#navigation li ul').each(function(item)
155 {
156 item.setStyle({ display: 'none' });
157 });
158
159 var curr = null;
160 // Then attach the onclick events
161 $$('ul#navigation li').each(function(item)
162 {
163 Event.observe(item, 'click', function(event)
164 {
165 // Hide the currently opened item if it is clicked again
166 // per BS.
167 if (curr && item == curr)
168 {
169 curr.down('ul').hide();
170 item.setStyle({ borderLeft: '0px' });
171 curr = null;
172 return;
173 }
174
175 curr = item;
176
177 // Hide everything
178 $$('ul#navigation li ul').each(function(item)
179 {
180 item.setStyle({ display: 'none' });
181 });
182
183 // Clear out the borders
184 $$('ul#navigation li').each(function(item)
185 {
186 item.setStyle({ borderLeft: '' });
187 });
188
189 // Figure out what color we SHOULD be, this will change
190 // depending on the card color we are on so just rely
191 // on the designer to communicate through the CSS,
192 // probably not wise relying on a designer for code
193 // but alas...
194 var color = $$('ul#navigation li a')[0].getStyle('background-color');
195
196 this.initIframe(item.href);
197
198 // Catch and execute or just leave it lie
199 try
200 {
201 if (item.down('ul').style.display != 'none')
202 {
203 item.down('ul').hide();
204 Event.stop(event);
205 return;
206 }
207
208 item.down('ul').show();
209 item.setStyle({ borderLeft: '3px solid ' + color });
210 //Event.stop(event);
211 }
212 catch (e)
213 {
214 // Just let the link do its thing
215 }
216 }.bind(this));
217 }.bind(this));
218 }
219}); \ No newline at end of file
diff --git a/docroot/classes/layouts/layout.error.class.js b/docroot/classes/layouts/layout.error.class.js
new file mode 100755
index 0000000..2fedfcf
--- /dev/null
+++ b/docroot/classes/layouts/layout.error.class.js
@@ -0,0 +1,33 @@
1/*
2 * Material Experience - Error Card Layout Engine
3 *
4 * EYEMG - Interactive Media Group
5 * Created by Mike Crute (mcrute@eyemg.com)
6 * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07
7 */
8
9Card.Layout.Errors = Class.create();
10Object.extend(Object.extend(Card.Layout.Errors.prototype, Card.Layout.prototype),
11{
12 /*
13 * Initialize the layout class
14 */
15 initialize: function(cframe, data, card)
16 {
17 this.cframe = cframe;
18 this.data = data;
19 this.card = card;
20 this.color = this.card.options.color;
21 this.hasResources = false;
22 },
23
24 /*
25 * Main function to layout the card.
26 */
27 layout: function()
28 {
29 this.cframe.innerHTML = '';
30 this.cframe.appendChild(Builder.node('h1', Strings.cardErrorTitle));
31 this.cframe.appendChild(Builder.node('p', Strings.cardErrorText));
32 }
33}); \ No newline at end of file
diff --git a/docroot/classes/layouts/layout.primary.class.js b/docroot/classes/layouts/layout.primary.class.js
new file mode 100755
index 0000000..87bc910
--- /dev/null
+++ b/docroot/classes/layouts/layout.primary.class.js
@@ -0,0 +1,411 @@
1/*
2 * Material Experience - Primary Card Layout Class
3 *
4 * EYEMG - Interactive Media Group
5 * Created by Mike Crute (mcrute@eyemg.com)
6 * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07
7 *
8 * WARNING: Here be dragons, OK so not as many as before but good luck you poor
9 * sap.
10 */
11
12Card.Layout.Primary = Class.create();
13Object.extend(Object.extend(Card.Layout.Primary.prototype, Card.Layout.prototype),
14{
15 /*
16 * Initialize the layout class
17 */
18 initialize: function(cframe, data, card)
19 {
20 this.cframe = cframe;
21 this.data = data;
22 this.card = card;
23 this.color = this.card.options.color;
24 this.hasResources = false;
25
26 var direction = data.template.replace(/narrow-/,'');
27 this.direction = direction ? direction : 'left';
28 this.oppositeDirection = (direction == 'left') ? 'right' : 'left';
29 },
30
31 /*
32 * Set the column content. This can be flash, an image or an iframe.
33 */
34 setColumn: function(url, direction, contTitle)
35 {
36 var content = Builder.node('div', { style: 'float: ' + this.oppositeDirection });
37 var myCol = direction + 'Col';
38
39 if (/swf$/.test(url)) // Flash
40 {
41 content.innerHTML = new SWFObject(url, 'cardFlash', 580, 479, 9, '#FFFFFF').getSWFHTML();
42 }
43 else if (/(png|gif|jpg|jpeg)$/.test(url)) // Image
44 {
45 content = Builder.node('img',
46 {
47 src: url,
48 alt: contTitle,
49 title: contTitle,
50 style: 'float: ' + this.oppositeDirection
51 });
52 }
53 else // iFrame
54 {
55 // Can't use DOM methods because IE is allergic
56 content.innerHTML = '<iframe ' +
57 'src = "' + url + '" ' +
58 'frameborder = "0" ' +
59 'scrolling = "auto" ' +
60 'width = "580" ' +
61 'height = "480" ' +
62 'id = "wide_content" ' +
63 'name = "wide_content" ' +
64 '></iframe>';
65 }
66
67 // We need to replace the content of the column if it exists to
68 // facilitate changing content later on (i.e. thumbnails)
69 if (typeof this[myCol] != 'undefined')
70 {
71 this.cframe.replaceChild(content, this[myCol]);
72 this[myCol] = content;
73 }
74 else
75 {
76 this[myCol] = this.cframe.appendChild(content);
77 }
78
79 },
80
81 /*
82 * Build the resource (more information) list.
83 */
84 getResourceList: function()
85 {
86 // Only run if we need to
87 if (!this.data.resources)
88 {
89 return;
90 }
91
92 var numResources = 0;
93 var iter = 0;
94 var limit = 5;
95 this.hasResources = true;
96
97 this.resourceContainer = this.contentArea.appendChild(
98 Builder.node('div',
99 [
100 Builder.node('h2', { style: 'color: ' + this.card.options.color }, Strings.moreInfo),
101 this.resources = Builder.node('ul', { className: 'resourceList' })
102 ]
103 ));
104
105 this.data.resources.each(function(item)
106 {
107 // Limit the number of allowable resources
108 if (typeof item == 'undefined' || ++iter > limit)
109 {
110 return;
111 }
112
113 numResources++;
114
115 this.resources.appendChild(Builder.node('li',
116 Builder.node('a',
117 {
118 href: item.link + '?entrypoint=DESIGNER',
119 target: '_new',
120 style: 'color: ' + this.color
121 }, item.title)
122 ));
123 }.bind(this));
124 },
125
126 /*
127 * Set the contents of the narrow column.
128 */
129 setHTML: function(content)
130 {
131 this.htmlDiv.innerHTML = content;
132 },
133
134 /*
135 * Setup the thumbnails.
136 */
137 getThumbnails: function()
138 {
139 // Only run if we need to
140 if (this.data.media.size() <= 1)
141 {
142 return;
143 }
144
145 var iter = 0;
146 var limit = 5; // Includes main media file
147 var lData = this.data.media;
148
149 this.thumbStrip = this.contentArea.appendChild(Builder.node('div', { className: 'thumbStrip' }));
150
151 // Add the main media file to the data array
152 lData.push(
153 {
154 thumb: this.data.contentWide[0].thumb,
155 url: this.data.contentWide[0].url
156 });
157
158 this.data.media.each(function(item)
159 {
160 // Limit the number of thumbnails
161 if (typeof item == 'undefined' || ++iter > limit)
162 {
163 return;
164 }
165
166 var mediaPiece = this.thumbStrip.appendChild(
167 Builder.node('img',
168 {
169 src: item.thumb,
170 className: 'mediaThumb'
171 })
172 );
173
174 Event.observe(mediaPiece, 'click', function()
175 {
176 this.setColumn(item.url, this.direction, '');
177 }.bindAsEventListener(this));
178 }.bind(this));
179 },
180
181 /*
182 * Clean up the data.
183 */
184 _cleanData: function()
185 {
186 var lData = this.data.contentNarrow[0].htmlContent;
187 this.data.contentNarrow[0].htmlContent = lData.replace(/<a/gi, '<a style="color: '+this.color+'"');
188 },
189
190 /*
191 * Calculate the heights of various interface elements for use in
192 * laying out the page.
193 */
194 _calculateHeights: function()
195 {
196 // This data structure will hold the heights of all the elements
197 // in the narrow column for use in dynamically calculating the
198 // layout of the column.
199 this.heightTable =
200 {
201 headline : this.headLine ? this.headLine.getHeight() : 0,
202 content : this.htmlDiv ? this.htmlDiv.getHeight() : 0,
203 thumbnails : this.thumbStrip ? this.thumbStrip.getHeight() : 0,
204 resources : this.resourceContainer ? this.resourceContainer.getHeight() : 0,
205 paddingFactor : !Prototype.Browser.IE ? 40 : 0
206 };
207
208 // If there is no headline IE comes up with a ridiculous height for some
209 // reason so we correct for that here.
210 if (Prototype.Browser.IE && this.data.contentNarrow[0].title.length == 0)
211 {
212 this.heightTable.headline = 0;
213 }
214
215 // A variety of exceptions/tweaks for IE6 found by testing every combination
216 // of layouts and finding the variance. There isn't much of another way to
217 // do it.
218 if (Prototype.Browser.IE)
219 {
220 this.heightTable.thumbnail += 2;
221 this.heightTable.content += 24;
222 }
223
224 if (Prototype.Browser.IE && this.resourceContainer)
225 {
226 this.heightTable.resources -= 30;
227 }
228
229 if (Prototype.Browser.IE && this.htmlDiv && this.thumbStrip && !this.resourceContainer)
230 {
231 this.heightTable.thumbnails += 10;
232 }
233
234
235 if (Prototype.Browser.IE && this.resourceContainer && !this.thumbStrip)
236 {
237 this.heightTable.resources += 70;
238 }
239
240 // Calculate the height available for content
241 this.heightTable.avaliableForContent = SME.sizes.innerCard.height - (
242 this.heightTable.headline +
243 this.heightTable.thumbnails +
244 this.heightTable.resources +
245 this.heightTable.paddingFactor
246 );
247 },
248
249 /*
250 * Setup the scroll bar on the wide content area.
251 */
252 _setupScroller: function()
253 {
254 // We only want to show the scroll bar if there is a need for it
255 if (this.heightTable.content < this.heightTable.avaliableForContent)
256 {
257 return;
258 }
259
260 this.contentArea.appendChild(
261 this.scrollCont = Builder.node('div',
262 {
263 style: 'border-left: 1px solid ' + this.card.options.color + ';' +
264 'width: 1px; right: 0px; position: absolute; ' +
265 'height:' + this.heightTable.avaliableForContent + 'px; ' +
266 'top: ' + (this.heightTable.headline + 10) + 'px;'
267 },
268
269 this.scrollBar = Builder.node('img',
270 {
271 src: 'images/pill.gif',
272 style: 'display: block; margin-left: -3px; cursor: move; ' +
273 'background: ' + this.card.options.color + ';'
274 })
275 ));
276
277 new Control.Slider(this.scrollBar, this.scrollCont,
278 {
279 axis: 'vertical',
280 range: $R(0, this.heightTable.content),
281
282 onSlide: function(value)
283 {
284 this.htmlDiv.scrollTop = value;
285 }.bind(this)
286 });
287
288 },
289
290 /*
291 * Main function to layout the card.
292 */
293 layout: function()
294 {
295 this._cleanData();
296
297 this.setColumn(this.data.contentWide[0].url, this.direction, this.data.contentWide[0].title);
298
299 this.contentArea = this.cframe.appendChild(
300 Builder.node('div', { className: 'narrowCol' },
301 this.headLine = Builder.node('h1', { style: 'color: ' + this.color }, this.data.contentNarrow[0].title)
302 )
303 );
304
305 // We've gotta set the width here otherwise the height
306 // calculations will be incorrect
307 this.htmlDiv = Builder.node('div', { style: 'width: 98%; overflow: hidden; width: 300px;' });
308
309 // Must set this early on otherwise there is no way to determine
310 // the actual height of the content div
311 this.setHTML(this.data.contentNarrow[0].htmlContent);
312 this.contentArea.appendChild(this.htmlDiv);
313
314 this.getResourceList();
315 this.getThumbnails();
316
317 // IE doesn't let prototype mess with the default object
318 // prototypes so we have to manually extend them. Sigh...
319 [
320 this.headLine,
321 this.htmlDiv,
322 this.thumbStrip,
323 this.resourceContainer,
324 this.contentArea
325 ].each(Element.extend);
326
327 // Set these styles up here or we get a -21px bug in our
328 // calculation code
329 this.contentArea.setStyle(
330 {
331 width: '310px',
332 position: 'absolute',
333 top: '0px'
334 });
335
336 this._calculateHeights();
337 this._setupScroller();
338
339 // We set this last, after all the height calculations are
340 // completed. That makes it a lot easier to work with.
341 this.htmlDiv.setStyle({ height: this.heightTable.avaliableForContent + 'px' });
342
343 // Webkit doesn't pad things correctly
344 if (this.direction == 'left')
345 {
346 this.contentArea.setStyle({ left: '10px' });
347 }
348 else
349 {
350 this.contentArea.setStyle({ right: '10px' });
351 }
352
353 // If this is an intro card do the intro card stuff.
354 if (this.data.template == 'intro')
355 {
356 this._doIntro();
357 }
358
359 return this;
360 },
361
362 /*
363 * Setup an intro card. Because intro cards aren't really that much
364 * different from a basic card and because class inheritance sucks in
365 * Prototype > 1.6 I'm just going to add this into here.
366 */
367 _doIntro: function()
368 {
369 this.contentArea.appendChild(Builder.node('div', { className: 'skipbox' },
370 this.skipLink = Builder.node('a', { href: '#tablehome', className: 'skiplink' },
371 [
372 Strings.skipButton,
373 Builder.node('img', { src: 'images/arrow_right_grey.gif', className: 'skipimg' })
374 ])
375 ));
376 },
377
378 /*
379 * Throw the card into a popup window for printing.
380 */
381 print: function()
382 {
383 var wind = window.open('', '', 'width=' + SME.sizes.innerCard.width + ',height=' + (SME.sizes.innerCard.height + 60));
384
385 with (wind)
386 {
387 with (document)
388 {
389 write('<html><head><style type="text/css">@import url(application.css); .narrowCol{top: 30px !important;}</style></head><body>');
390 write('<img src="images/logo.jpg" style="display: block; margin: 5px 0px 30px 5px;"/>');
391 write(this.cframe.innerHTML);
392 write('</body></html>');
393 close();
394 }
395
396 print();
397 close();
398 }
399
400 return true;
401 },
402
403 /*
404 * Debug the card layout. Generally this should be used to spot out
405 * data issues. But it could also be used for other debugging purposes.
406 */
407 _debug: function()
408 {
409
410 }
411}); \ No newline at end of file
diff --git a/docroot/classes/layouts/layout.special.class.js b/docroot/classes/layouts/layout.special.class.js
new file mode 100755
index 0000000..b285390
--- /dev/null
+++ b/docroot/classes/layouts/layout.special.class.js
@@ -0,0 +1,43 @@
1/*
2 * Material Experience - Special Case Layout Engine
3 *
4 * EYEMG - Interactive Media Group
5 * Created by Mike Crute (mcrute@eyemg.com)
6 * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07
7 */
8
9Card.Layout.Special = Class.create();
10Object.extend(Object.extend(Card.Layout.Special.prototype, Card.Layout.prototype),
11{
12 /*
13 * Initialize the layout class
14 */
15 initialize: function(cframe, data, card)
16 {
17 this.cframe = cframe;
18 this.data = data;
19 this.card = card;
20 this.color = this.card.options.color;
21 this.hasResources = false;
22
23 // Check if the url is provided otherwise pull it out of the
24 // wide content field.
25 if (!this.data.url)
26 {
27 this.data.url = this.data.contentWide[0].url;
28 }
29 },
30
31 /*
32 * Main function to layout the card.
33 */
34 layout: function()
35 {
36 this.cframe.innerHTML = '<iframe ' +
37 'src = "' + this.data.url + '" ' +
38 'frameborder = "0" ' +
39 'width = "' + (SME.sizes.card.width - 11) + '" ' +
40 'height = "' + (SME.sizes.card.height - 40) + '"' +
41 '></iframe>';
42 }
43}); \ No newline at end of file
diff --git a/docroot/classes/overlay.class.js b/docroot/classes/overlay.class.js
new file mode 100755
index 0000000..9a6e110
--- /dev/null
+++ b/docroot/classes/overlay.class.js
@@ -0,0 +1,118 @@
1/*
2 * Material Experience - Document Overlay Class
3 *
4 * EYEMG - Interactive Media Group
5 * Created by Mike Crute (mcrute@eyemg.com)
6 * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07
7 */
8
9var Overlay = Class.create();
10Object.extend(Overlay.prototype,
11{
12 /*
13 * Initializes the overlay class
14 */
15 initialize: function()
16 {
17 this.options = Object.extend(
18 {
19 zIndex: 9000, // z-index of the overlay
20 background: 'white', // Overlay color
21 opacity: 0.5, // Overlay opacity
22 fadeInSpeed: 0.2, // Fade in speed
23 fadeOutSpeed: 1, // Fade out speed
24 onClick: this.hide, // Callback for overlay click
25 onShow: Prototype.emptyFunction, // Callback when the overlay is shown
26 onHide: Prototype.emptyFunction // Callback when the overlay is hidden
27 }, $H(arguments[0]) || {});
28
29 this._createOverlay();
30 },
31
32 /*
33 * Creates the actual DOM elements of the overlay.
34 */
35 _createOverlay: function()
36 {
37 this.overlay = Element.extend(document.createElement('div'));
38 document.body.appendChild(this.overlay);
39
40 Event.observe(this.overlay, 'click', this.options.onClick.bindAsEventListener(this));
41
42 this.overlay.setStyle(
43 {
44 backgroundColor: this.options.background,
45 zIndex: this.options.zIndex,
46 height: '100%',
47 width: '100%',
48 position: 'absolute',
49 display: 'none',
50 top: 0,
51 left: 0
52 });
53 },
54
55 /*
56 * Displays the overlay.
57 */
58 show: function()
59 {
60 this._fixBody();
61
62 this.options.onShow(this);
63
64 new Effect.Appear(this.overlay,
65 {
66 to: this.options.opacity,
67 duration: this.options.fadeInSpeed,
68 limit: 1
69 });
70 },
71
72 /*
73 * Hides the overlay.
74 */
75 hide: function()
76 {
77 this.options.onHide(this);
78
79 new Effect.Fade(this.overlay,
80 {
81 duration: this.options.fadeOutSpeed,
82 limit: 1,
83
84 afterFinish: function()
85 {
86 this._fixBody(true);
87 }.bind(this)
88 });
89 },
90
91 /*
92 * Removes the overlay from the DOM
93 */
94 destroy: function()
95 {
96 document.body.removeChild(this.overlay);
97 },
98
99 /*
100 * Fix for IE 6, sets the body height and overflow so people can not
101 * scroll beyond the overlay. Disable overflow on the body and html
102 * so you can see the whole overlay.
103 */
104 _fixBody: function(reset)
105 {
106 var myHeight = reset ? '' : '100%';
107 var myOverflow = reset ? '' : 'hidden';
108
109 $A([document.body,document.getElementsByTagName('html')[0]]).each(function(t)
110 {
111 Element.extend(t).setStyle(
112 {
113 height: myHeight,
114 overflow: myOverflow
115 });
116 });
117 }
118}); \ No newline at end of file
diff --git a/docroot/classes/roundcorners.class.js b/docroot/classes/roundcorners.class.js
new file mode 100755
index 0000000..c156d30
--- /dev/null
+++ b/docroot/classes/roundcorners.class.js
@@ -0,0 +1,60 @@
1/*
2 * Material Experience - Card Chip Class
3 *
4 * EYEMG - Interactive Media Group
5 * Created by Mike Crute (mcrute@eyemg.com)
6 * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07
7 *
8 * An HTML based approach to drawing rounded corners using element
9 * "slices" to create the corners instead of images. This depends
10 * on a section in the stylesheet to work correctly.
11 *
12 * Code adapted from: http://www.html.it/articoli/nifty/index.html
13 */
14
15var RoundedCorners = Class.create();
16Object.extend(RoundedCorners.prototype,
17{
18 // Direction table for rounded corners.
19 directions:
20 {
21 top : 'top',
22 bottom : 'bottom'
23 },
24
25 /*
26 * Initialize the object
27 */
28 initialize: function(color)
29 {
30 this.color = color;
31 },
32
33 /*
34 * Creates a rounded corner container.
35 */
36 get: function(direction)
37 {
38 var slicebox = document.createElement('b');
39 slicebox.className = 'round';
40
41 if (direction == this.directions.top)
42 {
43 var myEnum = $A($R(1,10));
44 }
45 else
46 {
47 var myEnum = $A($R(1,10)).reverse();
48 }
49
50 myEnum.each(function(item)
51 {
52 var corner = document.createElement('b');
53 corner.className = 'rcSlice_' + item;
54 corner.style.backgroundColor = this.color;
55 slicebox.appendChild(corner);
56 }.bind(this));
57
58 return slicebox;
59 }
60}); \ No newline at end of file
diff --git a/docroot/classes/sketchbook.class.js b/docroot/classes/sketchbook.class.js
new file mode 100755
index 0000000..d62101e
--- /dev/null
+++ b/docroot/classes/sketchbook.class.js
@@ -0,0 +1,288 @@
1/*
2 * Material Experience - Sketchbook Class
3 *
4 * EYEMG - Interactive Media Group
5 * Created by Mike Crute (mcrute@eyemg.com)
6 * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07
7 */
8
9var Sketchbook = Class.create();
10
11Object.extend(Sketchbook.prototype,
12{
13 /*
14 * Initializes a new sketchbook and table.
15 */
16 initialize: function()
17 {
18 // Sketchbook chip data store
19 this.dataStore = [];
20
21 this.table = new CardTable(
22 {
23 color: SME.colors.grey,
24 name: Strings.sketchbook,
25 id: 'sketchbook',
26
27 onRender: function(table)
28 {
29 table.table.appendChild(Builder.node('img',
30 {
31 id: 'sbTrash',
32 src: 'images/trash_can.jpg'
33 }));
34
35 Droppables.add($('sbTrash'),
36 {
37 accept: 'chip',
38
39 onDrop: function(chip)
40 {
41 this.removeChip(chip);
42 }.bind(this)
43 });
44 }.bind(this)
45 });
46
47 this.hide();
48
49 if (Sketchbook.loggedIn)
50 {
51 this.loadData();
52 }
53
54 // Be sure to save everything when the window closes
55 Event.observe(window, 'unload', this.saveData.bindAsEventListener(this));
56 },
57
58 /*
59 * Loads sketchbook data from the server and calls a function to merge
60 * that data into the currently loaded sketchbook.
61 */
62 loadData: function()
63 {
64 new Ajax.Request(SME.url.sketchBookIN,
65 {
66 method: 'get',
67
68 onSuccess: function(transport)
69 {
70 this.dataStore = transport.responseText.cleanJSON().evalJSON();
71 this._mergeSketchbooks();
72 }.bind(this)
73 });
74 },
75
76 /*
77 * Merges the currently active (presumably guest) sketchbook with the
78 * data loaded from the server. Prevents the user from losing data when
79 * they login after adding chips to a guest sketchbook.
80 */
81 _mergeSketchbooks: function()
82 {
83 this.dataStore.each(function(chip)
84 {
85 if (!this.table.hasChip(chip.cid))
86 {
87 this.table.addChip(chip, { animate: false });
88 }
89 }.bind(this));
90 },
91
92 /*
93 * Add a chip to the sketchbook. This function does duplicate detection
94 * using methods provided by the CardTable class to avoid adding a chip
95 * more than once. The function also updates the sketchbook's data
96 * store.
97 */
98 addChip: function(contID)
99 {
100 if (this.table.hasChip(contID))
101 {
102 new Bezel().show(Strings.alreadyAddedSB);
103 return;
104 }
105
106 new Ajax.Request(SME.url.chipResolver.evaluate({card: contID}),
107 {
108 method: 'get',
109
110 onSuccess: function(transport)
111 {
112 var data = transport.responseText.cleanJSON().evalJSON();
113 var windSize = window.getDimensions();
114
115 this.table.addChip(data,
116 {
117 x: Math.floor(1 + (windSize.width - 1) * Math.random()),
118 y: Math.floor(1 + ((windSize.height - 120) - 1) * Math.random()),
119 animate: false
120 });
121
122 // Per BS: Would like different messages if logged in or not
123 if (Sketchbook.loggedIn)
124 {
125 new Bezel().show(Strings.addedSketchbook);
126 }
127 else
128 {
129 new Bezel({ fadeTime: 5 }).show(Strings.addedGuestSB);
130 }
131
132 this.saveData();
133 }.bind(this)
134 });
135 },
136
137 /*
138 * Remove a chip from the table by DOM node.
139 */
140 removeChip: function(chip)
141 {
142 this.table.removeChip(chip.classLink.options.contID);
143 this.saveData();
144 },
145
146 /*
147 * Serialize the datastore and post it back to the server for safe
148 * keeping.
149 */
150 saveData: function()
151 {
152 new Ajax.Request(SME.url.sketchBook,
153 {
154 method: 'post',
155 asynchronous: false,
156 parameters: { 'sketchbook_data': this.table.getChipData().toJSON() }
157 });
158 },
159
160 /*
161 * Show the sketchbook table or if not logged in show a login screen.
162 */
163 show: function()
164 {
165 if (!Sketchbook.loggedIn)
166 {
167 Sketchbook.showLoginScreen();
168 }
169 else
170 {
171 this.table.show();
172 }
173 },
174
175 /*
176 * Hide the sketchbook table.
177 */
178 hide: function()
179 {
180 this.table.hide();
181 }
182});
183
184Object.extend(Sketchbook,
185{
186 // Flag to determine if the user is logged in or not.
187 loggedIn: false,
188
189 // Username of the logged in user.
190 username: null,
191
192 // Show the login screen in a special case card.
193 showLoginScreen: function()
194 {
195 var t = new Card(
196 {
197 color: SME.colors.grey,
198 title: Strings.pleaseLogin,
199 addExtraButtons: false,
200
201 onClose: function()
202 {
203 Sketchbook.checkLogin();
204 }
205 });
206
207 t.setLayout(Card.Layout.Special, { url: SME.url.loginScreen });
208 t.show();
209 },
210
211 /*
212 * Once the user has successfully logged in do some actions to setup
213 * the user interface.
214 */
215 doLoggedIn: function()
216 {
217 // Assume that if your calling this function the login succeeded
218 Sketchbook.loggedIn = true;
219
220 $$('div.history')[0].innerHTML = 'Hello ' + Sketchbook.username + '! | ' +
221 '<a href="http://santoprene.com/cgi-bin/protected/register/logout_designer.pl">' + Strings.logout + '</a> | ' +
222 '<a href="#" class="manage">' + Strings.manageAccount + '</a> | ' +
223 '<a href="#" class="history">' + Strings.myHistory + '</a>';
224
225 // On mouseover of the history link show the dropdown.
226 // We need to re-attach it here because we're changing the
227 // links (above) and that causes the DOM to lose the original
228 // event.
229 $$('div.history a.history')[0].observe('mouseover', function()
230 {
231 SME.history.getDropDown();
232 });
233
234 $$('a.manage')[0].observe('click', function(event)
235 {
236 var t = new Card(
237 {
238 color: SME.colors.grey,
239 addExtraButtons: false,
240 title: Strings.manageAccount
241 });
242
243 t.setLayout(Card.Layout.Special, { url: SME.url.manageAccount });
244 t.show();
245
246 Event.stop(event);
247 });
248 },
249
250 /*
251 * Check that the user actually logged in.
252 */
253 checkLogin: function()
254 {
255 // If the site cookie doesn't exist no point even going on
256 if (!$C('Site'))
257 {
258 return;
259 }
260
261 /*
262 * Site cookie is in a weird format, basically sub-cookies are
263 * separated by & signs. The username and password of the
264 * currently logged in user is store as username:password in
265 * the sub-cookie called cookie. Oh yeah and the
266 * username/password pair is base64 encoded.
267 */
268 var username = decodeBase64($C('Site').split('Cookie&')[1]).split(':')[0];
269 Sketchbook.username = username;
270
271 /*
272 * Per Andy: the username and password won't be in the cookie
273 * (even though the cookie itself is set) if the login failed.
274 * So lets assume that if we find a username in the appropriate
275 * place in the cookie we can go ahead and log the user in.
276 */
277 if (username)
278 {
279 Sketchbook.loggedIn = true;
280 Sketchbook.doLoggedIn();
281 return true;
282 }
283 else
284 {
285 return false;
286 }
287 }
288}); \ No newline at end of file
diff --git a/docroot/classes/sme.namespace.js b/docroot/classes/sme.namespace.js
new file mode 100755
index 0000000..05ad125
--- /dev/null
+++ b/docroot/classes/sme.namespace.js
@@ -0,0 +1,140 @@
1/*
2 * Material Experience - Santoprene Material Experience Namespace
3 *
4 * EYEMG - Interactive Media Group
5 * Created by Mike Crute (mcrute@eyemg.com)
6 * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07
7 *
8 * The Santoprene Materials Experience Namespace holds all non-generic data
9 * and methods governing the working of this application.
10 *
11 * There may be a lot of things set here that are used at runtime but I
12 * explicitly define them in the SME namespace so other developers can see
13 * exactly what is contained here. There are also some dynamically defined
14 * variables based on the current querystring.
15 */
16
17var SME =
18{
19 /*
20 * Release version of the site. This has nothing to do with the SVN
21 * version of the site. This is not used right now but should be
22 * incremented with each production release of the site.
23 */
24 releaseVersion: '1.2.2',
25
26 /*
27 * The color table exists to provide aliases to the colors used in the
28 * interface. This will make it much easier to change at a later date
29 * should that ever be required. The color table should be used only
30 * for default values.
31 */
32 colors:
33 {
34 blue : 'rgb(100, 129, 145)',
35 brown : 'rgb(100, 065, 040)',
36 green : 'rgb(100, 150, 040)',
37 grey : 'rgb(100, 100, 100)',
38 orange : 'rgb(225, 125, 010)',
39 red : 'rgb(150, 035, 020)'
40 },
41
42 /*
43 * The sizes table governs the size of the user interface elements.
44 * This exists for much the same reason as the color table, to provide
45 * an easy way to change sizes later. Note that the rest of the styles
46 * in the interface should be set relatively so changing these
47 * parameters should not destroy the interface.
48 */
49 sizes:
50 {
51 card : { height: 520, width: 925 }, // Card Size
52 innerCard : { height: 481, width: 914 }, // Inside Size of Card
53 chipMax : { height: 180, width: 143 }, // Maximum Chip Size
54 windMin : { height: 589, width: 1020 } // Minimum Window Size
55 },
56
57 // URLs Used by the Application
58 url:
59 {
60 tableList : 'data/card_tables.js',
61 perisitChips : 'data/persist_chip.js',
62 loginScreen : 'http://' + window.location.hostname + '/cgi-bin/login.pl',
63 sketchBook : 'http://' + window.location.hostname + '/cgi-bin/sketchbook.pl',
64 sketchBookIN : 'http://' + window.location.hostname + '/cgi-bin/sketchbook.pl?interactive=false',
65 manageAccount: 'http://www.santoprene.com/cgi-bin/protected/register/account.pl?template=designer_',
66 introCards : 'http://' + window.location.hostname + '/cgi-bin/sop_proxy.pl?site=http://www.santoprene.com/cms/designer_json/224fa06db73ece8a/index.html',
67 cards : new Template('http://' + window.location.hostname + '/cgi-bin/sop_proxy.pl?site=http://www.santoprene.com/cms/designer_card/#{card}/index.html'),
68 sendToFriend : new Template('http://www.santoprene.com/cgi-bin/send_page/send.pl?tmpl=designer&title=#{title}&durl=#{durl}'),
69 cardTables : new Template('http://' + window.location.hostname + '/cgi-bin/card_table.pl?table=#{table}&h=#{h}&w=#{w}'),
70 chipResolver : new Template('http://' + window.location.hostname + '/cgi-bin/sketchbook_resolver.pl?card=#{card}'),
71 cardPreview : new Template('http://' + window.location.hostname + '/cgi-bin/sop_preview_proxy.pl?site=http://admin.santoprene.com/cgi-bin/content/display_content.pl?form_id=48&content_id=#{card}&site_id=1'),
72
73 // Debugging and Development URLS (removed by build system)
74/*;;;*/ chipResolver : new Template('http://' + window.location.hostname + '/cgi-bin/sop_proxy.pl?site=http://materialexperience.santoprene.com/cgi-bin/sketchbook_resolver.pl?card=#{card}'),
75/*;;;*/ cardTables : new Template('http://' + window.location.hostname + '/cgi-bin/sop_proxy.pl?site=http://materialexperience.santoprene.com/cgi-bin/card_table.pl?table=#{table}&h=#{h}&w=#{w}'),
76 // END Debugging and Development URLS
77
78 // Dummy item terminates the list since the items above are
79 // removed by the build system and the trailing comma would
80 // break in IE. Don't Remove this!
81 dontRemove: null
82 },
83
84 /*
85 * Compose layout name to layout engine mapping. Allows us to add more
86 * layout engines later a lot simpler than the previous method allowed.
87 */
88 engineMapping :
89 {
90 'narrow-left' : Card.Layout.Primary,
91 'narrow-right' : Card.Layout.Primary,
92 'special' : Card.Layout.Special,
93 'intro' : Card.Layout.Primary,
94 'custom-menu' : Card.Layout.Custom
95 },
96
97 /*
98 * When the page is called with a query string of debug then you will
99 * be able to open a firebug console and view debugging messages. Also
100 * checks to make sure console.log() is actually defined to avoid
101 * throwing errors on browsers w/out firebug.
102 */
103 debug: /debug=.*(true)/.test(window.location.search) && !!(console.log),
104
105 /*
106 * Skip the intro card because it wastes time during development.
107 */
108 skipIntro: /.*intro=false.*/.test(window.location.search),
109
110 /*
111 *
112 * RUNTIME VARIABLES SECTION
113 *
114 * These are variables set at runtime but declared explicitly here
115 * so other developers can have a clear picture of what is in this
116 * namespace.
117 *
118 * If you set a variable in this namespace at runtime please declare
119 * it explicitly here.
120 *
121 */
122
123 // Array of all loaded tables.
124 tables: [],
125
126 // AJAX information bezel.
127 AJAXBezel: null,
128
129 // Currently active card table.
130 currentTable: null,
131
132 // Sketchbook.
133 sketchbook: null,
134
135 // History manager.
136 history: null,
137
138 // Card currently being displayed.
139 currentCard: null
140};
diff --git a/docroot/classes/table.class.js b/docroot/classes/table.class.js
new file mode 100755
index 0000000..1784c5c
--- /dev/null
+++ b/docroot/classes/table.class.js
@@ -0,0 +1,315 @@
1/*
2 * Material Experience - Card Table Class
3 *
4 * EYEMG - Interactive Media Group
5 * Created by Mike Crute (mcrute@eyemg.com)
6 * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07
7 */
8
9var CardTable = Class.create();
10
11Object.extend(CardTable.prototype,
12{
13 /*
14 * Create the table object.
15 */
16 initialize: function()
17 {
18 this.options = Object.extend(
19 {
20 name: 'New Table', // Table Name
21 color: SME.colors.blue, // Table Color
22 id: null, // Table ID
23 decorate: true, // Draw table decorator
24 onRender: Prototype.emptyFunction // Callback function when the table renders
25 }, arguments[0] || {});
26
27 // Cache of the chips on the table
28 this.chips = [];
29
30 // Cache of table JSON data
31 this.data = null;
32
33 this._createTable();
34
35 // Initially resizes the window to the correct height, and keep
36 // resizing it as the window changes
37 this.resize();
38 Event.observe(window, 'resize', function()
39 {
40 this.resize();
41 }.bindAsEventListener(this));
42
43 if (this.options.decorate)
44 {
45 this._createAppDecorator();
46 }
47 },
48
49 /*
50 * Create the table DOM object.
51 */
52 _createTable: function()
53 {
54 this.table = document.createElement('div');
55
56 if (this.options.id)
57 {
58 this.table.id = this.options.id;
59 }
60
61 this.table.className = 'table';
62 document.body.appendChild(this.table);
63
64 this.options.onRender(this);
65 },
66
67 /*
68 * Load the chip data from the server and cache it in the object.
69 */
70 loadChipData: function(feedURL, urlParams, showAfterLoad)
71 {
72 new Ajax.Request(feedURL.evaluate(urlParams),
73 {
74 method: 'get',
75
76 onSuccess: function(transport)
77 {
78 this.data = transport.responseText.evalJSON();
79
80 if (showAfterLoad || this.options.id == SME.currentTable)
81 this.showChips();
82 }.bind(this)
83 });
84 },
85
86 /*
87 * Creates the decorator icon show in the footer of the user interface
88 * which just links to a table hash and relies on the history manager
89 * to load the appropriate table.
90 */
91 _createAppDecorator: function()
92 {
93 var link = Builder.node('a',
94 {
95 href: '#table' + this.options.id,
96 style: 'color: ' + this.options.color,
97 alt: this.options.name + ' Table',
98 title: this.options.name + ' Table'
99 },
100 [
101 Builder.node('img',
102 {
103 className: 'appDecorator',
104 src: 'images/pill.gif',
105 style: 'background: ' + this.options.color
106 }),
107
108 this.options.name.toUpperCase()
109 ]);
110
111 $('footer').appendChild(link);
112 },
113
114 /*
115 * Show chips on the table once data is loaded. This function creates
116 * and caches chip objects on the table.
117 */
118 showChips: function()
119 {
120 // Only load the chips once
121 if (this.chipsDone || !this.data)
122 {
123 return;
124 }
125 else
126 {
127 this.chipsDone = true;
128 }
129
130 this.bez = new Bezel(
131 {
132 onShow: function()
133 {
134 this.data.each(function(chip)
135 {
136 this.addChip(chip);
137 }.bind(this));
138
139 this.chips.each(function(chip)
140 {
141 chip.animate();
142 });
143 }.bind(this)
144 }).show(Strings.loadingNoAnim);
145 },
146
147 /*
148 * Remove a chip from the table.
149 */
150 removeChip: function(chipContID)
151 {
152 this.chips.each(function(chip)
153 {
154 if (chip.options.contID == chipContID)
155 {
156 chip.hide();
157 this.chips = this.chips.without(chip);
158 }
159 }.bind(this))
160 },
161
162 /*
163 * Add a chip to the table.
164 */
165 addChip: function(data)
166 {
167 var cords = Object.extend(
168 {
169 x: data.x,
170 y: data.y,
171 animate: true
172 }, arguments[1] || {});
173
174 var chip = new CardChip(
175 {
176 x: cords.x,
177 y: cords.y,
178 category: data.category,
179 contID: data.cid,
180 title: data.title,
181 image: data.chip,
182 locked: data.locked || false,
183 animate: cords.animate
184 });
185
186 chip.addTo(this.table);
187 this.chips.push(chip);
188 },
189
190 /*
191 * Clear out the chip cache. This function is not to be used within
192 * the code. It exists to be called manually on the command line for
193 * debugging purposes.
194 */
195 _blowChips: function()
196 {
197 this.chips = [];
198 },
199
200 /*
201 * Serialize all the chip data on the table to an object, presumably
202 * to be converted to JSON later. The object should match the output
203 * of card_table.pl
204 */
205 getChipData: function()
206 {
207 var data = [];
208
209 this.chips.each(function(c)
210 {
211 data.push(c._serialize());
212 });
213
214 return data;
215 },
216
217 /*
218 * Tests if the table contains a chip with the particular content ID.
219 */
220 hasChip: function(chipID)
221 {
222 var chip = this.chips.find(function(item)
223 {
224 if (item.options.contID == chipID)
225 return true;
226
227 return false;
228 });
229
230 return chip ? true : false;
231 },
232
233 /*
234 * Changes the size of the table when the browser window is resized.
235 */
236 resize: function()
237 {
238 this.table.style.height = ($('footer').offsetTop -
239 $('footer').offsetHeight) + 'px';
240 },
241
242 /*
243 * Shows the table.
244 */
245 show: function()
246 {
247 this.showChips();
248 SME.currentTable = this.options.id;
249 this.table.style.display = 'block';
250 },
251
252 /*
253 * Hides the table.
254 */
255 hide: function()
256 {
257 this.table.style.display = 'none';
258 }
259});
260
261Object.extend(CardTable,
262{
263 /*
264 * Show a table and hide all others. Not the special exception for the
265 * sketchbook.
266 */
267 showTable: function(table)
268 {
269 SME.tables.each(function(item)
270 {
271 if (item.options.id == table)
272 {
273 item.show();
274 }
275 else
276 {
277 item.hide();
278 }
279 });
280
281 if (table == 'sketchbook')
282 {
283 SME.sketchbook.show();
284 }
285 else
286 {
287 SME.sketchbook.hide();
288 }
289 },
290
291 /*
292 * Return the color as a string for a table based on table ID. Note
293 * the special exception for special case cards which are not associated
294 * with a particular table.
295 */
296 tableColor: function(table)
297 {
298 if (table == 'special')
299 {
300 return SME.colors.grey;
301 }
302
303 var color = SME.tables.find(function(item)
304 {
305 if (item.options.id == table)
306 {
307 return true;
308 }
309
310 return false;
311 }).options.color;
312
313 return color;
314 }
315}); \ No newline at end of file
diff --git a/docroot/classes/utility.js b/docroot/classes/utility.js
new file mode 100755
index 0000000..218a059
--- /dev/null
+++ b/docroot/classes/utility.js
@@ -0,0 +1,199 @@
1/*
2 * Material Experience - Utility Functions
3 *
4 * EYEMG - Interactive Media Group
5 * Created by Mike Crute (mcrute@eyemg.com)
6 * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07
7 *
8 * Various utility functions that do not natively exist in any of
9 * the libraries we use.
10 *
11 * This is also the zoo for monkey patches. Be careful.
12 */
13
14Object.extend(window,
15{
16 /*
17 * Calculates the top and left offsets to center an element within the
18 * page. Every browser seems to approach this in a different fashion but
19 * this function works well on FX, OP, WK, and IE.
20 */
21 calcCordsToCenter: function(height, width)
22 {
23 var windowSize = window.getDimensions();
24
25 return {
26 top: Math.round(((windowSize.height - height) / 2)),
27 left: Math.round(((windowSize.width - width) / 2))
28 };
29 },
30
31 /*
32 * Gets the dimensions of the current window accounting for variations
33 * in browsers. Supports IE in either quirks or strict mode.
34 */
35 getDimensions: function()
36 {
37 var windowSize = {
38 height: document.documentElement.clientHeight,
39 width: document.documentElement.clientWidth
40 };
41
42 if (Prototype.Browser.Opera)
43 {
44 windowSize = {
45 height: document.body.clientHeight,
46 width: document.body.clientWidth
47 };
48 }
49
50 if (Prototype.Browser.WebKit)
51 {
52 windowSize = {
53 height: window.innerHeight,
54 width: window.innerWidth
55 };
56 }
57
58 return windowSize;
59 },
60
61 /*
62 * Verify the screen size is great than a set minimum or warn the user.
63 * Probably superfluous but why not.
64 */
65 checkResolution: function()
66 {
67 var size = window.getDimensions();
68
69 if (
70 size.height < SME.sizes.windMin.height ||
71 size.width < SME.sizes.windMin.width
72 )
73 {
74 new Bezel({ displayTime: 2 }).show(Strings.windowSize);
75 Event.stopObserving(window, 'resize', window.checkResolution);
76 }
77 }
78});
79
80Object.extend(String.prototype,
81{
82 /*
83 * Cleans up sloppy Compose JSON output by removing comments, newlines,
84 * and tabs.
85 */
86 cleanJSON: function()
87 {
88 var data = this;
89 data = data.replace(/^\/\/.*/gm,'');
90 data = data.replace(/(\n|\t|\r)/g,'');
91
92 return data;
93 }
94});
95
96Object.extend(Ajax.Request.prototype,
97{
98 /*
99 * Forces IE to enqueue XHR requests otherwise it gets bogged down and
100 * freezes the interface.
101 */
102 initialize: function(url, options)
103 {
104 this.transport = Ajax.getTransport();
105 this.setOptions(options);
106 this.url = url;
107
108 if (Prototype.Browser.IE)
109 {
110 Ajax.requestQueue.push(this);
111 Ajax._runQueue();
112 }
113 else
114 {
115 this.request(url);
116 }
117 },
118
119 /*
120 * Monkey patch prototype so it stops auto-evaluating the returned JSON
121 * data. Our JSON data is simply not clean enough for that and it throws
122 * errors all over the place.
123 *
124 * I've made as few modifications from the original as possible to ease
125 * in porting forward changes made in Prototype.
126 */
127 respondToReadyState: function(readyState)
128 {
129 var state = Ajax.Request.Events[readyState];
130 var transport = this.transport;
131
132 if (state == 'Complete')
133 {
134 try
135 {
136 this._complete = true;
137 (this.options['on' + this.transport.status]
138 || this.options['on' + (this.success() ? 'Success' : 'Failure')]
139 || Prototype.emptyFunction)(transport);
140 }
141 catch (e)
142 {
143 this.dispatchException(e);
144 }
145 }
146
147 try
148 {
149 (this.options['on' + state] || Prototype.emptyFunction)(transport);
150 Ajax.Responders.dispatch('on' + state, this, transport);
151 }
152 catch (e)
153 {
154 this.dispatchException(e);
155 }
156
157 if (state == 'Complete')
158 {
159 // avoid memory leak in MSIE: clean up
160 this.transport.onreadystatechange = Prototype.emptyFunction;
161 }
162 }
163});
164
165/*
166 * Implement AJAX request queueing. IE does not handle AJAX request concurrency
167 * very well and will freeze the entire browser interface if more than 2 calls
168 * are queued at a single time. We implement a global request queue and a queue
169 * runner that handles concurrent AJAX requests. The code is pretty straight
170 * forward but it also depends on modifications made to Ajax.Request.prototype.
171 */
172Object.extend(Ajax,
173{
174 // Global AJAX Request Queue
175 requestQueue: [],
176
177
178 /*
179 * Process events in the request queue.
180 */
181 _runQueue: function()
182 {
183 if (this.activeRequestCount > 1)
184 {
185 // Too many requests so we will try again later
186 new PeriodicalExecuter(function(executer)
187 {
188 this._runQueue();
189 executer.stop();
190 }.bind(this), 0.1);
191
192 return;
193 }
194
195 // Run the first request in the queue
196 var currentRequest = this.requestQueue.shift();
197 currentRequest.request(currentRequest.url);
198 }
199}); \ No newline at end of file
diff --git a/docroot/custom_content/2col.css b/docroot/custom_content/2col.css
new file mode 100755
index 0000000..4e368c2
--- /dev/null
+++ b/docroot/custom_content/2col.css
@@ -0,0 +1,68 @@
1/*
2 * Material Experience - 2 Column Custom Content Base Styles
3 *
4 * EYEMG - Interactive Media Group
5 * Created by Mike Crute (mcrute@eyemg.com)
6 * Updated by Mike Crute (mcrute@eyemg.com) on 10/21/07
7 */
8
9body
10{
11 font-family: Verdana, sans-serif;
12 font-size: 11px;
13 margin: 10px;
14 padding: 0px;
15}
16
17ul#navigation
18{
19 margin: 0px;
20 padding: 0px;
21}
22
23ul#navigation li
24{
25 margin: 5px 30px 0px 0px;
26 list-style: none;
27 font-weight: bolder;
28 border: 1px solid white;
29}
30
31ul#navigation li a
32{
33 display: block;
34 text-decoration: none;
35 padding: 2px;
36 border: 3px solid black;
37}
38
39ul#navigation li ul li a
40{
41 margin: 0px -60px 0px -30px;
42 font-weight: normal;
43}
44
45.selected
46{
47 background: black;
48 color: white;
49 padding: 5px;
50}
51
52.narrowCol, .wideCol
53{
54 float: left;
55 height: 460px;
56 overflow: hidden;
57}
58
59.narrowCol
60{
61 width: 290px;
62 margin-right: 20px;
63}
64
65.wideCol
66{
67 width: 569px;
68}
diff --git a/docroot/custom_content/2col.js b/docroot/custom_content/2col.js
new file mode 100755
index 0000000..3a91e4d
--- /dev/null
+++ b/docroot/custom_content/2col.js
@@ -0,0 +1,170 @@
1/*
2 * Material Experience - 2 Column Custom Content Script
3 *
4 * EYEMG - Interactive Media Group
5 * Created by Mike Crute (mcrute@eyemg.com)
6 * Updated by Mike Crute (mcrute@eyemg.com) on 10/21/07
7 */
8
9// Tracks the last ID requested
10var lastID = null;
11
12// Track the currently open nav item
13var curr = null;
14
15document.write('<script type="text/javascript" src="http://materialexperience.santoprene.com/lib/scriptaculous/scriptaculous.js"></script>');
16
17function doLoad()
18{
19 // Hide everything first
20 $$('ul#navigation li ul').each(function(item)
21 {
22 item.setStyle({ display: 'none' });
23 });
24
25 // Then attach the onclick events
26 $$('ul#navigation li').each(function(item)
27 {
28 Event.observe(item, 'click', function(event)
29 {
30 // Hide the currently opened item if it is clicked again
31 // per BS.
32 if (curr && item == curr)
33 {
34 curr.down('ul').hide();
35 item.setStyle({ borderLeft: '0px' });
36 curr = null;
37 return;
38 }
39
40 curr = item;
41
42 // Hide everything
43 $$('ul#navigation li ul').each(function(item)
44 {
45 item.setStyle({ display: 'none' });
46 });
47
48 // Clear out the borders
49 $$('ul#navigation li').each(function(item)
50 {
51 item.setStyle({ borderLeft: '' });
52 });
53
54 // Figure out what color we SHOULD be, this will change
55 // depending on the card color we are on so just rely
56 // on the designer to communicate through the CSS,
57 // probably not wise relying on a designer for code
58 // but alas...
59 var color = $$('ul#navigation li a')[0].getStyle('background-color');
60
61 // Catch and execute or just leave it lie
62 try
63 {
64 if (item.down('ul').style.display != 'none')
65 {
66 item.down('ul').hide();
67 Event.stop(event);
68 return;
69 }
70
71 item.down('ul').show();
72 item.setStyle({ borderLeft: '3px solid ' + color });
73 Event.stop(event);
74 }
75 catch (e)
76 {
77 // Just let the link do its thing
78 }
79 });
80 });
81}
82
83function locateContent(contID)
84{
85 // Hide the last ID from a global variable because going over each
86 // item and hiding it could take a prohibitively long time on a big
87 // page and we will most definitely be dealing with big pages here.
88 if (lastID)
89 {
90 $(lastID).className = "";
91 }
92
93 // Track the requested content ID
94 lastID = contID;
95
96 // The scroll top but subtract the height of the item itself or we
97 // always scroll to the bottom of items.
98 $('test').scrollTop = $(contID).offsetTop - $(contID).getHeight();
99 $(contID).className = "selected";
100}
101
102/*
103 * Create custom scroll bars for an HTML element.
104 */
105function scrollify(div)
106{
107 // Inherit the color from a link
108 var color = $$('ul#navigation li a')[0].getStyle('background-color');
109
110 // Create the scroll container and draggable widget
111 document.body.appendChild(
112 this.scrollCont = Builder.node('div',
113 {
114 style: 'border-left: 1px solid ' + color + ';' +
115 'width: 1px; position: absolute; ' +
116 'height:' + div.offsetHeight + 'px; ' +
117 'top: 10px;' + 'left: ' + (div.offsetLeft + div.offsetWidth + 12) + 'px;'
118 },
119
120 this.scrollBar = Builder.node('img',
121 {
122 src: 'http://materialexperience.santoprene.com/images/pill.gif',
123 style: 'display: block; margin-left: -3px; cursor: move; ' +
124 'background: ' + color + '; padding: 0px;'
125 })
126 ));
127
128 // Create the scroller
129 this.scroller = new Control.Slider(this.scrollBar, this.scrollCont,
130 {
131 axis: 'vertical',
132 range: $R(0, div.scrollHeight),
133
134 onSlide: function(value)
135 {
136 div.scrollTop = Math.floor(value);
137 }.bind(this)
138 });
139
140 new PeriodicalExecuter(function()
141 {
142 // Update the scroller range when the contents change
143 this.scroller.range = $R(0, div.scrollHeight);
144
145 // Move the scroller when the scrolled element changes
146 // (e.g. linking down into the page).
147 if (this.scroller.value != div.scrollTop)
148 {
149 this.scroller.setValue(div.scrollTop);
150 }
151 }.bind(this), 1);
152}
153
154/*
155 * Attach scrollbars to the narrow column and the wide column if they exist.
156 */
157Event.observe(window, 'load', function()
158{
159 if ($$('.narrowCol')[0])
160 {
161 new scrollify($$('.narrowCol')[0]);
162 }
163
164 if ($$('.wideCol')[0])
165 {
166 new scrollify($$('.wideCol')[0]);
167 }
168});
169
170Event.observe(window, 'load', doLoad); \ No newline at end of file
diff --git a/docroot/data/card_tables.js b/docroot/data/card_tables.js
new file mode 100755
index 0000000..6b33ed7
--- /dev/null
+++ b/docroot/data/card_tables.js
@@ -0,0 +1,63 @@
1/*
2 * Material Experience - Card Tables Data File
3 *
4 * EYEMG - Interactive Media Group
5 * Created by Mike Crute (mcrute@eyemg.com)
6 * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07
7 *
8 * Data file for the card tables. This allows us the flexibility
9 * to add or remove tables at a later date. Note that each table
10 * id should correspond to a compose card type ID otherwise
11 * no data will be loaded.
12 */
13
14[
15 {
16 tid: 'home',
17 name: 'Home',
18 color: SME.colors.grey,
19 decorate: true
20 },
21
22 {
23 tid: 'inspiration',
24 name: 'Inspiration',
25 color: SME.colors.brown,
26 decorate: true
27 },
28
29 {
30 tid: 'exploration',
31 name: 'Exploration',
32 color: SME.colors.red,
33 decorate: true
34 },
35
36 {
37 tid: 'close-up',
38 name: 'Close-Up',
39 color: SME.colors.orange,
40 decorate: true
41 },
42
43 {
44 tid: 'making_it',
45 name: 'Making It',
46 color: SME.colors.blue,
47 decorate: true
48 },
49
50 {
51 tid: 'info-samples',
52 name: 'Info & Samples',
53 color: SME.colors.green,
54 decorate: true
55 },
56
57 {
58 tid: 'preview',
59 name: 'Preview',
60 color: SME.colors.grey,
61 decorate: false
62 }
63] \ No newline at end of file
diff --git a/docroot/data/persist_chips.js b/docroot/data/persist_chips.js
new file mode 100755
index 0000000..84c3e59
--- /dev/null
+++ b/docroot/data/persist_chips.js
@@ -0,0 +1,3 @@
1[
2
3] \ No newline at end of file
diff --git a/docroot/data/strings.en.js b/docroot/data/strings.en.js
new file mode 100755
index 0000000..c9a1faf
--- /dev/null
+++ b/docroot/data/strings.en.js
@@ -0,0 +1,122 @@
1/*
2 * Material Experience - English Strings File
3 *
4 * EYEMG - Interactive Media Group
5 * Created by Mike Crute (mcrute@eyemg.com) on 9/26/07
6 * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07
7 *
8 * This strings file is the foundation for internationalization of the
9 * Material Experience website. These strings can be changed to anything
10 * and those changes will be reflected in the interface. Be careful, you
11 * can also potentially seriously break things. Follow the rules and
12 * comments.
13 *
14 * Note that as of this writing full internationalization is not fully
15 * implemented and won't be till the client requests it, I anticipate
16 * this happening some day so here we are with a strings file.
17 *
18 * Rules:
19 * Quoting -- if you need to use quotes, either single or double make sure
20 * you vary your quoting, 'this won't' work you need "this'll work"
21 *
22 * HTML -- some strings can contain HTML, don't go crazy, constrain your
23 * HTML to line breaks or you will breaks and maybe images or
24 * you will break things.
25 *
26 * Doubt -- if your not sure what effect your changes will have ASK SOMEBODY
27 * don't just make changes and hope things work
28 *
29 * Unicode -- sure, you can use unicode, just make sure you save the file with
30 * the appropriate character set. No BOM please.
31 *
32 * Comment -- leave the comments alone, and change the updated by comment at the
33 * top of this file when you make changes.
34 */
35
36var Strings =
37{
38 //
39 // LANGUAGE FILE METADATA
40 //
41 // Language of the String File and version of the site this is for
42 // Neither of these are used yet but set them anyhow since they
43 // may be used later on. Use C-style locale codes for the language.
44 //
45 // Stars are perfectly acceptable in the version field but will only
46 // be matched to 3 places.
47 language : 'en_us',
48 version : '1.*',
49
50 //
51 // APPLICATION TITLE
52 //
53 // Displayed in the browser title bar
54 appTitle: 'Material Experience',
55
56 //
57 // WINDOW SIZE ERROR
58 //
59 // Displayed once when the window size is smaller than the minimum size
60 windowSize : "Your window size is smaller than the recommended 1024x768 resolution.<br/>" +
61 "Some items may be positioned outside the viewable area.",
62
63 //
64 // DATA LOADERS
65 //
66 // Displayed whenever an AJAX call is made and while it is waiting to load
67 loadingNoAnim : "Loading Data...",
68 loadingAnim : "Loading Data <img src='images/loader.gif'/>",
69
70 //
71 // PRINTING ERRORS
72 //
73 cantPrint : "Sorry Can't Print", // Displayed if the browser doesn't support printing
74 printClose : "Do you want to close this window?", // Displayed before we auto/close the print window
75
76 //
77 // CARD DATA ERRORS
78 //
79 // Displayed within a card when it fails to load correctly
80 cardErrorTitle : "Card Error",
81 cardErrorText : "We're very sorry, there was an error loading this card. Please try again later.",
82
83 //
84 // AJAX ERRORS
85 //
86 // Displayed when an AJAX call fails.
87 ajaxError : "Some Data Could Not Be Loaded.<br/> We're sorry, please refresh this page.",
88
89 //
90 // CARD BUTTON LABELS
91 //
92 // Labels shown next to the buttons in the card
93 addToSketchbook : "Add to Sketchbook",
94 sendToFriend : "Send to a Friend",
95 printCard : "Print this Card",
96 myHistory : "My History",
97 closeCard : "Close Card",
98
99 //
100 // CARD CONTENT
101 //
102 // Static content within the card that is hard-coded
103 moreInfo : "More Information",
104 skipButton : "Skip this Card",
105
106 //
107 // SKETCHBOOK ERRORS AND CONFIRMATIONS
108 //
109 // Confirmations and errors for sketchbook actions
110 sketchbook : "Sketchbook",
111 alreadyAddedSB : "Card already exists in your sketchbook.",
112 addedSketchbook : "Added to Sketchbook",
113 addedGuestSB : "Added to Guest Sketchbook<br/>Please visit the sketchbook to log in and save your items.",
114
115 //
116 // UTILITY BOX LINKS
117 //
118 // Links at the top right of the interface.
119 pleaseLogin : "Please Log In",
120 manageAccount : "Manage My Account",
121 logout : "Log Out"
122}; \ No newline at end of file
diff --git a/docroot/favicon.ico b/docroot/favicon.ico
new file mode 100755
index 0000000..32168fd
--- /dev/null
+++ b/docroot/favicon.ico
Binary files differ
diff --git a/docroot/graphics/chip.psd b/docroot/graphics/chip.psd
new file mode 100755
index 0000000..0f0828e
--- /dev/null
+++ b/docroot/graphics/chip.psd
Binary files differ
diff --git a/docroot/images/arrow_down.gif b/docroot/images/arrow_down.gif
new file mode 100755
index 0000000..7395d65
--- /dev/null
+++ b/docroot/images/arrow_down.gif
Binary files differ
diff --git a/docroot/images/arrow_right_grey.gif b/docroot/images/arrow_right_grey.gif
new file mode 100755
index 0000000..bd0a087
--- /dev/null
+++ b/docroot/images/arrow_right_grey.gif
Binary files differ
diff --git a/docroot/images/blank.gif b/docroot/images/blank.gif
new file mode 100755
index 0000000..75b945d
--- /dev/null
+++ b/docroot/images/blank.gif
Binary files differ
diff --git a/docroot/images/close.gif b/docroot/images/close.gif
new file mode 100755
index 0000000..b19fbac
--- /dev/null
+++ b/docroot/images/close.gif
Binary files differ
diff --git a/docroot/images/email.gif b/docroot/images/email.gif
new file mode 100755
index 0000000..5d18b3c
--- /dev/null
+++ b/docroot/images/email.gif
Binary files differ
diff --git a/docroot/images/history.gif b/docroot/images/history.gif
new file mode 100755
index 0000000..48347c0
--- /dev/null
+++ b/docroot/images/history.gif
Binary files differ
diff --git a/docroot/images/loader.gif b/docroot/images/loader.gif
new file mode 100755
index 0000000..cb91da8
--- /dev/null
+++ b/docroot/images/loader.gif
Binary files differ
diff --git a/docroot/images/logo.gif b/docroot/images/logo.gif
new file mode 100755
index 0000000..7e8779b
--- /dev/null
+++ b/docroot/images/logo.gif
Binary files differ
diff --git a/docroot/images/pill.gif b/docroot/images/pill.gif
new file mode 100755
index 0000000..cc96b81
--- /dev/null
+++ b/docroot/images/pill.gif
Binary files differ
diff --git a/docroot/images/plus.gif b/docroot/images/plus.gif
new file mode 100755
index 0000000..95eca20
--- /dev/null
+++ b/docroot/images/plus.gif
Binary files differ
diff --git a/docroot/images/print.gif b/docroot/images/print.gif
new file mode 100755
index 0000000..3668f6a
--- /dev/null
+++ b/docroot/images/print.gif
Binary files differ
diff --git a/docroot/images/tagline.png b/docroot/images/tagline.png
new file mode 100755
index 0000000..bda7a1d
--- /dev/null
+++ b/docroot/images/tagline.png
Binary files differ
diff --git a/docroot/images/trash_can.jpg b/docroot/images/trash_can.jpg
new file mode 100755
index 0000000..5801655
--- /dev/null
+++ b/docroot/images/trash_can.jpg
Binary files differ
diff --git a/docroot/index.html b/docroot/index.html
new file mode 100755
index 0000000..d14eedc
--- /dev/null
+++ b/docroot/index.html
@@ -0,0 +1,45 @@
1<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
3<!--
4 * Material Experience - Index Page
5 *
6 * EYEMG - Interactive Media Group
7 * Created by Mike Crute (mcrute@eyemg.com)
8 * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07
9-->
10 <head>
11 <title>Material Experience</title>
12 <style type="text/css">@import url('application.css');</style>
13 </head>
14
15 <body>
16 <img src="blank.gif" alt="Loading" style="display: none" />
17
18 <div id="header">
19 <a href="#tablehome"><img id="logo" src="images/logo.gif" alt="Material Experience Logo" title="Material Experience Logo" /></a>
20 <img src="images/tagline.png" id="tagline" alt="An ExxonMobil Chemical Initiative" title="An ExxonMobil Chemical Initiative" />
21 <div class="history" style="display: none;">
22 <a id="loginLink" href="#">Log In/Register</a> |
23 <a href="#" class="history">My Card History</a>
24 </div>
25 </div>
26
27 <noscript>It would appear that your browser does not have Javascript support. You will need to enable Javascript or upgrade your browser in order to view this site.</noscript>
28 <div style="display: none">It would appear that your browser doesn't have CSS support. You will need to enable CSS or upgrade your browser in order to view this site.</div>
29
30 <ul id="history"></ul>
31
32 <div id="footer">
33 <p id="version"></p>
34 <p id="copyright">
35 &copy; #### Advanced Elastomer Systems, LP, an ExxonMobil Chemical affiliate.
36 <a href="http://www.santoprene.com/Privacy" target="_blank">Privacy</a>,
37 <a href="http://www.santoprene.com/Terms" target="_blank">Terms</a> and
38 <a href="http://www.santoprene.com/Legal" target="_blank">Legal</a>
39 </p>
40 </div>
41 <a href="http://www.santoprene.com/cms/designer_seo/d79655481a900999/index.html" style="display: none">Search Engine Version</a>
42
43 <script type="text/javascript" src="application.js"></script>
44 </body>
45</html>
diff --git a/docroot/lib/firebug/errorIcon.png b/docroot/lib/firebug/errorIcon.png
new file mode 100755
index 0000000..2d75261
--- /dev/null
+++ b/docroot/lib/firebug/errorIcon.png
Binary files differ
diff --git a/docroot/lib/firebug/firebug.css b/docroot/lib/firebug/firebug.css
new file mode 100755
index 0000000..1f041c4
--- /dev/null
+++ b/docroot/lib/firebug/firebug.css
@@ -0,0 +1,209 @@
1
2html, body {
3 margin: 0;
4 background: #FFFFFF;
5 font-family: Lucida Grande, Tahoma, sans-serif;
6 font-size: 11px;
7 overflow: hidden;
8}
9
10a {
11 text-decoration: none;
12}
13
14a:hover {
15 text-decoration: underline;
16}
17
18.toolbar {
19 height: 14px;
20 border-top: 1px solid ThreeDHighlight;
21 border-bottom: 1px solid ThreeDShadow;
22 padding: 2px 6px;
23 background: ThreeDFace;
24}
25
26.toolbarRight {
27 position: absolute;
28 top: 4px;
29 right: 6px;
30}
31
32#log {
33 overflow: auto;
34 position: absolute;
35 left: 0;
36 width: 100%;
37}
38
39#commandLine {
40 position: absolute;
41 bottom: 0;
42 left: 0;
43 width: 100%;
44 height: 18px;
45 border: none;
46 border-top: 1px solid ThreeDShadow;
47}
48
49/************************************************************************************************/
50
51.logRow {
52 position: relative;
53 border-bottom: 1px solid #D7D7D7;
54 padding: 2px 4px 1px 6px;
55 background-color: #FFFFFF;
56}
57
58.logRow-command {
59 font-family: Monaco, monospace;
60 color: blue;
61}
62
63.objectBox-null {
64 padding: 0 2px;
65 border: 1px solid #666666;
66 background-color: #888888;
67 color: #FFFFFF;
68}
69
70.objectBox-string {
71 font-family: Monaco, monospace;
72 color: red;
73 white-space: pre;
74}
75
76.objectBox-number {
77 color: #000088;
78}
79
80.objectBox-function {
81 font-family: Monaco, monospace;
82 color: DarkGreen;
83}
84
85.objectBox-object {
86 color: DarkGreen;
87 font-weight: bold;
88}
89
90/************************************************************************************************/
91
92.logRow-info,
93.logRow-error,
94.logRow-warning {
95 background: #FFFFFF no-repeat 2px 2px;
96 padding-left: 20px;
97 padding-bottom: 3px;
98}
99
100.logRow-info {
101 background-image: url(infoIcon.png);
102}
103
104.logRow-warning {
105 background-color: cyan;
106 background-image: url(warningIcon.png);
107}
108
109.logRow-error {
110 background-color: LightYellow;
111 background-image: url(errorIcon.png);
112}
113
114.errorMessage {
115 vertical-align: top;
116 color: #FF0000;
117}
118
119.objectBox-sourceLink {
120 position: absolute;
121 right: 4px;
122 top: 2px;
123 padding-left: 8px;
124 font-family: Lucida Grande, sans-serif;
125 font-weight: bold;
126 color: #0000FF;
127}
128
129/************************************************************************************************/
130
131.logRow-group {
132 background: #EEEEEE;
133 border-bottom: none;
134}
135
136.logGroup {
137 background: #EEEEEE;
138}
139
140.logGroupBox {
141 margin-left: 24px;
142 border-top: 1px solid #D7D7D7;
143 border-left: 1px solid #D7D7D7;
144}
145
146/************************************************************************************************/
147
148.selectorTag,
149.selectorId,
150.selectorClass {
151 font-family: Monaco, monospace;
152 font-weight: normal;
153}
154
155.selectorTag {
156 color: #0000FF;
157}
158
159.selectorId {
160 color: DarkBlue;
161}
162
163.selectorClass {
164 color: red;
165}
166
167/************************************************************************************************/
168
169.objectBox-element {
170 font-family: Monaco, monospace;
171 color: #000088;
172}
173
174.nodeChildren {
175 margin-left: 16px;
176}
177
178.nodeTag {
179 color: blue;
180}
181
182.nodeValue {
183 color: #FF0000;
184 font-weight: normal;
185}
186
187.nodeText,
188.nodeComment {
189 margin: 0 2px;
190 vertical-align: top;
191}
192
193.nodeText {
194 color: #333333;
195}
196
197.nodeComment {
198 color: DarkGreen;
199}
200
201/************************************************************************************************/
202
203.propertyNameCell {
204 vertical-align: top;
205}
206
207.propertyName {
208 font-weight: bold;
209}
diff --git a/docroot/lib/firebug/firebug.html b/docroot/lib/firebug/firebug.html
new file mode 100755
index 0000000..861e639
--- /dev/null
+++ b/docroot/lib/firebug/firebug.html
@@ -0,0 +1,23 @@
1<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
2 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3
4<html xmlns="http://www.w3.org/1999/xhtml">
5
6<head>
7 <title>Firebug</title>
8 <link rel="stylesheet" type="text/css" href="firebug.css">
9</head>
10
11<body>
12 <div id="toolbar" class="toolbar">
13 <a href="#" onclick="parent.console.clear()">Clear</a>
14 <span class="toolbarRight">
15 <a href="#" onclick="parent.console.close()">Close</a>
16 </span>
17 </div>
18 <div id="log"></div>
19 <input type="text" id="commandLine">
20
21 <script>parent.onFirebugReady(document);</script>
22</body>
23</html>
diff --git a/docroot/lib/firebug/firebug.js b/docroot/lib/firebug/firebug.js
new file mode 100755
index 0000000..eb853b8
--- /dev/null
+++ b/docroot/lib/firebug/firebug.js
@@ -0,0 +1,672 @@
1
2if (!("console" in window) || !("firebug" in console)) {
3(function()
4{
5 window.console =
6 {
7 log: function()
8 {
9 logFormatted(arguments, "");
10 },
11
12 debug: function()
13 {
14 logFormatted(arguments, "debug");
15 },
16
17 info: function()
18 {
19 logFormatted(arguments, "info");
20 },
21
22 warn: function()
23 {
24 logFormatted(arguments, "warning");
25 },
26
27 error: function()
28 {
29 logFormatted(arguments, "error");
30 },
31
32 assert: function(truth, message)
33 {
34 if (!truth)
35 {
36 var args = [];
37 for (var i = 1; i < arguments.length; ++i)
38 args.push(arguments[i]);
39
40 logFormatted(args.length ? args : ["Assertion Failure"], "error");
41 throw message ? message : "Assertion Failure";
42 }
43 },
44
45 dir: function(object)
46 {
47 var html = [];
48
49 var pairs = [];
50 for (var name in object)
51 {
52 try
53 {
54 pairs.push([name, object[name]]);
55 }
56 catch (exc)
57 {
58 }
59 }
60
61 pairs.sort(function(a, b) { return a[0] < b[0] ? -1 : 1; });
62
63 html.push('<table>');
64 for (var i = 0; i < pairs.length; ++i)
65 {
66 var name = pairs[i][0], value = pairs[i][1];
67
68 html.push('<tr>',
69 '<td class="propertyNameCell"><span class="propertyName">',
70 escapeHTML(name), '</span></td>', '<td><span class="propertyValue">');
71 appendObject(value, html);
72 html.push('</span></td></tr>');
73 }
74 html.push('</table>');
75
76 logRow(html, "dir");
77 },
78
79 dirxml: function(node)
80 {
81 var html = [];
82
83 appendNode(node, html);
84 logRow(html, "dirxml");
85 },
86
87 group: function()
88 {
89 logRow(arguments, "group", pushGroup);
90 },
91
92 groupEnd: function()
93 {
94 logRow(arguments, "", popGroup);
95 },
96
97 time: function(name)
98 {
99 timeMap[name] = (new Date()).getTime();
100 },
101
102 timeEnd: function(name)
103 {
104 if (name in timeMap)
105 {
106 var delta = (new Date()).getTime() - timeMap[name];
107 logFormatted([name+ ":", delta+"ms"]);
108 delete timeMap[name];
109 }
110 },
111
112 count: function()
113 {
114 this.warn(["count() not supported."]);
115 },
116
117 trace: function()
118 {
119 this.warn(["trace() not supported."]);
120 },
121
122 profile: function()
123 {
124 this.warn(["profile() not supported."]);
125 },
126
127 profileEnd: function()
128 {
129 },
130
131 clear: function()
132 {
133 consoleBody.innerHTML = "";
134 },
135
136 open: function()
137 {
138 toggleConsole(true);
139 },
140
141 close: function()
142 {
143 if (frameVisible)
144 toggleConsole();
145 }
146 };
147
148 // ********************************************************************************************
149
150 var consoleFrame = null;
151 var consoleBody = null;
152 var commandLine = null;
153
154 var frameVisible = false;
155 var messageQueue = [];
156 var groupStack = [];
157 var timeMap = {};
158
159 var clPrefix = ">>> ";
160
161 var isFirefox = navigator.userAgent.indexOf("Firefox") != -1;
162 var isIE = navigator.userAgent.indexOf("MSIE") != -1;
163 var isOpera = navigator.userAgent.indexOf("Opera") != -1;
164 var isSafari = navigator.userAgent.indexOf("AppleWebKit") != -1;
165
166 // ********************************************************************************************
167
168 function toggleConsole(forceOpen)
169 {
170 frameVisible = forceOpen || !frameVisible;
171 if (consoleFrame)
172 consoleFrame.style.visibility = frameVisible ? "visible" : "hidden";
173 else
174 waitForBody();
175 }
176
177 function focusCommandLine()
178 {
179 toggleConsole(true);
180 if (commandLine)
181 commandLine.focus();
182 }
183
184 function waitForBody()
185 {
186 if (document.body)
187 createFrame();
188 else
189 setTimeout(waitForBody, 200);
190 }
191
192 function createFrame()
193 {
194 if (consoleFrame)
195 return;
196
197 window.onFirebugReady = function(doc)
198 {
199 window.onFirebugReady = null;
200
201 var toolbar = doc.getElementById("toolbar");
202 toolbar.onmousedown = onSplitterMouseDown;
203
204 commandLine = doc.getElementById("commandLine");
205 addEvent(commandLine, "keydown", onCommandLineKeyDown);
206
207 addEvent(doc, isIE || isSafari ? "keydown" : "keypress", onKeyDown);
208
209 consoleBody = doc.getElementById("log");
210 layout();
211 flush();
212 }
213
214 var baseURL = getFirebugURL();
215
216 consoleFrame = document.createElement("iframe");
217 consoleFrame.setAttribute("src", baseURL+"/firebug.html");
218 consoleFrame.setAttribute("frameBorder", "0");
219 consoleFrame.style.visibility = (frameVisible ? "visible" : "hidden");
220 consoleFrame.style.zIndex = "2147483647";
221 consoleFrame.style.position = "fixed";
222 consoleFrame.style.width = "100%";
223 consoleFrame.style.left = "0";
224 consoleFrame.style.bottom = "0";
225 consoleFrame.style.height = "200px";
226 document.body.appendChild(consoleFrame);
227 }
228
229 function getFirebugURL()
230 {
231 var scripts = document.getElementsByTagName("script");
232 for (var i = 0; i < scripts.length; ++i)
233 {
234 if (scripts[i].src.indexOf("firebug.js") != -1)
235 {
236 var lastSlash = scripts[i].src.lastIndexOf("/");
237 return scripts[i].src.substr(0, lastSlash);
238 }
239 }
240 }
241
242 function evalCommandLine()
243 {
244 var text = commandLine.value;
245 commandLine.value = "";
246
247 logRow([clPrefix, text], "command");
248
249 var value;
250 try
251 {
252 value = eval(text);
253 }
254 catch (exc)
255 {
256 }
257
258 console.log(value);
259 }
260
261 function layout()
262 {
263 var toolbar = consoleBody.ownerDocument.getElementById("toolbar");
264 var height = consoleFrame.offsetHeight - (toolbar.offsetHeight + commandLine.offsetHeight);
265 consoleBody.style.top = toolbar.offsetHeight + "px";
266 consoleBody.style.height = height + "px";
267
268 commandLine.style.top = (consoleFrame.offsetHeight - commandLine.offsetHeight) + "px";
269 }
270
271 function logRow(message, className, handler)
272 {
273 if (consoleBody)
274 writeMessage(message, className, handler);
275 else
276 {
277 messageQueue.push([message, className, handler]);
278 waitForBody();
279 }
280 }
281
282 function flush()
283 {
284 var queue = messageQueue;
285 messageQueue = [];
286
287 for (var i = 0; i < queue.length; ++i)
288 writeMessage(queue[i][0], queue[i][1], queue[i][2]);
289 }
290
291 function writeMessage(message, className, handler)
292 {
293 var isScrolledToBottom =
294 consoleBody.scrollTop + consoleBody.offsetHeight >= consoleBody.scrollHeight;
295
296 if (!handler)
297 handler = writeRow;
298
299 handler(message, className);
300
301 if (isScrolledToBottom)
302 consoleBody.scrollTop = consoleBody.scrollHeight - consoleBody.offsetHeight;
303 }
304
305 function appendRow(row)
306 {
307 var container = groupStack.length ? groupStack[groupStack.length-1] : consoleBody;
308 container.appendChild(row);
309 }
310
311 function writeRow(message, className)
312 {
313 var row = consoleBody.ownerDocument.createElement("div");
314 row.className = "logRow" + (className ? " logRow-"+className : "");
315 row.innerHTML = message.join("");
316 appendRow(row);
317 }
318
319 function pushGroup(message, className)
320 {
321 logFormatted(message, className);
322
323 var groupRow = consoleBody.ownerDocument.createElement("div");
324 groupRow.className = "logGroup";
325 var groupRowBox = consoleBody.ownerDocument.createElement("div");
326 groupRowBox.className = "logGroupBox";
327 groupRow.appendChild(groupRowBox);
328 appendRow(groupRowBox);
329 groupStack.push(groupRowBox);
330 }
331
332 function popGroup()
333 {
334 groupStack.pop();
335 }
336
337 // ********************************************************************************************
338
339 function logFormatted(objects, className)
340 {
341 var html = [];
342
343 var format = objects[0];
344 var objIndex = 0;
345
346 if (typeof(format) != "string")
347 {
348 format = "";
349 objIndex = -1;
350 }
351
352 var parts = parseFormat(format);
353 for (var i = 0; i < parts.length; ++i)
354 {
355 var part = parts[i];
356 if (part && typeof(part) == "object")
357 {
358 var object = objects[++objIndex];
359 part.appender(object, html);
360 }
361 else
362 appendText(part, html);
363 }
364
365 for (var i = objIndex+1; i < objects.length; ++i)
366 {
367 appendText(" ", html);
368
369 var object = objects[i];
370 if (typeof(object) == "string")
371 appendText(object, html);
372 else
373 appendObject(object, html);
374 }
375
376 logRow(html, className);
377 }
378
379 function parseFormat(format)
380 {
381 var parts = [];
382
383 var reg = /((^%|[^\\]%)(\d+)?(\.)([a-zA-Z]))|((^%|[^\\]%)([a-zA-Z]))/;
384 var appenderMap = {s: appendText, d: appendInteger, i: appendInteger, f: appendFloat};
385
386 for (var m = reg.exec(format); m; m = reg.exec(format))
387 {
388 var type = m[8] ? m[8] : m[5];
389 var appender = type in appenderMap ? appenderMap[type] : appendObject;
390 var precision = m[3] ? parseInt(m[3]) : (m[4] == "." ? -1 : 0);
391
392 parts.push(format.substr(0, m[0][0] == "%" ? m.index : m.index+1));
393 parts.push({appender: appender, precision: precision});
394
395 format = format.substr(m.index+m[0].length);
396 }
397
398 parts.push(format);
399
400 return parts;
401 }
402
403 function escapeHTML(value)
404 {
405 function replaceChars(ch)
406 {
407 switch (ch)
408 {
409 case "<":
410 return "&lt;";
411 case ">":
412 return "&gt;";
413 case "&":
414 return "&amp;";
415 case "'":
416 return "&#39;";
417 case '"':
418 return "&quot;";
419 }
420 return "?";
421 };
422 return String(value).replace(/[<>&"']/g, replaceChars);
423 }
424
425 function objectToString(object)
426 {
427 try
428 {
429 return object+"";
430 }
431 catch (exc)
432 {
433 return null;
434 }
435 }
436
437 // ********************************************************************************************
438
439 function appendText(object, html)
440 {
441 html.push(escapeHTML(objectToString(object)));
442 }
443
444 function appendNull(object, html)
445 {
446 html.push('<span class="objectBox-null">', escapeHTML(objectToString(object)), '</span>');
447 }
448
449 function appendString(object, html)
450 {
451 html.push('<span class="objectBox-string">&quot;', escapeHTML(objectToString(object)),
452 '&quot;</span>');
453 }
454
455 function appendInteger(object, html)
456 {
457 html.push('<span class="objectBox-number">', escapeHTML(objectToString(object)), '</span>');
458 }
459
460 function appendFloat(object, html)
461 {
462 html.push('<span class="objectBox-number">', escapeHTML(objectToString(object)), '</span>');
463 }
464
465 function appendFunction(object, html)
466 {
467 var reName = /function ?(.*?)\(/;
468 var m = reName.exec(objectToString(object));
469 var name = m ? m[1] : "function";
470 html.push('<span class="objectBox-function">', escapeHTML(name), '()</span>');
471 }
472
473 function appendObject(object, html)
474 {
475 try
476 {
477 if (object == undefined)
478 appendNull("undefined", html);
479 else if (object == null)
480 appendNull("null", html);
481 else if (typeof object == "string")
482 appendString(object, html);
483 else if (typeof object == "number")
484 appendInteger(object, html);
485 else if (typeof object == "function")
486 appendFunction(object, html);
487 else if (object.nodeType == 1)
488 appendSelector(object, html);
489 else if (typeof object == "object")
490 appendObjectFormatted(object, html);
491 else
492 appendText(object, html);
493 }
494 catch (exc)
495 {
496 }
497 }
498
499 function appendObjectFormatted(object, html)
500 {
501 var text = objectToString(object);
502 var reObject = /\[object (.*?)\]/;
503
504 var m = reObject.exec(text);
505 html.push('<span class="objectBox-object">', m ? m[1] : text, '</span>')
506 }
507
508 function appendSelector(object, html)
509 {
510 html.push('<span class="objectBox-selector">');
511
512 html.push('<span class="selectorTag">', escapeHTML(object.nodeName.toLowerCase()), '</span>');
513 if (object.id)
514 html.push('<span class="selectorId">#', escapeHTML(object.id), '</span>');
515 if (object.className)
516 html.push('<span class="selectorClass">.', escapeHTML(object.className), '</span>');
517
518 html.push('</span>');
519 }
520
521 function appendNode(node, html)
522 {
523 if (node.nodeType == 1)
524 {
525 html.push(
526 '<div class="objectBox-element">',
527 '&lt;<span class="nodeTag">', node.nodeName.toLowerCase(), '</span>');
528
529 for (var i = 0; i < node.attributes.length; ++i)
530 {
531 var attr = node.attributes[i];
532 if (!attr.specified)
533 continue;
534
535 html.push('&nbsp;<span class="nodeName">', attr.nodeName.toLowerCase(),
536 '</span>=&quot;<span class="nodeValue">', escapeHTML(attr.nodeValue),
537 '</span>&quot;')
538 }
539
540 if (node.firstChild)
541 {
542 html.push('&gt;</div><div class="nodeChildren">');
543
544 for (var child = node.firstChild; child; child = child.nextSibling)
545 appendNode(child, html);
546
547 html.push('</div><div class="objectBox-element">&lt;/<span class="nodeTag">',
548 node.nodeName.toLowerCase(), '&gt;</span></div>');
549 }
550 else
551 html.push('/&gt;</div>');
552 }
553 else if (node.nodeType == 3)
554 {
555 html.push('<div class="nodeText">', escapeHTML(node.nodeValue),
556 '</div>');
557 }
558 }
559
560 // ********************************************************************************************
561
562 function addEvent(object, name, handler)
563 {
564 if (document.all)
565 object.attachEvent("on"+name, handler);
566 else
567 object.addEventListener(name, handler, false);
568 }
569
570 function removeEvent(object, name, handler)
571 {
572 if (document.all)
573 object.detachEvent("on"+name, handler);
574 else
575 object.removeEventListener(name, handler, false);
576 }
577
578 function cancelEvent(event)
579 {
580 if (document.all)
581 event.cancelBubble = true;
582 else
583 event.stopPropagation();
584 }
585
586 function onError(msg, href, lineNo)
587 {
588 var html = [];
589
590 var lastSlash = href.lastIndexOf("/");
591 var fileName = lastSlash == -1 ? href : href.substr(lastSlash+1);
592
593 html.push(
594 '<span class="errorMessage">', msg, '</span>',
595 '<div class="objectBox-sourceLink">', fileName, ' (line ', lineNo, ')</div>'
596 );
597
598 logRow(html, "error");
599 };
600
601 function onKeyDown(event)
602 {
603 if (event.keyCode == 123)
604 toggleConsole();
605 else if ((event.keyCode == 108 || event.keyCode == 76) && event.shiftKey
606 && (event.metaKey || event.ctrlKey))
607 focusCommandLine();
608 else
609 return;
610
611 cancelEvent(event);
612 }
613
614 function onSplitterMouseDown(event)
615 {
616 if (isSafari || isOpera)
617 return;
618
619 addEvent(document, "mousemove", onSplitterMouseMove);
620 addEvent(document, "mouseup", onSplitterMouseUp);
621
622 for (var i = 0; i < frames.length; ++i)
623 {
624 addEvent(frames[i].document, "mousemove", onSplitterMouseMove);
625 addEvent(frames[i].document, "mouseup", onSplitterMouseUp);
626 }
627 }
628
629 function onSplitterMouseMove(event)
630 {
631 var win = document.all
632 ? event.srcElement.ownerDocument.parentWindow
633 : event.target.ownerDocument.defaultView;
634
635 var clientY = event.clientY;
636 if (win != win.parent)
637 clientY += win.frameElement ? win.frameElement.offsetTop : 0;
638
639 var height = consoleFrame.offsetTop + consoleFrame.clientHeight;
640 var y = height - clientY;
641
642 consoleFrame.style.height = y + "px";
643 layout();
644 }
645
646 function onSplitterMouseUp(event)
647 {
648 removeEvent(document, "mousemove", onSplitterMouseMove);
649 removeEvent(document, "mouseup", onSplitterMouseUp);
650
651 for (var i = 0; i < frames.length; ++i)
652 {
653 removeEvent(frames[i].document, "mousemove", onSplitterMouseMove);
654 removeEvent(frames[i].document, "mouseup", onSplitterMouseUp);
655 }
656 }
657
658 function onCommandLineKeyDown(event)
659 {
660 if (event.keyCode == 13)
661 evalCommandLine();
662 else if (event.keyCode == 27)
663 commandLine.value = "";
664 }
665
666 window.onerror = onError;
667 addEvent(document, isIE || isSafari ? "keydown" : "keypress", onKeyDown);
668
669 if (document.documentElement.getAttribute("debug") == "true")
670 toggleConsole(true);
671})();
672}
diff --git a/docroot/lib/firebug/firebugx.js b/docroot/lib/firebug/firebugx.js
new file mode 100755
index 0000000..5a467fc
--- /dev/null
+++ b/docroot/lib/firebug/firebugx.js
@@ -0,0 +1,10 @@
1
2if (!("console" in window) || !("firebug" in console))
3{
4 var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml",
5 "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"];
6
7 window.console = {};
8 for (var i = 0; i < names.length; ++i)
9 window.console[names[i]] = function() {}
10} \ No newline at end of file
diff --git a/docroot/lib/firebug/infoIcon.png b/docroot/lib/firebug/infoIcon.png
new file mode 100755
index 0000000..da1e533
--- /dev/null
+++ b/docroot/lib/firebug/infoIcon.png
Binary files differ
diff --git a/docroot/lib/firebug/warningIcon.png b/docroot/lib/firebug/warningIcon.png
new file mode 100755
index 0000000..de51084
--- /dev/null
+++ b/docroot/lib/firebug/warningIcon.png
Binary files differ
diff --git a/docroot/lib/prototype.js b/docroot/lib/prototype.js
new file mode 100755
index 0000000..5806f35
--- /dev/null
+++ b/docroot/lib/prototype.js
@@ -0,0 +1,3277 @@
1/* Prototype JavaScript framework, version 1.5.1.1
2 * (c) 2005-2007 Sam Stephenson
3 *
4 * Prototype is freely distributable under the terms of an MIT-style license.
5 * For details, see the Prototype web site: http://www.prototypejs.org/
6 *
7/*--------------------------------------------------------------------------*/
8
9var Prototype = {
10 Version: '1.5.1.1',
11
12 Browser: {
13 IE: !!(window.attachEvent && !window.opera),
14 Opera: !!window.opera,
15 WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
16 Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1
17 },
18
19 BrowserFeatures: {
20 XPath: !!document.evaluate,
21 ElementExtensions: !!window.HTMLElement,
22 SpecificElementExtensions:
23 (document.createElement('div').__proto__ !==
24 document.createElement('form').__proto__)
25 },
26
27 ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
28 JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
29
30 emptyFunction: function() { },
31 K: function(x) { return x }
32}
33
34var Class = {
35 create: function() {
36 return function() {
37 this.initialize.apply(this, arguments);
38 }
39 }
40}
41
42var Abstract = new Object();
43
44Object.extend = function(destination, source) {
45 for (var property in source) {
46 destination[property] = source[property];
47 }
48 return destination;
49}
50
51Object.extend(Object, {
52 inspect: function(object) {
53 try {
54 if (object === undefined) return 'undefined';
55 if (object === null) return 'null';
56 return object.inspect ? object.inspect() : object.toString();
57 } catch (e) {
58 if (e instanceof RangeError) return '...';
59 throw e;
60 }
61 },
62
63 toJSON: function(object) {
64 var type = typeof object;
65 switch(type) {
66 case 'undefined':
67 case 'function':
68 case 'unknown': return;
69 case 'boolean': return object.toString();
70 }
71 if (object === null) return 'null';
72 if (object.toJSON) return object.toJSON();
73 if (object.ownerDocument === document) return;
74 var results = [];
75 for (var property in object) {
76 var value = Object.toJSON(object[property]);
77 if (value !== undefined)
78 results.push(property.toJSON() + ': ' + value);
79 }
80 return '{' + results.join(', ') + '}';
81 },
82
83 keys: function(object) {
84 var keys = [];
85 for (var property in object)
86 keys.push(property);
87 return keys;
88 },
89
90 values: function(object) {
91 var values = [];
92 for (var property in object)
93 values.push(object[property]);
94 return values;
95 },
96
97 clone: function(object) {
98 return Object.extend({}, object);
99 }
100});
101
102Function.prototype.bind = function() {
103 var __method = this, args = $A(arguments), object = args.shift();
104 return function() {
105 return __method.apply(object, args.concat($A(arguments)));
106 }
107}
108
109Function.prototype.bindAsEventListener = function(object) {
110 var __method = this, args = $A(arguments), object = args.shift();
111 return function(event) {
112 return __method.apply(object, [event || window.event].concat(args));
113 }
114}
115
116Object.extend(Number.prototype, {
117 toColorPart: function() {
118 return this.toPaddedString(2, 16);
119 },
120
121 succ: function() {
122 return this + 1;
123 },
124
125 times: function(iterator) {
126 $R(0, this, true).each(iterator);
127 return this;
128 },
129
130 toPaddedString: function(length, radix) {
131 var string = this.toString(radix || 10);
132 return '0'.times(length - string.length) + string;
133 },
134
135 toJSON: function() {
136 return isFinite(this) ? this.toString() : 'null';
137 }
138});
139
140Date.prototype.toJSON = function() {
141 return '"' + this.getFullYear() + '-' +
142 (this.getMonth() + 1).toPaddedString(2) + '-' +
143 this.getDate().toPaddedString(2) + 'T' +
144 this.getHours().toPaddedString(2) + ':' +
145 this.getMinutes().toPaddedString(2) + ':' +
146 this.getSeconds().toPaddedString(2) + '"';
147};
148
149var Try = {
150 these: function() {
151 var returnValue;
152
153 for (var i = 0, length = arguments.length; i < length; i++) {
154 var lambda = arguments[i];
155 try {
156 returnValue = lambda();
157 break;
158 } catch (e) {}
159 }
160
161 return returnValue;
162 }
163}
164
165/*--------------------------------------------------------------------------*/
166
167var PeriodicalExecuter = Class.create();
168PeriodicalExecuter.prototype = {
169 initialize: function(callback, frequency) {
170 this.callback = callback;
171 this.frequency = frequency;
172 this.currentlyExecuting = false;
173
174 this.registerCallback();
175 },
176
177 registerCallback: function() {
178 this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
179 },
180
181 stop: function() {
182 if (!this.timer) return;
183 clearInterval(this.timer);
184 this.timer = null;
185 },
186
187 onTimerEvent: function() {
188 if (!this.currentlyExecuting) {
189 try {
190 this.currentlyExecuting = true;
191 this.callback(this);
192 } finally {
193 this.currentlyExecuting = false;
194 }
195 }
196 }
197}
198Object.extend(String, {
199 interpret: function(value) {
200 return value == null ? '' : String(value);
201 },
202 specialChar: {
203 '\b': '\\b',
204 '\t': '\\t',
205 '\n': '\\n',
206 '\f': '\\f',
207 '\r': '\\r',
208 '\\': '\\\\'
209 }
210});
211
212Object.extend(String.prototype, {
213 gsub: function(pattern, replacement) {
214 var result = '', source = this, match;
215 replacement = arguments.callee.prepareReplacement(replacement);
216
217 while (source.length > 0) {
218 if (match = source.match(pattern)) {
219 result += source.slice(0, match.index);
220 result += String.interpret(replacement(match));
221 source = source.slice(match.index + match[0].length);
222 } else {
223 result += source, source = '';
224 }
225 }
226 return result;
227 },
228
229 sub: function(pattern, replacement, count) {
230 replacement = this.gsub.prepareReplacement(replacement);
231 count = count === undefined ? 1 : count;
232
233 return this.gsub(pattern, function(match) {
234 if (--count < 0) return match[0];
235 return replacement(match);
236 });
237 },
238
239 scan: function(pattern, iterator) {
240 this.gsub(pattern, iterator);
241 return this;
242 },
243
244 truncate: function(length, truncation) {
245 length = length || 30;
246 truncation = truncation === undefined ? '...' : truncation;
247 return this.length > length ?
248 this.slice(0, length - truncation.length) + truncation : this;
249 },
250
251 strip: function() {
252 return this.replace(/^\s+/, '').replace(/\s+$/, '');
253 },
254
255 stripTags: function() {
256 return this.replace(/<\/?[^>]+>/gi, '');
257 },
258
259 stripScripts: function() {
260 return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
261 },
262
263 extractScripts: function() {
264 var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
265 var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
266 return (this.match(matchAll) || []).map(function(scriptTag) {
267 return (scriptTag.match(matchOne) || ['', ''])[1];
268 });
269 },
270
271 evalScripts: function() {
272 return this.extractScripts().map(function(script) { return eval(script) });
273 },
274
275 escapeHTML: function() {
276 var self = arguments.callee;
277 self.text.data = this;
278 return self.div.innerHTML;
279 },
280
281 unescapeHTML: function() {
282 var div = document.createElement('div');
283 div.innerHTML = this.stripTags();
284 return div.childNodes[0] ? (div.childNodes.length > 1 ?
285 $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
286 div.childNodes[0].nodeValue) : '';
287 },
288
289 toQueryParams: function(separator) {
290 var match = this.strip().match(/([^?#]*)(#.*)?$/);
291 if (!match) return {};
292
293 return match[1].split(separator || '&').inject({}, function(hash, pair) {
294 if ((pair = pair.split('='))[0]) {
295 var key = decodeURIComponent(pair.shift());
296 var value = pair.length > 1 ? pair.join('=') : pair[0];
297 if (value != undefined) value = decodeURIComponent(value);
298
299 if (key in hash) {
300 if (hash[key].constructor != Array) hash[key] = [hash[key]];
301 hash[key].push(value);
302 }
303 else hash[key] = value;
304 }
305 return hash;
306 });
307 },
308
309 toArray: function() {
310 return this.split('');
311 },
312
313 succ: function() {
314 return this.slice(0, this.length - 1) +
315 String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
316 },
317
318 times: function(count) {
319 var result = '';
320 for (var i = 0; i < count; i++) result += this;
321 return result;
322 },
323
324 camelize: function() {
325 var parts = this.split('-'), len = parts.length;
326 if (len == 1) return parts[0];
327
328 var camelized = this.charAt(0) == '-'
329 ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
330 : parts[0];
331
332 for (var i = 1; i < len; i++)
333 camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
334
335 return camelized;
336 },
337
338 capitalize: function() {
339 return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
340 },
341
342 underscore: function() {
343 return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
344 },
345
346 dasherize: function() {
347 return this.gsub(/_/,'-');
348 },
349
350 inspect: function(useDoubleQuotes) {
351 var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
352 var character = String.specialChar[match[0]];
353 return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
354 });
355 if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
356 return "'" + escapedString.replace(/'/g, '\\\'') + "'";
357 },
358
359 toJSON: function() {
360 return this.inspect(true);
361 },
362
363 unfilterJSON: function(filter) {
364 return this.sub(filter || Prototype.JSONFilter, '#{1}');
365 },
366
367 isJSON: function() {
368 var str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
369 return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
370 },
371
372 evalJSON: function(sanitize) {
373 var json = this.unfilterJSON();
374 try {
375 if (!sanitize || json.isJSON()) return eval('(' + json + ')');
376 } catch (e) { }
377 throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
378 },
379
380 include: function(pattern) {
381 return this.indexOf(pattern) > -1;
382 },
383
384 startsWith: function(pattern) {
385 return this.indexOf(pattern) === 0;
386 },
387
388 endsWith: function(pattern) {
389 var d = this.length - pattern.length;
390 return d >= 0 && this.lastIndexOf(pattern) === d;
391 },
392
393 empty: function() {
394 return this == '';
395 },
396
397 blank: function() {
398 return /^\s*$/.test(this);
399 }
400});
401
402if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {
403 escapeHTML: function() {
404 return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
405 },
406 unescapeHTML: function() {
407 return this.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
408 }
409});
410
411String.prototype.gsub.prepareReplacement = function(replacement) {
412 if (typeof replacement == 'function') return replacement;
413 var template = new Template(replacement);
414 return function(match) { return template.evaluate(match) };
415}
416
417String.prototype.parseQuery = String.prototype.toQueryParams;
418
419Object.extend(String.prototype.escapeHTML, {
420 div: document.createElement('div'),
421 text: document.createTextNode('')
422});
423
424with (String.prototype.escapeHTML) div.appendChild(text);
425
426var Template = Class.create();
427Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
428Template.prototype = {
429 initialize: function(template, pattern) {
430 this.template = template.toString();
431 this.pattern = pattern || Template.Pattern;
432 },
433
434 evaluate: function(object) {
435 return this.template.gsub(this.pattern, function(match) {
436 var before = match[1];
437 if (before == '\\') return match[2];
438 return before + String.interpret(object[match[3]]);
439 });
440 }
441}
442
443var $break = {}, $continue = new Error('"throw $continue" is deprecated, use "return" instead');
444
445var Enumerable = {
446 each: function(iterator) {
447 var index = 0;
448 try {
449 this._each(function(value) {
450 iterator(value, index++);
451 });
452 } catch (e) {
453 if (e != $break) throw e;
454 }
455 return this;
456 },
457
458 eachSlice: function(number, iterator) {
459 var index = -number, slices = [], array = this.toArray();
460 while ((index += number) < array.length)
461 slices.push(array.slice(index, index+number));
462 return slices.map(iterator);
463 },
464
465 all: function(iterator) {
466 var result = true;
467 this.each(function(value, index) {
468 result = result && !!(iterator || Prototype.K)(value, index);
469 if (!result) throw $break;
470 });
471 return result;
472 },
473
474 any: function(iterator) {
475 var result = false;
476 this.each(function(value, index) {
477 if (result = !!(iterator || Prototype.K)(value, index))
478 throw $break;
479 });
480 return result;
481 },
482
483 collect: function(iterator) {
484 var results = [];
485 this.each(function(value, index) {
486 results.push((iterator || Prototype.K)(value, index));
487 });
488 return results;
489 },
490
491 detect: function(iterator) {
492 var result;
493 this.each(function(value, index) {
494 if (iterator(value, index)) {
495 result = value;
496 throw $break;
497 }
498 });
499 return result;
500 },
501
502 findAll: function(iterator) {
503 var results = [];
504 this.each(function(value, index) {
505 if (iterator(value, index))
506 results.push(value);
507 });
508 return results;
509 },
510
511 grep: function(pattern, iterator) {
512 var results = [];
513 this.each(function(value, index) {
514 var stringValue = value.toString();
515 if (stringValue.match(pattern))
516 results.push((iterator || Prototype.K)(value, index));
517 })
518 return results;
519 },
520
521 include: function(object) {
522 var found = false;
523 this.each(function(value) {
524 if (value == object) {
525 found = true;
526 throw $break;
527 }
528 });
529 return found;
530 },
531
532 inGroupsOf: function(number, fillWith) {
533 fillWith = fillWith === undefined ? null : fillWith;
534 return this.eachSlice(number, function(slice) {
535 while(slice.length < number) slice.push(fillWith);
536 return slice;
537 });
538 },
539
540 inject: function(memo, iterator) {
541 this.each(function(value, index) {
542 memo = iterator(memo, value, index);
543 });
544 return memo;
545 },
546
547 invoke: function(method) {
548 var args = $A(arguments).slice(1);
549 return this.map(function(value) {
550 return value[method].apply(value, args);
551 });
552 },
553
554 max: function(iterator) {
555 var result;
556 this.each(function(value, index) {
557 value = (iterator || Prototype.K)(value, index);
558 if (result == undefined || value >= result)
559 result = value;
560 });
561 return result;
562 },
563
564 min: function(iterator) {
565 var result;
566 this.each(function(value, index) {
567 value = (iterator || Prototype.K)(value, index);
568 if (result == undefined || value < result)
569 result = value;
570 });
571 return result;
572 },
573
574 partition: function(iterator) {
575 var trues = [], falses = [];
576 this.each(function(value, index) {
577 ((iterator || Prototype.K)(value, index) ?
578 trues : falses).push(value);
579 });
580 return [trues, falses];
581 },
582
583 pluck: function(property) {
584 var results = [];
585 this.each(function(value, index) {
586 results.push(value[property]);
587 });
588 return results;
589 },
590
591 reject: function(iterator) {
592 var results = [];
593 this.each(function(value, index) {
594 if (!iterator(value, index))
595 results.push(value);
596 });
597 return results;
598 },
599
600 sortBy: function(iterator) {
601 return this.map(function(value, index) {
602 return {value: value, criteria: iterator(value, index)};
603 }).sort(function(left, right) {
604 var a = left.criteria, b = right.criteria;
605 return a < b ? -1 : a > b ? 1 : 0;
606 }).pluck('value');
607 },
608
609 toArray: function() {
610 return this.map();
611 },
612
613 zip: function() {
614 var iterator = Prototype.K, args = $A(arguments);
615 if (typeof args.last() == 'function')
616 iterator = args.pop();
617
618 var collections = [this].concat(args).map($A);
619 return this.map(function(value, index) {
620 return iterator(collections.pluck(index));
621 });
622 },
623
624 size: function() {
625 return this.toArray().length;
626 },
627
628 inspect: function() {
629 return '#<Enumerable:' + this.toArray().inspect() + '>';
630 }
631}
632
633Object.extend(Enumerable, {
634 map: Enumerable.collect,
635 find: Enumerable.detect,
636 select: Enumerable.findAll,
637 member: Enumerable.include,
638 entries: Enumerable.toArray
639});
640var $A = Array.from = function(iterable) {
641 if (!iterable) return [];
642 if (iterable.toArray) {
643 return iterable.toArray();
644 } else {
645 var results = [];
646 for (var i = 0, length = iterable.length; i < length; i++)
647 results.push(iterable[i]);
648 return results;
649 }
650}
651
652if (Prototype.Browser.WebKit) {
653 $A = Array.from = function(iterable) {
654 if (!iterable) return [];
655 if (!(typeof iterable == 'function' && iterable == '[object NodeList]') &&
656 iterable.toArray) {
657 return iterable.toArray();
658 } else {
659 var results = [];
660 for (var i = 0, length = iterable.length; i < length; i++)
661 results.push(iterable[i]);
662 return results;
663 }
664 }
665}
666
667Object.extend(Array.prototype, Enumerable);
668
669if (!Array.prototype._reverse)
670 Array.prototype._reverse = Array.prototype.reverse;
671
672Object.extend(Array.prototype, {
673 _each: function(iterator) {
674 for (var i = 0, length = this.length; i < length; i++)
675 iterator(this[i]);
676 },
677
678 clear: function() {
679 this.length = 0;
680 return this;
681 },
682
683 first: function() {
684 return this[0];
685 },
686
687 last: function() {
688 return this[this.length - 1];
689 },
690
691 compact: function() {
692 return this.select(function(value) {
693 return value != null;
694 });
695 },
696
697 flatten: function() {
698 return this.inject([], function(array, value) {
699 return array.concat(value && value.constructor == Array ?
700 value.flatten() : [value]);
701 });
702 },
703
704 without: function() {
705 var values = $A(arguments);
706 return this.select(function(value) {
707 return !values.include(value);
708 });
709 },
710
711 indexOf: function(object) {
712 for (var i = 0, length = this.length; i < length; i++)
713 if (this[i] == object) return i;
714 return -1;
715 },
716
717 reverse: function(inline) {
718 return (inline !== false ? this : this.toArray())._reverse();
719 },
720
721 reduce: function() {
722 return this.length > 1 ? this : this[0];
723 },
724
725 uniq: function(sorted) {
726 return this.inject([], function(array, value, index) {
727 if (0 == index || (sorted ? array.last() != value : !array.include(value)))
728 array.push(value);
729 return array;
730 });
731 },
732
733 clone: function() {
734 return [].concat(this);
735 },
736
737 size: function() {
738 return this.length;
739 },
740
741 inspect: function() {
742 return '[' + this.map(Object.inspect).join(', ') + ']';
743 },
744
745 toJSON: function() {
746 var results = [];
747 this.each(function(object) {
748 var value = Object.toJSON(object);
749 if (value !== undefined) results.push(value);
750 });
751 return '[' + results.join(', ') + ']';
752 }
753});
754
755Array.prototype.toArray = Array.prototype.clone;
756
757function $w(string) {
758 string = string.strip();
759 return string ? string.split(/\s+/) : [];
760}
761
762if (Prototype.Browser.Opera){
763 Array.prototype.concat = function() {
764 var array = [];
765 for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
766 for (var i = 0, length = arguments.length; i < length; i++) {
767 if (arguments[i].constructor == Array) {
768 for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
769 array.push(arguments[i][j]);
770 } else {
771 array.push(arguments[i]);
772 }
773 }
774 return array;
775 }
776}
777var Hash = function(object) {
778 if (object instanceof Hash) this.merge(object);
779 else Object.extend(this, object || {});
780};
781
782Object.extend(Hash, {
783 toQueryString: function(obj) {
784 var parts = [];
785 parts.add = arguments.callee.addPair;
786
787 this.prototype._each.call(obj, function(pair) {
788 if (!pair.key) return;
789 var value = pair.value;
790
791 if (value && typeof value == 'object') {
792 if (value.constructor == Array) value.each(function(value) {
793 parts.add(pair.key, value);
794 });
795 return;
796 }
797 parts.add(pair.key, value);
798 });
799
800 return parts.join('&');
801 },
802
803 toJSON: function(object) {
804 var results = [];
805 this.prototype._each.call(object, function(pair) {
806 var value = Object.toJSON(pair.value);
807 if (value !== undefined) results.push(pair.key.toJSON() + ': ' + value);
808 });
809 return '{' + results.join(', ') + '}';
810 }
811});
812
813Hash.toQueryString.addPair = function(key, value, prefix) {
814 key = encodeURIComponent(key);
815 if (value === undefined) this.push(key);
816 else this.push(key + '=' + (value == null ? '' : encodeURIComponent(value)));
817}
818
819Object.extend(Hash.prototype, Enumerable);
820Object.extend(Hash.prototype, {
821 _each: function(iterator) {
822 for (var key in this) {
823 var value = this[key];
824 if (value && value == Hash.prototype[key]) continue;
825
826 var pair = [key, value];
827 pair.key = key;
828 pair.value = value;
829 iterator(pair);
830 }
831 },
832
833 keys: function() {
834 return this.pluck('key');
835 },
836
837 values: function() {
838 return this.pluck('value');
839 },
840
841 merge: function(hash) {
842 return $H(hash).inject(this, function(mergedHash, pair) {
843 mergedHash[pair.key] = pair.value;
844 return mergedHash;
845 });
846 },
847
848 remove: function() {
849 var result;
850 for(var i = 0, length = arguments.length; i < length; i++) {
851 var value = this[arguments[i]];
852 if (value !== undefined){
853 if (result === undefined) result = value;
854 else {
855 if (result.constructor != Array) result = [result];
856 result.push(value)
857 }
858 }
859 delete this[arguments[i]];
860 }
861 return result;
862 },
863
864 toQueryString: function() {
865 return Hash.toQueryString(this);
866 },
867
868 inspect: function() {
869 return '#<Hash:{' + this.map(function(pair) {
870 return pair.map(Object.inspect).join(': ');
871 }).join(', ') + '}>';
872 },
873
874 toJSON: function() {
875 return Hash.toJSON(this);
876 }
877});
878
879function $H(object) {
880 if (object instanceof Hash) return object;
881 return new Hash(object);
882};
883
884// Safari iterates over shadowed properties
885if (function() {
886 var i = 0, Test = function(value) { this.key = value };
887 Test.prototype.key = 'foo';
888 for (var property in new Test('bar')) i++;
889 return i > 1;
890}()) Hash.prototype._each = function(iterator) {
891 var cache = [];
892 for (var key in this) {
893 var value = this[key];
894 if ((value && value == Hash.prototype[key]) || cache.include(key)) continue;
895 cache.push(key);
896 var pair = [key, value];
897 pair.key = key;
898 pair.value = value;
899 iterator(pair);
900 }
901};
902ObjectRange = Class.create();
903Object.extend(ObjectRange.prototype, Enumerable);
904Object.extend(ObjectRange.prototype, {
905 initialize: function(start, end, exclusive) {
906 this.start = start;
907 this.end = end;
908 this.exclusive = exclusive;
909 },
910
911 _each: function(iterator) {
912 var value = this.start;
913 while (this.include(value)) {
914 iterator(value);
915 value = value.succ();
916 }
917 },
918
919 include: function(value) {
920 if (value < this.start)
921 return false;
922 if (this.exclusive)
923 return value < this.end;
924 return value <= this.end;
925 }
926});
927
928var $R = function(start, end, exclusive) {
929 return new ObjectRange(start, end, exclusive);
930}
931
932var Ajax = {
933 getTransport: function() {
934 return Try.these(
935 function() {return new XMLHttpRequest()},
936 function() {return new ActiveXObject('Msxml2.XMLHTTP')},
937 function() {return new ActiveXObject('Microsoft.XMLHTTP')}
938 ) || false;
939 },
940
941 activeRequestCount: 0
942}
943
944Ajax.Responders = {
945 responders: [],
946
947 _each: function(iterator) {
948 this.responders._each(iterator);
949 },
950
951 register: function(responder) {
952 if (!this.include(responder))
953 this.responders.push(responder);
954 },
955
956 unregister: function(responder) {
957 this.responders = this.responders.without(responder);
958 },
959
960 dispatch: function(callback, request, transport, json) {
961 this.each(function(responder) {
962 if (typeof responder[callback] == 'function') {
963 try {
964 responder[callback].apply(responder, [request, transport, json]);
965 } catch (e) {}
966 }
967 });
968 }
969};
970
971Object.extend(Ajax.Responders, Enumerable);
972
973Ajax.Responders.register({
974 onCreate: function() {
975 Ajax.activeRequestCount++;
976 },
977 onComplete: function() {
978 Ajax.activeRequestCount--;
979 }
980});
981
982Ajax.Base = function() {};
983Ajax.Base.prototype = {
984 setOptions: function(options) {
985 this.options = {
986 method: 'post',
987 asynchronous: true,
988 contentType: 'application/x-www-form-urlencoded',
989 encoding: 'UTF-8',
990 parameters: ''
991 }
992 Object.extend(this.options, options || {});
993
994 this.options.method = this.options.method.toLowerCase();
995 if (typeof this.options.parameters == 'string')
996 this.options.parameters = this.options.parameters.toQueryParams();
997 }
998}
999
1000Ajax.Request = Class.create();
1001Ajax.Request.Events =
1002 ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
1003
1004Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
1005 _complete: false,
1006
1007 initialize: function(url, options) {
1008 this.transport = Ajax.getTransport();
1009 this.setOptions(options);
1010 this.request(url);
1011 },
1012
1013 request: function(url) {
1014 this.url = url;
1015 this.method = this.options.method;
1016 var params = Object.clone(this.options.parameters);
1017
1018 if (!['get', 'post'].include(this.method)) {
1019 // simulate other verbs over post
1020 params['_method'] = this.method;
1021 this.method = 'post';
1022 }
1023
1024 this.parameters = params;
1025
1026 if (params = Hash.toQueryString(params)) {
1027 // when GET, append parameters to URL
1028 if (this.method == 'get')
1029 this.url += (this.url.include('?') ? '&' : '?') + params;
1030 else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
1031 params += '&_=';
1032 }
1033
1034 try {
1035 if (this.options.onCreate) this.options.onCreate(this.transport);
1036 Ajax.Responders.dispatch('onCreate', this, this.transport);
1037
1038 this.transport.open(this.method.toUpperCase(), this.url,
1039 this.options.asynchronous);
1040
1041 if (this.options.asynchronous)
1042 setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10);
1043
1044 this.transport.onreadystatechange = this.onStateChange.bind(this);
1045 this.setRequestHeaders();
1046
1047 this.body = this.method == 'post' ? (this.options.postBody || params) : null;
1048 this.transport.send(this.body);
1049
1050 /* Force Firefox to handle ready state 4 for synchronous requests */
1051 if (!this.options.asynchronous && this.transport.overrideMimeType)
1052 this.onStateChange();
1053
1054 }
1055 catch (e) {
1056 this.dispatchException(e);
1057 }
1058 },
1059
1060 onStateChange: function() {
1061 var readyState = this.transport.readyState;
1062 if (readyState > 1 && !((readyState == 4) && this._complete))
1063 this.respondToReadyState(this.transport.readyState);
1064 },
1065
1066 setRequestHeaders: function() {
1067 var headers = {
1068 'X-Requested-With': 'XMLHttpRequest',
1069 'X-Prototype-Version': Prototype.Version,
1070 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
1071 };
1072
1073 if (this.method == 'post') {
1074 headers['Content-type'] = this.options.contentType +
1075 (this.options.encoding ? '; charset=' + this.options.encoding : '');
1076
1077 /* Force "Connection: close" for older Mozilla browsers to work
1078 * around a bug where XMLHttpRequest sends an incorrect
1079 * Content-length header. See Mozilla Bugzilla #246651.
1080 */
1081 if (this.transport.overrideMimeType &&
1082 (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
1083 headers['Connection'] = 'close';
1084 }
1085
1086 // user-defined headers
1087 if (typeof this.options.requestHeaders == 'object') {
1088 var extras = this.options.requestHeaders;
1089
1090 if (typeof extras.push == 'function')
1091 for (var i = 0, length = extras.length; i < length; i += 2)
1092 headers[extras[i]] = extras[i+1];
1093 else
1094 $H(extras).each(function(pair) { headers[pair.key] = pair.value });
1095 }
1096
1097 for (var name in headers)
1098 this.transport.setRequestHeader(name, headers[name]);
1099 },
1100
1101 success: function() {
1102 return !this.transport.status
1103 || (this.transport.status >= 200 && this.transport.status < 300);
1104 },
1105
1106 respondToReadyState: function(readyState) {
1107 var state = Ajax.Request.Events[readyState];
1108 var transport = this.transport, json = this.evalJSON();
1109
1110 if (state == 'Complete') {
1111 try {
1112 this._complete = true;
1113 (this.options['on' + this.transport.status]
1114 || this.options['on' + (this.success() ? 'Success' : 'Failure')]
1115 || Prototype.emptyFunction)(transport, json);
1116 } catch (e) {
1117 this.dispatchException(e);
1118 }
1119
1120 var contentType = this.getHeader('Content-type');
1121 if (contentType && contentType.strip().
1122 match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i))
1123 this.evalResponse();
1124 }
1125
1126 try {
1127 (this.options['on' + state] || Prototype.emptyFunction)(transport, json);
1128 Ajax.Responders.dispatch('on' + state, this, transport, json);
1129 } catch (e) {
1130 this.dispatchException(e);
1131 }
1132
1133 if (state == 'Complete') {
1134 // avoid memory leak in MSIE: clean up
1135 this.transport.onreadystatechange = Prototype.emptyFunction;
1136 }
1137 },
1138
1139 getHeader: function(name) {
1140 try {
1141 return this.transport.getResponseHeader(name);
1142 } catch (e) { return null }
1143 },
1144
1145 evalJSON: function() {
1146 try {
1147 var json = this.getHeader('X-JSON');
1148 return json ? json.evalJSON() : null;
1149 } catch (e) { return null }
1150 },
1151
1152 evalResponse: function() {
1153 try {
1154 return eval((this.transport.responseText || '').unfilterJSON());
1155 } catch (e) {
1156 this.dispatchException(e);
1157 }
1158 },
1159
1160 dispatchException: function(exception) {
1161 (this.options.onException || Prototype.emptyFunction)(this, exception);
1162 Ajax.Responders.dispatch('onException', this, exception);
1163 }
1164});
1165
1166Ajax.Updater = Class.create();
1167
1168Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
1169 initialize: function(container, url, options) {
1170 this.container = {
1171 success: (container.success || container),
1172 failure: (container.failure || (container.success ? null : container))
1173 }
1174
1175 this.transport = Ajax.getTransport();
1176 this.setOptions(options);
1177
1178 var onComplete = this.options.onComplete || Prototype.emptyFunction;
1179 this.options.onComplete = (function(transport, param) {
1180 this.updateContent();
1181 onComplete(transport, param);
1182 }).bind(this);
1183
1184 this.request(url);
1185 },
1186
1187 updateContent: function() {
1188 var receiver = this.container[this.success() ? 'success' : 'failure'];
1189 var response = this.transport.responseText;
1190
1191 if (!this.options.evalScripts) response = response.stripScripts();
1192
1193 if (receiver = $(receiver)) {
1194 if (this.options.insertion)
1195 new this.options.insertion(receiver, response);
1196 else
1197 receiver.update(response);
1198 }
1199
1200 if (this.success()) {
1201 if (this.onComplete)
1202 setTimeout(this.onComplete.bind(this), 10);
1203 }
1204 }
1205});
1206
1207Ajax.PeriodicalUpdater = Class.create();
1208Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
1209 initialize: function(container, url, options) {
1210 this.setOptions(options);
1211 this.onComplete = this.options.onComplete;
1212
1213 this.frequency = (this.options.frequency || 2);
1214 this.decay = (this.options.decay || 1);
1215
1216 this.updater = {};
1217 this.container = container;
1218 this.url = url;
1219
1220 this.start();
1221 },
1222
1223 start: function() {
1224 this.options.onComplete = this.updateComplete.bind(this);
1225 this.onTimerEvent();
1226 },
1227
1228 stop: function() {
1229 this.updater.options.onComplete = undefined;
1230 clearTimeout(this.timer);
1231 (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
1232 },
1233
1234 updateComplete: function(request) {
1235 if (this.options.decay) {
1236 this.decay = (request.responseText == this.lastText ?
1237 this.decay * this.options.decay : 1);
1238
1239 this.lastText = request.responseText;
1240 }
1241 this.timer = setTimeout(this.onTimerEvent.bind(this),
1242 this.decay * this.frequency * 1000);
1243 },
1244
1245 onTimerEvent: function() {
1246 this.updater = new Ajax.Updater(this.container, this.url, this.options);
1247 }
1248});
1249function $(element) {
1250 if (arguments.length > 1) {
1251 for (var i = 0, elements = [], length = arguments.length; i < length; i++)
1252 elements.push($(arguments[i]));
1253 return elements;
1254 }
1255 if (typeof element == 'string')
1256 element = document.getElementById(element);
1257 return Element.extend(element);
1258}
1259
1260if (Prototype.BrowserFeatures.XPath) {
1261 document._getElementsByXPath = function(expression, parentElement) {
1262 var results = [];
1263 var query = document.evaluate(expression, $(parentElement) || document,
1264 null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
1265 for (var i = 0, length = query.snapshotLength; i < length; i++)
1266 results.push(query.snapshotItem(i));
1267 return results;
1268 };
1269
1270 document.getElementsByClassName = function(className, parentElement) {
1271 var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]";
1272 return document._getElementsByXPath(q, parentElement);
1273 }
1274
1275} else document.getElementsByClassName = function(className, parentElement) {
1276 var children = ($(parentElement) || document.body).getElementsByTagName('*');
1277 var elements = [], child, pattern = new RegExp("(^|\\s)" + className + "(\\s|$)");
1278 for (var i = 0, length = children.length; i < length; i++) {
1279 child = children[i];
1280 var elementClassName = child.className;
1281 if (elementClassName.length == 0) continue;
1282 if (elementClassName == className || elementClassName.match(pattern))
1283 elements.push(Element.extend(child));
1284 }
1285 return elements;
1286};
1287
1288/*--------------------------------------------------------------------------*/
1289
1290if (!window.Element) var Element = {};
1291
1292Element.extend = function(element) {
1293 var F = Prototype.BrowserFeatures;
1294 if (!element || !element.tagName || element.nodeType == 3 ||
1295 element._extended || F.SpecificElementExtensions || element == window)
1296 return element;
1297
1298 var methods = {}, tagName = element.tagName, cache = Element.extend.cache,
1299 T = Element.Methods.ByTag;
1300
1301 // extend methods for all tags (Safari doesn't need this)
1302 if (!F.ElementExtensions) {
1303 Object.extend(methods, Element.Methods),
1304 Object.extend(methods, Element.Methods.Simulated);
1305 }
1306
1307 // extend methods for specific tags
1308 if (T[tagName]) Object.extend(methods, T[tagName]);
1309
1310 for (var property in methods) {
1311 var value = methods[property];
1312 if (typeof value == 'function' && !(property in element))
1313 element[property] = cache.findOrStore(value);
1314 }
1315
1316 element._extended = Prototype.emptyFunction;
1317 return element;
1318};
1319
1320Element.extend.cache = {
1321 findOrStore: function(value) {
1322 return this[value] = this[value] || function() {
1323 return value.apply(null, [this].concat($A(arguments)));
1324 }
1325 }
1326};
1327
1328Element.Methods = {
1329 visible: function(element) {
1330 return $(element).style.display != 'none';
1331 },
1332
1333 toggle: function(element) {
1334 element = $(element);
1335 Element[Element.visible(element) ? 'hide' : 'show'](element);
1336 return element;
1337 },
1338
1339 hide: function(element) {
1340 $(element).style.display = 'none';
1341 return element;
1342 },
1343
1344 show: function(element) {
1345 $(element).style.display = '';
1346 return element;
1347 },
1348
1349 remove: function(element) {
1350 element = $(element);
1351 element.parentNode.removeChild(element);
1352 return element;
1353 },
1354
1355 update: function(element, html) {
1356 html = typeof html == 'undefined' ? '' : html.toString();
1357 $(element).innerHTML = html.stripScripts();
1358 setTimeout(function() {html.evalScripts()}, 10);
1359 return element;
1360 },
1361
1362 replace: function(element, html) {
1363 element = $(element);
1364 html = typeof html == 'undefined' ? '' : html.toString();
1365 if (element.outerHTML) {
1366 element.outerHTML = html.stripScripts();
1367 } else {
1368 var range = element.ownerDocument.createRange();
1369 range.selectNodeContents(element);
1370 element.parentNode.replaceChild(
1371 range.createContextualFragment(html.stripScripts()), element);
1372 }
1373 setTimeout(function() {html.evalScripts()}, 10);
1374 return element;
1375 },
1376
1377 inspect: function(element) {
1378 element = $(element);
1379 var result = '<' + element.tagName.toLowerCase();
1380 $H({'id': 'id', 'className': 'class'}).each(function(pair) {
1381 var property = pair.first(), attribute = pair.last();
1382 var value = (element[property] || '').toString();
1383 if (value) result += ' ' + attribute + '=' + value.inspect(true);
1384 });
1385 return result + '>';
1386 },
1387
1388 recursivelyCollect: function(element, property) {
1389 element = $(element);
1390 var elements = [];
1391 while (element = element[property])
1392 if (element.nodeType == 1)
1393 elements.push(Element.extend(element));
1394 return elements;
1395 },
1396
1397 ancestors: function(element) {
1398 return $(element).recursivelyCollect('parentNode');
1399 },
1400
1401 descendants: function(element) {
1402 return $A($(element).getElementsByTagName('*')).each(Element.extend);
1403 },
1404
1405 firstDescendant: function(element) {
1406 element = $(element).firstChild;
1407 while (element && element.nodeType != 1) element = element.nextSibling;
1408 return $(element);
1409 },
1410
1411 immediateDescendants: function(element) {
1412 if (!(element = $(element).firstChild)) return [];
1413 while (element && element.nodeType != 1) element = element.nextSibling;
1414 if (element) return [element].concat($(element).nextSiblings());
1415 return [];
1416 },
1417
1418 previousSiblings: function(element) {
1419 return $(element).recursivelyCollect('previousSibling');
1420 },
1421
1422 nextSiblings: function(element) {
1423 return $(element).recursivelyCollect('nextSibling');
1424 },
1425
1426 siblings: function(element) {
1427 element = $(element);
1428 return element.previousSiblings().reverse().concat(element.nextSiblings());
1429 },
1430
1431 match: function(element, selector) {
1432 if (typeof selector == 'string')
1433 selector = new Selector(selector);
1434 return selector.match($(element));
1435 },
1436
1437 up: function(element, expression, index) {
1438 element = $(element);
1439 if (arguments.length == 1) return $(element.parentNode);
1440 var ancestors = element.ancestors();
1441 return expression ? Selector.findElement(ancestors, expression, index) :
1442 ancestors[index || 0];
1443 },
1444
1445 down: function(element, expression, index) {
1446 element = $(element);
1447 if (arguments.length == 1) return element.firstDescendant();
1448 var descendants = element.descendants();
1449 return expression ? Selector.findElement(descendants, expression, index) :
1450 descendants[index || 0];
1451 },
1452
1453 previous: function(element, expression, index) {
1454 element = $(element);
1455 if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
1456 var previousSiblings = element.previousSiblings();
1457 return expression ? Selector.findElement(previousSiblings, expression, index) :
1458 previousSiblings[index || 0];
1459 },
1460
1461 next: function(element, expression, index) {
1462 element = $(element);
1463 if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
1464 var nextSiblings = element.nextSiblings();
1465 return expression ? Selector.findElement(nextSiblings, expression, index) :
1466 nextSiblings[index || 0];
1467 },
1468
1469 getElementsBySelector: function() {
1470 var args = $A(arguments), element = $(args.shift());
1471 return Selector.findChildElements(element, args);
1472 },
1473
1474 getElementsByClassName: function(element, className) {
1475 return document.getElementsByClassName(className, element);
1476 },
1477
1478 readAttribute: function(element, name) {
1479 element = $(element);
1480 if (Prototype.Browser.IE) {
1481 if (!element.attributes) return null;
1482 var t = Element._attributeTranslations;
1483 if (t.values[name]) return t.values[name](element, name);
1484 if (t.names[name]) name = t.names[name];
1485 var attribute = element.attributes[name];
1486 return attribute ? attribute.nodeValue : null;
1487 }
1488 return element.getAttribute(name);
1489 },
1490
1491 getHeight: function(element) {
1492 return $(element).getDimensions().height;
1493 },
1494
1495 getWidth: function(element) {
1496 return $(element).getDimensions().width;
1497 },
1498
1499 classNames: function(element) {
1500 return new Element.ClassNames(element);
1501 },
1502
1503 hasClassName: function(element, className) {
1504 if (!(element = $(element))) return;
1505 var elementClassName = element.className;
1506 if (elementClassName.length == 0) return false;
1507 if (elementClassName == className ||
1508 elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
1509 return true;
1510 return false;
1511 },
1512
1513 addClassName: function(element, className) {
1514 if (!(element = $(element))) return;
1515 Element.classNames(element).add(className);
1516 return element;
1517 },
1518
1519 removeClassName: function(element, className) {
1520 if (!(element = $(element))) return;
1521 Element.classNames(element).remove(className);
1522 return element;
1523 },
1524
1525 toggleClassName: function(element, className) {
1526 if (!(element = $(element))) return;
1527 Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className);
1528 return element;
1529 },
1530
1531 observe: function() {
1532 Event.observe.apply(Event, arguments);
1533 return $A(arguments).first();
1534 },
1535
1536 stopObserving: function() {
1537 Event.stopObserving.apply(Event, arguments);
1538 return $A(arguments).first();
1539 },
1540
1541 // removes whitespace-only text node children
1542 cleanWhitespace: function(element) {
1543 element = $(element);
1544 var node = element.firstChild;
1545 while (node) {
1546 var nextNode = node.nextSibling;
1547 if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
1548 element.removeChild(node);
1549 node = nextNode;
1550 }
1551 return element;
1552 },
1553
1554 empty: function(element) {
1555 return $(element).innerHTML.blank();
1556 },
1557
1558 descendantOf: function(element, ancestor) {
1559 element = $(element), ancestor = $(ancestor);
1560 while (element = element.parentNode)
1561 if (element == ancestor) return true;
1562 return false;
1563 },
1564
1565 scrollTo: function(element) {
1566 element = $(element);
1567 var pos = Position.cumulativeOffset(element);
1568 window.scrollTo(pos[0], pos[1]);
1569 return element;
1570 },
1571
1572 getStyle: function(element, style) {
1573 element = $(element);
1574 style = style == 'float' ? 'cssFloat' : style.camelize();
1575 var value = element.style[style];
1576 if (!value) {
1577 var css = document.defaultView.getComputedStyle(element, null);
1578 value = css ? css[style] : null;
1579 }
1580 if (style == 'opacity') return value ? parseFloat(value) : 1.0;
1581 return value == 'auto' ? null : value;
1582 },
1583
1584 getOpacity: function(element) {
1585 return $(element).getStyle('opacity');
1586 },
1587
1588 setStyle: function(element, styles, camelized) {
1589 element = $(element);
1590 var elementStyle = element.style;
1591
1592 for (var property in styles)
1593 if (property == 'opacity') element.setOpacity(styles[property])
1594 else
1595 elementStyle[(property == 'float' || property == 'cssFloat') ?
1596 (elementStyle.styleFloat === undefined ? 'cssFloat' : 'styleFloat') :
1597 (camelized ? property : property.camelize())] = styles[property];
1598
1599 return element;
1600 },
1601
1602 setOpacity: function(element, value) {
1603 element = $(element);
1604 element.style.opacity = (value == 1 || value === '') ? '' :
1605 (value < 0.00001) ? 0 : value;
1606 return element;
1607 },
1608
1609 getDimensions: function(element) {
1610 element = $(element);
1611 var display = $(element).getStyle('display');
1612 if (display != 'none' && display != null) // Safari bug
1613 return {width: element.offsetWidth, height: element.offsetHeight};
1614
1615 // All *Width and *Height properties give 0 on elements with display none,
1616 // so enable the element temporarily
1617 var els = element.style;
1618 var originalVisibility = els.visibility;
1619 var originalPosition = els.position;
1620 var originalDisplay = els.display;
1621 els.visibility = 'hidden';
1622 els.position = 'absolute';
1623 els.display = 'block';
1624 var originalWidth = element.clientWidth;
1625 var originalHeight = element.clientHeight;
1626 els.display = originalDisplay;
1627 els.position = originalPosition;
1628 els.visibility = originalVisibility;
1629 return {width: originalWidth, height: originalHeight};
1630 },
1631
1632 makePositioned: function(element) {
1633 element = $(element);
1634 var pos = Element.getStyle(element, 'position');
1635 if (pos == 'static' || !pos) {
1636 element._madePositioned = true;
1637 element.style.position = 'relative';
1638 // Opera returns the offset relative to the positioning context, when an
1639 // element is position relative but top and left have not been defined
1640 if (window.opera) {
1641 element.style.top = 0;
1642 element.style.left = 0;
1643 }
1644 }
1645 return element;
1646 },
1647
1648 undoPositioned: function(element) {
1649 element = $(element);
1650 if (element._madePositioned) {
1651 element._madePositioned = undefined;
1652 element.style.position =
1653 element.style.top =
1654 element.style.left =
1655 element.style.bottom =
1656 element.style.right = '';
1657 }
1658 return element;
1659 },
1660
1661 makeClipping: function(element) {
1662 element = $(element);
1663 if (element._overflow) return element;
1664 element._overflow = element.style.overflow || 'auto';
1665 if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
1666 element.style.overflow = 'hidden';
1667 return element;
1668 },
1669
1670 undoClipping: function(element) {
1671 element = $(element);
1672 if (!element._overflow) return element;
1673 element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
1674 element._overflow = null;
1675 return element;
1676 }
1677};
1678
1679Object.extend(Element.Methods, {
1680 childOf: Element.Methods.descendantOf,
1681 childElements: Element.Methods.immediateDescendants
1682});
1683
1684if (Prototype.Browser.Opera) {
1685 Element.Methods._getStyle = Element.Methods.getStyle;
1686 Element.Methods.getStyle = function(element, style) {
1687 switch(style) {
1688 case 'left':
1689 case 'top':
1690 case 'right':
1691 case 'bottom':
1692 if (Element._getStyle(element, 'position') == 'static') return null;
1693 default: return Element._getStyle(element, style);
1694 }
1695 };
1696}
1697else if (Prototype.Browser.IE) {
1698 Element.Methods.getStyle = function(element, style) {
1699 element = $(element);
1700 style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
1701 var value = element.style[style];
1702 if (!value && element.currentStyle) value = element.currentStyle[style];
1703
1704 if (style == 'opacity') {
1705 if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
1706 if (value[1]) return parseFloat(value[1]) / 100;
1707 return 1.0;
1708 }
1709
1710 if (value == 'auto') {
1711 if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
1712 return element['offset'+style.capitalize()] + 'px';
1713 return null;
1714 }
1715 return value;
1716 };
1717
1718 Element.Methods.setOpacity = function(element, value) {
1719 element = $(element);
1720 var filter = element.getStyle('filter'), style = element.style;
1721 if (value == 1 || value === '') {
1722 style.filter = filter.replace(/alpha\([^\)]*\)/gi,'');
1723 return element;
1724 } else if (value < 0.00001) value = 0;
1725 style.filter = filter.replace(/alpha\([^\)]*\)/gi, '') +
1726 'alpha(opacity=' + (value * 100) + ')';
1727 return element;
1728 };
1729
1730 // IE is missing .innerHTML support for TABLE-related elements
1731 Element.Methods.update = function(element, html) {
1732 element = $(element);
1733 html = typeof html == 'undefined' ? '' : html.toString();
1734 var tagName = element.tagName.toUpperCase();
1735 if (['THEAD','TBODY','TR','TD'].include(tagName)) {
1736 var div = document.createElement('div');
1737 switch (tagName) {
1738 case 'THEAD':
1739 case 'TBODY':
1740 div.innerHTML = '<table><tbody>' + html.stripScripts() + '</tbody></table>';
1741 depth = 2;
1742 break;
1743 case 'TR':
1744 div.innerHTML = '<table><tbody><tr>' + html.stripScripts() + '</tr></tbody></table>';
1745 depth = 3;
1746 break;
1747 case 'TD':
1748 div.innerHTML = '<table><tbody><tr><td>' + html.stripScripts() + '</td></tr></tbody></table>';
1749 depth = 4;
1750 }
1751 $A(element.childNodes).each(function(node) { element.removeChild(node) });
1752 depth.times(function() { div = div.firstChild });
1753 $A(div.childNodes).each(function(node) { element.appendChild(node) });
1754 } else {
1755 element.innerHTML = html.stripScripts();
1756 }
1757 setTimeout(function() { html.evalScripts() }, 10);
1758 return element;
1759 }
1760}
1761else if (Prototype.Browser.Gecko) {
1762 Element.Methods.setOpacity = function(element, value) {
1763 element = $(element);
1764 element.style.opacity = (value == 1) ? 0.999999 :
1765 (value === '') ? '' : (value < 0.00001) ? 0 : value;
1766 return element;
1767 };
1768}
1769
1770Element._attributeTranslations = {
1771 names: {
1772 colspan: "colSpan",
1773 rowspan: "rowSpan",
1774 valign: "vAlign",
1775 datetime: "dateTime",
1776 accesskey: "accessKey",
1777 tabindex: "tabIndex",
1778 enctype: "encType",
1779 maxlength: "maxLength",
1780 readonly: "readOnly",
1781 longdesc: "longDesc"
1782 },
1783 values: {
1784 _getAttr: function(element, attribute) {
1785 return element.getAttribute(attribute, 2);
1786 },
1787 _flag: function(element, attribute) {
1788 return $(element).hasAttribute(attribute) ? attribute : null;
1789 },
1790 style: function(element) {
1791 return element.style.cssText.toLowerCase();
1792 },
1793 title: function(element) {
1794 var node = element.getAttributeNode('title');
1795 return node.specified ? node.nodeValue : null;
1796 }
1797 }
1798};
1799
1800(function() {
1801 Object.extend(this, {
1802 href: this._getAttr,
1803 src: this._getAttr,
1804 type: this._getAttr,
1805 disabled: this._flag,
1806 checked: this._flag,
1807 readonly: this._flag,
1808 multiple: this._flag
1809 });
1810}).call(Element._attributeTranslations.values);
1811
1812Element.Methods.Simulated = {
1813 hasAttribute: function(element, attribute) {
1814 var t = Element._attributeTranslations, node;
1815 attribute = t.names[attribute] || attribute;
1816 node = $(element).getAttributeNode(attribute);
1817 return node && node.specified;
1818 }
1819};
1820
1821Element.Methods.ByTag = {};
1822
1823Object.extend(Element, Element.Methods);
1824
1825if (!Prototype.BrowserFeatures.ElementExtensions &&
1826 document.createElement('div').__proto__) {
1827 window.HTMLElement = {};
1828 window.HTMLElement.prototype = document.createElement('div').__proto__;
1829 Prototype.BrowserFeatures.ElementExtensions = true;
1830}
1831
1832Element.hasAttribute = function(element, attribute) {
1833 if (element.hasAttribute) return element.hasAttribute(attribute);
1834 return Element.Methods.Simulated.hasAttribute(element, attribute);
1835};
1836
1837Element.addMethods = function(methods) {
1838 var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
1839
1840 if (!methods) {
1841 Object.extend(Form, Form.Methods);
1842 Object.extend(Form.Element, Form.Element.Methods);
1843 Object.extend(Element.Methods.ByTag, {
1844 "FORM": Object.clone(Form.Methods),
1845 "INPUT": Object.clone(Form.Element.Methods),
1846 "SELECT": Object.clone(Form.Element.Methods),
1847 "TEXTAREA": Object.clone(Form.Element.Methods)
1848 });
1849 }
1850
1851 if (arguments.length == 2) {
1852 var tagName = methods;
1853 methods = arguments[1];
1854 }
1855
1856 if (!tagName) Object.extend(Element.Methods, methods || {});
1857 else {
1858 if (tagName.constructor == Array) tagName.each(extend);
1859 else extend(tagName);
1860 }
1861
1862 function extend(tagName) {
1863 tagName = tagName.toUpperCase();
1864 if (!Element.Methods.ByTag[tagName])
1865 Element.Methods.ByTag[tagName] = {};
1866 Object.extend(Element.Methods.ByTag[tagName], methods);
1867 }
1868
1869 function copy(methods, destination, onlyIfAbsent) {
1870 onlyIfAbsent = onlyIfAbsent || false;
1871 var cache = Element.extend.cache;
1872 for (var property in methods) {
1873 var value = methods[property];
1874 if (!onlyIfAbsent || !(property in destination))
1875 destination[property] = cache.findOrStore(value);
1876 }
1877 }
1878
1879 function findDOMClass(tagName) {
1880 var klass;
1881 var trans = {
1882 "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
1883 "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
1884 "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
1885 "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
1886 "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
1887 "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
1888 "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
1889 "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
1890 "FrameSet", "IFRAME": "IFrame"
1891 };
1892 if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
1893 if (window[klass]) return window[klass];
1894 klass = 'HTML' + tagName + 'Element';
1895 if (window[klass]) return window[klass];
1896 klass = 'HTML' + tagName.capitalize() + 'Element';
1897 if (window[klass]) return window[klass];
1898
1899 window[klass] = {};
1900 window[klass].prototype = document.createElement(tagName).__proto__;
1901 return window[klass];
1902 }
1903
1904 if (F.ElementExtensions) {
1905 copy(Element.Methods, HTMLElement.prototype);
1906 copy(Element.Methods.Simulated, HTMLElement.prototype, true);
1907 }
1908
1909 if (F.SpecificElementExtensions) {
1910 for (var tag in Element.Methods.ByTag) {
1911 var klass = findDOMClass(tag);
1912 if (typeof klass == "undefined") continue;
1913 copy(T[tag], klass.prototype);
1914 }
1915 }
1916
1917 Object.extend(Element, Element.Methods);
1918 delete Element.ByTag;
1919};
1920
1921var Toggle = { display: Element.toggle };
1922
1923/*--------------------------------------------------------------------------*/
1924
1925Abstract.Insertion = function(adjacency) {
1926 this.adjacency = adjacency;
1927}
1928
1929Abstract.Insertion.prototype = {
1930 initialize: function(element, content) {
1931 this.element = $(element);
1932 this.content = content.stripScripts();
1933
1934 if (this.adjacency && this.element.insertAdjacentHTML) {
1935 try {
1936 this.element.insertAdjacentHTML(this.adjacency, this.content);
1937 } catch (e) {
1938 var tagName = this.element.tagName.toUpperCase();
1939 if (['TBODY', 'TR'].include(tagName)) {
1940 this.insertContent(this.contentFromAnonymousTable());
1941 } else {
1942 throw e;
1943 }
1944 }
1945 } else {
1946 this.range = this.element.ownerDocument.createRange();
1947 if (this.initializeRange) this.initializeRange();
1948 this.insertContent([this.range.createContextualFragment(this.content)]);
1949 }
1950
1951 setTimeout(function() {content.evalScripts()}, 10);
1952 },
1953
1954 contentFromAnonymousTable: function() {
1955 var div = document.createElement('div');
1956 div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>';
1957 return $A(div.childNodes[0].childNodes[0].childNodes);
1958 }
1959}
1960
1961var Insertion = new Object();
1962
1963Insertion.Before = Class.create();
1964Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {
1965 initializeRange: function() {
1966 this.range.setStartBefore(this.element);
1967 },
1968
1969 insertContent: function(fragments) {
1970 fragments.each((function(fragment) {
1971 this.element.parentNode.insertBefore(fragment, this.element);
1972 }).bind(this));
1973 }
1974});
1975
1976Insertion.Top = Class.create();
1977Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
1978 initializeRange: function() {
1979 this.range.selectNodeContents(this.element);
1980 this.range.collapse(true);
1981 },
1982
1983 insertContent: function(fragments) {
1984 fragments.reverse(false).each((function(fragment) {
1985 this.element.insertBefore(fragment, this.element.firstChild);
1986 }).bind(this));
1987 }
1988});
1989
1990Insertion.Bottom = Class.create();
1991Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), {
1992 initializeRange: function() {
1993 this.range.selectNodeContents(this.element);
1994 this.range.collapse(this.element);
1995 },
1996
1997 insertContent: function(fragments) {
1998 fragments.each((function(fragment) {
1999 this.element.appendChild(fragment);
2000 }).bind(this));
2001 }
2002});
2003
2004Insertion.After = Class.create();
2005Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), {
2006 initializeRange: function() {
2007 this.range.setStartAfter(this.element);
2008 },
2009
2010 insertContent: function(fragments) {
2011 fragments.each((function(fragment) {
2012 this.element.parentNode.insertBefore(fragment,
2013 this.element.nextSibling);
2014 }).bind(this));
2015 }
2016});
2017
2018/*--------------------------------------------------------------------------*/
2019
2020Element.ClassNames = Class.create();
2021Element.ClassNames.prototype = {
2022 initialize: function(element) {
2023 this.element = $(element);
2024 },
2025
2026 _each: function(iterator) {
2027 this.element.className.split(/\s+/).select(function(name) {
2028 return name.length > 0;
2029 })._each(iterator);
2030 },
2031
2032 set: function(className) {
2033 this.element.className = className;
2034 },
2035
2036 add: function(classNameToAdd) {
2037 if (this.include(classNameToAdd)) return;
2038 this.set($A(this).concat(classNameToAdd).join(' '));
2039 },
2040
2041 remove: function(classNameToRemove) {
2042 if (!this.include(classNameToRemove)) return;
2043 this.set($A(this).without(classNameToRemove).join(' '));
2044 },
2045
2046 toString: function() {
2047 return $A(this).join(' ');
2048 }
2049};
2050
2051Object.extend(Element.ClassNames.prototype, Enumerable);
2052/* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
2053 * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
2054 * license. Please see http://www.yui-ext.com/ for more information. */
2055
2056var Selector = Class.create();
2057
2058Selector.prototype = {
2059 initialize: function(expression) {
2060 this.expression = expression.strip();
2061 this.compileMatcher();
2062 },
2063
2064 compileMatcher: function() {
2065 // Selectors with namespaced attributes can't use the XPath version
2066 if (Prototype.BrowserFeatures.XPath && !(/\[[\w-]*?:/).test(this.expression))
2067 return this.compileXPathMatcher();
2068
2069 var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
2070 c = Selector.criteria, le, p, m;
2071
2072 if (Selector._cache[e]) {
2073 this.matcher = Selector._cache[e]; return;
2074 }
2075 this.matcher = ["this.matcher = function(root) {",
2076 "var r = root, h = Selector.handlers, c = false, n;"];
2077
2078 while (e && le != e && (/\S/).test(e)) {
2079 le = e;
2080 for (var i in ps) {
2081 p = ps[i];
2082 if (m = e.match(p)) {
2083 this.matcher.push(typeof c[i] == 'function' ? c[i](m) :
2084 new Template(c[i]).evaluate(m));
2085 e = e.replace(m[0], '');
2086 break;
2087 }
2088 }
2089 }
2090
2091 this.matcher.push("return h.unique(n);\n}");
2092 eval(this.matcher.join('\n'));
2093 Selector._cache[this.expression] = this.matcher;
2094 },
2095
2096 compileXPathMatcher: function() {
2097 var e = this.expression, ps = Selector.patterns,
2098 x = Selector.xpath, le, m;
2099
2100 if (Selector._cache[e]) {
2101 this.xpath = Selector._cache[e]; return;
2102 }
2103
2104 this.matcher = ['.//*'];
2105 while (e && le != e && (/\S/).test(e)) {
2106 le = e;
2107 for (var i in ps) {
2108 if (m = e.match(ps[i])) {
2109 this.matcher.push(typeof x[i] == 'function' ? x[i](m) :
2110 new Template(x[i]).evaluate(m));
2111 e = e.replace(m[0], '');
2112 break;
2113 }
2114 }
2115 }
2116
2117 this.xpath = this.matcher.join('');
2118 Selector._cache[this.expression] = this.xpath;
2119 },
2120
2121 findElements: function(root) {
2122 root = root || document;
2123 if (this.xpath) return document._getElementsByXPath(this.xpath, root);
2124 return this.matcher(root);
2125 },
2126
2127 match: function(element) {
2128 return this.findElements(document).include(element);
2129 },
2130
2131 toString: function() {
2132 return this.expression;
2133 },
2134
2135 inspect: function() {
2136 return "#<Selector:" + this.expression.inspect() + ">";
2137 }
2138};
2139
2140Object.extend(Selector, {
2141 _cache: {},
2142
2143 xpath: {
2144 descendant: "//*",
2145 child: "/*",
2146 adjacent: "/following-sibling::*[1]",
2147 laterSibling: '/following-sibling::*',
2148 tagName: function(m) {
2149 if (m[1] == '*') return '';
2150 return "[local-name()='" + m[1].toLowerCase() +
2151 "' or local-name()='" + m[1].toUpperCase() + "']";
2152 },
2153 className: "[contains(concat(' ', @class, ' '), ' #{1} ')]",
2154 id: "[@id='#{1}']",
2155 attrPresence: "[@#{1}]",
2156 attr: function(m) {
2157 m[3] = m[5] || m[6];
2158 return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
2159 },
2160 pseudo: function(m) {
2161 var h = Selector.xpath.pseudos[m[1]];
2162 if (!h) return '';
2163 if (typeof h === 'function') return h(m);
2164 return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
2165 },
2166 operators: {
2167 '=': "[@#{1}='#{3}']",
2168 '!=': "[@#{1}!='#{3}']",
2169 '^=': "[starts-with(@#{1}, '#{3}')]",
2170 '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
2171 '*=': "[contains(@#{1}, '#{3}')]",
2172 '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
2173 '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
2174 },
2175 pseudos: {
2176 'first-child': '[not(preceding-sibling::*)]',
2177 'last-child': '[not(following-sibling::*)]',
2178 'only-child': '[not(preceding-sibling::* or following-sibling::*)]',
2179 'empty': "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]",
2180 'checked': "[@checked]",
2181 'disabled': "[@disabled]",
2182 'enabled': "[not(@disabled)]",
2183 'not': function(m) {
2184 var e = m[6], p = Selector.patterns,
2185 x = Selector.xpath, le, m, v;
2186
2187 var exclusion = [];
2188 while (e && le != e && (/\S/).test(e)) {
2189 le = e;
2190 for (var i in p) {
2191 if (m = e.match(p[i])) {
2192 v = typeof x[i] == 'function' ? x[i](m) : new Template(x[i]).evaluate(m);
2193 exclusion.push("(" + v.substring(1, v.length - 1) + ")");
2194 e = e.replace(m[0], '');
2195 break;
2196 }
2197 }
2198 }
2199 return "[not(" + exclusion.join(" and ") + ")]";
2200 },
2201 'nth-child': function(m) {
2202 return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
2203 },
2204 'nth-last-child': function(m) {
2205 return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
2206 },
2207 'nth-of-type': function(m) {
2208 return Selector.xpath.pseudos.nth("position() ", m);
2209 },
2210 'nth-last-of-type': function(m) {
2211 return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
2212 },
2213 'first-of-type': function(m) {
2214 m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
2215 },
2216 'last-of-type': function(m) {
2217 m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
2218 },
2219 'only-of-type': function(m) {
2220 var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
2221 },
2222 nth: function(fragment, m) {
2223 var mm, formula = m[6], predicate;
2224 if (formula == 'even') formula = '2n+0';
2225 if (formula == 'odd') formula = '2n+1';
2226 if (mm = formula.match(/^(\d+)$/)) // digit only
2227 return '[' + fragment + "= " + mm[1] + ']';
2228 if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
2229 if (mm[1] == "-") mm[1] = -1;
2230 var a = mm[1] ? Number(mm[1]) : 1;
2231 var b = mm[2] ? Number(mm[2]) : 0;
2232 predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
2233 "((#{fragment} - #{b}) div #{a} >= 0)]";
2234 return new Template(predicate).evaluate({
2235 fragment: fragment, a: a, b: b });
2236 }
2237 }
2238 }
2239 },
2240
2241 criteria: {
2242 tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;',
2243 className: 'n = h.className(n, r, "#{1}", c); c = false;',
2244 id: 'n = h.id(n, r, "#{1}", c); c = false;',
2245 attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;',
2246 attr: function(m) {
2247 m[3] = (m[5] || m[6]);
2248 return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m);
2249 },
2250 pseudo: function(m) {
2251 if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
2252 return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
2253 },
2254 descendant: 'c = "descendant";',
2255 child: 'c = "child";',
2256 adjacent: 'c = "adjacent";',
2257 laterSibling: 'c = "laterSibling";'
2258 },
2259
2260 patterns: {
2261 // combinators must be listed first
2262 // (and descendant needs to be last combinator)
2263 laterSibling: /^\s*~\s*/,
2264 child: /^\s*>\s*/,
2265 adjacent: /^\s*\+\s*/,
2266 descendant: /^\s/,
2267
2268 // selectors follow
2269 tagName: /^\s*(\*|[\w\-]+)(\b|$)?/,
2270 id: /^#([\w\-\*]+)(\b|$)/,
2271 className: /^\.([\w\-\*]+)(\b|$)/,
2272 pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|\s|(?=:))/,
2273 attrPresence: /^\[([\w]+)\]/,
2274 attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\]]*?)\4|([^'"][^\]]*?)))?\]/
2275 },
2276
2277 handlers: {
2278 // UTILITY FUNCTIONS
2279 // joins two collections
2280 concat: function(a, b) {
2281 for (var i = 0, node; node = b[i]; i++)
2282 a.push(node);
2283 return a;
2284 },
2285
2286 // marks an array of nodes for counting
2287 mark: function(nodes) {
2288 for (var i = 0, node; node = nodes[i]; i++)
2289 node._counted = true;
2290 return nodes;
2291 },
2292
2293 unmark: function(nodes) {
2294 for (var i = 0, node; node = nodes[i]; i++)
2295 node._counted = undefined;
2296 return nodes;
2297 },
2298
2299 // mark each child node with its position (for nth calls)
2300 // "ofType" flag indicates whether we're indexing for nth-of-type
2301 // rather than nth-child
2302 index: function(parentNode, reverse, ofType) {
2303 parentNode._counted = true;
2304 if (reverse) {
2305 for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
2306 node = nodes[i];
2307 if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
2308 }
2309 } else {
2310 for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
2311 if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
2312 }
2313 },
2314
2315 // filters out duplicates and extends all nodes
2316 unique: function(nodes) {
2317 if (nodes.length == 0) return nodes;
2318 var results = [], n;
2319 for (var i = 0, l = nodes.length; i < l; i++)
2320 if (!(n = nodes[i])._counted) {
2321 n._counted = true;
2322 results.push(Element.extend(n));
2323 }
2324 return Selector.handlers.unmark(results);
2325 },
2326
2327 // COMBINATOR FUNCTIONS
2328 descendant: function(nodes) {
2329 var h = Selector.handlers;
2330 for (var i = 0, results = [], node; node = nodes[i]; i++)
2331 h.concat(results, node.getElementsByTagName('*'));
2332 return results;
2333 },
2334
2335 child: function(nodes) {
2336 var h = Selector.handlers;
2337 for (var i = 0, results = [], node; node = nodes[i]; i++) {
2338 for (var j = 0, children = [], child; child = node.childNodes[j]; j++)
2339 if (child.nodeType == 1 && child.tagName != '!') results.push(child);
2340 }
2341 return results;
2342 },
2343
2344 adjacent: function(nodes) {
2345 for (var i = 0, results = [], node; node = nodes[i]; i++) {
2346 var next = this.nextElementSibling(node);
2347 if (next) results.push(next);
2348 }
2349 return results;
2350 },
2351
2352 laterSibling: function(nodes) {
2353 var h = Selector.handlers;
2354 for (var i = 0, results = [], node; node = nodes[i]; i++)
2355 h.concat(results, Element.nextSiblings(node));
2356 return results;
2357 },
2358
2359 nextElementSibling: function(node) {
2360 while (node = node.nextSibling)
2361 if (node.nodeType == 1) return node;
2362 return null;
2363 },
2364
2365 previousElementSibling: function(node) {
2366 while (node = node.previousSibling)
2367 if (node.nodeType == 1) return node;
2368 return null;
2369 },
2370
2371 // TOKEN FUNCTIONS
2372 tagName: function(nodes, root, tagName, combinator) {
2373 tagName = tagName.toUpperCase();
2374 var results = [], h = Selector.handlers;
2375 if (nodes) {
2376 if (combinator) {
2377 // fastlane for ordinary descendant combinators
2378 if (combinator == "descendant") {
2379 for (var i = 0, node; node = nodes[i]; i++)
2380 h.concat(results, node.getElementsByTagName(tagName));
2381 return results;
2382 } else nodes = this[combinator](nodes);
2383 if (tagName == "*") return nodes;
2384 }
2385 for (var i = 0, node; node = nodes[i]; i++)
2386 if (node.tagName.toUpperCase() == tagName) results.push(node);
2387 return results;
2388 } else return root.getElementsByTagName(tagName);
2389 },
2390
2391 id: function(nodes, root, id, combinator) {
2392 var targetNode = $(id), h = Selector.handlers;
2393 if (!nodes && root == document) return targetNode ? [targetNode] : [];
2394 if (nodes) {
2395 if (combinator) {
2396 if (combinator == 'child') {
2397 for (var i = 0, node; node = nodes[i]; i++)
2398 if (targetNode.parentNode == node) return [targetNode];
2399 } else if (combinator == 'descendant') {
2400 for (var i = 0, node; node = nodes[i]; i++)
2401 if (Element.descendantOf(targetNode, node)) return [targetNode];
2402 } else if (combinator == 'adjacent') {
2403 for (var i = 0, node; node = nodes[i]; i++)
2404 if (Selector.handlers.previousElementSibling(targetNode) == node)
2405 return [targetNode];
2406 } else nodes = h[combinator](nodes);
2407 }
2408 for (var i = 0, node; node = nodes[i]; i++)
2409 if (node == targetNode) return [targetNode];
2410 return [];
2411 }
2412 return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
2413 },
2414
2415 className: function(nodes, root, className, combinator) {
2416 if (nodes && combinator) nodes = this[combinator](nodes);
2417 return Selector.handlers.byClassName(nodes, root, className);
2418 },
2419
2420 byClassName: function(nodes, root, className) {
2421 if (!nodes) nodes = Selector.handlers.descendant([root]);
2422 var needle = ' ' + className + ' ';
2423 for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
2424 nodeClassName = node.className;
2425 if (nodeClassName.length == 0) continue;
2426 if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
2427 results.push(node);
2428 }
2429 return results;
2430 },
2431
2432 attrPresence: function(nodes, root, attr) {
2433 var results = [];
2434 for (var i = 0, node; node = nodes[i]; i++)
2435 if (Element.hasAttribute(node, attr)) results.push(node);
2436 return results;
2437 },
2438
2439 attr: function(nodes, root, attr, value, operator) {
2440 if (!nodes) nodes = root.getElementsByTagName("*");
2441 var handler = Selector.operators[operator], results = [];
2442 for (var i = 0, node; node = nodes[i]; i++) {
2443 var nodeValue = Element.readAttribute(node, attr);
2444 if (nodeValue === null) continue;
2445 if (handler(nodeValue, value)) results.push(node);
2446 }
2447 return results;
2448 },
2449
2450 pseudo: function(nodes, name, value, root, combinator) {
2451 if (nodes && combinator) nodes = this[combinator](nodes);
2452 if (!nodes) nodes = root.getElementsByTagName("*");
2453 return Selector.pseudos[name](nodes, value, root);
2454 }
2455 },
2456
2457 pseudos: {
2458 'first-child': function(nodes, value, root) {
2459 for (var i = 0, results = [], node; node = nodes[i]; i++) {
2460 if (Selector.handlers.previousElementSibling(node)) continue;
2461 results.push(node);
2462 }
2463 return results;
2464 },
2465 'last-child': function(nodes, value, root) {
2466 for (var i = 0, results = [], node; node = nodes[i]; i++) {
2467 if (Selector.handlers.nextElementSibling(node)) continue;
2468 results.push(node);
2469 }
2470 return results;
2471 },
2472 'only-child': function(nodes, value, root) {
2473 var h = Selector.handlers;
2474 for (var i = 0, results = [], node; node = nodes[i]; i++)
2475 if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
2476 results.push(node);
2477 return results;
2478 },
2479 'nth-child': function(nodes, formula, root) {
2480 return Selector.pseudos.nth(nodes, formula, root);
2481 },
2482 'nth-last-child': function(nodes, formula, root) {
2483 return Selector.pseudos.nth(nodes, formula, root, true);
2484 },
2485 'nth-of-type': function(nodes, formula, root) {
2486 return Selector.pseudos.nth(nodes, formula, root, false, true);
2487 },
2488 'nth-last-of-type': function(nodes, formula, root) {
2489 return Selector.pseudos.nth(nodes, formula, root, true, true);
2490 },
2491 'first-of-type': function(nodes, formula, root) {
2492 return Selector.pseudos.nth(nodes, "1", root, false, true);
2493 },
2494 'last-of-type': function(nodes, formula, root) {
2495 return Selector.pseudos.nth(nodes, "1", root, true, true);
2496 },
2497 'only-of-type': function(nodes, formula, root) {
2498 var p = Selector.pseudos;
2499 return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
2500 },
2501
2502 // handles the an+b logic
2503 getIndices: function(a, b, total) {
2504 if (a == 0) return b > 0 ? [b] : [];
2505 return $R(1, total).inject([], function(memo, i) {
2506 if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
2507 return memo;
2508 });
2509 },
2510
2511 // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
2512 nth: function(nodes, formula, root, reverse, ofType) {
2513 if (nodes.length == 0) return [];
2514 if (formula == 'even') formula = '2n+0';
2515 if (formula == 'odd') formula = '2n+1';
2516 var h = Selector.handlers, results = [], indexed = [], m;
2517 h.mark(nodes);
2518 for (var i = 0, node; node = nodes[i]; i++) {
2519 if (!node.parentNode._counted) {
2520 h.index(node.parentNode, reverse, ofType);
2521 indexed.push(node.parentNode);
2522 }
2523 }
2524 if (formula.match(/^\d+$/)) { // just a number
2525 formula = Number(formula);
2526 for (var i = 0, node; node = nodes[i]; i++)
2527 if (node.nodeIndex == formula) results.push(node);
2528 } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
2529 if (m[1] == "-") m[1] = -1;
2530 var a = m[1] ? Number(m[1]) : 1;
2531 var b = m[2] ? Number(m[2]) : 0;
2532 var indices = Selector.pseudos.getIndices(a, b, nodes.length);
2533 for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
2534 for (var j = 0; j < l; j++)
2535 if (node.nodeIndex == indices[j]) results.push(node);
2536 }
2537 }
2538 h.unmark(nodes);
2539 h.unmark(indexed);
2540 return results;
2541 },
2542
2543 'empty': function(nodes, value, root) {
2544 for (var i = 0, results = [], node; node = nodes[i]; i++) {
2545 // IE treats comments as element nodes
2546 if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue;
2547 results.push(node);
2548 }
2549 return results;
2550 },
2551
2552 'not': function(nodes, selector, root) {
2553 var h = Selector.handlers, selectorType, m;
2554 var exclusions = new Selector(selector).findElements(root);
2555 h.mark(exclusions);
2556 for (var i = 0, results = [], node; node = nodes[i]; i++)
2557 if (!node._counted) results.push(node);
2558 h.unmark(exclusions);
2559 return results;
2560 },
2561
2562 'enabled': function(nodes, value, root) {
2563 for (var i = 0, results = [], node; node = nodes[i]; i++)
2564 if (!node.disabled) results.push(node);
2565 return results;
2566 },
2567
2568 'disabled': function(nodes, value, root) {
2569 for (var i = 0, results = [], node; node = nodes[i]; i++)
2570 if (node.disabled) results.push(node);
2571 return results;
2572 },
2573
2574 'checked': function(nodes, value, root) {
2575 for (var i = 0, results = [], node; node = nodes[i]; i++)
2576 if (node.checked) results.push(node);
2577 return results;
2578 }
2579 },
2580
2581 operators: {
2582 '=': function(nv, v) { return nv == v; },
2583 '!=': function(nv, v) { return nv != v; },
2584 '^=': function(nv, v) { return nv.startsWith(v); },
2585 '$=': function(nv, v) { return nv.endsWith(v); },
2586 '*=': function(nv, v) { return nv.include(v); },
2587 '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
2588 '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }
2589 },
2590
2591 matchElements: function(elements, expression) {
2592 var matches = new Selector(expression).findElements(), h = Selector.handlers;
2593 h.mark(matches);
2594 for (var i = 0, results = [], element; element = elements[i]; i++)
2595 if (element._counted) results.push(element);
2596 h.unmark(matches);
2597 return results;
2598 },
2599
2600 findElement: function(elements, expression, index) {
2601 if (typeof expression == 'number') {
2602 index = expression; expression = false;
2603 }
2604 return Selector.matchElements(elements, expression || '*')[index || 0];
2605 },
2606
2607 findChildElements: function(element, expressions) {
2608 var exprs = expressions.join(','), expressions = [];
2609 exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
2610 expressions.push(m[1].strip());
2611 });
2612 var results = [], h = Selector.handlers;
2613 for (var i = 0, l = expressions.length, selector; i < l; i++) {
2614 selector = new Selector(expressions[i].strip());
2615 h.concat(results, selector.findElements(element));
2616 }
2617 return (l > 1) ? h.unique(results) : results;
2618 }
2619});
2620
2621function $$() {
2622 return Selector.findChildElements(document, $A(arguments));
2623}
2624var Form = {
2625 reset: function(form) {
2626 $(form).reset();
2627 return form;
2628 },
2629
2630 serializeElements: function(elements, getHash) {
2631 var data = elements.inject({}, function(result, element) {
2632 if (!element.disabled && element.name) {
2633 var key = element.name, value = $(element).getValue();
2634 if (value != null) {
2635 if (key in result) {
2636 if (result[key].constructor != Array) result[key] = [result[key]];
2637 result[key].push(value);
2638 }
2639 else result[key] = value;
2640 }
2641 }
2642 return result;
2643 });
2644
2645 return getHash ? data : Hash.toQueryString(data);
2646 }
2647};
2648
2649Form.Methods = {
2650 serialize: function(form, getHash) {
2651 return Form.serializeElements(Form.getElements(form), getHash);
2652 },
2653
2654 getElements: function(form) {
2655 return $A($(form).getElementsByTagName('*')).inject([],
2656 function(elements, child) {
2657 if (Form.Element.Serializers[child.tagName.toLowerCase()])
2658 elements.push(Element.extend(child));
2659 return elements;
2660 }
2661 );
2662 },
2663
2664 getInputs: function(form, typeName, name) {
2665 form = $(form);
2666 var inputs = form.getElementsByTagName('input');
2667
2668 if (!typeName && !name) return $A(inputs).map(Element.extend);
2669
2670 for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
2671 var input = inputs[i];
2672 if ((typeName && input.type != typeName) || (name && input.name != name))
2673 continue;
2674 matchingInputs.push(Element.extend(input));
2675 }
2676
2677 return matchingInputs;
2678 },
2679
2680 disable: function(form) {
2681 form = $(form);
2682 Form.getElements(form).invoke('disable');
2683 return form;
2684 },
2685
2686 enable: function(form) {
2687 form = $(form);
2688 Form.getElements(form).invoke('enable');
2689 return form;
2690 },
2691
2692 findFirstElement: function(form) {
2693 return $(form).getElements().find(function(element) {
2694 return element.type != 'hidden' && !element.disabled &&
2695 ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
2696 });
2697 },
2698
2699 focusFirstElement: function(form) {
2700 form = $(form);
2701 form.findFirstElement().activate();
2702 return form;
2703 },
2704
2705 request: function(form, options) {
2706 form = $(form), options = Object.clone(options || {});
2707
2708 var params = options.parameters;
2709 options.parameters = form.serialize(true);
2710
2711 if (params) {
2712 if (typeof params == 'string') params = params.toQueryParams();
2713 Object.extend(options.parameters, params);
2714 }
2715
2716 if (form.hasAttribute('method') && !options.method)
2717 options.method = form.method;
2718
2719 return new Ajax.Request(form.readAttribute('action'), options);
2720 }
2721}
2722
2723/*--------------------------------------------------------------------------*/
2724
2725Form.Element = {
2726 focus: function(element) {
2727 $(element).focus();
2728 return element;
2729 },
2730
2731 select: function(element) {
2732 $(element).select();
2733 return element;
2734 }
2735}
2736
2737Form.Element.Methods = {
2738 serialize: function(element) {
2739 element = $(element);
2740 if (!element.disabled && element.name) {
2741 var value = element.getValue();
2742 if (value != undefined) {
2743 var pair = {};
2744 pair[element.name] = value;
2745 return Hash.toQueryString(pair);
2746 }
2747 }
2748 return '';
2749 },
2750
2751 getValue: function(element) {
2752 element = $(element);
2753 var method = element.tagName.toLowerCase();
2754 return Form.Element.Serializers[method](element);
2755 },
2756
2757 clear: function(element) {
2758 $(element).value = '';
2759 return element;
2760 },
2761
2762 present: function(element) {
2763 return $(element).value != '';
2764 },
2765
2766 activate: function(element) {
2767 element = $(element);
2768 try {
2769 element.focus();
2770 if (element.select && (element.tagName.toLowerCase() != 'input' ||
2771 !['button', 'reset', 'submit'].include(element.type)))
2772 element.select();
2773 } catch (e) {}
2774 return element;
2775 },
2776
2777 disable: function(element) {
2778 element = $(element);
2779 element.blur();
2780 element.disabled = true;
2781 return element;
2782 },
2783
2784 enable: function(element) {
2785 element = $(element);
2786 element.disabled = false;
2787 return element;
2788 }
2789}
2790
2791/*--------------------------------------------------------------------------*/
2792
2793var Field = Form.Element;
2794var $F = Form.Element.Methods.getValue;
2795
2796/*--------------------------------------------------------------------------*/
2797
2798Form.Element.Serializers = {
2799 input: function(element) {
2800 switch (element.type.toLowerCase()) {
2801 case 'checkbox':
2802 case 'radio':
2803 return Form.Element.Serializers.inputSelector(element);
2804 default:
2805 return Form.Element.Serializers.textarea(element);
2806 }
2807 },
2808
2809 inputSelector: function(element) {
2810 return element.checked ? element.value : null;
2811 },
2812
2813 textarea: function(element) {
2814 return element.value;
2815 },
2816
2817 select: function(element) {
2818 return this[element.type == 'select-one' ?
2819 'selectOne' : 'selectMany'](element);
2820 },
2821
2822 selectOne: function(element) {
2823 var index = element.selectedIndex;
2824 return index >= 0 ? this.optionValue(element.options[index]) : null;
2825 },
2826
2827 selectMany: function(element) {
2828 var values, length = element.length;
2829 if (!length) return null;
2830
2831 for (var i = 0, values = []; i < length; i++) {
2832 var opt = element.options[i];
2833 if (opt.selected) values.push(this.optionValue(opt));
2834 }
2835 return values;
2836 },
2837
2838 optionValue: function(opt) {
2839 // extend element because hasAttribute may not be native
2840 return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
2841 }
2842}
2843
2844/*--------------------------------------------------------------------------*/
2845
2846Abstract.TimedObserver = function() {}
2847Abstract.TimedObserver.prototype = {
2848 initialize: function(element, frequency, callback) {
2849 this.frequency = frequency;
2850 this.element = $(element);
2851 this.callback = callback;
2852
2853 this.lastValue = this.getValue();
2854 this.registerCallback();
2855 },
2856
2857 registerCallback: function() {
2858 setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
2859 },
2860
2861 onTimerEvent: function() {
2862 var value = this.getValue();
2863 var changed = ('string' == typeof this.lastValue && 'string' == typeof value
2864 ? this.lastValue != value : String(this.lastValue) != String(value));
2865 if (changed) {
2866 this.callback(this.element, value);
2867 this.lastValue = value;
2868 }
2869 }
2870}
2871
2872Form.Element.Observer = Class.create();
2873Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
2874 getValue: function() {
2875 return Form.Element.getValue(this.element);
2876 }
2877});
2878
2879Form.Observer = Class.create();
2880Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
2881 getValue: function() {
2882 return Form.serialize(this.element);
2883 }
2884});
2885
2886/*--------------------------------------------------------------------------*/
2887
2888Abstract.EventObserver = function() {}
2889Abstract.EventObserver.prototype = {
2890 initialize: function(element, callback) {
2891 this.element = $(element);
2892 this.callback = callback;
2893
2894 this.lastValue = this.getValue();
2895 if (this.element.tagName.toLowerCase() == 'form')
2896 this.registerFormCallbacks();
2897 else
2898 this.registerCallback(this.element);
2899 },
2900
2901 onElementEvent: function() {
2902 var value = this.getValue();
2903 if (this.lastValue != value) {
2904 this.callback(this.element, value);
2905 this.lastValue = value;
2906 }
2907 },
2908
2909 registerFormCallbacks: function() {
2910 Form.getElements(this.element).each(this.registerCallback.bind(this));
2911 },
2912
2913 registerCallback: function(element) {
2914 if (element.type) {
2915 switch (element.type.toLowerCase()) {
2916 case 'checkbox':
2917 case 'radio':
2918 Event.observe(element, 'click', this.onElementEvent.bind(this));
2919 break;
2920 default:
2921 Event.observe(element, 'change', this.onElementEvent.bind(this));
2922 break;
2923 }
2924 }
2925 }
2926}
2927
2928Form.Element.EventObserver = Class.create();
2929Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
2930 getValue: function() {
2931 return Form.Element.getValue(this.element);
2932 }
2933});
2934
2935Form.EventObserver = Class.create();
2936Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
2937 getValue: function() {
2938 return Form.serialize(this.element);
2939 }
2940});
2941if (!window.Event) {
2942 var Event = new Object();
2943}
2944
2945Object.extend(Event, {
2946 KEY_BACKSPACE: 8,
2947 KEY_TAB: 9,
2948 KEY_RETURN: 13,
2949 KEY_ESC: 27,
2950 KEY_LEFT: 37,
2951 KEY_UP: 38,
2952 KEY_RIGHT: 39,
2953 KEY_DOWN: 40,
2954 KEY_DELETE: 46,
2955 KEY_HOME: 36,
2956 KEY_END: 35,
2957 KEY_PAGEUP: 33,
2958 KEY_PAGEDOWN: 34,
2959
2960 element: function(event) {
2961 return $(event.target || event.srcElement);
2962 },
2963
2964 isLeftClick: function(event) {
2965 return (((event.which) && (event.which == 1)) ||
2966 ((event.button) && (event.button == 1)));
2967 },
2968
2969 pointerX: function(event) {
2970 return event.pageX || (event.clientX +
2971 (document.documentElement.scrollLeft || document.body.scrollLeft));
2972 },
2973
2974 pointerY: function(event) {
2975 return event.pageY || (event.clientY +
2976 (document.documentElement.scrollTop || document.body.scrollTop));
2977 },
2978
2979 stop: function(event) {
2980 if (event.preventDefault) {
2981 event.preventDefault();
2982 event.stopPropagation();
2983 } else {
2984 event.returnValue = false;
2985 event.cancelBubble = true;
2986 }
2987 },
2988
2989 // find the first node with the given tagName, starting from the
2990 // node the event was triggered on; traverses the DOM upwards
2991 findElement: function(event, tagName) {
2992 var element = Event.element(event);
2993 while (element.parentNode && (!element.tagName ||
2994 (element.tagName.toUpperCase() != tagName.toUpperCase())))
2995 element = element.parentNode;
2996 return element;
2997 },
2998
2999 observers: false,
3000
3001 _observeAndCache: function(element, name, observer, useCapture) {
3002 if (!this.observers) this.observers = [];
3003 if (element.addEventListener) {
3004 this.observers.push([element, name, observer, useCapture]);
3005 element.addEventListener(name, observer, useCapture);
3006 } else if (element.attachEvent) {
3007 this.observers.push([element, name, observer, useCapture]);
3008 element.attachEvent('on' + name, observer);
3009 }
3010 },
3011
3012 unloadCache: function() {
3013 if (!Event.observers) return;
3014 for (var i = 0, length = Event.observers.length; i < length; i++) {
3015 Event.stopObserving.apply(this, Event.observers[i]);
3016 Event.observers[i][0] = null;
3017 }
3018 Event.observers = false;
3019 },
3020
3021 observe: function(element, name, observer, useCapture) {
3022 element = $(element);
3023 useCapture = useCapture || false;
3024
3025 if (name == 'keypress' &&
3026 (Prototype.Browser.WebKit || element.attachEvent))
3027 name = 'keydown';
3028
3029 Event._observeAndCache(element, name, observer, useCapture);
3030 },
3031
3032 stopObserving: function(element, name, observer, useCapture) {
3033 element = $(element);
3034 useCapture = useCapture || false;
3035
3036 if (name == 'keypress' &&
3037 (Prototype.Browser.WebKit || element.attachEvent))
3038 name = 'keydown';
3039
3040 if (element.removeEventListener) {
3041 element.removeEventListener(name, observer, useCapture);
3042 } else if (element.detachEvent) {
3043 try {
3044 element.detachEvent('on' + name, observer);
3045 } catch (e) {}
3046 }
3047 }
3048});
3049
3050/* prevent memory leaks in IE */
3051if (Prototype.Browser.IE)
3052 Event.observe(window, 'unload', Event.unloadCache, false);
3053var Position = {
3054 // set to true if needed, warning: firefox performance problems
3055 // NOT neeeded for page scrolling, only if draggable contained in
3056 // scrollable elements
3057 includeScrollOffsets: false,
3058
3059 // must be called before calling withinIncludingScrolloffset, every time the
3060 // page is scrolled
3061 prepare: function() {
3062 this.deltaX = window.pageXOffset
3063 || document.documentElement.scrollLeft
3064 || document.body.scrollLeft
3065 || 0;
3066 this.deltaY = window.pageYOffset
3067 || document.documentElement.scrollTop
3068 || document.body.scrollTop
3069 || 0;
3070 },
3071
3072 realOffset: function(element) {
3073 var valueT = 0, valueL = 0;
3074 do {
3075 valueT += element.scrollTop || 0;
3076 valueL += element.scrollLeft || 0;
3077 element = element.parentNode;
3078 } while (element);
3079 return [valueL, valueT];
3080 },
3081
3082 cumulativeOffset: function(element) {
3083 var valueT = 0, valueL = 0;
3084 do {
3085 valueT += element.offsetTop || 0;
3086 valueL += element.offsetLeft || 0;
3087 element = element.offsetParent;
3088 } while (element);
3089 return [valueL, valueT];
3090 },
3091
3092 positionedOffset: function(element) {
3093 var valueT = 0, valueL = 0;
3094 do {
3095 valueT += element.offsetTop || 0;
3096 valueL += element.offsetLeft || 0;
3097 element = element.offsetParent;
3098 if (element) {
3099 if(element.tagName=='BODY') break;
3100 var p = Element.getStyle(element, 'position');
3101 if (p == 'relative' || p == 'absolute') break;
3102 }
3103 } while (element);
3104 return [valueL, valueT];
3105 },
3106
3107 offsetParent: function(element) {
3108 if (element.offsetParent) return element.offsetParent;
3109 if (element == document.body) return element;
3110
3111 while ((element = element.parentNode) && element != document.body)
3112 if (Element.getStyle(element, 'position') != 'static')
3113 return element;
3114
3115 return document.body;
3116 },
3117
3118 // caches x/y coordinate pair to use with overlap
3119 within: function(element, x, y) {
3120 if (this.includeScrollOffsets)
3121 return this.withinIncludingScrolloffsets(element, x, y);
3122 this.xcomp = x;
3123 this.ycomp = y;
3124 this.offset = this.cumulativeOffset(element);
3125
3126 return (y >= this.offset[1] &&
3127 y < this.offset[1] + element.offsetHeight &&
3128 x >= this.offset[0] &&
3129 x < this.offset[0] + element.offsetWidth);
3130 },
3131
3132 withinIncludingScrolloffsets: function(element, x, y) {
3133 var offsetcache = this.realOffset(element);
3134
3135 this.xcomp = x + offsetcache[0] - this.deltaX;
3136 this.ycomp = y + offsetcache[1] - this.deltaY;
3137 this.offset = this.cumulativeOffset(element);
3138
3139 return (this.ycomp >= this.offset[1] &&
3140 this.ycomp < this.offset[1] + element.offsetHeight &&
3141 this.xcomp >= this.offset[0] &&
3142 this.xcomp < this.offset[0] + element.offsetWidth);
3143 },
3144
3145 // within must be called directly before
3146 overlap: function(mode, element) {
3147 if (!mode) return 0;
3148 if (mode == 'vertical')
3149 return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
3150 element.offsetHeight;
3151 if (mode == 'horizontal')
3152 return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
3153 element.offsetWidth;
3154 },
3155
3156 page: function(forElement) {
3157 var valueT = 0, valueL = 0;
3158
3159 var element = forElement;
3160 do {
3161 valueT += element.offsetTop || 0;
3162 valueL += element.offsetLeft || 0;
3163
3164 // Safari fix
3165 if (element.offsetParent == document.body)
3166 if (Element.getStyle(element,'position')=='absolute') break;
3167
3168 } while (element = element.offsetParent);
3169
3170 element = forElement;
3171 do {
3172 if (!window.opera || element.tagName=='BODY') {
3173 valueT -= element.scrollTop || 0;
3174 valueL -= element.scrollLeft || 0;
3175 }
3176 } while (element = element.parentNode);
3177
3178 return [valueL, valueT];
3179 },
3180
3181 clone: function(source, target) {
3182 var options = Object.extend({
3183 setLeft: true,
3184 setTop: true,
3185 setWidth: true,
3186 setHeight: true,
3187 offsetTop: 0,
3188 offsetLeft: 0
3189 }, arguments[2] || {})
3190
3191 // find page position of source
3192 source = $(source);
3193 var p = Position.page(source);
3194
3195 // find coordinate system to use
3196 target = $(target);
3197 var delta = [0, 0];
3198 var parent = null;
3199 // delta [0,0] will do fine with position: fixed elements,
3200 // position:absolute needs offsetParent deltas
3201 if (Element.getStyle(target,'position') == 'absolute') {
3202 parent = Position.offsetParent(target);
3203 delta = Position.page(parent);
3204 }
3205
3206 // correct by body offsets (fixes Safari)
3207 if (parent == document.body) {
3208 delta[0] -= document.body.offsetLeft;
3209 delta[1] -= document.body.offsetTop;
3210 }
3211
3212 // set position
3213 if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px';
3214 if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px';
3215 if(options.setWidth) target.style.width = source.offsetWidth + 'px';
3216 if(options.setHeight) target.style.height = source.offsetHeight + 'px';
3217 },
3218
3219 absolutize: function(element) {
3220 element = $(element);
3221 if (element.style.position == 'absolute') return;
3222 Position.prepare();
3223
3224 var offsets = Position.positionedOffset(element);
3225 var top = offsets[1];
3226 var left = offsets[0];
3227 var width = element.clientWidth;
3228 var height = element.clientHeight;
3229
3230 element._originalLeft = left - parseFloat(element.style.left || 0);
3231 element._originalTop = top - parseFloat(element.style.top || 0);
3232 element._originalWidth = element.style.width;
3233 element._originalHeight = element.style.height;
3234
3235 element.style.position = 'absolute';
3236 element.style.top = top + 'px';
3237 element.style.left = left + 'px';
3238 element.style.width = width + 'px';
3239 element.style.height = height + 'px';
3240 },
3241
3242 relativize: function(element) {
3243 element = $(element);
3244 if (element.style.position == 'relative') return;
3245 Position.prepare();
3246
3247 element.style.position = 'relative';
3248 var top = parseFloat(element.style.top || 0) - (element._originalTop || 0);
3249 var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
3250
3251 element.style.top = top + 'px';
3252 element.style.left = left + 'px';
3253 element.style.height = element._originalHeight;
3254 element.style.width = element._originalWidth;
3255 }
3256}
3257
3258// Safari returns margins on body which is incorrect if the child is absolutely
3259// positioned. For performance reasons, redefine Position.cumulativeOffset for
3260// KHTML/WebKit only.
3261if (Prototype.Browser.WebKit) {
3262 Position.cumulativeOffset = function(element) {
3263 var valueT = 0, valueL = 0;
3264 do {
3265 valueT += element.offsetTop || 0;
3266 valueL += element.offsetLeft || 0;
3267 if (element.offsetParent == document.body)
3268 if (Element.getStyle(element, 'position') == 'absolute') break;
3269
3270 element = element.offsetParent;
3271 } while (element);
3272
3273 return [valueL, valueT];
3274 }
3275}
3276
3277Element.addMethods(); \ No newline at end of file
diff --git a/docroot/lib/scriptaculous/builder.js b/docroot/lib/scriptaculous/builder.js
new file mode 100755
index 0000000..5b4ce87
--- /dev/null
+++ b/docroot/lib/scriptaculous/builder.js
@@ -0,0 +1,136 @@
1// script.aculo.us builder.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007
2
3// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4//
5// script.aculo.us is freely distributable under the terms of an MIT-style license.
6// For details, see the script.aculo.us web site: http://script.aculo.us/
7
8var Builder = {
9 NODEMAP: {
10 AREA: 'map',
11 CAPTION: 'table',
12 COL: 'table',
13 COLGROUP: 'table',
14 LEGEND: 'fieldset',
15 OPTGROUP: 'select',
16 OPTION: 'select',
17 PARAM: 'object',
18 TBODY: 'table',
19 TD: 'table',
20 TFOOT: 'table',
21 TH: 'table',
22 THEAD: 'table',
23 TR: 'table'
24 },
25 // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
26 // due to a Firefox bug
27 node: function(elementName) {
28 elementName = elementName.toUpperCase();
29
30 // try innerHTML approach
31 var parentTag = this.NODEMAP[elementName] || 'div';
32 var parentElement = document.createElement(parentTag);
33 try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
34 parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
35 } catch(e) {}
36 var element = parentElement.firstChild || null;
37
38 // see if browser added wrapping tags
39 if(element && (element.tagName.toUpperCase() != elementName))
40 element = element.getElementsByTagName(elementName)[0];
41
42 // fallback to createElement approach
43 if(!element) element = document.createElement(elementName);
44
45 // abort if nothing could be created
46 if(!element) return;
47
48 // attributes (or text)
49 if(arguments[1])
50 if(this._isStringOrNumber(arguments[1]) ||
51 (arguments[1] instanceof Array) ||
52 arguments[1].tagName) {
53 this._children(element, arguments[1]);
54 } else {
55 var attrs = this._attributes(arguments[1]);
56 if(attrs.length) {
57 try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
58 parentElement.innerHTML = "<" +elementName + " " +
59 attrs + "></" + elementName + ">";
60 } catch(e) {}
61 element = parentElement.firstChild || null;
62 // workaround firefox 1.0.X bug
63 if(!element) {
64 element = document.createElement(elementName);
65 for(attr in arguments[1])
66 element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
67 }
68 if(element.tagName.toUpperCase() != elementName)
69 element = parentElement.getElementsByTagName(elementName)[0];
70 }
71 }
72
73 // text, or array of children
74 if(arguments[2])
75 this._children(element, arguments[2]);
76
77 return element;
78 },
79 _text: function(text) {
80 return document.createTextNode(text);
81 },
82
83 ATTR_MAP: {
84 'className': 'class',
85 'htmlFor': 'for'
86 },
87
88 _attributes: function(attributes) {
89 var attrs = [];
90 for(attribute in attributes)
91 attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
92 '="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'&quot;') + '"');
93 return attrs.join(" ");
94 },
95 _children: function(element, children) {
96 if(children.tagName) {
97 element.appendChild(children);
98 return;
99 }
100 if(typeof children=='object') { // array can hold nodes and text
101 children.flatten().each( function(e) {
102 if(typeof e=='object')
103 element.appendChild(e)
104 else
105 if(Builder._isStringOrNumber(e))
106 element.appendChild(Builder._text(e));
107 });
108 } else
109 if(Builder._isStringOrNumber(children))
110 element.appendChild(Builder._text(children));
111 },
112 _isStringOrNumber: function(param) {
113 return(typeof param=='string' || typeof param=='number');
114 },
115 build: function(html) {
116 var element = this.node('div');
117 $(element).update(html.strip());
118 return element.down();
119 },
120 dump: function(scope) {
121 if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope
122
123 var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " +
124 "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " +
125 "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+
126 "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+
127 "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+
128 "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);
129
130 tags.each( function(tag){
131 scope[tag] = function() {
132 return Builder.node.apply(Builder, [tag].concat($A(arguments)));
133 }
134 });
135 }
136}
diff --git a/docroot/lib/scriptaculous/controls.js b/docroot/lib/scriptaculous/controls.js
new file mode 100755
index 0000000..6783bd0
--- /dev/null
+++ b/docroot/lib/scriptaculous/controls.js
@@ -0,0 +1,875 @@
1// script.aculo.us controls.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007
2
3// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4// (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
5// (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
6// Contributors:
7// Richard Livsey
8// Rahul Bhargava
9// Rob Wills
10//
11// script.aculo.us is freely distributable under the terms of an MIT-style license.
12// For details, see the script.aculo.us web site: http://script.aculo.us/
13
14// Autocompleter.Base handles all the autocompletion functionality
15// that's independent of the data source for autocompletion. This
16// includes drawing the autocompletion menu, observing keyboard
17// and mouse events, and similar.
18//
19// Specific autocompleters need to provide, at the very least,
20// a getUpdatedChoices function that will be invoked every time
21// the text inside the monitored textbox changes. This method
22// should get the text for which to provide autocompletion by
23// invoking this.getToken(), NOT by directly accessing
24// this.element.value. This is to allow incremental tokenized
25// autocompletion. Specific auto-completion logic (AJAX, etc)
26// belongs in getUpdatedChoices.
27//
28// Tokenized incremental autocompletion is enabled automatically
29// when an autocompleter is instantiated with the 'tokens' option
30// in the options parameter, e.g.:
31// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
32// will incrementally autocomplete with a comma as the token.
33// Additionally, ',' in the above example can be replaced with
34// a token array, e.g. { tokens: [',', '\n'] } which
35// enables autocompletion on multiple tokens. This is most
36// useful when one of the tokens is \n (a newline), as it
37// allows smart autocompletion after linebreaks.
38
39if(typeof Effect == 'undefined')
40 throw("controls.js requires including script.aculo.us' effects.js library");
41
42var Autocompleter = {}
43Autocompleter.Base = function() {};
44Autocompleter.Base.prototype = {
45 baseInitialize: function(element, update, options) {
46 element = $(element)
47 this.element = element;
48 this.update = $(update);
49 this.hasFocus = false;
50 this.changed = false;
51 this.active = false;
52 this.index = 0;
53 this.entryCount = 0;
54
55 if(this.setOptions)
56 this.setOptions(options);
57 else
58 this.options = options || {};
59
60 this.options.paramName = this.options.paramName || this.element.name;
61 this.options.tokens = this.options.tokens || [];
62 this.options.frequency = this.options.frequency || 0.4;
63 this.options.minChars = this.options.minChars || 1;
64 this.options.onShow = this.options.onShow ||
65 function(element, update){
66 if(!update.style.position || update.style.position=='absolute') {
67 update.style.position = 'absolute';
68 Position.clone(element, update, {
69 setHeight: false,
70 offsetTop: element.offsetHeight
71 });
72 }
73 Effect.Appear(update,{duration:0.15});
74 };
75 this.options.onHide = this.options.onHide ||
76 function(element, update){ new Effect.Fade(update,{duration:0.15}) };
77
78 if(typeof(this.options.tokens) == 'string')
79 this.options.tokens = new Array(this.options.tokens);
80
81 this.observer = null;
82
83 this.element.setAttribute('autocomplete','off');
84
85 Element.hide(this.update);
86
87 Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
88 Event.observe(this.element, 'keypress', this.onKeyPress.bindAsEventListener(this));
89
90 // Turn autocomplete back on when the user leaves the page, so that the
91 // field's value will be remembered on Mozilla-based browsers.
92 Event.observe(window, 'beforeunload', function(){
93 element.setAttribute('autocomplete', 'on');
94 });
95 },
96
97 show: function() {
98 if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
99 if(!this.iefix &&
100 (Prototype.Browser.IE) &&
101 (Element.getStyle(this.update, 'position')=='absolute')) {
102 new Insertion.After(this.update,
103 '<iframe id="' + this.update.id + '_iefix" '+
104 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
105 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
106 this.iefix = $(this.update.id+'_iefix');
107 }
108 if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
109 },
110
111 fixIEOverlapping: function() {
112 Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
113 this.iefix.style.zIndex = 1;
114 this.update.style.zIndex = 2;
115 Element.show(this.iefix);
116 },
117
118 hide: function() {
119 this.stopIndicator();
120 if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
121 if(this.iefix) Element.hide(this.iefix);
122 },
123
124 startIndicator: function() {
125 if(this.options.indicator) Element.show(this.options.indicator);
126 },
127
128 stopIndicator: function() {
129 if(this.options.indicator) Element.hide(this.options.indicator);
130 },
131
132 onKeyPress: function(event) {
133 if(this.active)
134 switch(event.keyCode) {
135 case Event.KEY_TAB:
136 case Event.KEY_RETURN:
137 this.selectEntry();
138 Event.stop(event);
139 case Event.KEY_ESC:
140 this.hide();
141 this.active = false;
142 Event.stop(event);
143 return;
144 case Event.KEY_LEFT:
145 case Event.KEY_RIGHT:
146 return;
147 case Event.KEY_UP:
148 this.markPrevious();
149 this.render();
150 if(Prototype.Browser.WebKit) Event.stop(event);
151 return;
152 case Event.KEY_DOWN:
153 this.markNext();
154 this.render();
155 if(Prototype.Browser.WebKit) Event.stop(event);
156 return;
157 }
158 else
159 if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
160 (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
161
162 this.changed = true;
163 this.hasFocus = true;
164
165 if(this.observer) clearTimeout(this.observer);
166 this.observer =
167 setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
168 },
169
170 activate: function() {
171 this.changed = false;
172 this.hasFocus = true;
173 this.getUpdatedChoices();
174 },
175
176 onHover: function(event) {
177 var element = Event.findElement(event, 'LI');
178 if(this.index != element.autocompleteIndex)
179 {
180 this.index = element.autocompleteIndex;
181 this.render();
182 }
183 Event.stop(event);
184 },
185
186 onClick: function(event) {
187 var element = Event.findElement(event, 'LI');
188 this.index = element.autocompleteIndex;
189 this.selectEntry();
190 this.hide();
191 },
192
193 onBlur: function(event) {
194 // needed to make click events working
195 setTimeout(this.hide.bind(this), 250);
196 this.hasFocus = false;
197 this.active = false;
198 },
199
200 render: function() {
201 if(this.entryCount > 0) {
202 for (var i = 0; i < this.entryCount; i++)
203 this.index==i ?
204 Element.addClassName(this.getEntry(i),"selected") :
205 Element.removeClassName(this.getEntry(i),"selected");
206 if(this.hasFocus) {
207 this.show();
208 this.active = true;
209 }
210 } else {
211 this.active = false;
212 this.hide();
213 }
214 },
215
216 markPrevious: function() {
217 if(this.index > 0) this.index--
218 else this.index = this.entryCount-1;
219 this.getEntry(this.index).scrollIntoView(true);
220 },
221
222 markNext: function() {
223 if(this.index < this.entryCount-1) this.index++
224 else this.index = 0;
225 this.getEntry(this.index).scrollIntoView(false);
226 },
227
228 getEntry: function(index) {
229 return this.update.firstChild.childNodes[index];
230 },
231
232 getCurrentEntry: function() {
233 return this.getEntry(this.index);
234 },
235
236 selectEntry: function() {
237 this.active = false;
238 this.updateElement(this.getCurrentEntry());
239 },
240
241 updateElement: function(selectedElement) {
242 if (this.options.updateElement) {
243 this.options.updateElement(selectedElement);
244 return;
245 }
246 var value = '';
247 if (this.options.select) {
248 var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
249 if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
250 } else
251 value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
252
253 var lastTokenPos = this.findLastToken();
254 if (lastTokenPos != -1) {
255 var newValue = this.element.value.substr(0, lastTokenPos + 1);
256 var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
257 if (whitespace)
258 newValue += whitespace[0];
259 this.element.value = newValue + value;
260 } else {
261 this.element.value = value;
262 }
263 this.element.focus();
264
265 if (this.options.afterUpdateElement)
266 this.options.afterUpdateElement(this.element, selectedElement);
267 },
268
269 updateChoices: function(choices) {
270 if(!this.changed && this.hasFocus) {
271 this.update.innerHTML = choices;
272 Element.cleanWhitespace(this.update);
273 Element.cleanWhitespace(this.update.down());
274
275 if(this.update.firstChild && this.update.down().childNodes) {
276 this.entryCount =
277 this.update.down().childNodes.length;
278 for (var i = 0; i < this.entryCount; i++) {
279 var entry = this.getEntry(i);
280 entry.autocompleteIndex = i;
281 this.addObservers(entry);
282 }
283 } else {
284 this.entryCount = 0;
285 }
286
287 this.stopIndicator();
288 this.index = 0;
289
290 if(this.entryCount==1 && this.options.autoSelect) {
291 this.selectEntry();
292 this.hide();
293 } else {
294 this.render();
295 }
296 }
297 },
298
299 addObservers: function(element) {
300 Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
301 Event.observe(element, "click", this.onClick.bindAsEventListener(this));
302 },
303
304 onObserverEvent: function() {
305 this.changed = false;
306 if(this.getToken().length>=this.options.minChars) {
307 this.getUpdatedChoices();
308 } else {
309 this.active = false;
310 this.hide();
311 }
312 },
313
314 getToken: function() {
315 var tokenPos = this.findLastToken();
316 if (tokenPos != -1)
317 var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
318 else
319 var ret = this.element.value;
320
321 return /\n/.test(ret) ? '' : ret;
322 },
323
324 findLastToken: function() {
325 var lastTokenPos = -1;
326
327 for (var i=0; i<this.options.tokens.length; i++) {
328 var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
329 if (thisTokenPos > lastTokenPos)
330 lastTokenPos = thisTokenPos;
331 }
332 return lastTokenPos;
333 }
334}
335
336Ajax.Autocompleter = Class.create();
337Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
338 initialize: function(element, update, url, options) {
339 this.baseInitialize(element, update, options);
340 this.options.asynchronous = true;
341 this.options.onComplete = this.onComplete.bind(this);
342 this.options.defaultParams = this.options.parameters || null;
343 this.url = url;
344 },
345
346 getUpdatedChoices: function() {
347 this.startIndicator();
348
349 var entry = encodeURIComponent(this.options.paramName) + '=' +
350 encodeURIComponent(this.getToken());
351
352 this.options.parameters = this.options.callback ?
353 this.options.callback(this.element, entry) : entry;
354
355 if(this.options.defaultParams)
356 this.options.parameters += '&' + this.options.defaultParams;
357
358 new Ajax.Request(this.url, this.options);
359 },
360
361 onComplete: function(request) {
362 this.updateChoices(request.responseText);
363 }
364
365});
366
367// The local array autocompleter. Used when you'd prefer to
368// inject an array of autocompletion options into the page, rather
369// than sending out Ajax queries, which can be quite slow sometimes.
370//
371// The constructor takes four parameters. The first two are, as usual,
372// the id of the monitored textbox, and id of the autocompletion menu.
373// The third is the array you want to autocomplete from, and the fourth
374// is the options block.
375//
376// Extra local autocompletion options:
377// - choices - How many autocompletion choices to offer
378//
379// - partialSearch - If false, the autocompleter will match entered
380// text only at the beginning of strings in the
381// autocomplete array. Defaults to true, which will
382// match text at the beginning of any *word* in the
383// strings in the autocomplete array. If you want to
384// search anywhere in the string, additionally set
385// the option fullSearch to true (default: off).
386//
387// - fullSsearch - Search anywhere in autocomplete array strings.
388//
389// - partialChars - How many characters to enter before triggering
390// a partial match (unlike minChars, which defines
391// how many characters are required to do any match
392// at all). Defaults to 2.
393//
394// - ignoreCase - Whether to ignore case when autocompleting.
395// Defaults to true.
396//
397// It's possible to pass in a custom function as the 'selector'
398// option, if you prefer to write your own autocompletion logic.
399// In that case, the other options above will not apply unless
400// you support them.
401
402Autocompleter.Local = Class.create();
403Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
404 initialize: function(element, update, array, options) {
405 this.baseInitialize(element, update, options);
406 this.options.array = array;
407 },
408
409 getUpdatedChoices: function() {
410 this.updateChoices(this.options.selector(this));
411 },
412
413 setOptions: function(options) {
414 this.options = Object.extend({
415 choices: 10,
416 partialSearch: true,
417 partialChars: 2,
418 ignoreCase: true,
419 fullSearch: false,
420 selector: function(instance) {
421 var ret = []; // Beginning matches
422 var partial = []; // Inside matches
423 var entry = instance.getToken();
424 var count = 0;
425
426 for (var i = 0; i < instance.options.array.length &&
427 ret.length < instance.options.choices ; i++) {
428
429 var elem = instance.options.array[i];
430 var foundPos = instance.options.ignoreCase ?
431 elem.toLowerCase().indexOf(entry.toLowerCase()) :
432 elem.indexOf(entry);
433
434 while (foundPos != -1) {
435 if (foundPos == 0 && elem.length != entry.length) {
436 ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
437 elem.substr(entry.length) + "</li>");
438 break;
439 } else if (entry.length >= instance.options.partialChars &&
440 instance.options.partialSearch && foundPos != -1) {
441 if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
442 partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
443 elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
444 foundPos + entry.length) + "</li>");
445 break;
446 }
447 }
448
449 foundPos = instance.options.ignoreCase ?
450 elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
451 elem.indexOf(entry, foundPos + 1);
452
453 }
454 }
455 if (partial.length)
456 ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
457 return "<ul>" + ret.join('') + "</ul>";
458 }
459 }, options || {});
460 }
461});
462
463// AJAX in-place editor
464//
465// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
466
467// Use this if you notice weird scrolling problems on some browsers,
468// the DOM might be a bit confused when this gets called so do this
469// waits 1 ms (with setTimeout) until it does the activation
470Field.scrollFreeActivate = function(field) {
471 setTimeout(function() {
472 Field.activate(field);
473 }, 1);
474}
475
476Ajax.InPlaceEditor = Class.create();
477Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
478Ajax.InPlaceEditor.prototype = {
479 initialize: function(element, url, options) {
480 this.url = url;
481 this.element = $(element);
482
483 this.options = Object.extend({
484 paramName: "value",
485 okButton: true,
486 okLink: false,
487 okText: "ok",
488 cancelButton: false,
489 cancelLink: true,
490 cancelText: "cancel",
491 textBeforeControls: '',
492 textBetweenControls: '',
493 textAfterControls: '',
494 savingText: "Saving...",
495 clickToEditText: "Click to edit",
496 okText: "ok",
497 rows: 1,
498 onComplete: function(transport, element) {
499 new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
500 },
501 onFailure: function(transport) {
502 alert("Error communicating with the server: " + transport.responseText.stripTags());
503 },
504 callback: function(form) {
505 return Form.serialize(form);
506 },
507 handleLineBreaks: true,
508 loadingText: 'Loading...',
509 savingClassName: 'inplaceeditor-saving',
510 loadingClassName: 'inplaceeditor-loading',
511 formClassName: 'inplaceeditor-form',
512 highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
513 highlightendcolor: "#FFFFFF",
514 externalControl: null,
515 submitOnBlur: false,
516 ajaxOptions: {},
517 evalScripts: false
518 }, options || {});
519
520 if(!this.options.formId && this.element.id) {
521 this.options.formId = this.element.id + "-inplaceeditor";
522 if ($(this.options.formId)) {
523 // there's already a form with that name, don't specify an id
524 this.options.formId = null;
525 }
526 }
527
528 if (this.options.externalControl) {
529 this.options.externalControl = $(this.options.externalControl);
530 }
531
532 this.originalBackground = Element.getStyle(this.element, 'background-color');
533 if (!this.originalBackground) {
534 this.originalBackground = "transparent";
535 }
536
537 this.element.title = this.options.clickToEditText;
538
539 this.onclickListener = this.enterEditMode.bindAsEventListener(this);
540 this.mouseoverListener = this.enterHover.bindAsEventListener(this);
541 this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
542 Event.observe(this.element, 'click', this.onclickListener);
543 Event.observe(this.element, 'mouseover', this.mouseoverListener);
544 Event.observe(this.element, 'mouseout', this.mouseoutListener);
545 if (this.options.externalControl) {
546 Event.observe(this.options.externalControl, 'click', this.onclickListener);
547 Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
548 Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
549 }
550 },
551 enterEditMode: function(evt) {
552 if (this.saving) return;
553 if (this.editing) return;
554 this.editing = true;
555 this.onEnterEditMode();
556 if (this.options.externalControl) {
557 Element.hide(this.options.externalControl);
558 }
559 Element.hide(this.element);
560 this.createForm();
561 this.element.parentNode.insertBefore(this.form, this.element);
562 if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField);
563 // stop the event to avoid a page refresh in Safari
564 if (evt) {
565 Event.stop(evt);
566 }
567 return false;
568 },
569 createForm: function() {
570 this.form = document.createElement("form");
571 this.form.id = this.options.formId;
572 Element.addClassName(this.form, this.options.formClassName)
573 this.form.onsubmit = this.onSubmit.bind(this);
574
575 this.createEditField();
576
577 if (this.options.textarea) {
578 var br = document.createElement("br");
579 this.form.appendChild(br);
580 }
581
582 if (this.options.textBeforeControls)
583 this.form.appendChild(document.createTextNode(this.options.textBeforeControls));
584
585 if (this.options.okButton) {
586 var okButton = document.createElement("input");
587 okButton.type = "submit";
588 okButton.value = this.options.okText;
589 okButton.className = 'editor_ok_button';
590 this.form.appendChild(okButton);
591 }
592
593 if (this.options.okLink) {
594 var okLink = document.createElement("a");
595 okLink.href = "#";
596 okLink.appendChild(document.createTextNode(this.options.okText));
597 okLink.onclick = this.onSubmit.bind(this);
598 okLink.className = 'editor_ok_link';
599 this.form.appendChild(okLink);
600 }
601
602 if (this.options.textBetweenControls &&
603 (this.options.okLink || this.options.okButton) &&
604 (this.options.cancelLink || this.options.cancelButton))
605 this.form.appendChild(document.createTextNode(this.options.textBetweenControls));
606
607 if (this.options.cancelButton) {
608 var cancelButton = document.createElement("input");
609 cancelButton.type = "submit";
610 cancelButton.value = this.options.cancelText;
611 cancelButton.onclick = this.onclickCancel.bind(this);
612 cancelButton.className = 'editor_cancel_button';
613 this.form.appendChild(cancelButton);
614 }
615
616 if (this.options.cancelLink) {
617 var cancelLink = document.createElement("a");
618 cancelLink.href = "#";
619 cancelLink.appendChild(document.createTextNode(this.options.cancelText));
620 cancelLink.onclick = this.onclickCancel.bind(this);
621 cancelLink.className = 'editor_cancel editor_cancel_link';
622 this.form.appendChild(cancelLink);
623 }
624
625 if (this.options.textAfterControls)
626 this.form.appendChild(document.createTextNode(this.options.textAfterControls));
627 },
628 hasHTMLLineBreaks: function(string) {
629 if (!this.options.handleLineBreaks) return false;
630 return string.match(/<br/i) || string.match(/<p>/i);
631 },
632 convertHTMLLineBreaks: function(string) {
633 return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
634 },
635 createEditField: function() {
636 var text;
637 if(this.options.loadTextURL) {
638 text = this.options.loadingText;
639 } else {
640 text = this.getText();
641 }
642
643 var obj = this;
644
645 if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
646 this.options.textarea = false;
647 var textField = document.createElement("input");
648 textField.obj = this;
649 textField.type = "text";
650 textField.name = this.options.paramName;
651 textField.value = text;
652 textField.style.backgroundColor = this.options.highlightcolor;
653 textField.className = 'editor_field';
654 var size = this.options.size || this.options.cols || 0;
655 if (size != 0) textField.size = size;
656 if (this.options.submitOnBlur)
657 textField.onblur = this.onSubmit.bind(this);
658 this.editField = textField;
659 } else {
660 this.options.textarea = true;
661 var textArea = document.createElement("textarea");
662 textArea.obj = this;
663 textArea.name = this.options.paramName;
664 textArea.value = this.convertHTMLLineBreaks(text);
665 textArea.rows = this.options.rows;
666 textArea.cols = this.options.cols || 40;
667 textArea.className = 'editor_field';
668 if (this.options.submitOnBlur)
669 textArea.onblur = this.onSubmit.bind(this);
670 this.editField = textArea;
671 }
672
673 if(this.options.loadTextURL) {
674 this.loadExternalText();
675 }
676 this.form.appendChild(this.editField);
677 },
678 getText: function() {
679 return this.element.innerHTML;
680 },
681 loadExternalText: function() {
682 Element.addClassName(this.form, this.options.loadingClassName);
683 this.editField.disabled = true;
684 new Ajax.Request(
685 this.options.loadTextURL,
686 Object.extend({
687 asynchronous: true,
688 onComplete: this.onLoadedExternalText.bind(this)
689 }, this.options.ajaxOptions)
690 );
691 },
692 onLoadedExternalText: function(transport) {
693 Element.removeClassName(this.form, this.options.loadingClassName);
694 this.editField.disabled = false;
695 this.editField.value = transport.responseText.stripTags();
696 Field.scrollFreeActivate(this.editField);
697 },
698 onclickCancel: function() {
699 this.onComplete();
700 this.leaveEditMode();
701 return false;
702 },
703 onFailure: function(transport) {
704 this.options.onFailure(transport);
705 if (this.oldInnerHTML) {
706 this.element.innerHTML = this.oldInnerHTML;
707 this.oldInnerHTML = null;
708 }
709 return false;
710 },
711 onSubmit: function() {
712 // onLoading resets these so we need to save them away for the Ajax call
713 var form = this.form;
714 var value = this.editField.value;
715
716 // do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
717 // which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
718 // to be displayed indefinitely
719 this.onLoading();
720
721 if (this.options.evalScripts) {
722 new Ajax.Request(
723 this.url, Object.extend({
724 parameters: this.options.callback(form, value),
725 onComplete: this.onComplete.bind(this),
726 onFailure: this.onFailure.bind(this),
727 asynchronous:true,
728 evalScripts:true
729 }, this.options.ajaxOptions));
730 } else {
731 new Ajax.Updater(
732 { success: this.element,
733 // don't update on failure (this could be an option)
734 failure: null },
735 this.url, Object.extend({
736 parameters: this.options.callback(form, value),
737 onComplete: this.onComplete.bind(this),
738 onFailure: this.onFailure.bind(this)
739 }, this.options.ajaxOptions));
740 }
741 // stop the event to avoid a page refresh in Safari
742 if (arguments.length > 1) {
743 Event.stop(arguments[0]);
744 }
745 return false;
746 },
747 onLoading: function() {
748 this.saving = true;
749 this.removeForm();
750 this.leaveHover();
751 this.showSaving();
752 },
753 showSaving: function() {
754 this.oldInnerHTML = this.element.innerHTML;
755 this.element.innerHTML = this.options.savingText;
756 Element.addClassName(this.element, this.options.savingClassName);
757 this.element.style.backgroundColor = this.originalBackground;
758 Element.show(this.element);
759 },
760 removeForm: function() {
761 if(this.form) {
762 if (this.form.parentNode) Element.remove(this.form);
763 this.form = null;
764 }
765 },
766 enterHover: function() {
767 if (this.saving) return;
768 this.element.style.backgroundColor = this.options.highlightcolor;
769 if (this.effect) {
770 this.effect.cancel();
771 }
772 Element.addClassName(this.element, this.options.hoverClassName)
773 },
774 leaveHover: function() {
775 if (this.options.backgroundColor) {
776 this.element.style.backgroundColor = this.oldBackground;
777 }
778 Element.removeClassName(this.element, this.options.hoverClassName)
779 if (this.saving) return;
780 this.effect = new Effect.Highlight(this.element, {
781 startcolor: this.options.highlightcolor,
782 endcolor: this.options.highlightendcolor,
783 restorecolor: this.originalBackground
784 });
785 },
786 leaveEditMode: function() {
787 Element.removeClassName(this.element, this.options.savingClassName);
788 this.removeForm();
789 this.leaveHover();
790 this.element.style.backgroundColor = this.originalBackground;
791 Element.show(this.element);
792 if (this.options.externalControl) {
793 Element.show(this.options.externalControl);
794 }
795 this.editing = false;
796 this.saving = false;
797 this.oldInnerHTML = null;
798 this.onLeaveEditMode();
799 },
800 onComplete: function(transport) {
801 this.leaveEditMode();
802 this.options.onComplete.bind(this)(transport, this.element);
803 },
804 onEnterEditMode: function() {},
805 onLeaveEditMode: function() {},
806 dispose: function() {
807 if (this.oldInnerHTML) {
808 this.element.innerHTML = this.oldInnerHTML;
809 }
810 this.leaveEditMode();
811 Event.stopObserving(this.element, 'click', this.onclickListener);
812 Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
813 Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
814 if (this.options.externalControl) {
815 Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
816 Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
817 Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
818 }
819 }
820};
821
822Ajax.InPlaceCollectionEditor = Class.create();
823Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype);
824Object.extend(Ajax.InPlaceCollectionEditor.prototype, {
825 createEditField: function() {
826 if (!this.cached_selectTag) {
827 var selectTag = document.createElement("select");
828 var collection = this.options.collection || [];
829 var optionTag;
830 collection.each(function(e,i) {
831 optionTag = document.createElement("option");
832 optionTag.value = (e instanceof Array) ? e[0] : e;
833 if((typeof this.options.value == 'undefined') &&
834 ((e instanceof Array) ? this.element.innerHTML == e[1] : e == optionTag.value)) optionTag.selected = true;
835 if(this.options.value==optionTag.value) optionTag.selected = true;
836 optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e));
837 selectTag.appendChild(optionTag);
838 }.bind(this));
839 this.cached_selectTag = selectTag;
840 }
841
842 this.editField = this.cached_selectTag;
843 if(this.options.loadTextURL) this.loadExternalText();
844 this.form.appendChild(this.editField);
845 this.options.callback = function(form, value) {
846 return "value=" + encodeURIComponent(value);
847 }
848 }
849});
850
851// Delayed observer, like Form.Element.Observer,
852// but waits for delay after last key input
853// Ideal for live-search fields
854
855Form.Element.DelayedObserver = Class.create();
856Form.Element.DelayedObserver.prototype = {
857 initialize: function(element, delay, callback) {
858 this.delay = delay || 0.5;
859 this.element = $(element);
860 this.callback = callback;
861 this.timer = null;
862 this.lastValue = $F(this.element);
863 Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
864 },
865 delayedListener: function(event) {
866 if(this.lastValue == $F(this.element)) return;
867 if(this.timer) clearTimeout(this.timer);
868 this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
869 this.lastValue = $F(this.element);
870 },
871 onTimerEvent: function() {
872 this.timer = null;
873 this.callback(this.element, $F(this.element));
874 }
875};
diff --git a/docroot/lib/scriptaculous/dragdrop.js b/docroot/lib/scriptaculous/dragdrop.js
new file mode 100755
index 0000000..108dd66
--- /dev/null
+++ b/docroot/lib/scriptaculous/dragdrop.js
@@ -0,0 +1,970 @@
1// script.aculo.us dragdrop.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007
2
3// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4// (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
5//
6// script.aculo.us is freely distributable under the terms of an MIT-style license.
7// For details, see the script.aculo.us web site: http://script.aculo.us/
8
9if(typeof Effect == 'undefined')
10 throw("dragdrop.js requires including script.aculo.us' effects.js library");
11
12var Droppables = {
13 drops: [],
14
15 remove: function(element) {
16 this.drops = this.drops.reject(function(d) { return d.element==$(element) });
17 },
18
19 add: function(element) {
20 element = $(element);
21 var options = Object.extend({
22 greedy: true,
23 hoverclass: null,
24 tree: false
25 }, arguments[1] || {});
26
27 // cache containers
28 if(options.containment) {
29 options._containers = [];
30 var containment = options.containment;
31 if((typeof containment == 'object') &&
32 (containment.constructor == Array)) {
33 containment.each( function(c) { options._containers.push($(c)) });
34 } else {
35 options._containers.push($(containment));
36 }
37 }
38
39 if(options.accept) options.accept = [options.accept].flatten();
40
41 Element.makePositioned(element); // fix IE
42 options.element = element;
43
44 this.drops.push(options);
45 },
46
47 findDeepestChild: function(drops) {
48 deepest = drops[0];
49
50 for (i = 1; i < drops.length; ++i)
51 if (Element.isParent(drops[i].element, deepest.element))
52 deepest = drops[i];
53
54 return deepest;
55 },
56
57 isContained: function(element, drop) {
58 var containmentNode;
59 if(drop.tree) {
60 containmentNode = element.treeNode;
61 } else {
62 containmentNode = element.parentNode;
63 }
64 return drop._containers.detect(function(c) { return containmentNode == c });
65 },
66
67 isAffected: function(point, element, drop) {
68 return (
69 (drop.element!=element) &&
70 ((!drop._containers) ||
71 this.isContained(element, drop)) &&
72 ((!drop.accept) ||
73 (Element.classNames(element).detect(
74 function(v) { return drop.accept.include(v) } ) )) &&
75 Position.within(drop.element, point[0], point[1]) );
76 },
77
78 deactivate: function(drop) {
79 if(drop.hoverclass)
80 Element.removeClassName(drop.element, drop.hoverclass);
81 this.last_active = null;
82 },
83
84 activate: function(drop) {
85 if(drop.hoverclass)
86 Element.addClassName(drop.element, drop.hoverclass);
87 this.last_active = drop;
88 },
89
90 show: function(point, element) {
91 if(!this.drops.length) return;
92 var affected = [];
93
94 if(this.last_active) this.deactivate(this.last_active);
95 this.drops.each( function(drop) {
96 if(Droppables.isAffected(point, element, drop))
97 affected.push(drop);
98 });
99
100 if(affected.length>0) {
101 drop = Droppables.findDeepestChild(affected);
102 Position.within(drop.element, point[0], point[1]);
103 if(drop.onHover)
104 drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
105
106 Droppables.activate(drop);
107 }
108 },
109
110 fire: function(event, element) {
111 if(!this.last_active) return;
112 Position.prepare();
113
114 if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
115 if (this.last_active.onDrop) {
116 this.last_active.onDrop(element, this.last_active.element, event);
117 return true;
118 }
119 },
120
121 reset: function() {
122 if(this.last_active)
123 this.deactivate(this.last_active);
124 }
125}
126
127var Draggables = {
128 drags: [],
129 observers: [],
130
131 register: function(draggable) {
132 if(this.drags.length == 0) {
133 this.eventMouseUp = this.endDrag.bindAsEventListener(this);
134 this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
135 this.eventKeypress = this.keyPress.bindAsEventListener(this);
136
137 Event.observe(document, "mouseup", this.eventMouseUp);
138 Event.observe(document, "mousemove", this.eventMouseMove);
139 Event.observe(document, "keypress", this.eventKeypress);
140 }
141 this.drags.push(draggable);
142 },
143
144 unregister: function(draggable) {
145 this.drags = this.drags.reject(function(d) { return d==draggable });
146 if(this.drags.length == 0) {
147 Event.stopObserving(document, "mouseup", this.eventMouseUp);
148 Event.stopObserving(document, "mousemove", this.eventMouseMove);
149 Event.stopObserving(document, "keypress", this.eventKeypress);
150 }
151 },
152
153 activate: function(draggable) {
154 if(draggable.options.delay) {
155 this._timeout = setTimeout(function() {
156 Draggables._timeout = null;
157 window.focus();
158 Draggables.activeDraggable = draggable;
159 }.bind(this), draggable.options.delay);
160 } else {
161 window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
162 this.activeDraggable = draggable;
163 }
164 },
165
166 deactivate: function() {
167 this.activeDraggable = null;
168 },
169
170 updateDrag: function(event) {
171 if(!this.activeDraggable) return;
172 var pointer = [Event.pointerX(event), Event.pointerY(event)];
173 // Mozilla-based browsers fire successive mousemove events with
174 // the same coordinates, prevent needless redrawing (moz bug?)
175 if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
176 this._lastPointer = pointer;
177
178 this.activeDraggable.updateDrag(event, pointer);
179 },
180
181 endDrag: function(event) {
182 if(this._timeout) {
183 clearTimeout(this._timeout);
184 this._timeout = null;
185 }
186 if(!this.activeDraggable) return;
187 this._lastPointer = null;
188 this.activeDraggable.endDrag(event);
189 this.activeDraggable = null;
190 },
191
192 keyPress: function(event) {
193 if(this.activeDraggable)
194 this.activeDraggable.keyPress(event);
195 },
196
197 addObserver: function(observer) {
198 this.observers.push(observer);
199 this._cacheObserverCallbacks();
200 },
201
202 removeObserver: function(element) { // element instead of observer fixes mem leaks
203 this.observers = this.observers.reject( function(o) { return o.element==element });
204 this._cacheObserverCallbacks();
205 },
206
207 notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag'
208 if(this[eventName+'Count'] > 0)
209 this.observers.each( function(o) {
210 if(o[eventName]) o[eventName](eventName, draggable, event);
211 });
212 if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
213 },
214
215 _cacheObserverCallbacks: function() {
216 ['onStart','onEnd','onDrag'].each( function(eventName) {
217 Draggables[eventName+'Count'] = Draggables.observers.select(
218 function(o) { return o[eventName]; }
219 ).length;
220 });
221 }
222}
223
224/*--------------------------------------------------------------------------*/
225
226var Draggable = Class.create();
227Draggable._dragging = {};
228
229Draggable.prototype = {
230 initialize: function(element) {
231 var defaults = {
232 handle: false,
233 reverteffect: function(element, top_offset, left_offset) {
234 var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
235 new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
236 queue: {scope:'_draggable', position:'end'}
237 });
238 },
239 endeffect: function(element) {
240 var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0;
241 new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
242 queue: {scope:'_draggable', position:'end'},
243 afterFinish: function(){
244 Draggable._dragging[element] = false
245 }
246 });
247 },
248 zindex: 1000,
249 revert: false,
250 quiet: false,
251 scroll: false,
252 scrollSensitivity: 20,
253 scrollSpeed: 15,
254 snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] }
255 delay: 0
256 };
257
258 if(!arguments[1] || typeof arguments[1].endeffect == 'undefined')
259 Object.extend(defaults, {
260 starteffect: function(element) {
261 element._opacity = Element.getOpacity(element);
262 Draggable._dragging[element] = true;
263 new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
264 }
265 });
266
267 var options = Object.extend(defaults, arguments[1] || {});
268
269 this.element = $(element);
270
271 if(options.handle && (typeof options.handle == 'string'))
272 this.handle = this.element.down('.'+options.handle, 0);
273
274 if(!this.handle) this.handle = $(options.handle);
275 if(!this.handle) this.handle = this.element;
276
277 if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
278 options.scroll = $(options.scroll);
279 this._isScrollChild = Element.childOf(this.element, options.scroll);
280 }
281
282 Element.makePositioned(this.element); // fix IE
283
284 this.delta = this.currentDelta();
285 this.options = options;
286 this.dragging = false;
287
288 this.eventMouseDown = this.initDrag.bindAsEventListener(this);
289 Event.observe(this.handle, "mousedown", this.eventMouseDown);
290
291 Draggables.register(this);
292 },
293
294 destroy: function() {
295 Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
296 Draggables.unregister(this);
297 },
298
299 currentDelta: function() {
300 return([
301 parseInt(Element.getStyle(this.element,'left') || '0'),
302 parseInt(Element.getStyle(this.element,'top') || '0')]);
303 },
304
305 initDrag: function(event) {
306 if(typeof Draggable._dragging[this.element] != 'undefined' &&
307 Draggable._dragging[this.element]) return;
308 if(Event.isLeftClick(event)) {
309 // abort on form elements, fixes a Firefox issue
310 var src = Event.element(event);
311 if((tag_name = src.tagName.toUpperCase()) && (
312 tag_name=='INPUT' ||
313 tag_name=='SELECT' ||
314 tag_name=='OPTION' ||
315 tag_name=='BUTTON' ||
316 tag_name=='TEXTAREA')) return;
317
318 var pointer = [Event.pointerX(event), Event.pointerY(event)];
319 var pos = Position.cumulativeOffset(this.element);
320 this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
321
322 Draggables.activate(this);
323 Event.stop(event);
324 }
325 },
326
327 startDrag: function(event) {
328 this.dragging = true;
329
330 if(this.options.zindex) {
331 this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
332 this.element.style.zIndex = this.options.zindex;
333 }
334
335 if(this.options.ghosting) {
336 this._clone = this.element.cloneNode(true);
337 Position.absolutize(this.element);
338 this.element.parentNode.insertBefore(this._clone, this.element);
339 }
340
341 if(this.options.scroll) {
342 if (this.options.scroll == window) {
343 var where = this._getWindowScroll(this.options.scroll);
344 this.originalScrollLeft = where.left;
345 this.originalScrollTop = where.top;
346 } else {
347 this.originalScrollLeft = this.options.scroll.scrollLeft;
348 this.originalScrollTop = this.options.scroll.scrollTop;
349 }
350 }
351
352 Draggables.notify('onStart', this, event);
353
354 if(this.options.starteffect) this.options.starteffect(this.element);
355 },
356
357 updateDrag: function(event, pointer) {
358 if(!this.dragging) this.startDrag(event);
359
360 if(!this.options.quiet){
361 Position.prepare();
362 Droppables.show(pointer, this.element);
363 }
364
365 Draggables.notify('onDrag', this, event);
366
367 this.draw(pointer);
368 if(this.options.change) this.options.change(this);
369
370 if(this.options.scroll) {
371 this.stopScrolling();
372
373 var p;
374 if (this.options.scroll == window) {
375 with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
376 } else {
377 p = Position.page(this.options.scroll);
378 p[0] += this.options.scroll.scrollLeft + Position.deltaX;
379 p[1] += this.options.scroll.scrollTop + Position.deltaY;
380 p.push(p[0]+this.options.scroll.offsetWidth);
381 p.push(p[1]+this.options.scroll.offsetHeight);
382 }
383 var speed = [0,0];
384 if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
385 if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
386 if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
387 if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
388 this.startScrolling(speed);
389 }
390
391 // fix AppleWebKit rendering
392 if(Prototype.Browser.WebKit) window.scrollBy(0,0);
393
394 Event.stop(event);
395 },
396
397 finishDrag: function(event, success) {
398 this.dragging = false;
399
400 if(this.options.quiet){
401 Position.prepare();
402 var pointer = [Event.pointerX(event), Event.pointerY(event)];
403 Droppables.show(pointer, this.element);
404 }
405
406 if(this.options.ghosting) {
407 Position.relativize(this.element);
408 Element.remove(this._clone);
409 this._clone = null;
410 }
411
412 var dropped = false;
413 if(success) {
414 dropped = Droppables.fire(event, this.element);
415 if (!dropped) dropped = false;
416 }
417 if(dropped && this.options.onDropped) this.options.onDropped(this.element);
418 Draggables.notify('onEnd', this, event);
419
420 var revert = this.options.revert;
421 if(revert && typeof revert == 'function') revert = revert(this.element);
422
423 var d = this.currentDelta();
424 if(revert && this.options.reverteffect) {
425 if (dropped == 0 || revert != 'failure')
426 this.options.reverteffect(this.element,
427 d[1]-this.delta[1], d[0]-this.delta[0]);
428 } else {
429 this.delta = d;
430 }
431
432 if(this.options.zindex)
433 this.element.style.zIndex = this.originalZ;
434
435 if(this.options.endeffect)
436 this.options.endeffect(this.element);
437
438 Draggables.deactivate(this);
439 Droppables.reset();
440 },
441
442 keyPress: function(event) {
443 if(event.keyCode!=Event.KEY_ESC) return;
444 this.finishDrag(event, false);
445 Event.stop(event);
446 },
447
448 endDrag: function(event) {
449 if(!this.dragging) return;
450 this.stopScrolling();
451 this.finishDrag(event, true);
452 Event.stop(event);
453 },
454
455 draw: function(point) {
456 var pos = Position.cumulativeOffset(this.element);
457 if(this.options.ghosting) {
458 var r = Position.realOffset(this.element);
459 pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
460 }
461
462 var d = this.currentDelta();
463 pos[0] -= d[0]; pos[1] -= d[1];
464
465 if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
466 pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
467 pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
468 }
469
470 var p = [0,1].map(function(i){
471 return (point[i]-pos[i]-this.offset[i])
472 }.bind(this));
473
474 if(this.options.snap) {
475 if(typeof this.options.snap == 'function') {
476 p = this.options.snap(p[0],p[1],this);
477 } else {
478 if(this.options.snap instanceof Array) {
479 p = p.map( function(v, i) {
480 return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this))
481 } else {
482 p = p.map( function(v) {
483 return Math.round(v/this.options.snap)*this.options.snap }.bind(this))
484 }
485 }}
486
487 var style = this.element.style;
488 if((!this.options.constraint) || (this.options.constraint=='horizontal'))
489 style.left = p[0] + "px";
490 if((!this.options.constraint) || (this.options.constraint=='vertical'))
491 style.top = p[1] + "px";
492
493 if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
494 },
495
496 stopScrolling: function() {
497 if(this.scrollInterval) {
498 clearInterval(this.scrollInterval);
499 this.scrollInterval = null;
500 Draggables._lastScrollPointer = null;
501 }
502 },
503
504 startScrolling: function(speed) {
505 if(!(speed[0] || speed[1])) return;
506 this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
507 this.lastScrolled = new Date();
508 this.scrollInterval = setInterval(this.scroll.bind(this), 10);
509 },
510
511 scroll: function() {
512 var current = new Date();
513 var delta = current - this.lastScrolled;
514 this.lastScrolled = current;
515 if(this.options.scroll == window) {
516 with (this._getWindowScroll(this.options.scroll)) {
517 if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
518 var d = delta / 1000;
519 this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
520 }
521 }
522 } else {
523 this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
524 this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
525 }
526
527 Position.prepare();
528 Droppables.show(Draggables._lastPointer, this.element);
529 Draggables.notify('onDrag', this);
530 if (this._isScrollChild) {
531 Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
532 Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
533 Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
534 if (Draggables._lastScrollPointer[0] < 0)
535 Draggables._lastScrollPointer[0] = 0;
536 if (Draggables._lastScrollPointer[1] < 0)
537 Draggables._lastScrollPointer[1] = 0;
538 this.draw(Draggables._lastScrollPointer);
539 }
540
541 if(this.options.change) this.options.change(this);
542 },
543
544 _getWindowScroll: function(w) {
545 var T, L, W, H;
546 with (w.document) {
547 if (w.document.documentElement && documentElement.scrollTop) {
548 T = documentElement.scrollTop;
549 L = documentElement.scrollLeft;
550 } else if (w.document.body) {
551 T = body.scrollTop;
552 L = body.scrollLeft;
553 }
554 if (w.innerWidth) {
555 W = w.innerWidth;
556 H = w.innerHeight;
557 } else if (w.document.documentElement && documentElement.clientWidth) {
558 W = documentElement.clientWidth;
559 H = documentElement.clientHeight;
560 } else {
561 W = body.offsetWidth;
562 H = body.offsetHeight
563 }
564 }
565 return { top: T, left: L, width: W, height: H };
566 }
567}
568
569/*--------------------------------------------------------------------------*/
570
571var SortableObserver = Class.create();
572SortableObserver.prototype = {
573 initialize: function(element, observer) {
574 this.element = $(element);
575 this.observer = observer;
576 this.lastValue = Sortable.serialize(this.element);
577 },
578
579 onStart: function() {
580 this.lastValue = Sortable.serialize(this.element);
581 },
582
583 onEnd: function() {
584 Sortable.unmark();
585 if(this.lastValue != Sortable.serialize(this.element))
586 this.observer(this.element)
587 }
588}
589
590var Sortable = {
591 SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
592
593 sortables: {},
594
595 _findRootElement: function(element) {
596 while (element.tagName.toUpperCase() != "BODY") {
597 if(element.id && Sortable.sortables[element.id]) return element;
598 element = element.parentNode;
599 }
600 },
601
602 options: function(element) {
603 element = Sortable._findRootElement($(element));
604 if(!element) return;
605 return Sortable.sortables[element.id];
606 },
607
608 destroy: function(element){
609 var s = Sortable.options(element);
610
611 if(s) {
612 Draggables.removeObserver(s.element);
613 s.droppables.each(function(d){ Droppables.remove(d) });
614 s.draggables.invoke('destroy');
615
616 delete Sortable.sortables[s.element.id];
617 }
618 },
619
620 create: function(element) {
621 element = $(element);
622 var options = Object.extend({
623 element: element,
624 tag: 'li', // assumes li children, override with tag: 'tagname'
625 dropOnEmpty: false,
626 tree: false,
627 treeTag: 'ul',
628 overlap: 'vertical', // one of 'vertical', 'horizontal'
629 constraint: 'vertical', // one of 'vertical', 'horizontal', false
630 containment: element, // also takes array of elements (or id's); or false
631 handle: false, // or a CSS class
632 only: false,
633 delay: 0,
634 hoverclass: null,
635 ghosting: false,
636 quiet: false,
637 scroll: false,
638 scrollSensitivity: 20,
639 scrollSpeed: 15,
640 format: this.SERIALIZE_RULE,
641
642 // these take arrays of elements or ids and can be
643 // used for better initialization performance
644 elements: false,
645 handles: false,
646
647 onChange: Prototype.emptyFunction,
648 onUpdate: Prototype.emptyFunction
649 }, arguments[1] || {});
650
651 // clear any old sortable with same element
652 this.destroy(element);
653
654 // build options for the draggables
655 var options_for_draggable = {
656 revert: true,
657 quiet: options.quiet,
658 scroll: options.scroll,
659 scrollSpeed: options.scrollSpeed,
660 scrollSensitivity: options.scrollSensitivity,
661 delay: options.delay,
662 ghosting: options.ghosting,
663 constraint: options.constraint,
664 handle: options.handle };
665
666 if(options.starteffect)
667 options_for_draggable.starteffect = options.starteffect;
668
669 if(options.reverteffect)
670 options_for_draggable.reverteffect = options.reverteffect;
671 else
672 if(options.ghosting) options_for_draggable.reverteffect = function(element) {
673 element.style.top = 0;
674 element.style.left = 0;
675 };
676
677 if(options.endeffect)
678 options_for_draggable.endeffect = options.endeffect;
679
680 if(options.zindex)
681 options_for_draggable.zindex = options.zindex;
682
683 // build options for the droppables
684 var options_for_droppable = {
685 overlap: options.overlap,
686 containment: options.containment,
687 tree: options.tree,
688 hoverclass: options.hoverclass,
689 onHover: Sortable.onHover
690 }
691
692 var options_for_tree = {
693 onHover: Sortable.onEmptyHover,
694 overlap: options.overlap,
695 containment: options.containment,
696 hoverclass: options.hoverclass
697 }
698
699 // fix for gecko engine
700 Element.cleanWhitespace(element);
701
702 options.draggables = [];
703 options.droppables = [];
704
705 // drop on empty handling
706 if(options.dropOnEmpty || options.tree) {
707 Droppables.add(element, options_for_tree);
708 options.droppables.push(element);
709 }
710
711 (options.elements || this.findElements(element, options) || []).each( function(e,i) {
712 var handle = options.handles ? $(options.handles[i]) :
713 (options.handle ? $(e).getElementsByClassName(options.handle)[0] : e);
714 options.draggables.push(
715 new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
716 Droppables.add(e, options_for_droppable);
717 if(options.tree) e.treeNode = element;
718 options.droppables.push(e);
719 });
720
721 if(options.tree) {
722 (Sortable.findTreeElements(element, options) || []).each( function(e) {
723 Droppables.add(e, options_for_tree);
724 e.treeNode = element;
725 options.droppables.push(e);
726 });
727 }
728
729 // keep reference
730 this.sortables[element.id] = options;
731
732 // for onupdate
733 Draggables.addObserver(new SortableObserver(element, options.onUpdate));
734
735 },
736
737 // return all suitable-for-sortable elements in a guaranteed order
738 findElements: function(element, options) {
739 return Element.findChildren(
740 element, options.only, options.tree ? true : false, options.tag);
741 },
742
743 findTreeElements: function(element, options) {
744 return Element.findChildren(
745 element, options.only, options.tree ? true : false, options.treeTag);
746 },
747
748 onHover: function(element, dropon, overlap) {
749 if(Element.isParent(dropon, element)) return;
750
751 if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
752 return;
753 } else if(overlap>0.5) {
754 Sortable.mark(dropon, 'before');
755 if(dropon.previousSibling != element) {
756 var oldParentNode = element.parentNode;
757 element.style.visibility = "hidden"; // fix gecko rendering
758 dropon.parentNode.insertBefore(element, dropon);
759 if(dropon.parentNode!=oldParentNode)
760 Sortable.options(oldParentNode).onChange(element);
761 Sortable.options(dropon.parentNode).onChange(element);
762 }
763 } else {
764 Sortable.mark(dropon, 'after');
765 var nextElement = dropon.nextSibling || null;
766 if(nextElement != element) {
767 var oldParentNode = element.parentNode;
768 element.style.visibility = "hidden"; // fix gecko rendering
769 dropon.parentNode.insertBefore(element, nextElement);
770 if(dropon.parentNode!=oldParentNode)
771 Sortable.options(oldParentNode).onChange(element);
772 Sortable.options(dropon.parentNode).onChange(element);
773 }
774 }
775 },
776
777 onEmptyHover: function(element, dropon, overlap) {
778 var oldParentNode = element.parentNode;
779 var droponOptions = Sortable.options(dropon);
780
781 if(!Element.isParent(dropon, element)) {
782 var index;
783
784 var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
785 var child = null;
786
787 if(children) {
788 var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
789
790 for (index = 0; index < children.length; index += 1) {
791 if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
792 offset -= Element.offsetSize (children[index], droponOptions.overlap);
793 } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
794 child = index + 1 < children.length ? children[index + 1] : null;
795 break;
796 } else {
797 child = children[index];
798 break;
799 }
800 }
801 }
802
803 dropon.insertBefore(element, child);
804
805 Sortable.options(oldParentNode).onChange(element);
806 droponOptions.onChange(element);
807 }
808 },
809
810 unmark: function() {
811 if(Sortable._marker) Sortable._marker.hide();
812 },
813
814 mark: function(dropon, position) {
815 // mark on ghosting only
816 var sortable = Sortable.options(dropon.parentNode);
817 if(sortable && !sortable.ghosting) return;
818
819 if(!Sortable._marker) {
820 Sortable._marker =
821 ($('dropmarker') || Element.extend(document.createElement('DIV'))).
822 hide().addClassName('dropmarker').setStyle({position:'absolute'});
823 document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
824 }
825 var offsets = Position.cumulativeOffset(dropon);
826 Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
827
828 if(position=='after')
829 if(sortable.overlap == 'horizontal')
830 Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
831 else
832 Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
833
834 Sortable._marker.show();
835 },
836
837 _tree: function(element, options, parent) {
838 var children = Sortable.findElements(element, options) || [];
839
840 for (var i = 0; i < children.length; ++i) {
841 var match = children[i].id.match(options.format);
842
843 if (!match) continue;
844
845 var child = {
846 id: encodeURIComponent(match ? match[1] : null),
847 element: element,
848 parent: parent,
849 children: [],
850 position: parent.children.length,
851 container: $(children[i]).down(options.treeTag)
852 }
853
854 /* Get the element containing the children and recurse over it */
855 if (child.container)
856 this._tree(child.container, options, child)
857
858 parent.children.push (child);
859 }
860
861 return parent;
862 },
863
864 tree: function(element) {
865 element = $(element);
866 var sortableOptions = this.options(element);
867 var options = Object.extend({
868 tag: sortableOptions.tag,
869 treeTag: sortableOptions.treeTag,
870 only: sortableOptions.only,
871 name: element.id,
872 format: sortableOptions.format
873 }, arguments[1] || {});
874
875 var root = {
876 id: null,
877 parent: null,
878 children: [],
879 container: element,
880 position: 0
881 }
882
883 return Sortable._tree(element, options, root);
884 },
885
886 /* Construct a [i] index for a particular node */
887 _constructIndex: function(node) {
888 var index = '';
889 do {
890 if (node.id) index = '[' + node.position + ']' + index;
891 } while ((node = node.parent) != null);
892 return index;
893 },
894
895 sequence: function(element) {
896 element = $(element);
897 var options = Object.extend(this.options(element), arguments[1] || {});
898
899 return $(this.findElements(element, options) || []).map( function(item) {
900 return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
901 });
902 },
903
904 setSequence: function(element, new_sequence) {
905 element = $(element);
906 var options = Object.extend(this.options(element), arguments[2] || {});
907
908 var nodeMap = {};
909 this.findElements(element, options).each( function(n) {
910 if (n.id.match(options.format))
911 nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
912 n.parentNode.removeChild(n);
913 });
914
915 new_sequence.each(function(ident) {
916 var n = nodeMap[ident];
917 if (n) {
918 n[1].appendChild(n[0]);
919 delete nodeMap[ident];
920 }
921 });
922 },
923
924 serialize: function(element) {
925 element = $(element);
926 var options = Object.extend(Sortable.options(element), arguments[1] || {});
927 var name = encodeURIComponent(
928 (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
929
930 if (options.tree) {
931 return Sortable.tree(element, arguments[1]).children.map( function (item) {
932 return [name + Sortable._constructIndex(item) + "[id]=" +
933 encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
934 }).flatten().join('&');
935 } else {
936 return Sortable.sequence(element, arguments[1]).map( function(item) {
937 return name + "[]=" + encodeURIComponent(item);
938 }).join('&');
939 }
940 }
941}
942
943// Returns true if child is contained within element
944Element.isParent = function(child, element) {
945 if (!child.parentNode || child == element) return false;
946 if (child.parentNode == element) return true;
947 return Element.isParent(child.parentNode, element);
948}
949
950Element.findChildren = function(element, only, recursive, tagName) {
951 if(!element.hasChildNodes()) return null;
952 tagName = tagName.toUpperCase();
953 if(only) only = [only].flatten();
954 var elements = [];
955 $A(element.childNodes).each( function(e) {
956 if(e.tagName && e.tagName.toUpperCase()==tagName &&
957 (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
958 elements.push(e);
959 if(recursive) {
960 var grandchildren = Element.findChildren(e, only, recursive, tagName);
961 if(grandchildren) elements.push(grandchildren);
962 }
963 });
964
965 return (elements.length>0 ? elements.flatten() : []);
966}
967
968Element.offsetSize = function (element, type) {
969 return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
970}
diff --git a/docroot/lib/scriptaculous/effects.js b/docroot/lib/scriptaculous/effects.js
new file mode 100755
index 0000000..70d0752
--- /dev/null
+++ b/docroot/lib/scriptaculous/effects.js
@@ -0,0 +1,1094 @@
1// script.aculo.us effects.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007
2
3// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4// Contributors:
5// Justin Palmer (http://encytemedia.com/)
6// Mark Pilgrim (http://diveintomark.org/)
7// Martin Bialasinki
8//
9// script.aculo.us is freely distributable under the terms of an MIT-style license.
10// For details, see the script.aculo.us web site: http://script.aculo.us/
11
12// converts rgb() and #xxx to #xxxxxx format,
13// returns self (or first argument) if not convertable
14String.prototype.parseColor = function() {
15 var color = '#';
16 if(this.slice(0,4) == 'rgb(') {
17 var cols = this.slice(4,this.length-1).split(',');
18 var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
19 } else {
20 if(this.slice(0,1) == '#') {
21 if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
22 if(this.length==7) color = this.toLowerCase();
23 }
24 }
25 return(color.length==7 ? color : (arguments[0] || this));
26}
27
28/*--------------------------------------------------------------------------*/
29
30Element.collectTextNodes = function(element) {
31 return $A($(element).childNodes).collect( function(node) {
32 return (node.nodeType==3 ? node.nodeValue :
33 (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
34 }).flatten().join('');
35}
36
37Element.collectTextNodesIgnoreClass = function(element, className) {
38 return $A($(element).childNodes).collect( function(node) {
39 return (node.nodeType==3 ? node.nodeValue :
40 ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
41 Element.collectTextNodesIgnoreClass(node, className) : ''));
42 }).flatten().join('');
43}
44
45Element.setContentZoom = function(element, percent) {
46 element = $(element);
47 element.setStyle({fontSize: (percent/100) + 'em'});
48 if(Prototype.Browser.WebKit) window.scrollBy(0,0);
49 return element;
50}
51
52Element.getInlineOpacity = function(element){
53 return $(element).style.opacity || '';
54}
55
56Element.forceRerendering = function(element) {
57 try {
58 element = $(element);
59 var n = document.createTextNode(' ');
60 element.appendChild(n);
61 element.removeChild(n);
62 } catch(e) { }
63};
64
65/*--------------------------------------------------------------------------*/
66
67Array.prototype.call = function() {
68 var args = arguments;
69 this.each(function(f){ f.apply(this, args) });
70}
71
72/*--------------------------------------------------------------------------*/
73
74var Effect = {
75 _elementDoesNotExistError: {
76 name: 'ElementDoesNotExistError',
77 message: 'The specified DOM element does not exist, but is required for this effect to operate'
78 },
79 tagifyText: function(element) {
80 if(typeof Builder == 'undefined')
81 throw("Effect.tagifyText requires including script.aculo.us' builder.js library");
82
83 var tagifyStyle = 'position:relative';
84 if(Prototype.Browser.IE) tagifyStyle += ';zoom:1';
85
86 element = $(element);
87 $A(element.childNodes).each( function(child) {
88 if(child.nodeType==3) {
89 child.nodeValue.toArray().each( function(character) {
90 element.insertBefore(
91 Builder.node('span',{style: tagifyStyle},
92 character == ' ' ? String.fromCharCode(160) : character),
93 child);
94 });
95 Element.remove(child);
96 }
97 });
98 },
99 multiple: function(element, effect) {
100 var elements;
101 if(((typeof element == 'object') ||
102 (typeof element == 'function')) &&
103 (element.length))
104 elements = element;
105 else
106 elements = $(element).childNodes;
107
108 var options = Object.extend({
109 speed: 0.1,
110 delay: 0.0
111 }, arguments[2] || {});
112 var masterDelay = options.delay;
113
114 $A(elements).each( function(element, index) {
115 new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
116 });
117 },
118 PAIRS: {
119 'slide': ['SlideDown','SlideUp'],
120 'blind': ['BlindDown','BlindUp'],
121 'appear': ['Appear','Fade']
122 },
123 toggle: function(element, effect) {
124 element = $(element);
125 effect = (effect || 'appear').toLowerCase();
126 var options = Object.extend({
127 queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
128 }, arguments[2] || {});
129 Effect[element.visible() ?
130 Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
131 }
132};
133
134var Effect2 = Effect; // deprecated
135
136/* ------------- transitions ------------- */
137
138Effect.Transitions = {
139 linear: Prototype.K,
140 sinoidal: function(pos) {
141 return (-Math.cos(pos*Math.PI)/2) + 0.5;
142 },
143 reverse: function(pos) {
144 return 1-pos;
145 },
146 flicker: function(pos) {
147 var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
148 return (pos > 1 ? 1 : pos);
149 },
150 wobble: function(pos) {
151 return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
152 },
153 pulse: function(pos, pulses) {
154 pulses = pulses || 5;
155 return (
156 Math.round((pos % (1/pulses)) * pulses) == 0 ?
157 ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) :
158 1 - ((pos * pulses * 2) - Math.floor(pos * pulses * 2))
159 );
160 },
161 none: function(pos) {
162 return 0;
163 },
164 full: function(pos) {
165 return 1;
166 }
167};
168
169/* ------------- core effects ------------- */
170
171Effect.ScopedQueue = Class.create();
172Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), {
173 initialize: function() {
174 this.effects = [];
175 this.interval = null;
176 },
177 _each: function(iterator) {
178 this.effects._each(iterator);
179 },
180 add: function(effect) {
181 var timestamp = new Date().getTime();
182
183 var position = (typeof effect.options.queue == 'string') ?
184 effect.options.queue : effect.options.queue.position;
185
186 switch(position) {
187 case 'front':
188 // move unstarted effects after this effect
189 this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
190 e.startOn += effect.finishOn;
191 e.finishOn += effect.finishOn;
192 });
193 break;
194 case 'with-last':
195 timestamp = this.effects.pluck('startOn').max() || timestamp;
196 break;
197 case 'end':
198 // start effect after last queued effect has finished
199 timestamp = this.effects.pluck('finishOn').max() || timestamp;
200 break;
201 }
202
203 effect.startOn += timestamp;
204 effect.finishOn += timestamp;
205
206 if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
207 this.effects.push(effect);
208
209 if(!this.interval)
210 this.interval = setInterval(this.loop.bind(this), 15);
211 },
212 remove: function(effect) {
213 this.effects = this.effects.reject(function(e) { return e==effect });
214 if(this.effects.length == 0) {
215 clearInterval(this.interval);
216 this.interval = null;
217 }
218 },
219 loop: function() {
220 var timePos = new Date().getTime();
221 for(var i=0, len=this.effects.length;i<len;i++)
222 this.effects[i] && this.effects[i].loop(timePos);
223 }
224});
225
226Effect.Queues = {
227 instances: $H(),
228 get: function(queueName) {
229 if(typeof queueName != 'string') return queueName;
230
231 if(!this.instances[queueName])
232 this.instances[queueName] = new Effect.ScopedQueue();
233
234 return this.instances[queueName];
235 }
236}
237Effect.Queue = Effect.Queues.get('global');
238
239Effect.DefaultOptions = {
240 transition: Effect.Transitions.sinoidal,
241 duration: 1.0, // seconds
242 fps: 100, // 100= assume 66fps max.
243 sync: false, // true for combining
244 from: 0.0,
245 to: 1.0,
246 delay: 0.0,
247 queue: 'parallel'
248}
249
250Effect.Base = function() {};
251Effect.Base.prototype = {
252 position: null,
253 start: function(options) {
254 function codeForEvent(options,eventName){
255 return (
256 (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
257 (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
258 );
259 }
260 if(options.transition === false) options.transition = Effect.Transitions.linear;
261 this.options = Object.extend(Object.extend({},Effect.DefaultOptions), options || {});
262 this.currentFrame = 0;
263 this.state = 'idle';
264 this.startOn = this.options.delay*1000;
265 this.finishOn = this.startOn+(this.options.duration*1000);
266 this.fromToDelta = this.options.to-this.options.from;
267 this.totalTime = this.finishOn-this.startOn;
268 this.totalFrames = this.options.fps*this.options.duration;
269
270 eval('this.render = function(pos){ '+
271 'if(this.state=="idle"){this.state="running";'+
272 codeForEvent(options,'beforeSetup')+
273 (this.setup ? 'this.setup();':'')+
274 codeForEvent(options,'afterSetup')+
275 '};if(this.state=="running"){'+
276 'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+
277 'this.position=pos;'+
278 codeForEvent(options,'beforeUpdate')+
279 (this.update ? 'this.update(pos);':'')+
280 codeForEvent(options,'afterUpdate')+
281 '}}');
282
283 this.event('beforeStart');
284 if(!this.options.sync)
285 Effect.Queues.get(typeof this.options.queue == 'string' ?
286 'global' : this.options.queue.scope).add(this);
287 },
288 loop: function(timePos) {
289 if(timePos >= this.startOn) {
290 if(timePos >= this.finishOn) {
291 this.render(1.0);
292 this.cancel();
293 this.event('beforeFinish');
294 if(this.finish) this.finish();
295 this.event('afterFinish');
296 return;
297 }
298 var pos = (timePos - this.startOn) / this.totalTime,
299 frame = Math.round(pos * this.totalFrames);
300 if(frame > this.currentFrame) {
301 this.render(pos);
302 this.currentFrame = frame;
303 }
304 }
305 },
306 cancel: function() {
307 if(!this.options.sync)
308 Effect.Queues.get(typeof this.options.queue == 'string' ?
309 'global' : this.options.queue.scope).remove(this);
310 this.state = 'finished';
311 },
312 event: function(eventName) {
313 if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
314 if(this.options[eventName]) this.options[eventName](this);
315 },
316 inspect: function() {
317 var data = $H();
318 for(property in this)
319 if(typeof this[property] != 'function') data[property] = this[property];
320 return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
321 }
322}
323
324Effect.Parallel = Class.create();
325Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
326 initialize: function(effects) {
327 this.effects = effects || [];
328 this.start(arguments[1]);
329 },
330 update: function(position) {
331 this.effects.invoke('render', position);
332 },
333 finish: function(position) {
334 this.effects.each( function(effect) {
335 effect.render(1.0);
336 effect.cancel();
337 effect.event('beforeFinish');
338 if(effect.finish) effect.finish(position);
339 effect.event('afterFinish');
340 });
341 }
342});
343
344Effect.Event = Class.create();
345Object.extend(Object.extend(Effect.Event.prototype, Effect.Base.prototype), {
346 initialize: function() {
347 var options = Object.extend({
348 duration: 0
349 }, arguments[0] || {});
350 this.start(options);
351 },
352 update: Prototype.emptyFunction
353});
354
355Effect.Opacity = Class.create();
356Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
357 initialize: function(element) {
358 this.element = $(element);
359 if(!this.element) throw(Effect._elementDoesNotExistError);
360 // make this work on IE on elements without 'layout'
361 if(Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
362 this.element.setStyle({zoom: 1});
363 var options = Object.extend({
364 from: this.element.getOpacity() || 0.0,
365 to: 1.0
366 }, arguments[1] || {});
367 this.start(options);
368 },
369 update: function(position) {
370 this.element.setOpacity(position);
371 }
372});
373
374Effect.Move = Class.create();
375Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), {
376 initialize: function(element) {
377 this.element = $(element);
378 if(!this.element) throw(Effect._elementDoesNotExistError);
379 var options = Object.extend({
380 x: 0,
381 y: 0,
382 mode: 'relative'
383 }, arguments[1] || {});
384 this.start(options);
385 },
386 setup: function() {
387 // Bug in Opera: Opera returns the "real" position of a static element or
388 // relative element that does not have top/left explicitly set.
389 // ==> Always set top and left for position relative elements in your stylesheets
390 // (to 0 if you do not need them)
391 this.element.makePositioned();
392 this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
393 this.originalTop = parseFloat(this.element.getStyle('top') || '0');
394 if(this.options.mode == 'absolute') {
395 // absolute movement, so we need to calc deltaX and deltaY
396 this.options.x = this.options.x - this.originalLeft;
397 this.options.y = this.options.y - this.originalTop;
398 }
399 },
400 update: function(position) {
401 this.element.setStyle({
402 left: Math.round(this.options.x * position + this.originalLeft) + 'px',
403 top: Math.round(this.options.y * position + this.originalTop) + 'px'
404 });
405 }
406});
407
408// for backwards compatibility
409Effect.MoveBy = function(element, toTop, toLeft) {
410 return new Effect.Move(element,
411 Object.extend({ x: toLeft, y: toTop }, arguments[3] || {}));
412};
413
414Effect.Scale = Class.create();
415Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
416 initialize: function(element, percent) {
417 this.element = $(element);
418 if(!this.element) throw(Effect._elementDoesNotExistError);
419 var options = Object.extend({
420 scaleX: true,
421 scaleY: true,
422 scaleContent: true,
423 scaleFromCenter: false,
424 scaleMode: 'box', // 'box' or 'contents' or {} with provided values
425 scaleFrom: 100.0,
426 scaleTo: percent
427 }, arguments[2] || {});
428 this.start(options);
429 },
430 setup: function() {
431 this.restoreAfterFinish = this.options.restoreAfterFinish || false;
432 this.elementPositioning = this.element.getStyle('position');
433
434 this.originalStyle = {};
435 ['top','left','width','height','fontSize'].each( function(k) {
436 this.originalStyle[k] = this.element.style[k];
437 }.bind(this));
438
439 this.originalTop = this.element.offsetTop;
440 this.originalLeft = this.element.offsetLeft;
441
442 var fontSize = this.element.getStyle('font-size') || '100%';
443 ['em','px','%','pt'].each( function(fontSizeType) {
444 if(fontSize.indexOf(fontSizeType)>0) {
445 this.fontSize = parseFloat(fontSize);
446 this.fontSizeType = fontSizeType;
447 }
448 }.bind(this));
449
450 this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
451
452 this.dims = null;
453 if(this.options.scaleMode=='box')
454 this.dims = [this.element.offsetHeight, this.element.offsetWidth];
455 if(/^content/.test(this.options.scaleMode))
456 this.dims = [this.element.scrollHeight, this.element.scrollWidth];
457 if(!this.dims)
458 this.dims = [this.options.scaleMode.originalHeight,
459 this.options.scaleMode.originalWidth];
460 },
461 update: function(position) {
462 var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
463 if(this.options.scaleContent && this.fontSize)
464 this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
465 this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
466 },
467 finish: function(position) {
468 if(this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
469 },
470 setDimensions: function(height, width) {
471 var d = {};
472 if(this.options.scaleX) d.width = Math.round(width) + 'px';
473 if(this.options.scaleY) d.height = Math.round(height) + 'px';
474 if(this.options.scaleFromCenter) {
475 var topd = (height - this.dims[0])/2;
476 var leftd = (width - this.dims[1])/2;
477 if(this.elementPositioning == 'absolute') {
478 if(this.options.scaleY) d.top = this.originalTop-topd + 'px';
479 if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
480 } else {
481 if(this.options.scaleY) d.top = -topd + 'px';
482 if(this.options.scaleX) d.left = -leftd + 'px';
483 }
484 }
485 this.element.setStyle(d);
486 }
487});
488
489Effect.Highlight = Class.create();
490Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
491 initialize: function(element) {
492 this.element = $(element);
493 if(!this.element) throw(Effect._elementDoesNotExistError);
494 var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {});
495 this.start(options);
496 },
497 setup: function() {
498 // Prevent executing on elements not in the layout flow
499 if(this.element.getStyle('display')=='none') { this.cancel(); return; }
500 // Disable background image during the effect
501 this.oldStyle = {};
502 if (!this.options.keepBackgroundImage) {
503 this.oldStyle.backgroundImage = this.element.getStyle('background-image');
504 this.element.setStyle({backgroundImage: 'none'});
505 }
506 if(!this.options.endcolor)
507 this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
508 if(!this.options.restorecolor)
509 this.options.restorecolor = this.element.getStyle('background-color');
510 // init color calculations
511 this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
512 this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
513 },
514 update: function(position) {
515 this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
516 return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) });
517 },
518 finish: function() {
519 this.element.setStyle(Object.extend(this.oldStyle, {
520 backgroundColor: this.options.restorecolor
521 }));
522 }
523});
524
525Effect.ScrollTo = Class.create();
526Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
527 initialize: function(element) {
528 this.element = $(element);
529 this.start(arguments[1] || {});
530 },
531 setup: function() {
532 Position.prepare();
533 var offsets = Position.cumulativeOffset(this.element);
534 if(this.options.offset) offsets[1] += this.options.offset;
535 var max = window.innerHeight ?
536 window.height - window.innerHeight :
537 document.body.scrollHeight -
538 (document.documentElement.clientHeight ?
539 document.documentElement.clientHeight : document.body.clientHeight);
540 this.scrollStart = Position.deltaY;
541 this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
542 },
543 update: function(position) {
544 Position.prepare();
545 window.scrollTo(Position.deltaX,
546 this.scrollStart + (position*this.delta));
547 }
548});
549
550/* ------------- combination effects ------------- */
551
552Effect.Fade = function(element) {
553 element = $(element);
554 var oldOpacity = element.getInlineOpacity();
555 var options = Object.extend({
556 from: element.getOpacity() || 1.0,
557 to: 0.0,
558 afterFinishInternal: function(effect) {
559 if(effect.options.to!=0) return;
560 effect.element.hide().setStyle({opacity: oldOpacity});
561 }}, arguments[1] || {});
562 return new Effect.Opacity(element,options);
563}
564
565Effect.Appear = function(element) {
566 element = $(element);
567 var options = Object.extend({
568 from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
569 to: 1.0,
570 // force Safari to render floated elements properly
571 afterFinishInternal: function(effect) {
572 effect.element.forceRerendering();
573 },
574 beforeSetup: function(effect) {
575 effect.element.setOpacity(effect.options.from).show();
576 }}, arguments[1] || {});
577 return new Effect.Opacity(element,options);
578}
579
580Effect.Puff = function(element) {
581 element = $(element);
582 var oldStyle = {
583 opacity: element.getInlineOpacity(),
584 position: element.getStyle('position'),
585 top: element.style.top,
586 left: element.style.left,
587 width: element.style.width,
588 height: element.style.height
589 };
590 return new Effect.Parallel(
591 [ new Effect.Scale(element, 200,
592 { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
593 new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
594 Object.extend({ duration: 1.0,
595 beforeSetupInternal: function(effect) {
596 Position.absolutize(effect.effects[0].element)
597 },
598 afterFinishInternal: function(effect) {
599 effect.effects[0].element.hide().setStyle(oldStyle); }
600 }, arguments[1] || {})
601 );
602}
603
604Effect.BlindUp = function(element) {
605 element = $(element);
606 element.makeClipping();
607 return new Effect.Scale(element, 0,
608 Object.extend({ scaleContent: false,
609 scaleX: false,
610 restoreAfterFinish: true,
611 afterFinishInternal: function(effect) {
612 effect.element.hide().undoClipping();
613 }
614 }, arguments[1] || {})
615 );
616}
617
618Effect.BlindDown = function(element) {
619 element = $(element);
620 var elementDimensions = element.getDimensions();
621 return new Effect.Scale(element, 100, Object.extend({
622 scaleContent: false,
623 scaleX: false,
624 scaleFrom: 0,
625 scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
626 restoreAfterFinish: true,
627 afterSetup: function(effect) {
628 effect.element.makeClipping().setStyle({height: '0px'}).show();
629 },
630 afterFinishInternal: function(effect) {
631 effect.element.undoClipping();
632 }
633 }, arguments[1] || {}));
634}
635
636Effect.SwitchOff = function(element) {
637 element = $(element);
638 var oldOpacity = element.getInlineOpacity();
639 return new Effect.Appear(element, Object.extend({
640 duration: 0.4,
641 from: 0,
642 transition: Effect.Transitions.flicker,
643 afterFinishInternal: function(effect) {
644 new Effect.Scale(effect.element, 1, {
645 duration: 0.3, scaleFromCenter: true,
646 scaleX: false, scaleContent: false, restoreAfterFinish: true,
647 beforeSetup: function(effect) {
648 effect.element.makePositioned().makeClipping();
649 },
650 afterFinishInternal: function(effect) {
651 effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
652 }
653 })
654 }
655 }, arguments[1] || {}));
656}
657
658Effect.DropOut = function(element) {
659 element = $(element);
660 var oldStyle = {
661 top: element.getStyle('top'),
662 left: element.getStyle('left'),
663 opacity: element.getInlineOpacity() };
664 return new Effect.Parallel(
665 [ new Effect.Move(element, {x: 0, y: 100, sync: true }),
666 new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
667 Object.extend(
668 { duration: 0.5,
669 beforeSetup: function(effect) {
670 effect.effects[0].element.makePositioned();
671 },
672 afterFinishInternal: function(effect) {
673 effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
674 }
675 }, arguments[1] || {}));
676}
677
678Effect.Shake = function(element) {
679 element = $(element);
680 var oldStyle = {
681 top: element.getStyle('top'),
682 left: element.getStyle('left') };
683 return new Effect.Move(element,
684 { x: 20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
685 new Effect.Move(effect.element,
686 { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
687 new Effect.Move(effect.element,
688 { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
689 new Effect.Move(effect.element,
690 { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
691 new Effect.Move(effect.element,
692 { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
693 new Effect.Move(effect.element,
694 { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
695 effect.element.undoPositioned().setStyle(oldStyle);
696 }}) }}) }}) }}) }}) }});
697}
698
699Effect.SlideDown = function(element) {
700 element = $(element).cleanWhitespace();
701 // SlideDown need to have the content of the element wrapped in a container element with fixed height!
702 var oldInnerBottom = element.down().getStyle('bottom');
703 var elementDimensions = element.getDimensions();
704 return new Effect.Scale(element, 100, Object.extend({
705 scaleContent: false,
706 scaleX: false,
707 scaleFrom: window.opera ? 0 : 1,
708 scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
709 restoreAfterFinish: true,
710 afterSetup: function(effect) {
711 effect.element.makePositioned();
712 effect.element.down().makePositioned();
713 if(window.opera) effect.element.setStyle({top: ''});
714 effect.element.makeClipping().setStyle({height: '0px'}).show();
715 },
716 afterUpdateInternal: function(effect) {
717 effect.element.down().setStyle({bottom:
718 (effect.dims[0] - effect.element.clientHeight) + 'px' });
719 },
720 afterFinishInternal: function(effect) {
721 effect.element.undoClipping().undoPositioned();
722 effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
723 }, arguments[1] || {})
724 );
725}
726
727Effect.SlideUp = function(element) {
728 element = $(element).cleanWhitespace();
729 var oldInnerBottom = element.down().getStyle('bottom');
730 return new Effect.Scale(element, window.opera ? 0 : 1,
731 Object.extend({ scaleContent: false,
732 scaleX: false,
733 scaleMode: 'box',
734 scaleFrom: 100,
735 restoreAfterFinish: true,
736 beforeStartInternal: function(effect) {
737 effect.element.makePositioned();
738 effect.element.down().makePositioned();
739 if(window.opera) effect.element.setStyle({top: ''});
740 effect.element.makeClipping().show();
741 },
742 afterUpdateInternal: function(effect) {
743 effect.element.down().setStyle({bottom:
744 (effect.dims[0] - effect.element.clientHeight) + 'px' });
745 },
746 afterFinishInternal: function(effect) {
747 effect.element.hide().undoClipping().undoPositioned().setStyle({bottom: oldInnerBottom});
748 effect.element.down().undoPositioned();
749 }
750 }, arguments[1] || {})
751 );
752}
753
754// Bug in opera makes the TD containing this element expand for a instance after finish
755Effect.Squish = function(element) {
756 return new Effect.Scale(element, window.opera ? 1 : 0, {
757 restoreAfterFinish: true,
758 beforeSetup: function(effect) {
759 effect.element.makeClipping();
760 },
761 afterFinishInternal: function(effect) {
762 effect.element.hide().undoClipping();
763 }
764 });
765}
766
767Effect.Grow = function(element) {
768 element = $(element);
769 var options = Object.extend({
770 direction: 'center',
771 moveTransition: Effect.Transitions.sinoidal,
772 scaleTransition: Effect.Transitions.sinoidal,
773 opacityTransition: Effect.Transitions.full
774 }, arguments[1] || {});
775 var oldStyle = {
776 top: element.style.top,
777 left: element.style.left,
778 height: element.style.height,
779 width: element.style.width,
780 opacity: element.getInlineOpacity() };
781
782 var dims = element.getDimensions();
783 var initialMoveX, initialMoveY;
784 var moveX, moveY;
785
786 switch (options.direction) {
787 case 'top-left':
788 initialMoveX = initialMoveY = moveX = moveY = 0;
789 break;
790 case 'top-right':
791 initialMoveX = dims.width;
792 initialMoveY = moveY = 0;
793 moveX = -dims.width;
794 break;
795 case 'bottom-left':
796 initialMoveX = moveX = 0;
797 initialMoveY = dims.height;
798 moveY = -dims.height;
799 break;
800 case 'bottom-right':
801 initialMoveX = dims.width;
802 initialMoveY = dims.height;
803 moveX = -dims.width;
804 moveY = -dims.height;
805 break;
806 case 'center':
807 initialMoveX = dims.width / 2;
808 initialMoveY = dims.height / 2;
809 moveX = -dims.width / 2;
810 moveY = -dims.height / 2;
811 break;
812 }
813
814 return new Effect.Move(element, {
815 x: initialMoveX,
816 y: initialMoveY,
817 duration: 0.01,
818 beforeSetup: function(effect) {
819 effect.element.hide().makeClipping().makePositioned();
820 },
821 afterFinishInternal: function(effect) {
822 new Effect.Parallel(
823 [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
824 new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
825 new Effect.Scale(effect.element, 100, {
826 scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
827 sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
828 ], Object.extend({
829 beforeSetup: function(effect) {
830 effect.effects[0].element.setStyle({height: '0px'}).show();
831 },
832 afterFinishInternal: function(effect) {
833 effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);
834 }
835 }, options)
836 )
837 }
838 });
839}
840
841Effect.Shrink = function(element) {
842 element = $(element);
843 var options = Object.extend({
844 direction: 'center',
845 moveTransition: Effect.Transitions.sinoidal,
846 scaleTransition: Effect.Transitions.sinoidal,
847 opacityTransition: Effect.Transitions.none
848 }, arguments[1] || {});
849 var oldStyle = {
850 top: element.style.top,
851 left: element.style.left,
852 height: element.style.height,
853 width: element.style.width,
854 opacity: element.getInlineOpacity() };
855
856 var dims = element.getDimensions();
857 var moveX, moveY;
858
859 switch (options.direction) {
860 case 'top-left':
861 moveX = moveY = 0;
862 break;
863 case 'top-right':
864 moveX = dims.width;
865 moveY = 0;
866 break;
867 case 'bottom-left':
868 moveX = 0;
869 moveY = dims.height;
870 break;
871 case 'bottom-right':
872 moveX = dims.width;
873 moveY = dims.height;
874 break;
875 case 'center':
876 moveX = dims.width / 2;
877 moveY = dims.height / 2;
878 break;
879 }
880
881 return new Effect.Parallel(
882 [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
883 new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
884 new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
885 ], Object.extend({
886 beforeStartInternal: function(effect) {
887 effect.effects[0].element.makePositioned().makeClipping();
888 },
889 afterFinishInternal: function(effect) {
890 effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
891 }, options)
892 );
893}
894
895Effect.Pulsate = function(element) {
896 element = $(element);
897 var options = arguments[1] || {};
898 var oldOpacity = element.getInlineOpacity();
899 var transition = options.transition || Effect.Transitions.sinoidal;
900 var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
901 reverser.bind(transition);
902 return new Effect.Opacity(element,
903 Object.extend(Object.extend({ duration: 2.0, from: 0,
904 afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
905 }, options), {transition: reverser}));
906}
907
908Effect.Fold = function(element) {
909 element = $(element);
910 var oldStyle = {
911 top: element.style.top,
912 left: element.style.left,
913 width: element.style.width,
914 height: element.style.height };
915 element.makeClipping();
916 return new Effect.Scale(element, 5, Object.extend({
917 scaleContent: false,
918 scaleX: false,
919 afterFinishInternal: function(effect) {
920 new Effect.Scale(element, 1, {
921 scaleContent: false,
922 scaleY: false,
923 afterFinishInternal: function(effect) {
924 effect.element.hide().undoClipping().setStyle(oldStyle);
925 } });
926 }}, arguments[1] || {}));
927};
928
929Effect.Morph = Class.create();
930Object.extend(Object.extend(Effect.Morph.prototype, Effect.Base.prototype), {
931 initialize: function(element) {
932 this.element = $(element);
933 if(!this.element) throw(Effect._elementDoesNotExistError);
934 var options = Object.extend({
935 style: {}
936 }, arguments[1] || {});
937 if (typeof options.style == 'string') {
938 if(options.style.indexOf(':') == -1) {
939 var cssText = '', selector = '.' + options.style;
940 $A(document.styleSheets).reverse().each(function(styleSheet) {
941 if (styleSheet.cssRules) cssRules = styleSheet.cssRules;
942 else if (styleSheet.rules) cssRules = styleSheet.rules;
943 $A(cssRules).reverse().each(function(rule) {
944 if (selector == rule.selectorText) {
945 cssText = rule.style.cssText;
946 throw $break;
947 }
948 });
949 if (cssText) throw $break;
950 });
951 this.style = cssText.parseStyle();
952 options.afterFinishInternal = function(effect){
953 effect.element.addClassName(effect.options.style);
954 effect.transforms.each(function(transform) {
955 if(transform.style != 'opacity')
956 effect.element.style[transform.style] = '';
957 });
958 }
959 } else this.style = options.style.parseStyle();
960 } else this.style = $H(options.style)
961 this.start(options);
962 },
963 setup: function(){
964 function parseColor(color){
965 if(!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
966 color = color.parseColor();
967 return $R(0,2).map(function(i){
968 return parseInt( color.slice(i*2+1,i*2+3), 16 )
969 });
970 }
971 this.transforms = this.style.map(function(pair){
972 var property = pair[0], value = pair[1], unit = null;
973
974 if(value.parseColor('#zzzzzz') != '#zzzzzz') {
975 value = value.parseColor();
976 unit = 'color';
977 } else if(property == 'opacity') {
978 value = parseFloat(value);
979 if(Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
980 this.element.setStyle({zoom: 1});
981 } else if(Element.CSS_LENGTH.test(value)) {
982 var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
983 value = parseFloat(components[1]);
984 unit = (components.length == 3) ? components[2] : null;
985 }
986
987 var originalValue = this.element.getStyle(property);
988 return {
989 style: property.camelize(),
990 originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0),
991 targetValue: unit=='color' ? parseColor(value) : value,
992 unit: unit
993 };
994 }.bind(this)).reject(function(transform){
995 return (
996 (transform.originalValue == transform.targetValue) ||
997 (
998 transform.unit != 'color' &&
999 (isNaN(transform.originalValue) || isNaN(transform.targetValue))
1000 )
1001 )
1002 });
1003 },
1004 update: function(position) {
1005 var style = {}, transform, i = this.transforms.length;
1006 while(i--)
1007 style[(transform = this.transforms[i]).style] =
1008 transform.unit=='color' ? '#'+
1009 (Math.round(transform.originalValue[0]+
1010 (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
1011 (Math.round(transform.originalValue[1]+
1012 (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
1013 (Math.round(transform.originalValue[2]+
1014 (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
1015 transform.originalValue + Math.round(
1016 ((transform.targetValue - transform.originalValue) * position) * 1000)/1000 + transform.unit;
1017 this.element.setStyle(style, true);
1018 }
1019});
1020
1021Effect.Transform = Class.create();
1022Object.extend(Effect.Transform.prototype, {
1023 initialize: function(tracks){
1024 this.tracks = [];
1025 this.options = arguments[1] || {};
1026 this.addTracks(tracks);
1027 },
1028 addTracks: function(tracks){
1029 tracks.each(function(track){
1030 var data = $H(track).values().first();
1031 this.tracks.push($H({
1032 ids: $H(track).keys().first(),
1033 effect: Effect.Morph,
1034 options: { style: data }
1035 }));
1036 }.bind(this));
1037 return this;
1038 },
1039 play: function(){
1040 return new Effect.Parallel(
1041 this.tracks.map(function(track){
1042 var elements = [$(track.ids) || $$(track.ids)].flatten();
1043 return elements.map(function(e){ return new track.effect(e, Object.extend({ sync:true }, track.options)) });
1044 }).flatten(),
1045 this.options
1046 );
1047 }
1048});
1049
1050Element.CSS_PROPERTIES = $w(
1051 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +
1052 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
1053 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
1054 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
1055 'fontSize fontWeight height left letterSpacing lineHeight ' +
1056 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
1057 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
1058 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
1059 'right textIndent top width wordSpacing zIndex');
1060
1061Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
1062
1063String.prototype.parseStyle = function(){
1064 var element = document.createElement('div');
1065 element.innerHTML = '<div style="' + this + '"></div>';
1066 var style = element.childNodes[0].style, styleRules = $H();
1067
1068 Element.CSS_PROPERTIES.each(function(property){
1069 if(style[property]) styleRules[property] = style[property];
1070 });
1071 if(Prototype.Browser.IE && this.indexOf('opacity') > -1) {
1072 styleRules.opacity = this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1];
1073 }
1074 return styleRules;
1075};
1076
1077Element.morph = function(element, style) {
1078 new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || {}));
1079 return element;
1080};
1081
1082['getInlineOpacity','forceRerendering','setContentZoom',
1083 'collectTextNodes','collectTextNodesIgnoreClass','morph'].each(
1084 function(f) { Element.Methods[f] = Element[f]; }
1085);
1086
1087Element.Methods.visualEffect = function(element, effect, options) {
1088 s = effect.dasherize().camelize();
1089 effect_class = s.charAt(0).toUpperCase() + s.substring(1);
1090 new Effect[effect_class](element, options);
1091 return $(element);
1092};
1093
1094Element.addMethods(); \ No newline at end of file
diff --git a/docroot/lib/scriptaculous/scriptaculous.js b/docroot/lib/scriptaculous/scriptaculous.js
new file mode 100755
index 0000000..7c472a6
--- /dev/null
+++ b/docroot/lib/scriptaculous/scriptaculous.js
@@ -0,0 +1,58 @@
1// script.aculo.us scriptaculous.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007
2
3// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4//
5// Permission is hereby granted, free of charge, to any person obtaining
6// a copy of this software and associated documentation files (the
7// "Software"), to deal in the Software without restriction, including
8// without limitation the rights to use, copy, modify, merge, publish,
9// distribute, sublicense, and/or sell copies of the Software, and to
10// permit persons to whom the Software is furnished to do so, subject to
11// the following conditions:
12//
13// The above copyright notice and this permission notice shall be
14// included in all copies or substantial portions of the Software.
15//
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23//
24// For details, see the script.aculo.us web site: http://script.aculo.us/
25
26var Scriptaculous = {
27 Version: '1.7.1_beta3',
28 require: function(libraryName) {
29 // inserting via DOM fails in Safari 2.0, so brute force approach
30 document.write('<script type="text/javascript" src="'+libraryName+'"></script>');
31 },
32 REQUIRED_PROTOTYPE: '1.5.1',
33 load: function() {
34 function convertVersionString(versionString){
35 var r = versionString.split('.');
36 return parseInt(r[0])*100000 + parseInt(r[1])*1000 + parseInt(r[2]);
37 }
38
39 if((typeof Prototype=='undefined') ||
40 (typeof Element == 'undefined') ||
41 (typeof Element.Methods=='undefined') ||
42 (convertVersionString(Prototype.Version) <
43 convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE)))
44 throw("script.aculo.us requires the Prototype JavaScript framework >= " +
45 Scriptaculous.REQUIRED_PROTOTYPE);
46
47 $A(document.getElementsByTagName("script")).findAll( function(s) {
48 return (s.src && s.src.match(/scriptaculous\.js(\?.*)?$/))
49 }).each( function(s) {
50 var path = s.src.replace(/scriptaculous\.js(\?.*)?$/,'');
51 var includes = s.src.match(/\?.*load=([a-z,]*)/);
52 (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each(
53 function(include) { Scriptaculous.require(path+include+'.js') });
54 });
55 }
56}
57
58Scriptaculous.load(); \ No newline at end of file
diff --git a/docroot/lib/scriptaculous/slider.js b/docroot/lib/scriptaculous/slider.js
new file mode 100755
index 0000000..c1a84eb
--- /dev/null
+++ b/docroot/lib/scriptaculous/slider.js
@@ -0,0 +1,277 @@
1// script.aculo.us slider.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007
2
3// Copyright (c) 2005-2007 Marty Haught, Thomas Fuchs
4//
5// script.aculo.us is freely distributable under the terms of an MIT-style license.
6// For details, see the script.aculo.us web site: http://script.aculo.us/
7
8if(!Control) var Control = {};
9Control.Slider = Class.create();
10
11// options:
12// axis: 'vertical', or 'horizontal' (default)
13//
14// callbacks:
15// onChange(value)
16// onSlide(value)
17Control.Slider.prototype = {
18 initialize: function(handle, track, options) {
19 var slider = this;
20
21 if(handle instanceof Array) {
22 this.handles = handle.collect( function(e) { return $(e) });
23 } else {
24 this.handles = [$(handle)];
25 }
26
27 this.track = $(track);
28 this.options = options || {};
29
30 this.axis = this.options.axis || 'horizontal';
31 this.increment = this.options.increment || 1;
32 this.step = parseInt(this.options.step || '1');
33 this.range = this.options.range || $R(0,1);
34
35 this.value = 0; // assure backwards compat
36 this.values = this.handles.map( function() { return 0 });
37 this.spans = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false;
38 this.options.startSpan = $(this.options.startSpan || null);
39 this.options.endSpan = $(this.options.endSpan || null);
40
41 this.restricted = this.options.restricted || false;
42
43 this.maximum = this.options.maximum || this.range.end;
44 this.minimum = this.options.minimum || this.range.start;
45
46 // Will be used to align the handle onto the track, if necessary
47 this.alignX = parseInt(this.options.alignX || '0');
48 this.alignY = parseInt(this.options.alignY || '0');
49
50 this.trackLength = this.maximumOffset() - this.minimumOffset();
51
52 this.handleLength = this.isVertical() ?
53 (this.handles[0].offsetHeight != 0 ?
54 this.handles[0].offsetHeight : this.handles[0].style.height.replace(/px$/,"")) :
55 (this.handles[0].offsetWidth != 0 ? this.handles[0].offsetWidth :
56 this.handles[0].style.width.replace(/px$/,""));
57
58 this.active = false;
59 this.dragging = false;
60 this.disabled = false;
61
62 if(this.options.disabled) this.setDisabled();
63
64 // Allowed values array
65 this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false;
66 if(this.allowedValues) {
67 this.minimum = this.allowedValues.min();
68 this.maximum = this.allowedValues.max();
69 }
70
71 this.eventMouseDown = this.startDrag.bindAsEventListener(this);
72 this.eventMouseUp = this.endDrag.bindAsEventListener(this);
73 this.eventMouseMove = this.update.bindAsEventListener(this);
74
75 // Initialize handles in reverse (make sure first handle is active)
76 this.handles.each( function(h,i) {
77 i = slider.handles.length-1-i;
78 slider.setValue(parseFloat(
79 (slider.options.sliderValue instanceof Array ?
80 slider.options.sliderValue[i] : slider.options.sliderValue) ||
81 slider.range.start), i);
82 Element.makePositioned(h); // fix IE
83 Event.observe(h, "mousedown", slider.eventMouseDown);
84 });
85
86 Event.observe(this.track, "mousedown", this.eventMouseDown);
87 Event.observe(document, "mouseup", this.eventMouseUp);
88 Event.observe(document, "mousemove", this.eventMouseMove);
89
90 this.initialized = true;
91 },
92 dispose: function() {
93 var slider = this;
94 Event.stopObserving(this.track, "mousedown", this.eventMouseDown);
95 Event.stopObserving(document, "mouseup", this.eventMouseUp);
96 Event.stopObserving(document, "mousemove", this.eventMouseMove);
97 this.handles.each( function(h) {
98 Event.stopObserving(h, "mousedown", slider.eventMouseDown);
99 });
100 },
101 setDisabled: function(){
102 this.disabled = true;
103 },
104 setEnabled: function(){
105 this.disabled = false;
106 },
107 getNearestValue: function(value){
108 if(this.allowedValues){
109 if(value >= this.allowedValues.max()) return(this.allowedValues.max());
110 if(value <= this.allowedValues.min()) return(this.allowedValues.min());
111
112 var offset = Math.abs(this.allowedValues[0] - value);
113 var newValue = this.allowedValues[0];
114 this.allowedValues.each( function(v) {
115 var currentOffset = Math.abs(v - value);
116 if(currentOffset <= offset){
117 newValue = v;
118 offset = currentOffset;
119 }
120 });
121 return newValue;
122 }
123 if(value > this.range.end) return this.range.end;
124 if(value < this.range.start) return this.range.start;
125 return value;
126 },
127 setValue: function(sliderValue, handleIdx){
128 if(!this.active) {
129 this.activeHandleIdx = handleIdx || 0;
130 this.activeHandle = this.handles[this.activeHandleIdx];
131 this.updateStyles();
132 }
133 handleIdx = handleIdx || this.activeHandleIdx || 0;
134 if(this.initialized && this.restricted) {
135 if((handleIdx>0) && (sliderValue<this.values[handleIdx-1]))
136 sliderValue = this.values[handleIdx-1];
137 if((handleIdx < (this.handles.length-1)) && (sliderValue>this.values[handleIdx+1]))
138 sliderValue = this.values[handleIdx+1];
139 }
140 sliderValue = this.getNearestValue(sliderValue);
141 this.values[handleIdx] = sliderValue;
142 this.value = this.values[0]; // assure backwards compat
143
144 this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] =
145 this.translateToPx(sliderValue);
146
147 this.drawSpans();
148 if(!this.dragging || !this.event) this.updateFinished();
149 },
150 setValueBy: function(delta, handleIdx) {
151 this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta,
152 handleIdx || this.activeHandleIdx || 0);
153 },
154 translateToPx: function(value) {
155 return Math.round(
156 ((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) *
157 (value - this.range.start)) + "px";
158 },
159 translateToValue: function(offset) {
160 return ((offset/(this.trackLength-this.handleLength) *
161 (this.range.end-this.range.start)) + this.range.start);
162 },
163 getRange: function(range) {
164 var v = this.values.sortBy(Prototype.K);
165 range = range || 0;
166 return $R(v[range],v[range+1]);
167 },
168 minimumOffset: function(){
169 return(this.isVertical() ? this.alignY : this.alignX);
170 },
171 maximumOffset: function(){
172 return(this.isVertical() ?
173 (this.track.offsetHeight != 0 ? this.track.offsetHeight :
174 this.track.style.height.replace(/px$/,"")) - this.alignY :
175 (this.track.offsetWidth != 0 ? this.track.offsetWidth :
176 this.track.style.width.replace(/px$/,"")) - this.alignY);
177 },
178 isVertical: function(){
179 return (this.axis == 'vertical');
180 },
181 drawSpans: function() {
182 var slider = this;
183 if(this.spans)
184 $R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) });
185 if(this.options.startSpan)
186 this.setSpan(this.options.startSpan,
187 $R(0, this.values.length>1 ? this.getRange(0).min() : this.value ));
188 if(this.options.endSpan)
189 this.setSpan(this.options.endSpan,
190 $R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum));
191 },
192 setSpan: function(span, range) {
193 if(this.isVertical()) {
194 span.style.top = this.translateToPx(range.start);
195 span.style.height = this.translateToPx(range.end - range.start + this.range.start);
196 } else {
197 span.style.left = this.translateToPx(range.start);
198 span.style.width = this.translateToPx(range.end - range.start + this.range.start);
199 }
200 },
201 updateStyles: function() {
202 this.handles.each( function(h){ Element.removeClassName(h, 'selected') });
203 Element.addClassName(this.activeHandle, 'selected');
204 },
205 startDrag: function(event) {
206 if(Event.isLeftClick(event)) {
207 if(!this.disabled){
208 this.active = true;
209
210 var handle = Event.element(event);
211 var pointer = [Event.pointerX(event), Event.pointerY(event)];
212 var track = handle;
213 if(track==this.track) {
214 var offsets = Position.cumulativeOffset(this.track);
215 this.event = event;
216 this.setValue(this.translateToValue(
217 (this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2)
218 ));
219 var offsets = Position.cumulativeOffset(this.activeHandle);
220 this.offsetX = (pointer[0] - offsets[0]);
221 this.offsetY = (pointer[1] - offsets[1]);
222 } else {
223 // find the handle (prevents issues with Safari)
224 while((this.handles.indexOf(handle) == -1) && handle.parentNode)
225 handle = handle.parentNode;
226
227 if(this.handles.indexOf(handle)!=-1) {
228 this.activeHandle = handle;
229 this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
230 this.updateStyles();
231
232 var offsets = Position.cumulativeOffset(this.activeHandle);
233 this.offsetX = (pointer[0] - offsets[0]);
234 this.offsetY = (pointer[1] - offsets[1]);
235 }
236 }
237 }
238 Event.stop(event);
239 }
240 },
241 update: function(event) {
242 if(this.active) {
243 if(!this.dragging) this.dragging = true;
244 this.draw(event);
245 if(Prototype.Browser.WebKit) window.scrollBy(0,0);
246 Event.stop(event);
247 }
248 },
249 draw: function(event) {
250 var pointer = [Event.pointerX(event), Event.pointerY(event)];
251 var offsets = Position.cumulativeOffset(this.track);
252 pointer[0] -= this.offsetX + offsets[0];
253 pointer[1] -= this.offsetY + offsets[1];
254 this.event = event;
255 this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
256 if(this.initialized && this.options.onSlide)
257 this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
258 },
259 endDrag: function(event) {
260 if(this.active && this.dragging) {
261 this.finishDrag(event, true);
262 Event.stop(event);
263 }
264 this.active = false;
265 this.dragging = false;
266 },
267 finishDrag: function(event, success) {
268 this.active = false;
269 this.dragging = false;
270 this.updateFinished();
271 },
272 updateFinished: function() {
273 if(this.initialized && this.options.onChange)
274 this.options.onChange(this.values.length>1 ? this.values : this.value, this);
275 this.event = null;
276 }
277} \ No newline at end of file
diff --git a/docroot/lib/scriptaculous/sound.js b/docroot/lib/scriptaculous/sound.js
new file mode 100755
index 0000000..164c79a
--- /dev/null
+++ b/docroot/lib/scriptaculous/sound.js
@@ -0,0 +1,60 @@
1// script.aculo.us sound.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007
2
3// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4//
5// Based on code created by Jules Gravinese (http://www.webveteran.com/)
6//
7// script.aculo.us is freely distributable under the terms of an MIT-style license.
8// For details, see the script.aculo.us web site: http://script.aculo.us/
9
10Sound = {
11 tracks: {},
12 _enabled: true,
13 template:
14 new Template('<embed style="height:0" id="sound_#{track}_#{id}" src="#{url}" loop="false" autostart="true" hidden="true"/>'),
15 enable: function(){
16 Sound._enabled = true;
17 },
18 disable: function(){
19 Sound._enabled = false;
20 },
21 play: function(url){
22 if(!Sound._enabled) return;
23 var options = Object.extend({
24 track: 'global', url: url, replace: false
25 }, arguments[1] || {});
26
27 if(options.replace && this.tracks[options.track]) {
28 $R(0, this.tracks[options.track].id).each(function(id){
29 var sound = $('sound_'+options.track+'_'+id);
30 sound.Stop && sound.Stop();
31 sound.remove();
32 })
33 this.tracks[options.track] = null;
34 }
35
36 if(!this.tracks[options.track])
37 this.tracks[options.track] = { id: 0 }
38 else
39 this.tracks[options.track].id++;
40
41 options.id = this.tracks[options.track].id;
42 if (Prototype.Browser.IE) {
43 var sound = document.createElement('bgsound');
44 sound.setAttribute('id','sound_'+options.track+'_'+options.id);
45 sound.setAttribute('src',options.url);
46 sound.setAttribute('loop','1');
47 sound.setAttribute('autostart','true');
48 $$('body')[0].appendChild(sound);
49 }
50 else
51 new Insertion.Bottom($$('body')[0], Sound.template.evaluate(options));
52 }
53};
54
55if(Prototype.Browser.Gecko && navigator.userAgent.indexOf("Win") > 0){
56 if(navigator.plugins && $A(navigator.plugins).detect(function(p){ return p.name.indexOf('QuickTime') != -1 }))
57 Sound.template = new Template('<object id="sound_#{track}_#{id}" width="0" height="0" type="audio/mpeg" data="#{url}"/>')
58 else
59 Sound.play = function(){}
60}
diff --git a/docroot/lib/scriptaculous/unittest.js b/docroot/lib/scriptaculous/unittest.js
new file mode 100755
index 0000000..d2dff8b
--- /dev/null
+++ b/docroot/lib/scriptaculous/unittest.js
@@ -0,0 +1,564 @@
1// script.aculo.us unittest.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007
2
3// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4// (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
5// (c) 2005-2007 Michael Schuerig (http://www.schuerig.de/michael/)
6//
7// script.aculo.us is freely distributable under the terms of an MIT-style license.
8// For details, see the script.aculo.us web site: http://script.aculo.us/
9
10// experimental, Firefox-only
11Event.simulateMouse = function(element, eventName) {
12 var options = Object.extend({
13 pointerX: 0,
14 pointerY: 0,
15 buttons: 0,
16 ctrlKey: false,
17 altKey: false,
18 shiftKey: false,
19 metaKey: false
20 }, arguments[2] || {});
21 var oEvent = document.createEvent("MouseEvents");
22 oEvent.initMouseEvent(eventName, true, true, document.defaultView,
23 options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY,
24 options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 0, $(element));
25
26 if(this.mark) Element.remove(this.mark);
27 this.mark = document.createElement('div');
28 this.mark.appendChild(document.createTextNode(" "));
29 document.body.appendChild(this.mark);
30 this.mark.style.position = 'absolute';
31 this.mark.style.top = options.pointerY + "px";
32 this.mark.style.left = options.pointerX + "px";
33 this.mark.style.width = "5px";
34 this.mark.style.height = "5px;";
35 this.mark.style.borderTop = "1px solid red;"
36 this.mark.style.borderLeft = "1px solid red;"
37
38 if(this.step)
39 alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options));
40
41 $(element).dispatchEvent(oEvent);
42};
43
44// Note: Due to a fix in Firefox 1.0.5/6 that probably fixed "too much", this doesn't work in 1.0.6 or DP2.
45// You need to downgrade to 1.0.4 for now to get this working
46// See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much
47Event.simulateKey = function(element, eventName) {
48 var options = Object.extend({
49 ctrlKey: false,
50 altKey: false,
51 shiftKey: false,
52 metaKey: false,
53 keyCode: 0,
54 charCode: 0
55 }, arguments[2] || {});
56
57 var oEvent = document.createEvent("KeyEvents");
58 oEvent.initKeyEvent(eventName, true, true, window,
59 options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
60 options.keyCode, options.charCode );
61 $(element).dispatchEvent(oEvent);
62};
63
64Event.simulateKeys = function(element, command) {
65 for(var i=0; i<command.length; i++) {
66 Event.simulateKey(element,'keypress',{charCode:command.charCodeAt(i)});
67 }
68};
69
70var Test = {}
71Test.Unit = {};
72
73// security exception workaround
74Test.Unit.inspect = Object.inspect;
75
76Test.Unit.Logger = Class.create();
77Test.Unit.Logger.prototype = {
78 initialize: function(log) {
79 this.log = $(log);
80 if (this.log) {
81 this._createLogTable();
82 }
83 },
84 start: function(testName) {
85 if (!this.log) return;
86 this.testName = testName;
87 this.lastLogLine = document.createElement('tr');
88 this.statusCell = document.createElement('td');
89 this.nameCell = document.createElement('td');
90 this.nameCell.className = "nameCell";
91 this.nameCell.appendChild(document.createTextNode(testName));
92 this.messageCell = document.createElement('td');
93 this.lastLogLine.appendChild(this.statusCell);
94 this.lastLogLine.appendChild(this.nameCell);
95 this.lastLogLine.appendChild(this.messageCell);
96 this.loglines.appendChild(this.lastLogLine);
97 },
98 finish: function(status, summary) {
99 if (!this.log) return;
100 this.lastLogLine.className = status;
101 this.statusCell.innerHTML = status;
102 this.messageCell.innerHTML = this._toHTML(summary);
103 this.addLinksToResults();
104 },
105 message: function(message) {
106 if (!this.log) return;
107 this.messageCell.innerHTML = this._toHTML(message);
108 },
109 summary: function(summary) {
110 if (!this.log) return;
111 this.logsummary.innerHTML = this._toHTML(summary);
112 },
113 _createLogTable: function() {
114 this.log.innerHTML =
115 '<div id="logsummary"></div>' +
116 '<table id="logtable">' +
117 '<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' +
118 '<tbody id="loglines"></tbody>' +
119 '</table>';
120 this.logsummary = $('logsummary')
121 this.loglines = $('loglines');
122 },
123 _toHTML: function(txt) {
124 return txt.escapeHTML().replace(/\n/g,"<br/>");
125 },
126 addLinksToResults: function(){
127 $$("tr.failed .nameCell").each( function(td){ // todo: limit to children of this.log
128 td.title = "Run only this test"
129 Event.observe(td, 'click', function(){ window.location.search = "?tests=" + td.innerHTML;});
130 });
131 $$("tr.passed .nameCell").each( function(td){ // todo: limit to children of this.log
132 td.title = "Run all tests"
133 Event.observe(td, 'click', function(){ window.location.search = "";});
134 });
135 }
136}
137
138Test.Unit.Runner = Class.create();
139Test.Unit.Runner.prototype = {
140 initialize: function(testcases) {
141 this.options = Object.extend({
142 testLog: 'testlog'
143 }, arguments[1] || {});
144 this.options.resultsURL = this.parseResultsURLQueryParameter();
145 this.options.tests = this.parseTestsQueryParameter();
146 if (this.options.testLog) {
147 this.options.testLog = $(this.options.testLog) || null;
148 }
149 if(this.options.tests) {
150 this.tests = [];
151 for(var i = 0; i < this.options.tests.length; i++) {
152 if(/^test/.test(this.options.tests[i])) {
153 this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"]));
154 }
155 }
156 } else {
157 if (this.options.test) {
158 this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])];
159 } else {
160 this.tests = [];
161 for(var testcase in testcases) {
162 if(/^test/.test(testcase)) {
163 this.tests.push(
164 new Test.Unit.Testcase(
165 this.options.context ? ' -> ' + this.options.titles[testcase] : testcase,
166 testcases[testcase], testcases["setup"], testcases["teardown"]
167 ));
168 }
169 }
170 }
171 }
172 this.currentTest = 0;
173 this.logger = new Test.Unit.Logger(this.options.testLog);
174 setTimeout(this.runTests.bind(this), 1000);
175 },
176 parseResultsURLQueryParameter: function() {
177 return window.location.search.parseQuery()["resultsURL"];
178 },
179 parseTestsQueryParameter: function(){
180 if (window.location.search.parseQuery()["tests"]){
181 return window.location.search.parseQuery()["tests"].split(',');
182 };
183 },
184 // Returns:
185 // "ERROR" if there was an error,
186 // "FAILURE" if there was a failure, or
187 // "SUCCESS" if there was neither
188 getResult: function() {
189 var hasFailure = false;
190 for(var i=0;i<this.tests.length;i++) {
191 if (this.tests[i].errors > 0) {
192 return "ERROR";
193 }
194 if (this.tests[i].failures > 0) {
195 hasFailure = true;
196 }
197 }
198 if (hasFailure) {
199 return "FAILURE";
200 } else {
201 return "SUCCESS";
202 }
203 },
204 postResults: function() {
205 if (this.options.resultsURL) {
206 new Ajax.Request(this.options.resultsURL,
207 { method: 'get', parameters: 'result=' + this.getResult(), asynchronous: false });
208 }
209 },
210 runTests: function() {
211 var test = this.tests[this.currentTest];
212 if (!test) {
213 // finished!
214 this.postResults();
215 this.logger.summary(this.summary());
216 return;
217 }
218 if(!test.isWaiting) {
219 this.logger.start(test.name);
220 }
221 test.run();
222 if(test.isWaiting) {
223 this.logger.message("Waiting for " + test.timeToWait + "ms");
224 setTimeout(this.runTests.bind(this), test.timeToWait || 1000);
225 } else {
226 this.logger.finish(test.status(), test.summary());
227 this.currentTest++;
228 // tail recursive, hopefully the browser will skip the stackframe
229 this.runTests();
230 }
231 },
232 summary: function() {
233 var assertions = 0;
234 var failures = 0;
235 var errors = 0;
236 var messages = [];
237 for(var i=0;i<this.tests.length;i++) {
238 assertions += this.tests[i].assertions;
239 failures += this.tests[i].failures;
240 errors += this.tests[i].errors;
241 }
242 return (
243 (this.options.context ? this.options.context + ': ': '') +
244 this.tests.length + " tests, " +
245 assertions + " assertions, " +
246 failures + " failures, " +
247 errors + " errors");
248 }
249}
250
251Test.Unit.Assertions = Class.create();
252Test.Unit.Assertions.prototype = {
253 initialize: function() {
254 this.assertions = 0;
255 this.failures = 0;
256 this.errors = 0;
257 this.messages = [];
258 },
259 summary: function() {
260 return (
261 this.assertions + " assertions, " +
262 this.failures + " failures, " +
263 this.errors + " errors" + "\n" +
264 this.messages.join("\n"));
265 },
266 pass: function() {
267 this.assertions++;
268 },
269 fail: function(message) {
270 this.failures++;
271 this.messages.push("Failure: " + message);
272 },
273 info: function(message) {
274 this.messages.push("Info: " + message);
275 },
276 error: function(error) {
277 this.errors++;
278 this.messages.push(error.name + ": "+ error.message + "(" + Test.Unit.inspect(error) +")");
279 },
280 status: function() {
281 if (this.failures > 0) return 'failed';
282 if (this.errors > 0) return 'error';
283 return 'passed';
284 },
285 assert: function(expression) {
286 var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"';
287 try { expression ? this.pass() :
288 this.fail(message); }
289 catch(e) { this.error(e); }
290 },
291 assertEqual: function(expected, actual) {
292 var message = arguments[2] || "assertEqual";
293 try { (expected == actual) ? this.pass() :
294 this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
295 '", actual "' + Test.Unit.inspect(actual) + '"'); }
296 catch(e) { this.error(e); }
297 },
298 assertInspect: function(expected, actual) {
299 var message = arguments[2] || "assertInspect";
300 try { (expected == actual.inspect()) ? this.pass() :
301 this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
302 '", actual "' + Test.Unit.inspect(actual) + '"'); }
303 catch(e) { this.error(e); }
304 },
305 assertEnumEqual: function(expected, actual) {
306 var message = arguments[2] || "assertEnumEqual";
307 try { $A(expected).length == $A(actual).length &&
308 expected.zip(actual).all(function(pair) { return pair[0] == pair[1] }) ?
309 this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) +
310 ', actual ' + Test.Unit.inspect(actual)); }
311 catch(e) { this.error(e); }
312 },
313 assertNotEqual: function(expected, actual) {
314 var message = arguments[2] || "assertNotEqual";
315 try { (expected != actual) ? this.pass() :
316 this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); }
317 catch(e) { this.error(e); }
318 },
319 assertIdentical: function(expected, actual) {
320 var message = arguments[2] || "assertIdentical";
321 try { (expected === actual) ? this.pass() :
322 this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
323 '", actual "' + Test.Unit.inspect(actual) + '"'); }
324 catch(e) { this.error(e); }
325 },
326 assertNotIdentical: function(expected, actual) {
327 var message = arguments[2] || "assertNotIdentical";
328 try { !(expected === actual) ? this.pass() :
329 this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
330 '", actual "' + Test.Unit.inspect(actual) + '"'); }
331 catch(e) { this.error(e); }
332 },
333 assertNull: function(obj) {
334 var message = arguments[1] || 'assertNull'
335 try { (obj==null) ? this.pass() :
336 this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); }
337 catch(e) { this.error(e); }
338 },
339 assertMatch: function(expected, actual) {
340 var message = arguments[2] || 'assertMatch';
341 var regex = new RegExp(expected);
342 try { (regex.exec(actual)) ? this.pass() :
343 this.fail(message + ' : regex: "' + Test.Unit.inspect(expected) + ' did not match: ' + Test.Unit.inspect(actual) + '"'); }
344 catch(e) { this.error(e); }
345 },
346 assertHidden: function(element) {
347 var message = arguments[1] || 'assertHidden';
348 this.assertEqual("none", element.style.display, message);
349 },
350 assertNotNull: function(object) {
351 var message = arguments[1] || 'assertNotNull';
352 this.assert(object != null, message);
353 },
354 assertType: function(expected, actual) {
355 var message = arguments[2] || 'assertType';
356 try {
357 (actual.constructor == expected) ? this.pass() :
358 this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
359 '", actual "' + (actual.constructor) + '"'); }
360 catch(e) { this.error(e); }
361 },
362 assertNotOfType: function(expected, actual) {
363 var message = arguments[2] || 'assertNotOfType';
364 try {
365 (actual.constructor != expected) ? this.pass() :
366 this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
367 '", actual "' + (actual.constructor) + '"'); }
368 catch(e) { this.error(e); }
369 },
370 assertInstanceOf: function(expected, actual) {
371 var message = arguments[2] || 'assertInstanceOf';
372 try {
373 (actual instanceof expected) ? this.pass() :
374 this.fail(message + ": object was not an instance of the expected type"); }
375 catch(e) { this.error(e); }
376 },
377 assertNotInstanceOf: function(expected, actual) {
378 var message = arguments[2] || 'assertNotInstanceOf';
379 try {
380 !(actual instanceof expected) ? this.pass() :
381 this.fail(message + ": object was an instance of the not expected type"); }
382 catch(e) { this.error(e); }
383 },
384 assertRespondsTo: function(method, obj) {
385 var message = arguments[2] || 'assertRespondsTo';
386 try {
387 (obj[method] && typeof obj[method] == 'function') ? this.pass() :
388 this.fail(message + ": object doesn't respond to [" + method + "]"); }
389 catch(e) { this.error(e); }
390 },
391 assertReturnsTrue: function(method, obj) {
392 var message = arguments[2] || 'assertReturnsTrue';
393 try {
394 var m = obj[method];
395 if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
396 m() ? this.pass() :
397 this.fail(message + ": method returned false"); }
398 catch(e) { this.error(e); }
399 },
400 assertReturnsFalse: function(method, obj) {
401 var message = arguments[2] || 'assertReturnsFalse';
402 try {
403 var m = obj[method];
404 if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
405 !m() ? this.pass() :
406 this.fail(message + ": method returned true"); }
407 catch(e) { this.error(e); }
408 },
409 assertRaise: function(exceptionName, method) {
410 var message = arguments[2] || 'assertRaise';
411 try {
412 method();
413 this.fail(message + ": exception expected but none was raised"); }
414 catch(e) {
415 ((exceptionName == null) || (e.name==exceptionName)) ? this.pass() : this.error(e);
416 }
417 },
418 assertElementsMatch: function() {
419 var expressions = $A(arguments), elements = $A(expressions.shift());
420 if (elements.length != expressions.length) {
421 this.fail('assertElementsMatch: size mismatch: ' + elements.length + ' elements, ' + expressions.length + ' expressions');
422 return false;
423 }
424 elements.zip(expressions).all(function(pair, index) {
425 var element = $(pair.first()), expression = pair.last();
426 if (element.match(expression)) return true;
427 this.fail('assertElementsMatch: (in index ' + index + ') expected ' + expression.inspect() + ' but got ' + element.inspect());
428 }.bind(this)) && this.pass();
429 },
430 assertElementMatches: function(element, expression) {
431 this.assertElementsMatch([element], expression);
432 },
433 benchmark: function(operation, iterations) {
434 var startAt = new Date();
435 (iterations || 1).times(operation);
436 var timeTaken = ((new Date())-startAt);
437 this.info((arguments[2] || 'Operation') + ' finished ' +
438 iterations + ' iterations in ' + (timeTaken/1000)+'s' );
439 return timeTaken;
440 },
441 _isVisible: function(element) {
442 element = $(element);
443 if(!element.parentNode) return true;
444 this.assertNotNull(element);
445 if(element.style && Element.getStyle(element, 'display') == 'none')
446 return false;
447
448 return this._isVisible(element.parentNode);
449 },
450 assertNotVisible: function(element) {
451 this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1]));
452 },
453 assertVisible: function(element) {
454 this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1]));
455 },
456 benchmark: function(operation, iterations) {
457 var startAt = new Date();
458 (iterations || 1).times(operation);
459 var timeTaken = ((new Date())-startAt);
460 this.info((arguments[2] || 'Operation') + ' finished ' +
461 iterations + ' iterations in ' + (timeTaken/1000)+'s' );
462 return timeTaken;
463 }
464}
465
466Test.Unit.Testcase = Class.create();
467Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), {
468 initialize: function(name, test, setup, teardown) {
469 Test.Unit.Assertions.prototype.initialize.bind(this)();
470 this.name = name;
471
472 if(typeof test == 'string') {
473 test = test.gsub(/(\.should[^\(]+\()/,'#{0}this,');
474 test = test.gsub(/(\.should[^\(]+)\(this,\)/,'#{1}(this)');
475 this.test = function() {
476 eval('with(this){'+test+'}');
477 }
478 } else {
479 this.test = test || function() {};
480 }
481
482 this.setup = setup || function() {};
483 this.teardown = teardown || function() {};
484 this.isWaiting = false;
485 this.timeToWait = 1000;
486 },
487 wait: function(time, nextPart) {
488 this.isWaiting = true;
489 this.test = nextPart;
490 this.timeToWait = time;
491 },
492 run: function() {
493 try {
494 try {
495 if (!this.isWaiting) this.setup.bind(this)();
496 this.isWaiting = false;
497 this.test.bind(this)();
498 } finally {
499 if(!this.isWaiting) {
500 this.teardown.bind(this)();
501 }
502 }
503 }
504 catch(e) { this.error(e); }
505 }
506});
507
508// *EXPERIMENTAL* BDD-style testing to please non-technical folk
509// This draws many ideas from RSpec http://rspec.rubyforge.org/
510
511Test.setupBDDExtensionMethods = function(){
512 var METHODMAP = {
513 shouldEqual: 'assertEqual',
514 shouldNotEqual: 'assertNotEqual',
515 shouldEqualEnum: 'assertEnumEqual',
516 shouldBeA: 'assertType',
517 shouldNotBeA: 'assertNotOfType',
518 shouldBeAn: 'assertType',
519 shouldNotBeAn: 'assertNotOfType',
520 shouldBeNull: 'assertNull',
521 shouldNotBeNull: 'assertNotNull',
522
523 shouldBe: 'assertReturnsTrue',
524 shouldNotBe: 'assertReturnsFalse',
525 shouldRespondTo: 'assertRespondsTo'
526 };
527 Test.BDDMethods = {};
528 for(m in METHODMAP) {
529 Test.BDDMethods[m] = eval(
530 'function(){'+
531 'var args = $A(arguments);'+
532 'var scope = args.shift();'+
533 'scope.'+METHODMAP[m]+'.apply(scope,(args || []).concat([this])); }');
534 }
535 [Array.prototype, String.prototype, Number.prototype].each(
536 function(p){ Object.extend(p, Test.BDDMethods) }
537 );
538}
539
540Test.context = function(name, spec, log){
541 Test.setupBDDExtensionMethods();
542
543 var compiledSpec = {};
544 var titles = {};
545 for(specName in spec) {
546 switch(specName){
547 case "setup":
548 case "teardown":
549 compiledSpec[specName] = spec[specName];
550 break;
551 default:
552 var testName = 'test'+specName.gsub(/\s+/,'-').camelize();
553 var body = spec[specName].toString().split('\n').slice(1);
554 if(/^\{/.test(body[0])) body = body.slice(1);
555 body.pop();
556 body = body.map(function(statement){
557 return statement.strip()
558 });
559 compiledSpec[testName] = body.join('\n');
560 titles[testName] = specName;
561 }
562 }
563 new Test.Unit.Runner(compiledSpec, { titles: titles, testLog: log || 'testlog', context: name });
564}; \ No newline at end of file
diff --git a/docroot/lib/swfobject/swfobject.js b/docroot/lib/swfobject/swfobject.js
new file mode 100755
index 0000000..caa256a
--- /dev/null
+++ b/docroot/lib/swfobject/swfobject.js
@@ -0,0 +1,233 @@
1/**
2 * SWFObject v1.5: Flash Player detection and embed - http://blog.deconcept.com/swfobject/
3 *
4 * SWFObject is (c) 2007 Geoff Stearns and is released under the MIT License:
5 * http://www.opensource.org/licenses/mit-license.php
6 *
7 */
8if(typeof deconcept == "undefined") var deconcept = new Object();
9if(typeof deconcept.util == "undefined") deconcept.util = new Object();
10if(typeof deconcept.SWFObjectUtil == "undefined") deconcept.SWFObjectUtil = new Object();
11deconcept.SWFObject = function(swf, id, w, h, ver, c, quality, xiRedirectUrl, redirectUrl, detectKey) {
12 if (!document.getElementById) { return; }
13 this.DETECT_KEY = detectKey ? detectKey : 'detectflash';
14 this.skipDetect = deconcept.util.getRequestParameter(this.DETECT_KEY);
15 this.params = new Object();
16 this.variables = new Object();
17 this.attributes = new Array();
18 if(swf) { this.setAttribute('swf', swf); }
19 if(id) { this.setAttribute('id', id); }
20 if(w) { this.setAttribute('width', w); }
21 if(h) { this.setAttribute('height', h); }
22 if(ver) { this.setAttribute('version', new deconcept.PlayerVersion(ver.toString().split("."))); }
23 this.installedVer = deconcept.SWFObjectUtil.getPlayerVersion();
24 if (!window.opera && document.all && this.installedVer.major > 7) {
25 // only add the onunload cleanup if the Flash Player version supports External Interface and we are in IE
26 deconcept.SWFObject.doPrepUnload = true;
27 }
28 if(c) { this.addParam('bgcolor', c); }
29 var q = quality ? quality : 'high';
30 this.addParam('quality', q);
31 this.setAttribute('useExpressInstall', false);
32 this.setAttribute('doExpressInstall', false);
33 var xir = (xiRedirectUrl) ? xiRedirectUrl : window.location;
34 this.setAttribute('xiRedirectUrl', xir);
35 this.setAttribute('redirectUrl', '');
36 if(redirectUrl) { this.setAttribute('redirectUrl', redirectUrl); }
37}
38deconcept.SWFObject.prototype = {
39 useExpressInstall: function(path) {
40 this.xiSWFPath = !path ? "expressinstall.swf" : path;
41 this.setAttribute('useExpressInstall', true);
42 },
43 setAttribute: function(name, value){
44 this.attributes[name] = value;
45 },
46 getAttribute: function(name){
47 return this.attributes[name];
48 },
49 addParam: function(name, value){
50 this.params[name] = value;
51 },
52 getParams: function(){
53 return this.params;
54 },
55 addVariable: function(name, value){
56 this.variables[name] = value;
57 },
58 getVariable: function(name){
59 return this.variables[name];
60 },
61 getVariables: function(){
62 return this.variables;
63 },
64 getVariablePairs: function(){
65 var variablePairs = new Array();
66 var key;
67 var variables = this.getVariables();
68 for(key in variables){
69 variablePairs[variablePairs.length] = key +"="+ variables[key];
70 }
71 return variablePairs;
72 },
73 getSWFHTML: function() {
74 var swfNode = "";
75 if (navigator.plugins && navigator.mimeTypes && navigator.mimeTypes.length) { // netscape plugin architecture
76 if (this.getAttribute("doExpressInstall")) {
77 this.addVariable("MMplayerType", "PlugIn");
78 this.setAttribute('swf', this.xiSWFPath);
79 }
80 swfNode = '<embed type="application/x-shockwave-flash" src="'+ this.getAttribute('swf') +'" width="'+ this.getAttribute('width') +'" height="'+ this.getAttribute('height') +'" style="'+ this.getAttribute('style') +'"';
81 swfNode += ' id="'+ this.getAttribute('id') +'" name="'+ this.getAttribute('id') +'" ';
82 var params = this.getParams();
83 for(var key in params){ swfNode += [key] +'="'+ params[key] +'" '; }
84 var pairs = this.getVariablePairs().join("&");
85 if (pairs.length > 0){ swfNode += 'flashvars="'+ pairs +'"'; }
86 swfNode += '/>';
87 } else { // PC IE
88 if (this.getAttribute("doExpressInstall")) {
89 this.addVariable("MMplayerType", "ActiveX");
90 this.setAttribute('swf', this.xiSWFPath);
91 }
92 swfNode = '<object id="'+ this.getAttribute('id') +'" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="'+ this.getAttribute('width') +'" height="'+ this.getAttribute('height') +'" style="'+ this.getAttribute('style') +'">';
93 swfNode += '<param name="movie" value="'+ this.getAttribute('swf') +'" />';
94 var params = this.getParams();
95 for(var key in params) {
96 swfNode += '<param name="'+ key +'" value="'+ params[key] +'" />';
97 }
98 var pairs = this.getVariablePairs().join("&");
99 if(pairs.length > 0) {swfNode += '<param name="flashvars" value="'+ pairs +'" />';}
100 swfNode += "</object>";
101 }
102 return swfNode;
103 },
104 write: function(elementId){
105 if(this.getAttribute('useExpressInstall')) {
106 // check to see if we need to do an express install
107 var expressInstallReqVer = new deconcept.PlayerVersion([6,0,65]);
108 if (this.installedVer.versionIsValid(expressInstallReqVer) && !this.installedVer.versionIsValid(this.getAttribute('version'))) {
109 this.setAttribute('doExpressInstall', true);
110 this.addVariable("MMredirectURL", escape(this.getAttribute('xiRedirectUrl')));
111 document.title = document.title.slice(0, 47) + " - Flash Player Installation";
112 this.addVariable("MMdoctitle", document.title);
113 }
114 }
115 if(this.skipDetect || this.getAttribute('doExpressInstall') || this.installedVer.versionIsValid(this.getAttribute('version'))){
116 var n = (typeof elementId == 'string') ? document.getElementById(elementId) : elementId;
117 n.innerHTML = this.getSWFHTML();
118 return true;
119 }else{
120 if(this.getAttribute('redirectUrl') != "") {
121 document.location.replace(this.getAttribute('redirectUrl'));
122 }
123 }
124 return false;
125 }
126}
127
128/* ---- detection functions ---- */
129deconcept.SWFObjectUtil.getPlayerVersion = function(){
130 var PlayerVersion = new deconcept.PlayerVersion([0,0,0]);
131 if(navigator.plugins && navigator.mimeTypes.length){
132 var x = navigator.plugins["Shockwave Flash"];
133 if(x && x.description) {
134 PlayerVersion = new deconcept.PlayerVersion(x.description.replace(/([a-zA-Z]|\s)+/, "").replace(/(\s+r|\s+b[0-9]+)/, ".").split("."));
135 }
136 }else if (navigator.userAgent && navigator.userAgent.indexOf("Windows CE") >= 0){ // if Windows CE
137 var axo = 1;
138 var counter = 3;
139 while(axo) {
140 try {
141 counter++;
142 axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash."+ counter);
143// document.write("player v: "+ counter);
144 PlayerVersion = new deconcept.PlayerVersion([counter,0,0]);
145 } catch (e) {
146 axo = null;
147 }
148 }
149 } else { // Win IE (non mobile)
150 // do minor version lookup in IE, but avoid fp6 crashing issues
151 // see http://blog.deconcept.com/2006/01/11/getvariable-setvariable-crash-internet-explorer-flash-6/
152 try{
153 var axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");
154 }catch(e){
155 try {
156 var axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");
157 PlayerVersion = new deconcept.PlayerVersion([6,0,21]);
158 axo.AllowScriptAccess = "always"; // error if player version < 6.0.47 (thanks to Michael Williams @ Adobe for this code)
159 } catch(e) {
160 if (PlayerVersion.major == 6) {
161 return PlayerVersion;
162 }
163 }
164 try {
165 axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
166 } catch(e) {}
167 }
168 if (axo != null) {
169 PlayerVersion = new deconcept.PlayerVersion(axo.GetVariable("$version").split(" ")[1].split(","));
170 }
171 }
172 return PlayerVersion;
173}
174deconcept.PlayerVersion = function(arrVersion){
175 this.major = arrVersion[0] != null ? parseInt(arrVersion[0]) : 0;
176 this.minor = arrVersion[1] != null ? parseInt(arrVersion[1]) : 0;
177 this.rev = arrVersion[2] != null ? parseInt(arrVersion[2]) : 0;
178}
179deconcept.PlayerVersion.prototype.versionIsValid = function(fv){
180 if(this.major < fv.major) return false;
181 if(this.major > fv.major) return true;
182 if(this.minor < fv.minor) return false;
183 if(this.minor > fv.minor) return true;
184 if(this.rev < fv.rev) return false;
185 return true;
186}
187/* ---- get value of query string param ---- */
188deconcept.util = {
189 getRequestParameter: function(param) {
190 var q = document.location.search || document.location.hash;
191 if (param == null) { return q; }
192 if(q) {
193 var pairs = q.substring(1).split("&");
194 for (var i=0; i < pairs.length; i++) {
195 if (pairs[i].substring(0, pairs[i].indexOf("=")) == param) {
196 return pairs[i].substring((pairs[i].indexOf("=")+1));
197 }
198 }
199 }
200 return "";
201 }
202}
203/* fix for video streaming bug */
204deconcept.SWFObjectUtil.cleanupSWFs = function() {
205 var objects = document.getElementsByTagName("OBJECT");
206 for (var i = objects.length - 1; i >= 0; i--) {
207 objects[i].style.display = 'none';
208 for (var x in objects[i]) {
209 if (typeof objects[i][x] == 'function') {
210 objects[i][x] = function(){};
211 }
212 }
213 }
214}
215// fixes bug in some fp9 versions see http://blog.deconcept.com/2006/07/28/swfobject-143-released/
216if (deconcept.SWFObject.doPrepUnload) {
217 if (!deconcept.unloadSet) {
218 deconcept.SWFObjectUtil.prepUnload = function() {
219 __flash_unloadHandler = function(){};
220 __flash_savedUnloadHandler = function(){};
221 window.attachEvent("onunload", deconcept.SWFObjectUtil.cleanupSWFs);
222 }
223 window.attachEvent("onbeforeunload", deconcept.SWFObjectUtil.prepUnload);
224 deconcept.unloadSet = true;
225 }
226}
227/* add document.getElementById if needed (mobile IE < 5) */
228if (!document.getElementById && document.all) { document.getElementById = function(id) { return document.all[id]; }}
229
230/* add some aliases for ease of use/backwards compatibility */
231var getQueryParamValue = deconcept.util.getRequestParameter;
232var FlashObject = deconcept.SWFObject; // for legacy support
233var SWFObject = deconcept.SWFObject;
diff --git a/docroot/logged_out.html b/docroot/logged_out.html
new file mode 100755
index 0000000..8ae997a
--- /dev/null
+++ b/docroot/logged_out.html
@@ -0,0 +1,27 @@
1<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
3<!--
4 * Material Experience - Logged Out Page
5 *
6 * EYEMG - Interactive Media Group
7 * Created by Mike Crute (mcrute@eyemg.com)
8 * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07
9-->
10 <head>
11 <title>Logged Out - Material Experience</title>
12 <meta http-equiv="content-type" content="text/html;charset=utf-8" />
13 <style type="text/css">@import url('/application.css');</style>
14 </head>
15
16 <body style="text-align: center">
17
18 <img src="/images/logo.gif" alt="Material Experience Logo" style="margin-top:40px;" />
19 <h1>Logged Out</h1>
20 <p>
21 You have been logged out of the Material Experience site.<br />
22 You can <a href="http://materialexperience.santoprene.com">click
23 here to go back to
24 Material Experience</a>.
25 </p>
26 </body>
27</html> \ No newline at end of file
diff --git a/docroot/pngbehavior.htc b/docroot/pngbehavior.htc
new file mode 100755
index 0000000..31367d1
--- /dev/null
+++ b/docroot/pngbehavior.htc
@@ -0,0 +1,42 @@
1/*
2 * Material Experience - IE 6 PNG Behavior
3 *
4 * EYEMG - Interactive Media Group
5 * Created by Mike Crute (mcrute@eyemg.com)
6 * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07
7 *
8 * Fixes the transparent PNG issue with IE 6. We only modified this
9 * code, it actually came from:
10 * http://webfx.eae.net/dhtml/pngbehavior/pngbehavior.html
11 */
12<public:component lightWeight="true">
13<public:attach event="onpropertychange" onevent="propertyChanged()" />
14<script type="text/javascript">
15var supported = /MSIE ((5\.5)|[6789])/.test(navigator.userAgent) && navigator.platform == "Win32";
16var blankSrc = "/images/blank.gif";
17
18if (supported) fixImage();
19
20function propertyChanged() {
21 if (!supported) return;
22 if (event.propertyName != "src") return;
23 if (src == blankSrc) fixImage();
24}
25
26function fixImage() {
27 var src = element.src;
28 var alt = element.alt;
29 var realSrc;
30
31 if (!new RegExp(blankSrc).test(src))
32 realSrc = src;
33
34 if (/\.png$/i.test(realSrc)) {
35 element.src = blankSrc;
36 element.alt = alt;
37 element.title = alt;
38 element.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + src + ")";
39 }
40}
41</script>
42</public:component>
diff --git a/docroot/specialcases.css b/docroot/specialcases.css
new file mode 100755
index 0000000..35df927
--- /dev/null
+++ b/docroot/specialcases.css
@@ -0,0 +1,16 @@
1/*
2 * Material Experience - Special Cases Stylesheet
3 *
4 * EYEMG - Interactive Media Group
5 * Created by Mike Crute (mcrute@eyemg.com)
6 * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07
7 *
8 * Stylesheet that should be sourced by all special cases card content.
9 * This should do a bare minimum of styles but they are all required
10 * per the client.
11 */
12
13a, a:visited
14{
15 color: rgb(100, 100, 100);
16} \ No newline at end of file
diff --git a/make b/make
new file mode 100755
index 0000000..afdb386
--- /dev/null
+++ b/make
@@ -0,0 +1,135 @@
1#!/bin/bash
2#
3# ExxonMobil Designer Site Build Script
4# by Mike Crute, EYEMG (mcrute@eyemg.com)
5#
6# This is a pretty simple build script just export a
7# tag or the trunk from SVN and run ./make, running
8# make (w/out the ./) will not work. You can then
9# take the generated tarball and explode it on the
10# production server w/out any further configuration.
11#
12
13cd docroot
14
15# Create the build directory and copy over the cgi-bin
16mkdir -p build/{docroot,cgi-bin}
17mkdir build/docroot/lib
18cp -R ../cgi-bin/* build/cgi-bin/
19
20# Create a build date file
21echo "Designer Site Built `date +'%Y-%m-%d %H:%M:%S'` EST" > build/docroot/build.date
22echo "Code Version: `grep 'releaseVersion' classes/sme.namespace.js | cut -d"'" -f 2 `" >> build/docroot/build.date
23echo "Subversion Revision: `svn info | grep 'Revision' | awk '{ print $2 }'`" >> build/docroot/build.date
24
25# Remove stuff from the cgi-bin that does not belong in
26# production.
27rm build/cgi-bin/*.sql
28
29# Also copy over any files that don't need special processing
30cp -R images \
31 blank.gif \
32 favicon.ico \
33 custom_content \
34build/docroot/
35
36cp -R lib/scriptaculous build/docroot/lib/
37
38# Prepare the .htaccess file for production
39#
40# We use a sparse layout in development where each class is
41# in its own separate file but in production we use a solid
42# layout that is also gzipped so we have to un-comment the
43# gzip headers in our htaccess file.
44
45#sed '
46# /### PRODUCTION ###/,/### END PRODUCTION ###/ {
47# /^###.*/d
48# s/#//g
49# }
50#' .htaccess > build/docroot/.htaccess
51
52cp .htaccess build/docroot/.htaccess
53
54# Minify the PNG Behavior
55sed '
56 /\/\*/,/\*\// {
57 /.*/d;
58 }
59' pngbehavior.htc > build/docroot/pngbehavior.htc
60
61# Concatenate the Javascript files into a single library file
62#
63# application.js loads all the javascript files in the development
64# environment. When we go to production we need to parse out the
65# actual script names being loaded and cat them, in order, into
66# the final output application.js file.
67
68# GNU sed uses the -r flag for extended regular expressions,
69# Darwin uses the -E flag for regular expressions. If neither
70# of these hold true we might as well fail.
71if [[ `uname` == 'Linux' ]]; then
72 MYFILES=`sed -r -f ../build_system/get_jsfiles.sed application.js`
73elif [[ `uname` == 'Darwin' ]]; then
74 MYFILES=`sed -E -f ../build_system/get_jsfiles.sed application.js`
75else
76 print 'No valid sed command could be determined.'
77 exit 1
78fi
79
80for item in $MYFILES; do
81 cat $item >> build/docroot/application.js.in
82done
83
84# Remove development code from the application code
85# and append it to the libraries files
86sed '/^;;;.*/d' build/docroot/application.js.in >> build/docroot/application.js.out
87sed '/^\/\*;;;.*/d' build/docroot/application.js.out >> build/docroot/application.js
88rm build/docroot/application.js.in build/docroot/application.js.out
89
90# Minify index.html by removing leading spaces on each line
91# as well as the ID comment, blank lines, and any script tags
92# that appear in the <head> of the document.
93sed 's/^[[:space:]]*//; /^$/d; s/\n$//; /<!--/,/-->/ { /.*/d; }' index.html > build/docroot/index.html
94sed 's/^[[:space:]]*//; /^$/d; s/\n$//; /<!--/,/-->/ { /.*/d; }' logged_out.html > build/docroot/logged_out.html
95
96# Minify the application Javascript using the YUI Compressor
97java -jar ../build_system/yui_compressor.jar -o application.o build/docroot/application.js > /dev/null 2>&1
98mv application.o build/docroot/application.js
99
100# Minify the JSON data files
101mkdir -p build/docroot/data
102for item in `ls data/`; do
103 java -jar ../build_system/yui_compressor.jar -o build/docroot/data/$item data/$item > /dev/null 2>&1
104done
105
106# Can't minimize JSON, the semicolon at the end breaks everything
107cat data/card_tables.js > build/docroot/data/card_tables.js
108
109# Minify the application CSS using our custom compressor
110../build_system/cmpcss application.css > build/docroot/application.css
111../build_system/cmpcss specialcases.css > build/docroot/specialcases.css
112
113# Gzip all that should be gzipped. This really should be done server-side
114# but for now we work around it by statically compressing them at build time
115# and serving it with the correct headers in the .htaccess file
116#gzip build/docroot/application.js
117#gzip build/docroot/application.css
118#gzip build/docroot/index.html
119#
120#mv build/docroot/application.js.gz build/docroot/application.js
121#mv build/docroot/application.css.gz build/docroot/application.css
122#mv build/docroot/index.html.gz build/docroot/index.html
123
124# Tar it all up in a development dump
125mv build aes_designer
126cd aes_designer
127
128# Removing the date since its really not needed
129tar -cjvf ../../aes_designer.tbz2 * > /dev/null 2>&1
130#tar -cjvf ../../aes_designer-`date +%Y%m%d_%H%M%S`.tbz2 * > /dev/null 2>&1
131
132
133cd ..
134
135rm -rf aes_designer/
diff --git a/syncdev b/syncdev
new file mode 100755
index 0000000..7bb3413
--- /dev/null
+++ b/syncdev
@@ -0,0 +1,10 @@
1#!/bin/bash
2
3ssh mcrute@fred.eyemg.com "svn status /usr/web/designer | grep -v '?'" > /dev/null 2>&1
4
5if [[ $? == 0 && $1 != '-f' ]]; then
6 echo "Changes exist on dev. Use SVN instead."
7 exit 1
8fi
9
10rsync -auvz -e "ssh" --delete-after --exclude "*.svn" "`pwd`/" mcrute@fred.eyemg.com:/usr/web/designer/