From 55974d21a824378b287e563bce4c32597060cfca Mon Sep 17 00:00:00 2001 From: Mike Crute Date: Sun, 17 Jan 2010 12:06:15 -0500 Subject: Initial import --- Designer.tmproj | 278 ++ build_system/cmpcss | 125 + build_system/get_jsfiles.sed | 25 + build_system/make.gzip | 130 + build_system/yui_compressor.jar | Bin 0 -> 837686 bytes cgi-bin/card_table.pl | 371 +++ cgi-bin/designer.sql | 15 + cgi-bin/html/login.html | 41 + cgi-bin/login.pl | 209 ++ cgi-bin/sketchbook.pl | 142 + cgi-bin/sketchbook_resolver.pl | 49 + cgi-bin/sop_preview_proxy.pl | 28 + cgi-bin/sop_proxy.pl | 18 + deploy | 71 + docroot/.htaccess | 46 + docroot/account_deleted.html | 23 + docroot/application.css | 463 ++++ docroot/application.js | 67 + docroot/blank.gif | 5 + docroot/classes/application.js | 227 ++ docroot/classes/bezel.class.js | 154 ++ docroot/classes/card.class.js | 378 +++ docroot/classes/chip.class.js | 269 ++ docroot/classes/cookie.class.js | 70 + docroot/classes/decoder.module.js | 205 ++ docroot/classes/history.class.js | 208 ++ docroot/classes/layout.class.js | 51 + docroot/classes/layouts/layout.custom.class.js | 219 ++ docroot/classes/layouts/layout.error.class.js | 33 + docroot/classes/layouts/layout.primary.class.js | 411 +++ docroot/classes/layouts/layout.special.class.js | 43 + docroot/classes/overlay.class.js | 118 + docroot/classes/roundcorners.class.js | 60 + docroot/classes/sketchbook.class.js | 288 ++ docroot/classes/sme.namespace.js | 140 + docroot/classes/table.class.js | 315 +++ docroot/classes/utility.js | 199 ++ docroot/custom_content/2col.css | 68 + docroot/custom_content/2col.js | 170 ++ docroot/data/card_tables.js | 63 + docroot/data/persist_chips.js | 3 + docroot/data/strings.en.js | 122 + docroot/favicon.ico | Bin 0 -> 1150 bytes docroot/graphics/chip.psd | Bin 0 -> 226411 bytes docroot/images/arrow_down.gif | Bin 0 -> 65 bytes docroot/images/arrow_right_grey.gif | Bin 0 -> 64 bytes docroot/images/blank.gif | Bin 0 -> 49 bytes docroot/images/close.gif | Bin 0 -> 66 bytes docroot/images/email.gif | Bin 0 -> 65 bytes docroot/images/history.gif | Bin 0 -> 66 bytes docroot/images/loader.gif | Bin 0 -> 1877 bytes docroot/images/logo.gif | Bin 0 -> 2066 bytes docroot/images/pill.gif | Bin 0 -> 836 bytes docroot/images/plus.gif | Bin 0 -> 64 bytes docroot/images/print.gif | Bin 0 -> 64 bytes docroot/images/tagline.png | Bin 0 -> 52535 bytes docroot/images/trash_can.jpg | Bin 0 -> 1286 bytes docroot/index.html | 45 + docroot/lib/firebug/errorIcon.png | Bin 0 -> 457 bytes docroot/lib/firebug/firebug.css | 209 ++ docroot/lib/firebug/firebug.html | 23 + docroot/lib/firebug/firebug.js | 672 +++++ docroot/lib/firebug/firebugx.js | 10 + docroot/lib/firebug/infoIcon.png | Bin 0 -> 524 bytes docroot/lib/firebug/warningIcon.png | Bin 0 -> 516 bytes docroot/lib/prototype.js | 3277 +++++++++++++++++++++++ docroot/lib/scriptaculous/builder.js | 136 + docroot/lib/scriptaculous/controls.js | 875 ++++++ docroot/lib/scriptaculous/dragdrop.js | 970 +++++++ docroot/lib/scriptaculous/effects.js | 1094 ++++++++ docroot/lib/scriptaculous/scriptaculous.js | 58 + docroot/lib/scriptaculous/slider.js | 277 ++ docroot/lib/scriptaculous/sound.js | 60 + docroot/lib/scriptaculous/unittest.js | 564 ++++ docroot/lib/swfobject/swfobject.js | 233 ++ docroot/logged_out.html | 27 + docroot/pngbehavior.htc | 42 + docroot/specialcases.css | 16 + make | 135 + syncdev | 10 + 80 files changed, 14653 insertions(+) create mode 100644 Designer.tmproj create mode 100755 build_system/cmpcss create mode 100755 build_system/get_jsfiles.sed create mode 100755 build_system/make.gzip create mode 100755 build_system/yui_compressor.jar create mode 100755 cgi-bin/card_table.pl create mode 100755 cgi-bin/designer.sql create mode 100755 cgi-bin/html/login.html create mode 100755 cgi-bin/login.pl create mode 100755 cgi-bin/sketchbook.pl create mode 100755 cgi-bin/sketchbook_resolver.pl create mode 100755 cgi-bin/sop_preview_proxy.pl create mode 100755 cgi-bin/sop_proxy.pl create mode 100755 deploy create mode 100755 docroot/.htaccess create mode 100755 docroot/account_deleted.html create mode 100755 docroot/application.css create mode 100755 docroot/application.js create mode 100755 docroot/blank.gif create mode 100755 docroot/classes/application.js create mode 100755 docroot/classes/bezel.class.js create mode 100755 docroot/classes/card.class.js create mode 100755 docroot/classes/chip.class.js create mode 100755 docroot/classes/cookie.class.js create mode 100755 docroot/classes/decoder.module.js create mode 100755 docroot/classes/history.class.js create mode 100755 docroot/classes/layout.class.js create mode 100755 docroot/classes/layouts/layout.custom.class.js create mode 100755 docroot/classes/layouts/layout.error.class.js create mode 100755 docroot/classes/layouts/layout.primary.class.js create mode 100755 docroot/classes/layouts/layout.special.class.js create mode 100755 docroot/classes/overlay.class.js create mode 100755 docroot/classes/roundcorners.class.js create mode 100755 docroot/classes/sketchbook.class.js create mode 100755 docroot/classes/sme.namespace.js create mode 100755 docroot/classes/table.class.js create mode 100755 docroot/classes/utility.js create mode 100755 docroot/custom_content/2col.css create mode 100755 docroot/custom_content/2col.js create mode 100755 docroot/data/card_tables.js create mode 100755 docroot/data/persist_chips.js create mode 100755 docroot/data/strings.en.js create mode 100755 docroot/favicon.ico create mode 100755 docroot/graphics/chip.psd create mode 100755 docroot/images/arrow_down.gif create mode 100755 docroot/images/arrow_right_grey.gif create mode 100755 docroot/images/blank.gif create mode 100755 docroot/images/close.gif create mode 100755 docroot/images/email.gif create mode 100755 docroot/images/history.gif create mode 100755 docroot/images/loader.gif create mode 100755 docroot/images/logo.gif create mode 100755 docroot/images/pill.gif create mode 100755 docroot/images/plus.gif create mode 100755 docroot/images/print.gif create mode 100755 docroot/images/tagline.png create mode 100755 docroot/images/trash_can.jpg create mode 100755 docroot/index.html create mode 100755 docroot/lib/firebug/errorIcon.png create mode 100755 docroot/lib/firebug/firebug.css create mode 100755 docroot/lib/firebug/firebug.html create mode 100755 docroot/lib/firebug/firebug.js create mode 100755 docroot/lib/firebug/firebugx.js create mode 100755 docroot/lib/firebug/infoIcon.png create mode 100755 docroot/lib/firebug/warningIcon.png create mode 100755 docroot/lib/prototype.js create mode 100755 docroot/lib/scriptaculous/builder.js create mode 100755 docroot/lib/scriptaculous/controls.js create mode 100755 docroot/lib/scriptaculous/dragdrop.js create mode 100755 docroot/lib/scriptaculous/effects.js create mode 100755 docroot/lib/scriptaculous/scriptaculous.js create mode 100755 docroot/lib/scriptaculous/slider.js create mode 100755 docroot/lib/scriptaculous/sound.js create mode 100755 docroot/lib/scriptaculous/unittest.js create mode 100755 docroot/lib/swfobject/swfobject.js create mode 100755 docroot/logged_out.html create mode 100755 docroot/pngbehavior.htc create mode 100755 docroot/specialcases.css create mode 100755 make create mode 100755 syncdev diff --git a/Designer.tmproj b/Designer.tmproj new file mode 100644 index 0000000..9997ff3 --- /dev/null +++ b/Designer.tmproj @@ -0,0 +1,278 @@ + + + + + currentDocument + syncdev + documents + + + expanded + + name + aes_designer + regexFolderFilter + !.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$ + sourceDirectory + + + + fileHierarchyDrawerWidth + 246 + metaData + + docroot/application.js + + caret + + column + 0 + line + 0 + + firstVisibleColumn + 0 + firstVisibleLine + 3 + + docroot/classes/application.js + + caret + + column + 29 + line + 28 + + columnSelection + + firstVisibleColumn + 0 + firstVisibleLine + 0 + selectFrom + + column + 22 + line + 28 + + selectTo + + column + 29 + line + 28 + + + docroot/classes/card.class.js + + caret + + column + 6 + line + 272 + + firstVisibleColumn + 0 + firstVisibleLine + 46 + + docroot/classes/cookie.class.js + + caret + + column + 0 + line + 0 + + firstVisibleColumn + 0 + firstVisibleLine + 17 + + docroot/classes/history.class.js + + caret + + column + 0 + line + 0 + + firstVisibleColumn + 0 + firstVisibleLine + 136 + + docroot/classes/layouts/layout.primary.class.js + + caret + + column + 0 + line + 0 + + firstVisibleColumn + 0 + firstVisibleLine + 358 + + docroot/classes/overlay.class.js + + caret + + column + 0 + line + 0 + + firstVisibleColumn + 0 + firstVisibleLine + 65 + + docroot/classes/roundcorners.class.js + + caret + + column + 0 + line + 0 + + firstVisibleColumn + 0 + firstVisibleLine + 7 + + docroot/data/strings.en.js + + caret + + column + 3 + line + 64 + + firstVisibleColumn + 0 + firstVisibleLine + 0 + + docroot/specialcases.css + + caret + + column + 0 + line + 0 + + firstVisibleColumn + 0 + firstVisibleLine + 0 + + syncdev + + caret + + column + 22 + line + 9 + + firstVisibleColumn + 0 + firstVisibleLine + 0 + + + openDocuments + + docroot/classes/cookie.class.js + docroot/classes/overlay.class.js + docroot/classes/roundcorners.class.js + docroot/classes/history.class.js + docroot/classes/card.class.js + docroot/classes/layouts/layout.primary.class.js + docroot/classes/application.js + docroot/application.js + docroot/data/strings.en.js + docroot/specialcases.css + syncdev + + showFileHierarchyDrawer + + showFileHierarchyPanel + + treeState + + aes_designer + + isExpanded + + subItems + + build_system + + isExpanded + + subItems + + + cgi-bin + + isExpanded + + subItems + + + docroot + + isExpanded + + subItems + + classes + + isExpanded + + subItems + + layouts + + isExpanded + + subItems + + + + + data + + isExpanded + + subItems + + + lib + + isExpanded + + subItems + + + + + + + + windowFrame + {{151, 48}, {1199, 830}} + + 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 @@ +#!/usr/bin/python +############### +# cmpcss +# Compress a CSS file in prepration for production use. +# +# AUTHOR +# Michael Crute (mcrute@gmail.com) +# DATE +# 16 June 2006 +# VERSION +# 1.0 +# PURPOSE +# An implementation of a CSS compressor in Python. Written to +# prepare development CSS to production ready CSS. Does the +# following: +# * Removes spaces intelligently +# * Removes charset declarations +# * Removes extra semicolons +# * Flattens the file (tabs and line breaks) +# USAGE +# cmpcss site_dev.css > site_prod.css +# LICENSE +# You may copy and redistribute this program as you see fit, with no +# restrictions. +# WARRANTY +# This program comes with NO warranty, real or implied. If it eats +# your CSS, it's not my problem, you should have kept a backup. +############### + +import sys +import re +#import string + +# The chewy caramel center of the program +# Don't bitch about the short variable names, it makes it easy to read +def cleanline(theLine): + # Kills line breaks, tabs, and double spaces + p = re.compile('(\n|\r|\t|\f|\v)+') + m = p.sub('',theLine) + + # Kills double spaces + p = re.compile('( )+') + m = p.sub(' ',m) + + # Removes last semicolon before } + p = re.compile('(; }|;})+') + m = p.sub('}',m) + + # Removes space before { + p = re.compile('({ )+') + m = p.sub('{',m) + + # Removes all comments + p = re.compile('/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/') + m = p.sub('',m) + + # Strip off the Charset + p = re.compile('@CHARSET .*;') + m = p.sub('',m) + + # Strip spaces before the { + p = re.compile(' {') + m = p.sub('{',m) + + # Strip space after : + p = re.compile(': ') + m = p.sub(':',m) + + # Strip space after , + p = re.compile(', ') + m = p.sub(',',m) + + # Strip space after ; + p = re.compile('; ') + m = p.sub(';',m) + +# May return to add color code compression +# not a huge space saver so also not high priority +# p = re.compile('.*#[0-9A-F]{6}.*') +# cc = p.match(m) + +# if cc: +# cc = compcolor(m) + + return m + +#def compcolor(m): + +# Boring usage info for people too dumb to use the program +def printusage(): + print '' + print 'SoftGroup CSS Compressor' + print '' + print 'This is a compressor for CSS files.' + print 'As far as I know it doesn\'t break the standards' + print 'compliance of the file. If you can find a way' + print 'to make it break a file please email me.' + print '' + print 'Usage: cmpcss ' + sys.exit() + +# The main routine +if ( __name__ == "__main__" ): + if (len(sys.argv) <= 1): + printusage() + + # Open the file for reading + try: + theFile = file(sys.argv[1], "r") + except IOError: + print 'I couldn\'t open your file.' + sys.exit() + + # Initialize temp so it's not scoped to the for loop + temp = '' + + # Loop through the file line by line and clean it + for line in theFile: + temp = temp + cleanline(line) + + # Once more, clean the entire file string + print cleanline(temp) + + # Cleanup after ourselves + 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 @@ +#!/bin/sed -f +# +# SED script for parsing out include file names from +# the application.js file. +# + +# Remove single line comments first +/^\s+\/\//d + +# Remove multi-line comments +/^\s+\/\*.+/d +/\/\*/,/\*\// { + s/.*//g +} + +# Replace the require lines with just their contents +s/this\.require\(["']([^"']*)["']\);/\1/ + +# Remove all lines that don't end in js +# assumes we are getting all of our JS files +/js$/!d + +# Remove the spaces before and after the filename +# assumes there are no spaces in the filename iteself +s/\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 @@ +#!/bin/bash +# +# ExxonMobil Designer Site Build Script +# by Mike Crute, EYEMG (mcrute@eyemg.com) +# +# This is a pretty simple build script just export a +# tag or the trunk from SVN and run ./make, running +# make (w/out the ./) will not work. You can then +# take the generated tarball and explode it on the +# production server w/out any further configuration. +# + +cd docroot + +# Create the build directory and copy over the cgi-bin +mkdir -p build/{docroot,cgi-bin} +cp -R ../cgi-bin/* build/cgi-bin/ + +# Create a build date file +echo "Designer Site Built `date +'%Y-%m-%d %H:%M:%S'` EST" > build/docroot/build.date +echo "Code Version: `grep 'releaseVersion' classes/sme.namespace.js | cut -d"'" -f 2 `" >> build/docroot/build.date +echo "Subversion Revision: `svn info | grep 'Revision' | awk '{ print $2 }'`" >> build/docroot/build.date + +# Remove stuff from the cgi-bin that does not belong in +# production. +rm build/cgi-bin/*.sql + +# Also copy over any files that don't need special processing +cp -R images \ + blank.gif \ + favicon.ico \ + custom_content \ +build/docroot/ + +# Prepare the .htaccess file for production +# +# We use a sparse layout in development where each class is +# in its own separate file but in production we use a solid +# layout that is also gzipped so we have to un-comment the +# gzip headers in our htaccess file. + +sed ' + /### PRODUCTION ###/,/### END PRODUCTION ###/ { + /^###.*/d + s/#//g + } +' .htaccess > build/docroot/.htaccess + +# Minify the PNG Behavior +sed ' + /\/\*/,/\*\// { + /.*/d; + } +' pngbehavior.htc > build/docroot/pngbehavior.htc + +# Concatenate the Javascript files into a single library file +# +# application.js loads all the javascript files in the development +# environment. When we go to production we need to parse out the +# actual script names being loaded and cat them, in order, into +# the final output application.js file. + +# GNU sed uses the -r flag for extended regular expressions, +# Darwin uses the -E flag for regular expressions. If neither +# of these hold true we might as well fail. +if [[ `uname` == 'Linux' ]]; then + MYFILES=`sed -r -f ../build_system/get_jsfiles.sed application.js` +elif [[ `uname` == 'Darwin' ]]; then + MYFILES=`sed -E -f ../build_system/get_jsfiles.sed application.js` +else + print 'No valid sed command could be determined.' + exit 1 +fi + +for item in $MYFILES; do + cat $item >> build/docroot/application.js.in +done + +# Remove development code from the application code +# and append it to the libraries files +sed '/^;;;.*/d' build/docroot/application.js.in >> build/docroot/application.js.out +sed '/^\/\*;;;.*/d' build/docroot/application.js.out >> build/docroot/application.js +rm build/docroot/application.js.in build/docroot/application.js.out + +# Minify index.html by removing leading spaces on each line +# as well as the ID comment, blank lines, and any script tags +# that appear in the of the document. +sed 's/^[[:space:]]*//; /^$/d; s/\n$//; // { /.*/d; }' index.html > build/docroot/index.html +sed 's/^[[:space:]]*//; /^$/d; s/\n$//; // { /.*/d; }' logged_out.html > build/docroot/logged_out.html + +# Minify the application Javascript using the YUI Compressor +java -jar ../build_system/yui_compressor.jar -o application.o build/docroot/application.js > /dev/null 2>&1 +mv application.o build/docroot/application.js + +# Minify the JSON data files +mkdir -p build/docroot/data +for item in `ls data/`; do + java -jar ../build_system/yui_compressor.jar -o build/docroot/data/$item data/$item > /dev/null 2>&1 +done + +# Can't minimize JSON, the semicolon at the end breaks everything +cat data/card_tables.js > build/docroot/data/card_tables.js + +# Minify the application CSS using our custom compressor +../build_system/cmpcss application.css > build/docroot/application.css +../build_system/cmpcss specialcases.css > build/docroot/specialcases.css + +# Gzip all that should be gzipped. This really should be done server-side +# but for now we work around it by statically compressing them at build time +# and serving it with the correct headers in the .htaccess file +gzip build/docroot/application.js +gzip build/docroot/application.css +gzip build/docroot/index.html + +mv build/docroot/application.js.gz build/docroot/application.js +mv build/docroot/application.css.gz build/docroot/application.css +mv build/docroot/index.html.gz build/docroot/index.html + +# Tar it all up in a development dump +mv build aes_designer +cd aes_designer + +# Removing the date since its really not needed +tar -cjvf ../../aes_designer.tbz2 * > /dev/null 2>&1 +#tar -cjvf ../../aes_designer-`date +%Y%m%d_%H%M%S`.tbz2 * > /dev/null 2>&1 + + +cd .. + +rm -rf aes_designer/ diff --git a/build_system/yui_compressor.jar b/build_system/yui_compressor.jar new file mode 100755 index 0000000..4efe293 Binary files /dev/null and b/build_system/yui_compressor.jar 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 @@ +#!/usr/bin/perl + +$|=1; + +srand; + +use strict; + +use Apache::Request; +use Apache::Constants qw(REDIRECT); +use Benchmark::Timer; +use HTML::Template; +use Bit::Vector; +use Image::Magick; + +use Compose::local_lib; +use Compose::db_connection; + +my $r = Apache::Request->new(Apache->request); +$r->send_http_header('text/javascript'); + +my $tt = new Benchmark::Timer; +$tt->start('all'); + +my $local_lib = new Compose::local_lib(); + +my $dbh = new Compose::db_connection('localhost','aes','apache','webconnect'); + +my $form; +foreach my $key (sort $r->param) { + $form->{$key} = $local_lib->fix_spaces($r->param($key)); +} + +# table=home + +################################################## +# +# +if ($r->method() eq "GET") { + + if ($form->{'table'} eq "") { + + 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() ); + my %tables = $dbh->queryDB($qry,'select_0'); + + print qq( +[ +); + my @list=(); + foreach my $table (sort { $a cmp $b } keys %tables) { + push @list,"\t\"$table\""; + } + + print join(",\n",@list) . qq( +] +); + + + + } elsif ($form->{'table'} ne "") { + + my $qry = ""; + + my $ckb = "checkbox_1"; + if ($form->{'table'} eq "intro") { + $ckb = "checkbox_0"; + } + + my $t = new Benchmark::Timer; + + $form->{'num_cards'} = 15; # if ($form->{'num_cards'} eq ""); + $form->{'w'} = 900 if ($form->{'w'} eq "" || $form->{'w'} < 900); + $form->{'h'} = 400 if ($form->{'h'} eq "" || $form->{'h'} < 400); + $form->{'max'} = 20 if ($form->{'max'} eq ""); + + # get cards + if ($form->{'table'} eq 'home') { + $qry = qq( + SELECT + content_id, + enc_content_id, + textfield_0 AS title, + select_0 AS category, + checkbox_4 AS protected, + image_1_width AS rotated_width, + 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 + checkbox_0 = "Yes" AND + $ckb = "Yes" AND + checkbox_2 != "Yes" AND + !(textfield_5 is NULL or textfield_5 = "") + ORDER BY + last_modified_date + LIMIT + $form->{'num_cards'} + ); + } else { + #$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'}) ; + + $qry = qq( + SELECT + content_id, + enc_content_id, + textfield_0 AS title, + select_0 AS category, + checkbox_4 AS protected, + image_1_width AS rotated_width, + 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 + checkbox_2 != "Yes" AND + !(textfield_5 IS NULL OR textfield_5 = "") + ORDER BY + last_modified_date + LIMIT + $form->{'num_cards'} + ); + + } + + my %res = $dbh->queryRawDB($qry); + + my @master_v = new Bit::Vector($form->{'w'},$form->{'h'}); + my ($hei,$wid); + my @rowM=(); + + my $back_image; + if ($form->{'gen'} ne "") { + $back_image = Image::Magick->new(size=>"$form->{'w'} x $form->{'h'}"); + $back_image->ReadImage('xc:white'); + } + + my ($idx,$x,$y); + + print qq( [ \n); + my $iter = 0; + foreach $idx (sort {$a <=> $b} keys %res) { + + my $protected = ($res{$idx}{'protected'} eq 'Yes') ? 'true' : 'false'; + + ($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'}); + + print qq(\t{ + "cid" : "$res{$idx}{'enc_content_id'}", + "title" : "$res{$idx}{'title'}", + "category" : "$res{$idx}{'category'}", + "locked" : $protected, + "x" : "$x", + "y" : "$y", + "chip" : "$res{$idx}{'rotated_image'}" + }); + print ",\n" if (++$iter != keys(%res)); + } + print qq( \n ]); + + if ($form->{'gen'} ne "") { + $back_image->Write("/usr/web/designer/docroot/test.png"); + } + + } + +} + +#$tt->stop('all'); +#print $tt->report() ."
"; +#print qq() if ($form->{'gen'}); + +########################################### +# + +sub place_data { + + my $master_v = shift; + my $page_width = shift; + my $page_height = shift; + my $image_data_zdegree_image = shift; + my $image_data_rotated_image = shift; + my $image_data_rotated_width = shift; + my $image_data_rotated_height = shift; + my $image_data_publish_loc = shift; + my $back_image = shift; + my $gen_image = shift; + my $max_collide = shift; + + my $done = 0; + my $attempts = 0; + + my $t = new Benchmark::Timer; + + my ($max_x,$max_y,$xpos,$ypos,$vector,$sdone,$start,$attempts,$sattempts,$min,$max,$collisions,$xrand,$yrand ); + + while ((!$done) && $attempts < 500) { + + $xrand = int(rand($page_width-20-$image_data_rotated_width)+10); + $yrand = int(rand($page_height-20-$image_data_rotated_height)+10); + + my $max_collide = int($image_data_rotated_width*$image_data_rotated_height*($max_collide/100)); + + $max_x = $xrand+$image_data_rotated_width; + $max_y = $yrand+$image_data_rotated_height; + + $t->start('3.1'); + + $collisions = 0; + + for ($ypos = $yrand;$ypos <= $max_y; $ypos++) { + $vector = @{$master_v}->[$ypos]; + + $sdone=0; + $start = $xrand; + $sattempts = 0; + + while (!$sdone && $sattempts++ < 20) { + ($min,$max) = $vector->Interval_Scan_inc($start); + + if ($max+1 >= $page_width) { + $sdone=1; + } + + if ($min < $max_x && $max) { + if ($max > $max_x || $max+1 >= $page_width) { + $collisions += $max_x - $min; + $sdone = 1; + } else { + $collisions += $max - $min; + $start = $max+1; + } + + } elsif ($min > $max_x || !$max || !$min) { + $sdone=1; + } + } + } + + #$t->stop('3.1'); + #print $t->report() . "
"; + + + if ($collisions < $max_collide) { + + # place image in master array + + my ($xpos,$ypos); + my $max_x = $xrand+$image_data_rotated_width; + my $max_y = $yrand+$image_data_rotated_height; + + #$t->start('5.1'); + + my $vector; + for ($ypos = $yrand;$ypos <= $max_y; $ypos++) { + $vector = @{$master_v}->[$ypos]; + $vector->Interval_Fill($xrand,$max_x); + } + + #$t->stop('5.1'); + #print $t->report() . "
"; + + $done = 1; + + if ($gen_image) { + my $card_image = Image::Magick->new(); + $card_image->Read($image_data_publish_loc); + $back_image->Composite(image=>$card_image, x=>$xrand,y=>$yrand); + } + + } else { + + $attempts++; + } + } + + #print "ATTEMPTS: $attempts
"; + + if ($attempts >= 499) { + #print "UNABLE TO PLACE IMAGE
"; + } + + + return ($xrand,$yrand,$back_image); +} + +################################################## +# + +sub check_collisions { + + my $master_v = shift; + my $width = shift; + my $height = shift; + my $xrand = shift; + my $yrand = shift; + my $max_collide = shift; + my $page_width = shift; + + + my $t = new Benchmark::Timer; + + my $max_x = $xrand+$width; + my $max_y = $yrand+$height; + + #$t->start('3.1'); + + my $btot = 0; + + my ($xpos,$ypos,$vector,$sdone,$start,$attempts,$min,$max); + + for ($ypos = $yrand;$ypos <= $max_y; $ypos++) { + $vector = @{$master_v}->[$ypos]; + + $sdone=0; + $start = $xrand; + $attempts = 0; + + while (!$sdone && $attempts++ < 20) { + ($min,$max) = $vector->Interval_Scan_inc($start); + + if ($max+1 >= $page_width) { + $sdone=1; + } + + if ($min < $max_x && $max) { + if ($max > $max_x || $max+1 >= $page_width) { + $btot += $max_x - $min; + $sdone = 1; + } else { + $btot += $max - $min; + $start = $max+1; + } + + } elsif ($min > $max_x || !$max || !$min) { + $sdone=1; + } + } + } + + #$t->stop('3.1'); + #print $t->report() . "
"; + + return $btot; + +} + 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 @@ +drop database if exists designer; +create database designer; + +use designer; + + + +create TABLE sketchbook ( + id int not NULL auto_increment, + user_id int not NULL, + sketchbook_data text, + INDEX user_id(user_id), + PRIMARY KEY(id) +); + 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 @@ + + + + Please Log In! + + + + + + +

Please Log In!

+

You need to be registered and logged in to use certain features of the Material Experience Website.

+ + + +

+ If you are not yet registered, + register here! If you have forgotten your password, + we can e-mail it to you. +

+ +
+ + +

+ + " size="30" /> +

+

+ + + Forgot it? +

+ +

+
+ + 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 @@ +#!/usr/bin/perl + +$|=1; + +use strict; + +use Apache::Request; +use Apache::Constants qw(REDIRECT); +use MIME::Base64 qw(encode_base64 decode_base64); +use HTML::Template; + +use Compose::local_lib; +use Compose::site_user_lib; + +$Apache::DBI::DEBUG=2; + +my $r = Apache::Request->new(Apache->request); +#$r->send_http_header('text/html'); + +my $dbh = new Compose::db_connection('localhost','aes','apache','webconnect'); + +my $client_lib = new Compose::client_lib(); +my $local_lib = new Compose::local_lib($client_lib,0); + +my $client_id = 1; +$client_lib->setup_client($client_id); + + +my $site_user_lib = new Compose::site_user_lib($client_lib); + +$client_lib->{'dbh'}{'debug'} = 2; + +my ($form,$PASS); + +foreach my $key (sort $r->param) { + $form->{$key} = $local_lib->fix_spaces($r->param($key)); + #print "$key: $form->{$key}
"; +} + +my %cookiejar = Apache::Cookie->new($r)->parse; +my $newcookie = Apache::Cookie->new($r); + +##################################################### +# Get the username and password from the cookie. + +unless ($cookiejar{'Site'} || ($form->{'user'} && $form->{'password'})) { + $r->send_http_header('text/html'); + + my $template = HTML::Template->new( filename => "html/login.html", path => [ "$client_lib->{'client'}->{'server_docroot'}" ], die_on_bad_params => 0); + + $template->param('user' => $form->{'user'}); + + print $template->output(); + + exit(0); +} + +my %cookie_hash; + +if ( $cookiejar{'Site'} ) { + + my @values = $cookiejar{'Site'}->value; + + for (my $i=0;$i{'user'} && $form->{'password'}) { + + my $site_user = &get_user_info($form->{'user'},$dbh); + + if (lc $site_user->{'user_name'} eq lc $form->{'user'}) { + if ($site_user->{'user_passwd'} eq $form->{'password'}) { + &bake_cookie($r,$client_lib,$newcookie,\%cookie_hash,$form,$site_user,$dbh); + exit(0); + } else { + $errors .= qq(The password you entered is incorrect. Please try again.
); + } + + } else { + $errors .= qq(The user name $form->{'user'} does not exist.
); + } + +} elsif ($cookie_hash{'Site'}) { + + + my ($user, $password) = split /:/, decode_base64($cookie_hash{'Site'}), 2; + + if ($user eq "" ) { + $errors .= qq($cookie_hash{'Site'} Cookie could not be read.
); + + } else { + + my $site_user = &get_user_info($user,$dbh); + + if (defined $site_user->{'user_name'} && lc $site_user->{'user_name'} eq lc $user ) { + + if ($site_user->{'user_passwd'} eq $password) { + &bake_cookie($r,$client_lib,$newcookie,\%cookie_hash,$form,$site_user,$dbh); + exit(0); + } else { + $errors .= qq(The password you entered is incorrect. Please try again.
); + } + + } else { + $errors .= qq(The user name $form->{'user'} does not exist.
) if ($form->{'user'}); + } + } +} + + +$r->send_http_header('text/html'); + +my $template = HTML::Template->new( filename => "html/login.html", path => [ "$client_lib->{'client'}->{'server_docroot'}" ], die_on_bad_params => 0); + +$template->param('user' => $form->{'user'}); +$template->param('error' => "$errors"); + +print $template->output(); + + + +################################### + +sub bake_cookie { + + my $r = shift; + my $client_lib = shift; + my $cookiejar = shift; + my $cookie_hash = shift; + my $form = shift; + my $site_user = shift; + my $dbh = shift; + + if ( ($cookie_hash->{uri} =~ /login.pl/) || $cookie_hash->{uri} eq "") { + $cookie_hash->{uri} = "/"; + } + $cookie_hash->{uri} = $form->{'redir'}; + + + # We have some valid credientials, so set an authorization cookie. + my @values = ( + uri => $cookie_hash->{uri}, + Cookie => encode_base64(join ":", ($form->{'user'},$form->{'password'})), + ); + + my $c = $r->connection; + my $ip = $c->remote_ip; + 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")); + $dbh->updateDB($ins); + + + $cookiejar->name('Site'); + $cookiejar->value(\@values); + $cookiejar->path('/'); + $cookiejar->domain('.santoprene.com'); + $cookiejar->bake; + + + $r->status(REDIRECT); + $r->headers_out->set(Location => $cookie_hash->{uri}); + $r->send_http_header; + + + +} +####################### + +sub get_user_info { + + my $uid = shift; + my $dbh = shift; + + my ($qry,$gqry,%user_info,%group_info); + + %user_info=%group_info=(); + + ########################### + # Internet User + + + $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) ); + + %user_info = $dbh->queryRawDB($qry); + + my %USER_INFO; + + foreach my $k (keys %{$user_info{'0'}}) { + $USER_INFO{$k} = $user_info{'0'}{$k}; + } + + $USER_INFO{'FULL_NAME'} = "$USER_INFO{'first_name'} " if ($USER_INFO{'first_name'} ne ""); + $USER_INFO{'FULL_NAME'} .= "$USER_INFO{'last_name'} " if ($USER_INFO{'last_name'} ne ""); + + foreach my $group (keys %group_info) { + $USER_INFO{'group_info'}{$group_info{$group}{'group_id'}} = $group_info{$group}; + $USER_INFO{'groups'}{$group} = 1; + } + + return \%USER_INFO; +} + + 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 @@ +#!/usr/bin/perl + +$|=1; + +srand; + +use strict; + +use Apache::Request; +use Apache::Constants qw(REDIRECT); +use Benchmark::Timer; +use HTML::Template; +use MIME::Base64 qw(encode_base64 decode_base64); +use Compose::local_lib; +use Compose::db_connection; + +my $r = Apache::Request->new(Apache->request); + +my $local_lib = new Compose::local_lib(); + +my $dbh_aes = new Compose::db_connection('localhost','aes','apache','webconnect'); +my $dbh = new Compose::db_connection('localhost','designer','apache','webconnect'); + +my $form; +foreach my $key (sort $r->param) { + $form->{$key} = $local_lib->fix_spaces($r->param($key)); +} + +my %cookiejar = Apache::Cookie->new($r)->parse; +my $newcookie = Apache::Cookie->new($r); +my ($user, $password, %user_info, $qry, %user_info, %cookie_hash); + +################################################## +# +unless ($cookiejar{'Site'}) { + print "Content-type: text/html\n"; + print "Status: 403\n"; + exit(0); +################################################## +# +} elsif ( $cookiejar{'Site'} ) { + + my @values = $cookiejar{'Site'}->value; + + for (my $i=0;$i); + $cookie_hash{$values[$i]} = $values[$i+1]; + } + + ($user, $password) = split /:/, decode_base64($cookie_hash{'Cookie'}), 2; + + $qry = qq(select * from admin_user_info where user_name="$user"); + + %user_info = $dbh_aes->queryRawDB($qry); + + if ($user_info{'0'}{'id'} eq "") { + print "Content-type: text/html\n"; + print "Status: 403\n"; + exit(0); + } +} + + + +################################################## +# +if ($r->method() eq "GET") { + + $qry = qq(select * from sketchbook where user_id="$user_info{'0'}{'id'}"); + + my %data = $dbh->queryRawDB($qry); + + if ($data{'0'}{'sketchbook_data'} eq "") { + if ($form->{'interactive'} ne "false") { + $r->send_http_header('text/html'); + print qq{ + + + + Logged In + + + + +

Logged In

+

Thanks for logging in. You can close this card now.

+ +
+ }; + } else { + print "Status: 404\n"; + print "Content-type: text/html\n"; + } + + exit(0); + } else { + if ($form->{'interactive'} ne "false") { + $r->send_http_header('text/html'); + print qq{ + + + + Logged In + + + + +

Logged In

+

Thanks for logging in. You can close this card now.

+ +
+ }; + } else { + $r->send_http_header('text/javascript'); + print "$data{'0'}{'sketchbook_data'}\n"; + } + } + +################################################## +# +} else { + + if ($form->{'sketchbook_data'} ne "") { + my $upd = qq(delete from sketchbook where user_id="$user_info{'0'}{'id'}"); + $dbh->updateDB($upd); + + $form->{'sketchbook_data'} =~ s/"/\\"/g; + + my $upd = qq(insert into sketchbook (sketchbook_data,user_id) values ("$form->{'sketchbook_data'}","$user_info{'0'}{'id'}")); + my %data = $dbh->queryRawDB($upd); + } + + print "Content-type: text/html\n\n"; + +} + 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 @@ +#!/usr/bin/perl + +$|=1; + +use strict; +use Apache::Request; +use Apache::Constants qw(REDIRECT); +use Compose::local_lib; +use Compose::db_connection; + +my $r = Apache::Request->new(Apache->request); +my $local_lib = new Compose::local_lib(); +my $dbh = new Compose::db_connection('localhost','aes','apache','webconnect'); +my $item = $local_lib->fix_spaces($r->param('card')); + +$r->send_http_header('text/javascript'); + +if ($r->method() eq "GET") { + my %res = $dbh->queryRawDB(qq( + SELECT + enc_content_id, + textfield_0 AS title, + select_0 AS category, + CONCAT(clients.website.site_url,clients.website.publish_docroot,"/",human_dir,"/index.html") AS page_loc, + CONCAT(clients.website.site_url,clients.website.publish_docroot,"/",human_dir,"/orig/",image_1) AS rotated_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 + enc_content_id = "$item" AND + !(textfield_5 is NULL or textfield_5 = "") AND + checkbox_2 != "Yes" + )); + + print qq( +\({ + "cid" : "$res{0}{'enc_content_id'}", + "title" : "$res{0}{'title'}", + "category" : "$res{0}{'category'}", + "url" : "$res{0}{'page_loc'}", + "chip" : "$res{0}{'rotated_image'}" +}\) + ); +} \ 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 @@ +#!/usr/bin/perl + +# +# Perl Based Same Origin Proxy +# + +$|=1; + +srand; +use strict; + +use Apache::Request; +use LWP::Simple; +use LWP::UserAgent; + +my $r = Apache::Request->new(Apache->request); +$r->send_http_header('text/javascript'); + +my @url = split(/site=/,$r->parsed_uri()->unparse()); + +my $browser = LWP::UserAgent->new; +$browser->credentials( + 'admin.santoprene.com:80', + 'Admin', + 'DESIGNER_SOP_USER' => 'dsop4edit' +); + +print $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 @@ +#!/usr/bin/perl + +# +# Perl Based Same Origin Proxy +# + +$|=1; + +srand; +use strict; + +use Apache::Request; +use LWP::Simple; + +my $r = Apache::Request->new(Apache->request); +$r->send_http_header('text/javascript'); + +print 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 @@ +#!/bin/bash +# +# ExxonMobil Designer Site Deploy Script +# by Mike Crute, EYEMG (mcrute@eyemg.com) +# +# This is the deployment script for the AES designer site +# it exists to script the last piece of building and deploying +# the designer site. +# + +if [ `hostname` == 'calvin.eyemg.com' ]; then + cd /usr/web/designer + rm -rf cgi-bin/ docroot/ + tar -xvjf aes_designer.tbz2 + echo "Code Deployed: `date +'%Y-%m-%d %H:%M:%S'` EST" >> docroot/build.date + rm deploy +else + RED="\033[0;31m" + GREEN="\033[0;32m" + BROWN="\033[0;33m" + CYAN="\033[0;36m" + COLOR_CLEAR="\033[00m" + + clear + echo -e "${BROWN}***************************************************" + echo -e "${BROWN}* *" + echo -e "${BROWN}*${COLOR_CLEAR} ${CYAN}AES Designer Site Deployment Script${COLOR_CLEAR} ${BROWN}*" + echo -e "${BROWN}* *" + echo -e "${BROWN}***************************************************" + echo -e "$COLOR_CLEAR" + echo -n "Your SSH Username: " + read myuser + echo "" + + if [[ $myuser == '' ]]; then + echo -e "\n${RED}FAILED:${COLOR_CLEAR} Enter a username." + exit 1 + fi + + ./make + + if [[ $? != 0 ]]; then + echo -e "\n${RED}FAILED:${COLOR_CLEAR} Make failed, wisely refusing to continue." + exit 1 + fi + + scp ./aes_designer.tbz2 $myuser@calvin.eyemg.com:/usr/web/designer + + if [[ $? != 0 ]]; then + echo -e "\n${RED}FAILED:${COLOR_CLEAR} Failed to deploy code tarball." + exit 1 + fi + + scp ./deploy $myuser@calvin.eyemg.com:/usr/web/designer + + if [[ $? != 0 ]]; then + echo -e "\n${RED}FAILED:${COLOR_CLEAR} Failed to deploy install script." + exit 1 + fi + + ssh $myuser@calvin.eyemg.com /usr/web/designer/deploy > /dev/null 2>&1 + + if [[ $? != 0 ]]; then + echo -e "\n${RED}FAILED:${COLOR_CLEAR} Remote command execution failed." + exit 1 + fi + + rm aes_designer.tbz2 + + echo -e "\n ${GREEN}SUCCESS:${COLOR_CLEAR} Go check and make sure!" +fi diff --git a/docroot/.htaccess b/docroot/.htaccess new file mode 100755 index 0000000..70fc492 --- /dev/null +++ b/docroot/.htaccess @@ -0,0 +1,46 @@ +# +# Material Experience - .htaccess File +# +# EYEMG - Interactive Media Group +# Created by Mike Crute (mcrute@eyemg.com) +# Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07 +# +# Workarounds for missing mod_gzip and caching. This file +# is pre-processed before the site goes into production +# since we don't really gzip anything in development. +# +# WARNING!: Seconds of code marked with ### PRODUCTION### +# are blocks un-commented by the build system, you can +# add or remove them just be careful what you ask for. +# + +# Cache Control Headers +### PRODUCTION ### +#ExpiresActive on +#ExpiresDefault "access plus 1 week" +### END PRODUCTION ### + +# Disable the ETag Header (per YSlow) +# +# I know that this probably doesn't mean anything for us +# because we don't serve AES with a cluster but just for +# the sake of getting straight As in YSlow we'll remove +# them. +FileEtag none + +### PRODUCTION ### +# +# Header set Content-Encoding "gzip" +# + +# +# Header set Content-Encoding "gzip" +# +### END PRODUCTION ### + + + Header set Content-Type "text/html;charset=utf-8" +### PRODUCTION ### +# Header set Content-Encoding "gzip" +### END PRODUCTION ### + 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 @@ + + + + Please Log In! + + + + + + +

Your account has been deleted!

+ +

Your account information has been deleted and you are now logged out of Material Experience Website.
+ You will have to register and log in again if you would like to gain access to certain areas of this Web site.

+ +

You can close this card to continue.

+ + + + 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 @@ +/* + * Material Experience - Site Stylesheet + * + * EYEMG - Interactive Media Group + * Created by Mike Crute (mcrute@eyemg.com) + * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07 + * + * Site stylesheet the covers most of the look and feel of the site. + * Some of the CSS is also in the application logic so if you can't + * find what your looking for here go there. + */ + +/* + * General Application Styles + */ +html +{ + overflow: hidden; +} + +body +{ + font: 10px Verdana,sans-serif; + margin: 0px; + overflow: hidden; +} + +a +{ + color: blue; + text-decoration: underline; +} + +a img +{ + border: 0px; +} + +h1 +{ + font-size: 1.7em; +} + +h2 +{ + font-size: 1.6em; +} + +img +{ + /* The infamous PNG fix */ + behavior: url('pngbehavior.htc'); +} + +/* + * Application Header Styles + */ +img#tagline +{ + position: absolute; + top: 0px; + right: 0px; +} + +div#header, div#footer +{ + background: white; + height: 60px; + width: 100%; + position: relative; + z-index: 99; +} + +div#header +{ + height: 80px; +} + +p#version +{ + position: absolute; + bottom: 0px; + right: 7px; + z-index: 100; + color: red; +} + +img#logo +{ + position: absolute; + z-index: 9999999999; + cursor: pointer; + padding: 10px 0px 0px 10px; +} + +/* + * Table and Chip Element Styles + */ +div.appDecorator +{ + height: 21px; + width: 6px; +} + +div.table +{ + position: absolute; + top: 60px; + left: 0px; + width: 100%; +} + +img.chip +{ + position: absolute; + cursor: pointer; +} + +/* + * Application Footer Styles + */ +p#copyright, div#footer p#copyright a +{ + color: rgb(190,190,190); + font-size: 10px; + margin-right: 0px; + text-transform: none; +} + +div#footer p#copyright a +{ + text-decoration: underline; +} + +p#copyright +{ + width: 100%; + display: block; + position: absolute; + bottom: 0px; + left: 0px; + text-align: center; + z-index: 100; +} + +div#footer +{ + position: absolute; + bottom: -1px; + height: 40px; + text-align: center; + padding: 5px 0px 10px 0px; +} + +div#footer img +{ + position: relative; + top: 6px; + margin-right: 5px; +} + +div#footer a +{ + font-size: 1.2em; + text-transform: uppercase; + text-decoration: none; + margin-right: 25px; +} + +/* + * History Menu Styles + */ +div.history +{ + position: absolute; + right: 20px; + top: 20px; + padding: 3px 10px; + cursor: pointer; + background: none; + text-align: right; +} + +ul#history:hover, ul#history.hovered +{ + display: block; +} + +ul#history +{ + position: absolute; + z-index: 100; + display: none; + margin: 0px; + padding: 10px 0px 0px 0px; + color: rgb(255, 255, 255); + text-decoration: none; + width: 200px; +} + +ul#history li +{ + background: rgb(145, 145, 145); + list-style: none; + padding: 5px 10px; + margin: 0px; + border-bottom: 1px solid white; + text-align: left; + color: white; + text-decoration: none; +} + +ul#history li a +{ + color: white; + text-decoration: none; + border: 0px; + display: block; + width: 100%; + height: 100%; +} + +ul#history li:last-child +{ + border-bottom: 0px; +} + +/* + * Rounded Corner Styles + */ +.round { display: block; } +.round * { display:block; height: 1px; overflow: hidden; } +.rcSlice_1 { margin: 0 10px; } +.rcSlice_2 { margin: 0 8px; } +.rcSlice_3 { margin: 0 6px; } +.rcSlice_4 { margin: 0 5px; } +.rcSlice_5 { margin: 0 4px; } +.rcSlice_6 { margin: 0 3px; } +.rcSlice_7 { margin: 0 3px; } +.rcSlice_8 { margin: 0 2px; height: 1px; } +.rcSlice_9 { margin: 0 2px; height: 2px; } +.rcSlice_10 { margin: 0 1px; height: 3px; } + +/* + * Card Frame Styles (Card Class) + */ +img.commandbtn +{ + height: 11px; + width: 11px; + display: inline; + margin: 0px 2px; + cursor: pointer; +} + +span.commandlabel +{ + height: auto; + z-index: 9; + display: inline; + position: relative; + top: -2px; + font-size: 1.1em; +} + +span.commandlabel * +{ + height: auto; + display: inline; + font-size: 1.1em; +} + +span.cardTitle +{ + font-size: 1.1em; +} + +.cardTitle +{ + position: absolute; + color: white; + z-index: 1; + height: auto; + top: 4px; + left: 15px; +} + +.buttonbox +{ + position: absolute; + right: 15px; + top: 6px; + z-index: 100; + height: auto; + color: white; + font-size: 0.8em; +} + +/* + * Card Resource List Styles (Card Layout) + */ +.resourceList +{ + margin: 0px; + padding: 0px; +} + +.resourceList li +{ + list-style: none; + padding-bottom: 7px; +} + +/* + * Card Thumbnail Styles (Card Layout) + */ +.mediaThumb +{ + width: 50px; + height: 50px; + padding: 10px 10px 0px 0px; + cursor: pointer; +} + +.thumbStrip +{ + text-align: center; +} + +.thumbStrip img +{ + width: 50px; + height: 50px; + cursor: pointer; +} + +/* + * Sketchbook Trash Can Styles + */ +#sbTrash +{ + position: absolute; + bottom: 20px; + right: 10px; +} + +.trashHi +{ + background-color: lightyellow; +} + +/* + * Skip Link Styles (Intro Card) + */ +.skiplink +{ + color: rgb(145, 145, 145); + text-decoration: none; + font-size: 1.1em; + position: relative; + z-index: 99; +} + +.skipimg +{ + vertical-align: bottom; + margin-left: 3px; +} + +.skipbox +{ + text-align: center; + margin-top: -50px; +} + +/* + * Custom Content Styles + */ +ul#navigation +{ + margin: 0px; + padding: 0px; +} + +ul#navigation li +{ + margin: 5px 30px 0px 0px; + list-style: none; + border: 1px solid white; +} + +ul#navigation li a +{ + display: block; + text-decoration: none; + padding: 2px; + border: 3px solid black; +} + +ul#navigation li ul li a +{ + margin: 0px -60px 0px -30px; +} + +.selected +{ + background: black; + color: white; + padding: 5px; +} +/* +.narrowCol, .wideCol +{ + float: left; + height: 460px; + overflow: hidden; +} + +.narrowCol +{ + width: 290px; + margin-right: 20px; +} + +.wideCol +{ + width: 569px; +} +*/ + +/* + * Custom Content Color Styles + */ +ul#navigation li a +{ + color: white; + background: rgb(100, 100, 100); + border: 3px solid rgb(100, 100, 100); +} + +ul#navigation li ul li a +{ + background: white; + color: rgb(100, 100, 100); +} + +.selected +{ + background: #ccc; + color: #000; + padding: 0px; +} + +span.glossterm +{ + text-transform: uppercase; +} + +span.anchor +{ + text-transform: uppercase; + font-weight: bold; +} 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 @@ +/* + * Material Experience - Development Loader + * + * EYEMG - Interactive Media Group + * Created by Mike Crute (mcrute@eyemg.com) + * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07 + * + * Loads the designer site in the development environment, + * this facilitates us having multiple class files. This + * file is pre-processed by the build system to pull + * out include files for building into the final site. + * + * Thus, the require statements are magic :-) + */ + +var DesignerSite = +{ + Version: "$Revision$".match(/[0-9]+/), + + require: function(libraryName) + { + document.write(''); + }, + + load: function() + { + // Prototype Stuff + this.require("lib/prototype.js"); + + // Script.aculo.us Stuff + this.require("lib/scriptaculous/scriptaculous.js"); + this.require("lib/scriptaculous/effects.js"); + this.require("lib/scriptaculous/builder.js"); + this.require("lib/scriptaculous/dragdrop.js"); + this.require("lib/scriptaculous/slider.js"); + + // Other 3rd Party Libraries + this.require("lib/swfobject/swfobject.js"); + this.require("classes/decoder.module.js"); + + // Application Code + this.require("classes/utility.js"); + this.require("classes/chip.class.js"); + this.require("classes/cookie.class.js"); + this.require("classes/card.class.js"); + this.require("classes/bezel.class.js"); + this.require("classes/overlay.class.js"); + this.require("classes/roundcorners.class.js"); + this.require("classes/table.class.js"); + this.require("classes/history.class.js"); + this.require("classes/sketchbook.class.js"); + this.require("classes/application.js"); + + // Layout Engines + this.require("classes/layout.class.js"); + this.require("classes/layouts/layout.error.class.js"); + this.require("classes/layouts/layout.primary.class.js"); + this.require("classes/layouts/layout.special.class.js"); + this.require("classes/layouts/layout.custom.class.js"); + + // Namespaces and Data + this.require("data/strings.en.js"); + this.require("classes/sme.namespace.js"); + } +}; + +DesignerSite.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 @@ +XSym +0016 +88e2976783fa96f9cad2c9a96c39fa58 +images/blank.gif + \ 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 @@ +/* + * Material Experience - Main Application Code + * + * EYEMG - Interactive Media Group + * Created by Mike Crute (mcrute@eyemg.com) + * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07 + * + * Core application code that is responsible for starting up the application + * and initializing the core objects. + */ + +/* + * Register global actions for AJAX responders. + */ +Ajax.Responders.register( +{ + /* + * When an AJAX connection is created show the bezel that says + * "loading data". + */ + onCreate: function() + { + if (!SME.AJAXBezel) + { + SME.AJAXBezel = new Bezel({ displayTime: 0, destroy: false }).show(Strings.loadingAnim); + } + else + { + SME.AJAXBezel.show(Strings.loadingAnim); + } + }, + + /* + * Each time a requester completes we check to see if it was the last + * one, if it is then we take down the loading bezel. + */ + onComplete: function() + { + if (Ajax.activeRequestCount == 0) + { + SME.AJAXBezel.hide(); + } + }, + + /* + * When something goes wrong with loading data we die. + */ + onException: function(transport, exception) + { + if (SME.debug) + { + console.error(exception); + } + + SME.AJAXBezel.hide(); + new Bezel({ displayTime: 0 }).show(Strings.ajaxError); + } +}); + +/* + * Load the data for the card tables. + */ +function loadTables() +{ + new Ajax.Request(SME.url.tableList, + { + method: "get", + + onSuccess: function(transport) + { + transport.responseText.evalJSON().each(function(data) + { + var windowDims = window.getDimensions(); + + var table = new CardTable( + { + color: data.color, + name: data.name, + id: data.tid, + decorate: data.decorate + }); + + // Push the table onto the global table cache (see the SME namespace + // for more information about the global table cache) + SME.tables.push(table); + + // Subtracting the max chip width and height ensures that cards don't + // fall too far off the tables + table.loadChipData(SME.url.cardTables, + { + table: data.tid, + w: windowDims.width - (SME.sizes.chipMax.width / 2), + h: windowDims.height - (SME.sizes.chipMax.height / 2) + }, false); + }); + + // Initialize the sketchbook + SME.sketchbook = new Sketchbook(); + + // By default show the home table. When the history manager loads for the + // first time (after this step) it will load the right table from the URL + // if applicable. This just ensures that a table is always displayed. + CardTable.showTable("home"); + + // Show the tool box in the upper right + showToolBox(); + } + }); + + // Start up the history manager + SME.history = new HistoryManager().pollEvents(); +} + +/* + * Show an intro card. This function will gracefully pass if there are no + * intro cards to be shown. + */ +function showIntro() +{ + new Ajax.Request(SME.url.introCards, + { + method: "get", + + onSuccess: function(transport) + { + var data = transport.responseText.cleanJSON(); + + // If there is no intro card then just pass + if (data == "" || data == "\n") + { + return loadTables(); + } + else + { + data = data.evalJSON(); + + // By default just show the first card in the + // feed + var myCard = data[0]; + + // If more than one card then pick one at random + // to display (per client requirements). + if (data.length > 1) + { + myCard = data[Math.floor(1 + (data.length - 1) * Math.random())]; + } + } + + var card = new Card( + { + color: SME.colors.grey, + title: '', + addExtraButtons: false, + contID: myCard, + + onFadeComplete: function() + { + loadTables(); + } + }).show(); + } + }); +} + +/* + * Show the toolbox in the upper right side of the screen. + */ +function showToolBox() +{ + new Effect.Appear($$("div#header div.history")[0]); + + // On mouseover of the history link show the dropdown + $$("div.history a.history")[0].observe("mouseover", function() + { + SME.history.getDropDown(); + }); + + // Show the login screen when the login link is clicked + $("loginLink").observe("click", Sketchbook.showLoginScreen); + + // Check the login when they first hit the page, saves people logging + // in again + Sketchbook.checkLogin(); +} + +/* + * Main program function, this starts up the interface and does various + * little fixups of interface elements. + */ +function main() +{ + if (SME.debug) + { + new Bezel({ displayTime: 5 }).show("Full Debug Mode is Enabled"); + } + + // Check the resolution at load and when the screen size changes + window.checkResolution(); + Event.observe(window, "resize", window.checkResolution); + + // Show the intro card or load the tables + if (SME.skipIntro || window.location.hash.length > 1) + { + loadTables(); + } + else + { + showIntro(); + } + + // Per Bryan this should be a single year if 2007 otherwise it should + // be a date range starting on the year that the site was released. + if (new Date().getFullYear() > 2007) + { + $("copyright").innerHTML = $("copyright").innerHTML.replace(/####/, "2007-" + + new Date().getFullYear()); + } + else + { + $("copyright").innerHTML = $("copyright").innerHTML.replace(/####/, "2007"); + } +} + +/* + * Start the program up when the window loads. + */ +Event.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 @@ +/* + * Material Experience - Bezel Class + * + * EYEMG - Interactive Media Group + * Created by Mike Crute (mcrute@eyemg.com) + * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07 + * + * Notification bezel class mimics the notification bezel in BBEdit + * (and to a lesser extent, OS X). + */ + +var Bezel = Class.create(); +Object.extend(Bezel.prototype, +{ + /* + * Creates the bezel HTML elements and adds them to the document body. + */ + initialize: function() + { + this.options = Object.extend( + { + background: "black", // Background color of the bezel + opacity: 0.8, // % Opacity of the bezel + fontSize: "2em", // Font Size + fadeTime: 2, // Fade Time + destroy: true, // Destroy bezel on fade out + displayTime: 1, // How long to display the bezel - 0 is "sticky" + onShow: Prototype.emptyFunction, // After show callback + afterHide: Prototype.emptyFunction // After hide callback + }, arguments[0] || {}); + + this.visible = false; + + var Rounder = new RoundedCorners(this.options.background); + + this.bezel = Element.extend(document.createElement("div")); + this.message = Element.extend(document.createElement("div")); + + this.bezel.appendChild(Rounder.get(Rounder.directions.top)); + this.bezel.appendChild(this.message); + this.bezel.appendChild(Rounder.get(Rounder.directions.bottom)); + + document.body.appendChild(this.bezel); + + this.bezel.setStyle( + { + opacity: this.options.opacity, + zIndex: 9999999, + position: "absolute", + display: "none", + width: "auto", + cursor: "pointer" + }); + + this.message.setStyle( + { + background: this.options.background, + fontSize: this.options.fontSize, + color: "white", + padding: "0px 1em", + textAlign: "center" + }); + + // IE does not properly size the bezel so we must constrain it + if (Prototype.Browser.IE) + { + this.bezel.setStyle({ width: "50%" }); + } + + this.bezel.onclick = function() + { + this.hide(); + }.bind(this); + }, + + /* + * Displays the notification bezel with the requested message. + */ + show: function(message) + { + // Sets the bezel message + this.message.innerHTML = message; + this.visible = true; + + // Center the bezel + var mysize = this.bezel.getDimensions(); + mysize = window.calcCordsToCenter(mysize.height, mysize.width); + + // Set the position of the bezel + this.bezel.setStyle( + { + top: mysize.top + "px", + left: mysize.left + "px" + }); + + this.bezel.show(); + + if (typeof this.options.onShow == "function") + { + this.options.onShow(); + } + + if (this.options.displayTime > 0) + { + new PeriodicalExecuter(function(executer) + { + this.hide(); + executer.stop(); + }.bind(this), this.options.displayTime); + } + + return this; + }, + + /* + * Removes the bezel from the DOM. + */ + destroy: function() + { + try + { + document.body.removeChild(this.bezel); + } + catch (e) + { + // Ignore errors + } + }, + + /* + * Fades out the notification bezel. If this is not a sticky bezel + * (displayTime > 0) then this function will be called automatically + * by the show routine. + */ + hide: function() + { + new Effect.Fade(this.bezel, + { + duration: this.options.fadeTime, + + afterFinish: function() + { + this.visible = false; + + if (this.options.destroy) + { + this.destroy(); + } + + this.options.afterHide(this); + }.bind(this) + }); + } +}); \ 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 @@ +/* + * Material Experience - Card Class + * + * EYEMG - Interactive Media Group + * Created by Mike Crute (mcrute@eyemg.com) + * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07 + */ + +var Card = Class.create(); +Object.extend(Card.prototype, +{ + /* + * Creates the card and appends it to the document. This does + * not set the content. + */ + initialize: function() + { + this.options = Object.extend( + { + title: 'New Card', // Card Title + id: 'mycard', // Card ID + preview: false, // This is a preview card + addExtraButtons: true, // Add the extra buttons to non-system cards + contID: null, // Content ID of the card's contents + color: SME.colors.blue, // Card Color + width: SME.sizes.card.width, // Card Width + height: SME.sizes.card.height, // Card Height + onClose: Prototype.emptyFunction, // Card Close Callback + onFadeComplete: Prototype.emptyFunction, // Card Fade Completion Callback + onFadeOutFinish: Prototype.emptyFunction // Card Fade Out Completion Callback + }, arguments[0] || {}); + + // Keep track of our card chip + this.chip = this.options.chip; + + // Buttons on the card frame + this.buttons = []; + + // Create the document overlay but don't yet show it + this.overlay = new Overlay( + { + onClick: function() + { + this.hide(); + }.bind(this) + }); + + this._createCard(); + + if (this.options.addExtraButtons) + { + this._addExtraButtons(); + } + + // Always add a close button + this.addCommandButton(Strings.closeCard,'images/close.gif', function() + { + this.hide(); + }.bind(this)); + + this.setTitle(this.options.title); + }, + + /* + * Create the card DOM nodes. + */ + _createCard: function() + { + // Get the rounded corner generator and the coordinates of the page center + var Rounder = new RoundedCorners(this.options.color); + var cardPos = window.calcCordsToCenter(this.options.height, this.options.width); + var slicebox = Rounder.get(Rounder.directions.top); + + // Intro card will flash across the screen while card builds but + // before fading up if display is not set to none + this.card = Builder.node('div', + { + id: this.options.id, + style: 'display: none;', + className: 'card' + }); + + // Create the card header with title and button box + slicebox.appendChild(this.title = Builder.node('span', { className: 'cardTitle' })); + slicebox.appendChild(this.buttonbox = Builder.node('div', { className: 'buttonbox' }, + this.buttonLabel = Builder.node('span', { className: 'commandlabel' }) + )); + + // Append the header and the card content area + this.card.appendChild(slicebox); + this.card.appendChild(Builder.node('div', { style: 'background: white; opacity: 100%;' }, + this.cframe = Builder.node('div') + )); + + // Append the bottom of the card frame and add the card to the document + this.card.appendChild(Rounder.get(Rounder.directions.bottom)); + document.body.appendChild(this.card); + + // Card frame styles + Element.extend(this.cframe).setStyle( + { + width: this.options.width - 11 + 'px', + height: this.options.height - 40 + 'px', + borderLeft: '5px solid ' + this.options.color, + borderRight: '5px solid ' + this.options.color, + borderTop: '10px solid ' + this.options.color, + position: 'relative', + overflow: 'hidden' + }); + + // Card styles + Element.extend(this.card).setStyle( + { + height: this.options.height + 'px', + width: this.options.width + 'px', + top: cardPos.top + 'px', + left: cardPos.left + 'px', + zIndex: 9001, + opacity: '100%', + display: 'none', + position: 'absolute' + }); + + // Make the card draggable + this.drag = new Draggable(this.card, + { + handle: 'round', + zindex: 9001, + starteffect: null, + endeffect: null + }); + }, + + /* + * Add a command button to the top right of the card. + */ + addCommandButton: function(title, icon, action) + { + var newButton = Builder.node('img', { className: 'commandbtn', src: icon }); + + // Show the label on mouseover + Event.observe(newButton, 'mouseover', function() + { + this.buttonLabel.innerHTML = title; + }.bindAsEventListener(this)); + + // Hide the label on mouse out + Event.observe(newButton, 'mouseout', function() + { + this.buttonLabel.innerHTML = ''; + }.bindAsEventListener(this)); + + // Take the action specified when the button is clicked + Event.observe(newButton, 'click', function(event) + { + action(event); + }); + + // Add the button to the button box and cache it. + this.buttons[title] = this.buttonbox.appendChild(newButton); + }, + + /* + * Removes a command button from the card. + */ + removeCommandButton: function(button) + { + this.buttonLabel.innerHTML = ''; + this.buttonbox.removeChild(this.buttons[button]); + }, + + /* + * Sets the title of the card. + */ + setTitle: function(title) + { + this.options.title = title; + this.title.innerHTML = title; + }, + + /* + * Disables the drag on the card and removes the card + * from the DOM. + */ + destroy: function() + { + // Kill the drag to prevent probable memory leaks in IE + this.drag.destroy(); + + // Remove the card from the DOM + document.body.removeChild(this.card); + + // Destroy the overlay + this.overlay.destroy(); + }, + + /* + * Display the card. + */ + show: function() + { + // Track the currently active card for the history manager + // FLAWED LOGIC: We can have multiple cards visible at the same + // time. + SME.currentCard = this; + + // Get card positioning data + var cardPos = window.calcCordsToCenter(this.options.height, this.options.width); + var windSize = window.getDimensions(); + + // Position the card + this.card.setStyle( + { + top: cardPos.top + 'px', + left: cardPos.left + 'px' + }); + + this._autoSetLayout(); + + // Show the overlay first so it will fade in with the card + this.overlay.show(); + + // Fade the card in and run its fadeComplete function. + new Effect.Appear(this.card, + { + afterFinish: function() + { + this.options.onFadeComplete(); + }.bind(this) + }); + }, + + /* + * Automatically set the layout based on the content of a JSON feed + * if it is supplied. + */ + _autoSetLayout: function() + { + // Fetch data only if this is not an intro card and a content + // id has been specified. + if (!this.options.contID) + { + return; + } + + // Pull the correct data if this is a preview or a real + // card. + if (this.options.preview) + { + var theUrl = SME.url.cardPreview.evaluate({card: this.options.contID}) + } + else + { + var theUrl = SME.url.cards.evaluate({card: this.options.contID}); + } + + new Ajax.Request(theUrl, + { + method: 'get', + + onSuccess: function(transport) + { + try + { + var data = transport.responseText.cleanJSON().evalJSON(); + this.setLayout(SME.engineMapping[data.template], data); + } + catch (exception) + { + if (SME.debug) + { + console.error(exception); + } + + this.setLayout(Card.Layout.Errors, ''); + } + }.bind(this) + }); + }, + + /* + * Add extra buttons to a card. I moved this to its own private + * function because some cards, like system cards, don't need all + * those extra buttons. + */ + _addExtraButtons: function() + { + this.addCommandButton(Strings.addToSketchbook,'images/plus.gif', function() + { + SME.sketchbook.addChip(this.options.contID); + }.bind(this)); + + this.addCommandButton(Strings.sendToFriend,'images/email.gif', function() + { + if ($('cardFlash')) + { + this.flashP = $('cardFlash').up(); + this.flash = $('cardFlash').remove(); + } + + var card = new Card( + { + color: SME.colors.grey, + title: Strings.sendToFriend, + addExtraButtons: false, + + onFadeComplete: function() + { + if (this.flash) + { + this.flashP.appendChild(this.flash); + this.flash = null; + } + }.bind(this) + }); + + card.setLayout(Card.Layout.Special, + { + url: SME.url.sendToFriend.evaluate( + { + durl: 'card' + encodeURI(this.options.contID), + title: encodeURI(this.options.title) + }) + }); + + card.show(); + }.bind(this)); + + this.addCommandButton(Strings.printCard, 'images/print.gif', function() + { + this.layoutEngine.print(); + }.bind(this)); + + // Card history ON the card was poorly designed and implemented + // (yeah, I know) so I'm just removing it for now till I get + // some time to re-write it. + // + // TODO: Re-write this + this.addCommandButton(Strings.myHistory,'images/history.gif', function() + { + var center = window.calcCordsToCenter(SME.sizes.card.height, SME.sizes.card.width); + SME.history.getDropDown(center.left + 30, center.top + 13); + }); + }, + + /* + * Hides the current card but does not remove it from the DOM. + */ + hide: function() + { + // There is no current card so unset it + // FLAWED LOGIC: We can have multiple cards. + SME.currentCard = null; + + // Fade out and subsequently destroy the card + new Effect.Fade(this.card, + { + afterFinish: function() + { + this.options.onFadeOutFinish(); + this.destroy(); + }.bind(this) + }); + + // FLAWED LOGIC: Multiple cards, see above. + SME.history.clearHash(); + this.overlay.hide(); + this.options.onClose(); + }, + + /* + * Sets the layout of the card using a layout engine. + */ + setLayout: function(layoutE, data) + { + this.layoutEngine = new layoutE(this.cframe, data, this).layout(); + } +}); 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 @@ +/* + * Material Experience - Card Chip Class + * + * EYEMG - Interactive Media Group + * Created by Mike Crute (mcrute@eyemg.com) + * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07 + */ + +var CardChip = Class.create(); +Object.extend(CardChip.prototype, +{ + /* + * Initializes the chip class. + */ + initialize: function() + { + this.options = Object.extend( + { + x: 0, // X coordinate of the chip + y: 0, // Y coordinate of the chip + locked: false, // Require login to view card + category: null, // Chip target category + contID: null, // Chip target content ID + title: null, // Chip target title + image: null, // Chip image + className: 'chip', // Chip CSS Class Name + animate: true // Animate chip onto table + }, arguments[0] || {}); + + this.dragged = false; + + this._createChip(); + + return this; + }, + + /* + * Create the chip DOM nodes and attach events and drags as appropriate. + */ + _createChip: function() + { + this.chip = Builder.node('img', + { + src: this.options.image, + className: this.options.className, + style: 'top: ' + this.options.y + 'px; left: ' + this.options.x + 'px', + title: this.options.title + ((this.options.locked) ? ' (protected)' : ''), + alt: this.options.title + ((this.options.locked) ? ' (protected)' : '') + }); + + // Each DOM node should have a link back to this class so we can + // later look at a DOM node which holds no real data about the + // object and come back here to get the data we need. + this.chip.classLink = this; + + new Draggable(this.chip, + { + starteffect: null, + endeffect: null, + + change: function() + { + // Set the dragged flag (see onClick for more information) + this.dragged = true; + }.bind(this), + + onEnd: function(e) + { + // Ensures that cards stay on top of the stack after they are + // dropped. This is accomplished by determining the maximum + // z-index of the cards and applying max + 1 to the current + // chip. + // + // Note that we are modifying a variable used internally by + // Scriptaculous that is NOT part of the public API. The + // originalZ variable is used by Scriptaculous to record the + // z-index that it should return the draggable to, by + // incrementing this we effectively move the card up in the + // stack. + // + // Note that scriptaculous adds a z-index of 1000 when the drag + // starts and does not remove it till AFTER this function executes + // so we have to take off that extra 1000 to get the real z-index. + e.originalZ = $$('div#' + (SME.currentTable || 'home') + ' img.chip').max(function(x) + { + var z = parseInt(x.style.zIndex) || 0; + return (z >= 1000) ? z - 1000 : z; + }) + 1; + + // Track the X and Y coordinates of the chip + e.element.classLink.options.x = e.element.style.left.split('px')[0]; + e.element.classLink.options.y = e.element.style.top.split('px')[0]; + } + }); + + Event.observe(this.chip, 'click', function() + { + this.onClick(); + }.bindAsEventListener(this)); + + if (this.options.animate) + { + this._generateAnimation(); + } + }, + + /* + * Handle clicks on the chip. + */ + onClick: function() + { + // If we recently dragged this card then do nothing. This + // facilitates single click DND as well as single click + // activation. + if (this.dragged == true) + { + this.dragged = false; + return; + } + + // If this is a locked chip and we aren't logged in then show + // a login screen + if (this.options.locked && !Sketchbook.loggedIn) + { + return Sketchbook.showLoginScreen(); + } + + // Disable the puff in IE because the PNG fix breaks it + if (!Prototype.Browser.IE) + { + new Effect.Puff(this.chip, + { + afterFinish: function() + { + new Effect.Appear(this.chip); + }.bind(this) + }); + } + + SME.history.registerEvent('card', this.options.title, this.options.contID); + + var t = new Card( + { + color: CardTable.tableColor(this.options.category), + contID: this.options.contID, + title: this.options.title, + chip: this + }); + + t.show(); + }, + + /* + * Actually move the chip onto the table. This function expects the + * original and new coordinates to be pre-generated and stored by + * another function. + */ + animate: function() + { + new Effect.Morph(this.chip, + { + style: + { + top: this.newY + 'px', + left: this.newX + 'px' + } + }); + }, + + /* + * Return data about the chip in an object that matches of the format + * of the card table JSON feed. + */ + _serialize: function() + { + return { + cid : this.options.contID, + title : this.options.title, + category : this.options.category, + locked : this.options.locked, + x : this.options.x, + y : this.options.y, + chip : this.options.image + }; + }, + + /* + * Generate random coordinates for and place the chip off the edge of + * the table to be animated in later on by a different function. + */ + _generateAnimation: function() + { + var myRand = Math.floor(1 + (5 - 1) * Math.random()); + var windSize = window.getDimensions(); + + this.newX = this.options.x; + this.newY = this.options.y; + + this.chip.setStyle({ position: 'absolute' }); + + // Determine from which direction the cards will animate + switch (myRand) + { + case 1: // Top + this.chip.setStyle( + { + top: -(SME.sizes.chipMax.height) + 'px', + left: (windSize.width / 2) + 'px' + }); + break; + + case 2: // Right + this.chip.setStyle( + { + top: (windSize.height / 2) + 'px', + left: (SME.sizes.chipMax.width) + windSize.width + 'px' + }); + break; + + case 3: // Bottom + this.chip.setStyle( + { + top: (SME.sizes.chipMax.height) + windSize.height + 'px', + left: (windSize.width / 2) + 'px' + }); + break; + + default: // Left + this.chip.setStyle( + { + top: (windSize.height / 2) + 'px', + left: -(SME.sizes.chipMax.width + 40) + 'px' + }); + break; + } + }, + + /* + * Add a chip DOM node to a table. + */ + addTo: function(table) + { + table.appendChild(this.chip); + }, + + /* + * Remove the chip DOM node from the document. + */ + destroy: function() + { + document.removeChild(this.chip); + }, + + /* + * Show the chip. + */ + show: function() + { + this.chip.show(); + }, + + /* + * Hide the chip. + */ + hide: function() + { + this.chip.hide(); + } +}); \ 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 @@ +/* + * Material Experience - Cookie Handling Class + * + * EYEMG - Interactive Media Group + * Created by Mike Crute (mcrute@eyemg.com) on 10/2/07 + * Updated by Mike Crute (mcrute@eyemg.com) on 10/3/07 + * + * Class to handle cookie CRUD. Code adapted from: + * http://www.quirksmode.org/js/cookies.html + */ + +var Cookie = Object.extend(Class.create(), +{ + /* + * Creates a cookie. + */ + create: function(name, value, days) + { + if (days) + { + var date = new Date().setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); + var expires = "; expires=" + date.toGMTString(); + } + + document.cookie = name + "=" + value + (expires ? expires : '') + "; path=/"; + }, + + /* + * Setting the expiration date of the cookie to -1 effectively deletes + * it. + */ + erase: function(name) + { + createCookie(name,"",-1); + }, + + /* + * Reads a cookie and returns the value. + */ + read: function(name) + { + var nameEQ = name + "="; + var ca = document.cookie.split(';'); + + for ( var i = 0; i < ca.length; i++ ) + { + var c = ca[i]; + + while (c.charAt(0) == ' ') + { + c = c.substring(1,c.length); + } + + if (c.indexOf(nameEQ) == 0) + { + return c.substring(nameEQ.length,c.length); + } + } + + return null; + } +}); + +/* + * Shorthand for Cookie.read + */ +function $C(name) +{ + return Cookie.read(name); +} \ 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 @@ +/* + * Material Experience - Encoder/Decoder Module + * + * EYEMG - Interactive Media Group + * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07 + * + * Encoder/Decoder module for common encoding schemes like URL and + * base64. Code from: http://ostermiller.org/calc/encode.html + */ +var END_OF_INPUT = -1; + +var base64Chars = new Array( + 'A','B','C','D','E','F','G','H', + 'I','J','K','L','M','N','O','P', + 'Q','R','S','T','U','V','W','X', + 'Y','Z','a','b','c','d','e','f', + 'g','h','i','j','k','l','m','n', + 'o','p','q','r','s','t','u','v', + 'w','x','y','z','0','1','2','3', + '4','5','6','7','8','9','+','/' +); + +var reverseBase64Chars = new Array(); +var base64Str; +var base64Count; + +for (var i=0; i < base64Chars.length; i++) +{ + reverseBase64Chars[base64Chars[i]] = i; +} + +// -------------------------------------------------------------------------- \\ + +function urlDecode(str) +{ + return unescape(str.replace(new RegExp('\\+','g'),' ')); +} + +function urlEncode(str) +{ + str = escape(str); + str = str.replace(new RegExp('\\+','g'),'%2B'); + + return str.replace(new RegExp('%20','g'),'+'); +} + +// -------------------------------------------------------------------------- \\ + +function setBase64Str(str) +{ + base64Str = str; + base64Count = 0; +} + +function readBase64() +{ + if (!base64Str) + { + return END_OF_INPUT; + } + + if (base64Count >= base64Str.length) + { + return END_OF_INPUT; + } + + var c = base64Str.charCodeAt(base64Count) & 0xff; + + base64Count++; + + return c; +} + +function encodeBase64(str) +{ + setBase64Str(str); + + var result = ''; + var inBuffer = new Array(3); + var lineCount = 0; + var done = false; + + while (!done && (inBuffer[0] = readBase64()) != END_OF_INPUT) + { + inBuffer[1] = readBase64(); + inBuffer[2] = readBase64(); + result += (base64Chars[ inBuffer[0] >> 2 ]); + + if (inBuffer[1] != END_OF_INPUT) + { + result += (base64Chars [(( inBuffer[0] << 4 ) & 0x30) | (inBuffer[1] >> 4) ]); + + if (inBuffer[2] != END_OF_INPUT) + { + result += (base64Chars [((inBuffer[1] << 2) & 0x3c) | (inBuffer[2] >> 6) ]); + result += (base64Chars [inBuffer[2] & 0x3F]); + } + else + { + result += (base64Chars [((inBuffer[1] << 2) & 0x3c)]); + result += ('='); + done = true; + } + } + else + { + result += (base64Chars [(( inBuffer[0] << 4 ) & 0x30)]); + result += ('='); + result += ('='); + done = true; + } + + lineCount += 4; + + if (lineCount >= 76) + { + result += ('\n'); + lineCount = 0; + } + } + + return result; +} + +function readReverseBase64() +{ + if (!base64Str) + { + return END_OF_INPUT; + } + + while (true) + { + if (base64Count >= base64Str.length) + { + return END_OF_INPUT; + } + + var nextCharacter = base64Str.charAt(base64Count); + base64Count++; + + if (reverseBase64Chars[nextCharacter]) + { + return reverseBase64Chars[nextCharacter]; + } + + if (nextCharacter == 'A') + { + return 0; + } + } + + return END_OF_INPUT; +} + +function ntos(n) +{ + n = n.toString(16); + + if (n.length == 1) + { + n = "0" + n; + } + + n = "%" + n; + + return unescape(n); +} + +function decodeBase64(str) +{ + setBase64Str(str); + + var result = ""; + var inBuffer = new Array(4); + var done = false; + + while (!done && (inBuffer[0] = readReverseBase64()) != END_OF_INPUT + && (inBuffer[1] = readReverseBase64()) != END_OF_INPUT) + { + inBuffer[2] = readReverseBase64(); + inBuffer[3] = readReverseBase64(); + result += ntos((((inBuffer[0] << 2) & 0xff)| inBuffer[1] >> 4)); + + if (inBuffer[2] != END_OF_INPUT) + { + result += ntos((((inBuffer[1] << 4) & 0xff)| inBuffer[2] >> 2)); + + if (inBuffer[3] != END_OF_INPUT) + { + result += ntos((((inBuffer[2] << 6) & 0xff) | inBuffer[3])); + } + else + { + done = true; + } + } + else + { + done = true; + } + } + + return result; +} \ 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 @@ +/* + * Material Experience - History Manager Class + * + * EYEMG - Interactive Media Group + * Created by Mike Crute (mcrute@eyemg.com) + * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07 + */ + +var HistoryManager = Class.create(); +Object.extend(HistoryManager.prototype, +{ + /* + * Sets up the history storage array and initializes the last + * event to nothing. + */ + initialize: function() + { + this.historyStore = $H(); + this.lastEvent = null; + }, + + /* + * Clears the window hash. Generally called when a card closes. + */ + clearHash: function() + { + window.location.hash = '#'; + }, + + /* + * Register an event with the history manager. Also updates the + * window hash to reflect the event. This is the only way to + * register an event with the history manager. + */ + registerEvent: function(type, title, url) + { + window.location.hash = '#' + type + url; + + // If you don't set this explicitly IE will show the hash as + // the document title. + document.title = Strings.appTitle; + + this.lastEvent = '#' + type + url; + this.historyStore[type + url] = title; + + return this; + }, + + /* + * Stops polling the URL for new events. + */ + stopPolling: function() + { + this.poller.stop(); + + return this; + }, + + /* + * Starts polling the URL for new events and will execute the event + * handler if an event occurs. + */ + pollEvents: function() + { + this.poller = new PeriodicalExecuter(function() + { + // Make sure there is a valid hash and it is NOT the card + // that we just loaded (prevent double-carding) + if ( + this.lastEvent != window.location.hash && + window.location.hash != '#' && + window.location.hash != '' + ) { + this.lastEvent = window.location.hash; + this._handleEvents(); + } + }.bind(this), 0.1); + + return this; + }, + + /* + * Handles an event, this is called internally by the pollEvents function + * when a new event occurs and should never be called directly. + */ + _handleEvents: function() + { + // Breaks down the event type and parameters from the hash + var evtTypes = /^(card|table|preview)/; + var hash = window.location.hash; + + // When IE does the split it only returns one array element, + // the parameter, so we have to do a little magic to match + // the action too. All other browsers seem to work correctly. + var fullEvt = hash.substr(1, hash.length).split(evtTypes).without(''); + var eventType = (fullEvt.length == 1) ? hash.substr(1, hash.length).match(evtTypes)[0] : fullEvt[0]; + var eventParam = (fullEvt.length == 1) ? fullEvt : fullEvt[1]; + + // Hide the current card if there is one + if (SME.currentCard && SME.currentCard.hide) + { + SME.currentCard.hide(); + } + + switch (eventType) + { + case 'table': + CardTable.showTable(eventParam); + return; + break; + + case 'card': + theURL = SME.url.cards.evaluate({card: eventParam}); + break; + + case 'preview': + theURL = SME.url.cardPreview.evaluate({card: eventParam}); + this.stopPolling(); + break; + + default: + return; + break; + } + + new Ajax.Request(theURL, + { + method: 'get', + + onSuccess: function(transport) + { + var data = transport.responseText.cleanJSON().evalJSON(); + + var card = new Card( + { + color: CardTable.tableColor(data.type), + contID: eventParam, + title: data.title, + preview: (eventType == 'preview') ? true : false + }); + + this.registerEvent(eventType, data.title, eventParam); + card.show(); + }.bind(this) + }); + }, + + + /* + * Creates and populates the history dropdown based on the + * events stored in the history storage array. Also handles + * showing, hiding and positioning that menu. + */ + getDropDown: function(x, y) + { + var dropdown = $('history'); + + // If no x and y then position the menu for the my history + // dropdown from the tools menu. + x = x ? x : 20; + y = y ? y : 26; + + // Remove and re-append the menu from the DOM, this prevents + // IE z-index bugs. + document.body.appendChild(dropdown.remove()); + dropdown.innerHTML = ''; + + // Populate the menu + this.historyStore.each(function(item) + { + dropdown.appendChild(Builder.node('li', {}, Builder.node('a', { href: '#' + item.key }, item.value))); + }); + + // Initially show the menu + dropdown.addClassName('hovered'); + + // Must explicitly set left to nothing otherwise positioning + // is wrong. + dropdown.setStyle( + { + right: x + 'px', + top: y + 'px', + left: '', + zIndex: 9999 + }); + + // Hide the menu when the user... + $H({ + '#history a' : 'click', // Clicks on a link + 'div.table' : 'mouseover', // Mouses-off onto a table + 'p' : 'mouseover', // Mouses-off onto a paragraph + 'iframe' : 'mouseover', // Mouses-off onto an iframe + 'img' : 'mouseover' // Mouses-off onto an image + }).each(function(aitem) + { + $$(aitem.key).each(function(item) + { + item.observe(aitem.value, function() + { + if ($('history').hasClassName('hovered')) + { + $('history').removeClassName('hovered'); + } + }); + }); + }); + } +}); \ 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 @@ +/* + * Material Experience - Card Layout Engines Class + * + * EYEMG - Interactive Media Group + * Created by Mike Crute (mcrute@eyemg.com) + * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07 + */ + +// Card.Layout.Errors = Class.create(); +// Object.extend(Object.extend(Card.Layout.Errors.prototype, Card.Layout.prototype), + +Card.Layout = Class.create(); +Object.extend(Card.Layout.prototype, +{ + /* + * Initialize the layout class + */ + initialize: function(cframe, data, card) + { + this.cframe = cframe; + this.data = data; + this.card = card; + this.color = this.card.options.color; + this.hasResources = false; + }, + + /* + * Main function to layout the card. + */ + layout: function() + { + throw "Not implemented here."; + }, + + /* + * Throw the card into a popup window for printing. + */ + print: function() + { + throw "Not implemented here."; + }, + + /* + * Debug the card layout. Generally this should be used to spot out + * data issues. But it could also be used for other debugging purposes. + */ + _debug: function() + { + throw "Not implemented here."; + } +}); \ 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 @@ +/* + * Material Experience - Card Layout Engines Class + * + * EYEMG - Interactive Media Group + * Created by Mike Crute (mcrute@eyemg.com) + * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07 + */ + +Card.Layout.Custom = Class.create(); +Object.extend(Object.extend(Card.Layout.Custom.prototype, Card.Layout.prototype), +{ + /* + * Initialize the layout class + */ + initialize: function(cframe, data, card) + { + this.cframe = cframe; + this.data = data; + this.card = card; + this.color = this.card.options.color; + this.hasResources = false; + this.inited = false; + }, + + /* + * Create custom scroll bars for an HTML element. + */ + scrollify: function(div, isiframe) + { + var div2 = null; + + // Inherit the color from a link + var color = $$('ul#navigation li a')[0].getStyle('background-color'); + + if (isiframe) + { + div2 = div.contentWindow.document.documentElement; + } + else + { + div2 = div; + } + + // Create the scroll container and draggable widget + this.cframe.appendChild( + this.scrollCont = Builder.node('div', + { + style: 'border-left: 1px solid ' + color + ';' + + 'width: 1px; position: absolute; ' + + 'height:' + div.offsetHeight + 'px; ' + + 'top: 10px;' + 'left: ' + (div.offsetLeft + div.offsetWidth + 12) + 'px;' + }, + + this.scrollBar = Builder.node('img', + { + src: 'http://materialexperience.santoprene.com/images/pill.gif', + style: 'display: block; margin-left: -3px; cursor: move; ' + + 'background: ' + color + '; padding: 0px;' + }) + )); + + // Create the scroller + this.scroller = new Control.Slider(this.scrollBar, this.scrollCont, + { + axis: 'vertical', + range: $R(0, div2.scrollHeight), + + onSlide: function(value) + { + div2.scrollTop = Math.floor(value); + }.bind(this) + }); + + new PeriodicalExecuter(function() + { + // Hide the scroller if there isn't a need for it + if (div2.scrollHeight > 480) + { + this.scrollCont.show(); + } + else + { + this.scrollCont.hide(); + return; + } + + // Update the scroller range when the contents change + this.scroller.range = $R(0, div2.scrollHeight); + + // Move the scroller when the scrolled element changes + // (e.g. linking down into the page). + if (this.scroller.value != div2.scrollTop) + { + this.scroller.setValue(div2.scrollTop); + } + }.bind(this), 1); + }, + + initIframe: function(url) + { + $('wideCol').innerHTML = ''; + }, + + /* + * Main function to layout the card. + */ + layout: function() + { + var htmlstr = '
'; + this.cframe.innerHTML = htmlstr; + + if (this.data.intro) + { + $('wideCol').innerHTML = this.data.intro; + } + +// this.scrollify($('wide_content'), true); + this.scrollify($('narrowCol')); + + // Hide everything first + $$('ul#navigation li ul').each(function(item) + { + item.setStyle({ display: 'none' }); + }); + + var curr = null; + // Then attach the onclick events + $$('ul#navigation li').each(function(item) + { + Event.observe(item, 'click', function(event) + { + // Hide the currently opened item if it is clicked again + // per BS. + if (curr && item == curr) + { + curr.down('ul').hide(); + item.setStyle({ borderLeft: '0px' }); + curr = null; + return; + } + + curr = item; + + // Hide everything + $$('ul#navigation li ul').each(function(item) + { + item.setStyle({ display: 'none' }); + }); + + // Clear out the borders + $$('ul#navigation li').each(function(item) + { + item.setStyle({ borderLeft: '' }); + }); + + // Figure out what color we SHOULD be, this will change + // depending on the card color we are on so just rely + // on the designer to communicate through the CSS, + // probably not wise relying on a designer for code + // but alas... + var color = $$('ul#navigation li a')[0].getStyle('background-color'); + + this.initIframe(item.href); + + // Catch and execute or just leave it lie + try + { + if (item.down('ul').style.display != 'none') + { + item.down('ul').hide(); + Event.stop(event); + return; + } + + item.down('ul').show(); + item.setStyle({ borderLeft: '3px solid ' + color }); + //Event.stop(event); + } + catch (e) + { + // Just let the link do its thing + } + }.bind(this)); + }.bind(this)); + } +}); \ 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 @@ +/* + * Material Experience - Error Card Layout Engine + * + * EYEMG - Interactive Media Group + * Created by Mike Crute (mcrute@eyemg.com) + * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07 + */ + +Card.Layout.Errors = Class.create(); +Object.extend(Object.extend(Card.Layout.Errors.prototype, Card.Layout.prototype), +{ + /* + * Initialize the layout class + */ + initialize: function(cframe, data, card) + { + this.cframe = cframe; + this.data = data; + this.card = card; + this.color = this.card.options.color; + this.hasResources = false; + }, + + /* + * Main function to layout the card. + */ + layout: function() + { + this.cframe.innerHTML = ''; + this.cframe.appendChild(Builder.node('h1', Strings.cardErrorTitle)); + this.cframe.appendChild(Builder.node('p', Strings.cardErrorText)); + } +}); \ 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 @@ +/* + * Material Experience - Primary Card Layout Class + * + * EYEMG - Interactive Media Group + * Created by Mike Crute (mcrute@eyemg.com) + * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07 + * + * WARNING: Here be dragons, OK so not as many as before but good luck you poor + * sap. + */ + +Card.Layout.Primary = Class.create(); +Object.extend(Object.extend(Card.Layout.Primary.prototype, Card.Layout.prototype), +{ + /* + * Initialize the layout class + */ + initialize: function(cframe, data, card) + { + this.cframe = cframe; + this.data = data; + this.card = card; + this.color = this.card.options.color; + this.hasResources = false; + + var direction = data.template.replace(/narrow-/,''); + this.direction = direction ? direction : 'left'; + this.oppositeDirection = (direction == 'left') ? 'right' : 'left'; + }, + + /* + * Set the column content. This can be flash, an image or an iframe. + */ + setColumn: function(url, direction, contTitle) + { + var content = Builder.node('div', { style: 'float: ' + this.oppositeDirection }); + var myCol = direction + 'Col'; + + if (/swf$/.test(url)) // Flash + { + content.innerHTML = new SWFObject(url, 'cardFlash', 580, 479, 9, '#FFFFFF').getSWFHTML(); + } + else if (/(png|gif|jpg|jpeg)$/.test(url)) // Image + { + content = Builder.node('img', + { + src: url, + alt: contTitle, + title: contTitle, + style: 'float: ' + this.oppositeDirection + }); + } + else // iFrame + { + // Can't use DOM methods because IE is allergic + content.innerHTML = ''; + } + + // We need to replace the content of the column if it exists to + // facilitate changing content later on (i.e. thumbnails) + if (typeof this[myCol] != 'undefined') + { + this.cframe.replaceChild(content, this[myCol]); + this[myCol] = content; + } + else + { + this[myCol] = this.cframe.appendChild(content); + } + + }, + + /* + * Build the resource (more information) list. + */ + getResourceList: function() + { + // Only run if we need to + if (!this.data.resources) + { + return; + } + + var numResources = 0; + var iter = 0; + var limit = 5; + this.hasResources = true; + + this.resourceContainer = this.contentArea.appendChild( + Builder.node('div', + [ + Builder.node('h2', { style: 'color: ' + this.card.options.color }, Strings.moreInfo), + this.resources = Builder.node('ul', { className: 'resourceList' }) + ] + )); + + this.data.resources.each(function(item) + { + // Limit the number of allowable resources + if (typeof item == 'undefined' || ++iter > limit) + { + return; + } + + numResources++; + + this.resources.appendChild(Builder.node('li', + Builder.node('a', + { + href: item.link + '?entrypoint=DESIGNER', + target: '_new', + style: 'color: ' + this.color + }, item.title) + )); + }.bind(this)); + }, + + /* + * Set the contents of the narrow column. + */ + setHTML: function(content) + { + this.htmlDiv.innerHTML = content; + }, + + /* + * Setup the thumbnails. + */ + getThumbnails: function() + { + // Only run if we need to + if (this.data.media.size() <= 1) + { + return; + } + + var iter = 0; + var limit = 5; // Includes main media file + var lData = this.data.media; + + this.thumbStrip = this.contentArea.appendChild(Builder.node('div', { className: 'thumbStrip' })); + + // Add the main media file to the data array + lData.push( + { + thumb: this.data.contentWide[0].thumb, + url: this.data.contentWide[0].url + }); + + this.data.media.each(function(item) + { + // Limit the number of thumbnails + if (typeof item == 'undefined' || ++iter > limit) + { + return; + } + + var mediaPiece = this.thumbStrip.appendChild( + Builder.node('img', + { + src: item.thumb, + className: 'mediaThumb' + }) + ); + + Event.observe(mediaPiece, 'click', function() + { + this.setColumn(item.url, this.direction, ''); + }.bindAsEventListener(this)); + }.bind(this)); + }, + + /* + * Clean up the data. + */ + _cleanData: function() + { + var lData = this.data.contentNarrow[0].htmlContent; + this.data.contentNarrow[0].htmlContent = lData.replace(/ 1.6 I'm just going to add this into here. + */ + _doIntro: function() + { + this.contentArea.appendChild(Builder.node('div', { className: 'skipbox' }, + this.skipLink = Builder.node('a', { href: '#tablehome', className: 'skiplink' }, + [ + Strings.skipButton, + Builder.node('img', { src: 'images/arrow_right_grey.gif', className: 'skipimg' }) + ]) + )); + }, + + /* + * Throw the card into a popup window for printing. + */ + print: function() + { + var wind = window.open('', '', 'width=' + SME.sizes.innerCard.width + ',height=' + (SME.sizes.innerCard.height + 60)); + + with (wind) + { + with (document) + { + write(''); + write(''); + write(this.cframe.innerHTML); + write(''); + close(); + } + + print(); + close(); + } + + return true; + }, + + /* + * Debug the card layout. Generally this should be used to spot out + * data issues. But it could also be used for other debugging purposes. + */ + _debug: function() + { + + } +}); \ 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 @@ +/* + * Material Experience - Special Case Layout Engine + * + * EYEMG - Interactive Media Group + * Created by Mike Crute (mcrute@eyemg.com) + * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07 + */ + +Card.Layout.Special = Class.create(); +Object.extend(Object.extend(Card.Layout.Special.prototype, Card.Layout.prototype), +{ + /* + * Initialize the layout class + */ + initialize: function(cframe, data, card) + { + this.cframe = cframe; + this.data = data; + this.card = card; + this.color = this.card.options.color; + this.hasResources = false; + + // Check if the url is provided otherwise pull it out of the + // wide content field. + if (!this.data.url) + { + this.data.url = this.data.contentWide[0].url; + } + }, + + /* + * Main function to layout the card. + */ + layout: function() + { + this.cframe.innerHTML = ''; + } +}); \ 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 @@ +/* + * Material Experience - Document Overlay Class + * + * EYEMG - Interactive Media Group + * Created by Mike Crute (mcrute@eyemg.com) + * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07 + */ + +var Overlay = Class.create(); +Object.extend(Overlay.prototype, +{ + /* + * Initializes the overlay class + */ + initialize: function() + { + this.options = Object.extend( + { + zIndex: 9000, // z-index of the overlay + background: 'white', // Overlay color + opacity: 0.5, // Overlay opacity + fadeInSpeed: 0.2, // Fade in speed + fadeOutSpeed: 1, // Fade out speed + onClick: this.hide, // Callback for overlay click + onShow: Prototype.emptyFunction, // Callback when the overlay is shown + onHide: Prototype.emptyFunction // Callback when the overlay is hidden + }, $H(arguments[0]) || {}); + + this._createOverlay(); + }, + + /* + * Creates the actual DOM elements of the overlay. + */ + _createOverlay: function() + { + this.overlay = Element.extend(document.createElement('div')); + document.body.appendChild(this.overlay); + + Event.observe(this.overlay, 'click', this.options.onClick.bindAsEventListener(this)); + + this.overlay.setStyle( + { + backgroundColor: this.options.background, + zIndex: this.options.zIndex, + height: '100%', + width: '100%', + position: 'absolute', + display: 'none', + top: 0, + left: 0 + }); + }, + + /* + * Displays the overlay. + */ + show: function() + { + this._fixBody(); + + this.options.onShow(this); + + new Effect.Appear(this.overlay, + { + to: this.options.opacity, + duration: this.options.fadeInSpeed, + limit: 1 + }); + }, + + /* + * Hides the overlay. + */ + hide: function() + { + this.options.onHide(this); + + new Effect.Fade(this.overlay, + { + duration: this.options.fadeOutSpeed, + limit: 1, + + afterFinish: function() + { + this._fixBody(true); + }.bind(this) + }); + }, + + /* + * Removes the overlay from the DOM + */ + destroy: function() + { + document.body.removeChild(this.overlay); + }, + + /* + * Fix for IE 6, sets the body height and overflow so people can not + * scroll beyond the overlay. Disable overflow on the body and html + * so you can see the whole overlay. + */ + _fixBody: function(reset) + { + var myHeight = reset ? '' : '100%'; + var myOverflow = reset ? '' : 'hidden'; + + $A([document.body,document.getElementsByTagName('html')[0]]).each(function(t) + { + Element.extend(t).setStyle( + { + height: myHeight, + overflow: myOverflow + }); + }); + } +}); \ 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 @@ +/* + * Material Experience - Card Chip Class + * + * EYEMG - Interactive Media Group + * Created by Mike Crute (mcrute@eyemg.com) + * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07 + * + * An HTML based approach to drawing rounded corners using element + * "slices" to create the corners instead of images. This depends + * on a section in the stylesheet to work correctly. + * + * Code adapted from: http://www.html.it/articoli/nifty/index.html + */ + +var RoundedCorners = Class.create(); +Object.extend(RoundedCorners.prototype, +{ + // Direction table for rounded corners. + directions: + { + top : 'top', + bottom : 'bottom' + }, + + /* + * Initialize the object + */ + initialize: function(color) + { + this.color = color; + }, + + /* + * Creates a rounded corner container. + */ + get: function(direction) + { + var slicebox = document.createElement('b'); + slicebox.className = 'round'; + + if (direction == this.directions.top) + { + var myEnum = $A($R(1,10)); + } + else + { + var myEnum = $A($R(1,10)).reverse(); + } + + myEnum.each(function(item) + { + var corner = document.createElement('b'); + corner.className = 'rcSlice_' + item; + corner.style.backgroundColor = this.color; + slicebox.appendChild(corner); + }.bind(this)); + + return slicebox; + } +}); \ 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 @@ +/* + * Material Experience - Sketchbook Class + * + * EYEMG - Interactive Media Group + * Created by Mike Crute (mcrute@eyemg.com) + * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07 + */ + +var Sketchbook = Class.create(); + +Object.extend(Sketchbook.prototype, +{ + /* + * Initializes a new sketchbook and table. + */ + initialize: function() + { + // Sketchbook chip data store + this.dataStore = []; + + this.table = new CardTable( + { + color: SME.colors.grey, + name: Strings.sketchbook, + id: 'sketchbook', + + onRender: function(table) + { + table.table.appendChild(Builder.node('img', + { + id: 'sbTrash', + src: 'images/trash_can.jpg' + })); + + Droppables.add($('sbTrash'), + { + accept: 'chip', + + onDrop: function(chip) + { + this.removeChip(chip); + }.bind(this) + }); + }.bind(this) + }); + + this.hide(); + + if (Sketchbook.loggedIn) + { + this.loadData(); + } + + // Be sure to save everything when the window closes + Event.observe(window, 'unload', this.saveData.bindAsEventListener(this)); + }, + + /* + * Loads sketchbook data from the server and calls a function to merge + * that data into the currently loaded sketchbook. + */ + loadData: function() + { + new Ajax.Request(SME.url.sketchBookIN, + { + method: 'get', + + onSuccess: function(transport) + { + this.dataStore = transport.responseText.cleanJSON().evalJSON(); + this._mergeSketchbooks(); + }.bind(this) + }); + }, + + /* + * Merges the currently active (presumably guest) sketchbook with the + * data loaded from the server. Prevents the user from losing data when + * they login after adding chips to a guest sketchbook. + */ + _mergeSketchbooks: function() + { + this.dataStore.each(function(chip) + { + if (!this.table.hasChip(chip.cid)) + { + this.table.addChip(chip, { animate: false }); + } + }.bind(this)); + }, + + /* + * Add a chip to the sketchbook. This function does duplicate detection + * using methods provided by the CardTable class to avoid adding a chip + * more than once. The function also updates the sketchbook's data + * store. + */ + addChip: function(contID) + { + if (this.table.hasChip(contID)) + { + new Bezel().show(Strings.alreadyAddedSB); + return; + } + + new Ajax.Request(SME.url.chipResolver.evaluate({card: contID}), + { + method: 'get', + + onSuccess: function(transport) + { + var data = transport.responseText.cleanJSON().evalJSON(); + var windSize = window.getDimensions(); + + this.table.addChip(data, + { + x: Math.floor(1 + (windSize.width - 1) * Math.random()), + y: Math.floor(1 + ((windSize.height - 120) - 1) * Math.random()), + animate: false + }); + + // Per BS: Would like different messages if logged in or not + if (Sketchbook.loggedIn) + { + new Bezel().show(Strings.addedSketchbook); + } + else + { + new Bezel({ fadeTime: 5 }).show(Strings.addedGuestSB); + } + + this.saveData(); + }.bind(this) + }); + }, + + /* + * Remove a chip from the table by DOM node. + */ + removeChip: function(chip) + { + this.table.removeChip(chip.classLink.options.contID); + this.saveData(); + }, + + /* + * Serialize the datastore and post it back to the server for safe + * keeping. + */ + saveData: function() + { + new Ajax.Request(SME.url.sketchBook, + { + method: 'post', + asynchronous: false, + parameters: { 'sketchbook_data': this.table.getChipData().toJSON() } + }); + }, + + /* + * Show the sketchbook table or if not logged in show a login screen. + */ + show: function() + { + if (!Sketchbook.loggedIn) + { + Sketchbook.showLoginScreen(); + } + else + { + this.table.show(); + } + }, + + /* + * Hide the sketchbook table. + */ + hide: function() + { + this.table.hide(); + } +}); + +Object.extend(Sketchbook, +{ + // Flag to determine if the user is logged in or not. + loggedIn: false, + + // Username of the logged in user. + username: null, + + // Show the login screen in a special case card. + showLoginScreen: function() + { + var t = new Card( + { + color: SME.colors.grey, + title: Strings.pleaseLogin, + addExtraButtons: false, + + onClose: function() + { + Sketchbook.checkLogin(); + } + }); + + t.setLayout(Card.Layout.Special, { url: SME.url.loginScreen }); + t.show(); + }, + + /* + * Once the user has successfully logged in do some actions to setup + * the user interface. + */ + doLoggedIn: function() + { + // Assume that if your calling this function the login succeeded + Sketchbook.loggedIn = true; + + $$('div.history')[0].innerHTML = 'Hello ' + Sketchbook.username + '! | ' + + '' + Strings.logout + ' | ' + + '' + Strings.manageAccount + ' | ' + + '' + Strings.myHistory + ''; + + // On mouseover of the history link show the dropdown. + // We need to re-attach it here because we're changing the + // links (above) and that causes the DOM to lose the original + // event. + $$('div.history a.history')[0].observe('mouseover', function() + { + SME.history.getDropDown(); + }); + + $$('a.manage')[0].observe('click', function(event) + { + var t = new Card( + { + color: SME.colors.grey, + addExtraButtons: false, + title: Strings.manageAccount + }); + + t.setLayout(Card.Layout.Special, { url: SME.url.manageAccount }); + t.show(); + + Event.stop(event); + }); + }, + + /* + * Check that the user actually logged in. + */ + checkLogin: function() + { + // If the site cookie doesn't exist no point even going on + if (!$C('Site')) + { + return; + } + + /* + * Site cookie is in a weird format, basically sub-cookies are + * separated by & signs. The username and password of the + * currently logged in user is store as username:password in + * the sub-cookie called cookie. Oh yeah and the + * username/password pair is base64 encoded. + */ + var username = decodeBase64($C('Site').split('Cookie&')[1]).split(':')[0]; + Sketchbook.username = username; + + /* + * Per Andy: the username and password won't be in the cookie + * (even though the cookie itself is set) if the login failed. + * So lets assume that if we find a username in the appropriate + * place in the cookie we can go ahead and log the user in. + */ + if (username) + { + Sketchbook.loggedIn = true; + Sketchbook.doLoggedIn(); + return true; + } + else + { + return false; + } + } +}); \ 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 @@ +/* + * Material Experience - Santoprene Material Experience Namespace + * + * EYEMG - Interactive Media Group + * Created by Mike Crute (mcrute@eyemg.com) + * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07 + * + * The Santoprene Materials Experience Namespace holds all non-generic data + * and methods governing the working of this application. + * + * There may be a lot of things set here that are used at runtime but I + * explicitly define them in the SME namespace so other developers can see + * exactly what is contained here. There are also some dynamically defined + * variables based on the current querystring. + */ + +var SME = +{ + /* + * Release version of the site. This has nothing to do with the SVN + * version of the site. This is not used right now but should be + * incremented with each production release of the site. + */ + releaseVersion: '1.2.2', + + /* + * The color table exists to provide aliases to the colors used in the + * interface. This will make it much easier to change at a later date + * should that ever be required. The color table should be used only + * for default values. + */ + colors: + { + blue : 'rgb(100, 129, 145)', + brown : 'rgb(100, 065, 040)', + green : 'rgb(100, 150, 040)', + grey : 'rgb(100, 100, 100)', + orange : 'rgb(225, 125, 010)', + red : 'rgb(150, 035, 020)' + }, + + /* + * The sizes table governs the size of the user interface elements. + * This exists for much the same reason as the color table, to provide + * an easy way to change sizes later. Note that the rest of the styles + * in the interface should be set relatively so changing these + * parameters should not destroy the interface. + */ + sizes: + { + card : { height: 520, width: 925 }, // Card Size + innerCard : { height: 481, width: 914 }, // Inside Size of Card + chipMax : { height: 180, width: 143 }, // Maximum Chip Size + windMin : { height: 589, width: 1020 } // Minimum Window Size + }, + + // URLs Used by the Application + url: + { + tableList : 'data/card_tables.js', + perisitChips : 'data/persist_chip.js', + loginScreen : 'http://' + window.location.hostname + '/cgi-bin/login.pl', + sketchBook : 'http://' + window.location.hostname + '/cgi-bin/sketchbook.pl', + sketchBookIN : 'http://' + window.location.hostname + '/cgi-bin/sketchbook.pl?interactive=false', + manageAccount: 'http://www.santoprene.com/cgi-bin/protected/register/account.pl?template=designer_', + introCards : 'http://' + window.location.hostname + '/cgi-bin/sop_proxy.pl?site=http://www.santoprene.com/cms/designer_json/224fa06db73ece8a/index.html', + cards : new Template('http://' + window.location.hostname + '/cgi-bin/sop_proxy.pl?site=http://www.santoprene.com/cms/designer_card/#{card}/index.html'), + sendToFriend : new Template('http://www.santoprene.com/cgi-bin/send_page/send.pl?tmpl=designer&title=#{title}&durl=#{durl}'), + cardTables : new Template('http://' + window.location.hostname + '/cgi-bin/card_table.pl?table=#{table}&h=#{h}&w=#{w}'), + chipResolver : new Template('http://' + window.location.hostname + '/cgi-bin/sketchbook_resolver.pl?card=#{card}'), + 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'), + + // Debugging and Development URLS (removed by build system) +/*;;;*/ chipResolver : new Template('http://' + window.location.hostname + '/cgi-bin/sop_proxy.pl?site=http://materialexperience.santoprene.com/cgi-bin/sketchbook_resolver.pl?card=#{card}'), +/*;;;*/ 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}'), + // END Debugging and Development URLS + + // Dummy item terminates the list since the items above are + // removed by the build system and the trailing comma would + // break in IE. Don't Remove this! + dontRemove: null + }, + + /* + * Compose layout name to layout engine mapping. Allows us to add more + * layout engines later a lot simpler than the previous method allowed. + */ + engineMapping : + { + 'narrow-left' : Card.Layout.Primary, + 'narrow-right' : Card.Layout.Primary, + 'special' : Card.Layout.Special, + 'intro' : Card.Layout.Primary, + 'custom-menu' : Card.Layout.Custom + }, + + /* + * When the page is called with a query string of debug then you will + * be able to open a firebug console and view debugging messages. Also + * checks to make sure console.log() is actually defined to avoid + * throwing errors on browsers w/out firebug. + */ + debug: /debug=.*(true)/.test(window.location.search) && !!(console.log), + + /* + * Skip the intro card because it wastes time during development. + */ + skipIntro: /.*intro=false.*/.test(window.location.search), + + /* + * + * RUNTIME VARIABLES SECTION + * + * These are variables set at runtime but declared explicitly here + * so other developers can have a clear picture of what is in this + * namespace. + * + * If you set a variable in this namespace at runtime please declare + * it explicitly here. + * + */ + + // Array of all loaded tables. + tables: [], + + // AJAX information bezel. + AJAXBezel: null, + + // Currently active card table. + currentTable: null, + + // Sketchbook. + sketchbook: null, + + // History manager. + history: null, + + // Card currently being displayed. + currentCard: null +}; 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 @@ +/* + * Material Experience - Card Table Class + * + * EYEMG - Interactive Media Group + * Created by Mike Crute (mcrute@eyemg.com) + * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07 + */ + +var CardTable = Class.create(); + +Object.extend(CardTable.prototype, +{ + /* + * Create the table object. + */ + initialize: function() + { + this.options = Object.extend( + { + name: 'New Table', // Table Name + color: SME.colors.blue, // Table Color + id: null, // Table ID + decorate: true, // Draw table decorator + onRender: Prototype.emptyFunction // Callback function when the table renders + }, arguments[0] || {}); + + // Cache of the chips on the table + this.chips = []; + + // Cache of table JSON data + this.data = null; + + this._createTable(); + + // Initially resizes the window to the correct height, and keep + // resizing it as the window changes + this.resize(); + Event.observe(window, 'resize', function() + { + this.resize(); + }.bindAsEventListener(this)); + + if (this.options.decorate) + { + this._createAppDecorator(); + } + }, + + /* + * Create the table DOM object. + */ + _createTable: function() + { + this.table = document.createElement('div'); + + if (this.options.id) + { + this.table.id = this.options.id; + } + + this.table.className = 'table'; + document.body.appendChild(this.table); + + this.options.onRender(this); + }, + + /* + * Load the chip data from the server and cache it in the object. + */ + loadChipData: function(feedURL, urlParams, showAfterLoad) + { + new Ajax.Request(feedURL.evaluate(urlParams), + { + method: 'get', + + onSuccess: function(transport) + { + this.data = transport.responseText.evalJSON(); + + if (showAfterLoad || this.options.id == SME.currentTable) + this.showChips(); + }.bind(this) + }); + }, + + /* + * Creates the decorator icon show in the footer of the user interface + * which just links to a table hash and relies on the history manager + * to load the appropriate table. + */ + _createAppDecorator: function() + { + var link = Builder.node('a', + { + href: '#table' + this.options.id, + style: 'color: ' + this.options.color, + alt: this.options.name + ' Table', + title: this.options.name + ' Table' + }, + [ + Builder.node('img', + { + className: 'appDecorator', + src: 'images/pill.gif', + style: 'background: ' + this.options.color + }), + + this.options.name.toUpperCase() + ]); + + $('footer').appendChild(link); + }, + + /* + * Show chips on the table once data is loaded. This function creates + * and caches chip objects on the table. + */ + showChips: function() + { + // Only load the chips once + if (this.chipsDone || !this.data) + { + return; + } + else + { + this.chipsDone = true; + } + + this.bez = new Bezel( + { + onShow: function() + { + this.data.each(function(chip) + { + this.addChip(chip); + }.bind(this)); + + this.chips.each(function(chip) + { + chip.animate(); + }); + }.bind(this) + }).show(Strings.loadingNoAnim); + }, + + /* + * Remove a chip from the table. + */ + removeChip: function(chipContID) + { + this.chips.each(function(chip) + { + if (chip.options.contID == chipContID) + { + chip.hide(); + this.chips = this.chips.without(chip); + } + }.bind(this)) + }, + + /* + * Add a chip to the table. + */ + addChip: function(data) + { + var cords = Object.extend( + { + x: data.x, + y: data.y, + animate: true + }, arguments[1] || {}); + + var chip = new CardChip( + { + x: cords.x, + y: cords.y, + category: data.category, + contID: data.cid, + title: data.title, + image: data.chip, + locked: data.locked || false, + animate: cords.animate + }); + + chip.addTo(this.table); + this.chips.push(chip); + }, + + /* + * Clear out the chip cache. This function is not to be used within + * the code. It exists to be called manually on the command line for + * debugging purposes. + */ + _blowChips: function() + { + this.chips = []; + }, + + /* + * Serialize all the chip data on the table to an object, presumably + * to be converted to JSON later. The object should match the output + * of card_table.pl + */ + getChipData: function() + { + var data = []; + + this.chips.each(function(c) + { + data.push(c._serialize()); + }); + + return data; + }, + + /* + * Tests if the table contains a chip with the particular content ID. + */ + hasChip: function(chipID) + { + var chip = this.chips.find(function(item) + { + if (item.options.contID == chipID) + return true; + + return false; + }); + + return chip ? true : false; + }, + + /* + * Changes the size of the table when the browser window is resized. + */ + resize: function() + { + this.table.style.height = ($('footer').offsetTop - + $('footer').offsetHeight) + 'px'; + }, + + /* + * Shows the table. + */ + show: function() + { + this.showChips(); + SME.currentTable = this.options.id; + this.table.style.display = 'block'; + }, + + /* + * Hides the table. + */ + hide: function() + { + this.table.style.display = 'none'; + } +}); + +Object.extend(CardTable, +{ + /* + * Show a table and hide all others. Not the special exception for the + * sketchbook. + */ + showTable: function(table) + { + SME.tables.each(function(item) + { + if (item.options.id == table) + { + item.show(); + } + else + { + item.hide(); + } + }); + + if (table == 'sketchbook') + { + SME.sketchbook.show(); + } + else + { + SME.sketchbook.hide(); + } + }, + + /* + * Return the color as a string for a table based on table ID. Note + * the special exception for special case cards which are not associated + * with a particular table. + */ + tableColor: function(table) + { + if (table == 'special') + { + return SME.colors.grey; + } + + var color = SME.tables.find(function(item) + { + if (item.options.id == table) + { + return true; + } + + return false; + }).options.color; + + return color; + } +}); \ 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 @@ +/* + * Material Experience - Utility Functions + * + * EYEMG - Interactive Media Group + * Created by Mike Crute (mcrute@eyemg.com) + * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07 + * + * Various utility functions that do not natively exist in any of + * the libraries we use. + * + * This is also the zoo for monkey patches. Be careful. + */ + +Object.extend(window, +{ + /* + * Calculates the top and left offsets to center an element within the + * page. Every browser seems to approach this in a different fashion but + * this function works well on FX, OP, WK, and IE. + */ + calcCordsToCenter: function(height, width) + { + var windowSize = window.getDimensions(); + + return { + top: Math.round(((windowSize.height - height) / 2)), + left: Math.round(((windowSize.width - width) / 2)) + }; + }, + + /* + * Gets the dimensions of the current window accounting for variations + * in browsers. Supports IE in either quirks or strict mode. + */ + getDimensions: function() + { + var windowSize = { + height: document.documentElement.clientHeight, + width: document.documentElement.clientWidth + }; + + if (Prototype.Browser.Opera) + { + windowSize = { + height: document.body.clientHeight, + width: document.body.clientWidth + }; + } + + if (Prototype.Browser.WebKit) + { + windowSize = { + height: window.innerHeight, + width: window.innerWidth + }; + } + + return windowSize; + }, + + /* + * Verify the screen size is great than a set minimum or warn the user. + * Probably superfluous but why not. + */ + checkResolution: function() + { + var size = window.getDimensions(); + + if ( + size.height < SME.sizes.windMin.height || + size.width < SME.sizes.windMin.width + ) + { + new Bezel({ displayTime: 2 }).show(Strings.windowSize); + Event.stopObserving(window, 'resize', window.checkResolution); + } + } +}); + +Object.extend(String.prototype, +{ + /* + * Cleans up sloppy Compose JSON output by removing comments, newlines, + * and tabs. + */ + cleanJSON: function() + { + var data = this; + data = data.replace(/^\/\/.*/gm,''); + data = data.replace(/(\n|\t|\r)/g,''); + + return data; + } +}); + +Object.extend(Ajax.Request.prototype, +{ + /* + * Forces IE to enqueue XHR requests otherwise it gets bogged down and + * freezes the interface. + */ + initialize: function(url, options) + { + this.transport = Ajax.getTransport(); + this.setOptions(options); + this.url = url; + + if (Prototype.Browser.IE) + { + Ajax.requestQueue.push(this); + Ajax._runQueue(); + } + else + { + this.request(url); + } + }, + + /* + * Monkey patch prototype so it stops auto-evaluating the returned JSON + * data. Our JSON data is simply not clean enough for that and it throws + * errors all over the place. + * + * I've made as few modifications from the original as possible to ease + * in porting forward changes made in Prototype. + */ + respondToReadyState: function(readyState) + { + var state = Ajax.Request.Events[readyState]; + var transport = this.transport; + + if (state == 'Complete') + { + try + { + this._complete = true; + (this.options['on' + this.transport.status] + || this.options['on' + (this.success() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(transport); + } + catch (e) + { + this.dispatchException(e); + } + } + + try + { + (this.options['on' + state] || Prototype.emptyFunction)(transport); + Ajax.Responders.dispatch('on' + state, this, transport); + } + catch (e) + { + this.dispatchException(e); + } + + if (state == 'Complete') + { + // avoid memory leak in MSIE: clean up + this.transport.onreadystatechange = Prototype.emptyFunction; + } + } +}); + +/* + * Implement AJAX request queueing. IE does not handle AJAX request concurrency + * very well and will freeze the entire browser interface if more than 2 calls + * are queued at a single time. We implement a global request queue and a queue + * runner that handles concurrent AJAX requests. The code is pretty straight + * forward but it also depends on modifications made to Ajax.Request.prototype. + */ +Object.extend(Ajax, +{ + // Global AJAX Request Queue + requestQueue: [], + + + /* + * Process events in the request queue. + */ + _runQueue: function() + { + if (this.activeRequestCount > 1) + { + // Too many requests so we will try again later + new PeriodicalExecuter(function(executer) + { + this._runQueue(); + executer.stop(); + }.bind(this), 0.1); + + return; + } + + // Run the first request in the queue + var currentRequest = this.requestQueue.shift(); + currentRequest.request(currentRequest.url); + } +}); \ 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 @@ +/* + * Material Experience - 2 Column Custom Content Base Styles + * + * EYEMG - Interactive Media Group + * Created by Mike Crute (mcrute@eyemg.com) + * Updated by Mike Crute (mcrute@eyemg.com) on 10/21/07 + */ + +body +{ + font-family: Verdana, sans-serif; + font-size: 11px; + margin: 10px; + padding: 0px; +} + +ul#navigation +{ + margin: 0px; + padding: 0px; +} + +ul#navigation li +{ + margin: 5px 30px 0px 0px; + list-style: none; + font-weight: bolder; + border: 1px solid white; +} + +ul#navigation li a +{ + display: block; + text-decoration: none; + padding: 2px; + border: 3px solid black; +} + +ul#navigation li ul li a +{ + margin: 0px -60px 0px -30px; + font-weight: normal; +} + +.selected +{ + background: black; + color: white; + padding: 5px; +} + +.narrowCol, .wideCol +{ + float: left; + height: 460px; + overflow: hidden; +} + +.narrowCol +{ + width: 290px; + margin-right: 20px; +} + +.wideCol +{ + width: 569px; +} 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 @@ +/* + * Material Experience - 2 Column Custom Content Script + * + * EYEMG - Interactive Media Group + * Created by Mike Crute (mcrute@eyemg.com) + * Updated by Mike Crute (mcrute@eyemg.com) on 10/21/07 + */ + +// Tracks the last ID requested +var lastID = null; + +// Track the currently open nav item +var curr = null; + +document.write(''); + +function doLoad() +{ + // Hide everything first + $$('ul#navigation li ul').each(function(item) + { + item.setStyle({ display: 'none' }); + }); + + // Then attach the onclick events + $$('ul#navigation li').each(function(item) + { + Event.observe(item, 'click', function(event) + { + // Hide the currently opened item if it is clicked again + // per BS. + if (curr && item == curr) + { + curr.down('ul').hide(); + item.setStyle({ borderLeft: '0px' }); + curr = null; + return; + } + + curr = item; + + // Hide everything + $$('ul#navigation li ul').each(function(item) + { + item.setStyle({ display: 'none' }); + }); + + // Clear out the borders + $$('ul#navigation li').each(function(item) + { + item.setStyle({ borderLeft: '' }); + }); + + // Figure out what color we SHOULD be, this will change + // depending on the card color we are on so just rely + // on the designer to communicate through the CSS, + // probably not wise relying on a designer for code + // but alas... + var color = $$('ul#navigation li a')[0].getStyle('background-color'); + + // Catch and execute or just leave it lie + try + { + if (item.down('ul').style.display != 'none') + { + item.down('ul').hide(); + Event.stop(event); + return; + } + + item.down('ul').show(); + item.setStyle({ borderLeft: '3px solid ' + color }); + Event.stop(event); + } + catch (e) + { + // Just let the link do its thing + } + }); + }); +} + +function locateContent(contID) +{ + // Hide the last ID from a global variable because going over each + // item and hiding it could take a prohibitively long time on a big + // page and we will most definitely be dealing with big pages here. + if (lastID) + { + $(lastID).className = ""; + } + + // Track the requested content ID + lastID = contID; + + // The scroll top but subtract the height of the item itself or we + // always scroll to the bottom of items. + $('test').scrollTop = $(contID).offsetTop - $(contID).getHeight(); + $(contID).className = "selected"; +} + +/* + * Create custom scroll bars for an HTML element. + */ +function scrollify(div) +{ + // Inherit the color from a link + var color = $$('ul#navigation li a')[0].getStyle('background-color'); + + // Create the scroll container and draggable widget + document.body.appendChild( + this.scrollCont = Builder.node('div', + { + style: 'border-left: 1px solid ' + color + ';' + + 'width: 1px; position: absolute; ' + + 'height:' + div.offsetHeight + 'px; ' + + 'top: 10px;' + 'left: ' + (div.offsetLeft + div.offsetWidth + 12) + 'px;' + }, + + this.scrollBar = Builder.node('img', + { + src: 'http://materialexperience.santoprene.com/images/pill.gif', + style: 'display: block; margin-left: -3px; cursor: move; ' + + 'background: ' + color + '; padding: 0px;' + }) + )); + + // Create the scroller + this.scroller = new Control.Slider(this.scrollBar, this.scrollCont, + { + axis: 'vertical', + range: $R(0, div.scrollHeight), + + onSlide: function(value) + { + div.scrollTop = Math.floor(value); + }.bind(this) + }); + + new PeriodicalExecuter(function() + { + // Update the scroller range when the contents change + this.scroller.range = $R(0, div.scrollHeight); + + // Move the scroller when the scrolled element changes + // (e.g. linking down into the page). + if (this.scroller.value != div.scrollTop) + { + this.scroller.setValue(div.scrollTop); + } + }.bind(this), 1); +} + +/* + * Attach scrollbars to the narrow column and the wide column if they exist. + */ +Event.observe(window, 'load', function() +{ + if ($$('.narrowCol')[0]) + { + new scrollify($$('.narrowCol')[0]); + } + + if ($$('.wideCol')[0]) + { + new scrollify($$('.wideCol')[0]); + } +}); + +Event.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 @@ +/* + * Material Experience - Card Tables Data File + * + * EYEMG - Interactive Media Group + * Created by Mike Crute (mcrute@eyemg.com) + * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07 + * + * Data file for the card tables. This allows us the flexibility + * to add or remove tables at a later date. Note that each table + * id should correspond to a compose card type ID otherwise + * no data will be loaded. + */ + +[ + { + tid: 'home', + name: 'Home', + color: SME.colors.grey, + decorate: true + }, + + { + tid: 'inspiration', + name: 'Inspiration', + color: SME.colors.brown, + decorate: true + }, + + { + tid: 'exploration', + name: 'Exploration', + color: SME.colors.red, + decorate: true + }, + + { + tid: 'close-up', + name: 'Close-Up', + color: SME.colors.orange, + decorate: true + }, + + { + tid: 'making_it', + name: 'Making It', + color: SME.colors.blue, + decorate: true + }, + + { + tid: 'info-samples', + name: 'Info & Samples', + color: SME.colors.green, + decorate: true + }, + + { + tid: 'preview', + name: 'Preview', + color: SME.colors.grey, + decorate: false + } +] \ 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 @@ +[ + +] \ 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 @@ +/* + * Material Experience - English Strings File + * + * EYEMG - Interactive Media Group + * Created by Mike Crute (mcrute@eyemg.com) on 9/26/07 + * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07 + * + * This strings file is the foundation for internationalization of the + * Material Experience website. These strings can be changed to anything + * and those changes will be reflected in the interface. Be careful, you + * can also potentially seriously break things. Follow the rules and + * comments. + * + * Note that as of this writing full internationalization is not fully + * implemented and won't be till the client requests it, I anticipate + * this happening some day so here we are with a strings file. + * + * Rules: + * Quoting -- if you need to use quotes, either single or double make sure + * you vary your quoting, 'this won't' work you need "this'll work" + * + * HTML -- some strings can contain HTML, don't go crazy, constrain your + * HTML to line breaks or you will breaks and maybe images or + * you will break things. + * + * Doubt -- if your not sure what effect your changes will have ASK SOMEBODY + * don't just make changes and hope things work + * + * Unicode -- sure, you can use unicode, just make sure you save the file with + * the appropriate character set. No BOM please. + * + * Comment -- leave the comments alone, and change the updated by comment at the + * top of this file when you make changes. + */ + +var Strings = +{ + // + // LANGUAGE FILE METADATA + // + // Language of the String File and version of the site this is for + // Neither of these are used yet but set them anyhow since they + // may be used later on. Use C-style locale codes for the language. + // + // Stars are perfectly acceptable in the version field but will only + // be matched to 3 places. + language : 'en_us', + version : '1.*', + + // + // APPLICATION TITLE + // + // Displayed in the browser title bar + appTitle: 'Material Experience', + + // + // WINDOW SIZE ERROR + // + // Displayed once when the window size is smaller than the minimum size + windowSize : "Your window size is smaller than the recommended 1024x768 resolution.
" + + "Some items may be positioned outside the viewable area.", + + // + // DATA LOADERS + // + // Displayed whenever an AJAX call is made and while it is waiting to load + loadingNoAnim : "Loading Data...", + loadingAnim : "Loading Data ", + + // + // PRINTING ERRORS + // + cantPrint : "Sorry Can't Print", // Displayed if the browser doesn't support printing + printClose : "Do you want to close this window?", // Displayed before we auto/close the print window + + // + // CARD DATA ERRORS + // + // Displayed within a card when it fails to load correctly + cardErrorTitle : "Card Error", + cardErrorText : "We're very sorry, there was an error loading this card. Please try again later.", + + // + // AJAX ERRORS + // + // Displayed when an AJAX call fails. + ajaxError : "Some Data Could Not Be Loaded.
We're sorry, please refresh this page.", + + // + // CARD BUTTON LABELS + // + // Labels shown next to the buttons in the card + addToSketchbook : "Add to Sketchbook", + sendToFriend : "Send to a Friend", + printCard : "Print this Card", + myHistory : "My History", + closeCard : "Close Card", + + // + // CARD CONTENT + // + // Static content within the card that is hard-coded + moreInfo : "More Information", + skipButton : "Skip this Card", + + // + // SKETCHBOOK ERRORS AND CONFIRMATIONS + // + // Confirmations and errors for sketchbook actions + sketchbook : "Sketchbook", + alreadyAddedSB : "Card already exists in your sketchbook.", + addedSketchbook : "Added to Sketchbook", + addedGuestSB : "Added to Guest Sketchbook
Please visit the sketchbook to log in and save your items.", + + // + // UTILITY BOX LINKS + // + // Links at the top right of the interface. + pleaseLogin : "Please Log In", + manageAccount : "Manage My Account", + logout : "Log Out" +}; \ No newline at end of file diff --git a/docroot/favicon.ico b/docroot/favicon.ico new file mode 100755 index 0000000..32168fd Binary files /dev/null and b/docroot/favicon.ico differ diff --git a/docroot/graphics/chip.psd b/docroot/graphics/chip.psd new file mode 100755 index 0000000..0f0828e Binary files /dev/null and b/docroot/graphics/chip.psd differ diff --git a/docroot/images/arrow_down.gif b/docroot/images/arrow_down.gif new file mode 100755 index 0000000..7395d65 Binary files /dev/null and b/docroot/images/arrow_down.gif differ diff --git a/docroot/images/arrow_right_grey.gif b/docroot/images/arrow_right_grey.gif new file mode 100755 index 0000000..bd0a087 Binary files /dev/null and b/docroot/images/arrow_right_grey.gif differ diff --git a/docroot/images/blank.gif b/docroot/images/blank.gif new file mode 100755 index 0000000..75b945d Binary files /dev/null and b/docroot/images/blank.gif differ diff --git a/docroot/images/close.gif b/docroot/images/close.gif new file mode 100755 index 0000000..b19fbac Binary files /dev/null and b/docroot/images/close.gif differ diff --git a/docroot/images/email.gif b/docroot/images/email.gif new file mode 100755 index 0000000..5d18b3c Binary files /dev/null and b/docroot/images/email.gif differ diff --git a/docroot/images/history.gif b/docroot/images/history.gif new file mode 100755 index 0000000..48347c0 Binary files /dev/null and b/docroot/images/history.gif differ diff --git a/docroot/images/loader.gif b/docroot/images/loader.gif new file mode 100755 index 0000000..cb91da8 Binary files /dev/null and b/docroot/images/loader.gif differ diff --git a/docroot/images/logo.gif b/docroot/images/logo.gif new file mode 100755 index 0000000..7e8779b Binary files /dev/null and b/docroot/images/logo.gif differ diff --git a/docroot/images/pill.gif b/docroot/images/pill.gif new file mode 100755 index 0000000..cc96b81 Binary files /dev/null and b/docroot/images/pill.gif differ diff --git a/docroot/images/plus.gif b/docroot/images/plus.gif new file mode 100755 index 0000000..95eca20 Binary files /dev/null and b/docroot/images/plus.gif differ diff --git a/docroot/images/print.gif b/docroot/images/print.gif new file mode 100755 index 0000000..3668f6a Binary files /dev/null and b/docroot/images/print.gif differ diff --git a/docroot/images/tagline.png b/docroot/images/tagline.png new file mode 100755 index 0000000..bda7a1d Binary files /dev/null and b/docroot/images/tagline.png differ diff --git a/docroot/images/trash_can.jpg b/docroot/images/trash_can.jpg new file mode 100755 index 0000000..5801655 Binary files /dev/null and b/docroot/images/trash_can.jpg 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 @@ + + + + + Material Experience + + + + + Loading + + + + +
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.
+ +
    + + + Search Engine Version + + + + diff --git a/docroot/lib/firebug/errorIcon.png b/docroot/lib/firebug/errorIcon.png new file mode 100755 index 0000000..2d75261 Binary files /dev/null and b/docroot/lib/firebug/errorIcon.png 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 @@ + +html, body { + margin: 0; + background: #FFFFFF; + font-family: Lucida Grande, Tahoma, sans-serif; + font-size: 11px; + overflow: hidden; +} + +a { + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +.toolbar { + height: 14px; + border-top: 1px solid ThreeDHighlight; + border-bottom: 1px solid ThreeDShadow; + padding: 2px 6px; + background: ThreeDFace; +} + +.toolbarRight { + position: absolute; + top: 4px; + right: 6px; +} + +#log { + overflow: auto; + position: absolute; + left: 0; + width: 100%; +} + +#commandLine { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 18px; + border: none; + border-top: 1px solid ThreeDShadow; +} + +/************************************************************************************************/ + +.logRow { + position: relative; + border-bottom: 1px solid #D7D7D7; + padding: 2px 4px 1px 6px; + background-color: #FFFFFF; +} + +.logRow-command { + font-family: Monaco, monospace; + color: blue; +} + +.objectBox-null { + padding: 0 2px; + border: 1px solid #666666; + background-color: #888888; + color: #FFFFFF; +} + +.objectBox-string { + font-family: Monaco, monospace; + color: red; + white-space: pre; +} + +.objectBox-number { + color: #000088; +} + +.objectBox-function { + font-family: Monaco, monospace; + color: DarkGreen; +} + +.objectBox-object { + color: DarkGreen; + font-weight: bold; +} + +/************************************************************************************************/ + +.logRow-info, +.logRow-error, +.logRow-warning { + background: #FFFFFF no-repeat 2px 2px; + padding-left: 20px; + padding-bottom: 3px; +} + +.logRow-info { + background-image: url(infoIcon.png); +} + +.logRow-warning { + background-color: cyan; + background-image: url(warningIcon.png); +} + +.logRow-error { + background-color: LightYellow; + background-image: url(errorIcon.png); +} + +.errorMessage { + vertical-align: top; + color: #FF0000; +} + +.objectBox-sourceLink { + position: absolute; + right: 4px; + top: 2px; + padding-left: 8px; + font-family: Lucida Grande, sans-serif; + font-weight: bold; + color: #0000FF; +} + +/************************************************************************************************/ + +.logRow-group { + background: #EEEEEE; + border-bottom: none; +} + +.logGroup { + background: #EEEEEE; +} + +.logGroupBox { + margin-left: 24px; + border-top: 1px solid #D7D7D7; + border-left: 1px solid #D7D7D7; +} + +/************************************************************************************************/ + +.selectorTag, +.selectorId, +.selectorClass { + font-family: Monaco, monospace; + font-weight: normal; +} + +.selectorTag { + color: #0000FF; +} + +.selectorId { + color: DarkBlue; +} + +.selectorClass { + color: red; +} + +/************************************************************************************************/ + +.objectBox-element { + font-family: Monaco, monospace; + color: #000088; +} + +.nodeChildren { + margin-left: 16px; +} + +.nodeTag { + color: blue; +} + +.nodeValue { + color: #FF0000; + font-weight: normal; +} + +.nodeText, +.nodeComment { + margin: 0 2px; + vertical-align: top; +} + +.nodeText { + color: #333333; +} + +.nodeComment { + color: DarkGreen; +} + +/************************************************************************************************/ + +.propertyNameCell { + vertical-align: top; +} + +.propertyName { + font-weight: bold; +} 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 @@ + + + + + + Firebug + + + + +
    + Clear + + Close + +
    +
    + + + + + 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 @@ + +if (!("console" in window) || !("firebug" in console)) { +(function() +{ + window.console = + { + log: function() + { + logFormatted(arguments, ""); + }, + + debug: function() + { + logFormatted(arguments, "debug"); + }, + + info: function() + { + logFormatted(arguments, "info"); + }, + + warn: function() + { + logFormatted(arguments, "warning"); + }, + + error: function() + { + logFormatted(arguments, "error"); + }, + + assert: function(truth, message) + { + if (!truth) + { + var args = []; + for (var i = 1; i < arguments.length; ++i) + args.push(arguments[i]); + + logFormatted(args.length ? args : ["Assertion Failure"], "error"); + throw message ? message : "Assertion Failure"; + } + }, + + dir: function(object) + { + var html = []; + + var pairs = []; + for (var name in object) + { + try + { + pairs.push([name, object[name]]); + } + catch (exc) + { + } + } + + pairs.sort(function(a, b) { return a[0] < b[0] ? -1 : 1; }); + + html.push(''); + for (var i = 0; i < pairs.length; ++i) + { + var name = pairs[i][0], value = pairs[i][1]; + + html.push('', + '', ''); + } + html.push('
    ', + escapeHTML(name), ''); + appendObject(value, html); + html.push('
    '); + + logRow(html, "dir"); + }, + + dirxml: function(node) + { + var html = []; + + appendNode(node, html); + logRow(html, "dirxml"); + }, + + group: function() + { + logRow(arguments, "group", pushGroup); + }, + + groupEnd: function() + { + logRow(arguments, "", popGroup); + }, + + time: function(name) + { + timeMap[name] = (new Date()).getTime(); + }, + + timeEnd: function(name) + { + if (name in timeMap) + { + var delta = (new Date()).getTime() - timeMap[name]; + logFormatted([name+ ":", delta+"ms"]); + delete timeMap[name]; + } + }, + + count: function() + { + this.warn(["count() not supported."]); + }, + + trace: function() + { + this.warn(["trace() not supported."]); + }, + + profile: function() + { + this.warn(["profile() not supported."]); + }, + + profileEnd: function() + { + }, + + clear: function() + { + consoleBody.innerHTML = ""; + }, + + open: function() + { + toggleConsole(true); + }, + + close: function() + { + if (frameVisible) + toggleConsole(); + } + }; + + // ******************************************************************************************** + + var consoleFrame = null; + var consoleBody = null; + var commandLine = null; + + var frameVisible = false; + var messageQueue = []; + var groupStack = []; + var timeMap = {}; + + var clPrefix = ">>> "; + + var isFirefox = navigator.userAgent.indexOf("Firefox") != -1; + var isIE = navigator.userAgent.indexOf("MSIE") != -1; + var isOpera = navigator.userAgent.indexOf("Opera") != -1; + var isSafari = navigator.userAgent.indexOf("AppleWebKit") != -1; + + // ******************************************************************************************** + + function toggleConsole(forceOpen) + { + frameVisible = forceOpen || !frameVisible; + if (consoleFrame) + consoleFrame.style.visibility = frameVisible ? "visible" : "hidden"; + else + waitForBody(); + } + + function focusCommandLine() + { + toggleConsole(true); + if (commandLine) + commandLine.focus(); + } + + function waitForBody() + { + if (document.body) + createFrame(); + else + setTimeout(waitForBody, 200); + } + + function createFrame() + { + if (consoleFrame) + return; + + window.onFirebugReady = function(doc) + { + window.onFirebugReady = null; + + var toolbar = doc.getElementById("toolbar"); + toolbar.onmousedown = onSplitterMouseDown; + + commandLine = doc.getElementById("commandLine"); + addEvent(commandLine, "keydown", onCommandLineKeyDown); + + addEvent(doc, isIE || isSafari ? "keydown" : "keypress", onKeyDown); + + consoleBody = doc.getElementById("log"); + layout(); + flush(); + } + + var baseURL = getFirebugURL(); + + consoleFrame = document.createElement("iframe"); + consoleFrame.setAttribute("src", baseURL+"/firebug.html"); + consoleFrame.setAttribute("frameBorder", "0"); + consoleFrame.style.visibility = (frameVisible ? "visible" : "hidden"); + consoleFrame.style.zIndex = "2147483647"; + consoleFrame.style.position = "fixed"; + consoleFrame.style.width = "100%"; + consoleFrame.style.left = "0"; + consoleFrame.style.bottom = "0"; + consoleFrame.style.height = "200px"; + document.body.appendChild(consoleFrame); + } + + function getFirebugURL() + { + var scripts = document.getElementsByTagName("script"); + for (var i = 0; i < scripts.length; ++i) + { + if (scripts[i].src.indexOf("firebug.js") != -1) + { + var lastSlash = scripts[i].src.lastIndexOf("/"); + return scripts[i].src.substr(0, lastSlash); + } + } + } + + function evalCommandLine() + { + var text = commandLine.value; + commandLine.value = ""; + + logRow([clPrefix, text], "command"); + + var value; + try + { + value = eval(text); + } + catch (exc) + { + } + + console.log(value); + } + + function layout() + { + var toolbar = consoleBody.ownerDocument.getElementById("toolbar"); + var height = consoleFrame.offsetHeight - (toolbar.offsetHeight + commandLine.offsetHeight); + consoleBody.style.top = toolbar.offsetHeight + "px"; + consoleBody.style.height = height + "px"; + + commandLine.style.top = (consoleFrame.offsetHeight - commandLine.offsetHeight) + "px"; + } + + function logRow(message, className, handler) + { + if (consoleBody) + writeMessage(message, className, handler); + else + { + messageQueue.push([message, className, handler]); + waitForBody(); + } + } + + function flush() + { + var queue = messageQueue; + messageQueue = []; + + for (var i = 0; i < queue.length; ++i) + writeMessage(queue[i][0], queue[i][1], queue[i][2]); + } + + function writeMessage(message, className, handler) + { + var isScrolledToBottom = + consoleBody.scrollTop + consoleBody.offsetHeight >= consoleBody.scrollHeight; + + if (!handler) + handler = writeRow; + + handler(message, className); + + if (isScrolledToBottom) + consoleBody.scrollTop = consoleBody.scrollHeight - consoleBody.offsetHeight; + } + + function appendRow(row) + { + var container = groupStack.length ? groupStack[groupStack.length-1] : consoleBody; + container.appendChild(row); + } + + function writeRow(message, className) + { + var row = consoleBody.ownerDocument.createElement("div"); + row.className = "logRow" + (className ? " logRow-"+className : ""); + row.innerHTML = message.join(""); + appendRow(row); + } + + function pushGroup(message, className) + { + logFormatted(message, className); + + var groupRow = consoleBody.ownerDocument.createElement("div"); + groupRow.className = "logGroup"; + var groupRowBox = consoleBody.ownerDocument.createElement("div"); + groupRowBox.className = "logGroupBox"; + groupRow.appendChild(groupRowBox); + appendRow(groupRowBox); + groupStack.push(groupRowBox); + } + + function popGroup() + { + groupStack.pop(); + } + + // ******************************************************************************************** + + function logFormatted(objects, className) + { + var html = []; + + var format = objects[0]; + var objIndex = 0; + + if (typeof(format) != "string") + { + format = ""; + objIndex = -1; + } + + var parts = parseFormat(format); + for (var i = 0; i < parts.length; ++i) + { + var part = parts[i]; + if (part && typeof(part) == "object") + { + var object = objects[++objIndex]; + part.appender(object, html); + } + else + appendText(part, html); + } + + for (var i = objIndex+1; i < objects.length; ++i) + { + appendText(" ", html); + + var object = objects[i]; + if (typeof(object) == "string") + appendText(object, html); + else + appendObject(object, html); + } + + logRow(html, className); + } + + function parseFormat(format) + { + var parts = []; + + var reg = /((^%|[^\\]%)(\d+)?(\.)([a-zA-Z]))|((^%|[^\\]%)([a-zA-Z]))/; + var appenderMap = {s: appendText, d: appendInteger, i: appendInteger, f: appendFloat}; + + for (var m = reg.exec(format); m; m = reg.exec(format)) + { + var type = m[8] ? m[8] : m[5]; + var appender = type in appenderMap ? appenderMap[type] : appendObject; + var precision = m[3] ? parseInt(m[3]) : (m[4] == "." ? -1 : 0); + + parts.push(format.substr(0, m[0][0] == "%" ? m.index : m.index+1)); + parts.push({appender: appender, precision: precision}); + + format = format.substr(m.index+m[0].length); + } + + parts.push(format); + + return parts; + } + + function escapeHTML(value) + { + function replaceChars(ch) + { + switch (ch) + { + case "<": + return "<"; + case ">": + return ">"; + case "&": + return "&"; + case "'": + return "'"; + case '"': + return """; + } + return "?"; + }; + return String(value).replace(/[<>&"']/g, replaceChars); + } + + function objectToString(object) + { + try + { + return object+""; + } + catch (exc) + { + return null; + } + } + + // ******************************************************************************************** + + function appendText(object, html) + { + html.push(escapeHTML(objectToString(object))); + } + + function appendNull(object, html) + { + html.push('', escapeHTML(objectToString(object)), ''); + } + + function appendString(object, html) + { + html.push('"', escapeHTML(objectToString(object)), + '"'); + } + + function appendInteger(object, html) + { + html.push('', escapeHTML(objectToString(object)), ''); + } + + function appendFloat(object, html) + { + html.push('', escapeHTML(objectToString(object)), ''); + } + + function appendFunction(object, html) + { + var reName = /function ?(.*?)\(/; + var m = reName.exec(objectToString(object)); + var name = m ? m[1] : "function"; + html.push('', escapeHTML(name), '()'); + } + + function appendObject(object, html) + { + try + { + if (object == undefined) + appendNull("undefined", html); + else if (object == null) + appendNull("null", html); + else if (typeof object == "string") + appendString(object, html); + else if (typeof object == "number") + appendInteger(object, html); + else if (typeof object == "function") + appendFunction(object, html); + else if (object.nodeType == 1) + appendSelector(object, html); + else if (typeof object == "object") + appendObjectFormatted(object, html); + else + appendText(object, html); + } + catch (exc) + { + } + } + + function appendObjectFormatted(object, html) + { + var text = objectToString(object); + var reObject = /\[object (.*?)\]/; + + var m = reObject.exec(text); + html.push('', m ? m[1] : text, '') + } + + function appendSelector(object, html) + { + html.push(''); + + html.push('', escapeHTML(object.nodeName.toLowerCase()), ''); + if (object.id) + html.push('#', escapeHTML(object.id), ''); + if (object.className) + html.push('.', escapeHTML(object.className), ''); + + html.push(''); + } + + function appendNode(node, html) + { + if (node.nodeType == 1) + { + html.push( + '
    ', + '<', node.nodeName.toLowerCase(), ''); + + for (var i = 0; i < node.attributes.length; ++i) + { + var attr = node.attributes[i]; + if (!attr.specified) + continue; + + html.push(' ', attr.nodeName.toLowerCase(), + '="', escapeHTML(attr.nodeValue), + '"') + } + + if (node.firstChild) + { + html.push('>
    '); + + for (var child = node.firstChild; child; child = child.nextSibling) + appendNode(child, html); + + html.push('
    </', + node.nodeName.toLowerCase(), '>
    '); + } + else + html.push('/>
    '); + } + else if (node.nodeType == 3) + { + html.push('
    ', escapeHTML(node.nodeValue), + '
    '); + } + } + + // ******************************************************************************************** + + function addEvent(object, name, handler) + { + if (document.all) + object.attachEvent("on"+name, handler); + else + object.addEventListener(name, handler, false); + } + + function removeEvent(object, name, handler) + { + if (document.all) + object.detachEvent("on"+name, handler); + else + object.removeEventListener(name, handler, false); + } + + function cancelEvent(event) + { + if (document.all) + event.cancelBubble = true; + else + event.stopPropagation(); + } + + function onError(msg, href, lineNo) + { + var html = []; + + var lastSlash = href.lastIndexOf("/"); + var fileName = lastSlash == -1 ? href : href.substr(lastSlash+1); + + html.push( + '', msg, '', + '' + ); + + logRow(html, "error"); + }; + + function onKeyDown(event) + { + if (event.keyCode == 123) + toggleConsole(); + else if ((event.keyCode == 108 || event.keyCode == 76) && event.shiftKey + && (event.metaKey || event.ctrlKey)) + focusCommandLine(); + else + return; + + cancelEvent(event); + } + + function onSplitterMouseDown(event) + { + if (isSafari || isOpera) + return; + + addEvent(document, "mousemove", onSplitterMouseMove); + addEvent(document, "mouseup", onSplitterMouseUp); + + for (var i = 0; i < frames.length; ++i) + { + addEvent(frames[i].document, "mousemove", onSplitterMouseMove); + addEvent(frames[i].document, "mouseup", onSplitterMouseUp); + } + } + + function onSplitterMouseMove(event) + { + var win = document.all + ? event.srcElement.ownerDocument.parentWindow + : event.target.ownerDocument.defaultView; + + var clientY = event.clientY; + if (win != win.parent) + clientY += win.frameElement ? win.frameElement.offsetTop : 0; + + var height = consoleFrame.offsetTop + consoleFrame.clientHeight; + var y = height - clientY; + + consoleFrame.style.height = y + "px"; + layout(); + } + + function onSplitterMouseUp(event) + { + removeEvent(document, "mousemove", onSplitterMouseMove); + removeEvent(document, "mouseup", onSplitterMouseUp); + + for (var i = 0; i < frames.length; ++i) + { + removeEvent(frames[i].document, "mousemove", onSplitterMouseMove); + removeEvent(frames[i].document, "mouseup", onSplitterMouseUp); + } + } + + function onCommandLineKeyDown(event) + { + if (event.keyCode == 13) + evalCommandLine(); + else if (event.keyCode == 27) + commandLine.value = ""; + } + + window.onerror = onError; + addEvent(document, isIE || isSafari ? "keydown" : "keypress", onKeyDown); + + if (document.documentElement.getAttribute("debug") == "true") + toggleConsole(true); +})(); +} 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 @@ + +if (!("console" in window) || !("firebug" in console)) +{ + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", + "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"]; + + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {} +} \ 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 Binary files /dev/null and b/docroot/lib/firebug/infoIcon.png differ diff --git a/docroot/lib/firebug/warningIcon.png b/docroot/lib/firebug/warningIcon.png new file mode 100755 index 0000000..de51084 Binary files /dev/null and b/docroot/lib/firebug/warningIcon.png 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 @@ +/* Prototype JavaScript framework, version 1.5.1.1 + * (c) 2005-2007 Sam Stephenson + * + * Prototype is freely distributable under the terms of an MIT-style license. + * For details, see the Prototype web site: http://www.prototypejs.org/ + * +/*--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.5.1.1', + + Browser: { + IE: !!(window.attachEvent && !window.opera), + Opera: !!window.opera, + WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1, + Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1 + }, + + BrowserFeatures: { + XPath: !!document.evaluate, + ElementExtensions: !!window.HTMLElement, + SpecificElementExtensions: + (document.createElement('div').__proto__ !== + document.createElement('form').__proto__) + }, + + ScriptFragment: ']*>([\\S\\s]*?)<\/script>', + JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, + + emptyFunction: function() { }, + K: function(x) { return x } +} + +var Class = { + create: function() { + return function() { + this.initialize.apply(this, arguments); + } + } +} + +var Abstract = new Object(); + +Object.extend = function(destination, source) { + for (var property in source) { + destination[property] = source[property]; + } + return destination; +} + +Object.extend(Object, { + inspect: function(object) { + try { + if (object === undefined) return 'undefined'; + if (object === null) return 'null'; + return object.inspect ? object.inspect() : object.toString(); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } + }, + + toJSON: function(object) { + var type = typeof object; + switch(type) { + case 'undefined': + case 'function': + case 'unknown': return; + case 'boolean': return object.toString(); + } + if (object === null) return 'null'; + if (object.toJSON) return object.toJSON(); + if (object.ownerDocument === document) return; + var results = []; + for (var property in object) { + var value = Object.toJSON(object[property]); + if (value !== undefined) + results.push(property.toJSON() + ': ' + value); + } + return '{' + results.join(', ') + '}'; + }, + + keys: function(object) { + var keys = []; + for (var property in object) + keys.push(property); + return keys; + }, + + values: function(object) { + var values = []; + for (var property in object) + values.push(object[property]); + return values; + }, + + clone: function(object) { + return Object.extend({}, object); + } +}); + +Function.prototype.bind = function() { + var __method = this, args = $A(arguments), object = args.shift(); + return function() { + return __method.apply(object, args.concat($A(arguments))); + } +} + +Function.prototype.bindAsEventListener = function(object) { + var __method = this, args = $A(arguments), object = args.shift(); + return function(event) { + return __method.apply(object, [event || window.event].concat(args)); + } +} + +Object.extend(Number.prototype, { + toColorPart: function() { + return this.toPaddedString(2, 16); + }, + + succ: function() { + return this + 1; + }, + + times: function(iterator) { + $R(0, this, true).each(iterator); + return this; + }, + + toPaddedString: function(length, radix) { + var string = this.toString(radix || 10); + return '0'.times(length - string.length) + string; + }, + + toJSON: function() { + return isFinite(this) ? this.toString() : 'null'; + } +}); + +Date.prototype.toJSON = function() { + return '"' + this.getFullYear() + '-' + + (this.getMonth() + 1).toPaddedString(2) + '-' + + this.getDate().toPaddedString(2) + 'T' + + this.getHours().toPaddedString(2) + ':' + + this.getMinutes().toPaddedString(2) + ':' + + this.getSeconds().toPaddedString(2) + '"'; +}; + +var Try = { + these: function() { + var returnValue; + + for (var i = 0, length = arguments.length; i < length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) {} + } + + return returnValue; + } +} + +/*--------------------------------------------------------------------------*/ + +var PeriodicalExecuter = Class.create(); +PeriodicalExecuter.prototype = { + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + stop: function() { + if (!this.timer) return; + clearInterval(this.timer); + this.timer = null; + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.callback(this); + } finally { + this.currentlyExecuting = false; + } + } + } +} +Object.extend(String, { + interpret: function(value) { + return value == null ? '' : String(value); + }, + specialChar: { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '\\': '\\\\' + } +}); + +Object.extend(String.prototype, { + gsub: function(pattern, replacement) { + var result = '', source = this, match; + replacement = arguments.callee.prepareReplacement(replacement); + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += String.interpret(replacement(match)); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + }, + + sub: function(pattern, replacement, count) { + replacement = this.gsub.prepareReplacement(replacement); + count = count === undefined ? 1 : count; + + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + }, + + scan: function(pattern, iterator) { + this.gsub(pattern, iterator); + return this; + }, + + truncate: function(length, truncation) { + length = length || 30; + truncation = truncation === undefined ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : this; + }, + + strip: function() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + }, + + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + stripScripts: function() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + }, + + extractScripts: function() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + }, + + evalScripts: function() { + return this.extractScripts().map(function(script) { return eval(script) }); + }, + + escapeHTML: function() { + var self = arguments.callee; + self.text.data = this; + return self.div.innerHTML; + }, + + unescapeHTML: function() { + var div = document.createElement('div'); + div.innerHTML = this.stripTags(); + return div.childNodes[0] ? (div.childNodes.length > 1 ? + $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) : + div.childNodes[0].nodeValue) : ''; + }, + + toQueryParams: function(separator) { + var match = this.strip().match(/([^?#]*)(#.*)?$/); + if (!match) return {}; + + return match[1].split(separator || '&').inject({}, function(hash, pair) { + if ((pair = pair.split('='))[0]) { + var key = decodeURIComponent(pair.shift()); + var value = pair.length > 1 ? pair.join('=') : pair[0]; + if (value != undefined) value = decodeURIComponent(value); + + if (key in hash) { + if (hash[key].constructor != Array) hash[key] = [hash[key]]; + hash[key].push(value); + } + else hash[key] = value; + } + return hash; + }); + }, + + toArray: function() { + return this.split(''); + }, + + succ: function() { + return this.slice(0, this.length - 1) + + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); + }, + + times: function(count) { + var result = ''; + for (var i = 0; i < count; i++) result += this; + return result; + }, + + camelize: function() { + var parts = this.split('-'), len = parts.length; + if (len == 1) return parts[0]; + + var camelized = this.charAt(0) == '-' + ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) + : parts[0]; + + for (var i = 1; i < len; i++) + camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); + + return camelized; + }, + + capitalize: function() { + return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); + }, + + underscore: function() { + return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase(); + }, + + dasherize: function() { + return this.gsub(/_/,'-'); + }, + + inspect: function(useDoubleQuotes) { + var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) { + var character = String.specialChar[match[0]]; + return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16); + }); + if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; + return "'" + escapedString.replace(/'/g, '\\\'') + "'"; + }, + + toJSON: function() { + return this.inspect(true); + }, + + unfilterJSON: function(filter) { + return this.sub(filter || Prototype.JSONFilter, '#{1}'); + }, + + isJSON: function() { + var str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); + return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); + }, + + evalJSON: function(sanitize) { + var json = this.unfilterJSON(); + try { + if (!sanitize || json.isJSON()) return eval('(' + json + ')'); + } catch (e) { } + throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); + }, + + include: function(pattern) { + return this.indexOf(pattern) > -1; + }, + + startsWith: function(pattern) { + return this.indexOf(pattern) === 0; + }, + + endsWith: function(pattern) { + var d = this.length - pattern.length; + return d >= 0 && this.lastIndexOf(pattern) === d; + }, + + empty: function() { + return this == ''; + }, + + blank: function() { + return /^\s*$/.test(this); + } +}); + +if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, { + escapeHTML: function() { + return this.replace(/&/g,'&').replace(//g,'>'); + }, + unescapeHTML: function() { + return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); + } +}); + +String.prototype.gsub.prepareReplacement = function(replacement) { + if (typeof replacement == 'function') return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; +} + +String.prototype.parseQuery = String.prototype.toQueryParams; + +Object.extend(String.prototype.escapeHTML, { + div: document.createElement('div'), + text: document.createTextNode('') +}); + +with (String.prototype.escapeHTML) div.appendChild(text); + +var Template = Class.create(); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; +Template.prototype = { + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + return this.template.gsub(this.pattern, function(match) { + var before = match[1]; + if (before == '\\') return match[2]; + return before + String.interpret(object[match[3]]); + }); + } +} + +var $break = {}, $continue = new Error('"throw $continue" is deprecated, use "return" instead'); + +var Enumerable = { + each: function(iterator) { + var index = 0; + try { + this._each(function(value) { + iterator(value, index++); + }); + } catch (e) { + if (e != $break) throw e; + } + return this; + }, + + eachSlice: function(number, iterator) { + var index = -number, slices = [], array = this.toArray(); + while ((index += number) < array.length) + slices.push(array.slice(index, index+number)); + return slices.map(iterator); + }, + + all: function(iterator) { + var result = true; + this.each(function(value, index) { + result = result && !!(iterator || Prototype.K)(value, index); + if (!result) throw $break; + }); + return result; + }, + + any: function(iterator) { + var result = false; + this.each(function(value, index) { + if (result = !!(iterator || Prototype.K)(value, index)) + throw $break; + }); + return result; + }, + + collect: function(iterator) { + var results = []; + this.each(function(value, index) { + results.push((iterator || Prototype.K)(value, index)); + }); + return results; + }, + + detect: function(iterator) { + var result; + this.each(function(value, index) { + if (iterator(value, index)) { + result = value; + throw $break; + } + }); + return result; + }, + + findAll: function(iterator) { + var results = []; + this.each(function(value, index) { + if (iterator(value, index)) + results.push(value); + }); + return results; + }, + + grep: function(pattern, iterator) { + var results = []; + this.each(function(value, index) { + var stringValue = value.toString(); + if (stringValue.match(pattern)) + results.push((iterator || Prototype.K)(value, index)); + }) + return results; + }, + + include: function(object) { + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + }, + + inGroupsOf: function(number, fillWith) { + fillWith = fillWith === undefined ? null : fillWith; + return this.eachSlice(number, function(slice) { + while(slice.length < number) slice.push(fillWith); + return slice; + }); + }, + + inject: function(memo, iterator) { + this.each(function(value, index) { + memo = iterator(memo, value, index); + }); + return memo; + }, + + invoke: function(method) { + var args = $A(arguments).slice(1); + return this.map(function(value) { + return value[method].apply(value, args); + }); + }, + + max: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (result == undefined || value >= result) + result = value; + }); + return result; + }, + + min: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (result == undefined || value < result) + result = value; + }); + return result; + }, + + partition: function(iterator) { + var trues = [], falses = []; + this.each(function(value, index) { + ((iterator || Prototype.K)(value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + }, + + pluck: function(property) { + var results = []; + this.each(function(value, index) { + results.push(value[property]); + }); + return results; + }, + + reject: function(iterator) { + var results = []; + this.each(function(value, index) { + if (!iterator(value, index)) + results.push(value); + }); + return results; + }, + + sortBy: function(iterator) { + return this.map(function(value, index) { + return {value: value, criteria: iterator(value, index)}; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + }, + + toArray: function() { + return this.map(); + }, + + zip: function() { + var iterator = Prototype.K, args = $A(arguments); + if (typeof args.last() == 'function') + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + return iterator(collections.pluck(index)); + }); + }, + + size: function() { + return this.toArray().length; + }, + + inspect: function() { + return '#'; + } +} + +Object.extend(Enumerable, { + map: Enumerable.collect, + find: Enumerable.detect, + select: Enumerable.findAll, + member: Enumerable.include, + entries: Enumerable.toArray +}); +var $A = Array.from = function(iterable) { + if (!iterable) return []; + if (iterable.toArray) { + return iterable.toArray(); + } else { + var results = []; + for (var i = 0, length = iterable.length; i < length; i++) + results.push(iterable[i]); + return results; + } +} + +if (Prototype.Browser.WebKit) { + $A = Array.from = function(iterable) { + if (!iterable) return []; + if (!(typeof iterable == 'function' && iterable == '[object NodeList]') && + iterable.toArray) { + return iterable.toArray(); + } else { + var results = []; + for (var i = 0, length = iterable.length; i < length; i++) + results.push(iterable[i]); + return results; + } + } +} + +Object.extend(Array.prototype, Enumerable); + +if (!Array.prototype._reverse) + Array.prototype._reverse = Array.prototype.reverse; + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0, length = this.length; i < length; i++) + iterator(this[i]); + }, + + clear: function() { + this.length = 0; + return this; + }, + + first: function() { + return this[0]; + }, + + last: function() { + return this[this.length - 1]; + }, + + compact: function() { + return this.select(function(value) { + return value != null; + }); + }, + + flatten: function() { + return this.inject([], function(array, value) { + return array.concat(value && value.constructor == Array ? + value.flatten() : [value]); + }); + }, + + without: function() { + var values = $A(arguments); + return this.select(function(value) { + return !values.include(value); + }); + }, + + indexOf: function(object) { + for (var i = 0, length = this.length; i < length; i++) + if (this[i] == object) return i; + return -1; + }, + + reverse: function(inline) { + return (inline !== false ? this : this.toArray())._reverse(); + }, + + reduce: function() { + return this.length > 1 ? this : this[0]; + }, + + uniq: function(sorted) { + return this.inject([], function(array, value, index) { + if (0 == index || (sorted ? array.last() != value : !array.include(value))) + array.push(value); + return array; + }); + }, + + clone: function() { + return [].concat(this); + }, + + size: function() { + return this.length; + }, + + inspect: function() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + }, + + toJSON: function() { + var results = []; + this.each(function(object) { + var value = Object.toJSON(object); + if (value !== undefined) results.push(value); + }); + return '[' + results.join(', ') + ']'; + } +}); + +Array.prototype.toArray = Array.prototype.clone; + +function $w(string) { + string = string.strip(); + return string ? string.split(/\s+/) : []; +} + +if (Prototype.Browser.Opera){ + Array.prototype.concat = function() { + var array = []; + for (var i = 0, length = this.length; i < length; i++) array.push(this[i]); + for (var i = 0, length = arguments.length; i < length; i++) { + if (arguments[i].constructor == Array) { + for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) + array.push(arguments[i][j]); + } else { + array.push(arguments[i]); + } + } + return array; + } +} +var Hash = function(object) { + if (object instanceof Hash) this.merge(object); + else Object.extend(this, object || {}); +}; + +Object.extend(Hash, { + toQueryString: function(obj) { + var parts = []; + parts.add = arguments.callee.addPair; + + this.prototype._each.call(obj, function(pair) { + if (!pair.key) return; + var value = pair.value; + + if (value && typeof value == 'object') { + if (value.constructor == Array) value.each(function(value) { + parts.add(pair.key, value); + }); + return; + } + parts.add(pair.key, value); + }); + + return parts.join('&'); + }, + + toJSON: function(object) { + var results = []; + this.prototype._each.call(object, function(pair) { + var value = Object.toJSON(pair.value); + if (value !== undefined) results.push(pair.key.toJSON() + ': ' + value); + }); + return '{' + results.join(', ') + '}'; + } +}); + +Hash.toQueryString.addPair = function(key, value, prefix) { + key = encodeURIComponent(key); + if (value === undefined) this.push(key); + else this.push(key + '=' + (value == null ? '' : encodeURIComponent(value))); +} + +Object.extend(Hash.prototype, Enumerable); +Object.extend(Hash.prototype, { + _each: function(iterator) { + for (var key in this) { + var value = this[key]; + if (value && value == Hash.prototype[key]) continue; + + var pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + }, + + keys: function() { + return this.pluck('key'); + }, + + values: function() { + return this.pluck('value'); + }, + + merge: function(hash) { + return $H(hash).inject(this, function(mergedHash, pair) { + mergedHash[pair.key] = pair.value; + return mergedHash; + }); + }, + + remove: function() { + var result; + for(var i = 0, length = arguments.length; i < length; i++) { + var value = this[arguments[i]]; + if (value !== undefined){ + if (result === undefined) result = value; + else { + if (result.constructor != Array) result = [result]; + result.push(value) + } + } + delete this[arguments[i]]; + } + return result; + }, + + toQueryString: function() { + return Hash.toQueryString(this); + }, + + inspect: function() { + return '#'; + }, + + toJSON: function() { + return Hash.toJSON(this); + } +}); + +function $H(object) { + if (object instanceof Hash) return object; + return new Hash(object); +}; + +// Safari iterates over shadowed properties +if (function() { + var i = 0, Test = function(value) { this.key = value }; + Test.prototype.key = 'foo'; + for (var property in new Test('bar')) i++; + return i > 1; +}()) Hash.prototype._each = function(iterator) { + var cache = []; + for (var key in this) { + var value = this[key]; + if ((value && value == Hash.prototype[key]) || cache.include(key)) continue; + cache.push(key); + var pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } +}; +ObjectRange = Class.create(); +Object.extend(ObjectRange.prototype, Enumerable); +Object.extend(ObjectRange.prototype, { + initialize: function(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + }, + + _each: function(iterator) { + var value = this.start; + while (this.include(value)) { + iterator(value); + value = value.succ(); + } + }, + + include: function(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } +}); + +var $R = function(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +} + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +} + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responder) { + if (!this.include(responder)) + this.responders.push(responder); + }, + + unregister: function(responder) { + this.responders = this.responders.without(responder); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (typeof responder[callback] == 'function') { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) {} + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { + Ajax.activeRequestCount++; + }, + onComplete: function() { + Ajax.activeRequestCount--; + } +}); + +Ajax.Base = function() {}; +Ajax.Base.prototype = { + setOptions: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + encoding: 'UTF-8', + parameters: '' + } + Object.extend(this.options, options || {}); + + this.options.method = this.options.method.toLowerCase(); + if (typeof this.options.parameters == 'string') + this.options.parameters = this.options.parameters.toQueryParams(); + } +} + +Ajax.Request = Class.create(); +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Request.prototype = Object.extend(new Ajax.Base(), { + _complete: false, + + initialize: function(url, options) { + this.transport = Ajax.getTransport(); + this.setOptions(options); + this.request(url); + }, + + request: function(url) { + this.url = url; + this.method = this.options.method; + var params = Object.clone(this.options.parameters); + + if (!['get', 'post'].include(this.method)) { + // simulate other verbs over post + params['_method'] = this.method; + this.method = 'post'; + } + + this.parameters = params; + + if (params = Hash.toQueryString(params)) { + // when GET, append parameters to URL + if (this.method == 'get') + this.url += (this.url.include('?') ? '&' : '?') + params; + else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) + params += '&_='; + } + + try { + if (this.options.onCreate) this.options.onCreate(this.transport); + Ajax.Responders.dispatch('onCreate', this, this.transport); + + this.transport.open(this.method.toUpperCase(), this.url, + this.options.asynchronous); + + if (this.options.asynchronous) + setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10); + + this.transport.onreadystatechange = this.onStateChange.bind(this); + this.setRequestHeaders(); + + this.body = this.method == 'post' ? (this.options.postBody || params) : null; + this.transport.send(this.body); + + /* Force Firefox to handle ready state 4 for synchronous requests */ + if (!this.options.asynchronous && this.transport.overrideMimeType) + this.onStateChange(); + + } + catch (e) { + this.dispatchException(e); + } + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1 && !((readyState == 4) && this._complete)) + this.respondToReadyState(this.transport.readyState); + }, + + setRequestHeaders: function() { + var headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Prototype-Version': Prototype.Version, + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }; + + if (this.method == 'post') { + headers['Content-type'] = this.options.contentType + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + /* Force "Connection: close" for older Mozilla browsers to work + * around a bug where XMLHttpRequest sends an incorrect + * Content-length header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType && + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) + headers['Connection'] = 'close'; + } + + // user-defined headers + if (typeof this.options.requestHeaders == 'object') { + var extras = this.options.requestHeaders; + + if (typeof extras.push == 'function') + for (var i = 0, length = extras.length; i < length; i += 2) + headers[extras[i]] = extras[i+1]; + else + $H(extras).each(function(pair) { headers[pair.key] = pair.value }); + } + + for (var name in headers) + this.transport.setRequestHeader(name, headers[name]); + }, + + success: function() { + return !this.transport.status + || (this.transport.status >= 200 && this.transport.status < 300); + }, + + respondToReadyState: function(readyState) { + var state = Ajax.Request.Events[readyState]; + var transport = this.transport, json = this.evalJSON(); + + if (state == 'Complete') { + try { + this._complete = true; + (this.options['on' + this.transport.status] + || this.options['on' + (this.success() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(transport, json); + } catch (e) { + this.dispatchException(e); + } + + var contentType = this.getHeader('Content-type'); + if (contentType && contentType.strip(). + match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i)) + this.evalResponse(); + } + + try { + (this.options['on' + state] || Prototype.emptyFunction)(transport, json); + Ajax.Responders.dispatch('on' + state, this, transport, json); + } catch (e) { + this.dispatchException(e); + } + + if (state == 'Complete') { + // avoid memory leak in MSIE: clean up + this.transport.onreadystatechange = Prototype.emptyFunction; + } + }, + + getHeader: function(name) { + try { + return this.transport.getResponseHeader(name); + } catch (e) { return null } + }, + + evalJSON: function() { + try { + var json = this.getHeader('X-JSON'); + return json ? json.evalJSON() : null; + } catch (e) { return null } + }, + + evalResponse: function() { + try { + return eval((this.transport.responseText || '').unfilterJSON()); + } catch (e) { + this.dispatchException(e); + } + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Updater = Class.create(); + +Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { + initialize: function(container, url, options) { + this.container = { + success: (container.success || container), + failure: (container.failure || (container.success ? null : container)) + } + + this.transport = Ajax.getTransport(); + this.setOptions(options); + + var onComplete = this.options.onComplete || Prototype.emptyFunction; + this.options.onComplete = (function(transport, param) { + this.updateContent(); + onComplete(transport, param); + }).bind(this); + + this.request(url); + }, + + updateContent: function() { + var receiver = this.container[this.success() ? 'success' : 'failure']; + var response = this.transport.responseText; + + if (!this.options.evalScripts) response = response.stripScripts(); + + if (receiver = $(receiver)) { + if (this.options.insertion) + new this.options.insertion(receiver, response); + else + receiver.update(response); + } + + if (this.success()) { + if (this.onComplete) + setTimeout(this.onComplete.bind(this), 10); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(); +Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { + initialize: function(container, url, options) { + this.setOptions(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = {}; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.options.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(request) { + if (this.options.decay) { + this.decay = (request.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = request.responseText; + } + this.timer = setTimeout(this.onTimerEvent.bind(this), + this.decay * this.frequency * 1000); + }, + + onTimerEvent: function() { + this.updater = new Ajax.Updater(this.container, this.url, this.options); + } +}); +function $(element) { + if (arguments.length > 1) { + for (var i = 0, elements = [], length = arguments.length; i < length; i++) + elements.push($(arguments[i])); + return elements; + } + if (typeof element == 'string') + element = document.getElementById(element); + return Element.extend(element); +} + +if (Prototype.BrowserFeatures.XPath) { + document._getElementsByXPath = function(expression, parentElement) { + var results = []; + var query = document.evaluate(expression, $(parentElement) || document, + null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0, length = query.snapshotLength; i < length; i++) + results.push(query.snapshotItem(i)); + return results; + }; + + document.getElementsByClassName = function(className, parentElement) { + var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]"; + return document._getElementsByXPath(q, parentElement); + } + +} else document.getElementsByClassName = function(className, parentElement) { + var children = ($(parentElement) || document.body).getElementsByTagName('*'); + var elements = [], child, pattern = new RegExp("(^|\\s)" + className + "(\\s|$)"); + for (var i = 0, length = children.length; i < length; i++) { + child = children[i]; + var elementClassName = child.className; + if (elementClassName.length == 0) continue; + if (elementClassName == className || elementClassName.match(pattern)) + elements.push(Element.extend(child)); + } + return elements; +}; + +/*--------------------------------------------------------------------------*/ + +if (!window.Element) var Element = {}; + +Element.extend = function(element) { + var F = Prototype.BrowserFeatures; + if (!element || !element.tagName || element.nodeType == 3 || + element._extended || F.SpecificElementExtensions || element == window) + return element; + + var methods = {}, tagName = element.tagName, cache = Element.extend.cache, + T = Element.Methods.ByTag; + + // extend methods for all tags (Safari doesn't need this) + if (!F.ElementExtensions) { + Object.extend(methods, Element.Methods), + Object.extend(methods, Element.Methods.Simulated); + } + + // extend methods for specific tags + if (T[tagName]) Object.extend(methods, T[tagName]); + + for (var property in methods) { + var value = methods[property]; + if (typeof value == 'function' && !(property in element)) + element[property] = cache.findOrStore(value); + } + + element._extended = Prototype.emptyFunction; + return element; +}; + +Element.extend.cache = { + findOrStore: function(value) { + return this[value] = this[value] || function() { + return value.apply(null, [this].concat($A(arguments))); + } + } +}; + +Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function(element) { + element = $(element); + Element[Element.visible(element) ? 'hide' : 'show'](element); + return element; + }, + + hide: function(element) { + $(element).style.display = 'none'; + return element; + }, + + show: function(element) { + $(element).style.display = ''; + return element; + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + return element; + }, + + update: function(element, html) { + html = typeof html == 'undefined' ? '' : html.toString(); + $(element).innerHTML = html.stripScripts(); + setTimeout(function() {html.evalScripts()}, 10); + return element; + }, + + replace: function(element, html) { + element = $(element); + html = typeof html == 'undefined' ? '' : html.toString(); + if (element.outerHTML) { + element.outerHTML = html.stripScripts(); + } else { + var range = element.ownerDocument.createRange(); + range.selectNodeContents(element); + element.parentNode.replaceChild( + range.createContextualFragment(html.stripScripts()), element); + } + setTimeout(function() {html.evalScripts()}, 10); + return element; + }, + + inspect: function(element) { + element = $(element); + var result = '<' + element.tagName.toLowerCase(); + $H({'id': 'id', 'className': 'class'}).each(function(pair) { + var property = pair.first(), attribute = pair.last(); + var value = (element[property] || '').toString(); + if (value) result += ' ' + attribute + '=' + value.inspect(true); + }); + return result + '>'; + }, + + recursivelyCollect: function(element, property) { + element = $(element); + var elements = []; + while (element = element[property]) + if (element.nodeType == 1) + elements.push(Element.extend(element)); + return elements; + }, + + ancestors: function(element) { + return $(element).recursivelyCollect('parentNode'); + }, + + descendants: function(element) { + return $A($(element).getElementsByTagName('*')).each(Element.extend); + }, + + firstDescendant: function(element) { + element = $(element).firstChild; + while (element && element.nodeType != 1) element = element.nextSibling; + return $(element); + }, + + immediateDescendants: function(element) { + if (!(element = $(element).firstChild)) return []; + while (element && element.nodeType != 1) element = element.nextSibling; + if (element) return [element].concat($(element).nextSiblings()); + return []; + }, + + previousSiblings: function(element) { + return $(element).recursivelyCollect('previousSibling'); + }, + + nextSiblings: function(element) { + return $(element).recursivelyCollect('nextSibling'); + }, + + siblings: function(element) { + element = $(element); + return element.previousSiblings().reverse().concat(element.nextSiblings()); + }, + + match: function(element, selector) { + if (typeof selector == 'string') + selector = new Selector(selector); + return selector.match($(element)); + }, + + up: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(element.parentNode); + var ancestors = element.ancestors(); + return expression ? Selector.findElement(ancestors, expression, index) : + ancestors[index || 0]; + }, + + down: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return element.firstDescendant(); + var descendants = element.descendants(); + return expression ? Selector.findElement(descendants, expression, index) : + descendants[index || 0]; + }, + + previous: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); + var previousSiblings = element.previousSiblings(); + return expression ? Selector.findElement(previousSiblings, expression, index) : + previousSiblings[index || 0]; + }, + + next: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); + var nextSiblings = element.nextSiblings(); + return expression ? Selector.findElement(nextSiblings, expression, index) : + nextSiblings[index || 0]; + }, + + getElementsBySelector: function() { + var args = $A(arguments), element = $(args.shift()); + return Selector.findChildElements(element, args); + }, + + getElementsByClassName: function(element, className) { + return document.getElementsByClassName(className, element); + }, + + readAttribute: function(element, name) { + element = $(element); + if (Prototype.Browser.IE) { + if (!element.attributes) return null; + var t = Element._attributeTranslations; + if (t.values[name]) return t.values[name](element, name); + if (t.names[name]) name = t.names[name]; + var attribute = element.attributes[name]; + return attribute ? attribute.nodeValue : null; + } + return element.getAttribute(name); + }, + + getHeight: function(element) { + return $(element).getDimensions().height; + }, + + getWidth: function(element) { + return $(element).getDimensions().width; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + var elementClassName = element.className; + if (elementClassName.length == 0) return false; + if (elementClassName == className || + elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) + return true; + return false; + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element).add(className); + return element; + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element).remove(className); + return element; + }, + + toggleClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className); + return element; + }, + + observe: function() { + Event.observe.apply(Event, arguments); + return $A(arguments).first(); + }, + + stopObserving: function() { + Event.stopObserving.apply(Event, arguments); + return $A(arguments).first(); + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + element = $(element); + var node = element.firstChild; + while (node) { + var nextNode = node.nextSibling; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + element.removeChild(node); + node = nextNode; + } + return element; + }, + + empty: function(element) { + return $(element).innerHTML.blank(); + }, + + descendantOf: function(element, ancestor) { + element = $(element), ancestor = $(ancestor); + while (element = element.parentNode) + if (element == ancestor) return true; + return false; + }, + + scrollTo: function(element) { + element = $(element); + var pos = Position.cumulativeOffset(element); + window.scrollTo(pos[0], pos[1]); + return element; + }, + + getStyle: function(element, style) { + element = $(element); + style = style == 'float' ? 'cssFloat' : style.camelize(); + var value = element.style[style]; + if (!value) { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css[style] : null; + } + if (style == 'opacity') return value ? parseFloat(value) : 1.0; + return value == 'auto' ? null : value; + }, + + getOpacity: function(element) { + return $(element).getStyle('opacity'); + }, + + setStyle: function(element, styles, camelized) { + element = $(element); + var elementStyle = element.style; + + for (var property in styles) + if (property == 'opacity') element.setOpacity(styles[property]) + else + elementStyle[(property == 'float' || property == 'cssFloat') ? + (elementStyle.styleFloat === undefined ? 'cssFloat' : 'styleFloat') : + (camelized ? property : property.camelize())] = styles[property]; + + return element; + }, + + setOpacity: function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + return element; + }, + + getDimensions: function(element) { + element = $(element); + var display = $(element).getStyle('display'); + if (display != 'none' && display != null) // Safari bug + return {width: element.offsetWidth, height: element.offsetHeight}; + + // All *Width and *Height properties give 0 on elements with display none, + // so enable the element temporarily + var els = element.style; + var originalVisibility = els.visibility; + var originalPosition = els.position; + var originalDisplay = els.display; + els.visibility = 'hidden'; + els.position = 'absolute'; + els.display = 'block'; + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + els.display = originalDisplay; + els.position = originalPosition; + els.visibility = originalVisibility; + return {width: originalWidth, height: originalHeight}; + }, + + makePositioned: function(element) { + element = $(element); + var pos = Element.getStyle(element, 'position'); + if (pos == 'static' || !pos) { + element._madePositioned = true; + element.style.position = 'relative'; + // Opera returns the offset relative to the positioning context, when an + // element is position relative but top and left have not been defined + if (window.opera) { + element.style.top = 0; + element.style.left = 0; + } + } + return element; + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + return element; + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return element; + element._overflow = element.style.overflow || 'auto'; + if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') + element.style.overflow = 'hidden'; + return element; + }, + + undoClipping: function(element) { + element = $(element); + if (!element._overflow) return element; + element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; + element._overflow = null; + return element; + } +}; + +Object.extend(Element.Methods, { + childOf: Element.Methods.descendantOf, + childElements: Element.Methods.immediateDescendants +}); + +if (Prototype.Browser.Opera) { + Element.Methods._getStyle = Element.Methods.getStyle; + Element.Methods.getStyle = function(element, style) { + switch(style) { + case 'left': + case 'top': + case 'right': + case 'bottom': + if (Element._getStyle(element, 'position') == 'static') return null; + default: return Element._getStyle(element, style); + } + }; +} +else if (Prototype.Browser.IE) { + Element.Methods.getStyle = function(element, style) { + element = $(element); + style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); + var value = element.style[style]; + if (!value && element.currentStyle) value = element.currentStyle[style]; + + if (style == 'opacity') { + if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if (value[1]) return parseFloat(value[1]) / 100; + return 1.0; + } + + if (value == 'auto') { + if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) + return element['offset'+style.capitalize()] + 'px'; + return null; + } + return value; + }; + + Element.Methods.setOpacity = function(element, value) { + element = $(element); + var filter = element.getStyle('filter'), style = element.style; + if (value == 1 || value === '') { + style.filter = filter.replace(/alpha\([^\)]*\)/gi,''); + return element; + } else if (value < 0.00001) value = 0; + style.filter = filter.replace(/alpha\([^\)]*\)/gi, '') + + 'alpha(opacity=' + (value * 100) + ')'; + return element; + }; + + // IE is missing .innerHTML support for TABLE-related elements + Element.Methods.update = function(element, html) { + element = $(element); + html = typeof html == 'undefined' ? '' : html.toString(); + var tagName = element.tagName.toUpperCase(); + if (['THEAD','TBODY','TR','TD'].include(tagName)) { + var div = document.createElement('div'); + switch (tagName) { + case 'THEAD': + case 'TBODY': + div.innerHTML = '' + html.stripScripts() + '
    '; + depth = 2; + break; + case 'TR': + div.innerHTML = '' + html.stripScripts() + '
    '; + depth = 3; + break; + case 'TD': + div.innerHTML = '
    ' + html.stripScripts() + '
    '; + depth = 4; + } + $A(element.childNodes).each(function(node) { element.removeChild(node) }); + depth.times(function() { div = div.firstChild }); + $A(div.childNodes).each(function(node) { element.appendChild(node) }); + } else { + element.innerHTML = html.stripScripts(); + } + setTimeout(function() { html.evalScripts() }, 10); + return element; + } +} +else if (Prototype.Browser.Gecko) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1) ? 0.999999 : + (value === '') ? '' : (value < 0.00001) ? 0 : value; + return element; + }; +} + +Element._attributeTranslations = { + names: { + colspan: "colSpan", + rowspan: "rowSpan", + valign: "vAlign", + datetime: "dateTime", + accesskey: "accessKey", + tabindex: "tabIndex", + enctype: "encType", + maxlength: "maxLength", + readonly: "readOnly", + longdesc: "longDesc" + }, + values: { + _getAttr: function(element, attribute) { + return element.getAttribute(attribute, 2); + }, + _flag: function(element, attribute) { + return $(element).hasAttribute(attribute) ? attribute : null; + }, + style: function(element) { + return element.style.cssText.toLowerCase(); + }, + title: function(element) { + var node = element.getAttributeNode('title'); + return node.specified ? node.nodeValue : null; + } + } +}; + +(function() { + Object.extend(this, { + href: this._getAttr, + src: this._getAttr, + type: this._getAttr, + disabled: this._flag, + checked: this._flag, + readonly: this._flag, + multiple: this._flag + }); +}).call(Element._attributeTranslations.values); + +Element.Methods.Simulated = { + hasAttribute: function(element, attribute) { + var t = Element._attributeTranslations, node; + attribute = t.names[attribute] || attribute; + node = $(element).getAttributeNode(attribute); + return node && node.specified; + } +}; + +Element.Methods.ByTag = {}; + +Object.extend(Element, Element.Methods); + +if (!Prototype.BrowserFeatures.ElementExtensions && + document.createElement('div').__proto__) { + window.HTMLElement = {}; + window.HTMLElement.prototype = document.createElement('div').__proto__; + Prototype.BrowserFeatures.ElementExtensions = true; +} + +Element.hasAttribute = function(element, attribute) { + if (element.hasAttribute) return element.hasAttribute(attribute); + return Element.Methods.Simulated.hasAttribute(element, attribute); +}; + +Element.addMethods = function(methods) { + var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; + + if (!methods) { + Object.extend(Form, Form.Methods); + Object.extend(Form.Element, Form.Element.Methods); + Object.extend(Element.Methods.ByTag, { + "FORM": Object.clone(Form.Methods), + "INPUT": Object.clone(Form.Element.Methods), + "SELECT": Object.clone(Form.Element.Methods), + "TEXTAREA": Object.clone(Form.Element.Methods) + }); + } + + if (arguments.length == 2) { + var tagName = methods; + methods = arguments[1]; + } + + if (!tagName) Object.extend(Element.Methods, methods || {}); + else { + if (tagName.constructor == Array) tagName.each(extend); + else extend(tagName); + } + + function extend(tagName) { + tagName = tagName.toUpperCase(); + if (!Element.Methods.ByTag[tagName]) + Element.Methods.ByTag[tagName] = {}; + Object.extend(Element.Methods.ByTag[tagName], methods); + } + + function copy(methods, destination, onlyIfAbsent) { + onlyIfAbsent = onlyIfAbsent || false; + var cache = Element.extend.cache; + for (var property in methods) { + var value = methods[property]; + if (!onlyIfAbsent || !(property in destination)) + destination[property] = cache.findOrStore(value); + } + } + + function findDOMClass(tagName) { + var klass; + var trans = { + "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", + "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", + "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", + "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", + "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": + "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": + "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": + "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": + "FrameSet", "IFRAME": "IFrame" + }; + if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName.capitalize() + 'Element'; + if (window[klass]) return window[klass]; + + window[klass] = {}; + window[klass].prototype = document.createElement(tagName).__proto__; + return window[klass]; + } + + if (F.ElementExtensions) { + copy(Element.Methods, HTMLElement.prototype); + copy(Element.Methods.Simulated, HTMLElement.prototype, true); + } + + if (F.SpecificElementExtensions) { + for (var tag in Element.Methods.ByTag) { + var klass = findDOMClass(tag); + if (typeof klass == "undefined") continue; + copy(T[tag], klass.prototype); + } + } + + Object.extend(Element, Element.Methods); + delete Element.ByTag; +}; + +var Toggle = { display: Element.toggle }; + +/*--------------------------------------------------------------------------*/ + +Abstract.Insertion = function(adjacency) { + this.adjacency = adjacency; +} + +Abstract.Insertion.prototype = { + initialize: function(element, content) { + this.element = $(element); + this.content = content.stripScripts(); + + if (this.adjacency && this.element.insertAdjacentHTML) { + try { + this.element.insertAdjacentHTML(this.adjacency, this.content); + } catch (e) { + var tagName = this.element.tagName.toUpperCase(); + if (['TBODY', 'TR'].include(tagName)) { + this.insertContent(this.contentFromAnonymousTable()); + } else { + throw e; + } + } + } else { + this.range = this.element.ownerDocument.createRange(); + if (this.initializeRange) this.initializeRange(); + this.insertContent([this.range.createContextualFragment(this.content)]); + } + + setTimeout(function() {content.evalScripts()}, 10); + }, + + contentFromAnonymousTable: function() { + var div = document.createElement('div'); + div.innerHTML = '' + this.content + '
    '; + return $A(div.childNodes[0].childNodes[0].childNodes); + } +} + +var Insertion = new Object(); + +Insertion.Before = Class.create(); +Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { + initializeRange: function() { + this.range.setStartBefore(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, this.element); + }).bind(this)); + } +}); + +Insertion.Top = Class.create(); +Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(true); + }, + + insertContent: function(fragments) { + fragments.reverse(false).each((function(fragment) { + this.element.insertBefore(fragment, this.element.firstChild); + }).bind(this)); + } +}); + +Insertion.Bottom = Class.create(); +Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.appendChild(fragment); + }).bind(this)); + } +}); + +Insertion.After = Class.create(); +Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { + initializeRange: function() { + this.range.setStartAfter(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, + this.element.nextSibling); + }).bind(this)); + } +}); + +/*--------------------------------------------------------------------------*/ + +Element.ClassNames = Class.create(); +Element.ClassNames.prototype = { + initialize: function(element) { + this.element = $(element); + }, + + _each: function(iterator) { + this.element.className.split(/\s+/).select(function(name) { + return name.length > 0; + })._each(iterator); + }, + + set: function(className) { + this.element.className = className; + }, + + add: function(classNameToAdd) { + if (this.include(classNameToAdd)) return; + this.set($A(this).concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; + this.set($A(this).without(classNameToRemove).join(' ')); + }, + + toString: function() { + return $A(this).join(' '); + } +}; + +Object.extend(Element.ClassNames.prototype, Enumerable); +/* Portions of the Selector class are derived from Jack Slocum’s DomQuery, + * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style + * license. Please see http://www.yui-ext.com/ for more information. */ + +var Selector = Class.create(); + +Selector.prototype = { + initialize: function(expression) { + this.expression = expression.strip(); + this.compileMatcher(); + }, + + compileMatcher: function() { + // Selectors with namespaced attributes can't use the XPath version + if (Prototype.BrowserFeatures.XPath && !(/\[[\w-]*?:/).test(this.expression)) + return this.compileXPathMatcher(); + + var e = this.expression, ps = Selector.patterns, h = Selector.handlers, + c = Selector.criteria, le, p, m; + + if (Selector._cache[e]) { + this.matcher = Selector._cache[e]; return; + } + this.matcher = ["this.matcher = function(root) {", + "var r = root, h = Selector.handlers, c = false, n;"]; + + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i in ps) { + p = ps[i]; + if (m = e.match(p)) { + this.matcher.push(typeof c[i] == 'function' ? c[i](m) : + new Template(c[i]).evaluate(m)); + e = e.replace(m[0], ''); + break; + } + } + } + + this.matcher.push("return h.unique(n);\n}"); + eval(this.matcher.join('\n')); + Selector._cache[this.expression] = this.matcher; + }, + + compileXPathMatcher: function() { + var e = this.expression, ps = Selector.patterns, + x = Selector.xpath, le, m; + + if (Selector._cache[e]) { + this.xpath = Selector._cache[e]; return; + } + + this.matcher = ['.//*']; + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i in ps) { + if (m = e.match(ps[i])) { + this.matcher.push(typeof x[i] == 'function' ? x[i](m) : + new Template(x[i]).evaluate(m)); + e = e.replace(m[0], ''); + break; + } + } + } + + this.xpath = this.matcher.join(''); + Selector._cache[this.expression] = this.xpath; + }, + + findElements: function(root) { + root = root || document; + if (this.xpath) return document._getElementsByXPath(this.xpath, root); + return this.matcher(root); + }, + + match: function(element) { + return this.findElements(document).include(element); + }, + + toString: function() { + return this.expression; + }, + + inspect: function() { + return "#"; + } +}; + +Object.extend(Selector, { + _cache: {}, + + xpath: { + descendant: "//*", + child: "/*", + adjacent: "/following-sibling::*[1]", + laterSibling: '/following-sibling::*', + tagName: function(m) { + if (m[1] == '*') return ''; + return "[local-name()='" + m[1].toLowerCase() + + "' or local-name()='" + m[1].toUpperCase() + "']"; + }, + className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", + id: "[@id='#{1}']", + attrPresence: "[@#{1}]", + attr: function(m) { + m[3] = m[5] || m[6]; + return new Template(Selector.xpath.operators[m[2]]).evaluate(m); + }, + pseudo: function(m) { + var h = Selector.xpath.pseudos[m[1]]; + if (!h) return ''; + if (typeof h === 'function') return h(m); + return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); + }, + operators: { + '=': "[@#{1}='#{3}']", + '!=': "[@#{1}!='#{3}']", + '^=': "[starts-with(@#{1}, '#{3}')]", + '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", + '*=': "[contains(@#{1}, '#{3}')]", + '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", + '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" + }, + pseudos: { + 'first-child': '[not(preceding-sibling::*)]', + 'last-child': '[not(following-sibling::*)]', + 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', + 'empty': "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]", + 'checked': "[@checked]", + 'disabled': "[@disabled]", + 'enabled': "[not(@disabled)]", + 'not': function(m) { + var e = m[6], p = Selector.patterns, + x = Selector.xpath, le, m, v; + + var exclusion = []; + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i in p) { + if (m = e.match(p[i])) { + v = typeof x[i] == 'function' ? x[i](m) : new Template(x[i]).evaluate(m); + exclusion.push("(" + v.substring(1, v.length - 1) + ")"); + e = e.replace(m[0], ''); + break; + } + } + } + return "[not(" + exclusion.join(" and ") + ")]"; + }, + 'nth-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); + }, + 'nth-last-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); + }, + 'nth-of-type': function(m) { + return Selector.xpath.pseudos.nth("position() ", m); + }, + 'nth-last-of-type': function(m) { + return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m); + }, + 'first-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m); + }, + 'last-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m); + }, + 'only-of-type': function(m) { + var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); + }, + nth: function(fragment, m) { + var mm, formula = m[6], predicate; + if (formula == 'even') formula = '2n+0'; + if (formula == 'odd') formula = '2n+1'; + if (mm = formula.match(/^(\d+)$/)) // digit only + return '[' + fragment + "= " + mm[1] + ']'; + if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b + if (mm[1] == "-") mm[1] = -1; + var a = mm[1] ? Number(mm[1]) : 1; + var b = mm[2] ? Number(mm[2]) : 0; + predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " + + "((#{fragment} - #{b}) div #{a} >= 0)]"; + return new Template(predicate).evaluate({ + fragment: fragment, a: a, b: b }); + } + } + } + }, + + criteria: { + tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', + className: 'n = h.className(n, r, "#{1}", c); c = false;', + id: 'n = h.id(n, r, "#{1}", c); c = false;', + attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;', + attr: function(m) { + m[3] = (m[5] || m[6]); + return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m); + }, + pseudo: function(m) { + if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); + return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); + }, + descendant: 'c = "descendant";', + child: 'c = "child";', + adjacent: 'c = "adjacent";', + laterSibling: 'c = "laterSibling";' + }, + + patterns: { + // combinators must be listed first + // (and descendant needs to be last combinator) + laterSibling: /^\s*~\s*/, + child: /^\s*>\s*/, + adjacent: /^\s*\+\s*/, + descendant: /^\s/, + + // selectors follow + tagName: /^\s*(\*|[\w\-]+)(\b|$)?/, + id: /^#([\w\-\*]+)(\b|$)/, + className: /^\.([\w\-\*]+)(\b|$)/, + pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|\s|(?=:))/, + attrPresence: /^\[([\w]+)\]/, + attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\]]*?)\4|([^'"][^\]]*?)))?\]/ + }, + + handlers: { + // UTILITY FUNCTIONS + // joins two collections + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + a.push(node); + return a; + }, + + // marks an array of nodes for counting + mark: function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node._counted = true; + return nodes; + }, + + unmark: function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node._counted = undefined; + return nodes; + }, + + // mark each child node with its position (for nth calls) + // "ofType" flag indicates whether we're indexing for nth-of-type + // rather than nth-child + index: function(parentNode, reverse, ofType) { + parentNode._counted = true; + if (reverse) { + for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { + node = nodes[i]; + if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++; + } + } else { + for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) + if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++; + } + }, + + // filters out duplicates and extends all nodes + unique: function(nodes) { + if (nodes.length == 0) return nodes; + var results = [], n; + for (var i = 0, l = nodes.length; i < l; i++) + if (!(n = nodes[i])._counted) { + n._counted = true; + results.push(Element.extend(n)); + } + return Selector.handlers.unmark(results); + }, + + // COMBINATOR FUNCTIONS + descendant: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName('*')); + return results; + }, + + child: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) { + for (var j = 0, children = [], child; child = node.childNodes[j]; j++) + if (child.nodeType == 1 && child.tagName != '!') results.push(child); + } + return results; + }, + + adjacent: function(nodes) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + var next = this.nextElementSibling(node); + if (next) results.push(next); + } + return results; + }, + + laterSibling: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, Element.nextSiblings(node)); + return results; + }, + + nextElementSibling: function(node) { + while (node = node.nextSibling) + if (node.nodeType == 1) return node; + return null; + }, + + previousElementSibling: function(node) { + while (node = node.previousSibling) + if (node.nodeType == 1) return node; + return null; + }, + + // TOKEN FUNCTIONS + tagName: function(nodes, root, tagName, combinator) { + tagName = tagName.toUpperCase(); + var results = [], h = Selector.handlers; + if (nodes) { + if (combinator) { + // fastlane for ordinary descendant combinators + if (combinator == "descendant") { + for (var i = 0, node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName(tagName)); + return results; + } else nodes = this[combinator](nodes); + if (tagName == "*") return nodes; + } + for (var i = 0, node; node = nodes[i]; i++) + if (node.tagName.toUpperCase() == tagName) results.push(node); + return results; + } else return root.getElementsByTagName(tagName); + }, + + id: function(nodes, root, id, combinator) { + var targetNode = $(id), h = Selector.handlers; + if (!nodes && root == document) return targetNode ? [targetNode] : []; + if (nodes) { + if (combinator) { + if (combinator == 'child') { + for (var i = 0, node; node = nodes[i]; i++) + if (targetNode.parentNode == node) return [targetNode]; + } else if (combinator == 'descendant') { + for (var i = 0, node; node = nodes[i]; i++) + if (Element.descendantOf(targetNode, node)) return [targetNode]; + } else if (combinator == 'adjacent') { + for (var i = 0, node; node = nodes[i]; i++) + if (Selector.handlers.previousElementSibling(targetNode) == node) + return [targetNode]; + } else nodes = h[combinator](nodes); + } + for (var i = 0, node; node = nodes[i]; i++) + if (node == targetNode) return [targetNode]; + return []; + } + return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; + }, + + className: function(nodes, root, className, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + return Selector.handlers.byClassName(nodes, root, className); + }, + + byClassName: function(nodes, root, className) { + if (!nodes) nodes = Selector.handlers.descendant([root]); + var needle = ' ' + className + ' '; + for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { + nodeClassName = node.className; + if (nodeClassName.length == 0) continue; + if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) + results.push(node); + } + return results; + }, + + attrPresence: function(nodes, root, attr) { + var results = []; + for (var i = 0, node; node = nodes[i]; i++) + if (Element.hasAttribute(node, attr)) results.push(node); + return results; + }, + + attr: function(nodes, root, attr, value, operator) { + if (!nodes) nodes = root.getElementsByTagName("*"); + var handler = Selector.operators[operator], results = []; + for (var i = 0, node; node = nodes[i]; i++) { + var nodeValue = Element.readAttribute(node, attr); + if (nodeValue === null) continue; + if (handler(nodeValue, value)) results.push(node); + } + return results; + }, + + pseudo: function(nodes, name, value, root, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + if (!nodes) nodes = root.getElementsByTagName("*"); + return Selector.pseudos[name](nodes, value, root); + } + }, + + pseudos: { + 'first-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Selector.handlers.previousElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'last-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Selector.handlers.nextElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'only-child': function(nodes, value, root) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) + results.push(node); + return results; + }, + 'nth-child': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root); + }, + 'nth-last-child': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, true); + }, + 'nth-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, false, true); + }, + 'nth-last-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, true, true); + }, + 'first-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, "1", root, false, true); + }, + 'last-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, "1", root, true, true); + }, + 'only-of-type': function(nodes, formula, root) { + var p = Selector.pseudos; + return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); + }, + + // handles the an+b logic + getIndices: function(a, b, total) { + if (a == 0) return b > 0 ? [b] : []; + return $R(1, total).inject([], function(memo, i) { + if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); + return memo; + }); + }, + + // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type + nth: function(nodes, formula, root, reverse, ofType) { + if (nodes.length == 0) return []; + if (formula == 'even') formula = '2n+0'; + if (formula == 'odd') formula = '2n+1'; + var h = Selector.handlers, results = [], indexed = [], m; + h.mark(nodes); + for (var i = 0, node; node = nodes[i]; i++) { + if (!node.parentNode._counted) { + h.index(node.parentNode, reverse, ofType); + indexed.push(node.parentNode); + } + } + if (formula.match(/^\d+$/)) { // just a number + formula = Number(formula); + for (var i = 0, node; node = nodes[i]; i++) + if (node.nodeIndex == formula) results.push(node); + } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b + if (m[1] == "-") m[1] = -1; + var a = m[1] ? Number(m[1]) : 1; + var b = m[2] ? Number(m[2]) : 0; + var indices = Selector.pseudos.getIndices(a, b, nodes.length); + for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { + for (var j = 0; j < l; j++) + if (node.nodeIndex == indices[j]) results.push(node); + } + } + h.unmark(nodes); + h.unmark(indexed); + return results; + }, + + 'empty': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + // IE treats comments as element nodes + if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue; + results.push(node); + } + return results; + }, + + 'not': function(nodes, selector, root) { + var h = Selector.handlers, selectorType, m; + var exclusions = new Selector(selector).findElements(root); + h.mark(exclusions); + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node._counted) results.push(node); + h.unmark(exclusions); + return results; + }, + + 'enabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node.disabled) results.push(node); + return results; + }, + + 'disabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.disabled) results.push(node); + return results; + }, + + 'checked': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.checked) results.push(node); + return results; + } + }, + + operators: { + '=': function(nv, v) { return nv == v; }, + '!=': function(nv, v) { return nv != v; }, + '^=': function(nv, v) { return nv.startsWith(v); }, + '$=': function(nv, v) { return nv.endsWith(v); }, + '*=': function(nv, v) { return nv.include(v); }, + '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, + '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); } + }, + + matchElements: function(elements, expression) { + var matches = new Selector(expression).findElements(), h = Selector.handlers; + h.mark(matches); + for (var i = 0, results = [], element; element = elements[i]; i++) + if (element._counted) results.push(element); + h.unmark(matches); + return results; + }, + + findElement: function(elements, expression, index) { + if (typeof expression == 'number') { + index = expression; expression = false; + } + return Selector.matchElements(elements, expression || '*')[index || 0]; + }, + + findChildElements: function(element, expressions) { + var exprs = expressions.join(','), expressions = []; + exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { + expressions.push(m[1].strip()); + }); + var results = [], h = Selector.handlers; + for (var i = 0, l = expressions.length, selector; i < l; i++) { + selector = new Selector(expressions[i].strip()); + h.concat(results, selector.findElements(element)); + } + return (l > 1) ? h.unique(results) : results; + } +}); + +function $$() { + return Selector.findChildElements(document, $A(arguments)); +} +var Form = { + reset: function(form) { + $(form).reset(); + return form; + }, + + serializeElements: function(elements, getHash) { + var data = elements.inject({}, function(result, element) { + if (!element.disabled && element.name) { + var key = element.name, value = $(element).getValue(); + if (value != null) { + if (key in result) { + if (result[key].constructor != Array) result[key] = [result[key]]; + result[key].push(value); + } + else result[key] = value; + } + } + return result; + }); + + return getHash ? data : Hash.toQueryString(data); + } +}; + +Form.Methods = { + serialize: function(form, getHash) { + return Form.serializeElements(Form.getElements(form), getHash); + }, + + getElements: function(form) { + return $A($(form).getElementsByTagName('*')).inject([], + function(elements, child) { + if (Form.Element.Serializers[child.tagName.toLowerCase()]) + elements.push(Element.extend(child)); + return elements; + } + ); + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) return $A(inputs).map(Element.extend); + + for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || (name && input.name != name)) + continue; + matchingInputs.push(Element.extend(input)); + } + + return matchingInputs; + }, + + disable: function(form) { + form = $(form); + Form.getElements(form).invoke('disable'); + return form; + }, + + enable: function(form) { + form = $(form); + Form.getElements(form).invoke('enable'); + return form; + }, + + findFirstElement: function(form) { + return $(form).getElements().find(function(element) { + return element.type != 'hidden' && !element.disabled && + ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + }); + }, + + focusFirstElement: function(form) { + form = $(form); + form.findFirstElement().activate(); + return form; + }, + + request: function(form, options) { + form = $(form), options = Object.clone(options || {}); + + var params = options.parameters; + options.parameters = form.serialize(true); + + if (params) { + if (typeof params == 'string') params = params.toQueryParams(); + Object.extend(options.parameters, params); + } + + if (form.hasAttribute('method') && !options.method) + options.method = form.method; + + return new Ajax.Request(form.readAttribute('action'), options); + } +} + +/*--------------------------------------------------------------------------*/ + +Form.Element = { + focus: function(element) { + $(element).focus(); + return element; + }, + + select: function(element) { + $(element).select(); + return element; + } +} + +Form.Element.Methods = { + serialize: function(element) { + element = $(element); + if (!element.disabled && element.name) { + var value = element.getValue(); + if (value != undefined) { + var pair = {}; + pair[element.name] = value; + return Hash.toQueryString(pair); + } + } + return ''; + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + return Form.Element.Serializers[method](element); + }, + + clear: function(element) { + $(element).value = ''; + return element; + }, + + present: function(element) { + return $(element).value != ''; + }, + + activate: function(element) { + element = $(element); + try { + element.focus(); + if (element.select && (element.tagName.toLowerCase() != 'input' || + !['button', 'reset', 'submit'].include(element.type))) + element.select(); + } catch (e) {} + return element; + }, + + disable: function(element) { + element = $(element); + element.blur(); + element.disabled = true; + return element; + }, + + enable: function(element) { + element = $(element); + element.disabled = false; + return element; + } +} + +/*--------------------------------------------------------------------------*/ + +var Field = Form.Element; +var $F = Form.Element.Methods.getValue; + +/*--------------------------------------------------------------------------*/ + +Form.Element.Serializers = { + input: function(element) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element); + default: + return Form.Element.Serializers.textarea(element); + } + }, + + inputSelector: function(element) { + return element.checked ? element.value : null; + }, + + textarea: function(element) { + return element.value; + }, + + select: function(element) { + return this[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + }, + + selectOne: function(element) { + var index = element.selectedIndex; + return index >= 0 ? this.optionValue(element.options[index]) : null; + }, + + selectMany: function(element) { + var values, length = element.length; + if (!length) return null; + + for (var i = 0, values = []; i < length; i++) { + var opt = element.options[i]; + if (opt.selected) values.push(this.optionValue(opt)); + } + return values; + }, + + optionValue: function(opt) { + // extend element because hasAttribute may not be native + return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; + } +} + +/*--------------------------------------------------------------------------*/ + +Abstract.TimedObserver = function() {} +Abstract.TimedObserver.prototype = { + initialize: function(element, frequency, callback) { + this.frequency = frequency; + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + this.registerCallback(); + }, + + registerCallback: function() { + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + var value = this.getValue(); + var changed = ('string' == typeof this.lastValue && 'string' == typeof value + ? this.lastValue != value : String(this.lastValue) != String(value)); + if (changed) { + this.callback(this.element, value); + this.lastValue = value; + } + } +} + +Form.Element.Observer = Class.create(); +Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(); +Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = function() {} +Abstract.EventObserver.prototype = { + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + Form.getElements(this.element).each(this.registerCallback.bind(this)); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + default: + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +} + +Form.Element.EventObserver = Class.create(); +Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(); +Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); +if (!window.Event) { + var Event = new Object(); +} + +Object.extend(Event, { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + KEY_HOME: 36, + KEY_END: 35, + KEY_PAGEUP: 33, + KEY_PAGEDOWN: 34, + + element: function(event) { + return $(event.target || event.srcElement); + }, + + isLeftClick: function(event) { + return (((event.which) && (event.which == 1)) || + ((event.button) && (event.button == 1))); + }, + + pointerX: function(event) { + return event.pageX || (event.clientX + + (document.documentElement.scrollLeft || document.body.scrollLeft)); + }, + + pointerY: function(event) { + return event.pageY || (event.clientY + + (document.documentElement.scrollTop || document.body.scrollTop)); + }, + + stop: function(event) { + if (event.preventDefault) { + event.preventDefault(); + event.stopPropagation(); + } else { + event.returnValue = false; + event.cancelBubble = true; + } + }, + + // find the first node with the given tagName, starting from the + // node the event was triggered on; traverses the DOM upwards + findElement: function(event, tagName) { + var element = Event.element(event); + while (element.parentNode && (!element.tagName || + (element.tagName.toUpperCase() != tagName.toUpperCase()))) + element = element.parentNode; + return element; + }, + + observers: false, + + _observeAndCache: function(element, name, observer, useCapture) { + if (!this.observers) this.observers = []; + if (element.addEventListener) { + this.observers.push([element, name, observer, useCapture]); + element.addEventListener(name, observer, useCapture); + } else if (element.attachEvent) { + this.observers.push([element, name, observer, useCapture]); + element.attachEvent('on' + name, observer); + } + }, + + unloadCache: function() { + if (!Event.observers) return; + for (var i = 0, length = Event.observers.length; i < length; i++) { + Event.stopObserving.apply(this, Event.observers[i]); + Event.observers[i][0] = null; + } + Event.observers = false; + }, + + observe: function(element, name, observer, useCapture) { + element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (Prototype.Browser.WebKit || element.attachEvent)) + name = 'keydown'; + + Event._observeAndCache(element, name, observer, useCapture); + }, + + stopObserving: function(element, name, observer, useCapture) { + element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (Prototype.Browser.WebKit || element.attachEvent)) + name = 'keydown'; + + if (element.removeEventListener) { + element.removeEventListener(name, observer, useCapture); + } else if (element.detachEvent) { + try { + element.detachEvent('on' + name, observer); + } catch (e) {} + } + } +}); + +/* prevent memory leaks in IE */ +if (Prototype.Browser.IE) + Event.observe(window, 'unload', Event.unloadCache, false); +var Position = { + // set to true if needed, warning: firefox performance problems + // NOT neeeded for page scrolling, only if draggable contained in + // scrollable elements + includeScrollOffsets: false, + + // must be called before calling withinIncludingScrolloffset, every time the + // page is scrolled + prepare: function() { + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; + }, + + realOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return [valueL, valueT]; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return [valueL, valueT]; + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if(element.tagName=='BODY') break; + var p = Element.getStyle(element, 'position'); + if (p == 'relative' || p == 'absolute') break; + } + } while (element); + return [valueL, valueT]; + }, + + offsetParent: function(element) { + if (element.offsetParent) return element.offsetParent; + if (element == document.body) return element; + + while ((element = element.parentNode) && element != document.body) + if (Element.getStyle(element, 'position') != 'static') + return element; + + return document.body; + }, + + // caches x/y coordinate pair to use with overlap + within: function(element, x, y) { + if (this.includeScrollOffsets) + return this.withinIncludingScrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + this.offset = this.cumulativeOffset(element); + + return (y >= this.offset[1] && + y < this.offset[1] + element.offsetHeight && + x >= this.offset[0] && + x < this.offset[0] + element.offsetWidth); + }, + + withinIncludingScrolloffsets: function(element, x, y) { + var offsetcache = this.realOffset(element); + + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.offset = this.cumulativeOffset(element); + + return (this.ycomp >= this.offset[1] && + this.ycomp < this.offset[1] + element.offsetHeight && + this.xcomp >= this.offset[0] && + this.xcomp < this.offset[0] + element.offsetWidth); + }, + + // within must be called directly before + overlap: function(mode, element) { + if (!mode) return 0; + if (mode == 'vertical') + return ((this.offset[1] + element.offsetHeight) - this.ycomp) / + element.offsetHeight; + if (mode == 'horizontal') + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + element.offsetWidth; + }, + + page: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + // Safari fix + if (element.offsetParent == document.body) + if (Element.getStyle(element,'position')=='absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + if (!window.opera || element.tagName=='BODY') { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + + return [valueL, valueT]; + }, + + clone: function(source, target) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || {}) + + // find page position of source + source = $(source); + var p = Position.page(source); + + // find coordinate system to use + target = $(target); + var delta = [0, 0]; + var parent = null; + // delta [0,0] will do fine with position: fixed elements, + // position:absolute needs offsetParent deltas + if (Element.getStyle(target,'position') == 'absolute') { + parent = Position.offsetParent(target); + delta = Position.page(parent); + } + + // correct by body offsets (fixes Safari) + if (parent == document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + // set position + if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if(options.setWidth) target.style.width = source.offsetWidth + 'px'; + if(options.setHeight) target.style.height = source.offsetHeight + 'px'; + }, + + absolutize: function(element) { + element = $(element); + if (element.style.position == 'absolute') return; + Position.prepare(); + + var offsets = Position.positionedOffset(element); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.width = width + 'px'; + element.style.height = height + 'px'; + }, + + relativize: function(element) { + element = $(element); + if (element.style.position == 'relative') return; + Position.prepare(); + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + } +} + +// Safari returns margins on body which is incorrect if the child is absolutely +// positioned. For performance reasons, redefine Position.cumulativeOffset for +// KHTML/WebKit only. +if (Prototype.Browser.WebKit) { + Position.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return [valueL, valueT]; + } +} + +Element.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 @@ +// script.aculo.us builder.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007 + +// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +var Builder = { + NODEMAP: { + AREA: 'map', + CAPTION: 'table', + COL: 'table', + COLGROUP: 'table', + LEGEND: 'fieldset', + OPTGROUP: 'select', + OPTION: 'select', + PARAM: 'object', + TBODY: 'table', + TD: 'table', + TFOOT: 'table', + TH: 'table', + THEAD: 'table', + TR: 'table' + }, + // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken, + // due to a Firefox bug + node: function(elementName) { + elementName = elementName.toUpperCase(); + + // try innerHTML approach + var parentTag = this.NODEMAP[elementName] || 'div'; + var parentElement = document.createElement(parentTag); + try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707 + parentElement.innerHTML = "<" + elementName + ">"; + } catch(e) {} + var element = parentElement.firstChild || null; + + // see if browser added wrapping tags + if(element && (element.tagName.toUpperCase() != elementName)) + element = element.getElementsByTagName(elementName)[0]; + + // fallback to createElement approach + if(!element) element = document.createElement(elementName); + + // abort if nothing could be created + if(!element) return; + + // attributes (or text) + if(arguments[1]) + if(this._isStringOrNumber(arguments[1]) || + (arguments[1] instanceof Array) || + arguments[1].tagName) { + this._children(element, arguments[1]); + } else { + var attrs = this._attributes(arguments[1]); + if(attrs.length) { + try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707 + parentElement.innerHTML = "<" +elementName + " " + + attrs + ">"; + } catch(e) {} + element = parentElement.firstChild || null; + // workaround firefox 1.0.X bug + if(!element) { + element = document.createElement(elementName); + for(attr in arguments[1]) + element[attr == 'class' ? 'className' : attr] = arguments[1][attr]; + } + if(element.tagName.toUpperCase() != elementName) + element = parentElement.getElementsByTagName(elementName)[0]; + } + } + + // text, or array of children + if(arguments[2]) + this._children(element, arguments[2]); + + return element; + }, + _text: function(text) { + return document.createTextNode(text); + }, + + ATTR_MAP: { + 'className': 'class', + 'htmlFor': 'for' + }, + + _attributes: function(attributes) { + var attrs = []; + for(attribute in attributes) + attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) + + '="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'"') + '"'); + return attrs.join(" "); + }, + _children: function(element, children) { + if(children.tagName) { + element.appendChild(children); + return; + } + if(typeof children=='object') { // array can hold nodes and text + children.flatten().each( function(e) { + if(typeof e=='object') + element.appendChild(e) + else + if(Builder._isStringOrNumber(e)) + element.appendChild(Builder._text(e)); + }); + } else + if(Builder._isStringOrNumber(children)) + element.appendChild(Builder._text(children)); + }, + _isStringOrNumber: function(param) { + return(typeof param=='string' || typeof param=='number'); + }, + build: function(html) { + var element = this.node('div'); + $(element).update(html.strip()); + return element.down(); + }, + dump: function(scope) { + if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope + + var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " + + "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " + + "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+ + "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+ + "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+ + "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/); + + tags.each( function(tag){ + scope[tag] = function() { + return Builder.node.apply(Builder, [tag].concat($A(arguments))); + } + }); + } +} 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 @@ +// script.aculo.us controls.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007 + +// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// (c) 2005-2007 Jon Tirsen (http://www.tirsen.com) +// Contributors: +// Richard Livsey +// Rahul Bhargava +// Rob Wills +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +// Autocompleter.Base handles all the autocompletion functionality +// that's independent of the data source for autocompletion. This +// includes drawing the autocompletion menu, observing keyboard +// and mouse events, and similar. +// +// Specific autocompleters need to provide, at the very least, +// a getUpdatedChoices function that will be invoked every time +// the text inside the monitored textbox changes. This method +// should get the text for which to provide autocompletion by +// invoking this.getToken(), NOT by directly accessing +// this.element.value. This is to allow incremental tokenized +// autocompletion. Specific auto-completion logic (AJAX, etc) +// belongs in getUpdatedChoices. +// +// Tokenized incremental autocompletion is enabled automatically +// when an autocompleter is instantiated with the 'tokens' option +// in the options parameter, e.g.: +// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); +// will incrementally autocomplete with a comma as the token. +// Additionally, ',' in the above example can be replaced with +// a token array, e.g. { tokens: [',', '\n'] } which +// enables autocompletion on multiple tokens. This is most +// useful when one of the tokens is \n (a newline), as it +// allows smart autocompletion after linebreaks. + +if(typeof Effect == 'undefined') + throw("controls.js requires including script.aculo.us' effects.js library"); + +var Autocompleter = {} +Autocompleter.Base = function() {}; +Autocompleter.Base.prototype = { + baseInitialize: function(element, update, options) { + element = $(element) + this.element = element; + this.update = $(update); + this.hasFocus = false; + this.changed = false; + this.active = false; + this.index = 0; + this.entryCount = 0; + + if(this.setOptions) + this.setOptions(options); + else + this.options = options || {}; + + this.options.paramName = this.options.paramName || this.element.name; + this.options.tokens = this.options.tokens || []; + this.options.frequency = this.options.frequency || 0.4; + this.options.minChars = this.options.minChars || 1; + this.options.onShow = this.options.onShow || + function(element, update){ + if(!update.style.position || update.style.position=='absolute') { + update.style.position = 'absolute'; + Position.clone(element, update, { + setHeight: false, + offsetTop: element.offsetHeight + }); + } + Effect.Appear(update,{duration:0.15}); + }; + this.options.onHide = this.options.onHide || + function(element, update){ new Effect.Fade(update,{duration:0.15}) }; + + if(typeof(this.options.tokens) == 'string') + this.options.tokens = new Array(this.options.tokens); + + this.observer = null; + + this.element.setAttribute('autocomplete','off'); + + Element.hide(this.update); + + Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this)); + Event.observe(this.element, 'keypress', this.onKeyPress.bindAsEventListener(this)); + + // Turn autocomplete back on when the user leaves the page, so that the + // field's value will be remembered on Mozilla-based browsers. + Event.observe(window, 'beforeunload', function(){ + element.setAttribute('autocomplete', 'on'); + }); + }, + + show: function() { + if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); + if(!this.iefix && + (Prototype.Browser.IE) && + (Element.getStyle(this.update, 'position')=='absolute')) { + new Insertion.After(this.update, + ''); + this.iefix = $(this.update.id+'_iefix'); + } + if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); + }, + + fixIEOverlapping: function() { + Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)}); + this.iefix.style.zIndex = 1; + this.update.style.zIndex = 2; + Element.show(this.iefix); + }, + + hide: function() { + this.stopIndicator(); + if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); + if(this.iefix) Element.hide(this.iefix); + }, + + startIndicator: function() { + if(this.options.indicator) Element.show(this.options.indicator); + }, + + stopIndicator: function() { + if(this.options.indicator) Element.hide(this.options.indicator); + }, + + onKeyPress: function(event) { + if(this.active) + switch(event.keyCode) { + case Event.KEY_TAB: + case Event.KEY_RETURN: + this.selectEntry(); + Event.stop(event); + case Event.KEY_ESC: + this.hide(); + this.active = false; + Event.stop(event); + return; + case Event.KEY_LEFT: + case Event.KEY_RIGHT: + return; + case Event.KEY_UP: + this.markPrevious(); + this.render(); + if(Prototype.Browser.WebKit) Event.stop(event); + return; + case Event.KEY_DOWN: + this.markNext(); + this.render(); + if(Prototype.Browser.WebKit) Event.stop(event); + return; + } + else + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || + (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return; + + this.changed = true; + this.hasFocus = true; + + if(this.observer) clearTimeout(this.observer); + this.observer = + setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); + }, + + activate: function() { + this.changed = false; + this.hasFocus = true; + this.getUpdatedChoices(); + }, + + onHover: function(event) { + var element = Event.findElement(event, 'LI'); + if(this.index != element.autocompleteIndex) + { + this.index = element.autocompleteIndex; + this.render(); + } + Event.stop(event); + }, + + onClick: function(event) { + var element = Event.findElement(event, 'LI'); + this.index = element.autocompleteIndex; + this.selectEntry(); + this.hide(); + }, + + onBlur: function(event) { + // needed to make click events working + setTimeout(this.hide.bind(this), 250); + this.hasFocus = false; + this.active = false; + }, + + render: function() { + if(this.entryCount > 0) { + for (var i = 0; i < this.entryCount; i++) + this.index==i ? + Element.addClassName(this.getEntry(i),"selected") : + Element.removeClassName(this.getEntry(i),"selected"); + if(this.hasFocus) { + this.show(); + this.active = true; + } + } else { + this.active = false; + this.hide(); + } + }, + + markPrevious: function() { + if(this.index > 0) this.index-- + else this.index = this.entryCount-1; + this.getEntry(this.index).scrollIntoView(true); + }, + + markNext: function() { + if(this.index < this.entryCount-1) this.index++ + else this.index = 0; + this.getEntry(this.index).scrollIntoView(false); + }, + + getEntry: function(index) { + return this.update.firstChild.childNodes[index]; + }, + + getCurrentEntry: function() { + return this.getEntry(this.index); + }, + + selectEntry: function() { + this.active = false; + this.updateElement(this.getCurrentEntry()); + }, + + updateElement: function(selectedElement) { + if (this.options.updateElement) { + this.options.updateElement(selectedElement); + return; + } + var value = ''; + if (this.options.select) { + var nodes = document.getElementsByClassName(this.options.select, selectedElement) || []; + if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); + } else + value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); + + var lastTokenPos = this.findLastToken(); + if (lastTokenPos != -1) { + var newValue = this.element.value.substr(0, lastTokenPos + 1); + var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/); + if (whitespace) + newValue += whitespace[0]; + this.element.value = newValue + value; + } else { + this.element.value = value; + } + this.element.focus(); + + if (this.options.afterUpdateElement) + this.options.afterUpdateElement(this.element, selectedElement); + }, + + updateChoices: function(choices) { + if(!this.changed && this.hasFocus) { + this.update.innerHTML = choices; + Element.cleanWhitespace(this.update); + Element.cleanWhitespace(this.update.down()); + + if(this.update.firstChild && this.update.down().childNodes) { + this.entryCount = + this.update.down().childNodes.length; + for (var i = 0; i < this.entryCount; i++) { + var entry = this.getEntry(i); + entry.autocompleteIndex = i; + this.addObservers(entry); + } + } else { + this.entryCount = 0; + } + + this.stopIndicator(); + this.index = 0; + + if(this.entryCount==1 && this.options.autoSelect) { + this.selectEntry(); + this.hide(); + } else { + this.render(); + } + } + }, + + addObservers: function(element) { + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); + Event.observe(element, "click", this.onClick.bindAsEventListener(this)); + }, + + onObserverEvent: function() { + this.changed = false; + if(this.getToken().length>=this.options.minChars) { + this.getUpdatedChoices(); + } else { + this.active = false; + this.hide(); + } + }, + + getToken: function() { + var tokenPos = this.findLastToken(); + if (tokenPos != -1) + var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,''); + else + var ret = this.element.value; + + return /\n/.test(ret) ? '' : ret; + }, + + findLastToken: function() { + var lastTokenPos = -1; + + for (var i=0; i lastTokenPos) + lastTokenPos = thisTokenPos; + } + return lastTokenPos; + } +} + +Ajax.Autocompleter = Class.create(); +Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), { + initialize: function(element, update, url, options) { + this.baseInitialize(element, update, options); + this.options.asynchronous = true; + this.options.onComplete = this.onComplete.bind(this); + this.options.defaultParams = this.options.parameters || null; + this.url = url; + }, + + getUpdatedChoices: function() { + this.startIndicator(); + + var entry = encodeURIComponent(this.options.paramName) + '=' + + encodeURIComponent(this.getToken()); + + this.options.parameters = this.options.callback ? + this.options.callback(this.element, entry) : entry; + + if(this.options.defaultParams) + this.options.parameters += '&' + this.options.defaultParams; + + new Ajax.Request(this.url, this.options); + }, + + onComplete: function(request) { + this.updateChoices(request.responseText); + } + +}); + +// The local array autocompleter. Used when you'd prefer to +// inject an array of autocompletion options into the page, rather +// than sending out Ajax queries, which can be quite slow sometimes. +// +// The constructor takes four parameters. The first two are, as usual, +// the id of the monitored textbox, and id of the autocompletion menu. +// The third is the array you want to autocomplete from, and the fourth +// is the options block. +// +// Extra local autocompletion options: +// - choices - How many autocompletion choices to offer +// +// - partialSearch - If false, the autocompleter will match entered +// text only at the beginning of strings in the +// autocomplete array. Defaults to true, which will +// match text at the beginning of any *word* in the +// strings in the autocomplete array. If you want to +// search anywhere in the string, additionally set +// the option fullSearch to true (default: off). +// +// - fullSsearch - Search anywhere in autocomplete array strings. +// +// - partialChars - How many characters to enter before triggering +// a partial match (unlike minChars, which defines +// how many characters are required to do any match +// at all). Defaults to 2. +// +// - ignoreCase - Whether to ignore case when autocompleting. +// Defaults to true. +// +// It's possible to pass in a custom function as the 'selector' +// option, if you prefer to write your own autocompletion logic. +// In that case, the other options above will not apply unless +// you support them. + +Autocompleter.Local = Class.create(); +Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { + initialize: function(element, update, array, options) { + this.baseInitialize(element, update, options); + this.options.array = array; + }, + + getUpdatedChoices: function() { + this.updateChoices(this.options.selector(this)); + }, + + setOptions: function(options) { + this.options = Object.extend({ + choices: 10, + partialSearch: true, + partialChars: 2, + ignoreCase: true, + fullSearch: false, + selector: function(instance) { + var ret = []; // Beginning matches + var partial = []; // Inside matches + var entry = instance.getToken(); + var count = 0; + + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { + + var elem = instance.options.array[i]; + var foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase()) : + elem.indexOf(entry); + + while (foundPos != -1) { + if (foundPos == 0 && elem.length != entry.length) { + ret.push("
  • " + elem.substr(0, entry.length) + "" + + elem.substr(entry.length) + "
  • "); + break; + } else if (entry.length >= instance.options.partialChars && + instance.options.partialSearch && foundPos != -1) { + if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { + partial.push("
  • " + elem.substr(0, foundPos) + "" + + elem.substr(foundPos, entry.length) + "" + elem.substr( + foundPos + entry.length) + "
  • "); + break; + } + } + + foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : + elem.indexOf(entry, foundPos + 1); + + } + } + if (partial.length) + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) + return "
      " + ret.join('') + "
    "; + } + }, options || {}); + } +}); + +// AJAX in-place editor +// +// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor + +// Use this if you notice weird scrolling problems on some browsers, +// the DOM might be a bit confused when this gets called so do this +// waits 1 ms (with setTimeout) until it does the activation +Field.scrollFreeActivate = function(field) { + setTimeout(function() { + Field.activate(field); + }, 1); +} + +Ajax.InPlaceEditor = Class.create(); +Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99"; +Ajax.InPlaceEditor.prototype = { + initialize: function(element, url, options) { + this.url = url; + this.element = $(element); + + this.options = Object.extend({ + paramName: "value", + okButton: true, + okLink: false, + okText: "ok", + cancelButton: false, + cancelLink: true, + cancelText: "cancel", + textBeforeControls: '', + textBetweenControls: '', + textAfterControls: '', + savingText: "Saving...", + clickToEditText: "Click to edit", + okText: "ok", + rows: 1, + onComplete: function(transport, element) { + new Effect.Highlight(element, {startcolor: this.options.highlightcolor}); + }, + onFailure: function(transport) { + alert("Error communicating with the server: " + transport.responseText.stripTags()); + }, + callback: function(form) { + return Form.serialize(form); + }, + handleLineBreaks: true, + loadingText: 'Loading...', + savingClassName: 'inplaceeditor-saving', + loadingClassName: 'inplaceeditor-loading', + formClassName: 'inplaceeditor-form', + highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, + highlightendcolor: "#FFFFFF", + externalControl: null, + submitOnBlur: false, + ajaxOptions: {}, + evalScripts: false + }, options || {}); + + if(!this.options.formId && this.element.id) { + this.options.formId = this.element.id + "-inplaceeditor"; + if ($(this.options.formId)) { + // there's already a form with that name, don't specify an id + this.options.formId = null; + } + } + + if (this.options.externalControl) { + this.options.externalControl = $(this.options.externalControl); + } + + this.originalBackground = Element.getStyle(this.element, 'background-color'); + if (!this.originalBackground) { + this.originalBackground = "transparent"; + } + + this.element.title = this.options.clickToEditText; + + this.onclickListener = this.enterEditMode.bindAsEventListener(this); + this.mouseoverListener = this.enterHover.bindAsEventListener(this); + this.mouseoutListener = this.leaveHover.bindAsEventListener(this); + Event.observe(this.element, 'click', this.onclickListener); + Event.observe(this.element, 'mouseover', this.mouseoverListener); + Event.observe(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.observe(this.options.externalControl, 'click', this.onclickListener); + Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + }, + enterEditMode: function(evt) { + if (this.saving) return; + if (this.editing) return; + this.editing = true; + this.onEnterEditMode(); + if (this.options.externalControl) { + Element.hide(this.options.externalControl); + } + Element.hide(this.element); + this.createForm(); + this.element.parentNode.insertBefore(this.form, this.element); + if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField); + // stop the event to avoid a page refresh in Safari + if (evt) { + Event.stop(evt); + } + return false; + }, + createForm: function() { + this.form = document.createElement("form"); + this.form.id = this.options.formId; + Element.addClassName(this.form, this.options.formClassName) + this.form.onsubmit = this.onSubmit.bind(this); + + this.createEditField(); + + if (this.options.textarea) { + var br = document.createElement("br"); + this.form.appendChild(br); + } + + if (this.options.textBeforeControls) + this.form.appendChild(document.createTextNode(this.options.textBeforeControls)); + + if (this.options.okButton) { + var okButton = document.createElement("input"); + okButton.type = "submit"; + okButton.value = this.options.okText; + okButton.className = 'editor_ok_button'; + this.form.appendChild(okButton); + } + + if (this.options.okLink) { + var okLink = document.createElement("a"); + okLink.href = "#"; + okLink.appendChild(document.createTextNode(this.options.okText)); + okLink.onclick = this.onSubmit.bind(this); + okLink.className = 'editor_ok_link'; + this.form.appendChild(okLink); + } + + if (this.options.textBetweenControls && + (this.options.okLink || this.options.okButton) && + (this.options.cancelLink || this.options.cancelButton)) + this.form.appendChild(document.createTextNode(this.options.textBetweenControls)); + + if (this.options.cancelButton) { + var cancelButton = document.createElement("input"); + cancelButton.type = "submit"; + cancelButton.value = this.options.cancelText; + cancelButton.onclick = this.onclickCancel.bind(this); + cancelButton.className = 'editor_cancel_button'; + this.form.appendChild(cancelButton); + } + + if (this.options.cancelLink) { + var cancelLink = document.createElement("a"); + cancelLink.href = "#"; + cancelLink.appendChild(document.createTextNode(this.options.cancelText)); + cancelLink.onclick = this.onclickCancel.bind(this); + cancelLink.className = 'editor_cancel editor_cancel_link'; + this.form.appendChild(cancelLink); + } + + if (this.options.textAfterControls) + this.form.appendChild(document.createTextNode(this.options.textAfterControls)); + }, + hasHTMLLineBreaks: function(string) { + if (!this.options.handleLineBreaks) return false; + return string.match(/
    /i); + }, + convertHTMLLineBreaks: function(string) { + return string.replace(/
    /gi, "\n").replace(//gi, "\n").replace(/<\/p>/gi, "\n").replace(/

    /gi, ""); + }, + createEditField: function() { + var text; + if(this.options.loadTextURL) { + text = this.options.loadingText; + } else { + text = this.getText(); + } + + var obj = this; + + if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) { + this.options.textarea = false; + var textField = document.createElement("input"); + textField.obj = this; + textField.type = "text"; + textField.name = this.options.paramName; + textField.value = text; + textField.style.backgroundColor = this.options.highlightcolor; + textField.className = 'editor_field'; + var size = this.options.size || this.options.cols || 0; + if (size != 0) textField.size = size; + if (this.options.submitOnBlur) + textField.onblur = this.onSubmit.bind(this); + this.editField = textField; + } else { + this.options.textarea = true; + var textArea = document.createElement("textarea"); + textArea.obj = this; + textArea.name = this.options.paramName; + textArea.value = this.convertHTMLLineBreaks(text); + textArea.rows = this.options.rows; + textArea.cols = this.options.cols || 40; + textArea.className = 'editor_field'; + if (this.options.submitOnBlur) + textArea.onblur = this.onSubmit.bind(this); + this.editField = textArea; + } + + if(this.options.loadTextURL) { + this.loadExternalText(); + } + this.form.appendChild(this.editField); + }, + getText: function() { + return this.element.innerHTML; + }, + loadExternalText: function() { + Element.addClassName(this.form, this.options.loadingClassName); + this.editField.disabled = true; + new Ajax.Request( + this.options.loadTextURL, + Object.extend({ + asynchronous: true, + onComplete: this.onLoadedExternalText.bind(this) + }, this.options.ajaxOptions) + ); + }, + onLoadedExternalText: function(transport) { + Element.removeClassName(this.form, this.options.loadingClassName); + this.editField.disabled = false; + this.editField.value = transport.responseText.stripTags(); + Field.scrollFreeActivate(this.editField); + }, + onclickCancel: function() { + this.onComplete(); + this.leaveEditMode(); + return false; + }, + onFailure: function(transport) { + this.options.onFailure(transport); + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + this.oldInnerHTML = null; + } + return false; + }, + onSubmit: function() { + // onLoading resets these so we need to save them away for the Ajax call + var form = this.form; + var value = this.editField.value; + + // do this first, sometimes the ajax call returns before we get a chance to switch on Saving... + // which means this will actually switch on Saving... *after* we've left edit mode causing Saving... + // to be displayed indefinitely + this.onLoading(); + + if (this.options.evalScripts) { + new Ajax.Request( + this.url, Object.extend({ + parameters: this.options.callback(form, value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this), + asynchronous:true, + evalScripts:true + }, this.options.ajaxOptions)); + } else { + new Ajax.Updater( + { success: this.element, + // don't update on failure (this could be an option) + failure: null }, + this.url, Object.extend({ + parameters: this.options.callback(form, value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this) + }, this.options.ajaxOptions)); + } + // stop the event to avoid a page refresh in Safari + if (arguments.length > 1) { + Event.stop(arguments[0]); + } + return false; + }, + onLoading: function() { + this.saving = true; + this.removeForm(); + this.leaveHover(); + this.showSaving(); + }, + showSaving: function() { + this.oldInnerHTML = this.element.innerHTML; + this.element.innerHTML = this.options.savingText; + Element.addClassName(this.element, this.options.savingClassName); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + }, + removeForm: function() { + if(this.form) { + if (this.form.parentNode) Element.remove(this.form); + this.form = null; + } + }, + enterHover: function() { + if (this.saving) return; + this.element.style.backgroundColor = this.options.highlightcolor; + if (this.effect) { + this.effect.cancel(); + } + Element.addClassName(this.element, this.options.hoverClassName) + }, + leaveHover: function() { + if (this.options.backgroundColor) { + this.element.style.backgroundColor = this.oldBackground; + } + Element.removeClassName(this.element, this.options.hoverClassName) + if (this.saving) return; + this.effect = new Effect.Highlight(this.element, { + startcolor: this.options.highlightcolor, + endcolor: this.options.highlightendcolor, + restorecolor: this.originalBackground + }); + }, + leaveEditMode: function() { + Element.removeClassName(this.element, this.options.savingClassName); + this.removeForm(); + this.leaveHover(); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + if (this.options.externalControl) { + Element.show(this.options.externalControl); + } + this.editing = false; + this.saving = false; + this.oldInnerHTML = null; + this.onLeaveEditMode(); + }, + onComplete: function(transport) { + this.leaveEditMode(); + this.options.onComplete.bind(this)(transport, this.element); + }, + onEnterEditMode: function() {}, + onLeaveEditMode: function() {}, + dispose: function() { + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + } + this.leaveEditMode(); + Event.stopObserving(this.element, 'click', this.onclickListener); + Event.stopObserving(this.element, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.stopObserving(this.options.externalControl, 'click', this.onclickListener); + Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + } +}; + +Ajax.InPlaceCollectionEditor = Class.create(); +Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype); +Object.extend(Ajax.InPlaceCollectionEditor.prototype, { + createEditField: function() { + if (!this.cached_selectTag) { + var selectTag = document.createElement("select"); + var collection = this.options.collection || []; + var optionTag; + collection.each(function(e,i) { + optionTag = document.createElement("option"); + optionTag.value = (e instanceof Array) ? e[0] : e; + if((typeof this.options.value == 'undefined') && + ((e instanceof Array) ? this.element.innerHTML == e[1] : e == optionTag.value)) optionTag.selected = true; + if(this.options.value==optionTag.value) optionTag.selected = true; + optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e)); + selectTag.appendChild(optionTag); + }.bind(this)); + this.cached_selectTag = selectTag; + } + + this.editField = this.cached_selectTag; + if(this.options.loadTextURL) this.loadExternalText(); + this.form.appendChild(this.editField); + this.options.callback = function(form, value) { + return "value=" + encodeURIComponent(value); + } + } +}); + +// Delayed observer, like Form.Element.Observer, +// but waits for delay after last key input +// Ideal for live-search fields + +Form.Element.DelayedObserver = Class.create(); +Form.Element.DelayedObserver.prototype = { + initialize: function(element, delay, callback) { + this.delay = delay || 0.5; + this.element = $(element); + this.callback = callback; + this.timer = null; + this.lastValue = $F(this.element); + Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); + }, + delayedListener: function(event) { + if(this.lastValue == $F(this.element)) return; + if(this.timer) clearTimeout(this.timer); + this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); + this.lastValue = $F(this.element); + }, + onTimerEvent: function() { + this.timer = null; + this.callback(this.element, $F(this.element)); + } +}; 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 @@ +// script.aculo.us dragdrop.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007 + +// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +if(typeof Effect == 'undefined') + throw("dragdrop.js requires including script.aculo.us' effects.js library"); + +var Droppables = { + drops: [], + + remove: function(element) { + this.drops = this.drops.reject(function(d) { return d.element==$(element) }); + }, + + add: function(element) { + element = $(element); + var options = Object.extend({ + greedy: true, + hoverclass: null, + tree: false + }, arguments[1] || {}); + + // cache containers + if(options.containment) { + options._containers = []; + var containment = options.containment; + if((typeof containment == 'object') && + (containment.constructor == Array)) { + containment.each( function(c) { options._containers.push($(c)) }); + } else { + options._containers.push($(containment)); + } + } + + if(options.accept) options.accept = [options.accept].flatten(); + + Element.makePositioned(element); // fix IE + options.element = element; + + this.drops.push(options); + }, + + findDeepestChild: function(drops) { + deepest = drops[0]; + + for (i = 1; i < drops.length; ++i) + if (Element.isParent(drops[i].element, deepest.element)) + deepest = drops[i]; + + return deepest; + }, + + isContained: function(element, drop) { + var containmentNode; + if(drop.tree) { + containmentNode = element.treeNode; + } else { + containmentNode = element.parentNode; + } + return drop._containers.detect(function(c) { return containmentNode == c }); + }, + + isAffected: function(point, element, drop) { + return ( + (drop.element!=element) && + ((!drop._containers) || + this.isContained(element, drop)) && + ((!drop.accept) || + (Element.classNames(element).detect( + function(v) { return drop.accept.include(v) } ) )) && + Position.within(drop.element, point[0], point[1]) ); + }, + + deactivate: function(drop) { + if(drop.hoverclass) + Element.removeClassName(drop.element, drop.hoverclass); + this.last_active = null; + }, + + activate: function(drop) { + if(drop.hoverclass) + Element.addClassName(drop.element, drop.hoverclass); + this.last_active = drop; + }, + + show: function(point, element) { + if(!this.drops.length) return; + var affected = []; + + if(this.last_active) this.deactivate(this.last_active); + this.drops.each( function(drop) { + if(Droppables.isAffected(point, element, drop)) + affected.push(drop); + }); + + if(affected.length>0) { + drop = Droppables.findDeepestChild(affected); + Position.within(drop.element, point[0], point[1]); + if(drop.onHover) + drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); + + Droppables.activate(drop); + } + }, + + fire: function(event, element) { + if(!this.last_active) return; + Position.prepare(); + + if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) + if (this.last_active.onDrop) { + this.last_active.onDrop(element, this.last_active.element, event); + return true; + } + }, + + reset: function() { + if(this.last_active) + this.deactivate(this.last_active); + } +} + +var Draggables = { + drags: [], + observers: [], + + register: function(draggable) { + if(this.drags.length == 0) { + this.eventMouseUp = this.endDrag.bindAsEventListener(this); + this.eventMouseMove = this.updateDrag.bindAsEventListener(this); + this.eventKeypress = this.keyPress.bindAsEventListener(this); + + Event.observe(document, "mouseup", this.eventMouseUp); + Event.observe(document, "mousemove", this.eventMouseMove); + Event.observe(document, "keypress", this.eventKeypress); + } + this.drags.push(draggable); + }, + + unregister: function(draggable) { + this.drags = this.drags.reject(function(d) { return d==draggable }); + if(this.drags.length == 0) { + Event.stopObserving(document, "mouseup", this.eventMouseUp); + Event.stopObserving(document, "mousemove", this.eventMouseMove); + Event.stopObserving(document, "keypress", this.eventKeypress); + } + }, + + activate: function(draggable) { + if(draggable.options.delay) { + this._timeout = setTimeout(function() { + Draggables._timeout = null; + window.focus(); + Draggables.activeDraggable = draggable; + }.bind(this), draggable.options.delay); + } else { + window.focus(); // allows keypress events if window isn't currently focused, fails for Safari + this.activeDraggable = draggable; + } + }, + + deactivate: function() { + this.activeDraggable = null; + }, + + updateDrag: function(event) { + if(!this.activeDraggable) return; + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + // Mozilla-based browsers fire successive mousemove events with + // the same coordinates, prevent needless redrawing (moz bug?) + if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; + this._lastPointer = pointer; + + this.activeDraggable.updateDrag(event, pointer); + }, + + endDrag: function(event) { + if(this._timeout) { + clearTimeout(this._timeout); + this._timeout = null; + } + if(!this.activeDraggable) return; + this._lastPointer = null; + this.activeDraggable.endDrag(event); + this.activeDraggable = null; + }, + + keyPress: function(event) { + if(this.activeDraggable) + this.activeDraggable.keyPress(event); + }, + + addObserver: function(observer) { + this.observers.push(observer); + this._cacheObserverCallbacks(); + }, + + removeObserver: function(element) { // element instead of observer fixes mem leaks + this.observers = this.observers.reject( function(o) { return o.element==element }); + this._cacheObserverCallbacks(); + }, + + notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' + if(this[eventName+'Count'] > 0) + this.observers.each( function(o) { + if(o[eventName]) o[eventName](eventName, draggable, event); + }); + if(draggable.options[eventName]) draggable.options[eventName](draggable, event); + }, + + _cacheObserverCallbacks: function() { + ['onStart','onEnd','onDrag'].each( function(eventName) { + Draggables[eventName+'Count'] = Draggables.observers.select( + function(o) { return o[eventName]; } + ).length; + }); + } +} + +/*--------------------------------------------------------------------------*/ + +var Draggable = Class.create(); +Draggable._dragging = {}; + +Draggable.prototype = { + initialize: function(element) { + var defaults = { + handle: false, + reverteffect: function(element, top_offset, left_offset) { + var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; + new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, + queue: {scope:'_draggable', position:'end'} + }); + }, + endeffect: function(element) { + var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0; + new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, + queue: {scope:'_draggable', position:'end'}, + afterFinish: function(){ + Draggable._dragging[element] = false + } + }); + }, + zindex: 1000, + revert: false, + quiet: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } + delay: 0 + }; + + if(!arguments[1] || typeof arguments[1].endeffect == 'undefined') + Object.extend(defaults, { + starteffect: function(element) { + element._opacity = Element.getOpacity(element); + Draggable._dragging[element] = true; + new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); + } + }); + + var options = Object.extend(defaults, arguments[1] || {}); + + this.element = $(element); + + if(options.handle && (typeof options.handle == 'string')) + this.handle = this.element.down('.'+options.handle, 0); + + if(!this.handle) this.handle = $(options.handle); + if(!this.handle) this.handle = this.element; + + if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { + options.scroll = $(options.scroll); + this._isScrollChild = Element.childOf(this.element, options.scroll); + } + + Element.makePositioned(this.element); // fix IE + + this.delta = this.currentDelta(); + this.options = options; + this.dragging = false; + + this.eventMouseDown = this.initDrag.bindAsEventListener(this); + Event.observe(this.handle, "mousedown", this.eventMouseDown); + + Draggables.register(this); + }, + + destroy: function() { + Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); + Draggables.unregister(this); + }, + + currentDelta: function() { + return([ + parseInt(Element.getStyle(this.element,'left') || '0'), + parseInt(Element.getStyle(this.element,'top') || '0')]); + }, + + initDrag: function(event) { + if(typeof Draggable._dragging[this.element] != 'undefined' && + Draggable._dragging[this.element]) return; + if(Event.isLeftClick(event)) { + // abort on form elements, fixes a Firefox issue + var src = Event.element(event); + if((tag_name = src.tagName.toUpperCase()) && ( + tag_name=='INPUT' || + tag_name=='SELECT' || + tag_name=='OPTION' || + tag_name=='BUTTON' || + tag_name=='TEXTAREA')) return; + + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var pos = Position.cumulativeOffset(this.element); + this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); + + Draggables.activate(this); + Event.stop(event); + } + }, + + startDrag: function(event) { + this.dragging = true; + + if(this.options.zindex) { + this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); + this.element.style.zIndex = this.options.zindex; + } + + if(this.options.ghosting) { + this._clone = this.element.cloneNode(true); + Position.absolutize(this.element); + this.element.parentNode.insertBefore(this._clone, this.element); + } + + if(this.options.scroll) { + if (this.options.scroll == window) { + var where = this._getWindowScroll(this.options.scroll); + this.originalScrollLeft = where.left; + this.originalScrollTop = where.top; + } else { + this.originalScrollLeft = this.options.scroll.scrollLeft; + this.originalScrollTop = this.options.scroll.scrollTop; + } + } + + Draggables.notify('onStart', this, event); + + if(this.options.starteffect) this.options.starteffect(this.element); + }, + + updateDrag: function(event, pointer) { + if(!this.dragging) this.startDrag(event); + + if(!this.options.quiet){ + Position.prepare(); + Droppables.show(pointer, this.element); + } + + Draggables.notify('onDrag', this, event); + + this.draw(pointer); + if(this.options.change) this.options.change(this); + + if(this.options.scroll) { + this.stopScrolling(); + + var p; + if (this.options.scroll == window) { + with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } + } else { + p = Position.page(this.options.scroll); + p[0] += this.options.scroll.scrollLeft + Position.deltaX; + p[1] += this.options.scroll.scrollTop + Position.deltaY; + p.push(p[0]+this.options.scroll.offsetWidth); + p.push(p[1]+this.options.scroll.offsetHeight); + } + var speed = [0,0]; + if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); + if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); + if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); + if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); + this.startScrolling(speed); + } + + // fix AppleWebKit rendering + if(Prototype.Browser.WebKit) window.scrollBy(0,0); + + Event.stop(event); + }, + + finishDrag: function(event, success) { + this.dragging = false; + + if(this.options.quiet){ + Position.prepare(); + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + Droppables.show(pointer, this.element); + } + + if(this.options.ghosting) { + Position.relativize(this.element); + Element.remove(this._clone); + this._clone = null; + } + + var dropped = false; + if(success) { + dropped = Droppables.fire(event, this.element); + if (!dropped) dropped = false; + } + if(dropped && this.options.onDropped) this.options.onDropped(this.element); + Draggables.notify('onEnd', this, event); + + var revert = this.options.revert; + if(revert && typeof revert == 'function') revert = revert(this.element); + + var d = this.currentDelta(); + if(revert && this.options.reverteffect) { + if (dropped == 0 || revert != 'failure') + this.options.reverteffect(this.element, + d[1]-this.delta[1], d[0]-this.delta[0]); + } else { + this.delta = d; + } + + if(this.options.zindex) + this.element.style.zIndex = this.originalZ; + + if(this.options.endeffect) + this.options.endeffect(this.element); + + Draggables.deactivate(this); + Droppables.reset(); + }, + + keyPress: function(event) { + if(event.keyCode!=Event.KEY_ESC) return; + this.finishDrag(event, false); + Event.stop(event); + }, + + endDrag: function(event) { + if(!this.dragging) return; + this.stopScrolling(); + this.finishDrag(event, true); + Event.stop(event); + }, + + draw: function(point) { + var pos = Position.cumulativeOffset(this.element); + if(this.options.ghosting) { + var r = Position.realOffset(this.element); + pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY; + } + + var d = this.currentDelta(); + pos[0] -= d[0]; pos[1] -= d[1]; + + if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) { + pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; + pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; + } + + var p = [0,1].map(function(i){ + return (point[i]-pos[i]-this.offset[i]) + }.bind(this)); + + if(this.options.snap) { + if(typeof this.options.snap == 'function') { + p = this.options.snap(p[0],p[1],this); + } else { + if(this.options.snap instanceof Array) { + p = p.map( function(v, i) { + return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this)) + } else { + p = p.map( function(v) { + return Math.round(v/this.options.snap)*this.options.snap }.bind(this)) + } + }} + + var style = this.element.style; + if((!this.options.constraint) || (this.options.constraint=='horizontal')) + style.left = p[0] + "px"; + if((!this.options.constraint) || (this.options.constraint=='vertical')) + style.top = p[1] + "px"; + + if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering + }, + + stopScrolling: function() { + if(this.scrollInterval) { + clearInterval(this.scrollInterval); + this.scrollInterval = null; + Draggables._lastScrollPointer = null; + } + }, + + startScrolling: function(speed) { + if(!(speed[0] || speed[1])) return; + this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; + this.lastScrolled = new Date(); + this.scrollInterval = setInterval(this.scroll.bind(this), 10); + }, + + scroll: function() { + var current = new Date(); + var delta = current - this.lastScrolled; + this.lastScrolled = current; + if(this.options.scroll == window) { + with (this._getWindowScroll(this.options.scroll)) { + if (this.scrollSpeed[0] || this.scrollSpeed[1]) { + var d = delta / 1000; + this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); + } + } + } else { + this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; + this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; + } + + Position.prepare(); + Droppables.show(Draggables._lastPointer, this.element); + Draggables.notify('onDrag', this); + if (this._isScrollChild) { + Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); + Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; + Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; + if (Draggables._lastScrollPointer[0] < 0) + Draggables._lastScrollPointer[0] = 0; + if (Draggables._lastScrollPointer[1] < 0) + Draggables._lastScrollPointer[1] = 0; + this.draw(Draggables._lastScrollPointer); + } + + if(this.options.change) this.options.change(this); + }, + + _getWindowScroll: function(w) { + var T, L, W, H; + with (w.document) { + if (w.document.documentElement && documentElement.scrollTop) { + T = documentElement.scrollTop; + L = documentElement.scrollLeft; + } else if (w.document.body) { + T = body.scrollTop; + L = body.scrollLeft; + } + if (w.innerWidth) { + W = w.innerWidth; + H = w.innerHeight; + } else if (w.document.documentElement && documentElement.clientWidth) { + W = documentElement.clientWidth; + H = documentElement.clientHeight; + } else { + W = body.offsetWidth; + H = body.offsetHeight + } + } + return { top: T, left: L, width: W, height: H }; + } +} + +/*--------------------------------------------------------------------------*/ + +var SortableObserver = Class.create(); +SortableObserver.prototype = { + initialize: function(element, observer) { + this.element = $(element); + this.observer = observer; + this.lastValue = Sortable.serialize(this.element); + }, + + onStart: function() { + this.lastValue = Sortable.serialize(this.element); + }, + + onEnd: function() { + Sortable.unmark(); + if(this.lastValue != Sortable.serialize(this.element)) + this.observer(this.element) + } +} + +var Sortable = { + SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, + + sortables: {}, + + _findRootElement: function(element) { + while (element.tagName.toUpperCase() != "BODY") { + if(element.id && Sortable.sortables[element.id]) return element; + element = element.parentNode; + } + }, + + options: function(element) { + element = Sortable._findRootElement($(element)); + if(!element) return; + return Sortable.sortables[element.id]; + }, + + destroy: function(element){ + var s = Sortable.options(element); + + if(s) { + Draggables.removeObserver(s.element); + s.droppables.each(function(d){ Droppables.remove(d) }); + s.draggables.invoke('destroy'); + + delete Sortable.sortables[s.element.id]; + } + }, + + create: function(element) { + element = $(element); + var options = Object.extend({ + element: element, + tag: 'li', // assumes li children, override with tag: 'tagname' + dropOnEmpty: false, + tree: false, + treeTag: 'ul', + overlap: 'vertical', // one of 'vertical', 'horizontal' + constraint: 'vertical', // one of 'vertical', 'horizontal', false + containment: element, // also takes array of elements (or id's); or false + handle: false, // or a CSS class + only: false, + delay: 0, + hoverclass: null, + ghosting: false, + quiet: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + format: this.SERIALIZE_RULE, + + // these take arrays of elements or ids and can be + // used for better initialization performance + elements: false, + handles: false, + + onChange: Prototype.emptyFunction, + onUpdate: Prototype.emptyFunction + }, arguments[1] || {}); + + // clear any old sortable with same element + this.destroy(element); + + // build options for the draggables + var options_for_draggable = { + revert: true, + quiet: options.quiet, + scroll: options.scroll, + scrollSpeed: options.scrollSpeed, + scrollSensitivity: options.scrollSensitivity, + delay: options.delay, + ghosting: options.ghosting, + constraint: options.constraint, + handle: options.handle }; + + if(options.starteffect) + options_for_draggable.starteffect = options.starteffect; + + if(options.reverteffect) + options_for_draggable.reverteffect = options.reverteffect; + else + if(options.ghosting) options_for_draggable.reverteffect = function(element) { + element.style.top = 0; + element.style.left = 0; + }; + + if(options.endeffect) + options_for_draggable.endeffect = options.endeffect; + + if(options.zindex) + options_for_draggable.zindex = options.zindex; + + // build options for the droppables + var options_for_droppable = { + overlap: options.overlap, + containment: options.containment, + tree: options.tree, + hoverclass: options.hoverclass, + onHover: Sortable.onHover + } + + var options_for_tree = { + onHover: Sortable.onEmptyHover, + overlap: options.overlap, + containment: options.containment, + hoverclass: options.hoverclass + } + + // fix for gecko engine + Element.cleanWhitespace(element); + + options.draggables = []; + options.droppables = []; + + // drop on empty handling + if(options.dropOnEmpty || options.tree) { + Droppables.add(element, options_for_tree); + options.droppables.push(element); + } + + (options.elements || this.findElements(element, options) || []).each( function(e,i) { + var handle = options.handles ? $(options.handles[i]) : + (options.handle ? $(e).getElementsByClassName(options.handle)[0] : e); + options.draggables.push( + new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); + Droppables.add(e, options_for_droppable); + if(options.tree) e.treeNode = element; + options.droppables.push(e); + }); + + if(options.tree) { + (Sortable.findTreeElements(element, options) || []).each( function(e) { + Droppables.add(e, options_for_tree); + e.treeNode = element; + options.droppables.push(e); + }); + } + + // keep reference + this.sortables[element.id] = options; + + // for onupdate + Draggables.addObserver(new SortableObserver(element, options.onUpdate)); + + }, + + // return all suitable-for-sortable elements in a guaranteed order + findElements: function(element, options) { + return Element.findChildren( + element, options.only, options.tree ? true : false, options.tag); + }, + + findTreeElements: function(element, options) { + return Element.findChildren( + element, options.only, options.tree ? true : false, options.treeTag); + }, + + onHover: function(element, dropon, overlap) { + if(Element.isParent(dropon, element)) return; + + if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { + return; + } else if(overlap>0.5) { + Sortable.mark(dropon, 'before'); + if(dropon.previousSibling != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, dropon); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } else { + Sortable.mark(dropon, 'after'); + var nextElement = dropon.nextSibling || null; + if(nextElement != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, nextElement); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } + }, + + onEmptyHover: function(element, dropon, overlap) { + var oldParentNode = element.parentNode; + var droponOptions = Sortable.options(dropon); + + if(!Element.isParent(dropon, element)) { + var index; + + var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only}); + var child = null; + + if(children) { + var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); + + for (index = 0; index < children.length; index += 1) { + if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { + offset -= Element.offsetSize (children[index], droponOptions.overlap); + } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { + child = index + 1 < children.length ? children[index + 1] : null; + break; + } else { + child = children[index]; + break; + } + } + } + + dropon.insertBefore(element, child); + + Sortable.options(oldParentNode).onChange(element); + droponOptions.onChange(element); + } + }, + + unmark: function() { + if(Sortable._marker) Sortable._marker.hide(); + }, + + mark: function(dropon, position) { + // mark on ghosting only + var sortable = Sortable.options(dropon.parentNode); + if(sortable && !sortable.ghosting) return; + + if(!Sortable._marker) { + Sortable._marker = + ($('dropmarker') || Element.extend(document.createElement('DIV'))). + hide().addClassName('dropmarker').setStyle({position:'absolute'}); + document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); + } + var offsets = Position.cumulativeOffset(dropon); + Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); + + if(position=='after') + if(sortable.overlap == 'horizontal') + Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'}); + else + Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'}); + + Sortable._marker.show(); + }, + + _tree: function(element, options, parent) { + var children = Sortable.findElements(element, options) || []; + + for (var i = 0; i < children.length; ++i) { + var match = children[i].id.match(options.format); + + if (!match) continue; + + var child = { + id: encodeURIComponent(match ? match[1] : null), + element: element, + parent: parent, + children: [], + position: parent.children.length, + container: $(children[i]).down(options.treeTag) + } + + /* Get the element containing the children and recurse over it */ + if (child.container) + this._tree(child.container, options, child) + + parent.children.push (child); + } + + return parent; + }, + + tree: function(element) { + element = $(element); + var sortableOptions = this.options(element); + var options = Object.extend({ + tag: sortableOptions.tag, + treeTag: sortableOptions.treeTag, + only: sortableOptions.only, + name: element.id, + format: sortableOptions.format + }, arguments[1] || {}); + + var root = { + id: null, + parent: null, + children: [], + container: element, + position: 0 + } + + return Sortable._tree(element, options, root); + }, + + /* Construct a [i] index for a particular node */ + _constructIndex: function(node) { + var index = ''; + do { + if (node.id) index = '[' + node.position + ']' + index; + } while ((node = node.parent) != null); + return index; + }, + + sequence: function(element) { + element = $(element); + var options = Object.extend(this.options(element), arguments[1] || {}); + + return $(this.findElements(element, options) || []).map( function(item) { + return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; + }); + }, + + setSequence: function(element, new_sequence) { + element = $(element); + var options = Object.extend(this.options(element), arguments[2] || {}); + + var nodeMap = {}; + this.findElements(element, options).each( function(n) { + if (n.id.match(options.format)) + nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; + n.parentNode.removeChild(n); + }); + + new_sequence.each(function(ident) { + var n = nodeMap[ident]; + if (n) { + n[1].appendChild(n[0]); + delete nodeMap[ident]; + } + }); + }, + + serialize: function(element) { + element = $(element); + var options = Object.extend(Sortable.options(element), arguments[1] || {}); + var name = encodeURIComponent( + (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); + + if (options.tree) { + return Sortable.tree(element, arguments[1]).children.map( function (item) { + return [name + Sortable._constructIndex(item) + "[id]=" + + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); + }).flatten().join('&'); + } else { + return Sortable.sequence(element, arguments[1]).map( function(item) { + return name + "[]=" + encodeURIComponent(item); + }).join('&'); + } + } +} + +// Returns true if child is contained within element +Element.isParent = function(child, element) { + if (!child.parentNode || child == element) return false; + if (child.parentNode == element) return true; + return Element.isParent(child.parentNode, element); +} + +Element.findChildren = function(element, only, recursive, tagName) { + if(!element.hasChildNodes()) return null; + tagName = tagName.toUpperCase(); + if(only) only = [only].flatten(); + var elements = []; + $A(element.childNodes).each( function(e) { + if(e.tagName && e.tagName.toUpperCase()==tagName && + (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) + elements.push(e); + if(recursive) { + var grandchildren = Element.findChildren(e, only, recursive, tagName); + if(grandchildren) elements.push(grandchildren); + } + }); + + return (elements.length>0 ? elements.flatten() : []); +} + +Element.offsetSize = function (element, type) { + return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; +} 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 @@ +// script.aculo.us effects.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007 + +// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Contributors: +// Justin Palmer (http://encytemedia.com/) +// Mark Pilgrim (http://diveintomark.org/) +// Martin Bialasinki +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +// converts rgb() and #xxx to #xxxxxx format, +// returns self (or first argument) if not convertable +String.prototype.parseColor = function() { + var color = '#'; + if(this.slice(0,4) == 'rgb(') { + var cols = this.slice(4,this.length-1).split(','); + var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); + } else { + if(this.slice(0,1) == '#') { + if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); + if(this.length==7) color = this.toLowerCase(); + } + } + return(color.length==7 ? color : (arguments[0] || this)); +} + +/*--------------------------------------------------------------------------*/ + +Element.collectTextNodes = function(element) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); + }).flatten().join(''); +} + +Element.collectTextNodesIgnoreClass = function(element, className) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? + Element.collectTextNodesIgnoreClass(node, className) : '')); + }).flatten().join(''); +} + +Element.setContentZoom = function(element, percent) { + element = $(element); + element.setStyle({fontSize: (percent/100) + 'em'}); + if(Prototype.Browser.WebKit) window.scrollBy(0,0); + return element; +} + +Element.getInlineOpacity = function(element){ + return $(element).style.opacity || ''; +} + +Element.forceRerendering = function(element) { + try { + element = $(element); + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch(e) { } +}; + +/*--------------------------------------------------------------------------*/ + +Array.prototype.call = function() { + var args = arguments; + this.each(function(f){ f.apply(this, args) }); +} + +/*--------------------------------------------------------------------------*/ + +var Effect = { + _elementDoesNotExistError: { + name: 'ElementDoesNotExistError', + message: 'The specified DOM element does not exist, but is required for this effect to operate' + }, + tagifyText: function(element) { + if(typeof Builder == 'undefined') + throw("Effect.tagifyText requires including script.aculo.us' builder.js library"); + + var tagifyStyle = 'position:relative'; + if(Prototype.Browser.IE) tagifyStyle += ';zoom:1'; + + element = $(element); + $A(element.childNodes).each( function(child) { + if(child.nodeType==3) { + child.nodeValue.toArray().each( function(character) { + element.insertBefore( + Builder.node('span',{style: tagifyStyle}, + character == ' ' ? String.fromCharCode(160) : character), + child); + }); + Element.remove(child); + } + }); + }, + multiple: function(element, effect) { + var elements; + if(((typeof element == 'object') || + (typeof element == 'function')) && + (element.length)) + elements = element; + else + elements = $(element).childNodes; + + var options = Object.extend({ + speed: 0.1, + delay: 0.0 + }, arguments[2] || {}); + var masterDelay = options.delay; + + $A(elements).each( function(element, index) { + new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); + }); + }, + PAIRS: { + 'slide': ['SlideDown','SlideUp'], + 'blind': ['BlindDown','BlindUp'], + 'appear': ['Appear','Fade'] + }, + toggle: function(element, effect) { + element = $(element); + effect = (effect || 'appear').toLowerCase(); + var options = Object.extend({ + queue: { position:'end', scope:(element.id || 'global'), limit: 1 } + }, arguments[2] || {}); + Effect[element.visible() ? + Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options); + } +}; + +var Effect2 = Effect; // deprecated + +/* ------------- transitions ------------- */ + +Effect.Transitions = { + linear: Prototype.K, + sinoidal: function(pos) { + return (-Math.cos(pos*Math.PI)/2) + 0.5; + }, + reverse: function(pos) { + return 1-pos; + }, + flicker: function(pos) { + var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; + return (pos > 1 ? 1 : pos); + }, + wobble: function(pos) { + return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; + }, + pulse: function(pos, pulses) { + pulses = pulses || 5; + return ( + Math.round((pos % (1/pulses)) * pulses) == 0 ? + ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) : + 1 - ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) + ); + }, + none: function(pos) { + return 0; + }, + full: function(pos) { + return 1; + } +}; + +/* ------------- core effects ------------- */ + +Effect.ScopedQueue = Class.create(); +Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), { + initialize: function() { + this.effects = []; + this.interval = null; + }, + _each: function(iterator) { + this.effects._each(iterator); + }, + add: function(effect) { + var timestamp = new Date().getTime(); + + var position = (typeof effect.options.queue == 'string') ? + effect.options.queue : effect.options.queue.position; + + switch(position) { + case 'front': + // move unstarted effects after this effect + this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { + e.startOn += effect.finishOn; + e.finishOn += effect.finishOn; + }); + break; + case 'with-last': + timestamp = this.effects.pluck('startOn').max() || timestamp; + break; + case 'end': + // start effect after last queued effect has finished + timestamp = this.effects.pluck('finishOn').max() || timestamp; + break; + } + + effect.startOn += timestamp; + effect.finishOn += timestamp; + + if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) + this.effects.push(effect); + + if(!this.interval) + this.interval = setInterval(this.loop.bind(this), 15); + }, + remove: function(effect) { + this.effects = this.effects.reject(function(e) { return e==effect }); + if(this.effects.length == 0) { + clearInterval(this.interval); + this.interval = null; + } + }, + loop: function() { + var timePos = new Date().getTime(); + for(var i=0, len=this.effects.length;i= this.startOn) { + if(timePos >= this.finishOn) { + this.render(1.0); + this.cancel(); + this.event('beforeFinish'); + if(this.finish) this.finish(); + this.event('afterFinish'); + return; + } + var pos = (timePos - this.startOn) / this.totalTime, + frame = Math.round(pos * this.totalFrames); + if(frame > this.currentFrame) { + this.render(pos); + this.currentFrame = frame; + } + } + }, + cancel: function() { + if(!this.options.sync) + Effect.Queues.get(typeof this.options.queue == 'string' ? + 'global' : this.options.queue.scope).remove(this); + this.state = 'finished'; + }, + event: function(eventName) { + if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); + if(this.options[eventName]) this.options[eventName](this); + }, + inspect: function() { + var data = $H(); + for(property in this) + if(typeof this[property] != 'function') data[property] = this[property]; + return '#'; + } +} + +Effect.Parallel = Class.create(); +Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), { + initialize: function(effects) { + this.effects = effects || []; + this.start(arguments[1]); + }, + update: function(position) { + this.effects.invoke('render', position); + }, + finish: function(position) { + this.effects.each( function(effect) { + effect.render(1.0); + effect.cancel(); + effect.event('beforeFinish'); + if(effect.finish) effect.finish(position); + effect.event('afterFinish'); + }); + } +}); + +Effect.Event = Class.create(); +Object.extend(Object.extend(Effect.Event.prototype, Effect.Base.prototype), { + initialize: function() { + var options = Object.extend({ + duration: 0 + }, arguments[0] || {}); + this.start(options); + }, + update: Prototype.emptyFunction +}); + +Effect.Opacity = Class.create(); +Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + // make this work on IE on elements without 'layout' + if(Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) + this.element.setStyle({zoom: 1}); + var options = Object.extend({ + from: this.element.getOpacity() || 0.0, + to: 1.0 + }, arguments[1] || {}); + this.start(options); + }, + update: function(position) { + this.element.setOpacity(position); + } +}); + +Effect.Move = Class.create(); +Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + x: 0, + y: 0, + mode: 'relative' + }, arguments[1] || {}); + this.start(options); + }, + setup: function() { + // Bug in Opera: Opera returns the "real" position of a static element or + // relative element that does not have top/left explicitly set. + // ==> Always set top and left for position relative elements in your stylesheets + // (to 0 if you do not need them) + this.element.makePositioned(); + this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); + this.originalTop = parseFloat(this.element.getStyle('top') || '0'); + if(this.options.mode == 'absolute') { + // absolute movement, so we need to calc deltaX and deltaY + this.options.x = this.options.x - this.originalLeft; + this.options.y = this.options.y - this.originalTop; + } + }, + update: function(position) { + this.element.setStyle({ + left: Math.round(this.options.x * position + this.originalLeft) + 'px', + top: Math.round(this.options.y * position + this.originalTop) + 'px' + }); + } +}); + +// for backwards compatibility +Effect.MoveBy = function(element, toTop, toLeft) { + return new Effect.Move(element, + Object.extend({ x: toLeft, y: toTop }, arguments[3] || {})); +}; + +Effect.Scale = Class.create(); +Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), { + initialize: function(element, percent) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or {} with provided values + scaleFrom: 100.0, + scaleTo: percent + }, arguments[2] || {}); + this.start(options); + }, + setup: function() { + this.restoreAfterFinish = this.options.restoreAfterFinish || false; + this.elementPositioning = this.element.getStyle('position'); + + this.originalStyle = {}; + ['top','left','width','height','fontSize'].each( function(k) { + this.originalStyle[k] = this.element.style[k]; + }.bind(this)); + + this.originalTop = this.element.offsetTop; + this.originalLeft = this.element.offsetLeft; + + var fontSize = this.element.getStyle('font-size') || '100%'; + ['em','px','%','pt'].each( function(fontSizeType) { + if(fontSize.indexOf(fontSizeType)>0) { + this.fontSize = parseFloat(fontSize); + this.fontSizeType = fontSizeType; + } + }.bind(this)); + + this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; + + this.dims = null; + if(this.options.scaleMode=='box') + this.dims = [this.element.offsetHeight, this.element.offsetWidth]; + if(/^content/.test(this.options.scaleMode)) + this.dims = [this.element.scrollHeight, this.element.scrollWidth]; + if(!this.dims) + this.dims = [this.options.scaleMode.originalHeight, + this.options.scaleMode.originalWidth]; + }, + update: function(position) { + var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); + if(this.options.scaleContent && this.fontSize) + this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); + this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); + }, + finish: function(position) { + if(this.restoreAfterFinish) this.element.setStyle(this.originalStyle); + }, + setDimensions: function(height, width) { + var d = {}; + if(this.options.scaleX) d.width = Math.round(width) + 'px'; + if(this.options.scaleY) d.height = Math.round(height) + 'px'; + if(this.options.scaleFromCenter) { + var topd = (height - this.dims[0])/2; + var leftd = (width - this.dims[1])/2; + if(this.elementPositioning == 'absolute') { + if(this.options.scaleY) d.top = this.originalTop-topd + 'px'; + if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; + } else { + if(this.options.scaleY) d.top = -topd + 'px'; + if(this.options.scaleX) d.left = -leftd + 'px'; + } + } + this.element.setStyle(d); + } +}); + +Effect.Highlight = Class.create(); +Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {}); + this.start(options); + }, + setup: function() { + // Prevent executing on elements not in the layout flow + if(this.element.getStyle('display')=='none') { this.cancel(); return; } + // Disable background image during the effect + this.oldStyle = {}; + if (!this.options.keepBackgroundImage) { + this.oldStyle.backgroundImage = this.element.getStyle('background-image'); + this.element.setStyle({backgroundImage: 'none'}); + } + if(!this.options.endcolor) + this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); + if(!this.options.restorecolor) + this.options.restorecolor = this.element.getStyle('background-color'); + // init color calculations + this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); + 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)); + }, + update: function(position) { + this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ + return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) }); + }, + finish: function() { + this.element.setStyle(Object.extend(this.oldStyle, { + backgroundColor: this.options.restorecolor + })); + } +}); + +Effect.ScrollTo = Class.create(); +Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + this.start(arguments[1] || {}); + }, + setup: function() { + Position.prepare(); + var offsets = Position.cumulativeOffset(this.element); + if(this.options.offset) offsets[1] += this.options.offset; + var max = window.innerHeight ? + window.height - window.innerHeight : + document.body.scrollHeight - + (document.documentElement.clientHeight ? + document.documentElement.clientHeight : document.body.clientHeight); + this.scrollStart = Position.deltaY; + this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart; + }, + update: function(position) { + Position.prepare(); + window.scrollTo(Position.deltaX, + this.scrollStart + (position*this.delta)); + } +}); + +/* ------------- combination effects ------------- */ + +Effect.Fade = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + var options = Object.extend({ + from: element.getOpacity() || 1.0, + to: 0.0, + afterFinishInternal: function(effect) { + if(effect.options.to!=0) return; + effect.element.hide().setStyle({opacity: oldOpacity}); + }}, arguments[1] || {}); + return new Effect.Opacity(element,options); +} + +Effect.Appear = function(element) { + element = $(element); + var options = Object.extend({ + from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), + to: 1.0, + // force Safari to render floated elements properly + afterFinishInternal: function(effect) { + effect.element.forceRerendering(); + }, + beforeSetup: function(effect) { + effect.element.setOpacity(effect.options.from).show(); + }}, arguments[1] || {}); + return new Effect.Opacity(element,options); +} + +Effect.Puff = function(element) { + element = $(element); + var oldStyle = { + opacity: element.getInlineOpacity(), + position: element.getStyle('position'), + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height + }; + return new Effect.Parallel( + [ new Effect.Scale(element, 200, + { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], + Object.extend({ duration: 1.0, + beforeSetupInternal: function(effect) { + Position.absolutize(effect.effects[0].element) + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().setStyle(oldStyle); } + }, arguments[1] || {}) + ); +} + +Effect.BlindUp = function(element) { + element = $(element); + element.makeClipping(); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + restoreAfterFinish: true, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }, arguments[1] || {}) + ); +} + +Effect.BlindDown = function(element) { + element = $(element); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping(); + } + }, arguments[1] || {})); +} + +Effect.SwitchOff = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + return new Effect.Appear(element, Object.extend({ + duration: 0.4, + from: 0, + transition: Effect.Transitions.flicker, + afterFinishInternal: function(effect) { + new Effect.Scale(effect.element, 1, { + duration: 0.3, scaleFromCenter: true, + scaleX: false, scaleContent: false, restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity}); + } + }) + } + }, arguments[1] || {})); +} + +Effect.DropOut = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left'), + opacity: element.getInlineOpacity() }; + return new Effect.Parallel( + [ new Effect.Move(element, {x: 0, y: 100, sync: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 }) ], + Object.extend( + { duration: 0.5, + beforeSetup: function(effect) { + effect.effects[0].element.makePositioned(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle); + } + }, arguments[1] || {})); +} + +Effect.Shake = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left') }; + return new Effect.Move(element, + { x: 20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { + effect.element.undoPositioned().setStyle(oldStyle); + }}) }}) }}) }}) }}) }}); +} + +Effect.SlideDown = function(element) { + element = $(element).cleanWhitespace(); + // SlideDown need to have the content of the element wrapped in a container element with fixed height! + var oldInnerBottom = element.down().getStyle('bottom'); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: window.opera ? 0 : 1, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + effect.element.down().makePositioned(); + if(window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterUpdateInternal: function(effect) { + effect.element.down().setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping().undoPositioned(); + effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); } + }, arguments[1] || {}) + ); +} + +Effect.SlideUp = function(element) { + element = $(element).cleanWhitespace(); + var oldInnerBottom = element.down().getStyle('bottom'); + return new Effect.Scale(element, window.opera ? 0 : 1, + Object.extend({ scaleContent: false, + scaleX: false, + scaleMode: 'box', + scaleFrom: 100, + restoreAfterFinish: true, + beforeStartInternal: function(effect) { + effect.element.makePositioned(); + effect.element.down().makePositioned(); + if(window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping().show(); + }, + afterUpdateInternal: function(effect) { + effect.element.down().setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned().setStyle({bottom: oldInnerBottom}); + effect.element.down().undoPositioned(); + } + }, arguments[1] || {}) + ); +} + +// Bug in opera makes the TD containing this element expand for a instance after finish +Effect.Squish = function(element) { + return new Effect.Scale(element, window.opera ? 1 : 0, { + restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }); +} + +Effect.Grow = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.full + }, arguments[1] || {}); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = dims.width; + initialMoveY = moveY = 0; + moveX = -dims.width; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = dims.height; + moveY = -dims.height; + break; + case 'bottom-right': + initialMoveX = dims.width; + initialMoveY = dims.height; + moveX = -dims.width; + moveY = -dims.height; + break; + case 'center': + initialMoveX = dims.width / 2; + initialMoveY = dims.height / 2; + moveX = -dims.width / 2; + moveY = -dims.height / 2; + break; + } + + return new Effect.Move(element, { + x: initialMoveX, + y: initialMoveY, + duration: 0.01, + beforeSetup: function(effect) { + effect.element.hide().makeClipping().makePositioned(); + }, + afterFinishInternal: function(effect) { + new Effect.Parallel( + [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), + new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), + new Effect.Scale(effect.element, 100, { + scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, + sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) + ], Object.extend({ + beforeSetup: function(effect) { + effect.effects[0].element.setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); + } + }, options) + ) + } + }); +} + +Effect.Shrink = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.none + }, arguments[1] || {}); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = dims.width; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = dims.height; + break; + case 'bottom-right': + moveX = dims.width; + moveY = dims.height; + break; + case 'center': + moveX = dims.width / 2; + moveY = dims.height / 2; + break; + } + + return new Effect.Parallel( + [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), + new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), + new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }) + ], Object.extend({ + beforeStartInternal: function(effect) { + effect.effects[0].element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); } + }, options) + ); +} + +Effect.Pulsate = function(element) { + element = $(element); + var options = arguments[1] || {}; + var oldOpacity = element.getInlineOpacity(); + var transition = options.transition || Effect.Transitions.sinoidal; + var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) }; + reverser.bind(transition); + return new Effect.Opacity(element, + Object.extend(Object.extend({ duration: 2.0, from: 0, + afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } + }, options), {transition: reverser})); +} + +Effect.Fold = function(element) { + element = $(element); + var oldStyle = { + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height }; + element.makeClipping(); + return new Effect.Scale(element, 5, Object.extend({ + scaleContent: false, + scaleX: false, + afterFinishInternal: function(effect) { + new Effect.Scale(element, 1, { + scaleContent: false, + scaleY: false, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().setStyle(oldStyle); + } }); + }}, arguments[1] || {})); +}; + +Effect.Morph = Class.create(); +Object.extend(Object.extend(Effect.Morph.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + style: {} + }, arguments[1] || {}); + if (typeof options.style == 'string') { + if(options.style.indexOf(':') == -1) { + var cssText = '', selector = '.' + options.style; + $A(document.styleSheets).reverse().each(function(styleSheet) { + if (styleSheet.cssRules) cssRules = styleSheet.cssRules; + else if (styleSheet.rules) cssRules = styleSheet.rules; + $A(cssRules).reverse().each(function(rule) { + if (selector == rule.selectorText) { + cssText = rule.style.cssText; + throw $break; + } + }); + if (cssText) throw $break; + }); + this.style = cssText.parseStyle(); + options.afterFinishInternal = function(effect){ + effect.element.addClassName(effect.options.style); + effect.transforms.each(function(transform) { + if(transform.style != 'opacity') + effect.element.style[transform.style] = ''; + }); + } + } else this.style = options.style.parseStyle(); + } else this.style = $H(options.style) + this.start(options); + }, + setup: function(){ + function parseColor(color){ + if(!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff'; + color = color.parseColor(); + return $R(0,2).map(function(i){ + return parseInt( color.slice(i*2+1,i*2+3), 16 ) + }); + } + this.transforms = this.style.map(function(pair){ + var property = pair[0], value = pair[1], unit = null; + + if(value.parseColor('#zzzzzz') != '#zzzzzz') { + value = value.parseColor(); + unit = 'color'; + } else if(property == 'opacity') { + value = parseFloat(value); + if(Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) + this.element.setStyle({zoom: 1}); + } else if(Element.CSS_LENGTH.test(value)) { + var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/); + value = parseFloat(components[1]); + unit = (components.length == 3) ? components[2] : null; + } + + var originalValue = this.element.getStyle(property); + return { + style: property.camelize(), + originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), + targetValue: unit=='color' ? parseColor(value) : value, + unit: unit + }; + }.bind(this)).reject(function(transform){ + return ( + (transform.originalValue == transform.targetValue) || + ( + transform.unit != 'color' && + (isNaN(transform.originalValue) || isNaN(transform.targetValue)) + ) + ) + }); + }, + update: function(position) { + var style = {}, transform, i = this.transforms.length; + while(i--) + style[(transform = this.transforms[i]).style] = + transform.unit=='color' ? '#'+ + (Math.round(transform.originalValue[0]+ + (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() + + (Math.round(transform.originalValue[1]+ + (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() + + (Math.round(transform.originalValue[2]+ + (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() : + transform.originalValue + Math.round( + ((transform.targetValue - transform.originalValue) * position) * 1000)/1000 + transform.unit; + this.element.setStyle(style, true); + } +}); + +Effect.Transform = Class.create(); +Object.extend(Effect.Transform.prototype, { + initialize: function(tracks){ + this.tracks = []; + this.options = arguments[1] || {}; + this.addTracks(tracks); + }, + addTracks: function(tracks){ + tracks.each(function(track){ + var data = $H(track).values().first(); + this.tracks.push($H({ + ids: $H(track).keys().first(), + effect: Effect.Morph, + options: { style: data } + })); + }.bind(this)); + return this; + }, + play: function(){ + return new Effect.Parallel( + this.tracks.map(function(track){ + var elements = [$(track.ids) || $$(track.ids)].flatten(); + return elements.map(function(e){ return new track.effect(e, Object.extend({ sync:true }, track.options)) }); + }).flatten(), + this.options + ); + } +}); + +Element.CSS_PROPERTIES = $w( + 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + + 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' + + 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' + + 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' + + 'fontSize fontWeight height left letterSpacing lineHeight ' + + 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+ + 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' + + 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' + + 'right textIndent top width wordSpacing zIndex'); + +Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/; + +String.prototype.parseStyle = function(){ + var element = document.createElement('div'); + element.innerHTML = '

    '; + var style = element.childNodes[0].style, styleRules = $H(); + + Element.CSS_PROPERTIES.each(function(property){ + if(style[property]) styleRules[property] = style[property]; + }); + if(Prototype.Browser.IE && this.indexOf('opacity') > -1) { + styleRules.opacity = this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]; + } + return styleRules; +}; + +Element.morph = function(element, style) { + new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || {})); + return element; +}; + +['getInlineOpacity','forceRerendering','setContentZoom', + 'collectTextNodes','collectTextNodesIgnoreClass','morph'].each( + function(f) { Element.Methods[f] = Element[f]; } +); + +Element.Methods.visualEffect = function(element, effect, options) { + s = effect.dasherize().camelize(); + effect_class = s.charAt(0).toUpperCase() + s.substring(1); + new Effect[effect_class](element, options); + return $(element); +}; + +Element.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 @@ +// script.aculo.us scriptaculous.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007 + +// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +var Scriptaculous = { + Version: '1.7.1_beta3', + require: function(libraryName) { + // inserting via DOM fails in Safari 2.0, so brute force approach + document.write(''); + }, + REQUIRED_PROTOTYPE: '1.5.1', + load: function() { + function convertVersionString(versionString){ + var r = versionString.split('.'); + return parseInt(r[0])*100000 + parseInt(r[1])*1000 + parseInt(r[2]); + } + + if((typeof Prototype=='undefined') || + (typeof Element == 'undefined') || + (typeof Element.Methods=='undefined') || + (convertVersionString(Prototype.Version) < + convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE))) + throw("script.aculo.us requires the Prototype JavaScript framework >= " + + Scriptaculous.REQUIRED_PROTOTYPE); + + $A(document.getElementsByTagName("script")).findAll( function(s) { + return (s.src && s.src.match(/scriptaculous\.js(\?.*)?$/)) + }).each( function(s) { + var path = s.src.replace(/scriptaculous\.js(\?.*)?$/,''); + var includes = s.src.match(/\?.*load=([a-z,]*)/); + (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each( + function(include) { Scriptaculous.require(path+include+'.js') }); + }); + } +} + +Scriptaculous.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 @@ +// script.aculo.us slider.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007 + +// Copyright (c) 2005-2007 Marty Haught, Thomas Fuchs +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +if(!Control) var Control = {}; +Control.Slider = Class.create(); + +// options: +// axis: 'vertical', or 'horizontal' (default) +// +// callbacks: +// onChange(value) +// onSlide(value) +Control.Slider.prototype = { + initialize: function(handle, track, options) { + var slider = this; + + if(handle instanceof Array) { + this.handles = handle.collect( function(e) { return $(e) }); + } else { + this.handles = [$(handle)]; + } + + this.track = $(track); + this.options = options || {}; + + this.axis = this.options.axis || 'horizontal'; + this.increment = this.options.increment || 1; + this.step = parseInt(this.options.step || '1'); + this.range = this.options.range || $R(0,1); + + this.value = 0; // assure backwards compat + this.values = this.handles.map( function() { return 0 }); + this.spans = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false; + this.options.startSpan = $(this.options.startSpan || null); + this.options.endSpan = $(this.options.endSpan || null); + + this.restricted = this.options.restricted || false; + + this.maximum = this.options.maximum || this.range.end; + this.minimum = this.options.minimum || this.range.start; + + // Will be used to align the handle onto the track, if necessary + this.alignX = parseInt(this.options.alignX || '0'); + this.alignY = parseInt(this.options.alignY || '0'); + + this.trackLength = this.maximumOffset() - this.minimumOffset(); + + this.handleLength = this.isVertical() ? + (this.handles[0].offsetHeight != 0 ? + this.handles[0].offsetHeight : this.handles[0].style.height.replace(/px$/,"")) : + (this.handles[0].offsetWidth != 0 ? this.handles[0].offsetWidth : + this.handles[0].style.width.replace(/px$/,"")); + + this.active = false; + this.dragging = false; + this.disabled = false; + + if(this.options.disabled) this.setDisabled(); + + // Allowed values array + this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false; + if(this.allowedValues) { + this.minimum = this.allowedValues.min(); + this.maximum = this.allowedValues.max(); + } + + this.eventMouseDown = this.startDrag.bindAsEventListener(this); + this.eventMouseUp = this.endDrag.bindAsEventListener(this); + this.eventMouseMove = this.update.bindAsEventListener(this); + + // Initialize handles in reverse (make sure first handle is active) + this.handles.each( function(h,i) { + i = slider.handles.length-1-i; + slider.setValue(parseFloat( + (slider.options.sliderValue instanceof Array ? + slider.options.sliderValue[i] : slider.options.sliderValue) || + slider.range.start), i); + Element.makePositioned(h); // fix IE + Event.observe(h, "mousedown", slider.eventMouseDown); + }); + + Event.observe(this.track, "mousedown", this.eventMouseDown); + Event.observe(document, "mouseup", this.eventMouseUp); + Event.observe(document, "mousemove", this.eventMouseMove); + + this.initialized = true; + }, + dispose: function() { + var slider = this; + Event.stopObserving(this.track, "mousedown", this.eventMouseDown); + Event.stopObserving(document, "mouseup", this.eventMouseUp); + Event.stopObserving(document, "mousemove", this.eventMouseMove); + this.handles.each( function(h) { + Event.stopObserving(h, "mousedown", slider.eventMouseDown); + }); + }, + setDisabled: function(){ + this.disabled = true; + }, + setEnabled: function(){ + this.disabled = false; + }, + getNearestValue: function(value){ + if(this.allowedValues){ + if(value >= this.allowedValues.max()) return(this.allowedValues.max()); + if(value <= this.allowedValues.min()) return(this.allowedValues.min()); + + var offset = Math.abs(this.allowedValues[0] - value); + var newValue = this.allowedValues[0]; + this.allowedValues.each( function(v) { + var currentOffset = Math.abs(v - value); + if(currentOffset <= offset){ + newValue = v; + offset = currentOffset; + } + }); + return newValue; + } + if(value > this.range.end) return this.range.end; + if(value < this.range.start) return this.range.start; + return value; + }, + setValue: function(sliderValue, handleIdx){ + if(!this.active) { + this.activeHandleIdx = handleIdx || 0; + this.activeHandle = this.handles[this.activeHandleIdx]; + this.updateStyles(); + } + handleIdx = handleIdx || this.activeHandleIdx || 0; + if(this.initialized && this.restricted) { + if((handleIdx>0) && (sliderValuethis.values[handleIdx+1])) + sliderValue = this.values[handleIdx+1]; + } + sliderValue = this.getNearestValue(sliderValue); + this.values[handleIdx] = sliderValue; + this.value = this.values[0]; // assure backwards compat + + this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] = + this.translateToPx(sliderValue); + + this.drawSpans(); + if(!this.dragging || !this.event) this.updateFinished(); + }, + setValueBy: function(delta, handleIdx) { + this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta, + handleIdx || this.activeHandleIdx || 0); + }, + translateToPx: function(value) { + return Math.round( + ((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) * + (value - this.range.start)) + "px"; + }, + translateToValue: function(offset) { + return ((offset/(this.trackLength-this.handleLength) * + (this.range.end-this.range.start)) + this.range.start); + }, + getRange: function(range) { + var v = this.values.sortBy(Prototype.K); + range = range || 0; + return $R(v[range],v[range+1]); + }, + minimumOffset: function(){ + return(this.isVertical() ? this.alignY : this.alignX); + }, + maximumOffset: function(){ + return(this.isVertical() ? + (this.track.offsetHeight != 0 ? this.track.offsetHeight : + this.track.style.height.replace(/px$/,"")) - this.alignY : + (this.track.offsetWidth != 0 ? this.track.offsetWidth : + this.track.style.width.replace(/px$/,"")) - this.alignY); + }, + isVertical: function(){ + return (this.axis == 'vertical'); + }, + drawSpans: function() { + var slider = this; + if(this.spans) + $R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) }); + if(this.options.startSpan) + this.setSpan(this.options.startSpan, + $R(0, this.values.length>1 ? this.getRange(0).min() : this.value )); + if(this.options.endSpan) + this.setSpan(this.options.endSpan, + $R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum)); + }, + setSpan: function(span, range) { + if(this.isVertical()) { + span.style.top = this.translateToPx(range.start); + span.style.height = this.translateToPx(range.end - range.start + this.range.start); + } else { + span.style.left = this.translateToPx(range.start); + span.style.width = this.translateToPx(range.end - range.start + this.range.start); + } + }, + updateStyles: function() { + this.handles.each( function(h){ Element.removeClassName(h, 'selected') }); + Element.addClassName(this.activeHandle, 'selected'); + }, + startDrag: function(event) { + if(Event.isLeftClick(event)) { + if(!this.disabled){ + this.active = true; + + var handle = Event.element(event); + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var track = handle; + if(track==this.track) { + var offsets = Position.cumulativeOffset(this.track); + this.event = event; + this.setValue(this.translateToValue( + (this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2) + )); + var offsets = Position.cumulativeOffset(this.activeHandle); + this.offsetX = (pointer[0] - offsets[0]); + this.offsetY = (pointer[1] - offsets[1]); + } else { + // find the handle (prevents issues with Safari) + while((this.handles.indexOf(handle) == -1) && handle.parentNode) + handle = handle.parentNode; + + if(this.handles.indexOf(handle)!=-1) { + this.activeHandle = handle; + this.activeHandleIdx = this.handles.indexOf(this.activeHandle); + this.updateStyles(); + + var offsets = Position.cumulativeOffset(this.activeHandle); + this.offsetX = (pointer[0] - offsets[0]); + this.offsetY = (pointer[1] - offsets[1]); + } + } + } + Event.stop(event); + } + }, + update: function(event) { + if(this.active) { + if(!this.dragging) this.dragging = true; + this.draw(event); + if(Prototype.Browser.WebKit) window.scrollBy(0,0); + Event.stop(event); + } + }, + draw: function(event) { + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var offsets = Position.cumulativeOffset(this.track); + pointer[0] -= this.offsetX + offsets[0]; + pointer[1] -= this.offsetY + offsets[1]; + this.event = event; + this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] )); + if(this.initialized && this.options.onSlide) + this.options.onSlide(this.values.length>1 ? this.values : this.value, this); + }, + endDrag: function(event) { + if(this.active && this.dragging) { + this.finishDrag(event, true); + Event.stop(event); + } + this.active = false; + this.dragging = false; + }, + finishDrag: function(event, success) { + this.active = false; + this.dragging = false; + this.updateFinished(); + }, + updateFinished: function() { + if(this.initialized && this.options.onChange) + this.options.onChange(this.values.length>1 ? this.values : this.value, this); + this.event = null; + } +} \ 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 @@ +// script.aculo.us sound.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007 + +// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// +// Based on code created by Jules Gravinese (http://www.webveteran.com/) +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +Sound = { + tracks: {}, + _enabled: true, + template: + new Template(''), + enable: function(){ + Sound._enabled = true; + }, + disable: function(){ + Sound._enabled = false; + }, + play: function(url){ + if(!Sound._enabled) return; + var options = Object.extend({ + track: 'global', url: url, replace: false + }, arguments[1] || {}); + + if(options.replace && this.tracks[options.track]) { + $R(0, this.tracks[options.track].id).each(function(id){ + var sound = $('sound_'+options.track+'_'+id); + sound.Stop && sound.Stop(); + sound.remove(); + }) + this.tracks[options.track] = null; + } + + if(!this.tracks[options.track]) + this.tracks[options.track] = { id: 0 } + else + this.tracks[options.track].id++; + + options.id = this.tracks[options.track].id; + if (Prototype.Browser.IE) { + var sound = document.createElement('bgsound'); + sound.setAttribute('id','sound_'+options.track+'_'+options.id); + sound.setAttribute('src',options.url); + sound.setAttribute('loop','1'); + sound.setAttribute('autostart','true'); + $$('body')[0].appendChild(sound); + } + else + new Insertion.Bottom($$('body')[0], Sound.template.evaluate(options)); + } +}; + +if(Prototype.Browser.Gecko && navigator.userAgent.indexOf("Win") > 0){ + if(navigator.plugins && $A(navigator.plugins).detect(function(p){ return p.name.indexOf('QuickTime') != -1 })) + Sound.template = new Template('') + else + Sound.play = function(){} +} 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 @@ +// script.aculo.us unittest.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007 + +// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005-2007 Jon Tirsen (http://www.tirsen.com) +// (c) 2005-2007 Michael Schuerig (http://www.schuerig.de/michael/) +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +// experimental, Firefox-only +Event.simulateMouse = function(element, eventName) { + var options = Object.extend({ + pointerX: 0, + pointerY: 0, + buttons: 0, + ctrlKey: false, + altKey: false, + shiftKey: false, + metaKey: false + }, arguments[2] || {}); + var oEvent = document.createEvent("MouseEvents"); + oEvent.initMouseEvent(eventName, true, true, document.defaultView, + options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY, + options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 0, $(element)); + + if(this.mark) Element.remove(this.mark); + this.mark = document.createElement('div'); + this.mark.appendChild(document.createTextNode(" ")); + document.body.appendChild(this.mark); + this.mark.style.position = 'absolute'; + this.mark.style.top = options.pointerY + "px"; + this.mark.style.left = options.pointerX + "px"; + this.mark.style.width = "5px"; + this.mark.style.height = "5px;"; + this.mark.style.borderTop = "1px solid red;" + this.mark.style.borderLeft = "1px solid red;" + + if(this.step) + alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options)); + + $(element).dispatchEvent(oEvent); +}; + +// 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. +// You need to downgrade to 1.0.4 for now to get this working +// See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much +Event.simulateKey = function(element, eventName) { + var options = Object.extend({ + ctrlKey: false, + altKey: false, + shiftKey: false, + metaKey: false, + keyCode: 0, + charCode: 0 + }, arguments[2] || {}); + + var oEvent = document.createEvent("KeyEvents"); + oEvent.initKeyEvent(eventName, true, true, window, + options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, + options.keyCode, options.charCode ); + $(element).dispatchEvent(oEvent); +}; + +Event.simulateKeys = function(element, command) { + for(var i=0; i' + + '' + + '' + + '' + + '
    StatusTestMessage
    '; + this.logsummary = $('logsummary') + this.loglines = $('loglines'); + }, + _toHTML: function(txt) { + return txt.escapeHTML().replace(/\n/g,"
    "); + }, + addLinksToResults: function(){ + $$("tr.failed .nameCell").each( function(td){ // todo: limit to children of this.log + td.title = "Run only this test" + Event.observe(td, 'click', function(){ window.location.search = "?tests=" + td.innerHTML;}); + }); + $$("tr.passed .nameCell").each( function(td){ // todo: limit to children of this.log + td.title = "Run all tests" + Event.observe(td, 'click', function(){ window.location.search = "";}); + }); + } +} + +Test.Unit.Runner = Class.create(); +Test.Unit.Runner.prototype = { + initialize: function(testcases) { + this.options = Object.extend({ + testLog: 'testlog' + }, arguments[1] || {}); + this.options.resultsURL = this.parseResultsURLQueryParameter(); + this.options.tests = this.parseTestsQueryParameter(); + if (this.options.testLog) { + this.options.testLog = $(this.options.testLog) || null; + } + if(this.options.tests) { + this.tests = []; + for(var i = 0; i < this.options.tests.length; i++) { + if(/^test/.test(this.options.tests[i])) { + this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"])); + } + } + } else { + if (this.options.test) { + this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])]; + } else { + this.tests = []; + for(var testcase in testcases) { + if(/^test/.test(testcase)) { + this.tests.push( + new Test.Unit.Testcase( + this.options.context ? ' -> ' + this.options.titles[testcase] : testcase, + testcases[testcase], testcases["setup"], testcases["teardown"] + )); + } + } + } + } + this.currentTest = 0; + this.logger = new Test.Unit.Logger(this.options.testLog); + setTimeout(this.runTests.bind(this), 1000); + }, + parseResultsURLQueryParameter: function() { + return window.location.search.parseQuery()["resultsURL"]; + }, + parseTestsQueryParameter: function(){ + if (window.location.search.parseQuery()["tests"]){ + return window.location.search.parseQuery()["tests"].split(','); + }; + }, + // Returns: + // "ERROR" if there was an error, + // "FAILURE" if there was a failure, or + // "SUCCESS" if there was neither + getResult: function() { + var hasFailure = false; + for(var i=0;i 0) { + return "ERROR"; + } + if (this.tests[i].failures > 0) { + hasFailure = true; + } + } + if (hasFailure) { + return "FAILURE"; + } else { + return "SUCCESS"; + } + }, + postResults: function() { + if (this.options.resultsURL) { + new Ajax.Request(this.options.resultsURL, + { method: 'get', parameters: 'result=' + this.getResult(), asynchronous: false }); + } + }, + runTests: function() { + var test = this.tests[this.currentTest]; + if (!test) { + // finished! + this.postResults(); + this.logger.summary(this.summary()); + return; + } + if(!test.isWaiting) { + this.logger.start(test.name); + } + test.run(); + if(test.isWaiting) { + this.logger.message("Waiting for " + test.timeToWait + "ms"); + setTimeout(this.runTests.bind(this), test.timeToWait || 1000); + } else { + this.logger.finish(test.status(), test.summary()); + this.currentTest++; + // tail recursive, hopefully the browser will skip the stackframe + this.runTests(); + } + }, + summary: function() { + var assertions = 0; + var failures = 0; + var errors = 0; + var messages = []; + for(var i=0;i 0) return 'failed'; + if (this.errors > 0) return 'error'; + return 'passed'; + }, + assert: function(expression) { + var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"'; + try { expression ? this.pass() : + this.fail(message); } + catch(e) { this.error(e); } + }, + assertEqual: function(expected, actual) { + var message = arguments[2] || "assertEqual"; + try { (expected == actual) ? this.pass() : + this.fail(message + ': expected "' + Test.Unit.inspect(expected) + + '", actual "' + Test.Unit.inspect(actual) + '"'); } + catch(e) { this.error(e); } + }, + assertInspect: function(expected, actual) { + var message = arguments[2] || "assertInspect"; + try { (expected == actual.inspect()) ? this.pass() : + this.fail(message + ': expected "' + Test.Unit.inspect(expected) + + '", actual "' + Test.Unit.inspect(actual) + '"'); } + catch(e) { this.error(e); } + }, + assertEnumEqual: function(expected, actual) { + var message = arguments[2] || "assertEnumEqual"; + try { $A(expected).length == $A(actual).length && + expected.zip(actual).all(function(pair) { return pair[0] == pair[1] }) ? + this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) + + ', actual ' + Test.Unit.inspect(actual)); } + catch(e) { this.error(e); } + }, + assertNotEqual: function(expected, actual) { + var message = arguments[2] || "assertNotEqual"; + try { (expected != actual) ? this.pass() : + this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); } + catch(e) { this.error(e); } + }, + assertIdentical: function(expected, actual) { + var message = arguments[2] || "assertIdentical"; + try { (expected === actual) ? this.pass() : + this.fail(message + ': expected "' + Test.Unit.inspect(expected) + + '", actual "' + Test.Unit.inspect(actual) + '"'); } + catch(e) { this.error(e); } + }, + assertNotIdentical: function(expected, actual) { + var message = arguments[2] || "assertNotIdentical"; + try { !(expected === actual) ? this.pass() : + this.fail(message + ': expected "' + Test.Unit.inspect(expected) + + '", actual "' + Test.Unit.inspect(actual) + '"'); } + catch(e) { this.error(e); } + }, + assertNull: function(obj) { + var message = arguments[1] || 'assertNull' + try { (obj==null) ? this.pass() : + this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); } + catch(e) { this.error(e); } + }, + assertMatch: function(expected, actual) { + var message = arguments[2] || 'assertMatch'; + var regex = new RegExp(expected); + try { (regex.exec(actual)) ? this.pass() : + this.fail(message + ' : regex: "' + Test.Unit.inspect(expected) + ' did not match: ' + Test.Unit.inspect(actual) + '"'); } + catch(e) { this.error(e); } + }, + assertHidden: function(element) { + var message = arguments[1] || 'assertHidden'; + this.assertEqual("none", element.style.display, message); + }, + assertNotNull: function(object) { + var message = arguments[1] || 'assertNotNull'; + this.assert(object != null, message); + }, + assertType: function(expected, actual) { + var message = arguments[2] || 'assertType'; + try { + (actual.constructor == expected) ? this.pass() : + this.fail(message + ': expected "' + Test.Unit.inspect(expected) + + '", actual "' + (actual.constructor) + '"'); } + catch(e) { this.error(e); } + }, + assertNotOfType: function(expected, actual) { + var message = arguments[2] || 'assertNotOfType'; + try { + (actual.constructor != expected) ? this.pass() : + this.fail(message + ': expected "' + Test.Unit.inspect(expected) + + '", actual "' + (actual.constructor) + '"'); } + catch(e) { this.error(e); } + }, + assertInstanceOf: function(expected, actual) { + var message = arguments[2] || 'assertInstanceOf'; + try { + (actual instanceof expected) ? this.pass() : + this.fail(message + ": object was not an instance of the expected type"); } + catch(e) { this.error(e); } + }, + assertNotInstanceOf: function(expected, actual) { + var message = arguments[2] || 'assertNotInstanceOf'; + try { + !(actual instanceof expected) ? this.pass() : + this.fail(message + ": object was an instance of the not expected type"); } + catch(e) { this.error(e); } + }, + assertRespondsTo: function(method, obj) { + var message = arguments[2] || 'assertRespondsTo'; + try { + (obj[method] && typeof obj[method] == 'function') ? this.pass() : + this.fail(message + ": object doesn't respond to [" + method + "]"); } + catch(e) { this.error(e); } + }, + assertReturnsTrue: function(method, obj) { + var message = arguments[2] || 'assertReturnsTrue'; + try { + var m = obj[method]; + if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)]; + m() ? this.pass() : + this.fail(message + ": method returned false"); } + catch(e) { this.error(e); } + }, + assertReturnsFalse: function(method, obj) { + var message = arguments[2] || 'assertReturnsFalse'; + try { + var m = obj[method]; + if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)]; + !m() ? this.pass() : + this.fail(message + ": method returned true"); } + catch(e) { this.error(e); } + }, + assertRaise: function(exceptionName, method) { + var message = arguments[2] || 'assertRaise'; + try { + method(); + this.fail(message + ": exception expected but none was raised"); } + catch(e) { + ((exceptionName == null) || (e.name==exceptionName)) ? this.pass() : this.error(e); + } + }, + assertElementsMatch: function() { + var expressions = $A(arguments), elements = $A(expressions.shift()); + if (elements.length != expressions.length) { + this.fail('assertElementsMatch: size mismatch: ' + elements.length + ' elements, ' + expressions.length + ' expressions'); + return false; + } + elements.zip(expressions).all(function(pair, index) { + var element = $(pair.first()), expression = pair.last(); + if (element.match(expression)) return true; + this.fail('assertElementsMatch: (in index ' + index + ') expected ' + expression.inspect() + ' but got ' + element.inspect()); + }.bind(this)) && this.pass(); + }, + assertElementMatches: function(element, expression) { + this.assertElementsMatch([element], expression); + }, + benchmark: function(operation, iterations) { + var startAt = new Date(); + (iterations || 1).times(operation); + var timeTaken = ((new Date())-startAt); + this.info((arguments[2] || 'Operation') + ' finished ' + + iterations + ' iterations in ' + (timeTaken/1000)+'s' ); + return timeTaken; + }, + _isVisible: function(element) { + element = $(element); + if(!element.parentNode) return true; + this.assertNotNull(element); + if(element.style && Element.getStyle(element, 'display') == 'none') + return false; + + return this._isVisible(element.parentNode); + }, + assertNotVisible: function(element) { + this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1])); + }, + assertVisible: function(element) { + this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1])); + }, + benchmark: function(operation, iterations) { + var startAt = new Date(); + (iterations || 1).times(operation); + var timeTaken = ((new Date())-startAt); + this.info((arguments[2] || 'Operation') + ' finished ' + + iterations + ' iterations in ' + (timeTaken/1000)+'s' ); + return timeTaken; + } +} + +Test.Unit.Testcase = Class.create(); +Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), { + initialize: function(name, test, setup, teardown) { + Test.Unit.Assertions.prototype.initialize.bind(this)(); + this.name = name; + + if(typeof test == 'string') { + test = test.gsub(/(\.should[^\(]+\()/,'#{0}this,'); + test = test.gsub(/(\.should[^\(]+)\(this,\)/,'#{1}(this)'); + this.test = function() { + eval('with(this){'+test+'}'); + } + } else { + this.test = test || function() {}; + } + + this.setup = setup || function() {}; + this.teardown = teardown || function() {}; + this.isWaiting = false; + this.timeToWait = 1000; + }, + wait: function(time, nextPart) { + this.isWaiting = true; + this.test = nextPart; + this.timeToWait = time; + }, + run: function() { + try { + try { + if (!this.isWaiting) this.setup.bind(this)(); + this.isWaiting = false; + this.test.bind(this)(); + } finally { + if(!this.isWaiting) { + this.teardown.bind(this)(); + } + } + } + catch(e) { this.error(e); } + } +}); + +// *EXPERIMENTAL* BDD-style testing to please non-technical folk +// This draws many ideas from RSpec http://rspec.rubyforge.org/ + +Test.setupBDDExtensionMethods = function(){ + var METHODMAP = { + shouldEqual: 'assertEqual', + shouldNotEqual: 'assertNotEqual', + shouldEqualEnum: 'assertEnumEqual', + shouldBeA: 'assertType', + shouldNotBeA: 'assertNotOfType', + shouldBeAn: 'assertType', + shouldNotBeAn: 'assertNotOfType', + shouldBeNull: 'assertNull', + shouldNotBeNull: 'assertNotNull', + + shouldBe: 'assertReturnsTrue', + shouldNotBe: 'assertReturnsFalse', + shouldRespondTo: 'assertRespondsTo' + }; + Test.BDDMethods = {}; + for(m in METHODMAP) { + Test.BDDMethods[m] = eval( + 'function(){'+ + 'var args = $A(arguments);'+ + 'var scope = args.shift();'+ + 'scope.'+METHODMAP[m]+'.apply(scope,(args || []).concat([this])); }'); + } + [Array.prototype, String.prototype, Number.prototype].each( + function(p){ Object.extend(p, Test.BDDMethods) } + ); +} + +Test.context = function(name, spec, log){ + Test.setupBDDExtensionMethods(); + + var compiledSpec = {}; + var titles = {}; + for(specName in spec) { + switch(specName){ + case "setup": + case "teardown": + compiledSpec[specName] = spec[specName]; + break; + default: + var testName = 'test'+specName.gsub(/\s+/,'-').camelize(); + var body = spec[specName].toString().split('\n').slice(1); + if(/^\{/.test(body[0])) body = body.slice(1); + body.pop(); + body = body.map(function(statement){ + return statement.strip() + }); + compiledSpec[testName] = body.join('\n'); + titles[testName] = specName; + } + } + new Test.Unit.Runner(compiledSpec, { titles: titles, testLog: log || 'testlog', context: name }); +}; \ 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 @@ +/** + * SWFObject v1.5: Flash Player detection and embed - http://blog.deconcept.com/swfobject/ + * + * SWFObject is (c) 2007 Geoff Stearns and is released under the MIT License: + * http://www.opensource.org/licenses/mit-license.php + * + */ +if(typeof deconcept == "undefined") var deconcept = new Object(); +if(typeof deconcept.util == "undefined") deconcept.util = new Object(); +if(typeof deconcept.SWFObjectUtil == "undefined") deconcept.SWFObjectUtil = new Object(); +deconcept.SWFObject = function(swf, id, w, h, ver, c, quality, xiRedirectUrl, redirectUrl, detectKey) { + if (!document.getElementById) { return; } + this.DETECT_KEY = detectKey ? detectKey : 'detectflash'; + this.skipDetect = deconcept.util.getRequestParameter(this.DETECT_KEY); + this.params = new Object(); + this.variables = new Object(); + this.attributes = new Array(); + if(swf) { this.setAttribute('swf', swf); } + if(id) { this.setAttribute('id', id); } + if(w) { this.setAttribute('width', w); } + if(h) { this.setAttribute('height', h); } + if(ver) { this.setAttribute('version', new deconcept.PlayerVersion(ver.toString().split("."))); } + this.installedVer = deconcept.SWFObjectUtil.getPlayerVersion(); + if (!window.opera && document.all && this.installedVer.major > 7) { + // only add the onunload cleanup if the Flash Player version supports External Interface and we are in IE + deconcept.SWFObject.doPrepUnload = true; + } + if(c) { this.addParam('bgcolor', c); } + var q = quality ? quality : 'high'; + this.addParam('quality', q); + this.setAttribute('useExpressInstall', false); + this.setAttribute('doExpressInstall', false); + var xir = (xiRedirectUrl) ? xiRedirectUrl : window.location; + this.setAttribute('xiRedirectUrl', xir); + this.setAttribute('redirectUrl', ''); + if(redirectUrl) { this.setAttribute('redirectUrl', redirectUrl); } +} +deconcept.SWFObject.prototype = { + useExpressInstall: function(path) { + this.xiSWFPath = !path ? "expressinstall.swf" : path; + this.setAttribute('useExpressInstall', true); + }, + setAttribute: function(name, value){ + this.attributes[name] = value; + }, + getAttribute: function(name){ + return this.attributes[name]; + }, + addParam: function(name, value){ + this.params[name] = value; + }, + getParams: function(){ + return this.params; + }, + addVariable: function(name, value){ + this.variables[name] = value; + }, + getVariable: function(name){ + return this.variables[name]; + }, + getVariables: function(){ + return this.variables; + }, + getVariablePairs: function(){ + var variablePairs = new Array(); + var key; + var variables = this.getVariables(); + for(key in variables){ + variablePairs[variablePairs.length] = key +"="+ variables[key]; + } + return variablePairs; + }, + getSWFHTML: function() { + var swfNode = ""; + if (navigator.plugins && navigator.mimeTypes && navigator.mimeTypes.length) { // netscape plugin architecture + if (this.getAttribute("doExpressInstall")) { + this.addVariable("MMplayerType", "PlugIn"); + this.setAttribute('swf', this.xiSWFPath); + } + swfNode = ' 0){ swfNode += 'flashvars="'+ pairs +'"'; } + swfNode += '/>'; + } else { // PC IE + if (this.getAttribute("doExpressInstall")) { + this.addVariable("MMplayerType", "ActiveX"); + this.setAttribute('swf', this.xiSWFPath); + } + swfNode = ''; + swfNode += ''; + var params = this.getParams(); + for(var key in params) { + swfNode += ''; + } + var pairs = this.getVariablePairs().join("&"); + if(pairs.length > 0) {swfNode += '';} + swfNode += ""; + } + return swfNode; + }, + write: function(elementId){ + if(this.getAttribute('useExpressInstall')) { + // check to see if we need to do an express install + var expressInstallReqVer = new deconcept.PlayerVersion([6,0,65]); + if (this.installedVer.versionIsValid(expressInstallReqVer) && !this.installedVer.versionIsValid(this.getAttribute('version'))) { + this.setAttribute('doExpressInstall', true); + this.addVariable("MMredirectURL", escape(this.getAttribute('xiRedirectUrl'))); + document.title = document.title.slice(0, 47) + " - Flash Player Installation"; + this.addVariable("MMdoctitle", document.title); + } + } + if(this.skipDetect || this.getAttribute('doExpressInstall') || this.installedVer.versionIsValid(this.getAttribute('version'))){ + var n = (typeof elementId == 'string') ? document.getElementById(elementId) : elementId; + n.innerHTML = this.getSWFHTML(); + return true; + }else{ + if(this.getAttribute('redirectUrl') != "") { + document.location.replace(this.getAttribute('redirectUrl')); + } + } + return false; + } +} + +/* ---- detection functions ---- */ +deconcept.SWFObjectUtil.getPlayerVersion = function(){ + var PlayerVersion = new deconcept.PlayerVersion([0,0,0]); + if(navigator.plugins && navigator.mimeTypes.length){ + var x = navigator.plugins["Shockwave Flash"]; + if(x && x.description) { + PlayerVersion = new deconcept.PlayerVersion(x.description.replace(/([a-zA-Z]|\s)+/, "").replace(/(\s+r|\s+b[0-9]+)/, ".").split(".")); + } + }else if (navigator.userAgent && navigator.userAgent.indexOf("Windows CE") >= 0){ // if Windows CE + var axo = 1; + var counter = 3; + while(axo) { + try { + counter++; + axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash."+ counter); +// document.write("player v: "+ counter); + PlayerVersion = new deconcept.PlayerVersion([counter,0,0]); + } catch (e) { + axo = null; + } + } + } else { // Win IE (non mobile) + // do minor version lookup in IE, but avoid fp6 crashing issues + // see http://blog.deconcept.com/2006/01/11/getvariable-setvariable-crash-internet-explorer-flash-6/ + try{ + var axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7"); + }catch(e){ + try { + var axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6"); + PlayerVersion = new deconcept.PlayerVersion([6,0,21]); + axo.AllowScriptAccess = "always"; // error if player version < 6.0.47 (thanks to Michael Williams @ Adobe for this code) + } catch(e) { + if (PlayerVersion.major == 6) { + return PlayerVersion; + } + } + try { + axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash"); + } catch(e) {} + } + if (axo != null) { + PlayerVersion = new deconcept.PlayerVersion(axo.GetVariable("$version").split(" ")[1].split(",")); + } + } + return PlayerVersion; +} +deconcept.PlayerVersion = function(arrVersion){ + this.major = arrVersion[0] != null ? parseInt(arrVersion[0]) : 0; + this.minor = arrVersion[1] != null ? parseInt(arrVersion[1]) : 0; + this.rev = arrVersion[2] != null ? parseInt(arrVersion[2]) : 0; +} +deconcept.PlayerVersion.prototype.versionIsValid = function(fv){ + if(this.major < fv.major) return false; + if(this.major > fv.major) return true; + if(this.minor < fv.minor) return false; + if(this.minor > fv.minor) return true; + if(this.rev < fv.rev) return false; + return true; +} +/* ---- get value of query string param ---- */ +deconcept.util = { + getRequestParameter: function(param) { + var q = document.location.search || document.location.hash; + if (param == null) { return q; } + if(q) { + var pairs = q.substring(1).split("&"); + for (var i=0; i < pairs.length; i++) { + if (pairs[i].substring(0, pairs[i].indexOf("=")) == param) { + return pairs[i].substring((pairs[i].indexOf("=")+1)); + } + } + } + return ""; + } +} +/* fix for video streaming bug */ +deconcept.SWFObjectUtil.cleanupSWFs = function() { + var objects = document.getElementsByTagName("OBJECT"); + for (var i = objects.length - 1; i >= 0; i--) { + objects[i].style.display = 'none'; + for (var x in objects[i]) { + if (typeof objects[i][x] == 'function') { + objects[i][x] = function(){}; + } + } + } +} +// fixes bug in some fp9 versions see http://blog.deconcept.com/2006/07/28/swfobject-143-released/ +if (deconcept.SWFObject.doPrepUnload) { + if (!deconcept.unloadSet) { + deconcept.SWFObjectUtil.prepUnload = function() { + __flash_unloadHandler = function(){}; + __flash_savedUnloadHandler = function(){}; + window.attachEvent("onunload", deconcept.SWFObjectUtil.cleanupSWFs); + } + window.attachEvent("onbeforeunload", deconcept.SWFObjectUtil.prepUnload); + deconcept.unloadSet = true; + } +} +/* add document.getElementById if needed (mobile IE < 5) */ +if (!document.getElementById && document.all) { document.getElementById = function(id) { return document.all[id]; }} + +/* add some aliases for ease of use/backwards compatibility */ +var getQueryParamValue = deconcept.util.getRequestParameter; +var FlashObject = deconcept.SWFObject; // for legacy support +var 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 @@ + + + + + Logged Out - Material Experience + + + + + + + Material Experience Logo +

    Logged Out

    +

    + You have been logged out of the Material Experience site.
    + You can click + here to go back to + Material Experience. +

    + + \ 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 @@ +/* + * Material Experience - IE 6 PNG Behavior + * + * EYEMG - Interactive Media Group + * Created by Mike Crute (mcrute@eyemg.com) + * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07 + * + * Fixes the transparent PNG issue with IE 6. We only modified this + * code, it actually came from: + * http://webfx.eae.net/dhtml/pngbehavior/pngbehavior.html + */ + + + + 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 @@ +/* + * Material Experience - Special Cases Stylesheet + * + * EYEMG - Interactive Media Group + * Created by Mike Crute (mcrute@eyemg.com) + * Updated by Mike Crute (mcrute@eyemg.com) on 9/26/07 + * + * Stylesheet that should be sourced by all special cases card content. + * This should do a bare minimum of styles but they are all required + * per the client. + */ + +a, a:visited +{ + color: rgb(100, 100, 100); +} \ 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 @@ +#!/bin/bash +# +# ExxonMobil Designer Site Build Script +# by Mike Crute, EYEMG (mcrute@eyemg.com) +# +# This is a pretty simple build script just export a +# tag or the trunk from SVN and run ./make, running +# make (w/out the ./) will not work. You can then +# take the generated tarball and explode it on the +# production server w/out any further configuration. +# + +cd docroot + +# Create the build directory and copy over the cgi-bin +mkdir -p build/{docroot,cgi-bin} +mkdir build/docroot/lib +cp -R ../cgi-bin/* build/cgi-bin/ + +# Create a build date file +echo "Designer Site Built `date +'%Y-%m-%d %H:%M:%S'` EST" > build/docroot/build.date +echo "Code Version: `grep 'releaseVersion' classes/sme.namespace.js | cut -d"'" -f 2 `" >> build/docroot/build.date +echo "Subversion Revision: `svn info | grep 'Revision' | awk '{ print $2 }'`" >> build/docroot/build.date + +# Remove stuff from the cgi-bin that does not belong in +# production. +rm build/cgi-bin/*.sql + +# Also copy over any files that don't need special processing +cp -R images \ + blank.gif \ + favicon.ico \ + custom_content \ +build/docroot/ + +cp -R lib/scriptaculous build/docroot/lib/ + +# Prepare the .htaccess file for production +# +# We use a sparse layout in development where each class is +# in its own separate file but in production we use a solid +# layout that is also gzipped so we have to un-comment the +# gzip headers in our htaccess file. + +#sed ' +# /### PRODUCTION ###/,/### END PRODUCTION ###/ { +# /^###.*/d +# s/#//g +# } +#' .htaccess > build/docroot/.htaccess + +cp .htaccess build/docroot/.htaccess + +# Minify the PNG Behavior +sed ' + /\/\*/,/\*\// { + /.*/d; + } +' pngbehavior.htc > build/docroot/pngbehavior.htc + +# Concatenate the Javascript files into a single library file +# +# application.js loads all the javascript files in the development +# environment. When we go to production we need to parse out the +# actual script names being loaded and cat them, in order, into +# the final output application.js file. + +# GNU sed uses the -r flag for extended regular expressions, +# Darwin uses the -E flag for regular expressions. If neither +# of these hold true we might as well fail. +if [[ `uname` == 'Linux' ]]; then + MYFILES=`sed -r -f ../build_system/get_jsfiles.sed application.js` +elif [[ `uname` == 'Darwin' ]]; then + MYFILES=`sed -E -f ../build_system/get_jsfiles.sed application.js` +else + print 'No valid sed command could be determined.' + exit 1 +fi + +for item in $MYFILES; do + cat $item >> build/docroot/application.js.in +done + +# Remove development code from the application code +# and append it to the libraries files +sed '/^;;;.*/d' build/docroot/application.js.in >> build/docroot/application.js.out +sed '/^\/\*;;;.*/d' build/docroot/application.js.out >> build/docroot/application.js +rm build/docroot/application.js.in build/docroot/application.js.out + +# Minify index.html by removing leading spaces on each line +# as well as the ID comment, blank lines, and any script tags +# that appear in the of the document. +sed 's/^[[:space:]]*//; /^$/d; s/\n$//; // { /.*/d; }' index.html > build/docroot/index.html +sed 's/^[[:space:]]*//; /^$/d; s/\n$//; // { /.*/d; }' logged_out.html > build/docroot/logged_out.html + +# Minify the application Javascript using the YUI Compressor +java -jar ../build_system/yui_compressor.jar -o application.o build/docroot/application.js > /dev/null 2>&1 +mv application.o build/docroot/application.js + +# Minify the JSON data files +mkdir -p build/docroot/data +for item in `ls data/`; do + java -jar ../build_system/yui_compressor.jar -o build/docroot/data/$item data/$item > /dev/null 2>&1 +done + +# Can't minimize JSON, the semicolon at the end breaks everything +cat data/card_tables.js > build/docroot/data/card_tables.js + +# Minify the application CSS using our custom compressor +../build_system/cmpcss application.css > build/docroot/application.css +../build_system/cmpcss specialcases.css > build/docroot/specialcases.css + +# Gzip all that should be gzipped. This really should be done server-side +# but for now we work around it by statically compressing them at build time +# and serving it with the correct headers in the .htaccess file +#gzip build/docroot/application.js +#gzip build/docroot/application.css +#gzip build/docroot/index.html +# +#mv build/docroot/application.js.gz build/docroot/application.js +#mv build/docroot/application.css.gz build/docroot/application.css +#mv build/docroot/index.html.gz build/docroot/index.html + +# Tar it all up in a development dump +mv build aes_designer +cd aes_designer + +# Removing the date since its really not needed +tar -cjvf ../../aes_designer.tbz2 * > /dev/null 2>&1 +#tar -cjvf ../../aes_designer-`date +%Y%m%d_%H%M%S`.tbz2 * > /dev/null 2>&1 + + +cd .. + +rm -rf aes_designer/ diff --git a/syncdev b/syncdev new file mode 100755 index 0000000..7bb3413 --- /dev/null +++ b/syncdev @@ -0,0 +1,10 @@ +#!/bin/bash + +ssh mcrute@fred.eyemg.com "svn status /usr/web/designer | grep -v '?'" > /dev/null 2>&1 + +if [[ $? == 0 && $1 != '-f' ]]; then + echo "Changes exist on dev. Use SVN instead." + exit 1 +fi + +rsync -auvz -e "ssh" --delete-after --exclude "*.svn" "`pwd`/" mcrute@fred.eyemg.com:/usr/web/designer/ -- cgit v1.2.3