07e62eccbfc47e1535785816b8c73a1ec9762c30
Author: Nate Abele
Date: 2009-01-22 16:58:08 -0500
diff --git a/.htaccess b/.htaccess
new file mode 100755
index 0000000..f7f835c
--- /dev/null
+++ b/.htaccess
@@ -0,0 +1,5 @@
+<IfModule mod_rewrite.c>
+ RewriteEngine on
+ RewriteRule ^$ webroot/ [L]
+ RewriteRule (.*) webroot/$1 [L]
+ </IfModule>
\ No newline at end of file
diff --git a/app_controller.php b/app_controller.php
new file mode 100755
index 0000000..f832419
--- /dev/null
+++ b/app_controller.php
@@ -0,0 +1,7 @@
+<?php
+
+class AppController extends Controller {
+ var $helpers = array('Html', 'Javascript', 'Ajax', 'Cal','PrintMonth');
+}
+
+?>
\ No newline at end of file
diff --git a/app_model.php b/app_model.php
new file mode 100755
index 0000000..2f57a29
--- /dev/null
+++ b/app_model.php
@@ -0,0 +1,6 @@
+<?php
+
+class AppModel extends Model {
+}
+
+?>
\ No newline at end of file
diff --git a/config/bootstrap.php b/config/bootstrap.php
new file mode 100755
index 0000000..dc10d56
--- /dev/null
+++ b/config/bootstrap.php
@@ -0,0 +1,4 @@
+<?php
+
+
+?>
\ No newline at end of file
diff --git a/config/core.php b/config/core.php
new file mode 100755
index 0000000..d3ec262
--- /dev/null
+++ b/config/core.php
@@ -0,0 +1,227 @@
+<?php
+/* SVN FILE: $Id: core.php 7805 2008-10-30 17:30:26Z AD7six $ */
+/**
+ * This is core configuration file.
+ *
+ * Use it to configure core behavior of Cake.
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP(tm) : Rapid Development Framework (http://www.cakephp.org)
+ * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
+ * @package cake
+ * @subpackage cake.app.config
+ * @since CakePHP(tm) v 0.2.9
+ * @version $Revision: 7805 $
+ * @modifiedby $LastChangedBy: AD7six $
+ * @lastmodified $Date: 2008-10-30 13:30:26 -0400 (Thu, 30 Oct 2008) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+/**
+ * CakePHP Debug Level:
+ *
+ * Production Mode:
+ * 0: No error messages, errors, or warnings shown. Flash messages redirect.
+ *
+ * Development Mode:
+ * 1: Errors and warnings shown, model caches refreshed, flash messages halted.
+ * 2: As in 1, but also with full debug messages and SQL output.
+ * 3: As in 2, but also with full controller dump.
+ *
+ * In production mode, flash messages redirect after a time interval.
+ * In development mode, you need to click the flash message to continue.
+ */
+ Configure::write('debug', 1);
+/**
+ * Application wide charset encoding
+ */
+ Configure::write('App.encoding', 'UTF-8');
+/**
+ * To configure CakePHP *not* to use mod_rewrite and to
+ * use CakePHP pretty URLs, remove these .htaccess
+ * files:
+ *
+ * /.htaccess
+ * /app/.htaccess
+ * /app/webroot/.htaccess
+ *
+ * And uncomment the App.baseUrl below:
+ */
+ //Configure::write('App.baseUrl', env('SCRIPT_NAME'));
+/**
+ * Uncomment the define below to use CakePHP admin routes.
+ *
+ * The value of the define determines the name of the route
+ * and its associated controller actions:
+ *
+ * 'admin' -> admin_index() and /admin/controller/index
+ * 'superuser' -> superuser_index() and /superuser/controller/index
+ */
+ //Configure::write('Routing.admin', 'admin');
+
+/**
+ * Turn off all caching application-wide.
+ *
+ */
+ //Configure::write('Cache.disable', true);
+/**
+ * Enable cache checking.
+ *
+ * If set to true, for view caching you must still use the controller
+ * var $cacheAction inside your controllers to define caching settings.
+ * You can either set it controller-wide by setting var $cacheAction = true,
+ * or in each action using $this->cacheAction = true.
+ *
+ */
+ //Configure::write('Cache.check', true);
+/**
+ * Defines the default error type when using the log() function. Used for
+ * differentiating error logging and debugging. Currently PHP supports LOG_DEBUG.
+ */
+ define('LOG_ERROR', 2);
+/**
+ * The preferred session handling method. Valid values:
+ *
+ * 'php' Uses settings defined in your php.ini.
+ * 'cake' Saves session files in CakePHP's /tmp directory.
+ * 'database' Uses CakePHP's database sessions.
+ *
+ * To define a custom session handler, save it at /app/config/<name>.php.
+ * Set the value of 'Session.save' to <name> to utilize it in CakePHP.
+ *
+ * To use database sessions, execute the SQL file found at /app/config/sql/sessions.sql.
+ *
+ */
+ Configure::write('Session.save', 'php');
+/**
+ * The name of the table used to store CakePHP database sessions.
+ *
+ * 'Session.save' must be set to 'database' in order to utilize this constant.
+ *
+ * The table name set here should *not* include any table prefix defined elsewhere.
+ */
+ //Configure::write('Session.table', 'cake_sessions');
+/**
+ * The DATABASE_CONFIG::$var to use for database session handling.
+ *
+ * 'Session.save' must be set to 'database' in order to utilize this constant.
+ */
+ //Configure::write('Session.database', 'default');
+/**
+ * The name of CakePHP's session cookie.
+ */
+ Configure::write('Session.cookie', 'CAKEPHP');
+/**
+ * Session time out time (in seconds).
+ * Actual value depends on 'Security.level' setting.
+ */
+ Configure::write('Session.timeout', '120');
+/**
+ * If set to false, sessions are not automatically started.
+ */
+ Configure::write('Session.start', true);
+/**
+ * When set to false, HTTP_USER_AGENT will not be checked
+ * in the session
+ */
+ Configure::write('Session.checkAgent', true);
+/**
+ * The level of CakePHP security. The session timeout time defined
+ * in 'Session.timeout' is multiplied according to the settings here.
+ * Valid values:
+ *
+ * 'high' Session timeout in 'Session.timeout' x 10
+ * 'medium' Session timeout in 'Session.timeout' x 100
+ * 'low' Session timeout in 'Session.timeout' x 300
+ *
+ * CakePHP session IDs are also regenerated between requests if
+ * 'Security.level' is set to 'high'.
+ */
+ Configure::write('Security.level', 'high');
+/**
+ * A random string used in security hashing methods.
+ */
+ Configure::write('Security.salt', 'DYhG93b0qyJfIxfs2guVoUubWwvniR2G0FgaC9mi');
+/**
+ * Compress CSS output by removing comments, whitespace, repeating tags, etc.
+ * This requires a/var/cache directory to be writable by the web server for caching.
+ * and /vendors/csspp/csspp.php
+ *
+ * To use, prefix the CSS link URL with '/ccss/' instead of '/css/' or use HtmlHelper::css().
+ */
+ //Configure::write('Asset.filter.css', 'css.php');
+/**
+ * Plug in your own custom JavaScript compressor by dropping a script in your webroot to handle the
+ * output, and setting the config below to the name of the script.
+ *
+ * To use, prefix your JavaScript link URLs with '/cjs/' instead of '/js/' or use JavaScriptHelper::link().
+ */
+ //Configure::write('Asset.filter.js', 'custom_javascript_output_filter.php');
+/**
+ * The classname and database used in CakePHP's
+ * access control lists.
+ */
+ Configure::write('Acl.classname', 'DbAcl');
+ Configure::write('Acl.database', 'default');
+/**
+ *
+ * Cache Engine Configuration
+ * Default settings provided below
+ *
+ * File storage engine.
+ *
+ * Cache::config('default', array(
+ * 'engine' => 'File', //[required]
+ * 'duration'=> 3600, //[optional]
+ * 'probability'=> 100, //[optional]
+ * 'path' => CACHE, //[optional] use system tmp directory - remember to use absolute path
+ * 'prefix' => 'cake_', //[optional] prefix every cache file with this string
+ * 'lock' => false, //[optional] use file locking
+ * 'serialize' => true, [optional]
+ * ));
+ *
+ *
+ * APC (http://pecl.php.net/package/APC)
+ *
+ * Cache::config('default', array(
+ * 'engine' => 'Apc', //[required]
+ * 'duration'=> 3600, //[optional]
+ * 'probability'=> 100, //[optional]
+ * 'prefix' => Inflector::slug(APP_DIR) . '_', //[optional] prefix every cache file with this string
+ * ));
+ *
+ * Xcache (http://xcache.lighttpd.net/)
+ *
+ * Cache::config('default', array(
+ * 'engine' => 'Xcache', //[required]
+ * 'duration'=> 3600, //[optional]
+ * 'probability'=> 100, //[optional]
+ * 'prefix' => Inflector::slug(APP_DIR) . '_', //[optional] prefix every cache file with this string
+ * 'user' => 'user', //user from xcache.admin.user settings
+ * 'password' => 'password', //plaintext password (xcache.admin.pass)
+ * ));
+ *
+ *
+ * Memcache (http://www.danga.com/memcached/)
+ *
+ * Cache::config('default', array(
+ * 'engine' => 'Memcache', //[required]
+ * 'duration'=> 3600, //[optional]
+ * 'probability'=> 100, //[optional]
+ * 'prefix' => Inflector::slug(APP_DIR) . '_', //[optional] prefix every cache file with this string
+ * 'servers' => array(
+ * '127.0.0.1:11211' // localhost, default port 11211
+ * ), //[optional]
+ * 'compress' => false, // [optional] compress data in Memcache (slower, but uses less memory)
+ * ));
+ *
+ */
+ Cache::config('default', array('engine' => 'File'));
+?>
\ No newline at end of file
diff --git a/config/database.php b/config/database.php
new file mode 100755
index 0000000..b7beec7
--- /dev/null
+++ b/config/database.php
@@ -0,0 +1,20 @@
+<?php
+
+class DATABASE_CONFIG {
+
+ var $default = array(
+ 'driver' => 'mysql',
+ 'login' => 'root',
+ 'password' => '',
+ 'database' => 'cake_cal',
+ );
+
+ var $test = array(
+ 'driver' => 'postgres',
+ 'login' => 'root',
+ 'password' => '',
+ 'database' => 'cake_cal_test',
+ );
+}
+
+?>
\ No newline at end of file
diff --git a/config/routes.php b/config/routes.php
new file mode 100755
index 0000000..8ff5478
--- /dev/null
+++ b/config/routes.php
@@ -0,0 +1,6 @@
+<?php
+
+Router::connect ('/', array('controller' => 'calendar', 'action' => 'index'));
+Router::connect ('/import', array('controller' => 'calendar', 'action' => 'import'));
+
+?>
\ No newline at end of file
diff --git a/controllers/calendar_controller.php b/controllers/calendar_controller.php
new file mode 100755
index 0000000..56367b6
--- /dev/null
+++ b/controllers/calendar_controller.php
@@ -0,0 +1,245 @@
+<?php
+
+class CalendarController extends AppController {
+
+ var $uses = array('Event');
+ var $components = array('RequestHandler', 'ICal', 'Csv');
+
+ function beforeFilter() {
+ $this->RequestHandler->setContent(array(
+ 'ics' => 'text/calendar',
+ 'csv' => 'application/octet-stream'
+ ));
+
+ $this->set('isAjax', $this->RequestHandler->isAjax());
+ }
+
+ function index ($year = null, $month = null, $day = null) {
+ if ($year == null && $this->Session->check('Calendar.View')) {
+ extract($this->Session->read('Calendar.View'));
+ (intval($year) == 1969) ? $year = $month = $day = null : null;
+ }
+
+ if (empty($year)) {
+ $year = date('Y');
+ }
+ if ($month == null) {
+ $month = date('m');
+ }
+ if (intval($day) < 1) {
+ $day = null;
+ }
+
+ $tmpDay = $day == null ? 1 : $day;
+ $stamp = strtotime($year.'-'.$month.'-'.$tmpDay);
+ $start = date('Y-m-d', $stamp);
+ $end = $start;
+
+ if ($day == null) {
+ $end = date(
+ 'Y-m-d', strtotime('+1 month', $stamp) - (strtotime('+1 day') - strtotime('now'))
+ );
+ }
+
+ $events = $this->Event->find('all', array('conditions' => array(
+ 'Event.start <=' => $end . ' 23:59:59',
+ 'Event.end >=' => $start,
+ )));
+
+ $this->Session->write('Calendar.View', compact('year', 'month', 'day'));
+
+ $isDay = ($day != null);
+ $day = $tmpDay;
+ $events = array_values($events);
+ $this->set(compact('isDay', 'events', 'year', 'month', 'day'));
+ }
+
+ function view($year = null, $month = null, $day = null) {
+ $this->setAction('index', $year, $month, $day);
+ }
+
+ function edit($id = null) {
+ $this->data = $this->Event->findById($id);
+
+ $this->data['Event'] = array_merge($this->data['Event'], array(
+ 'start_Date' => date('m/d/Y', strtotime($this->data['Event']['start'])),
+ 'start_Time' => date('g:i a', strtotime($this->data['Event']['start'])),
+ 'end_Date' => date('m/d/Y', strtotime($this->data['Event']['end'])),
+ 'end_Time' => date('g:i a', strtotime($this->data['Event']['end']))
+ ));
+ }
+
+ function save() {
+ if (!empty($this->params['form'])) {
+ $this->Event->create();
+
+ if (!empty($this->data)) {
+ $this->Event->save($this->data);
+
+ if (!isset($this->data['Event']['id']) || empty($this->data['Event']['id'])) {
+ $id = $this->Event->id;
+ } else {
+ $data = $this->Event->findById($this->Event->id);
+ $data = $data['Event'];
+ }
+ } else {
+ // Serialized JavaScript data
+ $this->Event->create();
+ $this->Event->save($this->params['form']);
+ $data = $this->Event->findById($this->Event->id);
+ $data['Event']['__id'] = $this->params['form']['__id'];
+ $data = $data['Event'];
+ }
+ }
+ $this->set(compact('data', 'id'));
+ }
+
+ function delete($id = null) {
+ $this->Event->del($id);
+ }
+
+ function import() {
+ if (!empty($this->data)) {
+
+ $events = array();
+
+ if ($this->data['Event']['file']['type'] == 'text/calendar') {
+ // Parse as iCal
+ $data = $this->ICal->read($this->data['Event']['file']['tmp_name']);
+ if (!is_array($data) || empty($data) || !isset($data['Calendar'])) {
+ $this->Session->setFlash('Invalid import file');
+ return;
+ }
+ $data = $data['Calendar'];
+
+ if (!isset($data[0])) {
+ $data = array($data);
+ }
+
+ foreach ($data as $cal) {
+
+ if (isset($cal['Event'][0])) {
+ // Calendar contains
+ foreach($cal['Event'] as $event) {
+ $events[] = $this->__mapEvent($event);
+ }
+ } elseif (isset($cal['Event'])) {
+ $events[] = $this->__mapEvent($cal['Event']);
+ }
+ }
+ } else {
+ // Parse as CSV
+ $data = $this->Csv->read($this->data['Event']['file']['tmp_name']);
+ foreach ($data as $e) {
+ $events[] = $this->__mapEvent2($e);
+ }
+ }
+
+ foreach ($events as $e) {
+ $this->Event->create();
+ $this->Event->save($e);
+ }
+
+ $this->Session->setFlash('Import success');
+ $this->redirect(array('action' => 'index'));
+ }
+ }
+
+ function __mapEvent($event) {
+ $data = array('sharing' => 0, 'repeat' => 0);
+
+ if (isset($event['start_date'][0])) {
+ $event['start_date'] = $event['start_date'][0];
+ }
+ if (isset($event['end_date'][0])) {
+ $event['end_date'] = $event['end_date'][0];
+ }
+
+ $data['start'] = $event['start_date'];
+ $data['title'] = $event['summary'];
+
+ if (isset($event['duration'])) {
+ $offset = $this->__duration($event['duration']);
+ $data['end'] = date('Y-m-d H:i:s', strtotime($data['start'].' + '.$offset));
+ } elseif (isset($event['end_date'])) {
+ $data['end'] = $event['end_date'];
+ }
+ return $data;
+ }
+
+ function __mapEvent2($event) {
+ return array(
+ 'start' => date('Y-m-d H:i:s', strtotime(
+ $event['Start Date'] . ' ' . $event['Start Time']
+ )),
+ 'end' => date('Y-m-d H:i:s', strtotime(
+ $event['End Date'] . ' ' . $event['End Time']
+ )),
+ 'title' => $event['Subject'],
+ 'notes' => $event['Description'],
+ 'location' => $event['Location'],
+ 'repeat' => 0,
+ );
+ }
+
+ function export() {
+ // $this->RequestHandler->respondAs('text/calendar', array(
+ // 'attachment' => 'cal-export.ics'
+ // ));
+
+ $this->RequestHandler->respondAs('application/octet-stream', array(
+ 'attachment' => 'cal-export.csv'
+ ));
+
+ $data = array();
+ $events = $this->Event->find('all');
+
+ foreach ($events as $event) {
+ $data[] = array(
+ $event['title'],
+ date('m/d/Y', strtotime($event['Event']['start'])),
+ date('H:i:s A', strtotime($event['Event']['start'])),
+ date('m/d/Y', strtotime($event['Event']['end'])),
+ date('H:i:s A', strtotime($event['Event']['end'])),
+ 'False',
+ 'False',
+ date('m/d/Y', strtotime($event['Event']['start'])),
+ date('H:i:s A', strtotime($event['Event']['start'])),
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ $event['Event']['notes'],
+ $event['Event']['location'],
+ '',
+ 'Normal',
+ $event['Event']['sharing'] == 2 ? 'False' : 'True',
+ 'Normal',
+ 2
+ );
+ }
+
+ // iCal
+ /*foreach ($events as $event) {
+ $event = $event['Event'];
+ $tmp = array();
+ $tmp['DTSTART;TZID=US/Eastern'] = date('Ymd', strtotime($event['start'])).'T'.date('His', strtotime($event['start']));
+ $tmp['SUMMARY'] = $event['title'];
+ $tmp['DURATION'] = $this->ICal->__putDuration($event['start'], $event['end']);
+ $tmp['DESCRIPTION'] = r("\n", '\n', $event['notes']);
+ $tmp['DTSTAMP;TZID=US/Eastern'] = date('Ymd', strtotime($event['created'])).'T'.date('His', strtotime($event['created']));
+
+ if ($event['repeat'] && $event['repeat'] != 0) {
+ $tmp['RRULE'] = 'FREQ=' . ($event['repeat'] == 1 ? 'WEEKLY' : 'MONTHLY');
+ }
+ $data[] = $tmp;
+ }*/
+
+ $this->set(compact('data'));
+ $this->render('outlook_export', 'ajax');
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/controllers/components/csv.php b/controllers/components/csv.php
new file mode 100755
index 0000000..1a1a4b4
--- /dev/null
+++ b/controllers/components/csv.php
@@ -0,0 +1,41 @@
+<?php
+
+class CsvComponent extends Object {
+
+ var $config = array('database' => '');
+ var $__data = null;
+
+ function create($data) {
+ return $this->__output($data);
+ }
+
+ function read($filename = null) {
+
+ if ($filename != null) {
+ $this->config['database'] = $filename;
+ }
+
+ if ($this->__data == null) {
+ $this->__data = $this->__parse($this->config['database']);
+ }
+ return $this->__data;
+ }
+
+ function __parse($file) {
+ $rows = array();
+ $handle = fopen($file, "r");
+
+ $header = fgetcsv($handle, 1000, ",");
+ while (($data = fgetcsv($handle, 1000, ",")) !== false) {
+ $mapped = array();
+ for ($i = 0; $i < count($data); $i++) {
+ $mapped[$header[$i]] = $data[$i];
+ }
+ $rows[] = $mapped;
+ }
+ fclose($handle);
+ return $rows;
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/controllers/components/i_cal.php b/controllers/components/i_cal.php
new file mode 100755
index 0000000..8bd3088
--- /dev/null
+++ b/controllers/components/i_cal.php
@@ -0,0 +1,419 @@
+<?php
+
+class ICalComponent extends Object {
+
+ var $config = array('database' => '');
+ var $__count = 0;
+ var $__lastInsertId = null;
+ var $__data = null;
+
+ var $__timezones = array('US-Eastern');
+ var $columns = array('primary_key' => array('name' => 'uid'),
+ 'string' => array('name' => 'string'),
+ 'timestamp' => array('name' => 'timestamp', 'format' => 'Ymd/T/His'),
+ 'datetime' => array('name' => 'timestamp', 'format' => 'Ymd/T/His')//,
+ );
+ var $__keyMap = array(
+ 'id' => 'uid',
+ 'end_date' => 'dtend',
+ 'start_date' => 'dtstart',
+ 'date_stamp' => 'dtstamp'
+ );
+ var $__textMap = array(
+ '"' => 'DQUOTE',
+ ',' => '\,',
+ //':' => '":"', // Not sure about this one
+ ';' => '\;',
+ '\\' => '\\\\',
+ '\n' => '\\n'
+ );
+
+ function listSources() {
+ return array('calendars', 'events', 'todos', 'alarms', 'journals');
+ }
+
+ function create($data) {
+ return $this->__output($data);
+ }
+
+ function read($filename = null) {
+
+ if ($filename != null) {
+ $this->__count = 0;
+ $this->config['database'] = $filename;
+ }
+
+ if ($this->__data == null) {
+ $this->__data = $this->__parse(file_get_contents($this->config['database']));
+ }
+ return $this->__data;
+ }
+
+ function update($data) { }
+
+ function delete($data) { }
+
+ function __output($data) {
+ $out = '';
+ foreach($data as $key => $val) {
+
+ $keyAppend = '';
+ if (in_array($key, array('Calendar', 'Event', 'Timezone', 'Todo', 'Alarm', 'Journals'))) {
+ $key = 'v' . $key;
+ }
+
+ if (is_array($val) && low($key) != $key) {
+ if (countdim($val) > 1) {
+ foreach ($val as $val2) {
+ $out .= up("begin:{$key}\n");
+ $out .= $this->__output($val2);
+ $out .= up("end:{$key}\n");
+ }
+ } else {
+ $out .= up("begin:{$key}\n");
+ $out .= $this->__output($val);
+ $out .= up("end:{$key}\n");
+ }
+ } else {
+ if (is_array($val)) {
+ $tmp = array();
+ foreach ($val as $key2 => $val2) {
+ if ($key2 !== 0) {
+ $tmp[] = up($key2) . '=' . $val2;
+ }
+ }
+
+ if (!empty($tmp)) {
+ $keyAppend = ';' . join(';', $tmp);
+ }
+
+ $_val = $val[0];
+ } else {
+ $_val = $val;
+ }
+
+ switch ($key) {
+ case 'end_date':
+ case 'start_date':
+ case 'date_stamp':
+ case 'last_modified':
+ case 'trigger':
+ if (strpos($_val, ' weeks') === false && strpos($_val, ' days') === false && strpos($_val, ' hours') === false && strpos($_val, ' minutes') === false && strpos($_val, ' seconds') === false) {
+ $utc = false;
+ if (strpos($_val, 'UTC')) {
+ $utc = true;
+ }
+
+ if (strpos($_val, ' ') === false && strpos($_val, ':') === false) {
+ $val = date('Ymd', strtotime($_val));
+ } else {
+ $_val = trim(r('UTC', '', $_val));
+ $tmp = date('Ymd', strtotime($_val)).'T'.date('His', strtotime($_val));
+ if ($utc) {
+ $tmp .= 'Z';
+ }
+ $val = $tmp;
+ }
+ } else {
+ $val = $this->__putDuration($val);
+ }
+ break;
+ case 'duration':
+ $val = $this->__putDuration($val);
+ break;
+ case 'contact':
+ case 'comment':
+ case 'description':
+ case 'location':
+ case 'prodid':
+ case 'resources':
+ case 'status':
+ case 'summary':
+ $s = array_keys($this->__textMap);
+ $r = array_values($this->__textMap);
+ $val = r($s, $r, $val);
+ $val = r('\\\\', '\\', $val);
+ break;
+ default:
+ if ($val === true) {
+ $val = 'TRUE';
+ } elseif ($val === false) {
+ $val = 'FALSE';
+ }
+ break;
+ }
+
+ if (in_array($key, array_keys($this->__keyMap))) {
+ $key = $this->__keyMap[$key];
+ }
+ if (is_array($val) && isset($val[0])) {
+ $val = $val[0];
+ }
+
+ $out .= up(r('_', '-', $key)) . $keyAppend . ':' . $val . "\n";
+ }
+ }
+ return $out;
+ }
+
+ function __parse(&$lines) {
+
+ if (is_string($lines)) {
+ $lines = r("\r", '', $lines);
+ $lines = explode("\n", $lines);
+
+ $lines1 = ($lines);
+
+ for ($i = 0; $i < count($lines); $i++) {
+ if (substr($lines[$i], 0, 1) == ' ') {
+ $lines[$i - 1] .= substr($lines[$i], 1);
+ array_splice($lines, $i, 1);
+ } elseif ($lines[$i] == '') {
+ array_splice($lines, $i, 1);
+ }
+ }
+ }
+
+ $data = array();
+ for ($i = $this->__count; $i < count($lines); $i++) {
+
+ $idx = strpos($lines[$i], ':');
+ $key = r('-', '_', substr($lines[$i], 0, $idx));
+ $value = substr($lines[$i], $idx + 1);
+
+ if (low($key) == 'end') {
+ $this->__count = $i++;
+ return $data;
+ } elseif (low($key) == 'begin') {
+ $key = ucwords(low($value));
+ if ($key{0} == 'V') {
+ $key = ucwords(substr($key, 1));
+ }
+
+ $this->__count = ++$i;
+ $value = $this->__parse($lines);
+ $i = $this->__count;
+ } else {
+ if (strpos($key, ';')) {
+ $key = explode(';', $key);
+ $props = $key;
+ $key = $key[0];
+ array_shift($props);
+
+ $value = array($value);
+ foreach ($props as $v) {
+ $tmp = explode('=', $v);
+ if (isset($tmp[1])) {
+ $value[low($tmp[0])] = $tmp[1];
+ }
+ }
+ }
+ $key = low($key);
+ }
+
+ if (in_array($key, $this->__keyMap)) {
+ $reverse = array_combine(array_values($this->__keyMap), array_keys($this->__keyMap));
+ $key = $reverse[$key];
+ }
+
+ // Format the data types
+ switch ($key) {
+ case 'end_date':
+ case 'start_date':
+ case 'date_stamp':
+ case 'last_modified':
+ case 'trigger':
+
+ if (is_array($value)) {
+ $value[0] = $this->__timestamp($value[0]);
+ } elseif (strpos(low($value), '-p') !== false || strpos(low($value), 'p') !== false) {
+ $value = $this->__duration($value);
+ } else {
+ $value = $this->__timestamp($value);
+ }
+ break;
+ case 'duration':
+ $value = $this->__duration($value);
+ break;
+ case 'contact':
+ case 'comment':
+ case 'description':
+ case 'location':
+ case 'prodid':
+ case 'resources':
+ case 'status':
+ case 'summary':
+ $r = array_keys($this->__textMap);
+ $s = array_values($this->__textMap);
+ $value = r($s, $r, $value);
+ break;
+ default:
+ if ($value == 'TRUE') {
+ $value = true;
+ } elseif ($value == 'FALSE') {
+ $value = false;
+ }
+ break;
+ }
+
+ if (isset($data[$key])) {
+ if (!isset($data[$key][0])) {
+ $data[$key] = array($data[$key]);
+ $data[$key][] = $value;
+ } elseif (isset($data[$key][0]) && is_array($data[$key])) {
+ $data[$key][] = $value;
+ }
+ } else {
+ $data[$key] = $value;
+ }
+ }
+
+ return $data;
+ }
+
+ // Event UID generator
+ function __insertID() {
+ $chunk = array();
+ $hash = up(md5(intval(r('.', '', env('SERVER_ADDR'))).''.intval(rand() * 1000).time()));
+ $chunk[] = substr($hash, 0, 8);
+ $chunk[] = substr($hash, 8, 4);
+ $chunk[] = substr($hash, 12, 4);
+ $chunk[] = substr($hash, 16, 4);
+ $chunk[] = substr($hash, 20);
+ $this->__lastInsertId = join('-', $chunk);
+ return $this->__lastInsertId;
+ }
+
+ function lastInsertId() {
+ return $this->__lastInsertId;
+ }
+
+ function __timestamp($time) {
+ if (strpos(low($time), 'p') === 0 || strpos(low($time), 'p') === 1) {
+ return $this->__duration($time);
+ }
+ $utc = false;
+ if (strpos(low($time), 'z')) {
+ $utc = true;
+ }
+
+ if (strpos(low($time), 't') === false) {
+ return date('Y-m-d', strtotime($time));
+ }
+ $time = explode('t', r('z', '', low($time)));
+ $time[1] = substr($time[1], 0, 2).':'.substr($time[1], 2, 2).':'.substr($time[1], 4, 2);
+ return date('Y-m-d', strtotime($time[0])).' '.$time[1] . ($utc ? ' UTC' : '');
+ }
+
+ function __putDuration($time, $date = null) {
+
+ $negative = false;
+ if (is_string($time)) {
+ if (strpos($time, '-') === 0) {
+ $time = substr($time, 1);
+ $negative = true;
+ }
+ $time = strtotime('+' . $time) - strtotime('now');;
+ }
+
+ if ($time < 0) {
+ $time = abs($time);
+ $negative = true;
+ }
+
+ $out = 'P';
+ $t = false;
+ $offset = array('W' => 604800, 'D' => 86400, 'H' => 3600, 'M' => 60, 'S' => 1);
+
+ if ($date != null) {
+ $time = strtotime($date) - strtotime($time);
+ }
+
+ foreach ($offset as $key => $val) {
+ $tmp = 0;
+ if ($time >= $val) {
+ $tmp = $time / $val;
+ }
+
+ if ($tmp >= 1) {
+ if (in_array($key, array('H', 'M', 'S')) && $t == false) {
+ $t = true;
+ $out .= 'T';
+ }
+
+ $out .= $tmp.$key;
+ $time -= $tmp * $val;
+ }
+ }
+ return ($negative ? '-' : '') . $out;
+ }
+
+ function __duration($dur) {
+
+ $tmp = '';
+ $out = '';
+
+ for ($i = 0; $i < strlen($dur); $i++) {
+ switch(low($dur{$i})) {
+ case 't':
+ case 'p':
+ // do nothing
+ break;
+ case 'w':
+ $out .= $tmp . ' week' . (intval($tmp) != 1 ? 's' : '') . ' ';
+ $tmp = '';
+ break;
+ case 'd':
+ $out .= $tmp . ' day' . (intval($tmp) != 1 ? 's' : '') . ' ';
+ $tmp = '';
+ break;
+ case 'h':
+ $out .= $tmp . ' hour' . (intval($tmp) != 1 ? 's' : '') . ' ';
+ $tmp = '';
+ break;
+ case 'm':
+ $out .= $tmp . ' minute' . (intval($tmp) != 1 ? 's' : '') . ' ';
+ $tmp = '';
+ break;
+ case 's':
+ $out .= $tmp . ' second' . (intval($tmp) != 1 ? 's' : '') . ' ';
+ $tmp = '';
+ break;
+ default:
+ $tmp .= $dur{$i};
+ break;
+ }
+ }
+ return trim($out);
+ }
+
+ function debug_compare($lines1) {
+
+ $data = $this->__parse($lines1);
+ pr($data);
+
+ $diff = 0;
+ $lines2 = explode("\n", $this->__output($data));
+ pr($lines2);
+ e('<table border=1>');
+
+ foreach ($lines1 as $i => $val) {
+
+ e('<tr><td>');
+ pr($i . ' : ' . $val);
+ e('</td><td>');
+
+ if ($lines2[$i] != $val) {
+ pr($lines2[$i]);
+ $diff++;
+ }
+ e('</td></tr>');
+ }
+ e('</table>');
+ pr('Diffs : '.$diff);
+
+ die();
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/index.php b/index.php
new file mode 100755
index 0000000..a5f9f78
--- /dev/null
+++ b/index.php
@@ -0,0 +1,27 @@
+<?php
+/* SVN FILE: $Id: index.php 2017 2006-02-18 17:03:41Z phpnut $ */
+
+/**
+ * PHP versions 4 and 5
+ *
+ * CakePHP : Rapid Development Framework <http://www.cakephp.org/>
+ * Copyright (c) 2006, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2006, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake
+ * @subpackage cake.cake.app
+ * @since CakePHP v 0.10.0.1076
+ * @version $Revision: 2017 $
+ * @modifiedby $LastChangedBy: phpnut $
+ * @lastmodified $Date: 2006-02-18 12:03:41 -0500 (Sat, 18 Feb 2006) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+require 'webroot'.DIRECTORY_SEPARATOR.'index.php';
+?>
\ No newline at end of file
diff --git a/models/event.php b/models/event.php
new file mode 100755
index 0000000..e91b830
--- /dev/null
+++ b/models/event.php
@@ -0,0 +1,48 @@
+<?php
+
+class Event extends AppModel {
+
+ function beforeSave() {
+
+ if (!isset($this->data['Event']['start']) || empty($this->data['Event']['start'])) {
+ if (!isset($this->data['Event']['start_Date']) || empty($this->data['Event']['start_Date'])) {
+ $this->data['Event']['start_Date'] = date("m/d/Y");
+ }
+ if (!isset($this->data['Event']['start_Date']) || empty($this->data['Event']['start_Date'])) {
+ $this->data['Event']['start_Time'] = "12 pm";
+ }
+ $this->data['Event']['start'] = $this->data['Event']['start_Date'] . " " . $this->data['Event']['start_Time'];
+ }
+
+ if (!isset($this->data['Event']['end']) || empty($this->data['Event']['end'])) {
+ if (!isset($this->data['Event']['end_Date']) || empty($this->data['Event']['end_Date'])) {
+ $this->data['Event']['end_Date'] = $this->data['Event']['start_Date'];
+ }
+ if (!isset($this->data['Event']['end_Time']) || empty($this->data['Event']['end_Time'])) {
+ $endsecs = strtotime($this->data['Event']['start']) + 3600;
+ $this->data['Event']['end_Time'] = date("g:i a",$endsecs);
+ if(date("m/d/Y",$endsecs) != date("m/d/Y",strtotime($this->data['Event']['start']))) {
+ $this->data['Event']['end_Date'] = date("m/d/Y",$endsecs);
+ }
+ }
+ $this->data['Event']['end'] = $this->data['Event']['end_Date'] . " " . $this->data['Event']['end_Time'];
+ }
+
+ // Sanitize the data
+ $this->data['Event']['start'] = date('Y-m-d H:i:s', strtotime($this->data['Event']['start']));
+ $this->data['Event']['end'] = date('Y-m-d H:i:s', strtotime($this->data['Event']['end']));
+
+
+ $startTS = strtotime($this->data['Event']['start']);
+ $endTS = strtotime($this->data['Event']['end']);
+
+ // if end datetime is less that start datetime then switch em
+ if($endTS < $startTS) {
+ $this->data['Event']['start'] = $this->data['Event']['end'];
+ $this->data['Event']['end'] = date('Y-m-d H:i:s', $startTS);
+ }
+ return true;
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/models/user.php b/models/user.php
new file mode 100755
index 0000000..3ac206c
--- /dev/null
+++ b/models/user.php
@@ -0,0 +1,10 @@
+<?php
+
+class User extends AppModel {
+
+ var $displayField = 'full_name';
+
+ var $hasMany = 'Event';
+}
+
+?>
\ No newline at end of file
diff --git a/views/calendar/csv/index.ctp b/views/calendar/csv/index.ctp
new file mode 100755
index 0000000..bc1aea4
--- /dev/null
+++ b/views/calendar/csv/index.ctp
@@ -0,0 +1,8 @@
+"Subject","Start Date","Start Time","End Date","End Time","All day event","Reminder on/off","Reminder Date","Reminder Time","Meeting Organizer","Required Attendees","Optional Attendees","Meeting Resources","Billing Information","Categories","Description","Location","Mileage","Priority","Private","Sensitivity","Show time as"
+<?php
+
+foreach ($data as $item) {
+ e('"' . join('","', $item) . '"' . "\n");
+}
+
+?>
\ No newline at end of file
diff --git a/views/calendar/edit.ctp b/views/calendar/edit.ctp
new file mode 100755
index 0000000..e2ccf8c
--- /dev/null
+++ b/views/calendar/edit.ctp
@@ -0,0 +1,62 @@
+<div class="block">
+
+ <?php echo $html->formTag('/calendar/save', 'post', array('id' => 'EventFormData', 'onsubmit' => 'return true;')); ?>
+ <?php echo $html->hidden('Event/id', array('id' => 'EventId')); ?>
+
+ <table width='100%' border='0' cellpadding='4'>
+ <tr><td class="win_title" colspan='2' valign="top">
+ <div class='window_buttons'>
+ <?php echo $ajax->link('Save', '/calendar/save', array('id' => 'eventFormSave', 'complete' => 'Effect.SlideUp("eventForm");' . "\n" . 'Events.update(request.responseText);', 'with' => 'Form.serialize("EventFormData")')); ?>
+ <?php echo $html->link('Cancel', 'javascript:void(0);', array('id' => 'eventFormCancel', 'onclick' => "Effect.SlideUp('eventForm');")); ?>
+ </div>
+ <label>Event Edit Form</label>
+ </td></tr>
+ <tr><td class='labeltd'>
+ <label for="EventTitle">Title</label></td><td><?php echo $html->input('Event/title', array('class' => 'input_title')); ?><br />
+ </td></tr>
+ <tr><td class='labeltd'>
+ <label for="EventStartDate">Start</label>
+ </td><td>
+ <?php echo $html->input('Event/start_Date', array('class' => 'date')); ?>
+ <label for="EventStartTime" class="label_small">@</label>
+ <?php echo $html->input('Event/start_Time', array('class' => 'time')); ?><br />
+ </td></tr>
+ <tr><td class='labeltd'>
+ <label form="EventEndDate">End</label>
+ </td><td>
+ <?php echo $html->input('Event/end_Date', array('class' => 'date')); ?>
+ <label for="EventEndTime" class="label_small">@</label>
+ <?php echo $html->input('Event/end_Time', array('class' => 'time')); ?>
+ </tr><!--<tr><td class="labeltd">
+ <label form="EventRepeat">Repeat</label>
+ </td><td>
+ <?php echo $html->selectTag('Event/repeat', array('don\'t repeat', 'weekly', 'monthly'), null, array('id' => 'EventRepeat'), null, false); ?>
+ </tr>--><tr><td class="labeltd">
+ <label form="EventLocation">Location</label>
+ </td><td>
+ <?php echo $html->input('Event/location'); ?>
+ </tr><tr><td class="labeltd">
+ <label form="EventNotes">Notes</label>
+ </td><td>
+ <?php echo $html->textarea('Event/notes'); ?>
+ </td></tr>
+ <tr><td class='labeltd'>
+ <label form="EventEndDate">Sharing</label>
+ </td><td>
+ <?php echo $html->selectTag('Event/sharing', array('Private (only for me)', 'Shared (selected users)', 'Public (everyone can see)', 'Public Editable (everyone can update)'), null, null, null, false); ?>
+ <?php echo $javascript->event('EventSharing', 'change', '$F("EventSharing") == 1 ? $("EventShare").show() : $("EventShare").hide();'); ?>
+ </td></tr>
+ <tr><td colspan="2">
+ <div id="EventShare">
+ Share with users (Ctrl-click to select multiple):<br />
+ <?php echo $html->selectTag('SharedUsers/SharedUsers', $users, $this->data['SharedUsers'], array('id' => 'SharedUsers', 'multiple' => true), null, false); ?>
+
+ <?php if ($this->data['Event']['sharing'] != 1) { ?>
+ <?php echo $javascript->codeBlock('$("EventShare").hide();'); ?>
+ <?php } ?>
+ </div>
+ </td></tr>
+ </table>
+ </form>
+
+</div>
diff --git a/views/calendar/icaldbg.ctp b/views/calendar/icaldbg.ctp
new file mode 100755
index 0000000..764d124
--- /dev/null
+++ b/views/calendar/icaldbg.ctp
@@ -0,0 +1,19 @@
+<div id="eventForm">
+
+ <?php echo $html->formTag('/calendar/icaldbg', 'file'); ?>
+
+ <table style='width:100%;' border='0' cellpadding='10'>
+ <tr><td class='labeltd' style='text-align:left;background-color:#333;color:#ccc;padding:10px;' colspan='2'>
+ <label>Import Events</label>
+ <span style='float:right;'><?php echo $html->link('Cancel', '/', array('id' => 'eventFormCancel')); ?></span>
+ </td></tr>
+ <tr><td class='labeltd'>
+ <label for="EventTitle">File</label></td>
+ <td><?php echo $html->file('Event/file'); ?>
+ </td></tr>
+ <tr><td colspan='2' align='right'>
+ <?php echo $html->submit('Import'); ?>
+ </td></tr>
+ </table>
+ </form>
+</div>
\ No newline at end of file
diff --git a/views/calendar/ics/index.ctp b/views/calendar/ics/index.ctp
new file mode 100755
index 0000000..65a8e28
--- /dev/null
+++ b/views/calendar/ics/index.ctp
@@ -0,0 +1,37 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Sage Systems//Calendar 2.0//EN
+X-WR-TIMEZONE:US/Eastern
+CALSCALE:GREGORIAN
+METHOD:PUBLISH
+BEGIN:VTIMEZONE
+TZID:US/Eastern
+LAST-MODIFIED:20060515T203558Z
+BEGIN:DAYLIGHT
+DTSTART:20050403T070000
+TZOFFSETTO:-0400
+TZOFFSETFROM:+0000
+TZNAME:EDT
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20051030T020000
+TZOFFSETTO:-0500
+TZOFFSETFROM:-0400
+TZNAME:EST
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:20060402T010000
+TZOFFSETTO:-0400
+TZOFFSETFROM:-0500
+TZNAME:EDT
+END:DAYLIGHT
+END:VTIMEZONE
+<?php foreach ($data as $item) { ?>
+BEGIN:VEVENT
+<?php foreach ($item as $key => $val) { ?>
+<?php echo $key; ?>:<?php echo $val; ?>
+
+<?php } ?>
+END:VEVENT
+<?php } ?>
+END:VCALENDAR
diff --git a/views/calendar/import.ctp b/views/calendar/import.ctp
new file mode 100755
index 0000000..bcb4e41
--- /dev/null
+++ b/views/calendar/import.ctp
@@ -0,0 +1,19 @@
+<?php echo $html->formTag('/calendar/import', 'file'); ?>
+
+ <table style='width:100%;' border='0' cellpadding='10'>
+ <tr><td class='win_title' colspan='2'>
+ <span class='window_buttons'>
+ <?php echo $html->link('Cancel', 'javascript:void(0)', array('id' => 'importFormCancel')); ?>
+ </span>
+ <label>Import Events</label>
+ </td></tr>
+ <tr><td class='labeltd'>
+ <label for="EventTitle">File</label></td>
+ <td><?php echo $html->file('Event/file'); ?>
+ </td></tr>
+ <tr><td colspan='2' align='right'>
+ <?php echo $html->submit('Import'); ?>
+ </td></tr>
+ </table>
+</form>
+<?php echo $javascript->event('$("importFormCancel")', 'click', 'Effect.SlideUp("eventForm");'); ?>
\ No newline at end of file
diff --git a/views/calendar/index.ctp b/views/calendar/index.ctp
new file mode 100755
index 0000000..6c4a04e
--- /dev/null
+++ b/views/calendar/index.ctp
@@ -0,0 +1,120 @@
+<?php $javascript->cacheEvents(); ?>
+
+<?php if(!$isAjax) { ?>
+
+<div id="calLoader" style="display: none;">
+ <?php echo $html->image('cal/spinner.gif'); ?>
+</div>
+
+<div style="display: box;float:left;">
+<table id='tools'>
+ <tr>
+ <td>
+ <div class="tool_button" style="padding-left:5px;padding-right:5px;">
+ <?php echo $html->image('cal/spacer.gif', array('alt' => 'spacer','height'=>'20','width'=>'5')); ?>
+ </div>
+ </td><td>
+ <div id="btnImport" class="tool_button">
+ <?php echo $html->image('cal/btn_import.gif', array('alt' => 'Import')); ?>
+ </div>
+ Import
+ </td><td>
+ <div id="btnExport" class="tool_button">
+ <?php echo $html->image('cal/btn_export.gif', array('alt' => 'Export')); ?>
+ </div>
+ Export
+ </td><td>
+ <div class="tool_button" style="padding-left:5px;padding-right:5px;">
+ <?php echo $html->image('cal/div.gif', array('alt' => 'spacer','height'=>'20','width'=>'5')); ?>
+ </div>
+ </td><td>
+ <div id="btnPrint" class="tool_button">
+ <?php echo $html->image('cal/btn_print.gif', array('alt' => 'Print')); ?>
+ </div>
+ Print
+ </td><td>
+ <div class="tool_button" style="padding-left:5px;padding-right:5px;">
+ <?php echo $html->image('cal/spacer.gif', array('alt' => 'spacer','height'=>'20','width'=>'5')); ?>
+ </div>
+ </td>
+ </tr>
+</table>
+</div>
+
+<div id="toolbar">
+
+ <div id="eventTools" style="display: none;">
+ <span id="btnEdit" class="tool_button">
+ <?php echo $html->image('cal/btn_edit.gif', array('alt' => 'Edit Event')); ?>
+ </span>
+ <span id="btnDelete" class="tool_button">
+ <?php echo $html->image('cal/btn_delete.gif', array('alt' => 'Delete Event')); ?>
+ </span>
+ </div>
+
+ <div id="dayTools" style="display: none;">
+ <span id="btnAdd" class="tool_button">
+ <?php echo $html->image('cal/btn_add.gif', array('alt' => 'Add Event')); ?>
+ </span>
+ <span id="btnZoom" class="tool_button">
+ <?php echo $html->image('cal/btn_zoom.gif', array('alt' => 'Day View')); ?>
+ </span>
+ </div>
+</div>
+
+<?php $javascript->event('$("btnEdit")', 'click', 'Events.edit(Calendar.Events.selected);'); ?>
+<?php $javascript->event('$("btnDelete")', 'click', 'Events.remove(Calendar.Events.selected);'); ?>
+<?php $javascript->event('$("btnAdd")', 'click', 'Events.add(Calendar.selected)'); ?>
+<?php $javascript->event('$("btnZoom")', 'click', 'Events.view(Calendar.selected);'); ?>
+<?php $javascript->event('$("btnImport")', 'click', $ajax->remoteFunction(array(
+ 'url' => '/calendar/import',
+ 'update' => 'eventForm',
+ 'complete' => 'Effect.SlideDown("eventForm");'
+))); ?>
+<?php $javascript->event('$("btnExport")', 'click', 'window.location = "'.$this->base.'/calendar/export";'); ?>
+<?php $javascript->event('$("btnPrint")', 'click', $ajax->remoteFunction(array(
+ 'url' => '/calendar/printerForm',
+ 'update' => 'eventForm',
+ 'complete' => 'Effect.SlideDown("eventForm");'
+))); ?>
+<div id="eventForm" style="display: none;"></div>
+<div id="log" style="display: none; line-height: 16px; position: absolute; left: 10px; top: 80%; font-size: 12px; width: 75%; height: 500px; overflow: auto; border: 1px solid #333333;"></div>
+
+<?php } ?>
+
+<?php if(!$isDay) { ?>
+ <?php $cal->init($month, $year); ?>
+ <?php echo $cal->draw('calendar', 'month', true, !$isAjax); ?>
+
+ <?php // Set up JavaScript bindings between Calendar object and UI ?>
+ <?php echo $javascript->codeBlock('Object.extend(Calendar, {base : "'.$this->base.'", year : '.$year.', month : '.$month.'});'); ?>
+ <?php echo $javascript->codeBlock('$("tools").show();'); ?>
+<?php } else {
+ include $this->element('day');
+} ?>
+
+<?php echo $javascript->codeBlock('Calendar.bind("calendar");'); ?>
+<?php echo $javascript->object($events, true, 'events = ', ';'); ?>
+
+<?php echo $javascript->link('bindings'); ?>
+<script type="text/javascript">
+
+ Calendar.Events.click = Events.click;
+ Calendar.loading(true);
+ Calendar.Events.items = [];
+
+ z = 0;
+ while (typeof events[z] != 'undefined') {
+ Calendar.Events.add(events[z]);
+ z++;
+ }
+
+ Calendar.Events.select(null);
+ setTimeout('Calendar.loading(false);', 1);
+ Calendar.resize();
+
+ Event.observe(document, 'click', function(e) { Element.hide('toolbar'); }, true);
+ Element.hide('toolbar');
+</script>
+
+<?php echo $javascript->writeEvents(); ?>
\ No newline at end of file
diff --git a/views/calendar/save.ctp b/views/calendar/save.ctp
new file mode 100755
index 0000000..192691d
--- /dev/null
+++ b/views/calendar/save.ctp
@@ -0,0 +1,9 @@
+<?php
+
+if (isset($data) && is_array($data)) {
+ e($javascript->object($data) . ' //'); // "//" for debug timestamp at the end
+} elseif (isset($id)) {
+ e($id);
+}
+
+?>
\ No newline at end of file
diff --git a/views/calendar/share.ctp b/views/calendar/share.ctp
new file mode 100755
index 0000000..2430480
--- /dev/null
+++ b/views/calendar/share.ctp
@@ -0,0 +1,14 @@
+<ul class="user_list">
+ <?php foreach($users as $user) { ?>
+ <li><?php echo $user['full_name']; ?></li>
+ <?php } ?>
+</ul>
+
+<div style="padding: 2px;">
+ Add a user <?php echo $html->selectTag('Event/share', $userList, null, array('id' => 'EventShare')); ?>
+ <?php echo $ajax->observeField('EventShare', array(
+ 'url' => '/calendar/share',
+ 'update' => 'Shared',
+ 'with' => 'Form.serialize("EventFormData")'
+ )); ?>
+</div>
\ No newline at end of file
diff --git a/views/elements/day.ctp b/views/elements/day.ctp
new file mode 100755
index 0000000..96f8ffc
--- /dev/null
+++ b/views/elements/day.ctp
@@ -0,0 +1,57 @@
+<?php if (!$isAjax) { ?><div id="calendar"><?php } ?>
+
+<?php $javascript->cacheEvents(); ?>
+ <?php $current = "$year-$month-$day"; ?>
+
+<?
+ e("<div id='printhead'>");
+ e("<div class='month_name' style='margin-bottom:30px;padding-bottom:10px;border-bottom:1px solid #000;'>");
+ e($html->image('cal/sage_logo_sm.gif', array('alt' => 'Sage Logo','align'=>'baseline','style'=>'padding-right:20px;')));
+ e("Sage Calendar: Day View for " . date('D m/d/y', strtotime($current)) . "</div>");
+ e("</div>");
+?>
+<div id="cal_header_dayHead">
+
+ <div class="win_title" style="text-align: center;">
+ <?=$ajax->link('Back', '/calendar/index/' . date('Y/m', strtotime($current)), array('update' => 'calendar', 'style' => 'float:right;', 'class' => 'cal_nav')); ?>
+
+ <?php $prevDay = strtotime($current . ' -1 day'); ?>
+ <?=$ajax->link('< Prev', '/calendar/index' . date('/Y/m/d', $prevDay), array('update' => 'calendar', 'class' => 'cal_nav')); ?>
+
+ <span class='month_name'><?=date('l, F jS', strtotime($current)); ?></span>
+
+ <?php $nextDay = strtotime($current . ' +1 day'); ?>
+ <?=$ajax->link('Next >', '/calendar/index' . date('/Y/m/d', $nextDay), array('update' => 'calendar', 'class' => 'cal_nav')); ?>
+
+ </div>
+
+</div>
+
+<div id="day">
+ <div class="event_hours">
+ <?php for ($i = 0; $i < 24; $i++) { ?>
+ <?php $hour = ($i >= 12 ? $i - 12 : $i); ?>
+ <div class="hour">
+ <div class="hour_num">
+ <?=($hour == 0 ? 12 : $hour); ?>
+ <?=($i < 12 ? 'am' : 'pm'); ?>
+ </div>
+ <?
+ foreach ($events as $event) {
+ if (intval(date('H', strtotime($event['start']))) == $i) {
+ include $this->element('day_event');
+ }
+ }
+ ?>
+ </div>
+ <?php } ?>
+ </div>
+ <br /><br /><br /><br /><br /><br /><br /><br />
+</div>
+
+<?php if (!$isAjax) { ?></div><?php } ?>
+
+<?=$javascript->codeBlock('Calendar.resize();'); ?>
+
+<?php //pr($events); ?>
+<?=$javascript->writeEvents(); ?>
diff --git a/views/elements/day_event.ctp b/views/elements/day_event.ctp
new file mode 100755
index 0000000..e39b191
--- /dev/null
+++ b/views/elements/day_event.ctp
@@ -0,0 +1,12 @@
+<div class="hour_event" style="height: <?=$cal->getTimeHeight($event['start'], $event['end']); ?>;">
+ <b class="rtop"><b class="r1"></b><b class="r2"></b><b class="r3"></b><b class="r4"></b></b>
+ <? if (!empty($event['location'])) { ?>
+ <div class="event_location">Location: <?=$event['location']; ?></div>
+ <? } ?>
+ <div class="event_time"><?=date('g:i a', strtotime($event['start'])); ?></div>
+ <div class="event_body">
+ <?=$event['title']; ?>
+ <p><?=$event['notes']; ?></p>
+ </div>
+ <b class="rbottom"><b class="r4"></b><b class="r3"></b><b class="r2"></b><b class="r1"></b></b>
+</div>
diff --git a/views/helpers/cal.php b/views/helpers/cal.php
new file mode 100755
index 0000000..2e6c8bf
--- /dev/null
+++ b/views/helpers/cal.php
@@ -0,0 +1,214 @@
+<?
+
+class CalHelper extends Helper {
+
+ var $helpers = array('Html', 'Javascript', 'Ajax');
+
+ ////// properties
+ var $month;
+ var $year;
+ var $numDays;
+ var $firstDay;
+ var $monthName;
+ var $weekdays = array('sun','mon','tue','wed','thu','fri','sat');
+ var $today;
+ var $defColor = "#ddd";
+ var $todayColor = "#fda";
+ var $curDay;
+ var $curMo;
+ var $curYr;
+ var $oneDayEvents;
+ var $weekends = true;
+ var $showPrev = true;
+ var $showNext = true;
+ var $controllerName = 'calendar';
+
+ ////// methods
+
+ /* constructor function */
+ public function init($month = false, $year = false) {
+
+ // init current date
+ $this->curDay = date('j');
+ $this->curMo = date('n');
+ $this->curYr = date('Y');
+
+ if(!$month) {
+ $month = $this->curMo;
+ }
+ if(!$year) {
+ $year = $this->curYr;
+ }
+
+ $this->numDays = date('t', strtotime($year . '-' . $month . '-01'));
+ $this->firstDay = date('w', strtotime($year . '-' . $month . '-01'));
+ $this->monthName = date('F', strtotime($year . '-' . $month . '-01'));
+ $this->month = $month;
+ $this->year = $year;
+ $this->oneDayEvents = $this->getOneDayEvents($this->month,$this->year);
+
+ if ((date('n') == date('n', strtotime($year . '-' . $month . '-01'))) && (date('Y') == date('Y', strtotime($year . '-' . $month . '-01'))) ) {
+ $this->today = date('j');
+ } else {
+ $this->today = "wrong month or year";
+ }
+ }
+
+ /* get number of weeks to draw */
+ public function numWeeks($mo,$yr) {
+ $ts = strtotime($mo . "/1/" . $yr);
+ $firstDay = date("w",$ts);
+ if($firstDay == 0) {
+ $firstSun = 0;
+ } else {
+ $firstSun = 8-$firstDay;
+ }
+ $numDays = date("t",$ts);
+ $numSundays = (($numDays-$firstSun)/7);
+ return(floor($numSundays)+2);
+ }
+
+ /* draw calendar */
+ public function draw($id = 'calendar', $view = 'month', $header = true, $wrapper = true) {
+
+ $this->id = $id;
+
+ if ($wrapper) {
+ echo '<div id="'.$id.'">';
+ }
+
+ if ($header) {
+ $this->drawHeader();
+ }
+
+ $i = null;
+ $max = 0;
+ $this->Javascript->cacheEvents();
+
+ echo '<div class="days">';
+ for($w = 0; $w < $this->numWeeks($this->month, $this->year); $w++) {
+
+ $wClass = 'week';
+ if ($w == 0) {
+ $wClass .= ' week_first';
+ }
+
+ echo '<div class="'.$wClass.'">'."\n";
+ for($d = 0; $d < 7; $d++) {
+ if(($i == null) && ($d == $this->firstDay)) {
+ if($i > $this->numDays) {
+ $i = "done";
+ } else {
+ $i = 1;
+ }
+ } elseif(($i > 0) && ($i < $this->numDays)) {
+ ++$i;
+ } else {
+ $i = null;
+ }
+
+ if (intval($i) > $max) {
+ $max = $i;
+ }
+
+ $class = '';
+ if($i) {
+ if($i == $this->today) {
+ $class = " today";
+ }
+
+ echo "\t<div id='day_" . $i . "' class='day " . $class . "'>";
+ } else {
+ $class = "empty";
+ echo "\t<div class='day " . $class . "'>";
+ }
+
+ echo '<div id="dayNum_'.$i.'" class="day_num">'.$i.'</div>';
+ echo "</div>\n";
+ }
+ print("</div>\r");
+ }
+ echo "</div>\n";
+
+ if ($wrapper) {
+ echo "</div>\n";
+ }
+
+ $script = 'for (i = 1; i <= '.$max.'; i++) {'."\n";
+ $script .= ' if ($("day_" + i)) {' . "\n";
+ $script .= ' Droppables.add("day_" + i, {accept: "event", onDrop : function(element, day) { Calendar.addEvent(day, element); }, onHover : function(element, day) { Calendar.highlight(day, element); }, hoverclass : "day_hover"});'."\n";
+ $script .= ' Event.observe("day_" + i, "click", function(e) { Calendar.select(Event.element(e)); Calendar.stopEvent(e); }, true);'."\n";
+ $script .= ' Event.observe("day_" + i, "dblclick", function(e) { Events.save(Calendar.addEvent(Event.element(e), null)); Calendar.stopEvent(e); }, true);'."\n";
+ $script .= ' }' . "\n";
+ $script .= '}'."\n";
+
+ echo $this->Javascript->codeBlock("\n" . $script . "\n");
+ }
+
+ public function drawHeader() {
+
+ echo "<div class='cal_header'>\n<div class='cal_title'>\n";
+ $current = $this->year . '/' . $this->month . '/1';
+
+ if ($this->showPrev) {
+ $prevMonth = strtotime($current . ' -1 month');
+ echo $this->Ajax->link('< Prev', '/' . $this->controllerName . '/view' . date('/Y/m', $prevMonth), array('update' => $this->id, 'class' => 'cal_nav'));
+ }
+
+ echo "<span class='month_name'>" . $this->monthName . " " . $this->year . "</span>\n";
+
+ if ($this->showNext) {
+ $nextMonth = strtotime($current . ' +1 month');
+ echo $this->Ajax->link('Next >', '/' . $this->controllerName . '/view' . date('/Y/m', $nextMonth), array('update' => $this->id, 'class' => 'cal_nav'));
+ }
+ echo "</div>\n";
+
+ foreach($this->weekdays as $cur) {
+ if ($this->weekends || !in_array($cur, array('sun', 'sat'))) {
+ print("\t<div class='day_names'>" . $cur . "</div>\n");
+ }
+ }
+ print("</div>\r");
+ }
+
+ // draw multiday events
+ private function drawMultidayEvents($week) {
+ print("<div class='multiday_event' style='width:28%;'>this is event 2</div>\n");
+ print("<div class='multiday_event' style='width:39%;margin-left:40%;'>this is event 3</div>\n");
+ print("</div>");
+ }
+
+ // draw a single event
+ private function drawSingleEvent($day,$time,$title,$desc,$id) {
+ print("<div class='day_event' onClick=\"eventClick('" . $title . "');\">");
+ print("•<span class='event_time'>" . $time . "</span><span class='event_title'> - " . $title . "</span>");
+ //print("<span class='event_desc'>" . $desc . "</span><br />");
+ print("</div>\n");
+ }
+
+ // get event data for month
+ private function getOneDayEvents($month,$year) {
+ $ret = array(
+ 'day'=>11,
+ 'events' => array(
+ 0 => array(
+ 'id' => "21",
+ 'time'=>'14:30',
+ 'title'=>'Test Event',
+ 'desc'=>'my long description of this event.'
+ )
+ )
+ );
+ }
+
+ public function getTimeHeight($start, $end) {
+ $pixelsPerHour = 20;
+ $tmpStart = date('H:i:s', strtotime($start));
+ $tmpEnd = date('H:i:s', strtotime($end));
+ $diff = strtotime($end) - strtotime($start);
+ //return ($diff / 3600) * $pixelsPerHour;
+ return $pixelsPerHour;
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/views/helpers/print_month.php b/views/helpers/print_month.php
new file mode 100755
index 0000000..f3e4e39
--- /dev/null
+++ b/views/helpers/print_month.php
@@ -0,0 +1,204 @@
+<?
+
+class PrintMonthHelper extends Helper {
+
+ var $helpers = array('Html', 'Javascript', 'Ajax');
+
+ ////// properties
+ var $month;
+ var $year;
+ var $numDays;
+ var $firstDay;
+ var $monthName;
+ var $weekdays = array('sun','mon','tue','wed','thu','fri','sat');
+ var $today;
+ var $defColor = "#ddd";
+ var $todayColor = "#fda";
+ var $curDay;
+ var $curMo;
+ var $curYr;
+ var $oneDayEvents;
+ var $weekends = true;
+ var $showPrev = true;
+ var $showNext = true;
+ var $controllerName = 'calendar';
+
+ ////// methods
+
+ /* constructor function */
+ public function init($month = false, $year = false) {
+
+ // init current date
+ $this->curDay = date('j');
+ $this->curMo = date('n');
+ $this->curYr = date('Y');
+
+ if(!$month) {
+ $month = $this->curMo;
+ }
+ if(!$year) {
+ $year = $this->curYr;
+ }
+
+ $this->numDays = date('t', strtotime($year . '-' . $month . '-01'));
+ $this->firstDay = date('w', strtotime($year . '-' . $month . '-01'));
+ $this->monthName = date('F', strtotime($year . '-' . $month . '-01'));
+ $this->month = $month;
+ $this->year = $year;
+ $this->oneDayEvents = $this->getOneDayEvents($this->month,$this->year);
+
+ if ((date('n') == date('n', strtotime($year . '-' . $month . '-01'))) && (date('Y') == date('Y', strtotime($year . '-' . $month . '-01'))) ) {
+ $this->today = date('j');
+ } else {
+ $this->today = "wrong month or year";
+ }
+ }
+
+ /* get number of weeks to draw */
+ public function numWeeks($mo,$yr) {
+ $ts = strtotime($mo . "/1/" . $yr);
+ $firstDay = date("w",$ts);
+ if($firstDay == 0) {
+ $firstSun = 0;
+ } else {
+ $firstSun = 8-$firstDay;
+ }
+ $numDays = date("t",$ts);
+ $numSundays = (($numDays-$firstSun)/7);
+ return(floor($numSundays)+2);
+ }
+
+ /* draw calendar */
+ public function draw($id = 'calendar', $view = 'month', $header = true, $wrapper = true) {
+
+ $this->id = $id;
+
+ if ($wrapper) {
+ echo '<div id="'.$id.'">';
+ }
+
+ if ($header) {
+ $this->drawHeader();
+ }
+
+ $i = null;
+ $max = 0;
+ $this->Javascript->cacheEvents();
+
+ echo '<div class="days">';
+ for($w = 0; $w < $this->numWeeks($this->month, $this->year); $w++) {
+
+ $wClass = 'week';
+ if ($w == 0) {
+ $wClass .= ' week_first';
+ }
+
+ echo '<div class="'.$wClass.'">'."\n";
+ for($d = 0; $d < 7; $d++) {
+ if(($i == null) && ($d == $this->firstDay)) {
+ if($i > $this->numDays) {
+ $i = "done";
+ } else {
+ $i = 1;
+ }
+ } elseif(($i > 0) && ($i < $this->numDays)) {
+ ++$i;
+ } else {
+ $i = null;
+ }
+
+ if (intval($i) > $max) {
+ $max = $i;
+ }
+
+ $class = '';
+ if($i) {
+ if($i == $this->today) {
+ $class = " today";
+ }
+
+ echo "\t<div id='day_" . $i . "' class='day " . $class . "'>";
+ } else {
+ $class = "empty";
+ echo "\t<div class='day " . $class . "'>";
+ }
+
+ echo '<div id="dayNum_'.$i.'" class="day_num">'.$i.'</div>';
+ echo "</div>\n";
+ }
+ print("</div>\r");
+ }
+ echo "</div>\n";
+
+ if ($wrapper) {
+ echo "</div>\n";
+ }
+ }
+
+ public function drawHeader() {
+
+ echo "<div class='cal_header'>\n<div class='cal_title'>\n";
+ $current = $this->year . '/' . $this->month . '/1';
+
+ if ($this->showPrev) {
+ $prevMonth = strtotime($current . ' -1 month');
+ echo $this->Ajax->link('< Prev', '/' . $this->controllerName . '/view' . date('/Y/m', $prevMonth), array('update' => $this->id));
+ }
+
+ echo "<span class='month_name'>" . $this->monthName . " " . $this->year . "</span>\n";
+
+ if ($this->showNext) {
+ $nextMonth = strtotime($current . ' +1 month');
+ echo $this->Ajax->link('Next >', '/' . $this->controllerName . '/view' . date('/Y/m', $nextMonth), array('update' => $this->id));
+ }
+ echo "</div>\n";
+
+ foreach($this->weekdays as $cur) {
+ if ($this->weekends || !in_array($cur, array('sun', 'sat'))) {
+ print("\t<div class='day_names'>" . $cur . "</div>\n");
+ }
+ }
+ print("</div>\r");
+ }
+
+ // draw multiday events
+ private function drawMultidayEvents($week) {
+ print("<div class='multiday_event' style='width:28%;'>this is event 2</div>\n");
+ print("<div class='multiday_event' style='width:39%;margin-left:40%;'>this is event 3</div>\n");
+ print("</div>");
+ }
+
+ // draw a single event
+ private function drawSingleEvent($day,$time,$title,$desc,$id) {
+ print("<div class='day_event' onClick=\"eventClick('" . $title . "');\">");
+ print("•<span class='event_time'>" . $time . "</span><span class='event_title'> - " . $title . "</span>");
+ //print("<span class='event_desc'>" . $desc . "</span><br />");
+ print("</div>\n");
+ }
+
+ // get event data for month
+ private function getOneDayEvents($month,$year) {
+ $ret = array(
+ 'day'=>11,
+ 'events' => array(
+ 0 => array(
+ 'id' => "21",
+ 'time'=>'14:30',
+ 'title'=>'Test Event',
+ 'desc'=>'my long description of this event.'
+ )
+ )
+ );
+ }
+
+ public function getTimeHeight($start, $end) {
+ $pixelsPerHour = 20;
+ $tmpStart = date('H:i:s', strtotime($start));
+ $tmpEnd = date('H:i:s', strtotime($end));
+ $diff = strtotime($end) - strtotime($start);
+ //return ($diff / 3600) * $pixelsPerHour;
+ return $pixelsPerHour;
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/views/layouts/ajax.ctp b/views/layouts/ajax.ctp
new file mode 100755
index 0000000..b7c7428
--- /dev/null
+++ b/views/layouts/ajax.ctp
@@ -0,0 +1 @@
+<?php $content_for_layout; ?>
\ No newline at end of file
diff --git a/views/layouts/default.ctp b/views/layouts/default.ctp
new file mode 100755
index 0000000..88f9862
--- /dev/null
+++ b/views/layouts/default.ctp
@@ -0,0 +1,26 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<?php echo $html->docType('xhtml'); ?>
+<head>
+ <title><?php echo $title_for_layout; ?></title>
+ <?php echo $html->charset('UTF-8'); ?>
+
+ <?php echo $html->css('base', null, array('media' => 'screen')); ?>
+ <?php echo $html->css('print', null, array('media' => 'print')); ?>
+ <?php echo $html->css('cal', null, array('media' => 'all')); ?>
+ <?php echo $html->css('cal_print', null, array('media' => 'print')); ?>
+
+ <?php echo $javascript->link(array(
+ 'prototype', 'scriptaculous', 'date', 'application', 'calendar', 'functions'
+ )); ?>
+
+</head>
+
+<body>
+
+<?php echo $session->flash(); ?>
+<div id="content">
+<?php echo $content_for_layout; ?>
+</div>
+
+</body>
+</html>
diff --git a/views/layouts/error.ctp b/views/layouts/error.ctp
new file mode 100755
index 0000000..245b327
--- /dev/null
+++ b/views/layouts/error.ctp
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
+<html>
+<head>
+<title><?php echo $code; ?> <?php echo $name; ?></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+<h1><?php echo $name; ?></h1>
+<p><?php echo $message; ?></p>
+</body>
+</html>
diff --git a/views/layouts/flash.ctp b/views/layouts/flash.ctp
new file mode 100755
index 0000000..df81edd
--- /dev/null
+++ b/views/layouts/flash.ctp
@@ -0,0 +1,21 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="pl" xml:lang="pl">
+<head>
+<title><?php echo $page_title?></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<?php if(DEBUG == 0) { ?>
+<meta http-equiv="Refresh" content="<?php echo $pause?>;url=<?php echo $url?>" />
+<?php } ?>
+<style><!--
+P { text-align:center; font:bold 1.1em sans-serif }
+A { color:#444; text-decoration:none }
+A:HOVER { text-decoration: underline; color:#44E }
+--></style>
+</head>
+
+<body>
+
+<p><a href="<?php echo $url?>"><?php echo $message?></a></p>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/views/layouts/print.ctp b/views/layouts/print.ctp
new file mode 100755
index 0000000..db16e3f
--- /dev/null
+++ b/views/layouts/print.ctp
@@ -0,0 +1,11 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <title><?php echo $title_for_layout; ?></title>
+ <?php echo $html->css('print'); ?>
+ </head>
+
+<?php echo $content_for_layout; ?>
+
+</html>
diff --git a/views/printer/index.ctp b/views/printer/index.ctp
new file mode 100755
index 0000000..a533ea4
--- /dev/null
+++ b/views/printer/index.ctp
@@ -0,0 +1,76 @@
+<div id='printSizer' style='width:576px;padding-top:36px;margin-left:auto;margin-right:auto;'>
+<?php
+ $start = date("m/d/Y",strtotime($eventData['start']));
+ $end = date("m/d/Y",strtotime($eventData['end']));
+ $i = 0;
+
+ if(is_array($eventData['events'])) {
+ foreach($eventData['events'] as $key=>$cur) {
+ $cleaned[$i] = $cur;
+ $cleaned[$i]['printed'] = 0;
+ $cleaned[$i]['id'] = $key;
+ $i++;
+ }
+ } else {
+ $cleaned = false;
+ }
+ function cmp($a,$b) {
+ if(strtotime($a['start']) > strtotime($b['start'])) {
+ return 1;
+ } else {
+ return -1;
+ }
+ }
+ usort($cleaned,"cmp");
+ //pr($cleaned);die();
+ e("<div id='printhead' style='display:box;'>");
+ e("<div class='month_name' style='margin-bottom:30px;padding-bottom:10px;border-bottom:1px solid #000;'>");
+ e($html->image('cal/sage_logo_sm.gif', array('alt' => 'Sage Logo','align'=>'baseline','style'=>'padding-right:20px;')));
+ e("Sage Calendar: Scheduled Events</div>");
+ e("</div>");
+ e("<div style='margin-bottom:30px;color:#fff;background-color:#000;padding:20px;font-weight:bold;font-size:10px;'>From " . date("D m/d/y",strtotime($start)) . " through " . date("D m/d/y",strtotime($end)) . "</div>");
+ if($cleaned) {
+ for($i=0;$i<count($cleaned);$i++) {
+ $cur = $cleaned[$i];
+ if($cur['printed'] == 0) {
+ $curday = strtotime(date("m/d/Y",strtotime($cur['start'])));
+ e("<div class='dayDiv' style='margin-top:20px;padding-bottom:10px;border-bottom:1px dotted #000;'>");
+ e("<div style='float:left;width:30%;font-size:14px;'>" . date("D m/d/Y",strtotime($cur['start'])) . "</div>");
+ e("<div style='margin-left:25%;width:70%;text-align:left;'><table style='font-size:11px;' cellpadding=2 cellspacing=0>");
+ for($j=0;$j<count($cleaned);$j++) {
+ if( ($cleaned[$j]['printed'] == 0) && (strtotime(date("m/d/Y",strtotime($cleaned[$j]['start']))) == $curday) ) {
+ e("<tr><td><span style='font-weight:bold;'>". $cleaned[$j]['title'] . "</span></td><td>" . date("h:i a",strtotime($cleaned[$j]['start'])) . " - </td><td>");
+ if(strtotime(date("m/d/Y",strtotime($cleaned[$j]['end']))) > $curday) {
+ e(date("m/d/Y h:i a",strtotime($cleaned[$j]['end'])));
+ } else {
+ e(date("h:i a",strtotime($cleaned[$j]['end'])));
+ }
+ e("</td></tr>");
+ e("</div>");
+ $cleaned[$j]['printed'] = 1;
+ }
+ }
+ e("</table></div>");
+ e("</div>");
+ }
+ }
+ } else {
+ e("<div class='dayDiv' style='margin-top:20px;padding-bottom:10px;border-bottom:1px dotted #000;'>");
+ e("<span style='font-size:16px;font-weight:bold;'>No Events Scheduled</span>");
+ e("</div>");
+ }
+
+?>
+</div>
+<script type='text/javascript'>
+
+ Event.observe(window,'load',function(){ doOnceLoaded(); });
+
+ function doOnceLoaded() {
+ //parent.print();
+ //window.opener.Effect.SlideUp("eventForm");
+ //window.close();
+
+ }
+
+</script>
\ No newline at end of file
diff --git a/views/printer/index_nu.thtml b/views/printer/index_nu.thtml
new file mode 100755
index 0000000..05763a5
--- /dev/null
+++ b/views/printer/index_nu.thtml
@@ -0,0 +1,58 @@
+<? //die(pr($events)); ?>
+
+<? $javascript->cacheEvents(); ?>
+
+<? if(!$isAjax) { ?>
+
+<? $javascript->event('$("btnEdit")', 'click', 'Events.edit(Calendar.Events.selected);'); ?>
+<? $javascript->event('$("btnDelete")', 'click', 'Events.remove(Calendar.Events.selected);'); ?>
+<? $javascript->event('$("btnAdd")', 'click', 'Events.add(Calendar.selected)'); ?>
+<? $javascript->event('$("btnZoom")', 'click', 'Events.view(Calendar.selected);'); ?>
+<? $javascript->event('$("btnImport")', 'click', $ajax->remoteFunction(array(
+ 'url' => '/calendar/import',
+ 'update' => 'eventForm',
+ 'complete' => 'Effect.SlideDown("eventForm");'
+))); ?>
+<? $javascript->event('$("btnExport")', 'click', 'window.location = "'.$this->base.'/calendar/export";'); ?>
+<? $javascript->event('$("btnPrint")', 'click', $ajax->remoteFunction(array(
+ 'url' => '/calendar/printerForm',
+ 'update' => 'eventForm',
+ 'complete' => 'Effect.SlideDown("eventForm");'
+))); ?>
+<div id="eventForm" style="display: none;"></div>
+<div id="log" style="display: none; line-height: 16px; position: absolute; left: 10px; top: 80%; font-size: 12px; width: 75%; height: 500px; overflow: auto; border: 1px solid #333333;"></div>
+
+<? } ?>
+
+ <? $cal->init($month, $year); ?>
+ <?=$cal->draw('calendar', 'month', true, !$isAjax); ?>
+
+ <? // Set up JavaScript bindings between Calendar object and UI ?>
+ <?=$javascript->codeBlock('Object.extend(Calendar, {base : "'.$this->base.'", year : '.$year.', month : '.$month.'});'); ?>
+ <?=$javascript->codeBlock('$("tools").show();'); ?>
+
+<?=$javascript->codeBlock('Calendar.bind("calendar");'); ?>
+<?=$javascript->object($events, true, 'events = ', ';'); ?>
+
+<?=$javascript->link('bindings'); ?>
+<script type="text/javascript">
+
+ Calendar.Events.click = Events.click;
+ Calendar.loading(true);
+ Calendar.Events.items = [];
+
+ z = 0;
+ while (typeof events[z] != 'undefined') {
+ Calendar.Events.add(events[z]);
+ z++;
+ }
+
+ Calendar.Events.select(null);
+ setTimeout('Calendar.loading(false);', 1);
+ Calendar.resize();
+
+ Event.observe(document, 'click', function(e) { Element.hide('toolbar'); }, true);
+ Element.hide('toolbar');
+</script>
+
+<?=$javascript->writeEvents(); ?>
\ No newline at end of file
diff --git a/webroot/.htaccess b/webroot/.htaccess
new file mode 100755
index 0000000..c54f048
--- /dev/null
+++ b/webroot/.htaccess
@@ -0,0 +1,5 @@
+<IfModule mod_rewrite.c>
+ RewriteEngine On
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteRule ^(.*)$ index.php?url=$1&mod_rewrite=true [QSA,L]
+</IfModule>
diff --git a/webroot/css.php b/webroot/css.php
new file mode 100755
index 0000000..cf9d347
--- /dev/null
+++ b/webroot/css.php
@@ -0,0 +1,116 @@
+<?php
+/* SVN FILE: $Id: css.php 1591 2005-12-23 21:14:07Z phpnut $ */
+
+/**
+ * CSS functions.
+ *
+ * Outputs CSS.
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP : Rapid Development Framework <http://www.cakephp.org/>
+ * Copyright (c) 2005, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2005, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake
+ * @subpackage cake.cake.app.webroot
+ * @since CakePHP v 0.2.9
+ * @version $Revision: 1591 $
+ * @modifiedby $LastChangedBy: phpnut $
+ * @lastmodified $Date: 2005-12-23 16:14:07 -0500 (Fri, 23 Dec 2005) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+
+/**
+ * Included libs and files.
+ */
+require_once(CONFIGS.'paths.php');
+require_once(CAKE.'basics.php');
+require_once(LIBS.'folder.php');
+require(LIBS.'file.php');
+
+/**
+ * Returns CSSPP-compressed CSS string for file at given path.
+ *
+ * @param string $path
+ * @param string $name
+ * @return string Compressed CSS
+ */
+function make_clean_css ($path, $name)
+{
+ require_once(VENDORS.'csspp'.DS.'csspp.php');
+
+ $data = file_get_contents($path);
+ $csspp = new csspp();
+ $output = $csspp->compress($data);
+
+ $ratio = 100-(round(strlen($output)/strlen($data), 3)*100);
+ $output = " /* file: $name, ratio: $ratio% */ " . $output;
+
+ return $output;
+}
+
+/**
+ * Write given content to the CSS cache, at the given path.
+ * If the directory given in the path does not exist, it is created.
+ *
+ * @param string $path
+ * @param string $content
+ * @return boolean Success
+ */
+function write_css_cache ($path, $content)
+{
+ if (!is_dir(dirname($path)))
+ mkdir(dirname($path));
+
+ $cache = new File($path);
+ return $cache->write($content);
+}
+
+if (preg_match('|\.\.|', $url) || !preg_match('|^ccss/(.+)$|i', $url, $regs))
+ die('Wrong file name.');
+
+$filename = 'css/'.$regs[1];
+$filepath = CSS.$regs[1];
+$cachepath = CACHE.'css'.DS.str_replace(array('/','\\'), '-', $regs[1]);
+
+if (!file_exists($filepath))
+ die('Wrong file name.');
+
+
+if (file_exists($cachepath))
+{
+ $templateModified = filemtime($filepath);
+ $cacheModified = filemtime($cachepath);
+
+ if ($templateModified > $cacheModified)
+ {
+ $output = make_clean_css ($filepath, $filename);
+ write_css_cache ($cachepath, $output);
+ }
+ else
+ {
+ $output = file_get_contents($cachepath);
+ }
+}
+else
+{
+ $output = make_clean_css ($filepath, $filename);
+ write_css_cache ($cachepath, $output);
+}
+
+header("Date: ".date("D, j M Y G:i:s ", $templateModified).'GMT');
+header("Content-Type: text/css");
+header("Expires: ".gmdate("D, j M Y H:i:s", time()+DAY)." GMT");
+header("Cache-Control: cache"); // HTTP/1.1
+header("Pragma: cache"); // HTTP/1.0
+print $output;
+
+?>
\ No newline at end of file
diff --git a/webroot/css/LMstyles.css b/webroot/css/LMstyles.css
new file mode 100755
index 0000000..cefe8bb
--- /dev/null
+++ b/webroot/css/LMstyles.css
@@ -0,0 +1,329 @@
+.body {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ line-height: 15px;
+ color: #000000;
+ padding: 10px 20px 20px;
+}
+.footer {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 9px;
+ text-transform: uppercase;
+ color: #666666;
+ text-decoration: none;
+}
+a:link {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ color: #003399;
+ text-decoration: none;
+}
+a:visited {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ color: #003399;
+ text-decoration: none;
+}
+a:hover {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ color: #AECC38;
+ text-decoration: none;
+}
+a:active {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ color: #003399;
+ text-decoration: none;
+}
+.headers {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ font-style: normal;
+ line-height: normal;
+ font-weight: 600;
+ color: #003399;
+ text-decoration: none;
+}
+.bullets {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ line-height: 15px;
+ color: #000000;
+ list-style-position: outside;
+ list-style-image: url("/images/buttons/arrow.gif");
+}
+.medtext {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ color: #003399;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ margin-bottom: 5px;
+ margin-top: 5px;
+}
+.tab {
+ background-image: url("/images/centertab.gif");
+ background-repeat: repeat-x;
+ background-position: left;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ text-align: center;
+}
+.banner {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 16px;
+ font-weight: bold;
+ color: #666666;
+ padding: 5px 0px 5px 5px;
+ background-color: #EEEEEE;
+}
+
+
+#index_level_1 a {
+ font-size: 13px;
+ line-height: 110%;
+}
+
+#index_level_1 a:hover {
+ font-size: 13px;
+ line-height: 110%;
+}
+
+#index_level_2 a {
+ font-size: 11px;
+ line-height: 110%;
+}
+
+#index_level_2 a:hover {
+ font-size: 11px;
+ line-height: 110%;
+}
+
+#sidebar {
+ vertical-align: top;
+ padding-top: 20px;
+}
+
+#sidebar td.element {
+ font-size: 12px;
+ padding: 5px 0px 5px 10px;
+}
+
+#sidebar td.element_active {
+ font-size: 12px;
+ padding: 5px 0px 5px 10px;
+ float: left;
+ background-color: #C7E5FF;
+}
+
+#sidebar a.element:link {
+ padding: 3px 20px 2px 0px;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ text-decoration: underline;
+}
+
+#sidebar a.element:hover {
+ padding: 3px 20px 2px 0px;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ text-decoration: underline;
+}
+
+#sidebar a.element:visited {
+ padding: 3px 20px 2px 0px;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ text-decoration: underline;
+}
+
+#sidebar a.element:visited {
+ padding: 3px 20px 2px 0px;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ text-decoration: underline;
+}
+
+#main_block {
+ background: url("/images/bluegradient.gif") repeat-y left;
+}
+
+#main {
+ background-color: #FFFFFF;
+ margin: 10px 0px 20px 10px;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ color: #000000;
+}
+
+#main td {
+ color: #000000;
+ font-size: 11px;
+ line-height: 120%;
+ font-family: Verdana, Arial, "Trebuchet MS", Trebuchet, san-serif
+}
+
+#main td.sage_report {
+ font-size: 11px;
+}
+
+#main td.criteria {
+ background-color: #FFFFCC;
+ padding: 7px;
+}
+
+#main td.search {
+ background-color: #E3F5FF;
+ padding: 7px;
+}
+
+#main td.category_header {
+ background: #99CCFF url("/images/gradient_small.gif");
+ color: #000B9A;
+ font: 10px verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif;
+ font-weight: 600;
+}
+
+#main td.event {
+ color: #002E8B;
+ font: 9px verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif;
+ border-bottom: 1px solid black;
+}
+
+#main a {
+ color: #022C83;
+ font-size: 11px;
+ line-height: 120%;
+ font-family: Verdana, Arial, "Trebuchet MS", Trebuchet, san-serif;
+ text-decoration: none;
+}
+
+#main a:hover {
+ color: #999999;
+ font-size: 11px;
+ line-height: 120%;
+ font-family: Verdana, Arial, "Trebuchet MS", Trebuchet, san-serif;
+ text-decoration: underline;
+}
+
+#main select {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ font-weight: bold;
+ color: #022C83;
+}
+
+#main input {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ font-weight: bold;
+ color: #022C83;
+ border: 1px solid black;
+ padding: 2px 1px 0px 1px;
+ line-height: 110%;
+}
+
+#main button {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ font-weight: bold;
+ color: #022C83;
+ border: 1px solid black;
+ padding: 2px 1px 0px 1px;
+ line-height: 110%;
+}
+
+#main textarea {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ font-weight: bold;
+ color: #022C83;
+ border: 1px solid black;
+ padding: 2px 1px 0px 1px;
+ line-height: 110%;
+}
+
+#main button {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ font-weight: bold;
+ color: #022C83;
+}
+
+#main .header {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 14px;
+ line-height: 20px;
+ font-weight: bold;
+ color: #000000;
+ padding-bottom: 10px;
+}
+
+#permissions td.header {
+ border-bottom: 1px solid gray;
+}
+
+#permissions td.default {
+ border-left: 1px solid gray;
+ border-bottom: 1px solid gray;
+}
+
+#permissions td.view {
+ border-left: 1px solid gray;
+ border-right: 1px solid gray;
+ border-bottom: 1px solid gray;
+}
+
+#permissions td.add {
+ border-bottom: 1px solid gray;
+}
+
+#permissions td.edit {
+ border-bottom: 1px solid gray;
+}
+
+#permissions td.delete {
+ border-right: 1px solid gray;
+ border-bottom: 1px solid gray;
+}
+
+#permissions input {
+ border: 0px;
+}
+
+#criteria td {
+ color: #000000;
+ font-size: 10px;
+ line-height: 120%;
+ font-family: Verdana, Arial, "Trebuchet MS", Trebuchet, san-serif
+}
+
+#criteria input {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 10px;
+ font-weight: bold;
+ color: #022C83;
+ border: 1px solid black;
+ padding: 2px 1px 0px 2px;
+ line-height: 110%;
+}
+
+#record_info input {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 10px;
+ font-weight: bold;
+ color: #022C83;
+ border: 1px solid black;
+ padding: 2px 1px 0px 1px;
+ line-height: 110%;
+}
+
+#record_info select {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 9px;
+ font-weight: bold;
+ color: #022C83;
+ padding: 2px 1px 0px 1px;
+ line-height: 110%;
+}
diff --git a/webroot/css/bak_tabs.css b/webroot/css/bak_tabs.css
new file mode 100755
index 0000000..3e50d45
--- /dev/null
+++ b/webroot/css/bak_tabs.css
@@ -0,0 +1,59 @@
+#tabs {
+ float: left;
+ width: 100%;
+ line-height: normal;
+}
+
+#tabs ul {
+ margin: 0px;
+ padding: 0px;
+ list-style: none;
+ width: 880px;
+}
+
+#tabs li {
+ float: left;
+ background: url("/images/lefttab1.gif") no-repeat left top;
+ margin: 0px;
+ padding: 0px 0px 0px 20px;
+}
+
+#tabs li.current {
+ background: url("/images/lefttab2.gif") no-repeat left top;
+}
+
+#tabs a {
+ float: left;
+ display: block;
+ background: url("/images/righttab1.gif") no-repeat right top;
+ padding: 3px 20px 2px 0px;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+}
+
+#tabs .current a {
+ background: url("/images/righttab2.gif") no-repeat right top;
+}
+
+/* Commented Backslash Hack
+ hides rule from IE5-Mac \*/
+#tabs a {
+ float:none;
+}
+/* End IE5-Mac hack */
+
+#tabs a:link { color: #003399; }
+
+#tabs a:visited { color: #003399; }
+
+#tabs a:hover { color: #666666; }
+
+#tabs a:active { color: #003399; }
+
+#tabs .current a:link { color: #FFFFFF; }
+
+#tabs .current a:visited { color: #FFFFFF; }
+
+#tabs .current a:hover { color: #AECC38; }
+
+#tabs .current a:active { color: #FFFFFF; }
diff --git a/webroot/css/base.css b/webroot/css/base.css
new file mode 100755
index 0000000..67c50a8
--- /dev/null
+++ b/webroot/css/base.css
@@ -0,0 +1,32 @@
+/* CSS for Copy Cop Site */
+
+/* tag selectors */
+
+body {
+ background-color: #FFFFFF;
+ text-align:left;
+ font-family: 'Lucida Grande', 'Lucida Sans Unicode', Verdana, sans-serif;
+ font-size: 9px;
+ line-height:100%;
+ color:#000000;
+ padding:0px;
+ margin:0px;
+ overflow-x:hidden;
+}
+
+a {
+ border:0px solid blue;
+}
+
+a:link, a:visited{
+ color: #005FA9;
+ text-decoration: none;
+}
+
+a:hover{
+ text-decoration: underline;
+}
+img {
+ border: 0px solid blue;
+}
+/* class selectors */
diff --git a/webroot/css/cal.css b/webroot/css/cal.css
new file mode 100755
index 0000000..813af18
--- /dev/null
+++ b/webroot/css/cal.css
@@ -0,0 +1,559 @@
+html, body {
+ margin: 0px;
+ padding: 0px;
+}
+
+#toolbar, #tools {
+ z-index: 99999;
+ position: absolute;
+ background-color: #FFFFFF;
+ border: 1px solid #CFCFCF;
+ width: 100px;
+}
+
+#calLoader {
+ position: absolute;
+ left: 95%;
+ top: 10px;
+ float: right;
+}
+
+.tool_button {
+ padding: 3px;
+ cursor: pointer;
+}
+
+.window_buttons {
+ float : right;
+ height: 100%;
+}
+
+.window_buttons a, .window_buttons a:active, .window_buttons a:visited,
+.win_title a, .win_title a:active, .win_title a:visited {
+ padding: 2px 4px 2px 4px;
+ margin-left: 5px;
+ font-size: 11px;
+ color: #FFFFFF;
+ text-decoration: none;
+ background-color: #666666;
+ clear: none;
+}
+
+.window_buttons a:hover, .win_title a:hover {
+ color: #666666;
+ background-color: #FFFFFF;
+}
+
+.win_title {
+ text-align: left;
+ height: 20px;
+ background-color: #333;
+ color: #CCCCCC;
+ padding: 10px;
+ font-size: 16px;
+ font-weight: bold;
+}
+
+#day .win_title {
+ text-align: center;
+}
+
+#content {
+ padding: 0px;
+ margin: 0px;
+ left: 0px;
+ top: 0px;
+ height: 100%;
+ width: 100%;
+ overflow: hidden;
+}
+
+#day {
+ overflow: auto;
+}
+
+#calendar {
+ position: absolute;
+ left: 0px;
+ top: 0px;
+ overflow: hidden;
+ width: 100%;
+ height: 100%;
+ font-family: 'Trebuchet MS', Trebuchet, Tahoma, Verdana, Arial, sans-serif;
+ font-size: 11px;
+}
+
+#eventForm {
+ position: absolute;
+ top: 0px;
+ left: 30%;
+ width: 400px;
+ height: 400px;
+ padding: 2px;
+ font-size: 12px;
+ border: 1px solid #666666;
+ background-color: #F0F0F0;
+ z-index: 99999;
+}
+
+.event_capture {
+ width: 100%;
+ height: 100%;
+ z-index: 99999;
+ overflow: hidden;
+ /*background-color: #00FF00;*/
+ /*border: 1px solid #FF0000;*/
+}
+
+#eventForm img {
+ vertical-align: middle;
+}
+
+#formSaving, #formComplete {
+ float: right;
+ clear: right;
+}
+
+.drag_handle {
+ font-size: 9px;
+ color: #FFFFFF;
+ background-color: #606060;
+ float: left;
+ clear: none;
+ padding: 2px;
+ margin-bottom: 3px;
+ /*cursor: move;*/
+}
+
+#eventForm input[type=text], #eventForm select , #eventForm textarea {
+ font-size: 16px;
+ font-weight: bold;
+ font-family: Verdana, Arial, sans-serif;
+}
+
+input.time {
+ width: 95px;
+}
+
+input.date {
+ width: 120px;
+}
+
+#eventForm div.input {
+ clear: both;
+ width: 100%;
+}
+
+#eventForm div.input input, #eventForm div.input select, #eventForm div.input textarea {
+ float: right;
+}
+
+#eventForm div.input textarea {
+ height: 75px;
+}
+
+#eventForm div.input input[type=text], #eventForm div.input select , #eventForm div.input textarea {
+ width: 65%;
+ font-size: 11px;
+ font-weight: normal;
+}
+
+div.button_label {
+ float: none;
+ clear: both;
+ text-align: right;
+ margin-top: 6px;
+}
+
+div.button_label a {
+ float: right;
+}
+
+#eventForm div.input input[type=button], #eventForm div.input input[type=submit] {
+ margin-top: 2px;
+ width: 75px;
+}
+
+#event_form .input_title {
+ width: 200px;
+ font-size: 16px;
+ font-weight: bold;
+ font-family: Verdana, Arial, sans-serif;
+}
+
+#eventForm div.label, #eventForm label {
+ font-weight: bold;
+ font-size: 16px;
+ font-weight: bold;
+ font-family: Verdana, Arial, sans-serif;
+ color:#999;
+ vertical-align:top;
+}
+
+.labeltd {
+ width: 30%;
+ text-align: right;
+ font-size: 14px;
+ font-weight: normal;
+ padding: 2px;
+ height: 100%;
+}
+
+label.label_small {
+ font-size: 12px;
+ padding: 0px;
+ margin: 5px 0 0 0;
+ vertical-align: middle;
+}
+
+.cal_header {
+ padding: 0px;
+ margin: 3px;
+ clear: both;
+ width: 100%;
+ min-width: 100%;
+ text-align: center;
+}
+
+.cal_header .cal_title {
+ margin: 10px;
+ clear: both;
+}
+
+.month_name {
+ font-size: 18px;
+ font-weight: bold;
+ text-decoration: none;
+ margin-bottom: 10px;
+ margin-top: 10px;
+ clear: none;
+ margin: 2px 10px 2px 10px;
+}
+
+.day_names {
+ width: 13%;
+ height: 20px;
+ border: 1px solid #fff;
+ float: left;
+ background-color: #fff;
+}
+
+div.days, #day {
+ height: 100%;
+ width: 100%;
+}
+
+div.week {
+ position: relative;
+ margin-left: 1%;
+ clear: both;
+ width: 100%;
+ height: 16%;
+ width: 95%;
+}
+
+div.week_first {
+ border-top: 1px solid #999999;
+}
+
+div.day {
+ width: 14%;
+ height: 100%;
+ padding: 0px;
+ border-right: 1px solid #999999;
+ border-bottom: 1px solid #999999;
+ float: left;
+ background-color: #F9F9F9;
+ cursor: default;
+ overflow: hidden;
+ white-space: nowrap;
+}
+
+div.day_selected {
+ background-color: #F0F0FF;
+ border-color: #6666CC;
+ overflow: auto;
+ overflow-x: hidden;
+}
+
+div.today {
+ background-color: #FFFFCF;
+}
+
+div.day_hover {
+ background-color: #AACCFF;
+}
+
+div.empty {
+ border-color: #DFDFDF;
+ background-color:#FFFFFF;
+}
+
+div.day_num {
+ float: right;
+ clear: both;
+ margin: 3px 3px 2px 0px;
+ color: #999999;
+ font-weight: bold;
+}
+
+div.day_selected div.day_num {
+ color: #6666CC;
+}
+
+div.day_hover div.day_num {
+ color: #FCFCFC;
+}
+
+div.day div.event_spacer {
+ width: 95%;
+ height: 14px;
+ overflow: hidden;
+ padding: 2px;
+ font-size: 10px;
+ margin: 2px 0 2px 0;
+ clear: both;
+ z-index: 999;
+}
+
+div.event {
+ width: 100%;
+ float: none;
+ clear: both;
+ height: 14px;
+ overflow: hidden;
+ font-size: 10px;
+ cursor: pointer;
+ padding: 2px 0 2px 0;
+ margin: 2px 0 2px 0;
+ z-index: 999;
+ white-space: nowrap;
+}
+
+div.event div, div.event table, div.event tr, div.event td {
+ margin: 0px;
+ padding: 0px;
+ height: 17px;
+ float: left;
+}
+
+div.event div.event_body {
+ position: relative;
+ overflow: hidden;
+ left: 0px;
+ display: block;
+ float: left;
+ clear: none;
+ width: 88%;
+ height: 13px;
+ padding: 0px;
+ padding: 2px 0 2px 0;
+ background-color: #D0D0F6;
+}
+
+div.event div.event_body span.event_title {
+ font-weight: bold;
+ color: #000099;
+}
+
+div.event_shared div.event_body span.event_title {
+ height: 100%;
+ color: #000000;
+ padding-left: 17px;
+ background-repeat: no-repeat;
+ background-image: url(../img/cal/flag.gif);
+}
+
+div.event_start {
+ float: left;
+ clear: left;
+ width: 6px;
+ height: 17px;
+ background-color: none;
+ background-repeat: no-repeat;
+ background-image: url(../img/cal/l_corner.gif);
+}
+
+div.event_end {
+ float: left;
+ clear: right;
+ width: 6px;
+ height: 17px;
+ background-color: none;
+ background-repeat: no-repeat;
+ background-image: url(../img/cal/r_corner.gif);
+}
+
+div.event_selected div.event_body {
+ color: #FFFFFF;
+ background-color: #6699FF;
+}
+
+div.event_selected div.event_start {
+ background-image: url(../img/cal/l_corner_hi.gif);
+}
+
+div.event_selected div.event_end {
+ background-image: url(../img/cal/r_corner_hi.gif);
+}
+
+
+.new_event {
+ position: relative;
+ clear: left;
+ background-color: #CCCCFF;
+ border: 1px solid #000099;
+ text-align: center;
+ width: 80px;
+ font-family: 'Trebuchet MS', Trebuchet, Tahoma, Verdana, Arial, sans-serif;
+ font-size: 11px;
+}
+
+.event_time {
+ padding-right: 3px;
+}
+
+.event_title { }
+
+.multiday_wrapper {
+ position:relative;
+ top:10%;
+ height:12px;
+ margin-bottom:-16px;
+}
+
+.multiday_event {
+ padding: 2px;
+ background-color: #D84;
+ font-size: 9px;
+ font-weight: normal;
+ color: #FFF;
+ margin-bottom: 2px;
+}
+
+div.block {
+ float: none;
+ clear: both;
+}
+
+#dayHead {
+ font-size: 24px;
+ font-weight: bold;
+ padding: 15px 10px 10px 10px;
+ background-color: #999;
+}
+
+#dayBack a:hover {
+ text-decoration: none;
+ color: #FFFFFF;
+}
+
+div.hour {
+ position: relative;
+ height: 20px;
+ font-size: 16px;
+ padding: 5px 0px 0px 10px;
+ /*border: 1px solid #CCCCCC;
+ border-right: 0px;
+ border-top: 0px;*/
+ border: 1px;
+ margin-left: 1px;
+ z-index: 1;
+ overflow: visible;
+ clear: none;
+}
+
+div.hour div.hour_num {
+ display: inline;
+ float: left;
+ clear: none;
+}
+
+div.hour_event {
+ position: relative;
+ width: 25%;
+ margin: 0 8%;
+ background: #9BD1FA;
+ float: none;
+ clear: none;
+ font-size: 11px;
+ z-index: 10;
+}
+
+div.hour_event b.rtop, div.hour_event b.rbottom {
+ display: block;
+ background: #FFF;
+}
+div.hour_event b.rtop b, div.hour_event b.rbottom b {
+ display:block;
+ height: 1px;
+ overflow: hidden;
+}
+div.hour_event b.rtop b { background: #3366FF; }
+div.hour_event b.rbottom b { background: #9BD1FA; }
+div.hour_event b.r1{ margin: 0 5px }
+div.hour_event b.r2{ margin: 0 3px }
+div.hour_event b.r3{ margin: 0 2px }
+div.hour_event b.rtop b.r4, div.hour_event b.rbottom b.r4 {
+ margin: 0 1px;
+ height: 2px;
+}
+div.hour_event div.event_time {
+ background: #3366FF;
+ padding: -2px 4px 2px 4px;
+ font-weight: bold;
+ color: #FFFFFF;
+}
+div.hour_event div.event_body {
+ padding: 2px 5px 0 5px;
+ font-weight: bold;
+}
+div.hour_event div.event_body p {
+ font-weight: normal;
+}
+div.hour_event div.event_location {
+ float: right;
+ clear: right;
+ background: #3366FF;
+ padding: -2px 4px 2px 4px;
+ font-weight: bold;
+ color: #FFFFFF;
+}
+
+#Shared {
+ padding: 5px;
+ overflow: auto;
+}
+
+ul.user_list {
+ float: right;
+ clear: right;
+ width: 130px;
+ height: 50px;
+ line-height: 20px;
+ list-style-type: none;
+ border: 1px solid #999999;
+ overflow: auto;
+}
+
+pre {
+ line-height: 20px;
+}
+
+.cal_loading { }
+
+.cal_loading div.event {
+ display: none;
+}
+
+#printhead {
+ border: 0px solid red;
+ display: none;
+ }
+
+#SharedUsers {
+ width: 100%;
+ margin-top: 5px;
+ font-size: 12px;
+ font-weight: normal;
+}
\ No newline at end of file
diff --git a/webroot/css/cal_print.css b/webroot/css/cal_print.css
new file mode 100755
index 0000000..46af0d7
--- /dev/null
+++ b/webroot/css/cal_print.css
@@ -0,0 +1,91 @@
+@page { size:landscape; }
+
+html, body {
+ margin: 0px;
+ padding: 0px;
+}
+
+#toolbar, #tools {
+ display: none;
+}
+
+#calLoader {
+ display: none;
+}
+
+#content {
+ padding: 0px;
+ margin: 0px;
+ left: 0px;
+ top: 0px;
+ height: 100%;
+ width: 100%;
+ overflow: visible;
+}
+
+#day {
+ overflow: visible;
+}
+
+#calendar {
+ position: absolute;
+ left: 0px;
+ top: 0px;
+ width: 100%;
+ height: 100%;
+ font-family: 'Trebuchet MS', Trebuchet, Tahoma, Verdana, Arial, sans-serif;
+ font-size: 11px;
+ overflow: visible;
+}
+
+#eventForm {
+ display: none;
+}
+
+div.event {
+ border-top: 2px solid #000099;
+ border-bottom: 2px solid #000099;
+ padding: 0 0 3px 0;
+}
+
+.event_body {
+ margin: 0px;
+ padding: 0px;
+}
+
+div.hour_event div.event_time {
+ background-color: #FFFFFF;
+ color: #000066;
+ border-bottom: 2px solid #000066;
+}
+
+div.hour_event {
+ background-color: #FFFFFF;
+ border: 2px solid #000066;
+}
+
+div.event_start {
+ margin: 0px;
+ border-left: 1px solid #000099;
+}
+
+div.event_end {
+ margin: 0px;
+ border-right: 1px solid #000099;
+}
+
+.cal_nav {
+ display: none;
+}
+
+pre {
+ display: none;
+}
+
+#printhead {
+ display: block;
+}
+
+#cal_header_dayHead {
+ display:none;
+}
\ No newline at end of file
diff --git a/webroot/css/calendar.css b/webroot/css/calendar.css
new file mode 100755
index 0000000..fb8ed0a
--- /dev/null
+++ b/webroot/css/calendar.css
@@ -0,0 +1,97 @@
+.cpYearNavigation,
+.cpMonthNavigation {
+ background-color:#FFFFCC;
+ text-align:center;
+ vertical-align:center;
+ text-decoration:none;
+ color:#FFFFFF;
+ font-weight:bold;
+}
+
+.cpDayColumnHeader,
+.cpYearNavigation,
+.cpMonthNavigation,
+.cpCurrentMonthDate,
+.cpCurrentMonthDateDisabled,
+.cpOtherMonthDate,
+.cpOtherMonthDateDisabled,
+.cpCurrentDate,
+.cpCurrentDateDisabled,
+.cpTodayText,
+.cpTodayTextDisabled,
+.cpText {
+ font-family:arial;
+ font-size:8pt;
+}
+
+TD.cpDayColumnHeader {
+ text-align:right;
+ border:solid thin #0086CE;
+ border-width:0 0 1 0;
+}
+
+.cpCurrentMonthDate,
+.cpOtherMonthDate,
+.cpCurrentDate {
+ text-align:right;
+ text-decoration:none;
+}
+
+.cpCurrentMonthDateDisabled,
+.cpOtherMonthDateDisabled,
+.cpCurrentDateDisabled {
+ color:#D0D0D0;
+ text-align:right;
+ text-decoration:line-through;
+}
+
+.cpCurrentMonthDate {
+ color:#FFFFCC;
+ font-weight:bold;
+}
+
+.cpCurrentDate {
+ color: #FFFFFF;
+ font-weight:bold;
+}
+
+.cpOtherMonthDate {
+ color:#808080;
+}
+
+TD.cpCurrentDate {
+ color:#FFFFFF;
+ background-color: #FFFFCC;
+ border-width:1;
+ border:solid thin #000000;
+}
+
+TD.cpCurrentDateDisabled {
+ border-width:1;
+ border:solid thin #FFAAAA;
+}
+
+TD.cpTodayText,
+TD.cpTodayTextDisabled {
+ border:solid thin #0086CE;
+ border-width:1 0 0 0;
+}
+
+A.cpTodayText,
+SPAN.cpTodayTextDisabled {
+ height:20px;
+}
+
+A.cpTodayText {
+ color:#0086CE;
+ font-weight:bold;
+}
+
+SPAN.cpTodayTextDisabled {
+ color:#D0D0D0;
+}
+
+.cpBorder {
+ border:solid thin #0086CE;
+}
+
diff --git a/webroot/css/default.css b/webroot/css/default.css
new file mode 100755
index 0000000..1c3edfe
--- /dev/null
+++ b/webroot/css/default.css
@@ -0,0 +1,102 @@
+body {
+ font:small "Trebuchet MS",Verdana,Arial,Sans-serif;
+ background: #E5EDEC url(../img/bg_fade.gif) bottom left fixed repeat-x;
+}
+
+ul{}
+li{}
+
+h1 {
+ color: #71300F;
+ font-size: 30px;
+}
+
+h1 em {
+ font-style: normal;
+ font-weight: normal;
+ font-variant: normal;
+}
+
+h2 {
+ color: #777777;
+}
+
+h2 img {
+ vertical-align: -30%;
+}
+
+h2 em {
+ color: #DBA941;
+ font-style: normal;
+ font-weight: normal;
+ font-variant: normal;
+}
+
+hr {
+ border-top: 1px dotted #FF96F3;
+ height: 0px;
+}
+
+ul {
+ list-style-image: url(../img/red_box.gif);
+}
+
+#main {
+ display: block;
+ margin: 10px;
+ background-color: #FFFFFF;
+ border: 1px solid #516067;
+ padding: 5px;
+}
+
+#header {
+ position: relative;
+ top: -15px;
+ left: -15px;
+ width: 635px;
+ height: 131px;
+ background: #fff url(../img/bg_header.gif) repeat-x;
+ margin-bottom: -20px;
+}
+
+#headerLogo {
+ position: absolute;
+ top: 0px;
+ left: -30px;
+}
+
+#headerNav {
+ position: absolute;
+ right: 15px;
+ top: 63px;
+}
+
+#headerNav a {
+ padding: 10px;
+}
+
+a {
+ text-decoration: none;
+ padding: 1px 2px 1px 2px;
+}
+a:hover { text-decoration: none; }
+a:visited { color: #0000AA; }
+a:visited:hover {
+ color: #FFFFFF;
+ background-color: #0000AA;
+}
+
+#footer {
+ position: relative;
+ bottom: -15px;
+ left: -15px;
+ width: 615px;
+ background-color: #71300F;
+ color: #fff;
+ font-size: 75%;
+ padding: 10px;
+}
+
+.navActive {
+ background-color: #D2D7D8;
+}
diff --git a/webroot/css/forms.css b/webroot/css/forms.css
new file mode 100755
index 0000000..0a098d0
--- /dev/null
+++ b/webroot/css/forms.css
@@ -0,0 +1,338 @@
+/* form.css */
+
+* {
+ margin: 0px;
+ padding: 0px;
+}
+
+form {
+ margin: 0px;
+ padding: 0px;
+ font-size: 100%;
+ min-width: 560px;
+ max-width: 620px;
+ width: 590px;
+}
+
+form fieldset {
+ font-size: 100%;
+ border-color: #000000;
+ border-width: 1px 0px 0px 0px;
+ border-style: solid none none none;
+ padding: 10px;
+ margin: 0px 0px 0px 0px;
+}
+
+form fieldset legend {
+ font-size: 150%;
+ font-weight: normal;
+ color: #000000;
+ margin: 0px 0px 0px 0px;
+ padding: 0px 5px;
+}
+
+label {
+ font-size: 100%;
+}
+
+label u {
+ font-style: normal;
+ text-decoration: underline;
+}
+
+input, select, textarea {
+ font-family: Tahoma, Arial, sans-serif;
+ font-size: 100%;
+ color: #000000;
+}
+
+textarea {
+ overflow: auto;
+}
+
+form div {
+ clear: left;
+ display: block;
+ width: 354px;
+ height: expression('1%');
+ margin: 5px 0px 0px 0px;
+ padding: 1px 3px;
+}
+
+form fieldset div.notes {
+ float: right;
+ width: 158px;
+ height: auto;
+ margin: 0px 0px 10px 10px;
+ padding: 5px;
+ border: 1px solid #666666;
+ background-color: #ffffe1;
+ color: #666666;
+ font-size: 88%;
+}
+
+form fieldset div.notes h4 {
+ background-image: url(/images/icon_info.gif);
+ background-repeat: no-repeat;
+ background-position: top left;
+ padding: 3px 0px 3px 27px;
+ border-width: 0px 0px 1px 0px;
+ border-style: solid;
+ border-color: #666666;
+ color: #666666;
+ font-size: 110%;
+}
+
+form fieldset div.notes p {
+ margin: 0em 0em 1.2em 0em;
+ color: #666666;
+}
+
+form fieldset div.notes p.last {
+ margin: 0em;
+}
+
+form div fieldset {
+ clear: none;
+ border-width: 1px;
+ border-style: solid;
+ border-color: #666666;
+ margin: 0px 0px 0px 142px;
+ padding: 0px 5px 5px 5px;
+ width: 197px;
+}
+
+form div fieldset legend {
+ font-size: 100%;
+ padding: 0px 3px 0px 9px;
+}
+
+form div.required fieldset legend {
+ font-weight: bold;
+}
+
+form div label {
+ display: block;
+ float: left;
+ width: 130px;
+ padding: 3px 5px;
+ margin: 0px 0px 5px 0px;
+ text-align: right;
+}
+
+form div.optional label, label.optional {
+ font-weight: normal;
+}
+
+form div.required label, label.required {
+ font-weight: bold;
+}
+
+form div label.labelCheckbox, form div label.labelRadio {
+ float: none;
+ display: block;
+ width: 200px;
+ height: expression('1%');
+ padding: 0px;
+ margin: 0px 0px 5px 142px;
+ text-align: left;
+}
+
+form div fieldset label.labelCheckbox, form div fieldset label.labelRadio {
+ margin: 0px 0px 5px 0px;
+ width: 170px;
+}
+
+p.error {
+ background-color: #ff0000;
+ background-image: url(/images/icon_error.gif);
+ background-repeat: no-repeat;
+ background-position: 3px 3px;
+ color: #ffffff;
+ padding: 3px 3px 5px 27px;
+ border: 1px solid #000000;
+ margin: auto 100px;
+}
+
+form div.error {
+ background-color: #ffffe1;
+ background-image: url(/images/required_bg.gif);
+ background-repeat: no-repeat;
+ background-position: top left;
+ color: #666666;
+ border: 1px solid #ff0000;
+}
+
+form div.error p.error {
+ background-image: url(/images/icon_error.gif);
+ background-position: top left;
+ background-color: transparent;
+ border-style: none;
+ font-size: 88%;
+ font-weight: bold;
+ margin: 0px 0px 0px 118px;
+ width: 200px;
+ color: #ff0000;
+}
+
+form div input, form div select, form div textarea {
+ width: 200px;
+ padding: 1px 3px;
+ margin: 0px 0px 0px 0px;
+}
+
+form div input.inputFile {
+ width: 211px;
+}
+
+form div select.selectOne, form div select.selectMultiple {
+ width: 211px;
+ padding: 1px 3px;
+}
+
+form div input.inputCheckbox, form div input.inputRadio, input.inputCheckbox, input.inputRadio {
+ display: inline;
+ height: 14px;
+ width: 14px;
+ background-color: transparent;
+ border-width: 0px;
+ padding: 0px;
+ margin: 0px 0px 0px 0px;
+}
+
+form div.submit {
+ width: 214px;
+ padding: 0px 0px 0px 140px;
+ clear:both;
+ display:block;
+}
+
+div.submit input {
+ align:center;
+ text-align:center;
+ font-weight: bold;
+ color: #fff;
+ background-color:#3297FC;
+ display:block;
+ float:left;
+ text-decoration: none;
+ border: 1px;
+ width: 215px;
+ padding:6px;
+ margin: 5px;
+
+}
+form div.submit div {
+ display: inline;
+ float: left;
+ text-align: left;
+ width: auto;
+ padding: 0px;
+ margin: 0px;
+}
+
+form div input.inputSubmit, form div input.inputButton, input.inputSubmit, input.inputButton {
+ background-color: #cccccc;
+ color: #000000;
+ width: auto;
+ padding: 0px 6px;
+ margin: 0px;
+}
+
+form div.submit div input.inputSubmit, form div.submit div input.inputButton {
+ float: right;
+ margin: 0px 0px 0px 5px;
+}
+
+form div small {
+ display: block;
+ margin: 0px 0px 5px 142px;
+ padding: 1px 3px;
+ font-size: 88%;
+ height: expression('1%');
+}
+
+/* form.import.css */
+
+form fieldset legend {
+ line-height: 150%;
+}
+
+form input, form select, form textarea {
+ background-color: #ffffff;
+}
+
+form textarea.expanding {
+ overflow: auto;
+ overflow-x: auto;
+ overflow-y: visible;
+}
+
+div.optional label:before {
+ content: '';
+}
+
+div.required label:before {
+ content: '';
+}
+
+form div label.labelCheckbox, form div label.labelRadio, label.labelCheckbox, label.labelRadio {
+ display: block;
+ width: 190px;
+ height: expression('1%');
+ padding: 4px 0px 0px 18px;
+ text-indent: -18px;
+ line-height: 120%;
+}
+
+form div label.labelCheckbox input.inputCheckbox, form div label.labelRadio input.inputRadio, label.labelCheckbox input.inputCheckbox, label.labelRadio input.inputRadio {
+ margin: 0px 0px 0px 0px;
+}
+
+form div fieldset input.inputText, form div fieldset input.inputPassword, form div fieldset input.inputFile, form div fieldset textarea.inputTextarea {
+ width: 160px;
+ margin: 0px 0px 0px 18px;
+ margin: expression('0px 0px 0px -124px');
+}
+
+form div label.compact {
+ display: inline;
+ width: auto;
+ padding: 4px 10px 0px 0px;
+ text-indent: 0px;
+ margin: 0px 0px 0px 0px;
+}
+
+form div.wide label {
+ float: none;
+ display: block;
+}
+
+form div label.wide {
+ width: 348px;
+}
+
+form div.wide input.inputText, form div.wide input.inputPassword, form div.wide input.inputFile, form div.wide select, form div.wide textarea {
+ width: 344px;
+ margin: 0px;
+}
+
+form div.notes p, form div small {
+ line-height: 125%;
+}
+
+form div.wide small {
+ margin: 0px 0px 5px 0px;
+}
+
+div.date select {
+ width:auto;
+}
+
+select.autoWidth {
+ width:auto;
+}
+
+option {
+padding-left:1em;
+}
\ No newline at end of file
diff --git a/webroot/css/level2.css b/webroot/css/level2.css
new file mode 100755
index 0000000..8dd66fc
--- /dev/null
+++ b/webroot/css/level2.css
@@ -0,0 +1,31 @@
+#level2 {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 10px;
+ color: #AECC38;
+ padding-left: 15px;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ white-space: nowrap;
+ background-color: #003399;
+}
+
+#level2 a:link {
+ color: #FFFFFF;
+ font-size: 10px;
+}
+
+#level2 a:visited {
+ color: #FFFFFF;
+ font-size: 10px;
+}
+
+#level2 a:hover {
+ color: #AECC38;
+ text-decoration: none;
+ font-size: 10px;
+}
+
+#level2 a:active {
+ color: #FFFFFF;
+ font-size: 10px;
+}
diff --git a/webroot/css/level3.css b/webroot/css/level3.css
new file mode 100755
index 0000000..1f1ea62
--- /dev/null
+++ b/webroot/css/level3.css
@@ -0,0 +1,30 @@
+#level3 {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 10px;
+ color: #003399;
+ padding-left: 50px;
+ padding-top: 3px;
+ padding-bottom: 3px;
+ background-color: #AEC8DF;
+}
+
+#level3 a:link {
+ color: #003399;
+ font-size: 10px;
+}
+
+#level3 a:visited {
+ color: #003399;
+ font-size: 10px;
+}
+
+#level3 a:hover {
+ color: #333333;
+ text-decoration: none;
+ font-size: 10px;
+}
+
+#level3 a:active {
+ color: #003399;
+ font-size: 10px;
+}
diff --git a/webroot/css/office.css b/webroot/css/office.css
new file mode 100755
index 0000000..81724ed
--- /dev/null
+++ b/webroot/css/office.css
@@ -0,0 +1,523 @@
+body {
+ font-family: Verdana, Arial, sans-serif;
+ font-size: 11px;
+}
+
+body, form {
+ margin: 0px;
+ padding: 0px;
+}
+
+form {
+ display: inline;
+}
+
+table {
+ border: 0px;
+ width: 100%;
+}
+
+td {
+ border: 0px;
+ padding: 2px;
+ margin: 2px;
+ vertical-align: top;
+}
+
+img {
+ text-align: left;
+ vertical-align: middle;
+}
+
+textarea {
+ height: 100px;
+}
+
+.title {
+ clear: both;
+}
+
+h3.title {
+ font-size: 14px;
+}
+
+h4.title {
+ font-size: 12px;
+}
+
+td.left {
+ width: 40%;
+ height: 100%;
+}
+
+td.left_ext {
+ width: 60%;
+ height: 100%;
+}
+
+td.right {
+ width: 200px;
+}
+
+img {
+ border: 0px;
+}
+
+a, a:visited {
+ color: #0000FF;
+}
+
+.sidebar_left {
+ display: block;
+ float: left;
+ clear: both;
+ width: 95%;
+ padding: 5px;
+ margin: 4px 7px 4px 2px;
+ border: 1px solid #CDCDCD;
+}
+
+.sidebar_blocky_item {
+ display: block;
+ float: left;
+ clear: both;
+ width: 95%;
+}
+
+.sidebar_right {
+ display: block;
+ float: right;
+ clear: both;
+ width: 95%;
+ padding: 5px;
+ margin: 4px 7px 4px 2px;
+ border: 1px solid #CDCDCD;
+}
+
+h3, h4, h5 {
+ border-bottom: 1px dotted #666666;
+ padding-bottom: 2px;
+ margin-bottom: 3px;
+}
+
+.widget {
+ display: block;
+ background-color: #F0F0F0;
+ border: 1px solid #AAAAAA;
+ margin: 4px 2px 10px 2px;
+ padding: 3px 3px 5px 3px;
+ line-height: 15px;
+ overflow: hidden;
+}
+
+a:hover {
+ background-color: #0000AA;
+ color: #FFFFFF;
+}
+
+a.title:hover {
+ text-decoration: none;
+ color: #FFFFFF;
+ background-color: #0000AA;
+ border-bottom-color: #FFFFFF;
+}
+
+
+.critical {
+ border: 1px solid #AA6633;
+ background-color: #FF9999;
+}
+.critical a, .critical a:visited, a.critical, a.critical:visited { color: #A20B10; }
+.critical a.title:hover { border-bottom-color: #FFFFFF; }
+.critical a:hover, a.critical:hover {
+ color: #FFFFFF;
+ background-color: #A20B10;
+}
+
+.okay {
+ border: 1px solid #66AA66;
+ background-color: #AAFFAA;
+}
+.okay a, .okay a:visited, a.okay, a.okay:visited { color: #0BA210; }
+.okay a.title:hover { border-bottom-color: #FFFFFF; }
+.okay a:hover, a.okay:hover {
+ color: #FFFFFF;
+ background-color: #0BA210;
+}
+
+.important {
+ border: 1px solid #AAAA66;
+ background-color: #FFFFAA;
+ margin: 4px 2px 10px 2px;
+ padding: 3px;
+}
+.important a, .important a:visited, a.important, a.important:visited { color: #808C10; }
+.important a.title:hover { border-bottom-color: #FFFFFF; }
+.important a:hover, a.important:hover {
+ color: #FFFFFF;
+ background-color: #808C10;
+}
+
+.widget .title {
+ font-weight: bold;
+ border-bottom: 1px dotted #000000;
+ padding-bottom: 2px;
+ margin-bottom: 3px;
+ clear: left;
+ overflow: hidden;
+}
+
+.dark {
+ background-color: #666666;
+ color: #FFFFFF;
+ padding: 2px 0px 1px 3px;
+}
+
+.light {
+ background-color: #FFFFFF;
+ color: #666666;
+ padding: 2px 0px 1px 3px;
+}
+
+.calendar_day {
+ height: 80px;
+ width: 64px;
+ float: left;
+ clear: none;
+ width: 18.5%;
+ margin: 1px;
+ padding: 1px 2px 1px 1px;
+ overflow: hidden;
+}
+
+.calendar_date {
+ display: block;
+ float: right;
+ clear: both;
+}
+
+.calendar_item {
+ margin: 0px;
+ padding: 1px;
+ height: 18px;
+ overflow: hidden;
+ display: block;
+ clear: both;
+ font-size: 9px;
+ white-space: nowrap;
+}
+
+.item {
+ background-color: #FFFFFF;
+ color: #000000;
+ padding: 2px 4px 2px 4px;
+ margin: 2px 1px 1px 1px;
+ border: 1px solid #999999;
+}
+
+.item .title {
+ font-weight: bold;
+ font-size: 10px;
+}
+
+.item .body {
+ margin: 10px 0px 5px 30px;
+}
+
+.float_item {
+ font-weight: normal;
+ float: right;
+ clear: right;
+ vertical-align: middle;
+}
+
+.powered_by {
+ float: right;
+ padding: 4px;
+ margin-top: 7px;
+ font-size: 10px;
+}
+
+.powered_by a {
+ color: #999999;
+}
+
+.logo {
+ display: block;
+ float: right;
+ clear: both;
+ background-image: url(../img/process141_logo_sm.jpg);
+ background-repeat: no-repeat;
+ width: 193px;
+ height: 55px;
+ padding: 0px;
+ margin: 0px;
+ z-index: 1;
+}
+
+.clear {
+ display: block;
+ height: 1px;
+ clear: both;
+}
+
+.pad {
+ margin: 3px;
+}
+
+.note {
+ color: #777777;
+ font-size: 10px;
+}
+
+.page_tabs {
+ display: block;
+ padding: 4px;
+ margin: 34px 0px 0px 0px;
+ border-bottom: 1px solid #CCCCCC;
+ z-index: 3;
+}
+
+a.tab {
+ display: block;
+ padding: 5px;
+ margin: 0px 0px 3px 0px;
+ padding: 6px 10px 4px 10px;
+ border-top: 1px dotted #999999;
+ border-left: 1px dotted #999999;
+ border-right: 1px dotted #999999;
+ background-color: #6699CC;
+ color: #FFFFFF;
+ cursor: pointer;
+}
+
+a.tab:visited {
+ background-color: #6699CC;
+ color: #FFFFFF;
+ cursor: pointer;
+}
+
+a.tab:hover {
+ color: #3366CC;
+ background-color: #FAFAFA;
+}
+
+#tabAccount {
+ margin-left: 26px;
+}
+
+a.button {
+ float: right;
+ clear: none;
+}
+
+a.action:hover, a.button:hover {
+ background-color: transparent;
+ color: transparent;
+}
+
+.float_item input {
+ margin-right: 5px;
+}
+
+input.button, input[type=submit] {
+ width: 85px;
+}
+
+select {
+ width: 200px;
+}
+
+.sidebar_left .tab, .sidebar_right .tab, .sidebar .tab {
+ margin-bottom: 3px;
+}
+
+.page_tabs .tab {
+ display: inline;
+}
+
+.selected {
+ /*background-color: #C0A066;*/
+ font-weight: bold;
+}
+
+div.hypen, h1.hyphen, h2.hyphen, h3.hyphen, h4.hyphen {
+ display: inline;
+}
+
+div.input {
+ margin: 5px 0px 5px 5px;
+ width: 95%;
+ clear: both;
+}
+
+div.input input, div.input select, div.input textarea {
+ float: right;
+}
+
+input.title {
+ width: 93%;
+ font-size: 12px;
+ font-family: Tahoma, Verdana;
+ border-bottom: none;
+}
+
+input.numeric {
+ width: 100px;
+ text-align: right;
+}
+
+.slider_track {
+ width: 140px;
+ height: 18px;
+ background-image: url('../img/slider_track.gif');
+ background-repeat: no-repeat;
+ background-position: center left;
+ margin: 4px 0 0 10px;
+}
+
+div.input .slider_track {
+ float: right;
+ margin-bottom: 7px;
+}
+
+div.controls {
+ margin: 2px;
+}
+
+div.controls .float_item {
+ margin-top: 3px;
+}
+
+div.controls input, div.controls select, div.controls textarea {
+ margin: 2px;
+}
+
+input.number, select.number {
+ width: 50px;
+ text-align: right;
+}
+
+.slider_track .slider_handle {
+ position: relative;
+ top: -7px;
+ width: 18px;
+ height: 18px;
+}
+
+.slider_status {
+ float: right;
+ width: 30px;
+ padding-top: 5px;
+ text-align: right;
+}
+
+#dlgPublish {
+ display: none;
+ width: 300px;
+}
+
+#login {
+ margin: 25px 25px 25px 25px;
+}
+
+#login_button {
+ width: 75px;
+}
+
+#taskInfo, #frmEditPost {
+}
+
+#taskEditView {
+ display: block;
+ padding: 0px;
+ width: 100%;
+}
+
+div.auto_complete {
+ position: absolute;
+ width: 250px;
+ background-color: white;
+ border: 1px solid #888;
+ margin: 0px;
+ padding: 0px;
+}
+
+li.selected { background-color: #ffb; }
+
+#frmEditPost {
+ width: 400px;
+}
+
+#invoices {
+ border: 0px;
+ padding: 0px;
+ margin: 0px;
+}
+
+#invoices td, #invoices th {
+ padding: 2px;
+ border: 0px;
+ border-bottom: 1px dotted #CCCCCC;
+}
+
+#invoices th {
+ color: #333333;
+ background-color: #CFCFCF;
+}
+
+#invoices td {
+ padding: 3px 5px 3px 5px;
+}
+
+#invoices .date {
+ text-align: center;
+}
+
+#invoices .number {
+ text-align: right;
+}
+
+#invoices tr.paid {
+ background-color: #DDFFDD;
+}
+
+#invoices tr.late {
+ background-color: #FFDDDD;
+}
+
+div.invoice_item_detail {
+ margin-left: 35px;
+ border-left: 1px dotted #CCCCCC;
+}
+
+div.invoice_items {
+ height: 150px;
+ width: 390px;
+ border: 1px solid #999999;
+ border-right-color: #CCCCCC;
+ border-bottom-color: #CCCCCC;
+ background-color: #FFFFFF;
+ overflow: auto;
+}
+
+div.invoice_items_hover {
+ background-color: #DDDDFF;
+}
+
+a.sale_group, a.sale_item {
+ padding: 2px;
+ margin: 2px;
+}
+
+a.sale_group {
+ background-image: url(../img/icon_sort_za.gif);
+ background-repeat: no-repeat;
+ background-position: 99% 45%;
+}
+
+div.sale_group_content {
+ margin-left: 20px;
+ border: 1px solid #CFCFCF;
+ padding: 2px;
+}
diff --git a/webroot/css/print.css b/webroot/css/print.css
new file mode 100755
index 0000000..071da54
--- /dev/null
+++ b/webroot/css/print.css
@@ -0,0 +1,23 @@
+/* Print CSS */
+
+/* tag selectors */
+body {
+ background-color: #FFFFFF;
+ text-align:left;
+ font-family: 'Times New Roman', serif;
+ font-size: 12px;
+ line-height:150%;
+ color:#000000;
+ padding:10px;
+ margin:0px;
+}
+
+/* class selectors */
+.someClass {
+ margin:0px;
+}
+
+/* id selectors */
+#someID {
+ margin:0px;
+}
\ No newline at end of file
diff --git a/webroot/css/reports.css b/webroot/css/reports.css
new file mode 100755
index 0000000..139b061
--- /dev/null
+++ b/webroot/css/reports.css
@@ -0,0 +1,154 @@
+.body {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 9px;
+ line-height: 15px;
+ color: #000000;
+ padding: 10px 20px 20px;
+}
+.footer {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 9px;
+ text-transform: uppercase;
+ color: #666666;
+ text-decoration: none;
+}
+a:link {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 9px;
+ color: #003399;
+ text-decoration: none;
+}
+a:visited {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 9px;
+ color: #003399;
+ text-decoration: none;
+}
+a:hover {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 9px;
+ color: #AECC38;
+ text-decoration: none;
+}
+a:active {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 9px;
+ color: #003399;
+ text-decoration: none;
+}
+.headers {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 9px;
+ font-style: normal;
+ line-height: normal;
+ font-weight: 600;
+ color: #003399;
+ text-decoration: none;
+}
+.bullets {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 9px;
+ line-height: 15px;
+ color: #000000;
+ list-style-position: outside;
+ list-style-image: url("/images/buttons/arrow.gif");
+}
+.medtext {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 9px;
+ color: #003399;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ margin-bottom: 5px;
+ margin-top: 5px;
+}
+.tab {
+ background-image: url("/images/centertab.gif");
+ background-repeat: repeat-x;
+ background-position: left;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 9px;
+ text-align: center;
+}
+.banner {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 16px;
+ font-weight: bold;
+ color: #666666;
+ padding: 5px 0px 5px 5px;
+ background-color: #EEEEEE;
+}
+
+#main {
+ background-color: #FFFFFF;
+ margin: 10px 0px 20px 10px;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 9px;
+ color: #000000;
+}
+
+#main td {
+ color: #000000;
+ font-size: 9px;
+ line-height: 120%;
+ font-family: Verdana, Arial, "Trebuchet MS", Trebuchet, san-serif
+}
+
+#main a {
+ color: #022C83;
+ font-size: 9px;
+ line-height: 120%;
+ font-family: Verdana, Arial, "Trebuchet MS", Trebuchet, san-serif;
+ text-decoration: none;
+}
+
+#main a:hover {
+ color: #999999;
+ font-size: 9px;
+ line-height: 120%;
+ font-family: Verdana, Arial, "Trebuchet MS", Trebuchet, san-serif;
+ text-decoration: underline;
+}
+
+#main select {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 9px;
+ font-weight: bold;
+ color: #022C83;
+}
+
+#main input {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 9px;
+ font-weight: bold;
+ color: #022C83;
+ border: 1px solid black;
+ padding: 2px 1px 0px 1px;
+ line-height: 110%;
+}
+
+#main textarea {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 9px;
+ font-weight: bold;
+ color: #022C83;
+ border: 1px solid black;
+ padding: 2px 1px 0px 1px;
+ line-height: 110%;
+}
+
+#main button {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 9px;
+ font-weight: bold;
+ color: #022C83;
+}
+
+#main .header {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 14px;
+ line-height: 20px;
+ font-weight: bold;
+ color: #000000;
+ padding-bottom: 10px;
+}
diff --git a/webroot/css/scaffold.css b/webroot/css/scaffold.css
new file mode 100755
index 0000000..9246336
--- /dev/null
+++ b/webroot/css/scaffold.css
@@ -0,0 +1,168 @@
+/* CSS Document */
+* {
+ margin: 0;
+ padding: 0;
+}
+body {
+ font: 76% Verdana, Arial, Helvetica, sans-serif;
+ color: #333;
+}
+h1 {
+font-size:2.1em;
+color: #69c;
+}
+h2 {
+margin-top:5px;
+display:block;
+float:left;
+font-size:1.7em;
+color: #383;
+clear: both;
+}
+
+h3 {
+font-size:1.4em;
+color: #553;
+}
+h4 {
+font-size:1.15em;
+color: #338;
+}
+a {
+white-space:nowrap;
+text-decoration:underline;
+}
+a:hover {
+background-color:#EEE;
+}
+code, pre {
+font-family:monospace;
+font-size:1.15em;
+color:#44A;
+}
+code {
+color:#227;
+white-space:nowrap;
+margin:0 .2em;
+}
+pre {
+margin-left:1em;
+}
+acronym {
+border-bottom:1px dotted #666;
+}
+ul {
+margin-top:1em;
+list-style:none;
+}
+li {
+margin-left:2em;
+}
+#container {
+margin: 2em auto;
+color: #333;
+width:80%;
+}
+.notice {
+padding: 1em;
+background: #ffd;
+border: solid 2px #eeb;
+display: block;
+font-family: Verdana;
+}
+
+.tip {
+background: #efe;
+padding: 1em;
+border: solid 2px #cdc;
+}
+.error {
+background: #fee;
+padding: 1em;
+border: solid 2px #dcc;
+}
+ul.actions {
+ list-style: none;
+ text-align:right;
+ margin:2em 0;
+ float:left;
+}
+
+ul.actions li {
+ border: 1px solid #333;
+ width:10em;
+ float:left;
+ margin-left:1em;
+}
+
+ul.actions li a, ul.actions li input {
+ text-align:center;
+ font-weight: bold;
+ color: #fff;
+ background-color:#3297FC;
+ display:block;
+ clear: both;
+ text-decoration: none;
+ border:1px solid #3297FC;
+}
+
+td.listactions {
+ width:17em;
+}
+
+td.listactions a {
+ text-align:center;
+ font-weight: bold;
+ color: #fff;
+ background-color:#3297FC;
+ display:block;
+ float:left;
+ text-decoration: none;
+ margin-bottom:3px;
+ margin-right: 3px;
+ border: 1px;
+ width:5em;
+}
+
+table {
+ width: 100%;
+ border: 1px solid #686E74;
+ margin: 1em 0 2em 0;
+ background-color: #fff;
+}
+th {
+ background-color: #ccc;
+ text-align: left;
+ border-top: 1px solid #fff;
+ border-right: 1px solid #666;
+ border-bottom: 1px solid #666;
+ padding:3px;
+}
+table tr td {
+ padding:2px 0;
+ border-right: 1px solid #ccc;
+ vertical-align:top;
+}
+table tr.altRow td {
+ background: #EBF4FD;
+}
+
+div.related {
+ display:block;
+ float:left;
+ clear:both;
+}
+
+dl {
+line-height:2em;
+margin:1em;
+}
+dt {
+font-weight: bold;
+vertical-align:top;
+}
+dd {
+margin-left:10em;
+margin-top:-2em;
+vertical-align:top;
+}
\ No newline at end of file
diff --git a/webroot/css/seneca-LMstyles.css b/webroot/css/seneca-LMstyles.css
new file mode 100755
index 0000000..cefe8bb
--- /dev/null
+++ b/webroot/css/seneca-LMstyles.css
@@ -0,0 +1,329 @@
+.body {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ line-height: 15px;
+ color: #000000;
+ padding: 10px 20px 20px;
+}
+.footer {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 9px;
+ text-transform: uppercase;
+ color: #666666;
+ text-decoration: none;
+}
+a:link {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ color: #003399;
+ text-decoration: none;
+}
+a:visited {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ color: #003399;
+ text-decoration: none;
+}
+a:hover {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ color: #AECC38;
+ text-decoration: none;
+}
+a:active {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ color: #003399;
+ text-decoration: none;
+}
+.headers {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ font-style: normal;
+ line-height: normal;
+ font-weight: 600;
+ color: #003399;
+ text-decoration: none;
+}
+.bullets {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ line-height: 15px;
+ color: #000000;
+ list-style-position: outside;
+ list-style-image: url("/images/buttons/arrow.gif");
+}
+.medtext {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ color: #003399;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ margin-bottom: 5px;
+ margin-top: 5px;
+}
+.tab {
+ background-image: url("/images/centertab.gif");
+ background-repeat: repeat-x;
+ background-position: left;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ text-align: center;
+}
+.banner {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 16px;
+ font-weight: bold;
+ color: #666666;
+ padding: 5px 0px 5px 5px;
+ background-color: #EEEEEE;
+}
+
+
+#index_level_1 a {
+ font-size: 13px;
+ line-height: 110%;
+}
+
+#index_level_1 a:hover {
+ font-size: 13px;
+ line-height: 110%;
+}
+
+#index_level_2 a {
+ font-size: 11px;
+ line-height: 110%;
+}
+
+#index_level_2 a:hover {
+ font-size: 11px;
+ line-height: 110%;
+}
+
+#sidebar {
+ vertical-align: top;
+ padding-top: 20px;
+}
+
+#sidebar td.element {
+ font-size: 12px;
+ padding: 5px 0px 5px 10px;
+}
+
+#sidebar td.element_active {
+ font-size: 12px;
+ padding: 5px 0px 5px 10px;
+ float: left;
+ background-color: #C7E5FF;
+}
+
+#sidebar a.element:link {
+ padding: 3px 20px 2px 0px;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ text-decoration: underline;
+}
+
+#sidebar a.element:hover {
+ padding: 3px 20px 2px 0px;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ text-decoration: underline;
+}
+
+#sidebar a.element:visited {
+ padding: 3px 20px 2px 0px;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ text-decoration: underline;
+}
+
+#sidebar a.element:visited {
+ padding: 3px 20px 2px 0px;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ text-decoration: underline;
+}
+
+#main_block {
+ background: url("/images/bluegradient.gif") repeat-y left;
+}
+
+#main {
+ background-color: #FFFFFF;
+ margin: 10px 0px 20px 10px;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ color: #000000;
+}
+
+#main td {
+ color: #000000;
+ font-size: 11px;
+ line-height: 120%;
+ font-family: Verdana, Arial, "Trebuchet MS", Trebuchet, san-serif
+}
+
+#main td.sage_report {
+ font-size: 11px;
+}
+
+#main td.criteria {
+ background-color: #FFFFCC;
+ padding: 7px;
+}
+
+#main td.search {
+ background-color: #E3F5FF;
+ padding: 7px;
+}
+
+#main td.category_header {
+ background: #99CCFF url("/images/gradient_small.gif");
+ color: #000B9A;
+ font: 10px verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif;
+ font-weight: 600;
+}
+
+#main td.event {
+ color: #002E8B;
+ font: 9px verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif;
+ border-bottom: 1px solid black;
+}
+
+#main a {
+ color: #022C83;
+ font-size: 11px;
+ line-height: 120%;
+ font-family: Verdana, Arial, "Trebuchet MS", Trebuchet, san-serif;
+ text-decoration: none;
+}
+
+#main a:hover {
+ color: #999999;
+ font-size: 11px;
+ line-height: 120%;
+ font-family: Verdana, Arial, "Trebuchet MS", Trebuchet, san-serif;
+ text-decoration: underline;
+}
+
+#main select {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ font-weight: bold;
+ color: #022C83;
+}
+
+#main input {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ font-weight: bold;
+ color: #022C83;
+ border: 1px solid black;
+ padding: 2px 1px 0px 1px;
+ line-height: 110%;
+}
+
+#main button {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ font-weight: bold;
+ color: #022C83;
+ border: 1px solid black;
+ padding: 2px 1px 0px 1px;
+ line-height: 110%;
+}
+
+#main textarea {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ font-weight: bold;
+ color: #022C83;
+ border: 1px solid black;
+ padding: 2px 1px 0px 1px;
+ line-height: 110%;
+}
+
+#main button {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ font-weight: bold;
+ color: #022C83;
+}
+
+#main .header {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 14px;
+ line-height: 20px;
+ font-weight: bold;
+ color: #000000;
+ padding-bottom: 10px;
+}
+
+#permissions td.header {
+ border-bottom: 1px solid gray;
+}
+
+#permissions td.default {
+ border-left: 1px solid gray;
+ border-bottom: 1px solid gray;
+}
+
+#permissions td.view {
+ border-left: 1px solid gray;
+ border-right: 1px solid gray;
+ border-bottom: 1px solid gray;
+}
+
+#permissions td.add {
+ border-bottom: 1px solid gray;
+}
+
+#permissions td.edit {
+ border-bottom: 1px solid gray;
+}
+
+#permissions td.delete {
+ border-right: 1px solid gray;
+ border-bottom: 1px solid gray;
+}
+
+#permissions input {
+ border: 0px;
+}
+
+#criteria td {
+ color: #000000;
+ font-size: 10px;
+ line-height: 120%;
+ font-family: Verdana, Arial, "Trebuchet MS", Trebuchet, san-serif
+}
+
+#criteria input {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 10px;
+ font-weight: bold;
+ color: #022C83;
+ border: 1px solid black;
+ padding: 2px 1px 0px 2px;
+ line-height: 110%;
+}
+
+#record_info input {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 10px;
+ font-weight: bold;
+ color: #022C83;
+ border: 1px solid black;
+ padding: 2px 1px 0px 1px;
+ line-height: 110%;
+}
+
+#record_info select {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 9px;
+ font-weight: bold;
+ color: #022C83;
+ padding: 2px 1px 0px 1px;
+ line-height: 110%;
+}
diff --git a/webroot/css/seneca-level2.css b/webroot/css/seneca-level2.css
new file mode 100755
index 0000000..6c69370
--- /dev/null
+++ b/webroot/css/seneca-level2.css
@@ -0,0 +1,31 @@
+#level2 {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 10px;
+ color: #AECC38;
+ padding-left: 15px;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ white-space: nowrap;
+ background-color: #8C00BB;
+}
+
+#level2 a:link {
+ color: #FFFFFF;
+ font-size: 10px;
+}
+
+#level2 a:visited {
+ color: #FFFFFF;
+ font-size: 10px;
+}
+
+#level2 a:hover {
+ color: #AECC38;
+ text-decoration: none;
+ font-size: 10px;
+}
+
+#level2 a:active {
+ color: #FFFFFF;
+ font-size: 10px;
+}
diff --git a/webroot/css/seneca-level3.css b/webroot/css/seneca-level3.css
new file mode 100755
index 0000000..09dca9a
--- /dev/null
+++ b/webroot/css/seneca-level3.css
@@ -0,0 +1,30 @@
+#level3 {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 10px;
+ color: #003399;
+ padding-left: 50px;
+ padding-top: 3px;
+ padding-bottom: 3px;
+ background-color: #B8B1D1;
+}
+
+#level3 a:link {
+ color: #003399;
+ font-size: 10px;
+}
+
+#level3 a:visited {
+ color: #003399;
+ font-size: 10px;
+}
+
+#level3 a:hover {
+ color: #333333;
+ text-decoration: none;
+ font-size: 10px;
+}
+
+#level3 a:active {
+ color: #003399;
+ font-size: 10px;
+}
diff --git a/webroot/css/seneca-tabs.css b/webroot/css/seneca-tabs.css
new file mode 100755
index 0000000..690006b
--- /dev/null
+++ b/webroot/css/seneca-tabs.css
@@ -0,0 +1,59 @@
+#tabs {
+ float: left;
+ width: 100%;
+ line-height: normal;
+}
+
+#tabs ul {
+ margin: 0px;
+ padding: 0px;
+ list-style: none;
+ width: 880px;
+}
+
+#tabs li {
+ float: left;
+ background: url("/images/seneca-lefttab1.gif") no-repeat left top;
+ margin: 0px;
+ padding: 0px 0px 0px 12px;
+}
+
+#tabs li.current {
+ background: url("/images/seneca-lefttab2.gif") no-repeat left top;
+}
+
+#tabs a {
+ float: left;
+ display: block;
+ background: url("/images/seneca-righttab1.gif") no-repeat right top;
+ padding: 3px 12px 2px 0px;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+}
+
+#tabs .current a {
+ background: url("/images/seneca-righttab2.gif") no-repeat right top;
+}
+
+/* Commented Backslash Hack
+ hides rule from IE5-Mac \*/
+#tabs a {
+ float:none;
+}
+/* End IE5-Mac hack */
+
+#tabs a:link { color: #003399; }
+
+#tabs a:visited { color: #003399; }
+
+#tabs a:hover { color: #666666; }
+
+#tabs a:active { color: #003399; }
+
+#tabs .current a:link { color: #FFFFFF; }
+
+#tabs .current a:visited { color: #FFFFFF; }
+
+#tabs .current a:hover { color: #AECC38; }
+
+#tabs .current a:active { color: #FFFFFF; }
diff --git a/webroot/css/seneca_colors.txt b/webroot/css/seneca_colors.txt
new file mode 100755
index 0000000..f68607a
--- /dev/null
+++ b/webroot/css/seneca_colors.txt
@@ -0,0 +1,2 @@
+Dark purple: #330066
+Light purple: #B59BB7
diff --git a/webroot/css/styles.css b/webroot/css/styles.css
new file mode 100755
index 0000000..72415d9
--- /dev/null
+++ b/webroot/css/styles.css
@@ -0,0 +1,72 @@
+.body {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ line-height: 15px;
+ color: #000000;
+ padding: 10px 20px 20px;
+}
+.footer {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 9px;
+ text-transform: uppercase;
+ color: #666666;
+ text-decoration: none;
+}
+.boxes {
+ border: 1px solid #333333;
+ margin-top: 8px;
+ margin-bottom: 15px;
+ margin-left: 6px;
+}
+a:link {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ color: #003399;
+ text-decoration: none;
+}
+a:visited {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ color: #003399;
+ text-decoration: none;
+}
+a:hover {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ color: #AECC38;
+ text-decoration: underline;
+}
+a:active {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ color: #003399;
+ text-decoration: none;
+}
+.headers {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ font-style: normal;
+ line-height: normal;
+ font-weight: 600;
+ color: #003399;
+ text-decoration: none;
+}
+.bullets {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ line-height: 15px;
+ color: #000000;
+ list-style-position: outside;
+ list-style-image: url(images/buttons/arrow.gif);
+}
+.formtext {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ color: #000000;
+}
+.quote {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ font-style: italic;
+ color: #003399;
+}
diff --git a/webroot/css/tabs.css b/webroot/css/tabs.css
new file mode 100755
index 0000000..4db8c11
--- /dev/null
+++ b/webroot/css/tabs.css
@@ -0,0 +1,59 @@
+#tabs {
+ float: left;
+ width: 100%;
+ line-height: normal;
+}
+
+#tabs ul {
+ margin: 0px;
+ padding: 0px;
+ list-style: none;
+ width: 880px;
+}
+
+#tabs li {
+ float: left;
+ background: url("../../images/lefttab1.gif") no-repeat left top;
+ margin: 0px;
+ padding: 0px 0px 0px 12px;
+}
+
+#tabs li.current {
+ background: url("../../images/lefttab2.gif") no-repeat left top;
+}
+
+#tabs a {
+ float: left;
+ display: block;
+ background: url("../../images/righttab1.gif") no-repeat right top;
+ padding: 3px 12px 2px 0px;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+}
+
+#tabs .current a {
+ background: url("../../images/righttab2.gif") no-repeat right top;
+}
+
+/* Commented Backslash Hack
+ hides rule from IE5-Mac \*/
+#tabs a {
+ float:none;
+}
+/* End IE5-Mac hack */
+
+#tabs a:link { color: #003399; }
+
+#tabs a:visited { color: #003399; }
+
+#tabs a:hover { color: #666666; }
+
+#tabs a:active { color: #003399; }
+
+#tabs .current a:link { color: #FFFFFF; }
+
+#tabs .current a:visited { color: #FFFFFF; }
+
+#tabs .current a:hover { color: #AECC38; }
+
+#tabs .current a:active { color: #FFFFFF; }
diff --git a/webroot/favicon.ico b/webroot/favicon.ico
new file mode 100755
index 0000000..8c5c557
Binary files /dev/null and b/webroot/favicon.ico differ
diff --git a/webroot/img/Copy of lefttab2.gif b/webroot/img/Copy of lefttab2.gif
new file mode 100755
index 0000000..dabb685
Binary files /dev/null and b/webroot/img/Copy of lefttab2.gif differ
diff --git a/webroot/img/Copy of righttab2.gif b/webroot/img/Copy of righttab2.gif
new file mode 100755
index 0000000..6a64527
Binary files /dev/null and b/webroot/img/Copy of righttab2.gif differ
diff --git a/webroot/img/addnon.gif b/webroot/img/addnon.gif
new file mode 100755
index 0000000..db6baad
Binary files /dev/null and b/webroot/img/addnon.gif differ
diff --git a/webroot/img/administrator.gif b/webroot/img/administrator.gif
new file mode 100755
index 0000000..2287f34
Binary files /dev/null and b/webroot/img/administrator.gif differ
diff --git a/webroot/img/banner_bg.gif b/webroot/img/banner_bg.gif
new file mode 100755
index 0000000..604ed37
Binary files /dev/null and b/webroot/img/banner_bg.gif differ
diff --git a/webroot/img/bar1.gif b/webroot/img/bar1.gif
new file mode 100755
index 0000000..333e43f
Binary files /dev/null and b/webroot/img/bar1.gif differ
diff --git a/webroot/img/bar2.gif b/webroot/img/bar2.gif
new file mode 100755
index 0000000..6a55e29
Binary files /dev/null and b/webroot/img/bar2.gif differ
diff --git a/webroot/img/bar3.gif b/webroot/img/bar3.gif
new file mode 100755
index 0000000..b639b84
Binary files /dev/null and b/webroot/img/bar3.gif differ
diff --git a/webroot/img/bar4.gif b/webroot/img/bar4.gif
new file mode 100755
index 0000000..0659ae4
Binary files /dev/null and b/webroot/img/bar4.gif differ
diff --git a/webroot/img/bar4right.gif b/webroot/img/bar4right.gif
new file mode 100755
index 0000000..cefc951
Binary files /dev/null and b/webroot/img/bar4right.gif differ
diff --git a/webroot/img/baradvantage.gif b/webroot/img/baradvantage.gif
new file mode 100755
index 0000000..1636051
Binary files /dev/null and b/webroot/img/baradvantage.gif differ
diff --git a/webroot/img/barcampaign1.gif b/webroot/img/barcampaign1.gif
new file mode 100755
index 0000000..b60407e
Binary files /dev/null and b/webroot/img/barcampaign1.gif differ
diff --git a/webroot/img/barcampaign2.gif b/webroot/img/barcampaign2.gif
new file mode 100755
index 0000000..3742b21
Binary files /dev/null and b/webroot/img/barcampaign2.gif differ
diff --git a/webroot/img/barcampaign3.gif b/webroot/img/barcampaign3.gif
new file mode 100755
index 0000000..2477890
Binary files /dev/null and b/webroot/img/barcampaign3.gif differ
diff --git a/webroot/img/barclient.gif b/webroot/img/barclient.gif
new file mode 100755
index 0000000..d4febbf
Binary files /dev/null and b/webroot/img/barclient.gif differ
diff --git a/webroot/img/barconsulting.gif b/webroot/img/barconsulting.gif
new file mode 100755
index 0000000..a3563e9
Binary files /dev/null and b/webroot/img/barconsulting.gif differ
diff --git a/webroot/img/barcontact1.gif b/webroot/img/barcontact1.gif
new file mode 100755
index 0000000..c1fd840
Binary files /dev/null and b/webroot/img/barcontact1.gif differ
diff --git a/webroot/img/barcontact2.gif b/webroot/img/barcontact2.gif
new file mode 100755
index 0000000..01b7fdc
Binary files /dev/null and b/webroot/img/barcontact2.gif differ
diff --git a/webroot/img/bardemo.gif b/webroot/img/bardemo.gif
new file mode 100755
index 0000000..6bf09de
Binary files /dev/null and b/webroot/img/bardemo.gif differ
diff --git a/webroot/img/barnews.gif b/webroot/img/barnews.gif
new file mode 100755
index 0000000..b627e1a
Binary files /dev/null and b/webroot/img/barnews.gif differ
diff --git a/webroot/img/barnews2.gif b/webroot/img/barnews2.gif
new file mode 100755
index 0000000..489ca08
Binary files /dev/null and b/webroot/img/barnews2.gif differ
diff --git a/webroot/img/barpartners.gif b/webroot/img/barpartners.gif
new file mode 100755
index 0000000..3737789
Binary files /dev/null and b/webroot/img/barpartners.gif differ
diff --git a/webroot/img/barprivacy.gif b/webroot/img/barprivacy.gif
new file mode 100755
index 0000000..f5be468
Binary files /dev/null and b/webroot/img/barprivacy.gif differ
diff --git a/webroot/img/barsitemap.gif b/webroot/img/barsitemap.gif
new file mode 100755
index 0000000..4e83d4b
Binary files /dev/null and b/webroot/img/barsitemap.gif differ
diff --git a/webroot/img/barstrat.gif b/webroot/img/barstrat.gif
new file mode 100755
index 0000000..6b0980e
Binary files /dev/null and b/webroot/img/barstrat.gif differ
diff --git a/webroot/img/barteam1.gif b/webroot/img/barteam1.gif
new file mode 100755
index 0000000..645b0de
Binary files /dev/null and b/webroot/img/barteam1.gif differ
diff --git a/webroot/img/barteam2.gif b/webroot/img/barteam2.gif
new file mode 100755
index 0000000..1c4ac4a
Binary files /dev/null and b/webroot/img/barteam2.gif differ
diff --git a/webroot/img/barvoter.gif b/webroot/img/barvoter.gif
new file mode 100755
index 0000000..ac592bc
Binary files /dev/null and b/webroot/img/barvoter.gif differ
diff --git a/webroot/img/barweb.gif b/webroot/img/barweb.gif
new file mode 100755
index 0000000..20d6590
Binary files /dev/null and b/webroot/img/barweb.gif differ
diff --git a/webroot/img/bc.jpg b/webroot/img/bc.jpg
new file mode 100755
index 0000000..9957ec1
Binary files /dev/null and b/webroot/img/bc.jpg differ
diff --git a/webroot/img/bluegradient.gif b/webroot/img/bluegradient.gif
new file mode 100755
index 0000000..7293f20
Binary files /dev/null and b/webroot/img/bluegradient.gif differ
diff --git a/webroot/img/bottom.gif b/webroot/img/bottom.gif
new file mode 100755
index 0000000..03e8715
Binary files /dev/null and b/webroot/img/bottom.gif differ
diff --git a/webroot/img/bottom2.gif b/webroot/img/bottom2.gif
new file mode 100755
index 0000000..8f5b79b
Binary files /dev/null and b/webroot/img/bottom2.gif differ
diff --git a/webroot/img/bottom3.gif b/webroot/img/bottom3.gif
new file mode 100755
index 0000000..7535beb
Binary files /dev/null and b/webroot/img/bottom3.gif differ
diff --git a/webroot/img/bottombend.gif b/webroot/img/bottombend.gif
new file mode 100755
index 0000000..22e3922
Binary files /dev/null and b/webroot/img/bottombend.gif differ
diff --git a/webroot/img/buttons/arrow.gif b/webroot/img/buttons/arrow.gif
new file mode 100755
index 0000000..fe4539b
Binary files /dev/null and b/webroot/img/buttons/arrow.gif differ
diff --git a/webroot/img/buttons/calendar.gif b/webroot/img/buttons/calendar.gif
new file mode 100755
index 0000000..b38d1a4
Binary files /dev/null and b/webroot/img/buttons/calendar.gif differ
diff --git a/webroot/img/buttons/campaign1.gif b/webroot/img/buttons/campaign1.gif
new file mode 100755
index 0000000..41b4345
Binary files /dev/null and b/webroot/img/buttons/campaign1.gif differ
diff --git a/webroot/img/buttons/campaign2.gif b/webroot/img/buttons/campaign2.gif
new file mode 100755
index 0000000..66ec8b8
Binary files /dev/null and b/webroot/img/buttons/campaign2.gif differ
diff --git a/webroot/img/buttons/campaign3.gif b/webroot/img/buttons/campaign3.gif
new file mode 100755
index 0000000..aec6882
Binary files /dev/null and b/webroot/img/buttons/campaign3.gif differ
diff --git a/webroot/img/buttons/checked.gif b/webroot/img/buttons/checked.gif
new file mode 100755
index 0000000..da35c21
Binary files /dev/null and b/webroot/img/buttons/checked.gif differ
diff --git a/webroot/img/buttons/clients1.gif b/webroot/img/buttons/clients1.gif
new file mode 100755
index 0000000..344bdfa
Binary files /dev/null and b/webroot/img/buttons/clients1.gif differ
diff --git a/webroot/img/buttons/clients2.gif b/webroot/img/buttons/clients2.gif
new file mode 100755
index 0000000..d0f4f33
Binary files /dev/null and b/webroot/img/buttons/clients2.gif differ
diff --git a/webroot/img/buttons/clients3.gif b/webroot/img/buttons/clients3.gif
new file mode 100755
index 0000000..fbbe29e
Binary files /dev/null and b/webroot/img/buttons/clients3.gif differ
diff --git a/webroot/img/buttons/close1.gif b/webroot/img/buttons/close1.gif
new file mode 100755
index 0000000..cf1e059
Binary files /dev/null and b/webroot/img/buttons/close1.gif differ
diff --git a/webroot/img/buttons/close2.gif b/webroot/img/buttons/close2.gif
new file mode 100755
index 0000000..4a4045a
Binary files /dev/null and b/webroot/img/buttons/close2.gif differ
diff --git a/webroot/img/buttons/consulting1.gif b/webroot/img/buttons/consulting1.gif
new file mode 100755
index 0000000..00c50fc
Binary files /dev/null and b/webroot/img/buttons/consulting1.gif differ
diff --git a/webroot/img/buttons/consulting2.gif b/webroot/img/buttons/consulting2.gif
new file mode 100755
index 0000000..05f4377
Binary files /dev/null and b/webroot/img/buttons/consulting2.gif differ
diff --git a/webroot/img/buttons/contact1.gif b/webroot/img/buttons/contact1.gif
new file mode 100755
index 0000000..5b3e712
Binary files /dev/null and b/webroot/img/buttons/contact1.gif differ
diff --git a/webroot/img/buttons/contact2.gif b/webroot/img/buttons/contact2.gif
new file mode 100755
index 0000000..230b15f
Binary files /dev/null and b/webroot/img/buttons/contact2.gif differ
diff --git a/webroot/img/buttons/contact3.gif b/webroot/img/buttons/contact3.gif
new file mode 100755
index 0000000..4401ed6
Binary files /dev/null and b/webroot/img/buttons/contact3.gif differ
diff --git a/webroot/img/buttons/delete.gif b/webroot/img/buttons/delete.gif
new file mode 100755
index 0000000..f845e35
Binary files /dev/null and b/webroot/img/buttons/delete.gif differ
diff --git a/webroot/img/buttons/design1.gif b/webroot/img/buttons/design1.gif
new file mode 100755
index 0000000..e314a9d
Binary files /dev/null and b/webroot/img/buttons/design1.gif differ
diff --git a/webroot/img/buttons/design2.gif b/webroot/img/buttons/design2.gif
new file mode 100755
index 0000000..b31ebd7
Binary files /dev/null and b/webroot/img/buttons/design2.gif differ
diff --git a/webroot/img/buttons/down_arrow.gif b/webroot/img/buttons/down_arrow.gif
new file mode 100755
index 0000000..ea33f57
Binary files /dev/null and b/webroot/img/buttons/down_arrow.gif differ
diff --git a/webroot/img/buttons/down_arrow_disabled.gif b/webroot/img/buttons/down_arrow_disabled.gif
new file mode 100755
index 0000000..b26a578
Binary files /dev/null and b/webroot/img/buttons/down_arrow_disabled.gif differ
diff --git a/webroot/img/buttons/down_arrow_new.gif b/webroot/img/buttons/down_arrow_new.gif
new file mode 100755
index 0000000..51105ea
Binary files /dev/null and b/webroot/img/buttons/down_arrow_new.gif differ
diff --git a/webroot/img/buttons/emessenger1.gif b/webroot/img/buttons/emessenger1.gif
new file mode 100755
index 0000000..42d5051
Binary files /dev/null and b/webroot/img/buttons/emessenger1.gif differ
diff --git a/webroot/img/buttons/emessenger2.gif b/webroot/img/buttons/emessenger2.gif
new file mode 100755
index 0000000..5c2a574
Binary files /dev/null and b/webroot/img/buttons/emessenger2.gif differ
diff --git a/webroot/img/buttons/expand_half.gif b/webroot/img/buttons/expand_half.gif
new file mode 100755
index 0000000..e120c2b
Binary files /dev/null and b/webroot/img/buttons/expand_half.gif differ
diff --git a/webroot/img/buttons/expand_off.gif b/webroot/img/buttons/expand_off.gif
new file mode 100755
index 0000000..fcd565e
Binary files /dev/null and b/webroot/img/buttons/expand_off.gif differ
diff --git a/webroot/img/buttons/expand_on.gif b/webroot/img/buttons/expand_on.gif
new file mode 100755
index 0000000..9b7ffe0
Binary files /dev/null and b/webroot/img/buttons/expand_on.gif differ
diff --git a/webroot/img/buttons/ghost_arrow.gif b/webroot/img/buttons/ghost_arrow.gif
new file mode 100755
index 0000000..f901670
Binary files /dev/null and b/webroot/img/buttons/ghost_arrow.gif differ
diff --git a/webroot/img/buttons/green_dot.gif b/webroot/img/buttons/green_dot.gif
new file mode 100755
index 0000000..76c0017
Binary files /dev/null and b/webroot/img/buttons/green_dot.gif differ
diff --git a/webroot/img/buttons/halfchecked.gif b/webroot/img/buttons/halfchecked.gif
new file mode 100755
index 0000000..b3fa51e
Binary files /dev/null and b/webroot/img/buttons/halfchecked.gif differ
diff --git a/webroot/img/buttons/last.gif b/webroot/img/buttons/last.gif
new file mode 100755
index 0000000..b3cf73d
Binary files /dev/null and b/webroot/img/buttons/last.gif differ
diff --git a/webroot/img/buttons/last2.gif b/webroot/img/buttons/last2.gif
new file mode 100755
index 0000000..06c159d
Binary files /dev/null and b/webroot/img/buttons/last2.gif differ
diff --git a/webroot/img/buttons/left.gif b/webroot/img/buttons/left.gif
new file mode 100755
index 0000000..81dccb9
Binary files /dev/null and b/webroot/img/buttons/left.gif differ
diff --git a/webroot/img/buttons/left_arrow.gif b/webroot/img/buttons/left_arrow.gif
new file mode 100755
index 0000000..5b8518a
Binary files /dev/null and b/webroot/img/buttons/left_arrow.gif differ
diff --git a/webroot/img/buttons/lefttab.gif b/webroot/img/buttons/lefttab.gif
new file mode 100755
index 0000000..207572d
Binary files /dev/null and b/webroot/img/buttons/lefttab.gif differ
diff --git a/webroot/img/buttons/listmanager1.gif b/webroot/img/buttons/listmanager1.gif
new file mode 100755
index 0000000..49bcad1
Binary files /dev/null and b/webroot/img/buttons/listmanager1.gif differ
diff --git a/webroot/img/buttons/listmanager2.gif b/webroot/img/buttons/listmanager2.gif
new file mode 100755
index 0000000..ba3f4e8
Binary files /dev/null and b/webroot/img/buttons/listmanager2.gif differ
diff --git a/webroot/img/buttons/login.gif b/webroot/img/buttons/login.gif
new file mode 100755
index 0000000..c0f759f
Binary files /dev/null and b/webroot/img/buttons/login.gif differ
diff --git a/webroot/img/buttons/news1.gif b/webroot/img/buttons/news1.gif
new file mode 100755
index 0000000..9c04458
Binary files /dev/null and b/webroot/img/buttons/news1.gif differ
diff --git a/webroot/img/buttons/news2.gif b/webroot/img/buttons/news2.gif
new file mode 100755
index 0000000..85bf90d
Binary files /dev/null and b/webroot/img/buttons/news2.gif differ
diff --git a/webroot/img/buttons/news3.gif b/webroot/img/buttons/news3.gif
new file mode 100755
index 0000000..d83fbf5
Binary files /dev/null and b/webroot/img/buttons/news3.gif differ
diff --git a/webroot/img/buttons/next.gif b/webroot/img/buttons/next.gif
new file mode 100755
index 0000000..31c7f76
Binary files /dev/null and b/webroot/img/buttons/next.gif differ
diff --git a/webroot/img/buttons/next2.gif b/webroot/img/buttons/next2.gif
new file mode 100755
index 0000000..3cb27e2
Binary files /dev/null and b/webroot/img/buttons/next2.gif differ
diff --git a/webroot/img/buttons/partners1.gif b/webroot/img/buttons/partners1.gif
new file mode 100755
index 0000000..ec7ffc6
Binary files /dev/null and b/webroot/img/buttons/partners1.gif differ
diff --git a/webroot/img/buttons/partners2.gif b/webroot/img/buttons/partners2.gif
new file mode 100755
index 0000000..4a4aafb
Binary files /dev/null and b/webroot/img/buttons/partners2.gif differ
diff --git a/webroot/img/buttons/privacy1.gif b/webroot/img/buttons/privacy1.gif
new file mode 100755
index 0000000..2544332
Binary files /dev/null and b/webroot/img/buttons/privacy1.gif differ
diff --git a/webroot/img/buttons/privacy2.gif b/webroot/img/buttons/privacy2.gif
new file mode 100755
index 0000000..83c1e95
Binary files /dev/null and b/webroot/img/buttons/privacy2.gif differ
diff --git a/webroot/img/buttons/question_mark.gif b/webroot/img/buttons/question_mark.gif
new file mode 100755
index 0000000..ce88131
Binary files /dev/null and b/webroot/img/buttons/question_mark.gif differ
diff --git a/webroot/img/buttons/red_dot.gif b/webroot/img/buttons/red_dot.gif
new file mode 100755
index 0000000..01d81bd
Binary files /dev/null and b/webroot/img/buttons/red_dot.gif differ
diff --git a/webroot/img/buttons/right.gif b/webroot/img/buttons/right.gif
new file mode 100755
index 0000000..c6fc741
Binary files /dev/null and b/webroot/img/buttons/right.gif differ
diff --git a/webroot/img/buttons/right2.gif b/webroot/img/buttons/right2.gif
new file mode 100755
index 0000000..fbe9a82
Binary files /dev/null and b/webroot/img/buttons/right2.gif differ
diff --git a/webroot/img/buttons/right_arrow.gif b/webroot/img/buttons/right_arrow.gif
new file mode 100755
index 0000000..b9bd1cf
Binary files /dev/null and b/webroot/img/buttons/right_arrow.gif differ
diff --git a/webroot/img/buttons/righttab.gif b/webroot/img/buttons/righttab.gif
new file mode 100755
index 0000000..153aff7
Binary files /dev/null and b/webroot/img/buttons/righttab.gif differ
diff --git a/webroot/img/buttons/righttab2.gif b/webroot/img/buttons/righttab2.gif
new file mode 100755
index 0000000..9acee0f
Binary files /dev/null and b/webroot/img/buttons/righttab2.gif differ
diff --git a/webroot/img/buttons/sitemap1.gif b/webroot/img/buttons/sitemap1.gif
new file mode 100755
index 0000000..55c5480
Binary files /dev/null and b/webroot/img/buttons/sitemap1.gif differ
diff --git a/webroot/img/buttons/sitemap2.gif b/webroot/img/buttons/sitemap2.gif
new file mode 100755
index 0000000..b49850b
Binary files /dev/null and b/webroot/img/buttons/sitemap2.gif differ
diff --git a/webroot/img/buttons/team1.gif b/webroot/img/buttons/team1.gif
new file mode 100755
index 0000000..c4fe635
Binary files /dev/null and b/webroot/img/buttons/team1.gif differ
diff --git a/webroot/img/buttons/team2.gif b/webroot/img/buttons/team2.gif
new file mode 100755
index 0000000..b4c7a5d
Binary files /dev/null and b/webroot/img/buttons/team2.gif differ
diff --git a/webroot/img/buttons/team3.gif b/webroot/img/buttons/team3.gif
new file mode 100755
index 0000000..3ede2f3
Binary files /dev/null and b/webroot/img/buttons/team3.gif differ
diff --git a/webroot/img/buttons/unchecked.gif b/webroot/img/buttons/unchecked.gif
new file mode 100755
index 0000000..94db417
Binary files /dev/null and b/webroot/img/buttons/unchecked.gif differ
diff --git a/webroot/img/buttons/up_arrow.gif b/webroot/img/buttons/up_arrow.gif
new file mode 100755
index 0000000..e32fa51
Binary files /dev/null and b/webroot/img/buttons/up_arrow.gif differ
diff --git a/webroot/img/buttons/up_arrow_disabled.gif b/webroot/img/buttons/up_arrow_disabled.gif
new file mode 100755
index 0000000..c4a4e9d
Binary files /dev/null and b/webroot/img/buttons/up_arrow_disabled.gif differ
diff --git a/webroot/img/buttons/up_arrow_new.gif b/webroot/img/buttons/up_arrow_new.gif
new file mode 100755
index 0000000..beae1e6
Binary files /dev/null and b/webroot/img/buttons/up_arrow_new.gif differ
diff --git a/webroot/img/bw_owl.gif b/webroot/img/bw_owl.gif
new file mode 100755
index 0000000..e0edb56
Binary files /dev/null and b/webroot/img/bw_owl.gif differ
diff --git a/webroot/img/cal/btn_add.gif b/webroot/img/cal/btn_add.gif
new file mode 100755
index 0000000..ee375d8
Binary files /dev/null and b/webroot/img/cal/btn_add.gif differ
diff --git a/webroot/img/cal/btn_delete.gif b/webroot/img/cal/btn_delete.gif
new file mode 100755
index 0000000..ff3d74c
Binary files /dev/null and b/webroot/img/cal/btn_delete.gif differ
diff --git a/webroot/img/cal/btn_edit.gif b/webroot/img/cal/btn_edit.gif
new file mode 100755
index 0000000..3a0050c
Binary files /dev/null and b/webroot/img/cal/btn_edit.gif differ
diff --git a/webroot/img/cal/btn_export.gif b/webroot/img/cal/btn_export.gif
new file mode 100755
index 0000000..a5b849a
Binary files /dev/null and b/webroot/img/cal/btn_export.gif differ
diff --git a/webroot/img/cal/btn_import.gif b/webroot/img/cal/btn_import.gif
new file mode 100755
index 0000000..4a2c0a9
Binary files /dev/null and b/webroot/img/cal/btn_import.gif differ
diff --git a/webroot/img/cal/btn_print.gif b/webroot/img/cal/btn_print.gif
new file mode 100755
index 0000000..591118b
Binary files /dev/null and b/webroot/img/cal/btn_print.gif differ
diff --git a/webroot/img/cal/btn_zoom.gif b/webroot/img/cal/btn_zoom.gif
new file mode 100755
index 0000000..7d0d497
Binary files /dev/null and b/webroot/img/cal/btn_zoom.gif differ
diff --git a/webroot/img/cal/div.gif b/webroot/img/cal/div.gif
new file mode 100755
index 0000000..d360ae6
Binary files /dev/null and b/webroot/img/cal/div.gif differ
diff --git a/webroot/img/cal/flag.gif b/webroot/img/cal/flag.gif
new file mode 100755
index 0000000..9385f4c
Binary files /dev/null and b/webroot/img/cal/flag.gif differ
diff --git a/webroot/img/cal/l_corner.gif b/webroot/img/cal/l_corner.gif
new file mode 100755
index 0000000..49414ee
Binary files /dev/null and b/webroot/img/cal/l_corner.gif differ
diff --git a/webroot/img/cal/l_corner_hi.gif b/webroot/img/cal/l_corner_hi.gif
new file mode 100755
index 0000000..41a2171
Binary files /dev/null and b/webroot/img/cal/l_corner_hi.gif differ
diff --git a/webroot/img/cal/print_day.gif b/webroot/img/cal/print_day.gif
new file mode 100755
index 0000000..0e19bf3
Binary files /dev/null and b/webroot/img/cal/print_day.gif differ
diff --git a/webroot/img/cal/print_month.gif b/webroot/img/cal/print_month.gif
new file mode 100755
index 0000000..c7da143
Binary files /dev/null and b/webroot/img/cal/print_month.gif differ
diff --git a/webroot/img/cal/print_week.gif b/webroot/img/cal/print_week.gif
new file mode 100755
index 0000000..4621306
Binary files /dev/null and b/webroot/img/cal/print_week.gif differ
diff --git a/webroot/img/cal/r_corner.gif b/webroot/img/cal/r_corner.gif
new file mode 100755
index 0000000..5f0e3c0
Binary files /dev/null and b/webroot/img/cal/r_corner.gif differ
diff --git a/webroot/img/cal/r_corner_hi.gif b/webroot/img/cal/r_corner_hi.gif
new file mode 100755
index 0000000..c73b7b5
Binary files /dev/null and b/webroot/img/cal/r_corner_hi.gif differ
diff --git a/webroot/img/cal/sage_logo_sm.gif b/webroot/img/cal/sage_logo_sm.gif
new file mode 100755
index 0000000..74e0023
Binary files /dev/null and b/webroot/img/cal/sage_logo_sm.gif differ
diff --git a/webroot/img/cal/spacer.gif b/webroot/img/cal/spacer.gif
new file mode 100755
index 0000000..fc25609
Binary files /dev/null and b/webroot/img/cal/spacer.gif differ
diff --git a/webroot/img/cal/spinner.gif b/webroot/img/cal/spinner.gif
new file mode 100755
index 0000000..085ccae
Binary files /dev/null and b/webroot/img/cal/spinner.gif differ
diff --git a/webroot/img/campaign.jpg b/webroot/img/campaign.jpg
new file mode 100755
index 0000000..edee019
Binary files /dev/null and b/webroot/img/campaign.jpg differ
diff --git a/webroot/img/clients.jpg b/webroot/img/clients.jpg
new file mode 100755
index 0000000..94b4c8e
Binary files /dev/null and b/webroot/img/clients.jpg differ
diff --git a/webroot/img/compliance.gif b/webroot/img/compliance.gif
new file mode 100755
index 0000000..a07c766
Binary files /dev/null and b/webroot/img/compliance.gif differ
diff --git a/webroot/img/contact.jpg b/webroot/img/contact.jpg
new file mode 100755
index 0000000..cd16ddf
Binary files /dev/null and b/webroot/img/contact.jpg differ
diff --git a/webroot/img/corner_bottom_left.gif b/webroot/img/corner_bottom_left.gif
new file mode 100755
index 0000000..2ff1685
Binary files /dev/null and b/webroot/img/corner_bottom_left.gif differ
diff --git a/webroot/img/corner_bottom_right.gif b/webroot/img/corner_bottom_right.gif
new file mode 100755
index 0000000..2e20a96
Binary files /dev/null and b/webroot/img/corner_bottom_right.gif differ
diff --git a/webroot/img/corner_top_left.gif b/webroot/img/corner_top_left.gif
new file mode 100755
index 0000000..bb1b0b0
Binary files /dev/null and b/webroot/img/corner_top_left.gif differ
diff --git a/webroot/img/corner_top_right.gif b/webroot/img/corner_top_right.gif
new file mode 100755
index 0000000..eff486c
Binary files /dev/null and b/webroot/img/corner_top_right.gif differ
diff --git a/webroot/img/design.jpg b/webroot/img/design.jpg
new file mode 100755
index 0000000..71232a4
Binary files /dev/null and b/webroot/img/design.jpg differ
diff --git a/webroot/img/divider.gif b/webroot/img/divider.gif
new file mode 100755
index 0000000..6a9aa19
Binary files /dev/null and b/webroot/img/divider.gif differ
diff --git a/webroot/img/dw.jpg b/webroot/img/dw.jpg
new file mode 100755
index 0000000..c21314c
Binary files /dev/null and b/webroot/img/dw.jpg differ
diff --git a/webroot/img/dw2.jpg b/webroot/img/dw2.jpg
new file mode 100755
index 0000000..6b235f4
Binary files /dev/null and b/webroot/img/dw2.jpg differ
diff --git a/webroot/img/em.jpg b/webroot/img/em.jpg
new file mode 100755
index 0000000..95eafb5
Binary files /dev/null and b/webroot/img/em.jpg differ
diff --git a/webroot/img/emess.jpg b/webroot/img/emess.jpg
new file mode 100755
index 0000000..c71bf85
Binary files /dev/null and b/webroot/img/emess.jpg differ
diff --git a/webroot/img/emessenger.jpg b/webroot/img/emessenger.jpg
new file mode 100755
index 0000000..c372729
Binary files /dev/null and b/webroot/img/emessenger.jpg differ
diff --git a/webroot/img/envelope.gif b/webroot/img/envelope.gif
new file mode 100755
index 0000000..fa127a1
Binary files /dev/null and b/webroot/img/envelope.gif differ
diff --git a/webroot/img/envelopes.gif b/webroot/img/envelopes.gif
new file mode 100755
index 0000000..f561193
Binary files /dev/null and b/webroot/img/envelopes.gif differ
diff --git a/webroot/img/fec_down_arrow.jpg b/webroot/img/fec_down_arrow.jpg
new file mode 100755
index 0000000..4897f14
Binary files /dev/null and b/webroot/img/fec_down_arrow.jpg differ
diff --git a/webroot/img/fec_right_arrow.jpg b/webroot/img/fec_right_arrow.jpg
new file mode 100755
index 0000000..bbe44b8
Binary files /dev/null and b/webroot/img/fec_right_arrow.jpg differ
diff --git a/webroot/img/fec_up_arrow.jpg b/webroot/img/fec_up_arrow.jpg
new file mode 100755
index 0000000..570396e
Binary files /dev/null and b/webroot/img/fec_up_arrow.jpg differ
diff --git a/webroot/img/fp.jpg b/webroot/img/fp.jpg
new file mode 100755
index 0000000..955bac6
Binary files /dev/null and b/webroot/img/fp.jpg differ
diff --git a/webroot/img/ghost.GIF b/webroot/img/ghost.GIF
new file mode 100755
index 0000000..d89217e
Binary files /dev/null and b/webroot/img/ghost.GIF differ
diff --git a/webroot/img/gradient.gif b/webroot/img/gradient.gif
new file mode 100755
index 0000000..78ebe45
Binary files /dev/null and b/webroot/img/gradient.gif differ
diff --git a/webroot/img/gradient_logo.gif b/webroot/img/gradient_logo.gif
new file mode 100755
index 0000000..e63f2eb
Binary files /dev/null and b/webroot/img/gradient_logo.gif differ
diff --git a/webroot/img/gradient_logo_orig.gif b/webroot/img/gradient_logo_orig.gif
new file mode 100755
index 0000000..a9fde6f
Binary files /dev/null and b/webroot/img/gradient_logo_orig.gif differ
diff --git a/webroot/img/gradient_small.gif b/webroot/img/gradient_small.gif
new file mode 100755
index 0000000..2dde979
Binary files /dev/null and b/webroot/img/gradient_small.gif differ
diff --git a/webroot/img/high_priority.gif b/webroot/img/high_priority.gif
new file mode 100755
index 0000000..8408ca7
Binary files /dev/null and b/webroot/img/high_priority.gif differ
diff --git a/webroot/img/info.gif b/webroot/img/info.gif
new file mode 100755
index 0000000..43ef9f9
Binary files /dev/null and b/webroot/img/info.gif differ
diff --git a/webroot/img/layout_landscape.gif b/webroot/img/layout_landscape.gif
new file mode 100755
index 0000000..2a004a6
Binary files /dev/null and b/webroot/img/layout_landscape.gif differ
diff --git a/webroot/img/layout_portrait.gif b/webroot/img/layout_portrait.gif
new file mode 100755
index 0000000..7c89205
Binary files /dev/null and b/webroot/img/layout_portrait.gif differ
diff --git a/webroot/img/leftfade.gif b/webroot/img/leftfade.gif
new file mode 100755
index 0000000..f8d20df
Binary files /dev/null and b/webroot/img/leftfade.gif differ
diff --git a/webroot/img/lefttab1.gif b/webroot/img/lefttab1.gif
new file mode 100755
index 0000000..06ae0f1
Binary files /dev/null and b/webroot/img/lefttab1.gif differ
diff --git a/webroot/img/lefttab2.gif b/webroot/img/lefttab2.gif
new file mode 100755
index 0000000..bf2ef5a
Binary files /dev/null and b/webroot/img/lefttab2.gif differ
diff --git a/webroot/img/level_1_bullet.gif b/webroot/img/level_1_bullet.gif
new file mode 100755
index 0000000..d16f49f
Binary files /dev/null and b/webroot/img/level_1_bullet.gif differ
diff --git a/webroot/img/level_2_bullet.gif b/webroot/img/level_2_bullet.gif
new file mode 100755
index 0000000..46e4227
Binary files /dev/null and b/webroot/img/level_2_bullet.gif differ
diff --git a/webroot/img/level_3_bullet.gif b/webroot/img/level_3_bullet.gif
new file mode 100755
index 0000000..92e5722
Binary files /dev/null and b/webroot/img/level_3_bullet.gif differ
diff --git a/webroot/img/listmanager.jpg b/webroot/img/listmanager.jpg
new file mode 100755
index 0000000..5932771
Binary files /dev/null and b/webroot/img/listmanager.jpg differ
diff --git a/webroot/img/logo.gif b/webroot/img/logo.gif
new file mode 100755
index 0000000..ff7eda5
Binary files /dev/null and b/webroot/img/logo.gif differ
diff --git a/webroot/img/logo.jpg b/webroot/img/logo.jpg
new file mode 100755
index 0000000..91395f2
Binary files /dev/null and b/webroot/img/logo.jpg differ
diff --git a/webroot/img/logoLM.gif b/webroot/img/logoLM.gif
new file mode 100755
index 0000000..74e0023
Binary files /dev/null and b/webroot/img/logoLM.gif differ
diff --git a/webroot/img/logo_old.gif b/webroot/img/logo_old.gif
new file mode 100755
index 0000000..4bf2f98
Binary files /dev/null and b/webroot/img/logo_old.gif differ
diff --git a/webroot/img/logo_small.gif b/webroot/img/logo_small.gif
new file mode 100755
index 0000000..dbc8901
Binary files /dev/null and b/webroot/img/logo_small.gif differ
diff --git a/webroot/img/logobtm-orig.gif b/webroot/img/logobtm-orig.gif
new file mode 100755
index 0000000..86a7841
Binary files /dev/null and b/webroot/img/logobtm-orig.gif differ
diff --git a/webroot/img/logobtm.gif b/webroot/img/logobtm.gif
new file mode 100755
index 0000000..e277388
Binary files /dev/null and b/webroot/img/logobtm.gif differ
diff --git a/webroot/img/logobtm2-orig.gif b/webroot/img/logobtm2-orig.gif
new file mode 100755
index 0000000..9819f04
Binary files /dev/null and b/webroot/img/logobtm2-orig.gif differ
diff --git a/webroot/img/logobtm2.gif b/webroot/img/logobtm2.gif
new file mode 100755
index 0000000..f6c1d80
Binary files /dev/null and b/webroot/img/logobtm2.gif differ
diff --git a/webroot/img/lp.jpg b/webroot/img/lp.jpg
new file mode 100755
index 0000000..006bd50
Binary files /dev/null and b/webroot/img/lp.jpg differ
diff --git a/webroot/img/manager.gif b/webroot/img/manager.gif
new file mode 100755
index 0000000..3ace811
Binary files /dev/null and b/webroot/img/manager.gif differ
diff --git a/webroot/img/mapping.gif b/webroot/img/mapping.gif
new file mode 100755
index 0000000..e309522
Binary files /dev/null and b/webroot/img/mapping.gif differ
diff --git a/webroot/img/news.jpg b/webroot/img/news.jpg
new file mode 100755
index 0000000..d80433f
Binary files /dev/null and b/webroot/img/news.jpg differ
diff --git a/webroot/img/om.jpg b/webroot/img/om.jpg
new file mode 100755
index 0000000..f1b8eea
Binary files /dev/null and b/webroot/img/om.jpg differ
diff --git a/webroot/img/percent_dot.gif b/webroot/img/percent_dot.gif
new file mode 100755
index 0000000..f8acf0e
Binary files /dev/null and b/webroot/img/percent_dot.gif differ
diff --git a/webroot/img/permission_add.gif b/webroot/img/permission_add.gif
new file mode 100755
index 0000000..814a99b
Binary files /dev/null and b/webroot/img/permission_add.gif differ
diff --git a/webroot/img/permission_default.gif b/webroot/img/permission_default.gif
new file mode 100755
index 0000000..d004827
Binary files /dev/null and b/webroot/img/permission_default.gif differ
diff --git a/webroot/img/permission_delete.gif b/webroot/img/permission_delete.gif
new file mode 100755
index 0000000..2304f3f
Binary files /dev/null and b/webroot/img/permission_delete.gif differ
diff --git a/webroot/img/permission_edit.gif b/webroot/img/permission_edit.gif
new file mode 100755
index 0000000..72e849a
Binary files /dev/null and b/webroot/img/permission_edit.gif differ
diff --git a/webroot/img/permission_view.gif b/webroot/img/permission_view.gif
new file mode 100755
index 0000000..90a5830
Binary files /dev/null and b/webroot/img/permission_view.gif differ
diff --git a/webroot/img/report.jpg b/webroot/img/report.jpg
new file mode 100755
index 0000000..724be0f
Binary files /dev/null and b/webroot/img/report.jpg differ
diff --git a/webroot/img/reports.gif b/webroot/img/reports.gif
new file mode 100755
index 0000000..f3399fb
Binary files /dev/null and b/webroot/img/reports.gif differ
diff --git a/webroot/img/rightfade.gif b/webroot/img/rightfade.gif
new file mode 100755
index 0000000..155fbfb
Binary files /dev/null and b/webroot/img/rightfade.gif differ
diff --git a/webroot/img/righttab1.gif b/webroot/img/righttab1.gif
new file mode 100755
index 0000000..911e4e0
Binary files /dev/null and b/webroot/img/righttab1.gif differ
diff --git a/webroot/img/righttab2.gif b/webroot/img/righttab2.gif
new file mode 100755
index 0000000..1195d73
Binary files /dev/null and b/webroot/img/righttab2.gif differ
diff --git a/webroot/img/rs.jpg b/webroot/img/rs.jpg
new file mode 100755
index 0000000..8c94e50
Binary files /dev/null and b/webroot/img/rs.jpg differ
diff --git a/webroot/img/search.gif b/webroot/img/search.gif
new file mode 100755
index 0000000..674bc62
Binary files /dev/null and b/webroot/img/search.gif differ
diff --git a/webroot/img/seneca-lefttab1.gif b/webroot/img/seneca-lefttab1.gif
new file mode 100755
index 0000000..06ae0f1
Binary files /dev/null and b/webroot/img/seneca-lefttab1.gif differ
diff --git a/webroot/img/seneca-lefttab2.gif b/webroot/img/seneca-lefttab2.gif
new file mode 100755
index 0000000..c969d2d
Binary files /dev/null and b/webroot/img/seneca-lefttab2.gif differ
diff --git a/webroot/img/seneca-righttab1.gif b/webroot/img/seneca-righttab1.gif
new file mode 100755
index 0000000..911e4e0
Binary files /dev/null and b/webroot/img/seneca-righttab1.gif differ
diff --git a/webroot/img/seneca-righttab2.gif b/webroot/img/seneca-righttab2.gif
new file mode 100755
index 0000000..9a22c89
Binary files /dev/null and b/webroot/img/seneca-righttab2.gif differ
diff --git a/webroot/img/seneca.gif b/webroot/img/seneca.gif
new file mode 100755
index 0000000..3dea778
Binary files /dev/null and b/webroot/img/seneca.gif differ
diff --git a/webroot/img/sidebarselect.gif b/webroot/img/sidebarselect.gif
new file mode 100755
index 0000000..4025f7d
Binary files /dev/null and b/webroot/img/sidebarselect.gif differ
diff --git a/webroot/img/sort_asc.gif b/webroot/img/sort_asc.gif
new file mode 100755
index 0000000..b0914d3
Binary files /dev/null and b/webroot/img/sort_asc.gif differ
diff --git a/webroot/img/sort_desc.gif b/webroot/img/sort_desc.gif
new file mode 100755
index 0000000..ed0acab
Binary files /dev/null and b/webroot/img/sort_desc.gif differ
diff --git a/webroot/img/spinner.gif b/webroot/img/spinner.gif
new file mode 100755
index 0000000..085ccae
Binary files /dev/null and b/webroot/img/spinner.gif differ
diff --git a/webroot/img/star.gif b/webroot/img/star.gif
new file mode 100755
index 0000000..4022be1
Binary files /dev/null and b/webroot/img/star.gif differ
diff --git a/webroot/img/table_bottom.gif b/webroot/img/table_bottom.gif
new file mode 100755
index 0000000..c3ff56f
Binary files /dev/null and b/webroot/img/table_bottom.gif differ
diff --git a/webroot/img/table_top.gif b/webroot/img/table_top.gif
new file mode 100755
index 0000000..b1e528e
Binary files /dev/null and b/webroot/img/table_top.gif differ
diff --git a/webroot/img/team.jpg b/webroot/img/team.jpg
new file mode 100755
index 0000000..ae75390
Binary files /dev/null and b/webroot/img/team.jpg differ
diff --git a/webroot/img/top_gradient.gif b/webroot/img/top_gradient.gif
new file mode 100755
index 0000000..4b8c754
Binary files /dev/null and b/webroot/img/top_gradient.gif differ
diff --git a/webroot/img/topdots.gif b/webroot/img/topdots.gif
new file mode 100755
index 0000000..c291ad2
Binary files /dev/null and b/webroot/img/topdots.gif differ
diff --git a/webroot/img/transgif.pl b/webroot/img/transgif.pl
new file mode 100755
index 0000000..6d28258
--- /dev/null
+++ b/webroot/img/transgif.pl
@@ -0,0 +1,794 @@
+#!/usr/local/bin/perl -w
+## Make a gif "transparent"
+##
+## Jeffrey Friedl
+## jfriedl@omrongw.wg.omron.co.jp
+## 15 July 1994
+## 2 Aug 1994 - added ability to select transparent color by RGB values.
+## 940825.3 -- modified to work with possible future versions of the
+## GIF standard... just in case.
+##
+#$version = "940825.3";
+##
+## BLURB:
+## Transforms a "normal" gif into a "transparent background" gif.
+##
+##>
+##
+## I wrote this because people ask for something like this all the time.
+## I just learned the format of GIFs a week ago, so this will likely be
+## lacking in many respects.
+##
+##
+## Usage:
+## transgif [options] regular.gif > transparent.gif
+## or
+## cat regular.gif | transgif [options] transparent.gif
+##
+## The default is that whatever color happens to fall into the first colormap
+## slot (often black) will be made transparent. This can be changed via the
+## the options.
+##
+## The options are from:
+## -p print the colormap (to STDERR).
+## The new gif still goes to STDOUT.
+##
+## -### make colormap index-### transparent (default is -0)
+##
+## -rgb ## ## ## Take the three numbers as R G B values (in the range
+## of 0..255 (or 0x00..0xff). The first colormap entry
+## with those RGB values is made transparent.
+##
+## -rgb name Use the R G B values of the color 'name' if known
+## by this program (data from X11's rgb.txt)
+##
+## COLORNUM is the index of the color entry to make transparent, and
+## defaults to zero. For those that like the looks of it, you can put
+## a leading '-'.
+##
+##<
+
+sub usage {
+ die "@_\nUsage: $0 [-p] [-## | -rgb name | -rgb ## ## ##] [file]\n";
+}
+
+$trans_index = 0;
+$print_color_map = 0;
+$select_via_rgb = 0;
+
+while (@ARGV && $ARGV[0] =~ m/^-/) {
+ $arg = shift;
+ if ($arg eq '-p') { ## print color map
+ $print_color_map = 1;
+
+ } elsif ($arg =~ m/^-(\d+)$/) { ## set color map index number
+ $trans_index = $1;
+
+ } elsif ($arg eq '-rgb') { ## set what color to make transparent
+
+ ## if next three args look numerical (## or 0x##), use as R B G.
+ if (@ARGV >= 3 &&
+ $ARGV[0] =~ m/^(0x[\da-f]+|\d+)$/i &&
+ $ARGV[1] =~ m/^(0x[\da-f]+|\d+)$/i &&
+ $ARGV[2] =~ m/^(0x[\da-f]+|\d+)$/i)
+ {
+ ($R, $G, $B) = splice(@ARGV, 0, 3);
+ $select_via_rgb = 1;
+ $R = eval($R); ## eval these to process any hex or octal values.
+ $G = eval($G); ## eval these to process any hex or octal values.
+ $B = eval($B); ## eval these to process any hex or octal values.
+
+ ## if next arg looks like a color name, use those R G B values.
+ } elsif (@ARGV && (@RGB = &name2rgb($ARGV[0]), @RGB == 3)) {
+ shift; ## eat name;
+ ($R, $G, $B) = @RGB;
+ $select_via_rgb = 1;
+
+ } else {
+ warn(qq/(don't understand "$ARGV[0]" as a color name)\n/) if @ARGV;
+ die qq/$0: expected color name or a numerical triplet for $arg\n/;
+ }
+ } else {
+ &usage(qq/unknown arg "$arg".\n/);
+ }
+}
+
+&usage('too many args.') if @ARGV > 1;
+
+if (@ARGV == 0) {
+ &giftrans(*STDIN, *STDOUT, $trans_index);
+} else {
+ open(INPUT, $file =shift) || die "$0: couldn't open [$file] for input\n";
+ &giftrans(*INPUT, *STDOUT, $trans_index);
+ close(INPUT);
+}
+exit(0);
+
+
+
+##
+## Given indirect references to two filehandles, pass the file from
+## one to the other, changing nothing unless it's a GIF that we know
+## how to deal with, and if so do so.
+##
+## This is written rather verbosely for the sake of clarity... speed not
+## much of an issue for something like this, and the difference is minimal
+## anyway.
+##
+sub giftrans
+{
+ local(*IN, *OUT, $trans_index) = @_;
+ $trans_index = 0 if !defined $trans_index;
+ local($header, $color_table, $nextblock, $buffer) = ('') x 4;
+
+ ## The header looks like:
+ ## byte 0 - 5: "GIF89a" or "GIF87a"
+ ## byte 6, 7: width (low order first)
+ ## byte 8, 9: height (low order first)
+ ## byte 10: various flags
+ ## byte 11: background color index
+ ## byte 12: aspect ratio
+ sysread(IN, $header, 13) || die "sysread header: $!";
+ substr($header, 0, 6) = 'GIF89a' if substr($header,0,6) eq 'GIF87a';
+ print OUT $header;
+
+ if (substr($header, 0, 3) ne 'GIF') {
+ print STDERR "don't know input filetype, passing unchanged\n";
+ } else {
+ ##
+ ## Look at flags (8 bits): hi[MCCCSPPP]low
+ ## M = global colormap present?
+ ## CCC = bits/color/colormapentry - 1
+ ## S = color map sorted by importance?
+ ## PPP = bits/pixel - 1
+ ## therefore
+ ## Bits/pixel = PPP+1
+ ## Number of possible colors (entries in colormap): 2 ** (PPP+1)
+ ## : 1 << (PPP+1)
+ ## Size (bytes) of colormap: 3 * Number of possible colors
+ ## : 3 * (1 << (PPP+1))
+ ##
+ local($flags) = ord(substr($header, 10, 1));
+ local($has_global_colormap) = $flags & 0x80;
+
+ ## Copy over the colormap if need be.
+ if (!$has_global_colormap)
+ {
+ die "$0: picture has no colormap, so -rgb arg invalid\n"
+ if $select_via_rgb;
+ die "$0: no colormap, so any index except 0 or 1 makes no sense\n"
+ if $trans_index > 1;
+ } else {
+ local($bits_per_pixel) = 1 + ($flags & 0x07);
+ local($colormap_entries) = 1 << $bits_per_pixel;
+ local($color_tbl_size) = 3 * $colormap_entries;
+
+ sysread(IN, $color_table, $color_tbl_size) || die "sysread color";
+ print OUT $color_table;
+
+ if ($print_color_map || $select_via_rgb)
+ {
+ ## For each byte of each colormap's RGB triplit, we'll have
+ ## to mask off bits that aren't used when looking at the
+ ## color values.
+ local($bits_color_byte) = 1 + (($flags >> 4) & 0x07);
+ local($rgb_byte_mask) = (1 << $bits_color_byte) - 1;
+ local($r,$g,$b);
+
+ local($best_delta) = 1000; ## any big number ok
+ local(@delta, @r, @b, @g);
+
+ for ($i = 0; $i < $colormap_entries; $i++)
+ {
+ ($r, $g, $b) = unpack("CCC", substr($color_table, $i*3, 3));
+ $r &= $rgb_byte_mask;
+ $g &= $rgb_byte_mask;
+ $b &= $rgb_byte_mask;
+
+ if ($select_via_rgb) {
+ if ($r == $R && $g == $G && $b == $B) {
+ $select_via_rgb = 0;
+ $trans_index = $i;
+ print(STDERR "Found exact match (index #$i).\n");
+ } else {
+ $delta = ($r < $R ? $R - $r : $r - $R) +
+ ($g < $G ? $G - $g : $g - $G) +
+ ($b < $B ? $B - $b : $b - $B);
+ if ($delta < $best_delta) {
+ @delta = ($i);
+ @r = $r; @g = $g; @b = $b;
+ $best_delta = $delta;
+ } elsif ($delta == $best_delta) {
+ push(@delta, $i);
+ push(@r, $r); push(@g, $g); push(@b, $b);
+ }
+ }
+ }
+
+ printf(STDERR "%03d: %3d %3d %3d (x%02x x%02x x%02x)\n", $i,
+ $r, $g, $b, $r, $g, $b) if $print_color_map;
+ }
+
+ if ($select_via_rgb) {
+ ## Mmm, didn't find it. Use one of the close ones.
+ $trans_index = shift(@delta);
+ $r = shift(@r);
+ $g = shift(@g);
+ $b = shift(@b);
+
+ printf(STDERR "requested color not found, using index ".
+ "#%d: %3d %3d %3d (x%02x x%02x x%02x)\n",
+ $trans_index,
+ $r, $g, $b, $r, $g, $b);
+ if (@delta)
+ {
+ $count = @delta;
+ print(STDERR
+ "note: %d other entrie%s seem equally close:\n",
+ $count, $count == 1 ? "" : "s");
+ while (@delta) {
+ $index = shift(@delta);
+ $r = shift(@r);
+ $g = shift(@g);
+ $b = shift(@b);
+ printf(STDERR " index %03d: %3d %3d %3d ".
+ "(x%02x x%02x x%02x)\n",
+ $index, $r, $g, $b, $r, $g, $b);
+ }
+ }
+ }
+ }
+ }
+
+ ##
+ ## The next 8 bytes will either be an already-there graphic-extension
+ ## block, or something else that we'll not care about. In the latter
+ ## case, we'll add a graphic-extension block saying "color such-and-
+ ## such is transparent". If there's already one there, we'll just
+ ## ensure that it says that.
+ ##
+ sysread(IN, $nextblock, 8) || die "sysread nextblock";
+ local($extension, $label) = unpack('CC', $nextblock);
+ ## If extension is 0x21 and label is 0xf9, that's the magic tha means
+ ## there's already a graphic extension there.
+ if ($extension == 0x21 && $label == 0xf9) {
+ substr($nextblock, 3, 1) = pack('C', 1|substr($nextblock, 3, 1));
+ substr($nextblock, 6, 1) = pack('C', $trans_index);
+ } else {
+ print OUT pack('CCC CCCC C',
+ 0x21, ## magic: "Extension Introducer"
+ 0xf9, ## magic: "Graphic Control Label"
+ 4, ## bytes in block (between here and terminator)
+ 0x01, ## indicates that 'transparet index' is given
+ 0, 0, ## delay time.
+ $trans_index, ## index number of colormap entry
+ 0x00); ## terminator.
+ }
+ print OUT $nextblock;
+ }
+
+ ## Now just pass the rest of the file over unchanged.
+
+ print OUT $buffer while sysread(IN, $buffer, 4096);
+ close(IN);
+ close(OUT);
+}
+
+##
+## Change a name to a triplet of RGB values.
+## name and RGB data taken from the X11 lib/rgb.txt, with the
+## name regexe-compressed by me.
+##
+sub name2rgb
+{
+ local($_) = @_; ## name;
+ study;
+ %rgb = (
+ ' 0, 0, 0', 'black|gr[ae]y0',
+ ' 0, 0,128', 'navy([ \-]?blue)?',
+ ' 0, 0,139', 'blue4',
+ ' 0, 0,205', 'blue3|medium[ \-]?blue',
+ ' 0, 0,238', 'blue2',
+ ' 0, 0,255', 'blue1?',
+ ' 0,100, 0', 'dark[ \-]?green',
+ ' 0,104,139', 'deepskyblue4',
+ ' 0,134,139', 'turquoise4',
+ ' 0,139, 0', 'green4',
+ ' 0,139, 69', 'springgreen4',
+ ' 0,139,139', 'cyan4',
+ ' 0,154,205', 'deepskyblue3',
+ ' 0,178,238', 'deepskyblue2',
+ ' 0,191,255', 'deep( sky blue|-sky-blue|skyblue1?)',
+ ' 0,197,205', 'turquoise3',
+ ' 0,205, 0', 'green3',
+ ' 0,205,102', 'springgreen3',
+ ' 0,205,205', 'cyan3',
+ ' 0,206,209', 'dark[ \-]?turquoise',
+ ' 0,229,238', 'turquoise2',
+ ' 0,238, 0', 'green2',
+ ' 0,238,118', 'springgreen2',
+ ' 0,238,238', 'cyan2',
+ ' 0,245,255', 'turquoise1',
+ ' 0,250,154', 'medium[ \-]?spring[ \-]?green',
+ ' 0,255, 0', 'green1?',
+ ' 0,255,127', 'spring[ \-]?green1?',
+ ' 0,255,255', 'cyan1?',
+ ' 3, 3, 3', 'gr[ae]y1',
+ ' 5, 5, 5', 'gr[ae]y2',
+ ' 8, 8, 8', 'gr[ae]y3',
+ ' 10, 10, 10', 'gr[ae]y4',
+ ' 13, 13, 13', 'gr[ae]y5',
+ ' 15, 15, 15', 'gr[ae]y6',
+ ' 16, 78,139', 'dodgerblue4',
+ ' 18, 18, 18', 'gr[ae]y7',
+ ' 20, 20, 20', 'gr[ae]y8',
+ ' 23, 23, 23', 'gr[ae]y9',
+ ' 24,116,205', 'dodgerblue3',
+ ' 25, 25,112', 'midnight[ \-]?blue',
+ ' 26, 26, 26', 'gr[ae]y10',
+ ' 28, 28, 28', 'gr[ae]y11',
+ ' 28,134,238', 'dodgerblue2',
+ ' 30,144,255', 'dodger[ \-]?blue1?',
+ ' 31, 31, 31', 'gr[ae]y12',
+ ' 32,178,170', 'light[ \-]?sea[ \-]?green',
+ ' 33, 33, 33', 'gr[ae]y13',
+ ' 34,139, 34', 'forest[ \-]?green',
+ ' 36, 36, 36', 'gr[ae]y14',
+ ' 38, 38, 38', 'gr[ae]y15',
+ ' 39, 64,139', 'royalblue4',
+ ' 41, 41, 41', 'gr[ae]y16',
+ ' 43, 43, 43', 'gr[ae]y17',
+ ' 46, 46, 46', 'gr[ae]y18',
+ ' 46,139, 87', 'sea[ \-]?green4?',
+ ' 47, 79, 79', 'dark( slate gr[ae]|-slate-gr[ae]|slategr[ae])y',
+ ' 48, 48, 48', 'gr[ae]y19',
+ ' 50,205, 50', 'lime[ \-]?green',
+ ' 51, 51, 51', 'gr[ae]y20',
+ ' 54, 54, 54', 'gr[ae]y21',
+ ' 54,100,139', 'steelblue4',
+ ' 56, 56, 56', 'gr[ae]y22',
+ ' 58, 95,205', 'royalblue3',
+ ' 59, 59, 59', 'gr[ae]y23',
+ ' 60,179,113', 'medium[ \-]?sea[ \-]?green',
+ ' 61, 61, 61', 'gr[ae]y24',
+ ' 64, 64, 64', 'gr[ae]y25',
+ ' 64,224,208', 'turquoise',
+ ' 65,105,225', 'royal[ \-]?blue',
+ ' 66, 66, 66', 'gr[ae]y26',
+ ' 67,110,238', 'royalblue2',
+ ' 67,205,128', 'seagreen3',
+ ' 69, 69, 69', 'gr[ae]y27',
+ ' 69,139, 0', 'chartreuse4',
+ ' 69,139,116', 'aquamarine4',
+ ' 70,130,180', 'steel[ \-]?blue',
+ ' 71, 60,139', 'slateblue4',
+ ' 71, 71, 71', 'gr[ae]y28',
+ ' 72, 61,139', 'dark[ \-]?slate[ \-]?blue',
+ ' 72,118,255', 'royalblue1',
+ ' 72,209,204', 'medium[ \-]?turquoise',
+ ' 74, 74, 74', 'gr[ae]y29',
+ ' 74,112,139', 'skyblue4',
+ ' 77, 77, 77', 'gr[ae]y30',
+ ' 78,238,148', 'seagreen2',
+ ' 79, 79, 79', 'gr[ae]y31',
+ ' 79,148,205', 'steelblue3',
+ ' 82, 82, 82', 'gr[ae]y32',
+ ' 82,139,139', 'darkslategray4',
+ ' 83,134,139', 'cadetblue4',
+ ' 84, 84, 84', 'gr[ae]y33',
+ ' 84,139, 84', 'palegreen4',
+ ' 84,255,159', 'seagreen1',
+ ' 85, 26,139', 'purple4',
+ ' 85,107, 47', 'dark[ \-]?olive[ \-]?green',
+ ' 87, 87, 87', 'gr[ae]y34',
+ ' 89, 89, 89', 'gr[ae]y35',
+ ' 92, 92, 92', 'gr[ae]y36',
+ ' 92,172,238', 'steelblue2',
+ ' 93, 71,139', 'mediumpurple4',
+ ' 94, 94, 94', 'gr[ae]y37',
+ ' 95,158,160', 'cadet[ \-]?blue',
+ ' 96,123,139', 'lightskyblue4',
+ ' 97, 97, 97', 'gr[ae]y38',
+ ' 99, 99, 99', 'gr[ae]y39',
+ ' 99,184,255', 'steelblue1',
+ '100,149,237', 'cornflower[ \-]?blue',
+ '102,102,102', 'gr[ae]y40',
+ '102,139,139', 'paleturquoise4',
+ '102,205, 0', 'chartreuse3',
+ '102,205,170', 'aquamarine3|medium[ \-]?aquamarine',
+ '104, 34,139', 'darkorchid4',
+ '104,131,139', 'lightblue4',
+ '105, 89,205', 'slateblue3',
+ '105,105,105', 'dim( gr[ae]|-gr[ae]|gr[ae])y|gr[ae]y41',
+ '105,139, 34', 'olivedrab4',
+ '105,139,105', 'darkseagreen4',
+ '106, 90,205', 'slate[ \-]?blue',
+ '107,107,107', 'gr[ae]y42',
+ '107,142, 35', 'olive[ \-]?drab',
+ '108,123,139', 'slategray4',
+ '108,166,205', 'skyblue3',
+ '110,110,110', 'gr[ae]y43',
+ '110,123,139', 'lightsteelblue4',
+ '110,139, 61', 'darkolivegreen4',
+ '112,112,112', 'gr[ae]y44',
+ '112,128,144', 'slate( gr[ae]|-gr[ae]|gr[ae])y',
+ '115,115,115', 'gr[ae]y45',
+ '117,117,117', 'gr[ae]y46',
+ '118,238, 0', 'chartreuse2',
+ '118,238,198', 'aquamarine2',
+ '119,136,153', 'light( slate gr[ae]|-slate-gr[ae]|slategr[ae])y',
+ '120,120,120', 'gr[ae]y47',
+ '121,205,205', 'darkslategray3',
+ '122, 55,139', 'mediumorchid4',
+ '122,103,238', 'slateblue2',
+ '122,122,122', 'gr[ae]y48',
+ '122,139,139', 'lightcyan4',
+ '122,197,205', 'cadetblue3',
+ '123,104,238', 'medium[ \-]?slate[ \-]?blue',
+ '124,205,124', 'palegreen3',
+ '124,252, 0', 'lawn[ \-]?green',
+ '125, 38,205', 'purple3',
+ '125,125,125', 'gr[ae]y49',
+ '126,192,238', 'skyblue2',
+ '127,127,127', 'gr[ae]y50',
+ '127,255, 0', 'chartreuse1?',
+ '127,255,212', 'aquamarine1?',
+ '130,130,130', 'gr[ae]y51',
+ '131,111,255', 'slateblue1',
+ '131,139,131', 'honeydew4',
+ '131,139,139', 'azure4',
+ '132,112,255', 'light[ \-]?slate[ \-]?blue',
+ '133,133,133', 'gr[ae]y52',
+ '135,135,135', 'gr[ae]y53',
+ '135,206,235', 'sky[ \-]?blue',
+ '135,206,250', 'light[ \-]?sky[ \-]?blue',
+ '135,206,255', 'skyblue1',
+ '137,104,205', 'mediumpurple3',
+ '138, 43,226', 'blue[ \-]?violet',
+ '138,138,138', 'gr[ae]y54',
+ '139, 0, 0', 'red4',
+ '139, 0,139', 'magenta4',
+ '139, 10, 80', 'deeppink4',
+ '139, 26, 26', 'firebrick4',
+ '139, 28, 98', 'maroon4',
+ '139, 34, 82', 'violetred4',
+ '139, 35, 35', 'brown4',
+ '139, 37, 0', 'orangered4',
+ '139, 54, 38', 'tomato4',
+ '139, 58, 58', 'indianred4',
+ '139, 58, 98', 'hotpink4',
+ '139, 62, 47', 'coral4',
+ '139, 69, 0', 'darkorange4',
+ '139, 69, 19', 'chocolate4|saddle[ \-]?brown',
+ '139, 71, 38', 'sienna4',
+ '139, 71, 93', 'palevioletred4',
+ '139, 71,137', 'orchid4',
+ '139, 76, 57', 'salmon4',
+ '139, 87, 66', 'lightsalmon4',
+ '139, 90, 0', 'orange4',
+ '139, 90, 43', 'tan4',
+ '139, 95,101', 'lightpink4',
+ '139, 99,108', 'pink4',
+ '139,101, 8', 'darkgoldenrod4',
+ '139,102,139', 'plum4',
+ '139,105, 20', 'goldenrod4',
+ '139,105,105', 'rosybrown4',
+ '139,115, 85', 'burlywood4',
+ '139,117, 0', 'gold4',
+ '139,119,101', 'peachpuff4',
+ '139,121, 94', 'navajowhite4',
+ '139,123,139', 'thistle4',
+ '139,125,107', 'bisque4',
+ '139,125,123', 'mistyrose4',
+ '139,126,102', 'wheat4',
+ '139,129, 76', 'lightgoldenrod4',
+ '139,131,120', 'antiquewhite4',
+ '139,131,134', 'lavenderblush4',
+ '139,134, 78', 'khaki4',
+ '139,134,130', 'seashell4',
+ '139,136,120', 'cornsilk4',
+ '139,137,112', 'lemonchiffon4',
+ '139,137,137', 'snow4',
+ '139,139, 0', 'yellow4',
+ '139,139,122', 'lightyellow4',
+ '139,139,131', 'ivory4',
+ '140,140,140', 'gr[ae]y55',
+ '141,182,205', 'lightskyblue3',
+ '141,238,238', 'darkslategray2',
+ '142,229,238', 'cadetblue2',
+ '143,143,143', 'gr[ae]y56',
+ '143,188,143', 'dark[ \-]?sea[ \-]?green',
+ '144,238,144', 'palegreen2',
+ '145, 44,238', 'purple2',
+ '145,145,145', 'gr[ae]y57',
+ '147,112,219', 'medium[ \-]?purple',
+ '148, 0,211', 'dark[ \-]?violet',
+ '148,148,148', 'gr[ae]y58',
+ '150,150,150', 'gr[ae]y59',
+ '150,205,205', 'paleturquoise3',
+ '151,255,255', 'darkslategray1',
+ '152,245,255', 'cadetblue1',
+ '152,251,152', 'pale[ \-]?green',
+ '153, 50,204', 'dark[ \-]?orchid',
+ '153,153,153', 'gr[ae]y60',
+ '154, 50,205', 'darkorchid3',
+ '154,192,205', 'lightblue3',
+ '154,205, 50', 'olivedrab3|yellow[ \-]?green',
+ '154,255,154', 'palegreen1',
+ '155, 48,255', 'purple1',
+ '155,205,155', 'darkseagreen3',
+ '156,156,156', 'gr[ae]y61',
+ '158,158,158', 'gr[ae]y62',
+ '159,121,238', 'mediumpurple2',
+ '159,182,205', 'slategray3',
+ '160, 32,240', 'purple',
+ '160, 82, 45', 'sienna',
+ '161,161,161', 'gr[ae]y63',
+ '162,181,205', 'lightsteelblue3',
+ '162,205, 90', 'darkolivegreen3',
+ '163,163,163', 'gr[ae]y64',
+ '164,211,238', 'lightskyblue2',
+ '165, 42, 42', 'brown',
+ '166,166,166', 'gr[ae]y65',
+ '168,168,168', 'gr[ae]y66',
+ '171,130,255', 'mediumpurple1',
+ '171,171,171', 'gr[ae]y67',
+ '173,173,173', 'gr[ae]y68',
+ '173,216,230', 'light[ \-]?blue',
+ '173,255, 47', 'green[ \-]?yellow',
+ '174,238,238', 'paleturquoise2',
+ '175,238,238', 'pale[ \-]?turquoise',
+ '176, 48, 96', 'maroon',
+ '176,176,176', 'gr[ae]y69',
+ '176,196,222', 'light[ \-]?steel[ \-]?blue',
+ '176,224,230', 'powder[ \-]?blue',
+ '176,226,255', 'lightskyblue1',
+ '178, 34, 34', 'firebrick',
+ '178, 58,238', 'darkorchid2',
+ '178,223,238', 'lightblue2',
+ '179,179,179', 'gr[ae]y70',
+ '179,238, 58', 'olivedrab2',
+ '180, 82,205', 'mediumorchid3',
+ '180,205,205', 'lightcyan3',
+ '180,238,180', 'darkseagreen2',
+ '181,181,181', 'gr[ae]y71',
+ '184,134, 11', 'dark[ \-]?goldenrod',
+ '184,184,184', 'gr[ae]y72',
+ '185,211,238', 'slategray2',
+ '186, 85,211', 'medium[ \-]?orchid',
+ '186,186,186', 'gr[ae]y73',
+ '187,255,255', 'paleturquoise1',
+ '188,143,143', 'rosy[ \-]?brown',
+ '188,210,238', 'lightsteelblue2',
+ '188,238,104', 'darkolivegreen2',
+ '189,183,107', 'dark[ \-]?khaki',
+ '189,189,189', 'gr[ae]y74',
+ '190,190,190', 'gr[ae]y',
+ '191, 62,255', 'darkorchid1',
+ '191,191,191', 'gr[ae]y75',
+ '191,239,255', 'lightblue1',
+ '192,255, 62', 'olivedrab1',
+ '193,205,193', 'honeydew3',
+ '193,205,205', 'azure3',
+ '193,255,193', 'darkseagreen1',
+ '194,194,194', 'gr[ae]y76',
+ '196,196,196', 'gr[ae]y77',
+ '198,226,255', 'slategray1',
+ '199, 21,133', 'medium[ \-]?violet[ \-]?red',
+ '199,199,199', 'gr[ae]y78',
+ '201,201,201', 'gr[ae]y79',
+ '202,225,255', 'lightsteelblue1',
+ '202,255,112', 'darkolivegreen1',
+ '204,204,204', 'gr[ae]y80',
+ '205, 0, 0', 'red3',
+ '205, 0,205', 'magenta3',
+ '205, 16,118', 'deeppink3',
+ '205, 38, 38', 'firebrick3',
+ '205, 41,144', 'maroon3',
+ '205, 50,120', 'violetred3',
+ '205, 51, 51', 'brown3',
+ '205, 55, 0', 'orangered3',
+ '205, 79, 57', 'tomato3',
+ '205, 85, 85', 'indianred3',
+ '205, 91, 69', 'coral3',
+ '205, 92, 92', 'indian[ \-]?red',
+ '205, 96,144', 'hotpink3',
+ '205,102, 0', 'darkorange3',
+ '205,102, 29', 'chocolate3',
+ '205,104, 57', 'sienna3',
+ '205,104,137', 'palevioletred3',
+ '205,105,201', 'orchid3',
+ '205,112, 84', 'salmon3',
+ '205,129, 98', 'lightsalmon3',
+ '205,133, 0', 'orange3',
+ '205,133, 63', 'peru|tan3',
+ '205,140,149', 'lightpink3',
+ '205,145,158', 'pink3',
+ '205,149, 12', 'darkgoldenrod3',
+ '205,150,205', 'plum3',
+ '205,155, 29', 'goldenrod3',
+ '205,155,155', 'rosybrown3',
+ '205,170,125', 'burlywood3',
+ '205,173, 0', 'gold3',
+ '205,175,149', 'peachpuff3',
+ '205,179,139', 'navajowhite3',
+ '205,181,205', 'thistle3',
+ '205,183,158', 'bisque3',
+ '205,183,181', 'mistyrose3',
+ '205,186,150', 'wheat3',
+ '205,190,112', 'lightgoldenrod3',
+ '205,192,176', 'antiquewhite3',
+ '205,193,197', 'lavenderblush3',
+ '205,197,191', 'seashell3',
+ '205,198,115', 'khaki3',
+ '205,200,177', 'cornsilk3',
+ '205,201,165', 'lemonchiffon3',
+ '205,201,201', 'snow3',
+ '205,205, 0', 'yellow3',
+ '205,205,180', 'lightyellow3',
+ '205,205,193', 'ivory3',
+ '207,207,207', 'gr[ae]y81',
+ '208, 32,144', 'violet[ \-]?red',
+ '209, 95,238', 'mediumorchid2',
+ '209,209,209', 'gr[ae]y82',
+ '209,238,238', 'lightcyan2',
+ '210,105, 30', 'chocolate',
+ '210,180,140', 'tan',
+ '211,211,211', 'light( gr[ae]|-gr[ae]|gr[ae])y',
+ '212,212,212', 'gr[ae]y83',
+ '214,214,214', 'gr[ae]y84',
+ '216,191,216', 'thistle',
+ '217,217,217', 'gr[ae]y85',
+ '218,112,214', 'orchid',
+ '218,165, 32', 'goldenrod',
+ '219,112,147', 'pale[ \-]?violet[ \-]?red',
+ '219,219,219', 'gr[ae]y86',
+ '220,220,220', 'gainsboro',
+ '221,160,221', 'plum',
+ '222,184,135', 'burlywood',
+ '222,222,222', 'gr[ae]y87',
+ '224,102,255', 'mediumorchid1',
+ '224,224,224', 'gr[ae]y88',
+ '224,238,224', 'honeydew2',
+ '224,238,238', 'azure2',
+ '224,255,255', 'light[ \-]?cyan1?',
+ '227,227,227', 'gr[ae]y89',
+ '229,229,229', 'gr[ae]y90',
+ '230,230,250', 'lavender',
+ '232,232,232', 'gr[ae]y91',
+ '233,150,122', 'dark[ \-]?salmon',
+ '235,235,235', 'gr[ae]y92',
+ '237,237,237', 'gr[ae]y93',
+ '238, 0, 0', 'red2',
+ '238, 0,238', 'magenta2',
+ '238, 18,137', 'deeppink2',
+ '238, 44, 44', 'firebrick2',
+ '238, 48,167', 'maroon2',
+ '238, 58,140', 'violetred2',
+ '238, 59, 59', 'brown2',
+ '238, 64, 0', 'orangered2',
+ '238, 92, 66', 'tomato2',
+ '238, 99, 99', 'indianred2',
+ '238,106, 80', 'coral2',
+ '238,106,167', 'hotpink2',
+ '238,118, 0', 'darkorange2',
+ '238,118, 33', 'chocolate2',
+ '238,121, 66', 'sienna2',
+ '238,121,159', 'palevioletred2',
+ '238,122,233', 'orchid2',
+ '238,130, 98', 'salmon2',
+ '238,130,238', 'violet',
+ '238,149,114', 'lightsalmon2',
+ '238,154, 0', 'orange2',
+ '238,154, 73', 'tan2',
+ '238,162,173', 'lightpink2',
+ '238,169,184', 'pink2',
+ '238,173, 14', 'darkgoldenrod2',
+ '238,174,238', 'plum2',
+ '238,180, 34', 'goldenrod2',
+ '238,180,180', 'rosybrown2',
+ '238,197,145', 'burlywood2',
+ '238,201, 0', 'gold2',
+ '238,203,173', 'peachpuff2',
+ '238,207,161', 'navajowhite2',
+ '238,210,238', 'thistle2',
+ '238,213,183', 'bisque2',
+ '238,213,210', 'mistyrose2',
+ '238,216,174', 'wheat2',
+ '238,220,130', 'lightgoldenrod2',
+ '238,221,130', 'light[ \-]?goldenrod',
+ '238,223,204', 'antiquewhite2',
+ '238,224,229', 'lavenderblush2',
+ '238,229,222', 'seashell2',
+ '238,230,133', 'khaki2',
+ '238,232,170', 'pale[ \-]?goldenrod',
+ '238,232,205', 'cornsilk2',
+ '238,233,191', 'lemonchiffon2',
+ '238,233,233', 'snow2',
+ '238,238, 0', 'yellow2',
+ '238,238,209', 'lightyellow2',
+ '238,238,224', 'ivory2',
+ '240,128,128', 'light[ \-]?coral',
+ '240,230,140', 'khaki',
+ '240,240,240', 'gr[ae]y94',
+ '240,248,255', 'alice[ \-]?blue',
+ '240,255,240', 'honeydew1?',
+ '240,255,255', 'azure1?',
+ '242,242,242', 'gr[ae]y95',
+ '244,164, 96', 'sandy[ \-]?brown',
+ '245,222,179', 'wheat',
+ '245,245,220', 'beige',
+ '245,245,245', 'gr[ae]y96|white[ \-]?smoke',
+ '245,255,250', 'mint[ \-]?cream',
+ '247,247,247', 'gr[ae]y97',
+ '248,248,255', 'ghost[ \-]?white',
+ '250,128,114', 'salmon',
+ '250,235,215', 'antique[ \-]?white',
+ '250,240,230', 'linen',
+ '250,250,210', 'light[ \-]?goldenrod[ \-]?yellow',
+ '250,250,250', 'gr[ae]y98',
+ '252,252,252', 'gr[ae]y99',
+ '253,245,230', 'old[ \-]?lace',
+ '255, 0, 0', 'red1?',
+ '255, 0,255', 'magenta1?',
+ '255, 20,147', 'deep[ \-]?pink1?',
+ '255, 48, 48', 'firebrick1',
+ '255, 52,179', 'maroon1',
+ '255, 62,150', 'violetred1',
+ '255, 64, 64', 'brown1',
+ '255, 69, 0', 'orange[ \-]?red1?',
+ '255, 99, 71', 'tomato1?',
+ '255,105,180', 'hot[ \-]?pink',
+ '255,106,106', 'indianred1',
+ '255,110,180', 'hotpink1',
+ '255,114, 86', 'coral1',
+ '255,127, 0', 'darkorange1',
+ '255,127, 36', 'chocolate1',
+ '255,127, 80', 'coral',
+ '255,130, 71', 'sienna1',
+ '255,130,171', 'palevioletred1',
+ '255,131,250', 'orchid1',
+ '255,140, 0', 'dark[ \-]?orange',
+ '255,140,105', 'salmon1',
+ '255,160,122', 'light[ \-]?salmon1?',
+ '255,165, 0', 'orange1?',
+ '255,165, 79', 'tan1',
+ '255,174,185', 'lightpink1',
+ '255,181,197', 'pink1',
+ '255,182,193', 'light[ \-]?pink',
+ '255,185, 15', 'darkgoldenrod1',
+ '255,187,255', 'plum1',
+ '255,192,203', 'pink',
+ '255,193, 37', 'goldenrod1',
+ '255,193,193', 'rosybrown1',
+ '255,211,155', 'burlywood1',
+ '255,215, 0', 'gold1?',
+ '255,218,185', 'peach[ \-]?puff1?',
+ '255,222,173', 'navajo[ \-]?white1?',
+ '255,225,255', 'thistle1',
+ '255,228,181', 'moccasin',
+ '255,228,196', 'bisque1?',
+ '255,228,225', 'misty[ \-]?rose1?',
+ '255,231,186', 'wheat1',
+ '255,235,205', 'blanched[ \-]?almond',
+ '255,236,139', 'lightgoldenrod1',
+ '255,239,213', 'papaya[ \-]?whip',
+ '255,239,219', 'antiquewhite1',
+ '255,240,245', 'lavender[ \-]?blush1?',
+ '255,245,238', 'seashell1?',
+ '255,246,143', 'khaki1',
+ '255,248,220', 'cornsilk1?',
+ '255,250,205', 'lemon[ \-]?chiffon1?',
+ '255,250,240', 'floral[ \-]?white',
+ '255,250,250', 'snow1?',
+ '255,255, 0', 'yellow1?',
+ '255,255,224', 'light[ \-]?yellow1?',
+ '255,255,240', 'ivory1?',
+ '255,255,255', 'gr[ae]y100|white',
+ );
+ while (($val, $regex) = each %rgb) {
+ return split(',', $val) if m/^$regex$/i;
+ }
+
+}
+__END__
diff --git a/webroot/img/view_icon.gif b/webroot/img/view_icon.gif
new file mode 100755
index 0000000..5657341
Binary files /dev/null and b/webroot/img/view_icon.gif differ
diff --git a/webroot/img/voter.jpg b/webroot/img/voter.jpg
new file mode 100755
index 0000000..02493d9
Binary files /dev/null and b/webroot/img/voter.jpg differ
diff --git a/webroot/img/votercontact.jpg b/webroot/img/votercontact.jpg
new file mode 100755
index 0000000..ffd57c7
Binary files /dev/null and b/webroot/img/votercontact.jpg differ
diff --git a/webroot/img/web.jpg b/webroot/img/web.jpg
new file mode 100755
index 0000000..9b586b3
Binary files /dev/null and b/webroot/img/web.jpg differ
diff --git a/webroot/img/webstrategy.jpg b/webroot/img/webstrategy.jpg
new file mode 100755
index 0000000..c2441c9
Binary files /dev/null and b/webroot/img/webstrategy.jpg differ
diff --git a/webroot/index.php b/webroot/index.php
new file mode 100755
index 0000000..8a6f66e
--- /dev/null
+++ b/webroot/index.php
@@ -0,0 +1,96 @@
+<?php
+/* SVN FILE: $Id: index.php 72 2006-06-06 22:45:46Z nate $ */
+
+/**
+ * Short description for file.
+ *
+ * Long description for file
+ *
+ * PHP versions 4 and 5
+ *
+ * CakePHP : Rapid Development Framework <http://www.cakephp.org/>
+ * Copyright (c) 2006, Cake Software Foundation, Inc.
+ * 1785 E. Sahara Avenue, Suite 490-204
+ * Las Vegas, Nevada 89104
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @filesource
+ * @copyright Copyright (c) 2006, Cake Software Foundation, Inc.
+ * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP Project
+ * @package cake
+ * @subpackage cake.app.webroot
+ * @since CakePHP v 0.2.9
+ * @version $Revision: 72 $
+ * @modifiedby $LastChangedBy: nate $
+ * @lastmodified $Date: 2006-06-06 18:45:46 -0400 (Tue, 06 Jun 2006) $
+ * @license http://www.opensource.org/licenses/mit-license.php The MIT License
+ */
+
+/**
+ * Do not change
+ */
+if (!defined('DS')) {
+ define('DS', DIRECTORY_SEPARATOR);
+}
+
+/**
+ * These defines should only be edited if you have cake installed in
+ * a directory layout other than the way it is distributed.
+ * Each define has a commented line of code that explains what you would change.
+ *
+ */
+if (!defined('ROOT')) {
+ define('ROOT', dirname(dirname(dirname(__FILE__))));
+}
+
+if (!defined('APP_DIR')) {
+ define ('APP_DIR', basename(dirname(dirname(__FILE__))));
+}
+
+/**
+ * This only needs to be changed if the cake installed libs are located
+ * outside of the distributed directory structure.
+ */
+if (!defined('CAKE_CORE_INCLUDE_PATH')) {
+ define('CAKE_CORE_INCLUDE_PATH', '/Library/WebServer/Documents/cake/1/1.2.x.x');
+ // define('CAKE_CORE_INCLUDE_PATH', ROOT);
+}
+
+
+///////////////////////////////
+//DO NOT EDIT BELOW THIS LINE//
+///////////////////////////////
+
+if (!defined('WEBROOT_DIR')) {
+ define ('WEBROOT_DIR', basename(dirname(__FILE__)));
+}
+
+if (!defined('WWW_ROOT')) {
+ define('WWW_ROOT', dirname(__FILE__).DS);
+}
+
+if(!defined('CORE_PATH')) {
+ if(function_exists('ini_set')) {
+ ini_set('include_path',ini_get('include_path').PATH_SEPARATOR.CAKE_CORE_INCLUDE_PATH.PATH_SEPARATOR.ROOT.DS.APP_DIR.DS);
+ define('APP_PATH', null);
+ define('CORE_PATH', null);
+ } else {
+ define('APP_PATH', ROOT.DS.APP_DIR.DS);
+ define('CORE_PATH', CAKE_CORE_INCLUDE_PATH.DS);
+ }
+}
+
+require CORE_PATH.'cake'.DS.'bootstrap.php';
+
+if(!(isset($_GET['url']) && $_GET['url'] === 'favicon.ico')) {
+ $Dispatcher= new Dispatcher ();
+ $Dispatcher->dispatch($url);
+}
+
+if (Configure::read('debug') > 0) {
+ echo "<!-- ". round(getMicrotime() - $TIME_START, 4) ."s -->";
+}
+
+?>
\ No newline at end of file
diff --git a/webroot/js/application.js b/webroot/js/application.js
new file mode 100755
index 0000000..6553ac0
--- /dev/null
+++ b/webroot/js/application.js
@@ -0,0 +1,322 @@
+function trace(obj) {
+ data = '';
+ c = 0;
+ while (obj.parentNode && c < 1000) {
+ data = (obj.id || obj.toString()) + (data != '' ? '.' : '') + data;
+ obj = obj.parentNode;
+ c++;
+ }
+ alert(data);
+}
+
+if (!document.getHeight) {
+ document.getHeight = function() {
+ return self.innerHeight || document.documentElement.clientHeight ||
+ document.body.clientHeight || '500';
+ }
+}
+
+if (!document.getWidth) {
+ document.getWidth = function() {
+ return document.documentElement.clientWidth ||
+ self.innerWidth || document.body.clientWidth || '500';
+ }
+}
+
+String.prototype.trim = function() {
+ var x = this;
+ x = x.replace(/^\s*(.*)/, "$1");
+ x = x.replace(/(.*?)\s*$/, "$1");
+ return x;
+}
+
+// Disable text selection (interferes with draggable elements)
+
+if(/MSIE/.test(navigator.userAgent)) {
+ document.onselectstart = function(event) {
+
+ if (typeof event == 'undefined') {
+ event = window.event;
+ }
+
+ if (event.srcElement) {
+ if(!/input|textarea/i.test(event.srcElement.tagName)) {
+ return false;
+ }
+ } else {
+ return (event) ? true : false;
+ }
+ };
+}/* else { // assume DOM
+ document.onmousedown = function(event) {
+ if (event) {
+ e = Event.element(event);
+ if(!/input|textarea/i.test(e.tagName)) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ };
+}*/
+
+
+
+Object.extend(Form, {
+
+ __bindings : [],
+
+ __timer : new PeriodicalExecuter(function() { Form.__update }, 1),
+
+ bind : function (form, object, model, options) {
+
+ if (!form || !object) {
+ return null;
+ }
+
+ this.options = {
+ frequency : 2
+ }
+ Object.extend(this.options, options || {});
+
+ ids = [];
+ validIds = [];
+ validProperties = [];
+ test = [];
+ this.options.frequency = 2;
+ elements = Form.getElements(form);
+
+ for (var i = 0; i < elements.length; i++) {
+ if (elements[i].id && elements[i].id != '' && elements[i].id != null) {
+ ids[i] = elements[i].id;
+ }
+ }
+
+ for (n in object) {
+ fieldName = model.capitalize() + n.replace('_', '-').camelize().capitalize();
+ if (ids.indexOf(fieldName) != -1 && fieldName) {
+ validIds.push(fieldName);
+ validProperties.push(n);
+ } else {
+ test.push(fieldName + ' : ' + n);
+ }
+ }
+
+ var newBinding = {
+ form : $(form),
+ object : object,
+ cache : {},
+ formCache : Form.serialize($(form)),
+ model : model,
+ fields : validIds,
+ properties : validProperties,
+ valid : true
+ }
+
+ this.unbind(form);
+ this.__populateForm(newBinding);
+
+ //newBinding.watch = new PeriodicalExecuter(this.__update, this.options.frequency);
+ //newBinding.formWatch = new Form.Observer($(form), 2, function(form) { Form.invalidate(form); }),
+
+ // DEBUG
+ this.__bindings = [];
+
+ this.__populateForm(newBinding);
+ this.__bindings.push(newBinding);
+ return newBinding;
+ },
+
+ invalidate : function (form) {
+ form = $(form);
+
+ for (var i = 0; i < this.__bindings.length; i++) {
+ if (this.__bindings[i].form == form) {
+ if (this.__bindings[i].formCache != Form.serialize(form)) {
+ this.__bindings[i].valid = false;
+ this.__bindings[i].formCache = Form.serialize(form);
+ }
+ }
+ }
+ this.__update();
+ },
+
+ notify : function (obj) {
+ obj = $(obj);
+ if (obj.tagName && obj.tagName.toLowerCase() == 'form') {
+ for (var i = 0; i < this.__bindings.length; i++) {
+ if (this.__bindings[i].form == obj) {
+ this.__populateObject(this.__bindings[i]);
+ if (typeof this.__bindings[i].object.notify == 'function') {
+ this.__bindings[i].object.notify();
+ }
+ return true;
+ }
+ }
+ } else {
+ for (var i = 0; i < this.__bindings.length; i++) {
+ if (this.__bindings[i].object == obj) {
+ this.__populateForm(this.__bindings[i]);
+ return true;
+ }
+ }
+ }
+ return false;
+ },
+
+ __update : function () {
+ for (var i = 0; i < Form.__bindings.length; i++) {
+ bind = Form.__bindings[i];
+ if (!bind.valid) {
+ Form.__populateObject(bind);
+ bind.cache = bind.object;
+ bind.valid = true;
+
+ if (bind.object.notify) {
+ bind.object.notify();
+ }
+ }
+ if (!Object.compare(bind.object, bind.cache)) {
+ //Form.__populateForm(bind);
+ for (n in bind.object) {
+ bind.cache[n] = bind.object[n];
+ }
+
+ if (bind.object.notify) {
+ bind.object.notify();
+ }
+ }
+ }
+ },
+
+ __populateForm : function (binding) {
+ for (var i = 0; i < binding.fields.length; i++) {
+ value = binding.object[binding.properties[i]];
+ if (!value) {
+ value = "";
+ }
+ if (value instanceof Date) {
+ value = value.format('Y-m-d H:i:s');
+ }
+
+ Form.Element.setValue($(binding.fields[i]), value.toString());
+ /*if (typeof $(binding.fields[i]).value != 'undefined') {
+ $(binding.fields[i]).value = value.toString();
+ } else {
+ // TODO : Fix me
+
+ }*/
+ }
+ },
+
+ __populateObject : function (binding) {
+ for (var i = 0; i < binding.properties.length; i++) {
+ if (binding.object[binding.properties[i]]) {
+ if (binding.object[binding.properties[i]] instanceof Date) {
+ binding.object[binding.properties[i]].set($F(binding.fields[i]));
+ } else {
+ binding.object[binding.properties[i]] = $F(binding.fields[i]);
+ }
+ }
+ }
+ },
+
+ unbind : function (form) {
+ form = $(form);
+ remove = [];
+
+ for (var i = 0; i < this.__bindings.length; i++) {
+ if (this.__bindings[i].form == form) {
+ b = this.__bindings[i];
+ //b.watch.currentlyExecuting = true;
+ //b.formWatch.currentlyExecuting = true;
+ remove.push(i);
+ }
+ }
+
+ for (i = remove.length; i >= 0; i--) {
+ this.__bindings = this.__bindings.splice(remove[i], 1);
+ }
+ }
+});
+
+Object.extend(Form.Element, {
+
+ setValue : function(element, value) {
+ element = $(element);
+
+ switch(element.tagName.toLowerCase()) {
+ case 'select':
+ for (var i = 0; i < element.options.length; i++) {
+ if (element.options[i].value == value) {
+ element.selectedIndex = i;
+ return;
+ }
+ }
+ break;
+ case 'input':
+ case 'hidden':
+ case 'password':
+ element.value = value;
+ break;
+ }
+ }
+});
+
+Object.compare = function(object) {
+ for (n in this) {
+ if (typeof object[n] == 'undefined') {
+ return false;
+ }
+ if (object[n] != this[n]) {
+ return false;
+ }
+ }
+ return true;
+};
+
+Object.serialize = function(obj) {
+ var data = [];
+ for (n in obj) {
+ if (obj[n] == null) {
+ data.push(n + '=');
+ } else if (obj[n] instanceof Date) {
+ data.push(n + '=' + encodeURIComponent(obj[n].format('Y-m-d H:i:s')));
+ } else if (obj[n].toString) {
+ data.push(n + '=' + encodeURIComponent(obj[n].toString()));
+ }
+ }
+ return data.join('&');
+};
+
+Object.extend(String.prototype, {
+
+ toProperCase : function() {
+ return this.toLowerCase().replace(/^(.)|\s(.)/g, function($1) { return $1.toUpperCase(); });
+ },
+
+ capitalize : function() {
+ return this.substr(0, 1).toUpperCase() + this.substr(1, this.length);
+ }
+});
+
+Object.extend(Element, {
+
+ getPosition : function(elem) {
+ elem = $(elem);
+ pElem = (elem.parentElement || elem.parentNode);
+
+ if (!pElem || pElem == window) {
+ return 0;
+ }
+
+ if (typeof pElem.childNodes != 'undefined') {
+ for (var i = 0; i < pElem.childNodes.length; i++) {
+ if (pElem.childNodes[i] == elem) {
+ return i;
+ }
+ }
+ }
+ return 0;
+ }
+});
diff --git a/webroot/js/bindings.js b/webroot/js/bindings.js
new file mode 100755
index 0000000..b29df46
--- /dev/null
+++ b/webroot/js/bindings.js
@@ -0,0 +1,124 @@
+Calendar.selectEvent = function(evt) {
+ //Events.select(evt);
+}
+
+Calendar.click = function(day) {
+ day = $(day);
+ if (Element.hasClassName(day, 'day') || day.id.indexOf('day_') == 0) {
+ Position.clone(day, $('toolbar'), {setHeight: false, offsetTop: -25, offsetLeft: -15});
+ Element.show('dayTools');
+ Element.hide('eventTools');
+ $('toolbar').style.width = '55px';
+ Element.show('toolbar');
+ }
+}
+
+Calendar.update = function(evt) {
+ Events.save(evt);
+}
+
+var Events = {
+
+ editCounder : 0,
+
+ add : function(day) {
+ this.save(Calendar.addEvent(day, null));
+ },
+
+ edit : function(evt) {
+ if (evt == null && this.queue) {
+ evt = this.queue;
+ }
+
+ if (typeof evt.id != 'undefined') {
+ if (evt.id != null) {
+ new Ajax.Updater('eventForm', Calendar.base + '/calendar/edit/' + evt.id, {
+ onComplete : function() { Effect.SlideDown('eventForm'); },
+ evalScripts : true
+ });
+ this.editCounter = 0;
+ }
+ } else {
+ this.editCounter++;
+ if (this.editCounter < 30) {
+ setTimeout('Events.edit(null);', 500);
+ }
+ }
+ },
+
+ save : function(evt) {
+ // Is this a newly-created event?
+ if (evt.id == null && evt.__id != null) {
+ Events.create.push(evt);
+
+ new Ajax.Request(
+ Calendar.base + '/calendar/save', {
+ parameters : Object.serialize(evt),
+ onComplete : function(response) {
+ Events.update(response.responseText);
+ }
+ }
+ );
+ } else {
+ new Ajax.Request(Calendar.base + '/calendar/save', { parameters : Object.serialize(evt) });
+ }
+ },
+
+ update : function(response) {
+ eval('var data = ' + response);
+ if (typeof data.__id != 'undefined') {
+ for (var i = 0; i < Calendar.Events.items.length; i++) {
+ if (data.__id + '' == Calendar.Events.items[i].__id + '') {
+ Calendar.Events.set(Calendar.Events.items[i], data);
+ Calendar.Events.items[i].__id = null;
+ }
+ }
+ } else {
+ for (var i = 0; i < Calendar.Events.items.length; i++) {
+ if (data.id.toString().trim().toInt() == Calendar.Events.items[i].id.toString().trim().toInt()) {
+ Calendar.Events.set(Calendar.Events.items[i], data);
+ }
+ }
+ }
+ },
+
+ click : function(evt) {
+ if (evt.editable) {
+ if (evt.element && evt.element.length > 0) {
+ Position.clone(evt.element[0], $('toolbar'), {setHeight: false, setWidth: false, offsetTop: -25, offsetLeft: -15});
+ Element.hide('dayTools');
+ Element.show('eventTools');
+ $('toolbar').style.width = '55px';
+ Element.show('toolbar');
+ }
+ } else {
+ Element.hide('toolbar');
+ }
+ return true;
+ },
+
+ view : function(day) {
+ var date = Calendar.getDateFromElement($(day)).format('Y/m/d');
+ new Ajax.Updater('calendar', Calendar.base + '/calendar/index/' + date, {evalScripts:true});
+ },
+
+ remove : function(evt) {
+ if (!confirm('Are you sure you want to delete the event \"' + evt.title + '\"')) {
+ return false;
+ }
+
+ for (var i = evt.element.length - 1; i > -1; i--) {
+ Element.remove(evt.element[i]);
+ }
+ for (var i = 0; i < Calendar.Events.items.length; i++) {
+ if (Calendar.Events.items[i] == evt) {
+ rem = Calendar.Events.items.splice(i, 1);
+ new Ajax.Request(Calendar.base + '/calendar/delete/' + rem[0].id);
+ Calendar.layout();
+ break;
+ }
+ }
+ },
+
+ create : []
+};
\ No newline at end of file
diff --git a/webroot/js/builder.js b/webroot/js/builder.js
new file mode 100755
index 0000000..5b15ba9
--- /dev/null
+++ b/webroot/js/builder.js
@@ -0,0 +1,101 @@
+// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//
+// See scriptaculous.js for full license.
+
+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 + "></" + elementName + ">";
+ } catch(e) {}
+ var element = parentElement.firstChild || null;
+
+ // see if browser added wrapping tags
+ if(element && (element.tagName != 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)) {
+ 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 + "></" + elementName + ">";
+ } 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 != 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);
+ },
+ _attributes: function(attributes) {
+ var attrs = [];
+ for(attribute in attributes)
+ attrs.push((attribute=='className' ? 'class' : attribute) +
+ '="' + attributes[attribute].toString().escapeHTML() + '"');
+ return attrs.join(" ");
+ },
+ _children: function(element, children) {
+ 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');
+ }
+}
\ No newline at end of file
diff --git a/webroot/js/calendar.js b/webroot/js/calendar.js
new file mode 100755
index 0000000..299041b
--- /dev/null
+++ b/webroot/js/calendar.js
@@ -0,0 +1,826 @@
+function Log(data) {
+ //$('log').innerHTML = data + '<br />' + $('log').innerHTML;
+}
+
+function Diff(data) {
+ d = 'y: '+data.year+'\nm: '+data.month+'\nd: '+data.day+'\n';
+ d += 'h: '+data.hour+'\nm: '+data.minute+'\ns: '+data.second;
+ alert(d);
+}
+
+function debug (obj) {
+ d = '';
+ c = 0;
+ for (n in obj) {
+ d += n + " : " + obj[n] + "\n";
+ if (c > 13) {
+ alert(d);
+ d = '';
+ c = 0;
+ }
+ }
+ if (d != '') {
+ alert(d);
+ }
+}
+
+var Calendar = {
+
+ options : {
+ view : 'month',
+ weekends : true,
+ start : null,
+ end : null
+ },
+
+ container : null,
+
+ now : null,
+
+ __loading : false,
+
+ current : null,
+
+ calendar : null,
+
+ form : null,
+
+ selected : null,
+
+ __setForm : false,
+
+ __highlights : [],
+
+ __updateReceiver : null,
+
+ init : function () {
+ Event.observe(window, 'resize', this.resize, false);
+ Event.observe(window, 'load', this.resize, false);
+ },
+
+ startUpdate : function () {
+ this.__updateReceiver = Calendar.current;
+ },
+
+ loading : function(isLoading) {
+ this.__loading = isLoading;
+ if (this.__loading) {
+ Element.addClassName(this.calendar, 'cal_loading');
+ Element.show('calLoader');
+ } else {
+ Element.removeClassName(this.calendar, 'cal_loading');
+ Element.hide('calLoader');
+ this.layout();
+ }
+ },
+
+ bind : function (calendar) {
+ this.calendar = $(calendar);
+ //this.form = $(form);
+ this.init();
+ },
+
+ getEvent : function (id) {
+ return this.Events.get(id);
+ },
+
+ addEvent : function (day, element) {
+
+ day = $(day);
+ this.unhighlight();
+ //Log('AddEvent : ' + element.id + ' (' + element.className + ') to ' + day.id);
+
+ if (element == null) {
+ var start = this.getDateFromElement(day);
+ start.set(new Date().hours() + ":" + new Date().minutes() + ":00");
+ var end = new Date();
+ end.set(start);
+ end.setHours(end.hours() + 1);
+
+ newEvent = this.Events.add({start : start, end : end});
+ } else {
+ element.style.left = element.style.top = '0px';
+ newEvent = this.Events.get(element);
+
+ diff = newEvent.start.diff(newEvent.end);
+
+ var start = new Date();
+ start.set(this.getDateFromElement(day).format('Y-m-d') + ' ' + newEvent.start.format('H:i:s'));
+
+ var end = new Date();
+ end.set(start);
+ end.set(diff);
+
+ this.Events.set(newEvent, {start : start, end : end});
+ if (newEvent.notify) {
+ newEvent.notify();
+ }
+ newEvent.dropped = true;
+ this.Events.select(newEvent);
+ }
+
+ this.layout(newEvent);
+ return newEvent;
+ },
+
+ select : function (day) {
+ if (Event.element(day) != null) {
+ Event.stop(day);
+ day = Event.element(day);
+ while (!Element.hasClassName(day, 'day')) {
+ if (day == window) {
+ return false;
+ }
+ day = day.parentNode;
+ }
+ }
+
+ day = $(day);
+
+ if (!Element.hasClassName(day, 'day') && Element.hasClassName(day.parentNode, 'day')) {
+ day = day.parentNode;
+ }
+
+ if (day == this.selected) {
+ this.click(day);
+ return true;
+ }
+
+ day.addClassName('day_selected');
+
+ if (this.selected) {
+ this.selected.removeClassName('day_selected');
+ }
+
+ this.selected = day;
+ this.click(day);
+ return true;
+ },
+
+ // Stub method, to be overridden
+ click : function (day) { },
+
+ // Highlight a block of days equal to the range of the current event
+ highlight : function(day, element) {
+
+ this.unhighlight();
+ theEvent = this.Events.get(element);
+
+ if (theEvent) {
+ size = this.getDateRange(theEvent.start, theEvent.end).length;
+ if (size > 1) {
+ start = parseInt(day.id.replace('day_', ''));
+ for (i = 1; i < size; i++) {
+ Element.addClassName($('day_' + (start + i)), 'day_hover');
+ this.__highlights.push($('day_' + (start + i)));
+ }
+ }
+ }
+ },
+
+ // Un-highlight previously highlighted days
+ unhighlight : function() {
+ for (var i = 0; i < this.__highlights.length; i++) {
+ Element.removeClassName(this.__highlights[i], 'day_hover');
+ }
+ this.__highlights = [];
+ },
+
+ resize : function () {
+ if (typeof $('day') != 'undefined') {
+ Element.setStyle($('day'), {
+ width : (document.getWidth() - 10) + 'px',
+ height : (document.getHeight() - 10) + 'px',
+ position : 'absolute'
+ });
+ }
+
+ if (Calendar.calendar) {
+ Element.setStyle(Calendar.calendar, {
+ width : (document.getWidth() - 5) + "px", height : (document.getHeight() - 10) + 'px'
+ });
+ Calendar.layout(null, true);
+ }
+ if (!this.__loading) {
+ for (var i = 0; i < Calendar.Events.items.length; i++) {
+ Calendar.Events.resizeElements(Calendar.Events.items[i]);
+ }
+ }
+ },
+
+ // Get a DOM element from a Date object or date string
+ // (Currently just uses the day of the month)
+ getElementFromDate : function (d) {
+ if (d.innerHTML) {
+ return d;
+ }
+
+ if (d instanceof Date) {
+ d = d.day();
+ } else if (parseInt(d) != d) {
+ d = (new Date()).set(d).day();
+ }
+ return $('day_' + d);
+ },
+
+ getDateFromElement : function (elem) {
+ r = new Date();
+ r.setYear(this.year);
+ r.setMonth(this.month - 1, elem.id.replace('day_', ''));
+ r.setHours(0);
+ r.setMinutes(0);
+ r.setSeconds(0);
+ return r;
+ },
+
+ // Gets a range of day elements
+ getDateRange : function (start, end) {
+
+ start = new Date().set(start);
+ end = new Date().set(end);
+ days = [];
+
+ for (var i = start.day(); i <= end.day(); i++) {
+ if (typeof $('day_' + i) != 'undefined') {
+ days.push($('day_' + i));
+ }
+ }
+ return days;
+ },
+
+ isStartOfWeek : function (day) {
+ day = $(day);
+ for (var i = 0; i < day.parentNode.childNodes.length; i++) {
+ if (day.parentNode.childNodes[i].nodeType == 1) {
+ if (day.parentNode.childNodes[i] == day) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+ return false;
+ },
+
+ // This function will both distribute event elements across days,
+ // and arrange event elements within days
+ layout : function(elem, quick) {
+
+ if (elem) {
+ // Layout the passed event element across it's respective days
+ if (typeof elem.start != 'undefined' && typeof elem.title != 'undefined' && typeof elem.element != 'undefined') {
+
+ // Distribute event elements among day elements
+ dateRange = this.getDateRange(elem.start, elem.end);
+ theStart = this.getElementFromDate(elem.start);
+ theCurrent = theStart;
+
+ for (i = 0; i < dateRange.length; i++) {
+
+ if (typeof elem.element[i] == 'undefined') {
+ this.Events.addElement(elem);
+ }
+
+ theCurrent.appendChild(elem.element[i]);
+ Element.show(elem.element[i]);
+ newId = theCurrent.id.replace('day_', '').toInt() + 1;
+
+ // Show multiday event names on each weekday start
+ if (i > 0) {
+ eventBody = $$('#' + elem.element[i].id + ' div.event_body');
+
+ if (eventBody[0]) {
+ eventTitle = $$('#' + elem.element[i].id + ' div.event_body .event_title');
+ if (this.isStartOfWeek(theCurrent)) {
+ if (!eventTitle.length && (eventBody[0].childNodes[1] || eventBody[0].childNodes[0])) {
+ eventBody[0].insertBefore(
+ Builder.node('span', { className : 'event_title', 'style' : 'padding-left:5px' }, ' ' + elem.title),
+ (eventBody[0].childNodes[1] || eventBody[0].childNodes[0])
+ );
+ } else if (!eventTitle.length) {
+ eventBody[0].appendChild(
+ Builder.node('span', { className : 'event_title', 'style' : 'padding-left:5px' }, ' ' + elem.title)
+ );
+ }
+ } else {
+ if (typeof eventTitle[0] != 'undefined') {
+ Element.remove(eventTitle[0]);
+ }
+ }
+ }
+ }
+ theCurrent = $('day_' + newId);
+ }
+
+ if (elem.element.length > dateRange.length) {
+ for (i = dateRange.length; i <= elem.element.length; i++) {
+ if (typeof elem.element[i] != 'undefined') {
+ Element.remove(elem.element[i]);
+ }
+ }
+ }
+ }
+ }
+
+ if (this.__loading || quick) {
+ return;
+ }
+
+ // Sort events by time of day
+ tmp = this.Events.items.sort(function(a, b) {
+ diff1 = a.end.diff(a.start);
+ diff2 = b.end.diff(b.start);
+
+ if (diff1.day == diff2.day || (diff1.day > 0 && diff2.day > 0)) {
+ s1 = new Date();
+ s2 = new Date();
+ s1.set(a.start.format('H:i:s'));
+ s2.set(b.start.format('H:i:s'));
+ return (s1 < s2 ? -1 : 1);
+ } else if (diff1.day > diff2.day) {
+ return 1;
+ } else {
+ return -1;
+ }
+ });
+
+ // Append event elements to days in order by time
+ for (var i = 0; i < tmp.length; i++) {
+ for (var j = 0; j < tmp[i].element.length; j++) {
+ node = tmp[i].element[j];
+ pNode = (node.parentElement || node.parentNode);
+ if (pNode && pNode != window) {
+ pNode.appendChild(node);
+ }
+ }
+ }
+
+ // Remove all spacer divs
+ spacers = document.getElementsByClassName('event_spacer');
+ for (i = spacers.length - 1; i > -1; i--) {
+ Element.remove(spacers[i]);
+ }
+
+ // Loop 3 times
+ for (k = 0; k < 3; k++) {
+
+ // Determine lowest vertical position of each element
+ for (var i = 0; i < tmp.length; i++) {
+ tmp[i].position = 0;
+ for (var j = 0; j < tmp[i].element.length; j++) {
+ pos = Element.getPosition(tmp[i].element[j]);
+ if (pos > tmp[i].position) {
+ tmp[i].position = pos;
+ }
+ }
+ }
+
+ // Add spacer divs to even out multi-day events
+ for (i = 0; i < tmp.length; i++) {
+ if (tmp[i].element.length > 1) {
+ for (j = 0; j < tmp[i].element.length; j++) {
+ pNode = (tmp[i].element[j].parentElement || tmp[i].element[j].parentNode);
+ if (pNode && pNode != window) {
+ while(Element.getPosition(tmp[i].element[j]) < tmp[i].position) {
+ spc = Builder.node('div', {className : 'event_spacer'});
+ pNode.insertBefore(spc, tmp[i].element[j]);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Add elipsis to days that have more than 4 elements
+ day = $('day_1');
+ i = 1;
+ while (typeof day != 'undefined') {
+
+ if (day.childNodes.length > 4) {
+ $('dayNum_' + i).innerHTML = '...' + i;
+ } else {
+ $('dayNum_' + i).innerHTML = i;
+ }
+
+ i++;
+ day = $('day_' + i);
+ }
+
+ if (elem == null || typeof elem == 'undefined') {
+ return true;
+ }
+ },
+
+ __tmp : null,
+
+ stopEvent : function(e) {
+ if (!Calendar.Events.isEvent(Event.element(e)) || Element.hasClassName(Event.element(e), 'day')) {
+ Event.stop(e);
+ }
+ }
+};
+
+Calendar.Events = {
+
+ items : [],
+
+ __saveQueue : [],
+
+ selected : null,
+
+ // Create a new calendar Event object
+ add : function (options) {
+
+ options = (options || {});
+
+ if (!options.title) {
+ options.title = 'New Event';
+ }
+
+ start = new Date();
+ start.set((options.start || new Date().set((typeof start == 'undefined' ? null : options.start))));
+ options.start = start;
+
+ end = new Date();
+ end.set((options.end || new Date().set((typeof end == 'undefined' ? null : options.end))));
+ options.end = end;
+
+ // Create the new event object
+ newEvent = {
+ id : (options.id || null),
+ __id : null,
+ start : options.start,
+ end : options.end,
+ title : options.title,
+ location: (options.location || ''),
+ sharing : (typeof options.sharing == 'undefined' ? 0 : options.sharing),
+ notes : (options.notes || ''),
+ repeat : (typeof options.repeat == 'undefined' ? 0 : options.repeat),
+ editable: (typeof options.editable == 'undefined' ? 1 : options.editable),
+ element : [],
+ dropped : false,
+ drag : [],
+ notify : function() {
+ Log('Notifying '+this.title+' ('+this.id+')');
+ Calendar.Events.set(this, {title : this.title, start : this.start});
+ Calendar.update(this);
+ }
+ };
+
+ if (!options.id) {
+ newEvent.__id = parseInt(Math.random() * 1000000) + "";
+ }
+
+ this.addElement(newEvent);
+ Calendar.layout(newEvent);
+
+ // Add the event object to the events array
+ this.items.push(newEvent);
+ this.select(newEvent);
+ return newEvent;
+ },
+
+ addElement : function (theEvent) {
+
+ timeStamp = theEvent.start.format('g:i a');
+ title = theEvent.title;
+
+ if (theEvent.element == null || typeof theEvent.element == 'undefined') {
+ theEvent.element = [];
+ }
+
+ if (typeof theEvent.element.length == 'undefined') {
+ elem = theEvent.element;
+ theEvent.element = [];
+ theEvent.element.push(elem);
+ }
+
+ sharedClass = (theEvent.editable == 0 ? ' event_shared' : '');
+
+ node = null;
+ if (theEvent.element.length == 0) {
+ node = Builder.node('div', {id : 'event' + parseInt(Math.random() * 10000), className : 'event' + sharedClass}, [
+ Builder.node('div', { className : 'event_start' }),
+ Builder.node('div', { className : 'event_body' }, [
+ Builder.node('span', { className : 'event_time' }, timeStamp),
+ Builder.node('span', { className : 'event_title' }, title)
+ ]),
+ Builder.node('div', { className : 'event_end' })
+ ]);
+ node.childNodes[1].childNodes[0].innerHTML = '• ' + node.childNodes[1].childNodes[0].innerHTML;
+ } else {
+ endCap = null;
+ for (i = 0; i < theEvent.element[theEvent.element.length - 1].childNodes.length; i++) {
+ tmpNode = theEvent.element[theEvent.element.length - 1].childNodes[i];
+ if (Element.hasClassName(tmpNode, 'event_end')) {
+ endCap = tmpNode;
+ }
+ }
+
+ node = Builder.node('div', {id : 'event' + parseInt(Math.random() * 10000)}, [
+ Builder.node('div', {className : 'event_body'})
+ ]);
+ if (endCap != null) {
+ node.appendChild(endCap);
+ }
+ node.className = theEvent.element[0].className;
+ }
+
+ if (node != null) {
+ theEvent.element.push(node);
+ }
+ this.__initElement(node, theEvent);
+ this.resizeElements(theEvent);
+ },
+
+ resizeElements : function(theEvent) {
+
+ // Environment-specific hack: Hide the time for multiday events
+ elem = theEvent.element[0];
+ eventTime = $$('#' + elem.id + ' div.event_body span.event_time');
+
+ if (eventTime[0]) {
+ if (theEvent.element.length == 1) {
+ Element.show(eventTime[0]);
+ } else {
+ Element.hide(eventTime[0]);
+ }
+ }
+
+ /*for (var i = 0; i < elem.childNodes.length; i++) {
+
+ if (Element.hasClassName(elem.childNodes[i], 'event_body')) {
+ for (var j = 0; j < elem.childNodes[i].childNodes.length; j++) {
+ }
+ }
+ }*/
+
+ for (var i = 0; i < theEvent.element.length; i++) {
+
+ buf = 0;
+ n = theEvent.element[i];
+
+ if (i == 0) {
+ buf = (theEvent.element.length == 1 ? 2 : 1);
+ } else if (n == theEvent.element[theEvent.element.length - 1]) {
+ buf = 1;
+ } else {
+ buf = 0;
+ }
+
+ switch (buf) {
+ case 0:
+ w = 100;
+ break;
+ case 1:
+ w = 93;
+ break;
+ case 2:
+ w = 87;
+ break;
+ }
+
+ for (var j = 0; j < n.childNodes.length; j++) {
+ _w = ((100 - w) / (n.childNodes.length - 1));
+ if (Element.hasClassName(n.childNodes[j], 'event_body')) {
+ n.childNodes[j].style.width = w + '%';
+ } else {
+ n.childNodes[j].style.width = _w + '%';
+ }
+ }
+ }
+ },
+
+ select : function(theEvent) {
+
+ if (this.selected == theEvent) {
+ return;
+ }
+
+ if (this.selected != null) {
+ if (this.selected.element.length) {
+ for (var i = 0; i < this.selected.element.length; i++) {
+ this.selected.element[i].removeClassName('event_selected');
+ }
+ } else {
+ this.selected.element.removeClassName('event_selected');
+ }
+ }
+
+ if (theEvent != null) {
+ Calendar.selectEvent(theEvent);
+ }
+
+ this.selected = theEvent;
+
+ if (this.selected != null) {
+ if (this.selected.element.length) {
+ for (var i = 0; i < this.selected.element.length; i++) {
+ this.selected.element[i].addClassName('event_selected');
+ }
+ } else {
+ this.selected.element.addClassName('event_selected');
+ }
+ }
+ },
+
+ // Initializes a DOM element associated with an Event object
+ __initElement : function(elem, theEvent) {
+ Event.observe(
+ elem,
+ 'click',
+ function(e) {
+ Event.stop(e);
+ e = Event.element(e);
+ while (!Element.hasClassName(e, 'event')) {
+ if (e == window) {
+ return false;
+ }
+ e = e.parentNode;
+ }
+ evt = Calendar.Events.get(e);
+
+ if (evt) {
+ if (!evt.dropped) {
+ // Fire the event if this element was *not* just moved
+ Calendar.Events.__dispatch(evt, 'click');
+ }
+ evt.dropped = false;
+ }
+ },
+ false
+ );
+
+ if (theEvent.editable) {
+ if (theEvent.element.length) {
+ theEvent.drag.push(new Draggable(elem, { revert : true, ghosting : true }));
+ } else {
+ theEvent.drag = new Draggable(elem, { revert : true, ghosting : true });
+ }
+ }
+ },
+
+ // Get a reference to an event object from the given id
+ // id [Object, Integer] - The identifier of the object
+ // Options: A DOM element referenced by the object, an integer index of the object
+ get : function (id) {
+ // id is an event object
+ if (id.start && id.title) {
+ return id;
+ }
+
+ // id is an integer
+ if (parseInt(id) == id) {
+ if (this.items[id]) {
+ return this.items[id];
+ }
+ return null;
+ }
+
+ // id is a DOM element
+ if (typeof id.innerHTML != 'undefined') {
+
+ for (var n = 0; n < this.items.length; n++) {
+
+ for (var m = 0; m < this.items[n].element.length; m++) {
+ if (this.items[n].element[m] == id) {
+ return this.items[n];
+ }
+ }
+ }
+ return null;
+ }
+ },
+
+ // Set the properties of an event. Updates display and meta-data, and saves to db
+ set : function (theEvent, options) {
+ Log('Setting data for '+theEvent.title+', New: '+(options.title || theEvent.title));
+ options = (options || {});
+
+ for (n in options) {
+ if (theEvent[n] instanceof Date) {
+ theEvent[n].set(options[n]);
+ } else {
+ theEvent[n] = options[n];
+ }
+ }
+
+ if (theEvent.element[0].getElementsByTagName('span')[1]) {
+ theEvent.element[0].getElementsByTagName('span')[1].innerHTML = theEvent.title;
+ } else if (theEvent.element.getElementsByTagName && theEvent.element.getElementsByTagName('span')[1]) {
+ theEvent.element.getElementsByTagName('span')[1].innerHTML = theEvent.title;
+ }
+
+ if (theEvent.element.length) {
+ divs = theEvent.element[0].getElementsByTagName('div');
+ } else {
+ divs = theEvent.element.getElementsByTagName('div');
+ }
+
+ Calendar.layout(theEvent);
+
+ for (var i = 0; i < divs.length; i++) {
+ if (Element.hasClassName(divs[i], 'event_body')) {
+ if (divs[i].childNodes && divs[i].childNodes.length) {
+ for (var j = 0; j < divs[i].childNodes.length; j++) {
+ if (Element.hasClassName(divs[i].childNodes[j], 'event_time')) {
+ start = (typeof options.start == 'string' ? new Date().set(options.start) : options.start);
+ divs[i].childNodes[j].innerHTML = '• ' + start.format('g:i a');
+ }
+ }
+ }
+ }
+ }
+
+ /*for (var i = 0; i < Form.__bindings.length; i++) {
+ if (Form.__bindings[i].object == theEvent) {
+ Form.__populateForm(Form.__bindings[i]);
+ }
+ }*/
+ },
+
+ getAllByDay : function (day) {
+
+ if (!(day instanceof Date)) {
+ day = Calendar.getDateFromElement(day);
+ }
+
+ var theEvents = [];
+ var dayStart = day;
+ var dayEnd = new Date();
+ dayEnd.set(dayStart.format('Y-m-d') + ' 23:59:59');
+
+ for (var i = 0; i < this.items.length; i++) {
+ if (this.items[i].start <= dayEnd && this.items[i].end >= day) {
+ theEvents.push(this.items[i]);
+ }
+ }
+ return theEvents;
+ },
+
+ // Delete an event
+ remove : function (id) {
+ theEvent = this.get(id);
+ if (theEvent.element.length) {
+ theEvent.element.each(function(element) {
+ Element.remove(element);
+ });
+ } else {
+ Element.remove(theEvent.element);
+ }
+ theEvent = null;
+ },
+
+ // Show the details form for the given event object
+ showForm : function(theEvent) {
+
+ formWindow = Calendar.form.parentElement || Calendar.form.parentNode;
+ formWindow = formWindow.parentElement || formWindow.parentNode;
+
+ if (!Calendar.__setForm) {
+ Calendar.__setForm = true;
+ }
+ if (!Element.visible(formWindow)) {
+ Effect.SlideDown(formWindow, {duration : 0.5});
+ } else {
+
+ new Effect.Highlight(formWindow);
+ }
+
+ if (theEvent.editable == 1) {
+ Form.enable(Calendar.form);
+ } else {
+ Form.disable(Calendar.form);
+ }
+ },
+
+ // Handles event dispatching for event elements
+ __dispatch : function(theEvent, e) {
+ switch (e) {
+ case 'click':
+ if (this.click(theEvent)) {
+ Event.stop(e);
+ this.select(theEvent);
+ }
+ break;
+ }
+ },
+
+ click : function(element) {
+ // Shell event handler
+ return true;
+ },
+
+ isEvent : function(element) {
+ while (element) {
+ if (element == window || element.tagName.toLowerCase() == 'body') {
+ return false;
+ }
+ if (Element.hasClassName(element, 'event') || Element.hasClassName(element, 'day')) {
+ return true;
+ }
+ element = element.parentNode;
+ }
+ return false;
+ }
+};
diff --git a/webroot/js/controls.js b/webroot/js/controls.js
new file mode 100755
index 0000000..de0261e
--- /dev/null
+++ b/webroot/js/controls.js
@@ -0,0 +1,815 @@
+// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
+// (c) 2005 Jon Tirsen (http://www.tirsen.com)
+// Contributors:
+// Richard Livsey
+// Rahul Bhargava
+// Rob Wills
+//
+// See scriptaculous.js for full license.
+
+// 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.
+
+var Autocompleter = {}
+Autocompleter.Base = function() {};
+Autocompleter.Base.prototype = {
+ baseInitialize: function(element, update, options) {
+ 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));
+ },
+
+ show: function() {
+ if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
+ if(!this.iefix &&
+ (navigator.appVersion.indexOf('MSIE')>0) &&
+ (navigator.userAgent.indexOf('Opera')<0) &&
+ (Element.getStyle(this.update, 'position')=='absolute')) {
+ new Insertion.After(this.update,
+ '<iframe id="' + this.update.id + '_iefix" '+
+ 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
+ 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
+ this.iefix = $(this.update.id+'_iefix');
+ }
+ if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
+ },
+
+ fixIEOverlapping: function() {
+ Position.clone(this.update, this.iefix);
+ 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(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
+ return;
+ case Event.KEY_DOWN:
+ this.markNext();
+ this.render();
+ if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
+ return;
+ }
+ else
+ if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
+ (navigator.appVersion.indexOf('AppleWebKit') > 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;
+ },
+
+ markNext: function() {
+ if(this.index < this.entryCount-1) this.index++
+ else this.index = 0;
+ },
+
+ 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.firstChild);
+
+ if(this.update.firstChild && this.update.firstChild.childNodes) {
+ this.entryCount =
+ this.update.firstChild.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;
+ 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.startIndicator();
+ 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<this.options.tokens.length; i++) {
+ var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
+ if (thisTokenPos > 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() {
+ 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("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
+ elem.substr(entry.length) + "</li>");
+ 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("<li>" + elem.substr(0, foundPos) + "<strong>" +
+ elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
+ foundPos + entry.length) + "</li>");
+ 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 "<ul>" + ret.join('') + "</ul>";
+ }
+ }, 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({
+ okButton: true,
+ okText: "ok",
+ cancelLink: true,
+ cancelText: "cancel",
+ 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);
+ 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.okButton) {
+ okButton = document.createElement("input");
+ okButton.type = "submit";
+ okButton.value = this.options.okText;
+ okButton.className = 'editor_ok_button';
+ this.form.appendChild(okButton);
+ }
+
+ if (this.options.cancelLink) {
+ cancelLink = document.createElement("a");
+ cancelLink.href = "#";
+ cancelLink.appendChild(document.createTextNode(this.options.cancelText));
+ cancelLink.onclick = this.onclickCancel.bind(this);
+ cancelLink.className = 'editor_cancel';
+ this.form.appendChild(cancelLink);
+ }
+ },
+ hasHTMLLineBreaks: function(string) {
+ if (!this.options.handleLineBreaks) return false;
+ return string.match(/<br/i) || string.match(/<p>/i);
+ },
+ convertHTMLLineBreaks: function(string) {
+ return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/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 = "value";
+ 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 = "value";
+ 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();
+ },
+ 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(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/webroot/js/date.js b/webroot/js/date.js
new file mode 100755
index 0000000..1b61316
--- /dev/null
+++ b/webroot/js/date.js
@@ -0,0 +1,221 @@
+// I got tired of JavaScript's Date object, so I rewrote it
+
+// Sets the date object from a string
+Object.extend(Date.prototype, {
+
+ diff : function(date) {
+ return {
+ year : date.year() - this.year(),
+ month : date.month() - this.month(),
+ day : date.day() - this.day(),
+ hour : date.hours() - this.hours(),
+ minute : date.minutes() - this.minutes(),
+ second : date.seconds() - this.seconds()
+ };
+ },
+
+ set : function(date) {
+
+ if (date == null || !date) {
+ return this;
+ }
+
+ if (typeof date == 'object') {
+ if (typeof date.year != 'undefined' && typeof date.second != 'undefined') {
+ this.setYear (this.year() + date.year);
+ this.setMonth (this.month() + date.month - 1);
+ this.setDate (this.day() + date.day);
+ this.setHours (this.hours() + date.hour);
+ this.setMinutes (this.minutes() + date.minute);
+ this.setSeconds (this.seconds() + date.second);
+ return this;
+ } else if (date instanceof Date) {
+ // It's already a date object
+ this.set(date.format('Y-m-d H:i:s'));
+ return date;
+ }
+ }
+
+ date = date.toString();
+
+ if (date.indexOf(' ') > 0) {
+ date = date.split(' ');
+ } else {
+ date = [date, ''];
+ }
+
+ if (date[0].indexOf('/') > 0) {
+ date[0] = date[0].split('/');
+ } else if (date[0].indexOf('-') > 0) {
+ date[0] = date[0].split('-');
+ }
+
+ if (!date[0]) {
+ //alert('Date Error:'+date);
+ }
+
+ _time = null;
+ _meridian = null;
+
+ if (date[0].indexOf(':') > 0) {
+ _time = date[0];
+ if (date[1]) {
+ _meridian = date[1];
+ }
+ } else {
+ if (date[0][0].length == 4) {
+ // date is mysql format
+ this.setYear(date[0][0].unpad());
+ this.setMonth(date[0][1].unpad() - 1);
+ this.setDate(date[0][2].unpad());
+ } else {
+ // date is 'standard' format
+ if(date[0][2]) {
+ this.setYear(this.y2k(date[0][2].unpad()));
+ }
+ this.setMonth(date[0][0].unpad() - 1);
+ this.setDate(date[0][1].unpad());
+ }
+ _time = date[1];
+ if (date[2]) {
+ _meridian = date[2];
+ }
+ }
+
+ if (_time) {
+ if (_meridian) {
+ if (_meridian.toLowerCase().indexOf('p') != -1) {
+ _meridian = 12;
+ } else {
+ _meridian = 0;
+ }
+ } else {
+ _meridian = 0;
+ }
+
+ _time = _time.split(':');
+ this.setHours(_time[0].unpad() + _meridian);
+ if (_time[1]) {
+ this.setMinutes(_time[1].unpad());
+ }
+ if (_time.length > 2 && _time[2]) {
+ this.setSeconds(_time[2].unpad());
+ }
+ }
+
+ return this;
+ },
+
+ y2k : function(year) {
+ if (!year) {
+ year = this.year();
+ }
+ return (year < 1000) ? year + 1900 : year;
+ },
+
+ year : function () {
+ return this.getFullYear();
+ },
+
+ month : function () {
+ return this.getMonth() + 1;
+ },
+
+ day : function () {
+ return this.getDate();
+ },
+
+ hours : function () {
+ return this.getHours();
+ },
+
+ minutes : function () {
+ return this.getMinutes();
+ },
+
+ seconds : function () {
+ return this.getSeconds();
+ },
+
+ format : function(fmt) {
+ if (!fmt) {
+ fmt = 'm/d/Y';
+ }
+
+ fmt = fmt.replace('r', 'D, j M Y H:i:s O');
+ map = {
+ m : this.month().pad(),
+ n : this.month(),
+ d : this.day().pad(),
+ D : this.shortDays[this.getDay()],
+ j : this.day(),
+ l : this.days[this.getDay()],
+ w : this.getDay(),
+ Y : this.year().pad(4),
+ y : (this.getYear() - 100).pad(),
+ F : this.months[this.getMonth()],
+ M : this.shortMonths[this.getMonth()],
+ H : this.hours().pad(),
+ G : this.hours(),
+ h : (this.hours() <= 12 ? ( this.hours() == 0 ? this.hours() + 12 : this.hours() ) : this.hours() - 12 ).pad(),
+ g : (this.hours() <= 12 ? ( this.hours() == 0 ? this.hours() + 12 : this.hours() ) : this.hours() - 12 ),
+ i : this.minutes().pad(),
+ s : this.seconds().pad(),
+ a : (this.hours() < 12 ? 'am' : 'pm'),
+ A : (this.hours() < 12 ? 'AM' : 'PM'),
+ O : this.getTimezoneOffset().pad(4)
+ };
+
+ data = '';
+ for (var i = 0; i < fmt.length; i++) {
+ c = fmt.substr(i, 1);
+
+ if (map[c] || (map[c] == 0)) {
+ data += map[c];
+ } else {
+ data += c;
+ }
+ }
+ return data;
+ },
+
+ months : ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
+ shortMonths : ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec'],
+ days : ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
+ shortDays : ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
+});
+
+Object.extend(Number.prototype, {
+ pad : function (length) {
+ if (!length) {
+ length = 2;
+ }
+
+ data = this + "";
+ while (data.length < length) {
+ data = '0' + data;
+ }
+ return data;
+ }
+});
+
+Object.extend(String.prototype, {
+ unpad : function() {
+ data = this;
+ while (data.indexOf('0') == 0 && data.length > 0) {
+ data = data.substr(1, data.length - 1);
+ }
+ if (!data || data == '') {
+ data = '0';
+ }
+ return data.toInt();
+ },
+
+ toInt : function() {
+ return parseInt(this);
+ },
+
+ toFloat : function() {
+ return parseFloat(this);
+ }
+});
diff --git a/webroot/js/dragdrop.js b/webroot/js/dragdrop.js
new file mode 100755
index 0000000..be2a30f
--- /dev/null
+++ b/webroot/js/dragdrop.js
@@ -0,0 +1,915 @@
+// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// (c) 2005 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
+//
+// See scriptaculous.js for full license.
+
+/*--------------------------------------------------------------------------*/
+
+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);
+ },
+
+ 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) {
+ 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.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);
+ });
+ },
+
+ _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.prototype = {
+ initialize: function(element) {
+ var options = Object.extend({
+ handle: false,
+ starteffect: function(element) {
+ element._opacity = Element.getOpacity(element);
+ new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
+ },
+ reverteffect: function(element, top_offset, left_offset) {
+ var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
+ element._revert = new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur});
+ },
+ 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});
+ },
+ zindex: 1000,
+ revert: false,
+ scroll: false,
+ scrollSensitivity: 20,
+ scrollSpeed: 15,
+ snap: false // false, or xy or [x,y] or function(x,y){ return [x,y] }
+ }, arguments[1] || {});
+
+ this.element = $(element);
+
+ if(options.handle && (typeof options.handle == 'string')) {
+ var h = Element.childrenWithClassName(this.element, options.handle, true);
+ if(h.length>0) this.handle = h[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);
+
+ 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(Event.isLeftClick(event)) {
+ // abort on form elements, fixes a Firefox issue
+ var src = Event.element(event);
+ if(src.tagName && (
+ src.tagName=='INPUT' ||
+ src.tagName=='SELECT' ||
+ src.tagName=='OPTION' ||
+ src.tagName=='BUTTON' ||
+ src.tagName=='TEXTAREA')) return;
+
+ if(this.element._revert) {
+ this.element._revert.cancel();
+ this.element._revert = null;
+ }
+
+ 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);
+ 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;
+ p[1] += this.options.scroll.scrollTop;
+ 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(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
+
+ Event.stop(event);
+ },
+
+ finishDrag: function(event, success) {
+ this.dragging = false;
+
+ if(this.options.ghosting) {
+ Position.relativize(this.element);
+ Element.remove(this._clone);
+ this._clone = null;
+ }
+
+ if(success) Droppables.fire(event, 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) {
+ 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);
+ var d = this.currentDelta();
+ pos[0] -= d[0]; pos[1] -= d[1];
+
+ if(this.options.scroll && (this.options.scroll != window)) {
+ 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) {
+ 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);
+ 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 = {
+ sortables: {},
+
+ _findRootElement: function(element) {
+ while (element.tagName != "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,
+ hoverclass: null,
+ ghosting: false,
+ scroll: false,
+ scrollSensitivity: 20,
+ scrollSpeed: 15,
+ format: /^[^_]*_(.*)$/,
+ 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,
+ scroll: options.scroll,
+ scrollSpeed: options.scrollSpeed,
+ scrollSensitivity: options.scrollSensitivity,
+ 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
+ //greedy: !options.dropOnEmpty
+ }
+
+ 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);
+ }
+
+ (this.findElements(element, options) || []).each( function(e) {
+ // handles are per-draggable
+ var handle = options.handle ?
+ Element.childrenWithClassName(e, 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});
+ 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) Element.hide(Sortable._marker);
+ },
+
+ 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') || document.createElement('DIV');
+ Element.hide(Sortable._marker);
+ Element.addClassName(Sortable._marker, 'dropmarker');
+ Sortable._marker.style.position = 'absolute';
+ document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
+ }
+ var offsets = Position.cumulativeOffset(dropon);
+ Sortable._marker.style.left = offsets[0] + 'px';
+ Sortable._marker.style.top = offsets[1] + 'px';
+
+ if(position=='after')
+ if(sortable.overlap == 'horizontal')
+ Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px';
+ else
+ Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px';
+
+ Element.show(Sortable._marker);
+ },
+
+ _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: new Array,
+ position: parent.children.length,
+ container: Sortable._findChildrenElement(children[i], options.treeTag.toUpperCase())
+ }
+
+ /* 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;
+ },
+
+ /* Finds the first element of the given tag type within a parent element.
+ Used for finding the first LI[ST] within a L[IST]I[TEM].*/
+ _findChildrenElement: function (element, containerTag) {
+ if (element && element.hasChildNodes)
+ for (var i = 0; i < element.childNodes.length; ++i)
+ if (element.childNodes[i].tagName == containerTag)
+ return element.childNodes[i];
+
+ return null;
+ },
+
+ 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: new Array,
+ 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) + "=" +
+ 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) {
+ if (type == 'vertical' || type == 'height')
+ return element.offsetHeight;
+ else
+ return element.offsetWidth;
+}
\ No newline at end of file
diff --git a/webroot/js/effects.js b/webroot/js/effects.js
new file mode 100755
index 0000000..0864323
--- /dev/null
+++ b/webroot/js/effects.js
@@ -0,0 +1,958 @@
+// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// Contributors:
+// Justin Palmer (http://encytemedia.com/)
+// Mark Pilgrim (http://diveintomark.org/)
+// Martin Bialasinki
+//
+// See scriptaculous.js for full license.
+
+// 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(element, {fontSize: (percent/100) + 'em'});
+ if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
+}
+
+Element.getOpacity = function(element){
+ var opacity;
+ if (opacity = Element.getStyle(element, 'opacity'))
+ return parseFloat(opacity);
+ if (opacity = (Element.getStyle(element, 'filter') || '').match(/alpha\(opacity=(.*)\)/))
+ if(opacity[1]) return parseFloat(opacity[1]) / 100;
+ return 1.0;
+}
+
+Element.setOpacity = function(element, value){
+ element= $(element);
+ if (value == 1){
+ Element.setStyle(element, { opacity:
+ (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ?
+ 0.999999 : null });
+ if(/MSIE/.test(navigator.userAgent))
+ Element.setStyle(element, {filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')});
+ } else {
+ if(value < 0.00001) value = 0;
+ Element.setStyle(element, {opacity: value});
+ if(/MSIE/.test(navigator.userAgent))
+ Element.setStyle(element,
+ { filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') +
+ 'alpha(opacity='+value*100+')' });
+ }
+}
+
+Element.getInlineOpacity = function(element){
+ return $(element).style.opacity || '';
+}
+
+Element.childrenWithClassName = function(element, className, findFirst) {
+ var classNameRegExp = new RegExp("(^|\\s)" + className + "(\\s|$)");
+ var results = $A($(element).getElementsByTagName('*'))[findFirst ? 'detect' : 'select']( function(c) {
+ return (c.className && c.className.match(classNameRegExp));
+ });
+ if(!results) results = [];
+ return results;
+}
+
+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 = {
+ tagifyText: function(element) {
+ var tagifyStyle = 'position:relative';
+ if(/MSIE/.test(navigator.userAgent)) 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 = {}
+
+Effect.Transitions.linear = function(pos) {
+ return pos;
+}
+Effect.Transitions.sinoidal = function(pos) {
+ return (-Math.cos(pos*Math.PI)/2) + 0.5;
+}
+Effect.Transitions.reverse = function(pos) {
+ return 1-pos;
+}
+Effect.Transitions.flicker = function(pos) {
+ return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
+}
+Effect.Transitions.wobble = function(pos) {
+ return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
+}
+Effect.Transitions.pulse = function(pos) {
+ return (Math.floor(pos*10) % 2 == 0 ?
+ (pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10)));
+}
+Effect.Transitions.none = function(pos) {
+ return 0;
+}
+Effect.Transitions.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 '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), 40);
+ },
+ 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();
+ this.effects.invoke('loop', timePos);
+ }
+});
+
+Effect.Queues = {
+ instances: $H(),
+ get: function(queueName) {
+ if(typeof queueName != 'string') return queueName;
+
+ if(!this.instances[queueName])
+ this.instances[queueName] = new Effect.ScopedQueue();
+
+ return this.instances[queueName];
+ }
+}
+Effect.Queue = Effect.Queues.get('global');
+
+Effect.DefaultOptions = {
+ transition: Effect.Transitions.sinoidal,
+ duration: 1.0, // seconds
+ fps: 25.0, // max. 25fps due to Effect.Queue implementation
+ sync: false, // true for combining
+ from: 0.0,
+ to: 1.0,
+ delay: 0.0,
+ queue: 'parallel'
+}
+
+Effect.Base = function() {};
+Effect.Base.prototype = {
+ position: null,
+ start: function(options) {
+ this.options = Object.extend(Object.extend({},Effect.DefaultOptions), options || {});
+ this.currentFrame = 0;
+ this.state = 'idle';
+ this.startOn = this.options.delay*1000;
+ this.finishOn = this.startOn + (this.options.duration*1000);
+ this.event('beforeStart');
+ if(!this.options.sync)
+ Effect.Queues.get(typeof this.options.queue == 'string' ?
+ 'global' : this.options.queue.scope).add(this);
+ },
+ loop: function(timePos) {
+ if(timePos >= 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.finishOn - this.startOn);
+ var frame = Math.round(pos * this.options.fps * this.options.duration);
+ if(frame > this.currentFrame) {
+ this.render(pos);
+ this.currentFrame = frame;
+ }
+ }
+ },
+ render: function(pos) {
+ if(this.state == 'idle') {
+ this.state = 'running';
+ this.event('beforeSetup');
+ if(this.setup) this.setup();
+ this.event('afterSetup');
+ }
+ if(this.state == 'running') {
+ if(this.options.transition) pos = this.options.transition(pos);
+ pos *= (this.options.to-this.options.from);
+ pos += this.options.from;
+ this.position = pos;
+ this.event('beforeUpdate');
+ if(this.update) this.update(pos);
+ this.event('afterUpdate');
+ }
+ },
+ 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() {
+ return '#<Effect:' + $H(this).inspect() + ',options:' + $H(this.options).inspect() + '>';
+ }
+}
+
+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.Opacity = Class.create();
+Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
+ initialize: function(element) {
+ this.element = $(element);
+ // make this work on IE on elements without 'layout'
+ if(/MSIE/.test(navigator.userAgent) && (!this.element.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);
+ 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: this.options.x * position + this.originalLeft + 'px',
+ top: 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)
+ 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','%'].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 = width + 'px';
+ if(this.options.scaleY) d.height = 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);
+ 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 = {
+ 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();
+ effect.element.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);
+ effect.element.show();
+ }}, arguments[1] || {});
+ return new Effect.Opacity(element,options);
+}
+
+Effect.Puff = function(element) {
+ element = $(element);
+ var oldStyle = { opacity: element.getInlineOpacity(), position: element.getStyle('position') };
+ 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) {
+ effect.effects[0].element.setStyle({position: 'absolute'}); },
+ afterFinishInternal: function(effect) {
+ effect.effects[0].element.hide();
+ effect.effects[0].element.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();
+ effect.element.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();
+ effect.element.setStyle({height: '0px'});
+ effect.element.show();
+ },
+ afterFinishInternal: function(effect) {
+ effect.element.undoClipping();
+ }
+ }, arguments[1] || {})
+ );
+}
+
+Effect.SwitchOff = function(element) {
+ element = $(element);
+ var oldOpacity = element.getInlineOpacity();
+ return new Effect.Appear(element, {
+ 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();
+ effect.element.makeClipping();
+ },
+ afterFinishInternal: function(effect) {
+ effect.element.hide();
+ effect.element.undoClipping();
+ effect.element.undoPositioned();
+ effect.element.setStyle({opacity: oldOpacity});
+ }
+ })
+ }
+ });
+}
+
+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();
+ effect.effects[0].element.undoPositioned();
+ effect.effects[0].element.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();
+ effect.element.setStyle(oldStyle);
+ }}) }}) }}) }}) }}) }});
+}
+
+Effect.SlideDown = function(element) {
+ element = $(element);
+ element.cleanWhitespace();
+ // SlideDown need to have the content of the element wrapped in a container element with fixed height!
+ var oldInnerBottom = $(element.firstChild).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.firstChild.makePositioned();
+ if(window.opera) effect.element.setStyle({top: ''});
+ effect.element.makeClipping();
+ effect.element.setStyle({height: '0px'});
+ effect.element.show(); },
+ afterUpdateInternal: function(effect) {
+ effect.element.firstChild.setStyle({bottom:
+ (effect.dims[0] - effect.element.clientHeight) + 'px' });
+ },
+ afterFinishInternal: function(effect) {
+ effect.element.undoClipping();
+ // IE will crash if child is undoPositioned first
+ if(/MSIE/.test(navigator.userAgent)){
+ effect.element.undoPositioned();
+ effect.element.firstChild.undoPositioned();
+ }else{
+ effect.element.firstChild.undoPositioned();
+ effect.element.undoPositioned();
+ }
+ effect.element.firstChild.setStyle({bottom: oldInnerBottom}); }
+ }, arguments[1] || {})
+ );
+}
+
+Effect.SlideUp = function(element) {
+ element = $(element);
+ element.cleanWhitespace();
+ var oldInnerBottom = $(element.firstChild).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.firstChild.makePositioned();
+ if(window.opera) effect.element.setStyle({top: ''});
+ effect.element.makeClipping();
+ effect.element.show(); },
+ afterUpdateInternal: function(effect) {
+ effect.element.firstChild.setStyle({bottom:
+ (effect.dims[0] - effect.element.clientHeight) + 'px' }); },
+ afterFinishInternal: function(effect) {
+ effect.element.hide();
+ effect.element.undoClipping();
+ effect.element.firstChild.undoPositioned();
+ effect.element.undoPositioned();
+ effect.element.setStyle({bottom: oldInnerBottom}); }
+ }, 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(effect.element); },
+ afterFinishInternal: function(effect) {
+ effect.element.hide(effect.element);
+ effect.element.undoClipping(effect.element); }
+ });
+}
+
+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();
+ effect.element.makeClipping();
+ effect.element.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'});
+ effect.effects[0].element.show();
+ },
+ afterFinishInternal: function(effect) {
+ effect.effects[0].element.undoClipping();
+ effect.effects[0].element.undoPositioned();
+ effect.effects[0].element.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();
+ effect.effects[0].element.makeClipping(); },
+ afterFinishInternal: function(effect) {
+ effect.effects[0].element.hide();
+ effect.effects[0].element.undoClipping();
+ effect.effects[0].element.undoPositioned();
+ effect.effects[0].element.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)) };
+ reverser.bind(transition);
+ return new Effect.Opacity(element,
+ Object.extend(Object.extend({ duration: 3.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(element);
+ 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();
+ effect.element.undoClipping();
+ effect.element.setStyle(oldStyle);
+ } });
+ }}, arguments[1] || {}));
+};
+
+['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom',
+ 'collectTextNodes','collectTextNodesIgnoreClass','childrenWithClassName'].each(
+ function(f) { Element.Methods[f] = Element[f]; }
+);
+
+Element.Methods.visualEffect = function(element, effect, options) {
+ s = effect.gsub(/_/, '-').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/webroot/js/excanvas.js b/webroot/js/excanvas.js
new file mode 100755
index 0000000..0797159
--- /dev/null
+++ b/webroot/js/excanvas.js
@@ -0,0 +1,704 @@
+// Copyright 2006 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// TODO: Patterns
+// TODO: Radial gradient
+// TODO: Clipping paths
+// TODO: Coordsize
+// TODO: Painting mode
+// TODO: Optimize
+// TODO: canvas width/height sets content size in moz, border size in ie
+// TODO: Painting outside the canvas should not be allowed
+
+// only add this code if we do not already have a canvas implementation
+if (!window.CanvasRenderingContext2D) {
+
+(function () {
+
+ var G_vmlCanvasManager_ = {
+ init: function (opt_doc) {
+ var doc = opt_doc || document;
+ if (/MSIE/.test(navigator.userAgent) && !window.opera) {
+ var self = this;
+ doc.attachEvent("onreadystatechange", function () {
+ self.init_(doc);
+ });
+ }
+ },
+
+ init_: function (doc, e) {
+ if (doc.readyState == "complete") {
+ // create xmlns
+ if (!doc.namespaces["g_vml_"]) {
+ doc.namespaces.add("g_vml_", "urn:schemas-microsoft-com:vml");
+ }
+
+ // setup default css
+ var ss = doc.createStyleSheet();
+ ss.cssText = "canvas{display:inline-block;overflow:hidden;" +
+ "text-align:left;}" +
+ "canvas *{behavior:url(#default#VML)}";
+
+ // find all canvas elements
+ var els = doc.getElementsByTagName("canvas");
+ for (var i = 0; i < els.length; i++) {
+ if (!els[i].getContext) {
+ this.initElement(els[i]);
+ }
+ }
+ }
+ },
+
+ fixElement_: function (el) {
+ // in IE before version 5.5 we would need to add HTML: to the tag name
+ // but we do not care about IE before version 6
+ var outerHTML = el.outerHTML;
+ var newEl = document.createElement(outerHTML);
+ // if the tag is still open IE has created the children as siblings and
+ // it has also created a tag with the name "/FOO"
+ if (outerHTML.slice(-2) != "/>") {
+ var tagName = "/" + el.tagName;
+ var ns;
+ // remove content
+ while ((ns = el.nextSibling) && ns.tagName != tagName) {
+ ns.removeNode();
+ }
+ // remove the incorrect closing tag
+ if (ns) {
+ ns.removeNode();
+ }
+ }
+ el.parentNode.replaceChild(newEl, el);
+ return newEl;
+ },
+
+ /**
+ * Public initializes a canvas element so that it can be used as canvas
+ * element from now on. This is called automatically before the page is
+ * loaded but if you are creating elements using createElement yuo need to
+ * make sure this is called on the element.
+ * @param el {HTMLElement} The canvas element to initialize.
+ */
+ initElement: function (el) {
+ el = this.fixElement_(el);
+ el.getContext = function () {
+ if (this.context_) {
+ return this.context_;
+ }
+ return this.context_ = new CanvasRenderingContext2D_(this);
+ };
+
+ var self = this; //bind
+ el.attachEvent("onpropertychange", function (e) {
+ // we need to watch changes to width and height
+ switch (e.propertyName) {
+ case "width":
+ case "height":
+ // coord size changed?
+ break;
+ }
+ });
+
+ // if style.height is set
+
+ var attrs = el.attributes;
+ if (attrs.width && attrs.width.specified) {
+ // TODO: use runtimeStyle and coordsize
+ // el.getContext().setWidth_(attrs.width.nodeValue);
+ el.style.width = attrs.width.nodeValue + "px";
+ }
+ if (attrs.height && attrs.height.specified) {
+ // TODO: use runtimeStyle and coordsize
+ // el.getContext().setHeight_(attrs.height.nodeValue);
+ el.style.height = attrs.height.nodeValue + "px";
+ }
+ //el.getContext().setCoordsize_()
+ }
+ };
+
+ G_vmlCanvasManager_.init();
+
+ // precompute "00" to "FF"
+ var dec2hex = [];
+ for (var i = 0; i < 16; i++) {
+ for (var j = 0; j < 16; j++) {
+ dec2hex[i * 16 + j] = i.toString(16) + j.toString(16);
+ }
+ }
+
+ function createMatrixIdentity() {
+ return [
+ [1, 0, 0],
+ [0, 1, 0],
+ [0, 0, 1]
+ ];
+ }
+
+ function matrixMultiply(m1, m2) {
+ var result = createMatrixIdentity();
+
+ for (var x = 0; x < 3; x++) {
+ for (var y = 0; y < 3; y++) {
+ var sum = 0;
+
+ for (var z = 0; z < 3; z++) {
+ sum += m1[x][z] * m2[z][y];
+ }
+
+ result[x][y] = sum;
+ }
+ }
+ return result;
+ }
+
+ function copyState(o1, o2) {
+ o2.fillStyle = o1.fillStyle;
+ o2.lineCap = o1.lineCap;
+ o2.lineJoin = o1.lineJoin;
+ o2.lineWidth = o1.lineWidth;
+ o2.miterLimit = o1.miterLimit;
+ o2.shadowBlur = o1.shadowBlur;
+ o2.shadowColor = o1.shadowColor;
+ o2.shadowOffsetX = o1.shadowOffsetX;
+ o2.shadowOffsetY = o1.shadowOffsetY;
+ o2.strokeStyle = o1.strokeStyle;
+ }
+
+ function processStyle(styleString) {
+ var str, alpha = 1;
+
+ styleString = String(styleString);
+ if (styleString.substring(0, 3) == "rgb") {
+ var start = styleString.indexOf("(", 3);
+ var end = styleString.indexOf(")", start + 1);
+ var guts = styleString.substring(start + 1, end).split(",");
+
+ str = "#";
+ for (var i = 0; i < 3; i++) {
+ str += dec2hex[parseInt(guts[i])];
+ }
+
+ if ((guts.length == 4) && (styleString.substr(3, 1) == "a")) {
+ alpha = guts[3];
+ }
+ } else {
+ str = styleString;
+ }
+
+ return [str, alpha];
+ }
+
+ function processLineCap(lineCap) {
+ switch (lineCap) {
+ case "butt":
+ return "flat";
+ case "round":
+ return "round";
+ case "square":
+ default:
+ return "square";
+ }
+ }
+
+ /**
+ * This class implements CanvasRenderingContext2D interface as described by
+ * the WHATWG.
+ * @param surfaceElement {HTMLElement} The element that the 2D context should
+ * be associated with
+ */
+ function CanvasRenderingContext2D_(surfaceElement) {
+ this.m_ = createMatrixIdentity();
+ this.element_ = surfaceElement;
+
+ this.mStack_ = [];
+ this.aStack_ = [];
+ this.currentPath_ = [];
+
+ // Canvas context properties
+ this.strokeStyle = "#000";
+ this.fillStyle = "#ccc";
+
+ this.lineWidth = 1;
+ this.lineJoin = "miter";
+ this.lineCap = "butt";
+ this.miterLimit = 10;
+ this.globalAlpha = 1;
+ };
+
+ var contextPrototype = CanvasRenderingContext2D_.prototype;
+ contextPrototype.clearRect = function() {
+ this.element_.innerHTML = "";
+ this.currentPath_ = [];
+ };
+
+ contextPrototype.beginPath = function() {
+ // TODO: Branch current matrix so that save/restore has no effect
+ // as per safari docs.
+
+ this.currentPath_ = [];
+ };
+
+ contextPrototype.moveTo = function(aX, aY) {
+ this.currentPath_.push({type: "moveTo", x: aX, y: aY});
+ };
+
+ contextPrototype.lineTo = function(aX, aY) {
+ this.currentPath_.push({type: "lineTo", x: aX, y: aY});
+ };
+
+ contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
+ aCP2x, aCP2y,
+ aX, aY) {
+ this.currentPath_.push({type: "bezierCurveTo",
+ cp1x: aCP1x,
+ cp1y: aCP1y,
+ cp2x: aCP2x,
+ cp2y: aCP2y,
+ x: aX,
+ y: aY});
+ };
+
+ contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
+ // VML's qb produces different output to Firefox's
+ // FF's behaviour seems to have changed in 1.5.0.1, check this
+ this.bezierCurveTo(aCPx, aCPy, aCPx, aCPy, aX, aY);
+ };
+
+ contextPrototype.arc = function(aX, aY, aRadius,
+ aStartAngle, aEndAngle, aClockwise) {
+ if (!aClockwise) {
+ var t = aStartAngle;
+ aStartAngle = aEndAngle;
+ aEndAngle = t;
+ }
+
+ var xStart = aX + (Math.cos(aStartAngle) * aRadius);
+ var yStart = aY + (Math.sin(aStartAngle) * aRadius);
+
+ var xEnd = aX + (Math.cos(aEndAngle) * aRadius);
+ var yEnd = aY + (Math.sin(aEndAngle) * aRadius);
+
+ this.currentPath_.push({type: "arc",
+ x: aX,
+ y: aY,
+ radius: aRadius,
+ xStart: xStart,
+ yStart: yStart,
+ xEnd: xEnd,
+ yEnd: yEnd});
+
+ };
+
+ contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
+ this.moveTo(aX, aY);
+ this.lineTo(aX + aWidth, aY);
+ this.lineTo(aX + aWidth, aY + aHeight);
+ this.lineTo(aX, aY + aHeight);
+ this.closePath();
+ };
+
+ contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
+ // Will destroy any existing path (same as FF behaviour)
+ this.beginPath();
+ this.moveTo(aX, aY);
+ this.lineTo(aX + aWidth, aY);
+ this.lineTo(aX + aWidth, aY + aHeight);
+ this.lineTo(aX, aY + aHeight);
+ this.closePath();
+ this.stroke();
+ };
+
+ contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
+ // Will destroy any existing path (same as FF behaviour)
+ this.beginPath();
+ this.moveTo(aX, aY);
+ this.lineTo(aX + aWidth, aY);
+ this.lineTo(aX + aWidth, aY + aHeight);
+ this.lineTo(aX, aY + aHeight);
+ this.closePath();
+ this.fill();
+ };
+
+ contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
+ var gradient = new CanvasGradient_("gradient");
+ return gradient;
+ };
+
+ contextPrototype.createRadialGradient = function(aX0, aY0,
+ aR0, aX1,
+ aY1, aR1) {
+ var gradient = new CanvasGradient_("gradientradial");
+ gradient.radius1_ = aR0;
+ gradient.radius2_ = aR1;
+ gradient.focus_.x = aX0;
+ gradient.focus_.y = aY0;
+ return gradient;
+ };
+
+ contextPrototype.drawImage = function (image, var_args) {
+ var dx, dy, dw, dh, sx, sy, sw, sh;
+ var w = image.width;
+ var h = image.height;
+
+ if (arguments.length == 3) {
+ dx = arguments[1];
+ dy = arguments[2];
+ sx = sy = 0;
+ sw = dw = w;
+ sh = dh = h;
+ } else if (arguments.length == 5) {
+ dx = arguments[1];
+ dy = arguments[2];
+ dw = arguments[3];
+ dh = arguments[4];
+ sx = sy = 0;
+ sw = w;
+ sh = h;
+ } else if (arguments.length == 9) {
+ sx = arguments[1];
+ sy = arguments[2];
+ sw = arguments[3];
+ sh = arguments[4];
+ dx = arguments[5];
+ dy = arguments[6];
+ dw = arguments[7];
+ dh = arguments[8];
+ } else {
+ throw "Invalid number of arguments";
+ }
+
+ var d = this.getCoords_(dx, dy);
+
+ var w2 = (sw / 2);
+ var h2 = (sh / 2);
+
+ var vmlStr = [];
+
+ // For some reason that I've now forgotten, using divs didn't work
+ vmlStr.push(' <g_vml_:group',
+ ' coordsize="100,100"',
+ ' coordorigin="0, 0"' ,
+ ' style="width:100px;height:100px;position:absolute;');
+
+ // If filters are necessary (rotation exists), create them
+ // filters are bog-slow, so only create them if abbsolutely necessary
+ // The following check doesn't account for skews (which don't exist
+ // in the canvas spec (yet) anyway.
+
+ if (this.m_[0][0] != 1 || this.m_[0][1]) {
+ var filter = [];
+
+ // Note the 12/21 reversal
+ filter.push("M11='", this.m_[0][0], "',",
+ "M12='", this.m_[1][0], "',",
+ "M21='", this.m_[0][1], "',",
+ "M22='", this.m_[1][1], "',",
+ "Dx='", d.x, "',",
+ "Dy='", d.y, "'");
+
+ // Bounding box calculation (need to minimize displayed area so that
+ // filters don't waste time on unused pixels.
+ var max = d;
+ var c2 = this.getCoords_(dx+dw, dy);
+ var c3 = this.getCoords_(dx, dy+dh);
+ var c4 = this.getCoords_(dx+dw, dy+dh);
+
+ max.x = Math.max(max.x, c2.x, c3.x, c4.x);
+ max.y = Math.max(max.y, c2.y, c3.y, c4.y);
+
+ vmlStr.push(" padding:0 ", Math.floor(max.x), "px ", Math.floor(max.y),
+ "px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",
+ filter.join(""), ", sizingmethod='clip');")
+ } else {
+ vmlStr.push(" top:", d.y, "px;left:", d.x, "px;")
+ }
+
+ vmlStr.push(' ">' ,
+ '<g_vml_:image src="', image.src, '"',
+ ' style="width:', dw, ';',
+ ' height:', dh, ';"',
+ ' cropleft="', sx / w, '"',
+ ' croptop="', sy / h, '"',
+ ' cropright="', (w - sx - sw) / w, '"',
+ ' cropbottom="', (h - sy - sh) / h, '"',
+ ' />',
+ '</g_vml_:group>');
+
+ this.element_.insertAdjacentHTML("BeforeEnd",
+ vmlStr.join(""));
+ };
+
+ contextPrototype.stroke = function(aFill) {
+ var lineStr = [];
+ var lineOpen = false;
+ var a = processStyle(aFill ? this.fillStyle : this.strokeStyle);
+ var color = a[0];
+ var opacity = a[1] * this.globalAlpha;
+
+ lineStr.push('<g_vml_:shape',
+ ' fillcolor="', color, '"',
+ ' filled="', Boolean(aFill), '"',
+ ' style="position:absolute;width:10;height:10;"',
+ ' coordorigin="0 0" coordsize="10 10"',
+ ' stroked="', !aFill, '"',
+ ' strokeweight="', this.lineWidth, '"',
+ ' strokecolor="', color, '"',
+ ' path="');
+
+ var newSeq = false;
+ var min = {x: null, y: null};
+ var max = {x: null, y: null};
+
+ for (var i = 0; i < this.currentPath_.length; i++) {
+ var p = this.currentPath_[i];
+
+ if (p.type == "moveTo") {
+ lineStr.push(" m ");
+ var c = this.getCoords_(p.x, p.y);
+ lineStr.push(Math.floor(c.x), ",", Math.floor(c.y));
+ } else if (p.type == "lineTo") {
+ lineStr.push(" l ");
+ var c = this.getCoords_(p.x, p.y);
+ lineStr.push(Math.floor(c.x), ",", Math.floor(c.y));
+ } else if (p.type == "close") {
+ lineStr.push(" x ");
+ } else if (p.type == "bezierCurveTo") {
+ lineStr.push(" c ");
+ var c = this.getCoords_(p.x, p.y);
+ var c1 = this.getCoords_(p.cp1x, p.cp1y);
+ var c2 = this.getCoords_(p.cp2x, p.cp2y);
+ lineStr.push(Math.floor(c1.x), ",", Math.floor(c1.y), ",",
+ Math.floor(c2.x), ",", Math.floor(c2.y), ",",
+ Math.floor(c.x), ",", Math.floor(c.y));
+ } else if (p.type == "arc") {
+ lineStr.push(" ar ");
+ var c = this.getCoords_(p.x, p.y);
+ var cStart = this.getCoords_(p.xStart, p.yStart);
+ var cEnd = this.getCoords_(p.xEnd, p.yEnd);
+
+ // TODO: FIX (matricies (scale+rotation) now buggered this up)
+ // VML arc also doesn't seem able to do rotated non-circular
+ // arcs without parent grouping.
+ var absXScale = this.m_[0][0];
+ var absYScale = this.m_[1][1];
+
+ lineStr.push(Math.floor(c.x - absXScale * p.radius), ",",
+ Math.floor(c.y - absYScale * p.radius), " ",
+ Math.floor(c.x + absXScale * p.radius), ",",
+ Math.floor(c.y + absYScale * p.radius), " ",
+ Math.floor(cStart.x), ",", Math.floor(cStart.y), " ",
+ Math.floor(cEnd.x), ",", Math.floor(cEnd.y));
+ }
+
+
+ // TODO: Following is broken for curves due to
+ // move to proper paths.
+
+ // Figure out dimensions so we can do gradient fills
+ // properly
+ if(c) {
+ if (min.x == null || c.x < min.x) {
+ min.x = c.x;
+ }
+ if (max.x == null || c.x > max.x) {
+ max.x = c.x;
+ }
+ if (min.y == null || c.y < min.y) {
+ min.y = c.y;
+ }
+ if (max.y == null || c.y > max.y) {
+ max.y = c.y;
+ }
+ }
+ }
+ lineStr.push(' ">');
+
+ if (typeof this.fillStyle == "object") {
+ var focus = {x: "50%", y: "50%"};
+ var width = (max.x - min.x);
+ var height = (max.y - min.y);
+ var dimension = (width > height) ? width : height;
+
+ focus.x = Math.floor((this.fillStyle.focus_.x / width) * 100 + 50) + "%";
+ focus.y = Math.floor((this.fillStyle.focus_.y / height) * 100 + 50) + "%";
+
+ var colors = [];
+
+ // inside radius (%)
+ if (this.fillStyle.type_ == "gradientradial") {
+ var inside = (this.fillStyle.radius1_ / dimension * 100);
+
+ // percentage that outside radius exceeds inside radius
+ var expansion = (this.fillStyle.radius2_ / dimension * 100) - inside;
+ } else {
+ var inside = 0;
+ var expansion = 100;
+ }
+
+ var insidecolor = {offset: null, color: null};
+ var outsidecolor = {offset: null, color: null};
+
+ // We need to sort 'colors' by percentage, from 0 > 100 otherwise ie
+ // won't interpret it correctly
+ this.fillStyle.colors_.sort(function (cs1, cs2) {
+ return cs1.offset - cs2.offset;
+ });
+
+ for (var i = 0; i < this.fillStyle.colors_.length; i++) {
+ var fs = this.fillStyle.colors_[i];
+
+ colors.push( (fs.offset * expansion) + inside, "% ", fs.color, ",");
+
+ if (fs.offset > insidecolor.offset || insidecolor.offset == null) {
+ insidecolor.offset = fs.offset;
+ insidecolor.color = fs.color;
+ }
+
+ if (fs.offset < outsidecolor.offset || outsidecolor.offset == null) {
+ outsidecolor.offset = fs.offset;
+ outsidecolor.color = fs.color;
+ }
+ }
+ colors.pop();
+
+ lineStr.push('<g_vml_:fill',
+ ' color="', outsidecolor.color, '"',
+ ' color2="', insidecolor.color, '"',
+ ' type="', this.fillStyle.type_, '"',
+ ' focusposition="', focus.x, ', ', focus.y, '"',
+ ' colors="', colors.join(""), '"',
+ ' opacity="', opacity, '" />');
+ } else if (aFill) {
+ lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity, '" />');
+ } else {
+ lineStr.push(
+ '<g_vml_:stroke',
+ ' opacity="', opacity,'"',
+ ' joinstyle="', this.lineJoin, '"',
+ ' miterlimit="', this.miterLimit, '"',
+ ' endcap="', processLineCap(this.lineCap) ,'"',
+ ' weight="', this.lineWidth, 'px"',
+ ' color="', color,'" />'
+ );
+ }
+
+ lineStr.push("</g_vml_:shape>");
+
+ this.element_.insertAdjacentHTML("beforeEnd", lineStr.join(""));
+
+ this.currentPath_ = [];
+ };
+
+ contextPrototype.fill = function() {
+ this.stroke(true);
+ }
+
+ contextPrototype.closePath = function() {
+ this.currentPath_.push({type: "close"});
+ };
+
+ /**
+ * @private
+ */
+ contextPrototype.getCoords_ = function(aX, aY) {
+ return {
+ x: (aX * this.m_[0][0] + aY * this.m_[1][0] + this.m_[2][0]),
+ y: (aX * this.m_[0][1] + aY * this.m_[1][1] + this.m_[2][1])
+ }
+ };
+
+ contextPrototype.save = function() {
+ var o = {};
+ copyState(this, o);
+ this.aStack_.push(o);
+ this.mStack_.push(this.m_);
+ this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
+ };
+
+ contextPrototype.restore = function() {
+ copyState(this.aStack_.pop(), this);
+ this.m_ = this.mStack_.pop();
+ };
+
+ contextPrototype.translate = function(aX, aY) {
+ var m1 = [
+ [1, 0, 0],
+ [0, 1, 0],
+ [aX, aY, 1]
+ ];
+
+ this.m_ = matrixMultiply(m1, this.m_);
+ };
+
+ contextPrototype.rotate = function(aRot) {
+ var c = Math.cos(aRot);
+ var s = Math.sin(aRot);
+
+ var m1 = [
+ [c, s, 0],
+ [-s, c, 0],
+ [0, 0, 1]
+ ];
+
+ this.m_ = matrixMultiply(m1, this.m_);
+ };
+
+ contextPrototype.scale = function(aX, aY) {
+ var m1 = [
+ [aX, 0, 0],
+ [0, aY, 0],
+ [0, 0, 1]
+ ];
+
+ this.m_ = matrixMultiply(m1, this.m_);
+ };
+
+ /******** STUBS ********/
+ contextPrototype.clip = function() {
+ // TODO: Implement
+ };
+
+ contextPrototype.arcTo = function() {
+ // TODO: Implement
+ };
+
+ contextPrototype.createPattern = function() {
+ return new CanvasPattern_;
+ };
+
+ // Gradient / Pattern Stubs
+ function CanvasGradient_(aType) {
+ this.type_ = aType;
+ this.radius1_ = 0;
+ this.radius2_ = 0;
+ this.colors_ = [];
+ this.focus_ = {x: 0, y: 0};
+ }
+
+ CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
+ aColor = processStyle(aColor);
+ this.colors_.push({offset: 1-aOffset, color: aColor});
+ };
+
+ function CanvasPattern_() {}
+
+ // set up externs
+ G_vmlCanvasManager = G_vmlCanvasManager_;
+ CanvasRenderingContext2D = CanvasRenderingContext2D_;
+ CanvasGradient = CanvasGradient_;
+ CanvasPattern = CanvasPattern_;
+
+})();
+
+} // if
\ No newline at end of file
diff --git a/webroot/js/firebug/errorIcon.png b/webroot/js/firebug/errorIcon.png
new file mode 100644
index 0000000..2d75261
Binary files /dev/null and b/webroot/js/firebug/errorIcon.png differ
diff --git a/webroot/js/firebug/firebug.css b/webroot/js/firebug/firebug.css
new file mode 100644
index 0000000..1f041c4
--- /dev/null
+++ b/webroot/js/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/webroot/js/firebug/firebug.html b/webroot/js/firebug/firebug.html
new file mode 100644
index 0000000..861e639
--- /dev/null
+++ b/webroot/js/firebug/firebug.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+ <title>Firebug</title>
+ <link rel="stylesheet" type="text/css" href="firebug.css">
+</head>
+
+<body>
+ <div id="toolbar" class="toolbar">
+ <a href="#" onclick="parent.console.clear()">Clear</a>
+ <span class="toolbarRight">
+ <a href="#" onclick="parent.console.close()">Close</a>
+ </span>
+ </div>
+ <div id="log"></div>
+ <input type="text" id="commandLine">
+
+ <script>parent.onFirebugReady(document);</script>
+</body>
+</html>
diff --git a/webroot/js/firebug/firebug.js b/webroot/js/firebug/firebug.js
new file mode 100644
index 0000000..eb853b8
--- /dev/null
+++ b/webroot/js/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('<table>');
+ for (var i = 0; i < pairs.length; ++i)
+ {
+ var name = pairs[i][0], value = pairs[i][1];
+
+ html.push('<tr>',
+ '<td class="propertyNameCell"><span class="propertyName">',
+ escapeHTML(name), '</span></td>', '<td><span class="propertyValue">');
+ appendObject(value, html);
+ html.push('</span></td></tr>');
+ }
+ html.push('</table>');
+
+ 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('<span class="objectBox-null">', escapeHTML(objectToString(object)), '</span>');
+ }
+
+ function appendString(object, html)
+ {
+ html.push('<span class="objectBox-string">"', escapeHTML(objectToString(object)),
+ '"</span>');
+ }
+
+ function appendInteger(object, html)
+ {
+ html.push('<span class="objectBox-number">', escapeHTML(objectToString(object)), '</span>');
+ }
+
+ function appendFloat(object, html)
+ {
+ html.push('<span class="objectBox-number">', escapeHTML(objectToString(object)), '</span>');
+ }
+
+ function appendFunction(object, html)
+ {
+ var reName = /function ?(.*?)\(/;
+ var m = reName.exec(objectToString(object));
+ var name = m ? m[1] : "function";
+ html.push('<span class="objectBox-function">', escapeHTML(name), '()</span>');
+ }
+
+ 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('<span class="objectBox-object">', m ? m[1] : text, '</span>')
+ }
+
+ function appendSelector(object, html)
+ {
+ html.push('<span class="objectBox-selector">');
+
+ html.push('<span class="selectorTag">', escapeHTML(object.nodeName.toLowerCase()), '</span>');
+ if (object.id)
+ html.push('<span class="selectorId">#', escapeHTML(object.id), '</span>');
+ if (object.className)
+ html.push('<span class="selectorClass">.', escapeHTML(object.className), '</span>');
+
+ html.push('</span>');
+ }
+
+ function appendNode(node, html)
+ {
+ if (node.nodeType == 1)
+ {
+ html.push(
+ '<div class="objectBox-element">',
+ '<<span class="nodeTag">', node.nodeName.toLowerCase(), '</span>');
+
+ for (var i = 0; i < node.attributes.length; ++i)
+ {
+ var attr = node.attributes[i];
+ if (!attr.specified)
+ continue;
+
+ html.push(' <span class="nodeName">', attr.nodeName.toLowerCase(),
+ '</span>="<span class="nodeValue">', escapeHTML(attr.nodeValue),
+ '</span>"')
+ }
+
+ if (node.firstChild)
+ {
+ html.push('></div><div class="nodeChildren">');
+
+ for (var child = node.firstChild; child; child = child.nextSibling)
+ appendNode(child, html);
+
+ html.push('</div><div class="objectBox-element"></<span class="nodeTag">',
+ node.nodeName.toLowerCase(), '></span></div>');
+ }
+ else
+ html.push('/></div>');
+ }
+ else if (node.nodeType == 3)
+ {
+ html.push('<div class="nodeText">', escapeHTML(node.nodeValue),
+ '</div>');
+ }
+ }
+
+ // ********************************************************************************************
+
+ 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(
+ '<span class="errorMessage">', msg, '</span>',
+ '<div class="objectBox-sourceLink">', fileName, ' (line ', lineNo, ')</div>'
+ );
+
+ 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/webroot/js/firebug/firebugx.js b/webroot/js/firebug/firebugx.js
new file mode 100644
index 0000000..5a467fc
--- /dev/null
+++ b/webroot/js/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/webroot/js/firebug/infoIcon.png b/webroot/js/firebug/infoIcon.png
new file mode 100644
index 0000000..da1e533
Binary files /dev/null and b/webroot/js/firebug/infoIcon.png differ
diff --git a/webroot/js/firebug/warningIcon.png b/webroot/js/firebug/warningIcon.png
new file mode 100644
index 0000000..de51084
Binary files /dev/null and b/webroot/js/firebug/warningIcon.png differ
diff --git a/webroot/js/functions.js b/webroot/js/functions.js
new file mode 100755
index 0000000..f716f58
--- /dev/null
+++ b/webroot/js/functions.js
@@ -0,0 +1,204 @@
+// create my own string trim function
+function trim(s) {
+ while (s.substring(0,1) == ' ') {
+ s = s.substring(1,s.length);
+ }
+ while (s.substring(s.length-1,s.length) == ' ') {
+ s = s.substring(0,s.length-1);
+ }
+ return s;
+}
+
+// unserialize values from php (serialized with backend/utility/functions.php)
+function phpUnserialize(val) {
+ alert('starting');
+ ret = new Array();
+ val = val.split("#^#");
+ for(i=0;i<val.length;i++) {
+ rec = val[i].split("#$#");
+ for(j=0;j<rec.length;j++) {
+ fld = rec[j].split("#:#");
+ ret[i] = new Array();
+ if(trim(fld[0]) != "") {
+ //currKey = "\"" + fld[0] + "\"";
+ if(!fld[1]) { fld[1] = "blank"; }
+ ret[i][fld[0]] = fld[1];
+ }
+ }
+ }
+ alert(ret[2][account_id]);
+}
+
+// get the current value of a form item
+function getValue(ctrl) {
+ ctrl = $(ctrl);
+ switch(ctrl.nodeName.toLowerCase()) {
+ case "select":
+ if(ctrl.options && ctrl.options.length > 0) {
+ try {
+ return ctrl.options[ctrl.selectedIndex].value;
+ } catch(e) {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ break;
+ case "input":
+ if(ctrl.value) {
+ return ctrl.value;
+ } else {
+ return null;
+ }
+ break;
+ }
+}
+
+// give me an assoc array and a form reference and this will populate form
+// assuming matches tween field names & array keys
+function populateForm(frm,ar) {
+ // convert this array into a hash (Prototype.js)
+ ar = $H(ar);
+
+ // reset form
+ frm.reset();
+
+ // populate vals into specs form
+ keys = ar.keys();
+ vals = ar.values();
+ //alert(keys);
+ // iterate, calling my setVal function
+ for(i=0;i<keys.length;i++) {
+ key = keys[i];
+ val = vals[i];
+ // ignore account_id (we get it from $_SESSION)
+ if(key != "account_id") {
+ isSetVal(frm[key],val);
+ }
+ }
+}
+
+// this will handle setting the value of any type of form element
+function isSetVal(el, val) {
+ //alert(el.name + " : " + el.type + " : " + val);
+ switch(el.type) {
+ case 'undefined':
+ // radio buttons should return a type of undefined, but don't
+ // will leave this anyway, since I often use prototype.js and that might be causing prob
+ alert(el.length);
+ if(el.length) {
+ if(el[0].type == 'radio') {
+ for(var i = 0; i < el.length; i++) {
+ if(el[i].value == val) { el[i].checked = true; }
+ }
+ break;
+ }
+ } else {
+ return;
+ }
+ case 'radio':
+ // this doesn't seem to work, but see about prototype.js above
+ for(var i = 0; i < el.length; i++) {
+ if(el[i].value == val) { el[i].checked = true; }
+ }
+ break;
+ case 'checkbox': el.checked = val; break;
+ case 'select-one':
+ for(var i = 0; i < el.options.length; i++) {
+ if(el.options[i].value == val) { el.selectedIndex = i; }
+ }
+ break;
+ case 'select-multiple':
+ // not even gonna attempt this since I never use them
+ break;
+ default:
+ // try the test for radio buttons again
+ if(el.length) {
+ if(el[0].type == 'radio') {
+ for(var i = 0; i < el.length; i++) {
+ if(el[i].value == val) {
+ el[i].checked = true;
+ }
+ }
+ break;
+ }
+ } else {
+ // default - should handle text & textarea
+ el.value = val;
+ }
+ break;
+ }
+}
+
+// function to add hours to a date object
+function addHours(theDate,theHours) {
+ GMTOffset = 5; //we're 5 hours ahead of GMT
+ var date = new Date( Date.UTC(y2k(theDate.getYear()),theDate.getMonth(),theDate.getDate(),theDate.getHours(),theDate.getMinutes(),theDate.getSeconds()) + ((theHours+GMTOffset)*60*60*1000) );
+ return(date);
+}
+
+// required by above
+function y2k(number) {
+ return (number < 1000) ? number + 1900 : number;
+}
+
+/* ---------------------------------------------------------------- */
+/* --------------- Functions specific to Calendar --------------------- */
+/* ---------------------------------------------------------------- */
+
+var allowBubble = true;
+
+// function to trap event clicks
+function eventClick(id) {
+ alert('Show details for event id ' + id);
+ allowBubble = false;
+}
+
+// function to add event
+function addEvent(day,month,year) {
+ if(allowBubble) {
+ //alert('Show event form for ' + month + '/' + day + '/' + year);
+ // test multiday event
+ addMultidayEvent(day,month,year,3)
+ } else {
+ allowBubble = true;
+ }
+}
+
+// function to add event
+function addMultidayEvent(day,month,year,numDays) {
+ cur = $('day_'+day);
+ dup = $('multiday');
+ pos = Position.cumulativeOffset(cur);
+ x=pos[0]; y=pos[1];
+ width = (13 * numDays);
+ dup.style.left = x; dup.style.top = y;dup.style.width=(width + '%');dup.style.display='box';
+ for(i=1;i<32;i++) {
+ $('spacer_' + day).style.display = 'none';
+ }
+ for(i=0;i<numDays;i++) {
+ $('spacer_' + (day+i)).style.height = '13px';
+ $('spacer_' + (day+i)).style.display = 'box';
+ }
+ if(allowBubble) {
+ //alert('Add event for ' + month + '/' + day + '/' + year);
+ } else {
+ allowBubble = true;
+ }
+}
+
+function debug (obj) {
+ d = '';
+ c = 0;
+ for (n in obj) {
+ d += n + " : " + obj[n] + "\n";
+ if (c > 13) {
+ alert(d);
+ d = '';
+ c = 0;
+ }
+ }
+ if (d != '') {
+ alert(d);
+ }
+}
\ No newline at end of file
diff --git a/webroot/js/office.js b/webroot/js/office.js
new file mode 100755
index 0000000..aaa7752
--- /dev/null
+++ b/webroot/js/office.js
@@ -0,0 +1,18 @@
+var Office = function() { };
+
+Office.prototype = {
+
+ publish : function(model, id, set) {
+ alert($('dlgPublishTitle' + model + id));
+ $('dlgPublishTitle' + model + id).innerHTML = "This item is currently <strong>" +
+ (set ? "" : "un") + "published</strong>. Do you want to publish it?";
+ $('dlgPublishID' + model + id).value = id;
+ $('dlgPublishModel' + model + id).value = model;
+ $('dlgPublishSetting' + model + id).value = set;
+ alert($('dlgPublish' + model + id));
+ Effect.SlideDown('dlgPublish' + model + id);
+ }
+
+};
+
+var office = new Office();
\ No newline at end of file
diff --git a/webroot/js/prototype.js b/webroot/js/prototype.js
new file mode 100755
index 0000000..0ab0db7
--- /dev/null
+++ b/webroot/js/prototype.js
@@ -0,0 +1,2008 @@
+/* Prototype JavaScript framework, version 1.5.0_rc0
+ * (c) 2005 Sam Stephenson <sam@conio.net>
+ *
+ * Prototype is freely distributable under the terms of an MIT-style license.
+ * For details, see the Prototype web site: http://prototype.conio.net/
+ *
+/*--------------------------------------------------------------------------*/
+
+var Prototype = {
+ Version: '1.5.0',
+ ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)',
+
+ 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.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;
+ }
+}
+
+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;
+ return function(event) {
+ return __method.call(object, event || window.event);
+ }
+}
+
+Object.extend(Number.prototype, {
+ toColorPart: function() {
+ var digits = this.toString(16);
+ if (this < 16) return '0' + digits;
+ return digits;
+ },
+
+ succ: function() {
+ return this + 1;
+ },
+
+ times: function(iterator) {
+ $R(0, this, true).each(iterator);
+ return this;
+ }
+});
+
+var Try = {
+ these: function() {
+ var returnValue;
+
+ for (var i = 0; i < arguments.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() {
+ setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
+ },
+
+ onTimerEvent: function() {
+ if (!this.currentlyExecuting) {
+ try {
+ this.currentlyExecuting = true;
+ this.callback();
+ } finally {
+ this.currentlyExecuting = false;
+ }
+ }
+ }
+}
+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 += (replacement(match) || '').toString();
+ 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 div = document.createElement('div');
+ var text = document.createTextNode(this);
+ div.appendChild(text);
+ return div.innerHTML;
+ },
+
+ unescapeHTML: function() {
+ var div = document.createElement('div');
+ div.innerHTML = this.stripTags();
+ return div.childNodes[0] ? div.childNodes[0].nodeValue : '';
+ },
+
+ toQueryParams: function() {
+ var pairs = this.match(/^\??(.*)$/)[1].split('&');
+ return pairs.inject({}, function(params, pairString) {
+ var pair = pairString.split('=');
+ params[pair[0]] = pair[1];
+ return params;
+ });
+ },
+
+ toArray: function() {
+ return this.split('');
+ },
+
+ camelize: function() {
+ var oStringList = this.split('-');
+ if (oStringList.length == 1) return oStringList[0];
+
+ var camelizedString = this.indexOf('-') == 0
+ ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
+ : oStringList[0];
+
+ for (var i = 1, len = oStringList.length; i < len; i++) {
+ var s = oStringList[i];
+ camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
+ }
+
+ return camelizedString;
+ },
+
+ inspect: function() {
+ return "'" + this.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;
+
+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 + (object[match[3]] || '').toString();
+ });
+ }
+}
+
+var $break = new Object();
+var $continue = new Object();
+
+var Enumerable = {
+ each: function(iterator) {
+ var index = 0;
+ try {
+ this._each(function(value) {
+ try {
+ iterator(value, index++);
+ } catch (e) {
+ if (e != $continue) throw e;
+ }
+ });
+ } catch (e) {
+ if (e != $break) throw e;
+ }
+ },
+
+ 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 = true;
+ 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(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;
+ },
+
+ 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.collect(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.collect(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.collect(Prototype.K);
+ },
+
+ 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));
+ });
+ },
+
+ inspect: function() {
+ return '#<Enumerable:' + this.toArray().inspect() + '>';
+ }
+}
+
+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; i < iterable.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; i < this.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 != undefined || 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; i < this.length; i++)
+ if (this[i] == object) return i;
+ return -1;
+ },
+
+ reverse: function(inline) {
+ return (inline !== false ? this : this.toArray())._reverse();
+ },
+
+ inspect: function() {
+ return '[' + this.map(Object.inspect).join(', ') + ']';
+ }
+});
+var Hash = {
+ _each: function(iterator) {
+ for (var key in this) {
+ var value = this[key];
+ if (typeof value == 'function') 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($H(this), function(mergedHash, pair) {
+ mergedHash[pair.key] = pair.value;
+ return mergedHash;
+ });
+ },
+
+ toQueryString: function() {
+ return this.map(function(pair) {
+ return pair.map(encodeURIComponent).join('=');
+ }).join('&');
+ },
+
+ inspect: function() {
+ return '#<Hash:{' + this.map(function(pair) {
+ return pair.map(Object.inspect).join(': ');
+ }).join(', ') + '}>';
+ }
+}
+
+function $H(object) {
+ var hash = Object.extend({}, object || {});
+ Object.extend(hash, Enumerable);
+ Object.extend(hash, Hash);
+ return hash;
+}
+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;
+ do {
+ iterator(value);
+ value = value.succ();
+ } while (this.include(value));
+ },
+
+ 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(responderToAdd) {
+ if (!this.include(responderToAdd))
+ this.responders.push(responderToAdd);
+ },
+
+ unregister: function(responderToRemove) {
+ this.responders = this.responders.without(responderToRemove);
+ },
+
+ dispatch: function(callback, request, transport, json) {
+ this.each(function(responder) {
+ if (responder[callback] && 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',
+ parameters: ''
+ }
+ Object.extend(this.options, options || {});
+ },
+
+ responseIsSuccess: function() {
+ return this.transport.status == undefined
+ || this.transport.status == 0
+ || (this.transport.status >= 200 && this.transport.status < 300);
+ },
+
+ responseIsFailure: function() {
+ return !this.responseIsSuccess();
+ }
+}
+
+Ajax.Request = Class.create();
+Ajax.Request.Events =
+ ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
+
+Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
+ initialize: function(url, options) {
+ this.transport = Ajax.getTransport();
+ this.setOptions(options);
+ this.request(url);
+ },
+
+ request: function(url) {
+ var parameters = this.options.parameters || '';
+ if (parameters.length > 0) parameters += '&_=';
+
+ try {
+ this.url = url;
+ if (this.options.method == 'get' && parameters.length > 0)
+ this.url += (this.url.match(/\?/) ? '&' : '?') + parameters;
+
+ Ajax.Responders.dispatch('onCreate', this, this.transport);
+
+ this.transport.open(this.options.method, this.url,
+ this.options.asynchronous);
+
+ if (this.options.asynchronous) {
+ this.transport.onreadystatechange = this.onStateChange.bind(this);
+ setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10);
+ }
+
+ this.setRequestHeaders();
+
+ var body = this.options.postBody ? this.options.postBody : parameters;
+ this.transport.send(this.options.method == 'post' ? body : null);
+
+ } catch (e) {
+ this.dispatchException(e);
+ }
+ },
+
+ setRequestHeaders: function() {
+ var requestHeaders =
+ ['X-Requested-With', 'XMLHttpRequest',
+ 'X-Prototype-Version', Prototype.Version,
+ 'Accept', 'text/javascript, text/html, application/xml, text/xml, */*'];
+
+ if (this.options.method == 'post') {
+ requestHeaders.push('Content-type', this.options.contentType);
+
+ /* Force "Connection: close" for Mozilla browsers to work around
+ * a bug where XMLHttpReqeuest sends an incorrect Content-length
+ * header. See Mozilla Bugzilla #246651.
+ */
+ if (this.transport.overrideMimeType)
+ requestHeaders.push('Connection', 'close');
+ }
+
+ if (this.options.requestHeaders)
+ requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);
+
+ for (var i = 0; i < requestHeaders.length; i += 2)
+ this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]);
+ },
+
+ onStateChange: function() {
+ var readyState = this.transport.readyState;
+ if (readyState != 1)
+ this.respondToReadyState(this.transport.readyState);
+ },
+
+ header: function(name) {
+ try {
+ return this.transport.getResponseHeader(name);
+ } catch (e) {}
+ },
+
+ evalJSON: function() {
+ try {
+ return eval('(' + this.header('X-JSON') + ')');
+ } catch (e) {}
+ },
+
+ evalResponse: function() {
+ try {
+ return eval(this.transport.responseText);
+ } catch (e) {
+ this.dispatchException(e);
+ }
+ },
+
+ respondToReadyState: function(readyState) {
+ var event = Ajax.Request.Events[readyState];
+ var transport = this.transport, json = this.evalJSON();
+
+ if (event == 'Complete') {
+ try {
+ (this.options['on' + this.transport.status]
+ || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
+ || Prototype.emptyFunction)(transport, json);
+ } catch (e) {
+ this.dispatchException(e);
+ }
+
+ if ((this.header('Content-type') || '').match(/^text\/javascript/i))
+ this.evalResponse();
+ }
+
+ try {
+ (this.options['on' + event] || Prototype.emptyFunction)(transport, json);
+ Ajax.Responders.dispatch('on' + event, this, transport, json);
+ } catch (e) {
+ this.dispatchException(e);
+ }
+
+ /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
+ if (event == 'Complete')
+ this.transport.onreadystatechange = Prototype.emptyFunction;
+ },
+
+ 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.containers = {
+ success: container.success ? $(container.success) : $(container),
+ failure: 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, object) {
+ this.updateContent();
+ onComplete(transport, object);
+ }).bind(this);
+
+ this.request(url);
+ },
+
+ updateContent: function() {
+ var receiver = this.responseIsSuccess() ?
+ this.containers.success : this.containers.failure;
+ var response = this.transport.responseText;
+
+ if (!this.options.evalScripts)
+ response = response.stripScripts();
+
+ if (receiver) {
+ if (this.options.insertion) {
+ new this.options.insertion(receiver, response);
+ } else {
+ Element.update(receiver, response);
+ }
+ }
+
+ if (this.responseIsSuccess()) {
+ 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.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 $() {
+ var results = [], element;
+ for (var i = 0; i < arguments.length; i++) {
+ element = arguments[i];
+ if (typeof element == 'string')
+ element = document.getElementById(element);
+ results.push(Element.extend(element));
+ }
+ return results.length < 2 ? results[0] : results;
+}
+
+document.getElementsByClassName = function(className, parentElement) {
+ var children = ($(parentElement) || document.body).getElementsByTagName('*');
+ return $A(children).inject([], function(elements, child) {
+ if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
+ elements.push(Element.extend(child));
+ return elements;
+ });
+}
+
+/*--------------------------------------------------------------------------*/
+
+if (!window.Element)
+ var Element = new Object();
+
+Element.extend = function(element) {
+ if (!element) return;
+ if (_nativeExtensions) return element;
+
+ if (!element._extended && element.tagName && element != window) {
+ var methods = Element.Methods, cache = Element.extend.cache;
+ for (property in methods) {
+ var value = methods[property];
+ if (typeof value == 'function')
+ element[property] = cache.findOrStore(value);
+ }
+ }
+
+ element._extended = true;
+ 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() {
+ for (var i = 0; i < arguments.length; i++) {
+ var element = $(arguments[i]);
+ Element[Element.visible(element) ? 'hide' : 'show'](element);
+ }
+ },
+
+ hide: function() {
+ for (var i = 0; i < arguments.length; i++) {
+ var element = $(arguments[i]);
+ element.style.display = 'none';
+ }
+ },
+
+ show: function() {
+ for (var i = 0; i < arguments.length; i++) {
+ var element = $(arguments[i]);
+ element.style.display = '';
+ }
+ },
+
+ remove: function(element) {
+ element = $(element);
+ element.parentNode.removeChild(element);
+ },
+
+ update: function(element, html) {
+ $(element).innerHTML = html.stripScripts();
+ setTimeout(function() {html.evalScripts()}, 10);
+ },
+
+ replace: function(element, html) {
+ element = $(element);
+ 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);
+ },
+
+ getHeight: function(element) {
+ element = $(element);
+ return element.offsetHeight;
+ },
+
+ classNames: function(element) {
+ return new Element.ClassNames(element);
+ },
+
+ hasClassName: function(element, className) {
+ if (!(element = $(element))) return;
+ return Element.classNames(element).include(className);
+ },
+
+ addClassName: function(element, className) {
+ if (!(element = $(element))) return;
+ return Element.classNames(element).add(className);
+ },
+
+ removeClassName: function(element, className) {
+ if (!(element = $(element))) return;
+ return Element.classNames(element).remove(className);
+ },
+
+ // removes whitespace-only text node children
+ cleanWhitespace: function(element) {
+ element = $(element);
+ for (var i = 0; i < element.childNodes.length; i++) {
+ var node = element.childNodes[i];
+ if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
+ Element.remove(node);
+ }
+ },
+
+ empty: function(element) {
+ return $(element).innerHTML.match(/^\s*$/);
+ },
+
+ childOf: function(element, ancestor) {
+ element = $(element), ancestor = $(ancestor);
+ while (element = element.parentNode)
+ if (element == ancestor) return true;
+ return false;
+ },
+
+ scrollTo: function(element) {
+ element = $(element);
+ var x = element.x ? element.x : element.offsetLeft,
+ y = element.y ? element.y : element.offsetTop;
+ window.scrollTo(x, y);
+ },
+
+ getStyle: function(element, style) {
+ element = $(element);
+ var value = element.style[style.camelize()];
+ if (!value) {
+ if (document.defaultView && document.defaultView.getComputedStyle) {
+ var css = document.defaultView.getComputedStyle(element, null);
+ value = css ? css.getPropertyValue(style) : null;
+ } else if (element.currentStyle) {
+ value = element.currentStyle[style.camelize()];
+ }
+ }
+
+ if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))
+ if (Element.getStyle(element, 'position') == 'static') value = 'auto';
+
+ return value == 'auto' ? null : value;
+ },
+
+ setStyle: function(element, style) {
+ element = $(element);
+ for (var name in style)
+ element.style[name.camelize()] = style[name];
+ },
+
+ getDimensions: function(element) {
+ element = $(element);
+ if (Element.getStyle(element, 'display') != 'none')
+ 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;
+ els.visibility = 'hidden';
+ els.position = 'absolute';
+ els.display = '';
+ var originalWidth = element.clientWidth;
+ var originalHeight = element.clientHeight;
+ els.display = 'none';
+ 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;
+ }
+ }
+ },
+
+ 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 = '';
+ }
+ },
+
+ makeClipping: function(element) {
+ element = $(element);
+ if (element._overflow) return;
+ element._overflow = element.style.overflow;
+ if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
+ element.style.overflow = 'hidden';
+ },
+
+ undoClipping: function(element) {
+ element = $(element);
+ if (element._overflow) return;
+ element.style.overflow = element._overflow;
+ element._overflow = undefined;
+ }
+}
+
+Object.extend(Element, Element.Methods);
+
+var _nativeExtensions = false;
+
+if(!HTMLElement && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
+ var HTMLElement = {}
+ HTMLElement.prototype = document.createElement('div').__proto__;
+}
+
+Element.addMethods = function(methods) {
+ Object.extend(Element.Methods, methods || {});
+
+ if(typeof HTMLElement != 'undefined') {
+ var methods = Element.Methods, cache = Element.extend.cache;
+ for (property in methods) {
+ var value = methods[property];
+ if (typeof value == 'function')
+ HTMLElement.prototype[property] = cache.findOrStore(value);
+ }
+ _nativeExtensions = true;
+ }
+}
+
+Element.addMethods();
+
+var Toggle = new Object();
+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.toLowerCase();
+ if (tagName == 'tbody' || tagName == 'tr') {
+ 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 = '<table><tbody>' + this.content + '</tbody></table>';
+ 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(this.toArray().concat(classNameToAdd).join(' '));
+ },
+
+ remove: function(classNameToRemove) {
+ if (!this.include(classNameToRemove)) return;
+ this.set(this.select(function(className) {
+ return className != classNameToRemove;
+ }).join(' '));
+ },
+
+ toString: function() {
+ return this.toArray().join(' ');
+ }
+}
+
+Object.extend(Element.ClassNames.prototype, Enumerable);
+var Selector = Class.create();
+Selector.prototype = {
+ initialize: function(expression) {
+ this.params = {classNames: []};
+ this.expression = expression.toString().strip();
+ this.parseExpression();
+ this.compileMatcher();
+ },
+
+ parseExpression: function() {
+ function abort(message) { throw 'Parse error in selector: ' + message; }
+
+ if (this.expression == '') abort('empty expression');
+
+ var params = this.params, expr = this.expression, match, modifier, clause, rest;
+ while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) {
+ params.attributes = params.attributes || [];
+ params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''});
+ expr = match[1];
+ }
+
+ if (expr == '*') return this.params.wildcard = true;
+
+ while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) {
+ modifier = match[1], clause = match[2], rest = match[3];
+ switch (modifier) {
+ case '#': params.id = clause; break;
+ case '.': params.classNames.push(clause); break;
+ case '':
+ case undefined: params.tagName = clause.toUpperCase(); break;
+ default: abort(expr.inspect());
+ }
+ expr = rest;
+ }
+
+ if (expr.length > 0) abort(expr.inspect());
+ },
+
+ buildMatchExpression: function() {
+ var params = this.params, conditions = [], clause;
+
+ if (params.wildcard)
+ conditions.push('true');
+ if (clause = params.id)
+ conditions.push('element.id == ' + clause.inspect());
+ if (clause = params.tagName)
+ conditions.push('element.tagName.toUpperCase() == ' + clause.inspect());
+ if ((clause = params.classNames).length > 0)
+ for (var i = 0; i < clause.length; i++)
+ conditions.push('Element.hasClassName(element, ' + clause[i].inspect() + ')');
+ if (clause = params.attributes) {
+ clause.each(function(attribute) {
+ var value = 'element.getAttribute(' + attribute.name.inspect() + ')';
+ var splitValueBy = function(delimiter) {
+ return value + ' && ' + value + '.split(' + delimiter.inspect() + ')';
+ }
+
+ switch (attribute.operator) {
+ case '=': conditions.push(value + ' == ' + attribute.value.inspect()); break;
+ case '~=': conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break;
+ case '|=': conditions.push(
+ splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect()
+ ); break;
+ case '!=': conditions.push(value + ' != ' + attribute.value.inspect()); break;
+ case '':
+ case undefined: conditions.push(value + ' != null'); break;
+ default: throw 'Unknown operator ' + attribute.operator + ' in selector';
+ }
+ });
+ }
+
+ return conditions.join(' && ');
+ },
+
+ compileMatcher: function() {
+ this.match = new Function('element', 'if (!element.tagName) return false; \
+ return ' + this.buildMatchExpression());
+ },
+
+ findElements: function(scope) {
+ var element;
+
+ if (element = $(this.params.id))
+ if (this.match(element))
+ if (!scope || Element.childOf(element, scope))
+ return [element];
+
+ scope = (scope || document).getElementsByTagName(this.params.tagName || '*');
+
+ var results = [];
+ for (var i = 0; i < scope.length; i++)
+ if (this.match(element = scope[i]))
+ results.push(Element.extend(element));
+
+ return results;
+ },
+
+ toString: function() {
+ return this.expression;
+ }
+}
+
+function $$() {
+ return $A(arguments).map(function(expression) {
+ return expression.strip().split(/\s+/).inject([null], function(results, expr) {
+ var selector = new Selector(expr);
+ return results.map(selector.findElements.bind(selector)).flatten();
+ });
+ }).flatten();
+}
+var Field = {
+ clear: function() {
+ for (var i = 0; i < arguments.length; i++)
+ $(arguments[i]).value = '';
+ },
+
+ focus: function(element) {
+ $(element).focus();
+ },
+
+ present: function() {
+ for (var i = 0; i < arguments.length; i++)
+ if ($(arguments[i]).value == '') return false;
+ return true;
+ },
+
+ select: function(element) {
+ $(element).select();
+ },
+
+ activate: function(element) {
+ element = $(element);
+ element.focus();
+ if (element.select)
+ element.select();
+ }
+}
+
+/*--------------------------------------------------------------------------*/
+
+var Form = {
+ serialize: function(form) {
+ var elements = Form.getElements($(form));
+ var queryComponents = new Array();
+
+ for (var i = 0; i < elements.length; i++) {
+ var queryComponent = Form.Element.serialize(elements[i]);
+ if (queryComponent)
+ queryComponents.push(queryComponent);
+ }
+
+ return queryComponents.join('&');
+ },
+
+ getElements: function(form) {
+ form = $(form);
+ var elements = new Array();
+
+ for (var tagName in Form.Element.Serializers) {
+ var tagElements = form.getElementsByTagName(tagName);
+ for (var j = 0; j < tagElements.length; j++)
+ elements.push(tagElements[j]);
+ }
+ return elements;
+ },
+
+ getInputs: function(form, typeName, name) {
+ form = $(form);
+ var inputs = form.getElementsByTagName('input');
+
+ if (!typeName && !name)
+ return inputs;
+
+ var matchingInputs = new Array();
+ for (var i = 0; i < inputs.length; i++) {
+ var input = inputs[i];
+ if ((typeName && input.type != typeName) ||
+ (name && input.name != name))
+ continue;
+ matchingInputs.push(input);
+ }
+
+ return matchingInputs;
+ },
+
+ disable: function(form) {
+ var elements = Form.getElements(form);
+ for (var i = 0; i < elements.length; i++) {
+ var element = elements[i];
+ element.blur();
+ element.disabled = 'true';
+ }
+ },
+
+ enable: function(form) {
+ var elements = Form.getElements(form);
+ for (var i = 0; i < elements.length; i++) {
+ var element = elements[i];
+ element.disabled = '';
+ }
+ },
+
+ findFirstElement: function(form) {
+ return Form.getElements(form).find(function(element) {
+ return element.type != 'hidden' && !element.disabled &&
+ ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
+ });
+ },
+
+ focusFirstElement: function(form) {
+ Field.activate(Form.findFirstElement(form));
+ },
+
+ reset: function(form) {
+ $(form).reset();
+ }
+}
+
+Form.Element = {
+ serialize: function(element) {
+ element = $(element);
+ var method = element.tagName.toLowerCase();
+ var parameter = Form.Element.Serializers[method](element);
+
+ if (parameter) {
+ var key = encodeURIComponent(parameter[0]);
+ if (key.length == 0) return;
+
+ if (parameter[1].constructor != Array)
+ parameter[1] = [parameter[1]];
+
+ return parameter[1].map(function(value) {
+ return key + '=' + encodeURIComponent(value);
+ }).join('&');
+ }
+ },
+
+ getValue: function(element) {
+ element = $(element);
+ var method = element.tagName.toLowerCase();
+ var parameter = Form.Element.Serializers[method](element);
+
+ if (parameter)
+ return parameter[1];
+ }
+}
+
+Form.Element.Serializers = {
+ input: function(element) {
+ switch (element.type.toLowerCase()) {
+ case 'submit':
+ case 'hidden':
+ case 'password':
+ case 'text':
+ return Form.Element.Serializers.textarea(element);
+ case 'checkbox':
+ case 'radio':
+ return Form.Element.Serializers.inputSelector(element);
+ }
+ return false;
+ },
+
+ inputSelector: function(element) {
+ if (element.checked)
+ return [element.name, element.value];
+ },
+
+ textarea: function(element) {
+ return [element.name, element.value];
+ },
+
+ select: function(element) {
+ return Form.Element.Serializers[element.type == 'select-one' ?
+ 'selectOne' : 'selectMany'](element);
+ },
+
+ selectOne: function(element) {
+ var value = '', opt, index = element.selectedIndex;
+ if (index >= 0) {
+ opt = element.options[index];
+ value = opt.value || opt.text;
+ }
+ return [element.name, value];
+ },
+
+ selectMany: function(element) {
+ var value = [];
+ for (var i = 0; i < element.length; i++) {
+ var opt = element.options[i];
+ if (opt.selected)
+ value.push(opt.value || opt.text);
+ }
+ return [element.name, value];
+ }
+}
+
+/*--------------------------------------------------------------------------*/
+
+var $F = Form.Element.getValue;
+
+/*--------------------------------------------------------------------------*/
+
+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();
+ if (this.lastValue != value) {
+ 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() {
+ var elements = Form.getElements(this.element);
+ for (var i = 0; i < elements.length; i++)
+ this.registerCallback(elements[i]);
+ },
+
+ registerCallback: function(element) {
+ if (element.type) {
+ switch (element.type.toLowerCase()) {
+ case 'checkbox':
+ case 'radio':
+ Event.observe(element, 'click', this.onElementEvent.bind(this));
+ break;
+ case 'password':
+ case 'text':
+ case 'textarea':
+ case 'select-one':
+ case 'select-multiple':
+ 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,
+
+ 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; i < Event.observers.length; i++) {
+ Event.stopObserving.apply(this, Event.observers[i]);
+ Event.observers[i][0] = null;
+ }
+ Event.observers = false;
+ },
+
+ observe: function(element, name, observer, useCapture) {
+ var element = $(element);
+ useCapture = useCapture || false;
+
+ if (name == 'keypress' &&
+ (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
+ || element.attachEvent))
+ name = 'keydown';
+
+ this._observeAndCache(element, name, observer, useCapture);
+ },
+
+ stopObserving: function(element, name, observer, useCapture) {
+ var element = $(element);
+ useCapture = useCapture || false;
+
+ if (name == 'keypress' &&
+ (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
+ || element.detachEvent))
+ name = 'keydown';
+
+ if (element.removeEventListener) {
+ element.removeEventListener(name, observer, useCapture);
+ } else if (element.detachEvent) {
+ element.detachEvent('on' + name, observer);
+ }
+ }
+});
+
+/* prevent memory leaks in IE */
+if (navigator.appVersion.match(/\bMSIE\b/))
+ 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;
+ if (typeof element.offsetParent == 'undefined' || typeof element.offsetParent == 'unknown')
+ break;
+ 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) {
+ 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;
+ },
+
+ clone: function(source, target) {
+ source = $(source);
+ target = $(target);
+ target.style.position = 'absolute';
+ var offsets = this.cumulativeOffset(source);
+ target.style.top = offsets[1] + 'px';
+ target.style.left = offsets[0] + 'px';
+ target.style.width = source.offsetWidth + 'px';
+ target.style.height = source.offsetHeight + 'px';
+ },
+
+ 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 {
+ 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 (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
+ 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];
+ }
+}
\ No newline at end of file
diff --git a/webroot/js/rounder.js b/webroot/js/rounder.js
new file mode 100755
index 0000000..5d2534d
--- /dev/null
+++ b/webroot/js/rounder.js
@@ -0,0 +1,481 @@
+/**
+ *
+ * Copyright 2005 Sabre Airline Solutions
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
+ * file except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ **/
+//
+// Very simple changes and extraction by Justin Palmer (http://encytemedia.com) to make rounding
+// work for those who are already using scriptaculous and don't wish to use the full Rico library.
+// Version 0.2 This uses Prototype 1.5's $$ function to apply rounded corners based on css selectors.
+
+Rico = {}
+//-------------------- ricoCorner.js
+Rico.Corner = {
+
+ round: function(e, options) {
+ this._setOptions(options);
+
+ $$(e).each(function(e) {
+ var color = this.options.color;
+ if ( this.options.color == "fromElement" )
+ color = this._background(e);
+
+ var bgColor = this.options.bgColor;
+ if ( this.options.bgColor == "fromParent" )
+ bgColor = this._background(e.offsetParent);
+
+ this._roundCornersImpl(e, color, bgColor);
+
+ }.bind(this));
+
+ },
+
+ _roundCornersImpl: function(e, color, bgColor) {
+ if(this.options.border)
+ this._renderBorder(e,bgColor);
+ if(this._isTopRounded())
+ this._roundTopCorners(e,color,bgColor);
+ if(this._isBottomRounded())
+ this._roundBottomCorners(e,color,bgColor);
+ },
+
+ _renderBorder: function(el,bgColor) {
+ var borderValue = "1px solid " + this._borderColor(bgColor);
+ var borderL = "border-left: " + borderValue;
+ var borderR = "border-right: " + borderValue;
+ var style = "style='" + borderL + ";" + borderR + "'";
+ el.innerHTML = "<div " + style + ">" + el.innerHTML + "</div>"
+ },
+
+ _roundTopCorners: function(el, color, bgColor) {
+ var corner = this._createCorner(bgColor);
+ for(var i=0 ; i < this.options.numSlices ; i++ )
+ corner.appendChild(this._createCornerSlice(color,bgColor,i,"top"));
+ el.style.paddingTop = 0;
+ el.insertBefore(corner,el.firstChild);
+ },
+
+ _roundBottomCorners: function(el, color, bgColor) {
+ var corner = this._createCorner(bgColor);
+ for(var i=(this.options.numSlices-1) ; i >= 0 ; i-- )
+ corner.appendChild(this._createCornerSlice(color,bgColor,i,"bottom"));
+ el.style.paddingBottom = 0;
+ el.appendChild(corner);
+ },
+
+ _createCorner: function(bgColor) {
+ var corner = document.createElement("div");
+ corner.style.backgroundColor = (this._isTransparent() ? "transparent" : bgColor);
+ return corner;
+ },
+
+ _createCornerSlice: function(color,bgColor, n, position) {
+ var slice = document.createElement("span");
+
+ var inStyle = slice.style;
+ inStyle.backgroundColor = color;
+ inStyle.display = "block";
+ inStyle.height = "1px";
+ inStyle.overflow = "hidden";
+ inStyle.fontSize = "1px";
+
+ var borderColor = this._borderColor(color,bgColor);
+ if ( this.options.border && n == 0 ) {
+ inStyle.borderTopStyle = "solid";
+ inStyle.borderTopWidth = "1px";
+ inStyle.borderLeftWidth = "0px";
+ inStyle.borderRightWidth = "0px";
+ inStyle.borderBottomWidth = "0px";
+ inStyle.height = "0px"; // css compliant box model
+ if(/MSIE/.test(navigator.userAgent)) inStyle.height = "1px"; // ie box model
+ inStyle.borderColor = borderColor;
+ }
+ else if(borderColor) {
+ inStyle.borderColor = borderColor;
+ inStyle.borderStyle = "solid";
+ inStyle.borderWidth = "0px 1px";
+ }
+
+ if ( !this.options.compact && (n == (this.options.numSlices-1)) )
+ inStyle.height = "2px";
+
+ this._setMargin(slice, n, position);
+ this._setBorder(slice, n, position);
+ return slice;
+ },
+
+ _setOptions: function(options) {
+ this.options = {
+ corners : "all",
+ color : "fromElement",
+ bgColor : "fromParent",
+ blend : true,
+ border : false,
+ compact : false
+ }
+ Object.extend(this.options, options || {});
+
+ this.options.numSlices = this.options.compact ? 2 : 4;
+ if ( this._isTransparent() )
+ this.options.blend = false;
+ },
+
+ _whichSideTop: function() {
+ if ( this._hasString(this.options.corners, "all", "top") )
+ return "";
+
+ if ( this.options.corners.indexOf("tl") >= 0 && this.options.corners.indexOf("tr") >= 0 )
+ return "";
+
+ if (this.options.corners.indexOf("tl") >= 0)
+ return "left";
+ else if (this.options.corners.indexOf("tr") >= 0)
+ return "right";
+ return "";
+ },
+
+ _whichSideBottom: function() {
+ if ( this._hasString(this.options.corners, "all", "bottom") )
+ return "";
+
+ if ( this.options.corners.indexOf("bl")>=0 && this.options.corners.indexOf("br")>=0 )
+ return "";
+
+ if(this.options.corners.indexOf("bl") >=0)
+ return "left";
+ else if(this.options.corners.indexOf("br")>=0)
+ return "right";
+ return "";
+ },
+
+ _borderColor : function(color,bgColor) {
+ if ( color == "transparent" )
+ return bgColor;
+ else if ( this.options.border )
+ return this.options.border;
+ else if ( this.options.blend )
+ return this._blend( bgColor, color );
+ else
+ return "";
+ },
+
+
+ _setMargin: function(el, n, corners) {
+ var marginSize = this._marginSize(n);
+ var whichSide = corners == "top" ? this._whichSideTop() : this._whichSideBottom();
+
+ if ( whichSide == "left" ) {
+ el.style.marginLeft = marginSize + "px"; el.style.marginRight = "0px";
+ }
+ else if ( whichSide == "right" ) {
+ el.style.marginRight = marginSize + "px"; el.style.marginLeft = "0px";
+ }
+ else {
+ el.style.marginLeft = marginSize + "px"; el.style.marginRight = marginSize + "px";
+ }
+ },
+
+ _setBorder: function(el,n,corners) {
+ var borderSize = this._borderSize(n);
+ var whichSide = corners == "top" ? this._whichSideTop() : this._whichSideBottom();
+ if ( whichSide == "left" ) {
+ el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = "0px";
+ }
+ else if ( whichSide == "right" ) {
+ el.style.borderRightWidth = borderSize + "px"; el.style.borderLeftWidth = "0px";
+ }
+ else {
+ el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = borderSize + "px";
+ }
+ if (this.options.border != false)
+ el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = borderSize + "px";
+ },
+
+ _marginSize: function(n) {
+ if ( this._isTransparent() )
+ return 0;
+
+ var marginSizes = [ 5, 3, 2, 1 ];
+ var blendedMarginSizes = [ 3, 2, 1, 0 ];
+ var compactMarginSizes = [ 2, 1 ];
+ var smBlendedMarginSizes = [ 1, 0 ];
+
+ if ( this.options.compact && this.options.blend )
+ return smBlendedMarginSizes[n];
+ else if ( this.options.compact )
+ return compactMarginSizes[n];
+ else if ( this.options.blend )
+ return blendedMarginSizes[n];
+ else
+ return marginSizes[n];
+ },
+
+ _borderSize: function(n) {
+ var transparentBorderSizes = [ 5, 3, 2, 1 ];
+ var blendedBorderSizes = [ 2, 1, 1, 1 ];
+ var compactBorderSizes = [ 1, 0 ];
+ var actualBorderSizes = [ 0, 2, 0, 0 ];
+
+ if ( this.options.compact && (this.options.blend || this._isTransparent()) )
+ return 1;
+ else if ( this.options.compact )
+ return compactBorderSizes[n];
+ else if ( this.options.blend )
+ return blendedBorderSizes[n];
+ else if ( this.options.border )
+ return actualBorderSizes[n];
+ else if ( this._isTransparent() )
+ return transparentBorderSizes[n];
+ return 0;
+ },
+
+
+ _hasString: function(str) { for(var i=1 ; i<arguments.length ; i++) if (str.indexOf(arguments[i]) >= 0) return true; return false; },
+ _blend: function(c1, c2) { var cc1 = Rico.Color.createFromHex(c1); cc1.blend(Rico.Color.createFromHex(c2)); return cc1; },
+ _background: function(el) { try { return Rico.Color.createColorFromBackground(el).asHex(); } catch(err) { return "#ffffff"; } },
+ _isTransparent: function() { return this.options.color == "transparent"; },
+ _isTopRounded: function() { return this._hasString(this.options.corners, "all", "top", "tl", "tr"); },
+ _isBottomRounded: function() { return this._hasString(this.options.corners, "all", "bottom", "bl", "br"); },
+ _hasSingleTextChild: function(el) { return el.childNodes.length == 1 && el.childNodes[0].nodeType == 3; }
+}
+
+
+
+Rico.Color = Class.create();
+
+Rico.Color.prototype = {
+
+ initialize: function(red, green, blue) {
+ this.rgb = { r: red, g : green, b : blue };
+ },
+
+ setRed: function(r) {
+ this.rgb.r = r;
+ },
+
+ setGreen: function(g) {
+ this.rgb.g = g;
+ },
+
+ setBlue: function(b) {
+ this.rgb.b = b;
+ },
+
+ setHue: function(h) {
+
+ // get an HSB model, and set the new hue...
+ var hsb = this.asHSB();
+ hsb.h = h;
+
+ // convert back to RGB...
+ this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
+ },
+
+ setSaturation: function(s) {
+ // get an HSB model, and set the new hue...
+ var hsb = this.asHSB();
+ hsb.s = s;
+
+ // convert back to RGB and set values...
+ this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
+ },
+
+ setBrightness: function(b) {
+ // get an HSB model, and set the new hue...
+ var hsb = this.asHSB();
+ hsb.b = b;
+
+ // convert back to RGB and set values...
+ this.rgb = Rico.Color.HSBtoRGB( hsb.h, hsb.s, hsb.b );
+ },
+
+ darken: function(percent) {
+ var hsb = this.asHSB();
+ this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.max(hsb.b - percent,0));
+ },
+
+ brighten: function(percent) {
+ var hsb = this.asHSB();
+ this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.min(hsb.b + percent,1));
+ },
+
+ blend: function(other) {
+ this.rgb.r = Math.floor((this.rgb.r + other.rgb.r)/2);
+ this.rgb.g = Math.floor((this.rgb.g + other.rgb.g)/2);
+ this.rgb.b = Math.floor((this.rgb.b + other.rgb.b)/2);
+ },
+
+ isBright: function() {
+ var hsb = this.asHSB();
+ return this.asHSB().b > 0.5;
+ },
+
+ isDark: function() {
+ return ! this.isBright();
+ },
+
+ asRGB: function() {
+ return "rgb(" + this.rgb.r + "," + this.rgb.g + "," + this.rgb.b + ")";
+ },
+
+ asHex: function() {
+ return "#" + this.rgb.r.toColorPart() + this.rgb.g.toColorPart() + this.rgb.b.toColorPart();
+ },
+
+ asHSB: function() {
+ return Rico.Color.RGBtoHSB(this.rgb.r, this.rgb.g, this.rgb.b);
+ },
+
+ toString: function() {
+ return this.asHex();
+ }
+
+};
+
+Rico.Color.createFromHex = function(hexCode) {
+
+ if ( hexCode.indexOf('#') == 0 )
+ hexCode = hexCode.substring(1);
+ var red = hexCode.substring(0,2);
+ var green = hexCode.substring(2,4);
+ var blue = hexCode.substring(4,6);
+ return new Rico.Color( parseInt(red,16), parseInt(green,16), parseInt(blue,16) );
+}
+
+/**
+ * Factory method for creating a color from the background of
+ * an HTML element.
+ */
+Rico.Color.createColorFromBackground = function(elem) {
+
+ var actualColor = Element.getStyle($(elem), "background-color");
+
+ if ( actualColor == "transparent" && elem.parent )
+ return Rico.Color.createColorFromBackground(elem.parent);
+
+ if ( actualColor == null )
+ return new Rico.Color(255,255,255);
+
+ if ( actualColor.indexOf("rgb(") == 0 ) {
+ var colors = actualColor.substring(4, actualColor.length - 1 );
+ var colorArray = colors.split(",");
+ return new Rico.Color( parseInt( colorArray[0] ),
+ parseInt( colorArray[1] ),
+ parseInt( colorArray[2] ) );
+
+ }
+ else if ( actualColor.indexOf("#") == 0 ) {
+ var redPart = parseInt(actualColor.substring(1,3), 16);
+ var greenPart = parseInt(actualColor.substring(3,5), 16);
+ var bluePart = parseInt(actualColor.substring(5), 16);
+ return new Rico.Color( redPart, greenPart, bluePart );
+ }
+ else
+ return new Rico.Color(255,255,255);
+}
+
+Rico.Color.HSBtoRGB = function(hue, saturation, brightness) {
+
+ var red = 0;
+ var green = 0;
+ var blue = 0;
+
+ if (saturation == 0) {
+ red = parseInt(brightness * 255.0 + 0.5);
+ green = red;
+ blue = red;
+ }
+ else {
+ var h = (hue - Math.floor(hue)) * 6.0;
+ var f = h - Math.floor(h);
+ var p = brightness * (1.0 - saturation);
+ var q = brightness * (1.0 - saturation * f);
+ var t = brightness * (1.0 - (saturation * (1.0 - f)));
+
+ switch (parseInt(h)) {
+ case 0:
+ red = (brightness * 255.0 + 0.5);
+ green = (t * 255.0 + 0.5);
+ blue = (p * 255.0 + 0.5);
+ break;
+ case 1:
+ red = (q * 255.0 + 0.5);
+ green = (brightness * 255.0 + 0.5);
+ blue = (p * 255.0 + 0.5);
+ break;
+ case 2:
+ red = (p * 255.0 + 0.5);
+ green = (brightness * 255.0 + 0.5);
+ blue = (t * 255.0 + 0.5);
+ break;
+ case 3:
+ red = (p * 255.0 + 0.5);
+ green = (q * 255.0 + 0.5);
+ blue = (brightness * 255.0 + 0.5);
+ break;
+ case 4:
+ red = (t * 255.0 + 0.5);
+ green = (p * 255.0 + 0.5);
+ blue = (brightness * 255.0 + 0.5);
+ break;
+ case 5:
+ red = (brightness * 255.0 + 0.5);
+ green = (p * 255.0 + 0.5);
+ blue = (q * 255.0 + 0.5);
+ break;
+ }
+ }
+
+ return { r : parseInt(red), g : parseInt(green) , b : parseInt(blue) };
+}
+
+Rico.Color.RGBtoHSB = function(r, g, b) {
+
+ var hue;
+ var saturation;
+ var brightness;
+
+ var cmax = (r > g) ? r : g;
+ if (b > cmax)
+ cmax = b;
+
+ var cmin = (r < g) ? r : g;
+ if (b < cmin)
+ cmin = b;
+
+ brightness = cmax / 255.0;
+ if (cmax != 0)
+ saturation = (cmax - cmin)/cmax;
+ else
+ saturation = 0;
+
+ if (saturation == 0)
+ hue = 0;
+ else {
+ var redc = (cmax - r)/(cmax - cmin);
+ var greenc = (cmax - g)/(cmax - cmin);
+ var bluec = (cmax - b)/(cmax - cmin);
+
+ if (r == cmax)
+ hue = bluec - greenc;
+ else if (g == cmax)
+ hue = 2.0 + redc - bluec;
+ else
+ hue = 4.0 + greenc - redc;
+
+ hue = hue / 6.0;
+ if (hue < 0)
+ hue = hue + 1.0;
+ }
+
+ return { h : hue, s : saturation, b : brightness };
+}
\ No newline at end of file
diff --git a/webroot/js/scriptaculous.js b/webroot/js/scriptaculous.js
new file mode 100755
index 0000000..f61fc57
--- /dev/null
+++ b/webroot/js/scriptaculous.js
@@ -0,0 +1,47 @@
+// Copyright (c) 2005 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.
+
+var Scriptaculous = {
+ Version: '1.6.1',
+ require: function(libraryName) {
+ // inserting via DOM fails in Safari 2.0, so brute force approach
+ document.write('<script type="text/javascript" src="'+libraryName+'"></script>');
+ },
+ load: function() {
+ if((typeof Prototype=='undefined') ||
+ (typeof Element == 'undefined') ||
+ (typeof Element.Methods=='undefined') ||
+ parseFloat(Prototype.Version.split(".")[0] + "." +
+ Prototype.Version.split(".")[1]) < 1.5)
+ throw("script.aculo.us requires the Prototype JavaScript framework >= 1.5.0");
+
+ $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').split(',').each(
+ function(include) { Scriptaculous.require(path+include+'.js') });
+ });
+ }
+}
+
+Scriptaculous.load();
\ No newline at end of file
diff --git a/webroot/js/slider.js b/webroot/js/slider.js
new file mode 100755
index 0000000..c0f1fc0
--- /dev/null
+++ b/webroot/js/slider.js
@@ -0,0 +1,283 @@
+// Copyright (c) 2005 Marty Haught, Thomas Fuchs
+//
+// See http://script.aculo.us for more info
+//
+// 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.
+
+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 : this.handles[0].offsetWidth;
+
+ 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.activeHandle = this.handles[handleIdx];
+ this.activeHandleIdx = handleIdx;
+ this.updateStyles();
+ }
+ handleIdx = handleIdx || this.activeHandleIdx || 0;
+ if(this.initialized && this.restricted) {
+ if((handleIdx>0) && (sliderValue<this.values[handleIdx-1]))
+ sliderValue = this.values[handleIdx-1];
+ if((handleIdx < (this.handles.length-1)) && (sliderValue>this.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 - this.alignY : this.track.offsetWidth - this.alignX);
+ },
+ 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)];
+ if(handle==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;
+
+ 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);
+ // fix AppleWebKit rendering
+ if(navigator.appVersion.indexOf('AppleWebKit')>0) 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/webroot/js/unittest.js b/webroot/js/unittest.js
new file mode 100755
index 0000000..d2c2d81
--- /dev/null
+++ b/webroot/js/unittest.js
@@ -0,0 +1,383 @@
+// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// (c) 2005 Jon Tirsen (http://www.tirsen.com)
+// (c) 2005 Michael Schuerig (http://www.schuerig.de/michael/)
+//
+// 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.
+
+
+// experimental, Firefox-only
+Event.simulateMouse = function(element, eventName) {
+ var options = Object.extend({
+ pointerX: 0,
+ pointerY: 0,
+ buttons: 0
+ }, arguments[2] || {});
+ var oEvent = document.createEvent("MouseEvents");
+ oEvent.initMouseEvent(eventName, true, true, document.defaultView,
+ options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY,
+ false, false, false, false, 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<command.length; i++) {
+ Event.simulateKey(element,'keypress',{charCode:command.charCodeAt(i)});
+ }
+};
+
+var Test = {}
+Test.Unit = {};
+
+// security exception workaround
+Test.Unit.inspect = Object.inspect;
+
+Test.Unit.Logger = Class.create();
+Test.Unit.Logger.prototype = {
+ initialize: function(log) {
+ this.log = $(log);
+ if (this.log) {
+ this._createLogTable();
+ }
+ },
+ start: function(testName) {
+ if (!this.log) return;
+ this.testName = testName;
+ this.lastLogLine = document.createElement('tr');
+ this.statusCell = document.createElement('td');
+ this.nameCell = document.createElement('td');
+ this.nameCell.appendChild(document.createTextNode(testName));
+ this.messageCell = document.createElement('td');
+ this.lastLogLine.appendChild(this.statusCell);
+ this.lastLogLine.appendChild(this.nameCell);
+ this.lastLogLine.appendChild(this.messageCell);
+ this.loglines.appendChild(this.lastLogLine);
+ },
+ finish: function(status, summary) {
+ if (!this.log) return;
+ this.lastLogLine.className = status;
+ this.statusCell.innerHTML = status;
+ this.messageCell.innerHTML = this._toHTML(summary);
+ },
+ message: function(message) {
+ if (!this.log) return;
+ this.messageCell.innerHTML = this._toHTML(message);
+ },
+ summary: function(summary) {
+ if (!this.log) return;
+ this.logsummary.innerHTML = this._toHTML(summary);
+ },
+ _createLogTable: function() {
+ this.log.innerHTML =
+ '<div id="logsummary"></div>' +
+ '<table id="logtable">' +
+ '<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' +
+ '<tbody id="loglines"></tbody>' +
+ '</table>';
+ this.logsummary = $('logsummary')
+ this.loglines = $('loglines');
+ },
+ _toHTML: function(txt) {
+ return txt.escapeHTML().replace(/\n/g,"<br/>");
+ }
+}
+
+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();
+ 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(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"];
+ },
+ // 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<this.tests.length;i++) {
+ if (this.tests[i].errors > 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<this.tests.length;i++) {
+ assertions += this.tests[i].assertions;
+ failures += this.tests[i].failures;
+ errors += this.tests[i].errors;
+ }
+ return (
+ this.tests.length + " tests, " +
+ assertions + " assertions, " +
+ failures + " failures, " +
+ errors + " errors");
+ }
+}
+
+Test.Unit.Assertions = Class.create();
+Test.Unit.Assertions.prototype = {
+ initialize: function() {
+ this.assertions = 0;
+ this.failures = 0;
+ this.errors = 0;
+ this.messages = [];
+ },
+ summary: function() {
+ return (
+ this.assertions + " assertions, " +
+ this.failures + " failures, " +
+ this.errors + " errors" + "\n" +
+ this.messages.join("\n"));
+ },
+ pass: function() {
+ this.assertions++;
+ },
+ fail: function(message) {
+ this.failures++;
+ this.messages.push("Failure: " + message);
+ },
+ info: function(message) {
+ this.messages.push("Info: " + message);
+ },
+ error: function(error) {
+ this.errors++;
+ this.messages.push(error.name + ": "+ error.message + "(" + Test.Unit.inspect(error) +")");
+ },
+ status: function() {
+ if (this.failures > 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); }
+ },
+ 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); }
+ },
+ 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); }
+ },
+ 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);
+ },
+ 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); }
+ },
+ _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;
+ 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); }
+ }
+});
diff --git a/webroot/js/util.js b/webroot/js/util.js
new file mode 100755
index 0000000..c83101b
--- /dev/null
+++ b/webroot/js/util.js
@@ -0,0 +1,521 @@
+// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//
+// See scriptaculous.js for full license.
+
+Object.inspect = function(obj) {
+ var info = [];
+
+ if(typeof obj in ["string","number"]) {
+ return obj;
+ } else {
+ for(property in obj)
+ if(typeof obj[property]!="function")
+ info.push(property + ' => ' +
+ (typeof obj[property] == "string" ?
+ '"' + obj[property] + '"' :
+ obj[property]));
+ }
+
+ return ("'" + obj + "' #" + typeof obj +
+ ": {" + info.join(", ") + "}");
+}
+
+// borrowed from http://www.schuerig.de/michael/javascript/stdext.js
+// Copyright (c) 2005, Michael Schuerig, michael@schuerig.de
+
+Array.flatten = function(array, excludeUndefined) {
+ if (excludeUndefined === undefined) {
+ excludeUndefined = false;
+ }
+ var result = [];
+ var len = array.length;
+ for (var i = 0; i < len; i++) {
+ var el = array[i];
+ if (el instanceof Array) {
+ var flat = el.flatten(excludeUndefined);
+ result = result.concat(flat);
+ } else if (!excludeUndefined || el != undefined) {
+ result.push(el);
+ }
+ }
+ return result;
+};
+
+if (!Array.prototype.flatten) {
+ Array.prototype.flatten = function(excludeUndefined) {
+ return Array.flatten(this, excludeUndefined);
+ }
+}
+
+String.prototype.toArray = function() {
+ var results = [];
+ for (var i = 0; i < this.length; i++)
+ results.push(this.charAt(i));
+ return results;
+}
+
+/*--------------------------------------------------------------------------*/
+
+var Builder = {
+ node: function(elementName) {
+ var element = document.createElement('div');
+ element.innerHTML =
+ "<" + elementName + "></" + elementName + ">";
+
+ // attributes (or text)
+ if(arguments[1])
+ if(this._isStringOrNumber(arguments[1]) ||
+ (arguments[1] instanceof Array)) {
+ this._children(element.firstChild, arguments[1]);
+ } else {
+ var attrs = this._attributes(arguments[1]);
+ if(attrs.length)
+ element.innerHTML = "<" +elementName + " " +
+ attrs + "></" + elementName + ">";
+ }
+
+ // text, or array of children
+ if(arguments[2])
+ this._children(element.firstChild, arguments[2]);
+
+ return element.firstChild;
+ },
+ _text: function(text) {
+ return document.createTextNode(text);
+ },
+ _attributes: function(attributes) {
+ var attrs = [];
+ for(attribute in attributes)
+ attrs.push((attribute=='className' ? 'class' : attribute) +
+ '="' + attributes[attribute].toString().escapeHTML() + '"');
+ return attrs.join(" ");
+ },
+ _children: function(element, children) {
+ 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');
+ }
+}
+
+/* ------------- element ext -------------- */
+
+// adapted from http://dhtmlkitchen.com/learn/js/setstyle/index4.jsp
+// note: Safari return null on elements with display:none; see http://bugzilla.opendarwin.org/show_bug.cgi?id=4125
+// instead of "auto" values returns null so it's easier to use with || constructs
+
+String.prototype.camelize = function() {
+ var oStringList = this.split('-');
+ if(oStringList.length == 1)
+ return oStringList[0];
+ var ret = this.indexOf("-") == 0 ?
+ oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) : oStringList[0];
+ for(var i = 1, len = oStringList.length; i < len; i++){
+ var s = oStringList[i];
+ ret += s.charAt(0).toUpperCase() + s.substring(1)
+ }
+ return ret;
+}
+
+Element.getStyle = function(element, style) {
+ element = $(element);
+ var value = element.style[style.camelize()];
+ if(!value)
+ if(document.defaultView && document.defaultView.getComputedStyle) {
+ var css = document.defaultView.getComputedStyle(element, null);
+ value = (css!=null) ? css.getPropertyValue(style) : null;
+ } else if(element.currentStyle) {
+ value = element.currentStyle[style.camelize()];
+ }
+
+ // If top, left, bottom, or right values have been queried, return "auto" for consistency resaons
+ // if position is "static", as Opera (and others?) returns the pixel values relative to root element
+ // (or positioning context?)
+ if (window.opera && (style == "left" || style == "top" || style == "right" || style == "bottom"))
+ if (Element.getStyle(element, "position") == "static") value = "auto";
+
+ if(value=='auto') value = null;
+ return value;
+}
+
+// converts rgb() and #xxx to #xxxxxx format,
+// returns self (or first argument) if not convertable
+String.prototype.parseColor = function() {
+ 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.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;
+ }
+ }
+}
+
+Element.undoPositioned = function(element) {
+ element = $(element);
+ if(typeof element._madePositioned != "undefined"){
+ element._madePositioned = undefined;
+ element.style.position = "";
+ element.style.top = "";
+ element.style.left = "";
+ element.style.bottom = "";
+ element.style.right = "";
+ }
+}
+
+Element.makeClipping = function(element) {
+ element = $(element);
+ if (typeof element._overflow != 'undefined') return;
+ element._overflow = element.style.overflow;
+ if((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') element.style.overflow = 'hidden';
+}
+
+Element.undoClipping = function(element) {
+ element = $(element);
+ if (typeof element._overflow == 'undefined') return;
+ element.style.overflow = element._overflow;
+ element._overflow = undefined;
+}
+
+Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {
+ var children = $(element).childNodes;
+ var text = "";
+ var classtest = new RegExp("^([^ ]+ )*" + ignoreclass+ "( [^ ]+)*$","i");
+
+ for (var i = 0; i < children.length; i++) {
+ if(children[i].nodeType==3) {
+ text+=children[i].nodeValue;
+ } else {
+ if((!children[i].className.match(classtest)) && children[i].hasChildNodes())
+ text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass);
+ }
+ }
+
+ return text;
+}
+
+Element.setContentZoom = function(element, percent) {
+ element = $(element);
+ element.style.fontSize = (percent/100) + "em";
+ if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
+}
+
+Element.getOpacity = function(element){
+ return parseFloat(Element.getStyle(element, "opacity") || '1');
+}
+
+Element.setOpacity = function(element, value){
+ element= $(element);
+ var els = element.style;
+ if (value == 1){
+ els.opacity = '0.999999';
+ els.filter = null;
+ } else {
+ if(value < 0.00001) value = 0;
+ els.opacity = value;
+ els.filter = "alpha(opacity:"+value*100+")";
+ }
+}
+
+Element.getInlineOpacity = function(element){
+ element= $(element);
+ var op;
+ op = element.style.opacity;
+ if (typeof op != "undefined" && op != "") return op;
+ return "";
+}
+
+Element.setInlineOpacity = function(element, value){
+ element= $(element);
+ var els = element.style;
+ els.opacity = value;
+}
+
+Element.getDimensions = function(element){
+ element = $(element);
+ // All *Width and *Height properties give 0 on elements with display "none", so enable the element temporarily
+ if (element.style.display == "none"){
+ var originalVisibility = element.style.visibility;
+ var originalPosition = element.style.position;
+ element.style.visibility = "hidden";
+ element.style.position = "absolute";
+ element.style.display = "";
+ var originalWidth = element.clientWidth;
+ var originalHeight = element.clientHeight;
+ element.style.display = "none";
+ element.style.position = originalPosition;
+ element.style.visibility = originalVisibility;
+ return {width: originalWidth, height: originalHeight};
+ } else {
+ return {width: element.offsetWidth, height: element.offsetHeight};
+ }
+}
+
+/*--------------------------------------------------------------------------*/
+
+Position.positionedOffset = function(element) {
+ var valueT = 0, valueL = 0;
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+ element = element.offsetParent;
+ if (element) {
+ p = Element.getStyle(element,'position');
+ if(p == 'relative' || p == 'absolute') break;
+ }
+ } while (element);
+ return [valueL, valueT];
+}
+
+// Safari returns margins on body which is incorrect if the child is absolutely positioned.
+// for performance reasons, we create a specialized version of Position.cumulativeOffset for
+// KHTML/WebKit only
+
+if(/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
+ 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];
+ }
+}
+
+Position.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 {
+ valueT -= element.scrollTop || 0;
+ valueL -= element.scrollLeft || 0;
+ } while (element = element.parentNode);
+
+ return [valueL, valueT];
+}
+
+// elements with display:none don't return an offsetParent,
+// fall back to manual calculation
+Position.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;
+}
+
+Position.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";
+}
+
+Position.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';;
+}
+
+Position.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;
+}
+
+/*--------------------------------------------------------------------------*/
+
+Element.Class = {
+ // Element.toggleClass(element, className) toggles the class being on/off
+ // Element.toggleClass(element, className1, className2) toggles between both classes,
+ // defaulting to className1 if neither exist
+ toggle: function(element, className) {
+ if(Element.Class.has(element, className)) {
+ Element.Class.remove(element, className);
+ if(arguments.length == 3) Element.Class.add(element, arguments[2]);
+ } else {
+ Element.Class.add(element, className);
+ if(arguments.length == 3) Element.Class.remove(element, arguments[2]);
+ }
+ },
+
+ // gets space-delimited classnames of an element as an array
+ get: function(element) {
+ element = $(element);
+ return element.className.split(' ');
+ },
+
+ // functions adapted from original functions by Gavin Kistner
+ remove: function(element) {
+ element = $(element);
+ var regEx;
+ for(var i = 1; i < arguments.length; i++) {
+ regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)", 'g');
+ element.className = element.className.replace(regEx, '')
+ }
+ },
+
+ add: function(element) {
+ element = $(element);
+ for(var i = 1; i < arguments.length; i++) {
+ Element.Class.remove(element, arguments[i]);
+ element.className += (element.className.length > 0 ? ' ' : '') + arguments[i];
+ }
+ },
+
+ // returns true if all given classes exist in said element
+ has: function(element) {
+ element = $(element);
+ if(!element || !element.className) return false;
+ var regEx;
+ for(var i = 1; i < arguments.length; i++) {
+ if((typeof arguments[i] == 'object') &&
+ (arguments[i].constructor == Array)) {
+ for(var j = 0; j < arguments[i].length; j++) {
+ regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)");
+ if(!regEx.test(element.className)) return false;
+ }
+ } else {
+ regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)");
+ if(!regEx.test(element.className)) return false;
+ }
+ }
+ return true;
+ },
+
+ // expects arrays of strings and/or strings as optional paramters
+ // Element.Class.has_any(element, ['classA','classB','classC'], 'classD')
+ has_any: function(element) {
+ element = $(element);
+ if(!element || !element.className) return false;
+ var regEx;
+ for(var i = 1; i < arguments.length; i++) {
+ if((typeof arguments[i] == 'object') &&
+ (arguments[i].constructor == Array)) {
+ for(var j = 0; j < arguments[i].length; j++) {
+ regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)");
+ if(regEx.test(element.className)) return true;
+ }
+ } else {
+ regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)");
+ if(regEx.test(element.className)) return true;
+ }
+ }
+ return false;
+ },
+
+ childrenWith: function(element, className) {
+ var children = $(element).getElementsByTagName('*');
+ var elements = new Array();
+
+ for (var i = 0; i < children.length; i++)
+ if (Element.Class.has(children[i], className))
+ elements.push(children[i]);
+
+ return elements;
+ }
+}
\ No newline at end of file
