Add new directory structure. Tags will contain current and past releases, and stable will contain any bug fixes to the current stable release so users won't have to get the potentially broken trunk build to fix a screwup in stable.

Trunk updated to 0.9

git-svn-id: http://kusabax.cultnet.net/svn/repo/stable@176 73d99b7e-4f53-4972-b7b7-2ecd3c4d89ee
This commit is contained in:
Sazpaimon committed 2009-05-27 06:37:50 +00:00
commit 88bacfad9c
316 files changed
+79806

No files matched your search

+968
View File
@@ -0,0 +1,968 @@
/*
Pure Javascript implementation of Uniforum message translation.
Copyright (C) 2008 Joshua I. Miller <unrtst@cpan.org>, all rights reserved
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published
by the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
USA.
=head1 NAME
Javascript Gettext - Javascript implemenation of GNU Gettext API.
*/
Gettext = function (args) {
this.domain = 'messages';
// locale_data will be populated from <link...> if not specified in args
this.locale_data = undefined;
// set options
var options = [ "domain", "locale_data" ];
if (this.isValidObject(args)) {
for (var i in args) {
for (var j=0; j<options.length; j++) {
if (i == options[j]) {
// don't set it if it's null or undefined
if (this.isValidObject(args[i]))
this[i] = args[i];
}
}
}
}
// try to load the lang file from somewhere
this.try_load_lang();
return this;
}
Gettext.context_glue = "\004";
Gettext._locale_data = {};
Gettext.prototype.try_load_lang = function() {
// check to see if language is statically included
if (typeof(this.locale_data) != 'undefined') {
// we're going to reformat it, and overwrite the variable
var locale_copy = this.locale_data;
this.locale_data = undefined;
this.parse_locale_data(locale_copy);
if (typeof(Gettext._locale_data[this.domain]) == 'undefined') {
throw new Error("Error: Gettext 'locale_data' does not contain the domain '"+this.domain+"'");
}
}
// try loading from JSON
// get lang links
var lang_link = this.get_lang_refs();
if (typeof(lang_link) == 'object' && lang_link.length > 0) {
// NOTE: there will be a delay here, as this is async.
// So, any i18n calls made right after page load may not
// get translated.
// XXX: we may want to see if we can "fix" this behavior
for (var i=0; i<lang_link.length; i++) {
var link = lang_link[i];
if (link.type == 'application/json') {
if (! this.try_load_lang_json(link.href) ) {
throw new Error("Error: Gettext 'try_load_lang_json' failed. Unable to exec xmlhttprequest for link ["+link.href+"]");
}
} else if (link.type == 'application/x-po') {
if (! this.try_load_lang_po(link.href) ) {
throw new Error("Error: Gettext 'try_load_lang_po' failed. Unable to exec xmlhttprequest for link ["+link.href+"]");
}
} else {
// TODO: implement the other types (.mo)
throw new Error("TODO: link type ["+link.type+"] found, and support is planned, but not implemented at this time.");
}
}
}
};
// This takes the bin/po2json'd data, and moves it into an internal form
// for use in our lib, and puts it in our object as:
// Gettext._locale_data = {
// domain : {
// head : { headfield : headvalue },
// msgs : {
// msgid : [ msgid_plural, msgstr, msgstr_plural ],
// },
Gettext.prototype.parse_locale_data = function(locale_data) {
if (typeof(Gettext._locale_data) == 'undefined') {
Gettext._locale_data = { };
}
// suck in every domain defined in the supplied data
for (var domain in locale_data) {
// skip empty specs (flexibly)
if ((! locale_data.hasOwnProperty(domain)) || (! this.isValidObject(locale_data[domain])))
continue;
// skip if it has no msgid's
var has_msgids = false;
for (var msgid in locale_data[domain]) {
has_msgids = true;
break;
}
if (! has_msgids) continue;
// grab shortcut to data
var data = locale_data[domain];
// if they specifcy a blank domain, default to "messages"
if (domain == "") domain = "messages";
// init the data structure
if (! this.isValidObject(Gettext._locale_data[domain]) )
Gettext._locale_data[domain] = { };
if (! this.isValidObject(Gettext._locale_data[domain].head) )
Gettext._locale_data[domain].head = { };
if (! this.isValidObject(Gettext._locale_data[domain].msgs) )
Gettext._locale_data[domain].msgs = { };
for (var key in data) {
if (key == "") {
var header = data[key];
for (var head in header) {
var h = head.toLowerCase();
Gettext._locale_data[domain].head[h] = header[head];
}
} else {
Gettext._locale_data[domain].msgs[key] = data[key];
}
}
}
// build the plural forms function
for (var domain in Gettext._locale_data) {
if (this.isValidObject(Gettext._locale_data[domain].head['plural-forms']) &&
typeof(Gettext._locale_data[domain].head.plural_func) == 'undefined') {
// untaint data
var plural_forms = Gettext._locale_data[domain].head['plural-forms'];
var pf_re = new RegExp('^(\\s*nplurals\\s*=\\s*[0-9]+\\s*;\\s*plural\\s*=\\s*(?:\\s|[-\\?\\|&=!<>+*/%:;a-zA-Z0-9_\(\)])+)', 'm');
if (pf_re.test(plural_forms)) {
//ex english: "Plural-Forms: nplurals=2; plural=(n != 1);\n"
//pf = "nplurals=2; plural=(n != 1);";
//ex russian: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 or n%100>=20) ? 1 : 2)
//pf = "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)";
var pf = Gettext._locale_data[domain].head['plural-forms'];
if (! /;\s*$/.test(pf)) pf = pf.concat(';');
/* We used to use eval, but it seems IE has issues with it.
* We now use "new Function", though it carries a slightly
* bigger performance hit.
var code = 'function (n) { var plural; var nplurals; '+pf+' return { "nplural" : nplurals, "plural" : (plural === true ? 1 : plural ? plural : 0) }; };';
Gettext._locale_data[domain].head.plural_func = eval("("+code+")");
*/
var code = 'var plural; var nplurals; '+pf+' return { "nplural" : nplurals, "plural" : (plural === true ? 1 : plural ? plural : 0) };';
Gettext._locale_data[domain].head.plural_func = new Function("n", code);
} else {
throw new Error("Syntax error in language file. Plural-Forms header is invalid ["+plural_forms+"]");
}
// default to english plural form
} else if (typeof(Gettext._locale_data[domain].head.plural_func) == 'undefined') {
Gettext._locale_data[domain].head.plural_func = function (n) {
var p = (n != 1) ? 1 : 0;
return { 'nplural' : 2, 'plural' : p };
};
} // else, plural_func already created
}
return;
};
// try_load_lang_po : do an ajaxy call to load in the .po lang defs
Gettext.prototype.try_load_lang_po = function(uri) {
var data = this.sjax(uri);
if (! data) return;
var domain = this.uri_basename(uri);
var parsed = this.parse_po(data);
var rv = {};
// munge domain into/outof header
if (parsed) {
if (! parsed[""]) parsed[""] = {};
if (! parsed[""]["domain"]) parsed[""]["domain"] = domain;
domain = parsed[""]["domain"];
rv[domain] = parsed;
this.parse_locale_data(rv);
}
return 1;
};
Gettext.prototype.uri_basename = function(uri) {
var rv;
if (rv = uri.match(/^(.*\/)?(.*)/)) {
var ext_strip;
if (ext_strip = rv[2].match(/^(.*)\..+$/))
return ext_strip[1];
else
return rv[2];
} else {
return "";
}
};
Gettext.prototype.parse_po = function(data) {
var rv = {};
var buffer = {};
var lastbuffer = "";
var errors = [];
var lines = data.split("\n");
for (var i=0; i<lines.length; i++) {
// chomp
lines[i] = lines[i].replace(/(\n|\r)+$/, '');
var match;
// Empty line / End of an entry.
if (/^$/.test(lines[i])) {
if (typeof(buffer['msgid']) != 'undefined') {
var msg_ctxt_id = (typeof(buffer['msgctxt']) != 'undefined' &&
buffer['msgctxt'].length) ?
buffer['msgctxt']+Gettext.context_glue+buffer['msgid'] :
buffer['msgid'];
var msgid_plural = (typeof(buffer['msgid_plural']) != 'undefined' &&
buffer['msgid_plural'].length) ?
buffer['msgid_plural'] :
null;
// find msgstr_* translations and push them on
var trans = [];
for (var str in buffer) {
var match;
if (match = str.match(/^msgstr_(\d+)/))
trans[parseInt(match[1])] = buffer[str];
}
trans.unshift(msgid_plural);
// only add it if we've got a translation
// NOTE: this doesn't conform to msgfmt specs
if (trans.length > 1) rv[msg_ctxt_id] = trans;
buffer = {};
lastbuffer = "";
}
// comments
} else if (/^#/.test(lines[i])) {
continue;
// msgctxt
} else if (match = lines[i].match(/^msgctxt\s+(.*)/)) {
lastbuffer = 'msgctxt';
buffer[lastbuffer] = this.parse_po_dequote(match[1]);
// msgid
} else if (match = lines[i].match(/^msgid\s+(.*)/)) {
lastbuffer = 'msgid';
buffer[lastbuffer] = this.parse_po_dequote(match[1]);
// msgid_plural
} else if (match = lines[i].match(/^msgid_plural\s+(.*)/)) {
lastbuffer = 'msgid_plural';
buffer[lastbuffer] = this.parse_po_dequote(match[1]);
// msgstr
} else if (match = lines[i].match(/^msgstr\s+(.*)/)) {
lastbuffer = 'msgstr_0';
buffer[lastbuffer] = this.parse_po_dequote(match[1]);
// msgstr[0] (treak like msgstr)
} else if (match = lines[i].match(/^msgstr\[0\]\s+(.*)/)) {
lastbuffer = 'msgstr_0';
buffer[lastbuffer] = this.parse_po_dequote(match[1]);
// msgstr[n]
} else if (match = lines[i].match(/^msgstr\[(\d+)\]\s+(.*)/)) {
lastbuffer = 'msgstr_'+match[1];
buffer[lastbuffer] = this.parse_po_dequote(match[2]);
// continued string
} else if (/^"/.test(lines[i])) {
buffer[lastbuffer] += this.parse_po_dequote(lines[i]);
// something strange
} else {
errors.push("Strange line ["+i+"] : "+lines[i]);
}
}
// handle the final entry
if (typeof(buffer['msgid']) != 'undefined') {
var msg_ctxt_id = (typeof(buffer['msgctxt']) != 'undefined' &&
buffer['msgctxt'].length) ?
buffer['msgctxt']+Gettext.context_glue+buffer['msgid'] :
buffer['msgid'];
var msgid_plural = (typeof(buffer['msgid_plural']) != 'undefined' &&
buffer['msgid_plural'].length) ?
buffer['msgid_plural'] :
null;
// find msgstr_* translations and push them on
var trans = [];
for (var str in buffer) {
var match;
if (match = str.match(/^msgstr_(\d+)/))
trans[parseInt(match[1])] = buffer[str];
}
trans.unshift(msgid_plural);
// only add it if we've got a translation
// NOTE: this doesn't conform to msgfmt specs
if (trans.length > 1) rv[msg_ctxt_id] = trans;
buffer = {};
lastbuffer = "";
}
// parse out the header
if (rv[""] && rv[""][1]) {
var cur = {};
var hlines = rv[""][1].split(/\\n/);
for (var i=0; i<hlines.length; i++) {
if (! hlines.length) continue;
var pos = hlines[i].indexOf(':', 0);
if (pos != -1) {
var key = hlines[i].substring(0, pos);
var val = hlines[i].substring(pos +1);
var keylow = key.toLowerCase();
if (cur[keylow] && cur[keylow].length) {
errors.push("SKIPPING DUPLICATE HEADER LINE: "+hlines[i]);
} else if (/#-#-#-#-#/.test(keylow)) {
errors.push("SKIPPING ERROR MARKER IN HEADER: "+hlines[i]);
} else {
// remove begining spaces if any
val = val.replace(/^\s+/, '');
cur[keylow] = val;
}
} else {
errors.push("PROBLEM LINE IN HEADER: "+hlines[i]);
cur[hlines[i]] = '';
}
}
// replace header string with assoc array
rv[""] = cur;
} else {
rv[""] = {};
}
// TODO: XXX: if there are errors parsing, what do we want to do?
// GNU Gettext silently ignores errors. So will we.
// alert( "Errors parsing po file:\n" + errors.join("\n") );
return rv;
};
Gettext.prototype.parse_po_dequote = function(str) {
var match;
if (match = str.match(/^"(.*)"/)) {
str = match[1];
}
str = str.replace(/\\"/, "");
return str;
};
// try_load_lang_json : do an ajaxy call to load in the lang defs
Gettext.prototype.try_load_lang_json = function(uri) {
var data = this.sjax(uri);
if (! data) return;
var rv = this.JSON(data);
this.parse_locale_data(rv);
return 1;
};
// this finds all <link> tags, filters out ones that match our
// specs, and returns a list of hashes of those
Gettext.prototype.get_lang_refs = function() {
var langs = new Array();
var links = document.getElementsByTagName("link");
// find all <link> tags in dom; filter ours
for (var i=0; i<links.length; i++) {
if (links[i].rel == 'gettext' && links[i].href) {
if (typeof(links[i].type) == 'undefined' ||
links[i].type == '') {
if (/\.json$/i.test(links[i].href)) {
links[i].type = 'application/json';
} else if (/\.js$/i.test(links[i].href)) {
links[i].type = 'application/json';
} else if (/\.po$/i.test(links[i].href)) {
links[i].type = 'application/x-po';
} else if (/\.mo$/i.test(links[i].href)) {
links[i].type = 'application/x-mo';
} else {
throw new Error("LINK tag with rel=gettext found, but the type and extension are unrecognized.");
}
}
links[i].type = links[i].type.toLowerCase();
if (links[i].type == 'application/json') {
links[i].type = 'application/json';
} else if (links[i].type == 'text/javascript') {
links[i].type = 'application/json';
} else if (links[i].type == 'application/x-po') {
links[i].type = 'application/x-po';
} else if (links[i].type == 'application/x-mo') {
links[i].type = 'application/x-mo';
} else {
throw new Error("LINK tag with rel=gettext found, but the type attribute ["+links[i].type+"] is unrecognized.");
}
langs.push(links[i]);
}
}
return langs;
};
/*
=head2 textdomain( domain )
Set domain for future gettext() calls
A message domain is a set of translatable msgid messages. Usually,
every software package has its own message domain. The domain name is
used to determine the message catalog where a translation is looked up;
it must be a non-empty string.
The current message domain is used by the gettext, ngettext, pgettext,
npgettext functions, and by the dgettext, dcgettext, dngettext, dcngettext,
dpgettext, dcpgettext, dnpgettext and dcnpgettext functions when called
with a NULL domainname argument.
If domainname is not NULL, the current message domain is set to
domainname.
If domainname is undefined, null, or empty string, the function returns
the current message domain.
If successful, the textdomain function returns the current message
domain, after possibly changing it. (ie. if you set a new domain, the
value returned will NOT be the previous domain).
=cut
*/
Gettext.prototype.textdomain = function (domain) {
if (domain && domain.length) this.domain = domain;
return this.domain;
}
/*
=head2 gettext( MSGID )
Returns the translation for B<MSGID>. Example:
alert( gt.gettext("Hello World!\n") );
If no translation can be found, the unmodified B<MSGID> is returned,
i. e. the function can I<never> fail, and will I<never> mess up your
original message.
One common mistake is to interpolate a variable into the string like this:
var translated = gt.gettext("Hello " + full_name);
The interpolation will happen before it's passed to gettext, and it's
unlikely you'll have a translation for every "Hello Tom" and "Hello Dick"
and "Hellow Harry" that may arise.
Use C<strargs()> (see below) to solve this problem:
var translated = Gettext.strargs( gt.gettext("Hello %1"), [full_name] );
This is espeically useful when multiple replacements are needed, as they
may not appear in the same order within the translation. As an English to
French example:
Expected result: "This is the red ball"
English: "This is the %1 %2"
French: "C'est le %2 %1"
Code: Gettext.strargs( gt.gettext("This is the %1 %2"), ["red", "ball"] );
(The example is stupid because neither color nor thing will get
translated here ...).
=head2 dgettext( TEXTDOMAIN, MSGID )
Like gettext(), but retrieves the message for the specified
B<TEXTDOMAIN> instead of the default domain. In case you wonder what
a textdomain is, see above section on the textdomain() call.
=head2 dcgettext( TEXTDOMAIN, MSGID, CATEGORY )
Like dgettext() but retrieves the message from the specified B<CATEGORY>
instead of the default category C<LC_MESSAGES>.
NOTE: the categories are really useless in javascript context. This is
here for GNU Gettext API compatability. In practice, you'll never need
to use this. This applies to all the calls including the B<CATEGORY>.
=head2 ngettext( MSGID, MSGID_PLURAL, COUNT )
Retrieves the correct translation for B<COUNT> items. In legacy software
you will often find something like:
alert( count + " file(s) deleted.\n" );
or
printf(count + " file%s deleted.\n", $count == 1 ? '' : 's');
I<NOTE: javascript lacks a builtin printf, so the above isn't a working example>
The first example looks awkward, the second will only work in English
and languages with similar plural rules. Before ngettext() was introduced,
the best practice for internationalized programs was:
if (count == 1) {
alert( gettext("One file deleted.\n") );
} else {
printf( gettext("%d files deleted.\n"), count );
}
This is a nuisance for the programmer and often still not sufficient
for an adequate translation. Many languages have completely different
ideas on numerals. Some (French, Italian, ...) treat 0 and 1 alike,
others make no distinction at all (Japanese, Korean, Chinese, ...),
others have two or more plural forms (Russian, Latvian, Czech,
Polish, ...). The solution is:
printf( ngettext("One file deleted.\n",
"%d files deleted.\n",
count), // argument to ngettext!
count); // argument to printf!
In English, or if no translation can be found, the first argument
(B<MSGID>) is picked if C<count> is one, the second one otherwise.
For other languages, the correct plural form (of 1, 2, 3, 4, ...)
is automatically picked, too. You don't have to know anything about
the plural rules in the target language, ngettext() will take care
of that.
This is most of the time sufficient but you will have to prove your
creativity in cases like
"%d file(s) deleted, and %d file(s) created.\n"
That said, javascript lacks C<printf()> support. Supplied with Gettext.js
is the C<strargs()> method, which can be used for these cases:
Gettext.strargs( gt.ngettext( "One file deleted.\n",
"%d files deleted.\n",
count), // argument to ngettext!
count); // argument to strargs!
NOTE: the variable replacement isn't done for you, so you must
do it yourself as in the above.
=head2 dngettext( TEXTDOMAIN, MSGID, MSGID_PLURAL, COUNT )
Like ngettext() but retrieves the translation from the specified
textdomain instead of the default domain.
=head2 dcngettext( TEXTDOMAIN, MSGID, MSGID_PLURAL, COUNT, CATEGORY )
Like dngettext() but retrieves the translation from the specified
category, instead of the default category C<LC_MESSAGES>.
=head2 pgettext( MSGCTXT, MSGID )
Returns the translation of MSGID, given the context of MSGCTXT.
Both items are used as a unique key into the message catalog.
This allows the translator to have two entries for words that may
translate to different foreign words based on their context. For
example, the word "View" may be a noun or a verb, which may be
used in a menu as File->View or View->Source.
alert( pgettext( "Verb: To View", "View" ) );
alert( pgettext( "Noun: A View", "View" ) );
The above will both lookup different entries in the message catalog.
In English, or if no translation can be found, the second argument
(B<MSGID>) is returned.
=head2 dpgettext( TEXTDOMAIN, MSGCTXT, MSGID )
Like pgettext(), but retrieves the message for the specified
B<TEXTDOMAIN> instead of the default domain.
=head2 dcpgettext( TEXTDOMAIN, MSGCTXT, MSGID, CATEGORY )
Like dpgettext() but retrieves the message from the specified B<CATEGORY>
instead of the default category C<LC_MESSAGES>.
=head2 npgettext( MSGCTXT, MSGID, MSGID_PLURAL, COUNT )
Like ngettext() with the addition of context as in pgettext().
In English, or if no translation can be found, the second argument
(MSGID) is picked if B<COUNT> is one, the third one otherwise.
=head2 dnpgettext( TEXTDOMAIN, MSGCTXT, MSGID, MSGID_PLURAL, COUNT )
Like npgettext() but retrieves the translation from the specified
textdomain instead of the default domain.
=head2 dcnpgettext( TEXTDOMAIN, MSGCTXT, MSGID, MSGID_PLURAL, COUNT, CATEGORY )
Like dnpgettext() but retrieves the translation from the specified
category, instead of the default category C<LC_MESSAGES>.
=cut
*/
// gettext
Gettext.prototype.gettext = function (msgid) {
var msgctxt;
var msgid_plural;
var n;
var category;
return this.dcnpgettext(null, msgctxt, msgid, msgid_plural, n, category);
};
Gettext.prototype.dgettext = function (domain, msgid) {
var msgctxt;
var msgid_plural;
var n;
var category;
return this.dcnpgettext(domain, msgctxt, msgid, msgid_plural, n, category);
};
Gettext.prototype.dcgettext = function (domain, msgid, category) {
var msgctxt;
var msgid_plural;
var n;
return this.dcnpgettext(domain, msgctxt, msgid, msgid_plural, n, category);
};
// ngettext
Gettext.prototype.ngettext = function (msgid, msgid_plural, n) {
var msgctxt;
var category;
return this.dcnpgettext(null, msgctxt, msgid, msgid_plural, n, category);
};
Gettext.prototype.dngettext = function (domain, msgid, msgid_plural, n) {
var msgctxt;
var category;
return this.dcnpgettext(domain, msgctxt, msgid, msgid_plural, n, category);
};
Gettext.prototype.dcngettext = function (domain, msgid, msgid_plural, n, category) {
var msgctxt;
return this.dcnpgettext(domain, msgctxt, msgid, msgid_plural, n, category, category);
};
// pgettext
Gettext.prototype.pgettext = function (msgctxt, msgid) {
var msgid_plural;
var n;
var category;
return this.dcnpgettext(null, msgctxt, msgid, msgid_plural, n, category);
};
Gettext.prototype.dpgettext = function (domain, msgctxt, msgid) {
var msgid_plural;
var n;
var category;
return this.dcnpgettext(domain, msgctxt, msgid, msgid_plural, n, category);
};
Gettext.prototype.dcpgettext = function (domain, msgctxt, msgid, category) {
var msgid_plural;
var n;
return this.dcnpgettext(domain, msgctxt, msgid, msgid_plural, n, category);
};
// npgettext
Gettext.prototype.npgettext = function (msgctxt, msgid, msgid_plural, n) {
var category;
return this.dcnpgettext(null, msgctxt, msgid, msgid_plural, n, category);
};
Gettext.prototype.dnpgettext = function (domain, msgctxt, msgid, msgid_plural, n) {
var category;
return this.dcnpgettext(domain, msgctxt, msgid, msgid_plural, n, category);
};
// this has all the options, so we use it for all of them.
Gettext.prototype.dcnpgettext = function (domain, msgctxt, msgid, msgid_plural, n, category) {
if (! this.isValidObject(msgid)) return '';
var plural = this.isValidObject(msgid_plural);
var msg_ctxt_id = this.isValidObject(msgctxt) ? msgctxt+Gettext.context_glue+msgid : msgid;
var domainname = this.isValidObject(domain) ? domain :
this.isValidObject(this.domain) ? this.domain :
'messages';
// category is always LC_MESSAGES. We ignore all else
var category_name = 'LC_MESSAGES';
var category = 5;
var locale_data = new Array();
if (typeof(Gettext._locale_data) != 'undefined' &&
this.isValidObject(Gettext._locale_data[domainname])) {
locale_data.push( Gettext._locale_data[domainname] );
} else if (typeof(Gettext._locale_data) != 'undefined') {
// didn't find domain we're looking for. Search all of them.
for (var dom in Gettext._locale_data) {
locale_data.push( Gettext._locale_data[dom] );
}
}
var trans = [];
var found = false;
var domain_used; // so we can find plural-forms if needed
if (locale_data.length) {
for (var i=0; i<locale_data.length; i++) {
var locale = locale_data[i];
if (this.isValidObject(locale.msgs[msg_ctxt_id])) {
// make copy of that array (cause we'll be destructive)
for (var j=0; j<locale.msgs[msg_ctxt_id].length; j++) {
trans[j] = locale.msgs[msg_ctxt_id][j];
}
trans.shift(); // throw away the msgid_plural
domain_used = locale;
found = true;
// only break if found translation actually has a translation.
if ( trans.length > 0 && trans[0].length != 0 )
break;
}
}
}
// default to english if we lack a match, or match has zero length
if ( trans.length == 0 || trans[0].length == 0 ) {
trans = [ msgid, msgid_plural ];
}
var translation = trans[0];
if (plural) {
var p;
if (found && this.isValidObject(domain_used.head.plural_func) ) {
var rv = domain_used.head.plural_func(n);
if (! rv.plural) rv.plural = 0;
if (! rv.nplural) rv.nplural = 0;
// if plurals returned is out of bound for total plural forms
if (rv.nplural <= rv.plural) rv.plural = 0;
p = rv.plural;
} else {
p = (n != 1) ? 1 : 0;
}
if (this.isValidObject(trans[p]))
translation = trans[p];
}
return translation;
};
/*
=head2 strargs (string, argument_array)
string : a string that potentially contains formatting characters.
argument_array : an array of positional replacement values
This is a utility method to provide some way to support positional parameters within a string, as javascript lacks a printf() method.
The format is similar to printf(), but greatly simplified (ie. fewer features).
Any percent signs followed by numbers are replaced with the corrosponding item from the B<argument_array>.
Example:
var string = "%2 roses are red, %1 violets are blue";
var args = new Array("10", "15");
var result = Gettext.strargs(string, args);
// result is "15 roses are red, 10 violets are blue"
The format numbers are 1 based, so the first itme is %1.
A lone percent sign may be escaped by preceeding it with another percent sign.
A percent sign followed by anything other than a number or another percent sign will be passed through as is.
Some more examples should clear up any abmiguity. The following were called with the orig string, and the array as Array("[one]", "[two]") :
orig string "blah" becomes "blah"
orig string "" becomes ""
orig string "%%" becomes "%"
orig string "%%%" becomes "%%"
orig string "%%%%" becomes "%%"
orig string "%%%%%" becomes "%%%"
orig string "tom%%dick" becomes "tom%dick"
orig string "thing%1bob" becomes "thing[one]bob"
orig string "thing%1%2bob" becomes "thing[one][two]bob"
orig string "thing%1asdf%2asdf" becomes "thing[one]asdf[two]asdf"
orig string "%1%2%3" becomes "[one][two]"
orig string "tom%1%%2%aDick" becomes "tom[one]%2%aDick"
This is especially useful when using plurals, as the string will nearly always contain the number.
It's also useful in translated strings where the translator may have needed to move the position of the parameters.
For example:
var count = 14;
Gettext.strargs( gt.ngettext('one banana', '%1 bananas', count), [count] );
NOTE: this may be called as an instance method, or as a class method.
// instance method:
var gt = new Gettext(params);
gt.strargs(string, args);
// class method:
Gettext.strargs(string, args);
=cut
*/
/* utility method, since javascript lacks a printf */
Gettext.strargs = function (str, args) {
// make sure args is an array
if ( null == args ||
'undefined' == typeof(args) ) {
args = [];
} else if (args.constructor != Array) {
args = [args];
}
// NOTE: javascript lacks support for zero length negative look-behind
// in regex, so we must step through w/ index.
// The perl equiv would simply be:
// $string =~ s/(?<!\%)\%([0-9]+)/$args[$1]/g;
// $string =~ s/\%\%/\%/g; # restore escaped percent signs
var newstr = "";
while (true) {
var i = str.indexOf('%');
var match_n;
// no more found. Append whatever remains
if (i == -1) {
newstr += str;
break;
}
// we found it, append everything up to that
newstr += str.substr(0, i);
// check for escpaed %%
if (str.substr(i, 2) == '%%') {
newstr += '%';
str = str.substr((i+2));
// % followed by number
} else if ( match_n = str.substr(i).match(/^%(\d+)/) ) {
var arg_n = parseInt(match_n[1]);
var length_n = match_n[1].length;
if ( arg_n > 0 && args[arg_n -1] != null && typeof(args[arg_n -1]) != 'undefined' )
newstr += args[arg_n -1];
str = str.substr( (i + 1 + length_n) );
// % followed by some other garbage - just remove the %
} else {
newstr += '%';
str = str.substr((i+1));
}
}
return newstr;
}
/* instance method wrapper of strargs */
Gettext.prototype.strargs = function (str, args) {
return Gettext.strargs(str, args);
}
/* verify that something is an array */
Gettext.prototype.isArray = function (thisObject) {
return this.isValidObject(thisObject) && thisObject.constructor == Array;
};
/* verify that an object exists and is valid */
Gettext.prototype.isValidObject = function (thisObject) {
if (null == thisObject) {
return false;
} else if ('undefined' == typeof(thisObject) ) {
return false;
} else {
return true;
}
};
Gettext.prototype.sjax = function (uri) {
var xmlhttp;
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest();
} else if (navigator.userAgent.toLowerCase().indexOf('msie 5') != -1) {
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
} else {
xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
}
if (! xmlhttp)
throw new Error("Your browser doesn't do Ajax. Unable to support external language files.");
xmlhttp.open('GET', uri, false);
try { xmlhttp.send(null); }
catch (e) { return; }
// we consider status 200 and 0 as ok.
// 0 happens when we request local file, allowing this to run on local files
var sjax_status = xmlhttp.status;
if (sjax_status == 200 || sjax_status == 0) {
return xmlhttp.responseText;
} else {
var error = xmlhttp.statusText + " (Error " + xmlhttp.status + ")";
if (xmlhttp.responseText.length) {
error += "\n" + xmlhttp.responseText;
}
alert(error);
return;
}
}
Gettext.prototype.JSON = function (data) {
return eval('(' + data + ')');
}
+830
View File
@@ -0,0 +1,830 @@
var style_cookie;
var style_cookie_txt;
var style_cookie_site;
var kumod_set = false;
var quick_reply = false;
var ispage;
/* IE/Opera fix, because they need to go learn a book on how to use indexOf with arrays */
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function(elt /*, from*/) {
var len = this.length;
var from = Number(arguments[1]) || 0;
from = (from < 0)
? Math.ceil(from)
: Math.floor(from);
if (from < 0)
from += len;
for (; from < len; from++) {
if (from in this &&
this[from] === elt)
return from;
}
return -1;
};
}
/**
*
* UTF-8 data encode / decode
* http://www.webtoolkit.info/
*
**/
var Utf8 = {
// public method for url encoding
encode : function (string) {
string = string.replace(/\r\n/g,"\n");
var utftext = "";
for (var n = 0; n < string.length; n++) {
var c = string.charCodeAt(n);
if (c < 128) {
utftext += String.fromCharCode(c);
}
else if((c > 127) && (c < 2048)) {
utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128);
}
else {
utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128);
}
}
return utftext;
},
// public method for url decoding
decode : function (utftext) {
var string = "";
var i = 0;
var c = c1 = c2 = 0;
while ( i < utftext.length ) {
c = utftext.charCodeAt(i);
if (c < 128) {
string += String.fromCharCode(c);
i++;
}
else if((c > 191) && (c < 224)) {
c2 = utftext.charCodeAt(i+1);
string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
i += 2;
}
else {
c2 = utftext.charCodeAt(i+1);
c3 = utftext.charCodeAt(i+2);
string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
i += 3;
}
}
return string;
}
}
var gt = new Gettext({ 'domain' : 'kusaba' });
function _ (msgid) { return gt.gettext(msgid); }
function toggle(button, area) {
var tog=document.getElementById(area);
if(tog.style.display) {
tog.style.display="";
} else {
tog.style.display="none";
}
button.innerHTML=(tog.style.display)?'+':'&minus;';
set_cookie('nav_show_'+area, tog.style.display?'0':'1', 30);
}
function removeframes() {
var boardlinks = document.getElementsByTagName("a");
for(var i=0;i<boardlinks.length;i++) if(boardlinks[i].className == "boardlink") boardlinks[i].target = "_top";
document.getElementById("removeframes").innerHTML = 'Frames removed.';
return false;
}
function reloadmain() {
if (parent.main) {
parent.main.location.reload();
}
}
function replaceAll( str, from, to ) {
var idx = str.indexOf( from );
while ( idx > -1 ) {
str = str.replace( from, to );
idx = str.indexOf( from );
}
return str;
}
function insert(text) {
if(!ispage || quick_reply) {
var textarea=document.forms.postform.message;
if(textarea) {
if(textarea.createTextRange && textarea.caretPos) { // IE
var caretPos=textarea.caretPos;
caretPos.text=caretPos.text.charAt(caretPos.text.length-1)==" "?text+" ":text;
} else if(textarea.setSelectionRange) { // Firefox
var start=textarea.selectionStart;
var end=textarea.selectionEnd;
textarea.value=textarea.value.substr(0,start)+text+textarea.value.substr(end);
textarea.setSelectionRange(start+text.length,start+text.length);
} else {
textarea.value+=text+" ";
}
textarea.focus();
return false;
}
}
return true;
}
function quote(b, a) {
var v = eval("document." + a + ".message");
v.value += (">>" + b + "\r");
v.focus();
}
function checkhighlight() {
var match;
if(match=/#i([0-9]+)/.exec(document.location.toString()))
if(!document.forms.postform.message.value)
insert(">>" + match[1] + "\n");
if(match=/#([0-9]+)/.exec(document.location.toString()))
highlight(match[1]);
}
function highlight(post, checknopage) {
if ((checknopage && ispage) || ispage) {
// Uncomment the following line to always send the user to the thread if the link was clicked on the board page.
//return;
}
var cells = document.getElementsByTagName("td");
for(var i=0;i<cells.length;i++) if(cells[i].className == "highlight") cells[i].className = "reply";
var reply = document.getElementById("reply" + post);
var replytable = reply.parentNode;
while (replytable.nodeName != 'TABLE') {
replytable = replytable.parentNode;
}
if((reply || document.postform.replythread.value == post) && replytable.parentNode.className != "reflinkpreview") {
if(reply) {
reply.className = "highlight";
}
var match = /^([^#]*)/.exec(document.location.toString());
document.location = match[1] + "#" + post;
return false;
}
return true;
}
function get_password(name) {
var pass = getCookie(name);
if(pass) return pass;
var chars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var pass='';
for(var i=0;i<8;i++) {
var rnd = Math.floor(Math.random()*chars.length);
pass += chars.substring(rnd, rnd+1);
}
set_cookie(name, pass, 365);
return(pass);
}
function togglePassword() {
/* Now IE/Opera safe */
var bSaf = (navigator.userAgent.indexOf('Safari') != -1);
var bOpera = (navigator.userAgent.indexOf('Opera') != -1);
var bMoz = (navigator.appName == 'Netscape');
var passwordbox = document.getElementById("passwordbox");
if (passwordbox) {
var passwordbox_html;
if ((bSaf) || (bOpera) || (bMoz))
passwordbox_html = passwordbox.innerHTML;
else passwordbox_html = passwordbox.text;
passwordbox_html = passwordbox_html.toLowerCase();
var newhtml = '<td></td><td></td>';
if (passwordbox_html == newhtml) {
var newhtml = '<td class="postblock">Mod</td><td><input type="text" name="modpassword" size="28" maxlength="75">&nbsp;<acronym title="Display staff status (Mod/Admin)">D</acronym>:&nbsp;<input type="checkbox" name="displaystaffstatus" checked>&nbsp;<acronym title="Lock">L</acronym>:&nbsp;<input type="checkbox" name="lockonpost">&nbsp;&nbsp;<acronym title="Sticky">S</acronym>:&nbsp;<input type="checkbox" name="stickyonpost">&nbsp;&nbsp;<acronym title="Raw HTML">RH</acronym>:&nbsp;<input type="checkbox" name="rawhtml">&nbsp;&nbsp;<acronym title="Name">N</acronym>:&nbsp;<input type="checkbox" name="usestaffname"></td>';
}
if ((bSaf) || (bOpera) || (bMoz))
passwordbox.innerHTML = newhtml;
else passwordbox.text = newhtml;
}
return false;
}
function toggleOptions(threadid, formid, board) {
if (document.getElementById('opt' + threadid)) {
if (document.getElementById('opt' + threadid).style.display == '') {
document.getElementById('opt' + threadid).style.display = 'none';
document.getElementById('opt' + threadid).innerHTML = '';
} else {
var newhtml = '<td class="label"><label for="formatting">Formatting:</label></td><td colspan="3"><select name="formatting"><option value="" onclick="javascript:document.getElementById(\'formattinginfo' + threadid + '\').innerHTML = \'All formatting is performed by the user.\';">Normal</option><option value="aa" onclick="javascript:document.getElementById(\'formattinginfo' + threadid + '\').innerHTML = \'[aa] and [/aa] will surround your message.\';"';
if (getCookie('kuformatting') == 'aa') {
newhtml += ' selected';
}
newhtml += '>Text Art</option></select> <input type="checkbox" name="rememberformatting"><label for="rememberformatting">Remember</label> <span id="formattinginfo' + threadid + '">';
if (getCookie('kuformatting') == 'aa') {
newhtml += '[aa] and [/aa] will surround your message.';
} else {
newhtml += 'All formatting is performed by the user.';
}
newhtml += '</span></td><td><input type="button" value="Preview" class="submit" onclick="javascript:postpreview(\'preview' + threadid + '\', \'' + board + '\', \'' + threadid + '\', document.' + formid + '.message.value);"></td>';
document.getElementById('opt' + threadid).innerHTML = newhtml;
document.getElementById('opt' + threadid).style.display = '';
}
}
}
function getCookie(name) {
with(document.cookie) {
var regexp=new RegExp("(^|;\\s+)"+name+"=(.*?)(;|$)");
var hit=regexp.exec(document.cookie);
if(hit&&hit.length>2) return Utf8.decode(unescape(replaceAll(hit[2],'+','%20')));
else return '';
}
}
function set_cookie(name,value,days) {
if(days) {
var date=new Date();
date.setTime(date.getTime()+(days*24*60*60*1000));
var expires="; expires="+date.toGMTString();
} else expires="";
document.cookie=name+"="+value+expires+"; path=/";
}
function del_cookie(name) {
document.cookie = name +'=; expires=Thu, 01-Jan-70 00:00:01 GMT; path=/';
}
function set_stylesheet(styletitle, txt, site) {
if (txt) {
if (styletitle == get_default_stylesheet())
del_cookie("kustyle_txt");
else
set_cookie("kustyle_txt",styletitle,365);
} else if (site) {
if (styletitle == get_default_stylesheet())
del_cookie("kustyle_site");
else
set_cookie("kustyle_site",styletitle,365);
} else {
if (styletitle == get_default_stylesheet())
del_cookie("kustyle");
else
set_cookie("kustyle",styletitle,365);
}
var links=document.getElementsByTagName("link");
var found=false;
for(var i=0;i<links.length;i++) {
var rel=links[i].getAttribute("rel");
var title=links[i].getAttribute("title");
if(rel.indexOf("style")!=-1&&title) {
links[i].disabled=true; // IE needs this to work. IE needs to die.
if(styletitle==title) { links[i].disabled=false; found=true; }
}
}
if(!found) set_preferred_stylesheet();
}
function set_preferred_stylesheet() {
var links=document.getElementsByTagName("link");
for(var i=0;i<links.length;i++) {
var rel=links[i].getAttribute("rel");
var title=links[i].getAttribute("title");
if(rel.indexOf("style")!=-1&&title) links[i].disabled=(rel.indexOf("alt")!=-1);
}
}
function get_active_stylesheet() {
var links=document.getElementsByTagName("link");
for(var i=0;i<links.length;i++) {
var rel=links[i].getAttribute("rel");
var title=links[i].getAttribute("title");
if(rel.indexOf("style")!=-1&&title&&!links[i].disabled) return title;
}
return null;
}
function get_preferred_stylesheet() {
var links=document.getElementsByTagName("link");
for(var i=0;i<links.length;i++) {
var rel=links[i].getAttribute("rel");
var title=links[i].getAttribute("title");
if(rel.indexOf("style")!=-1&&rel.indexOf("alt")==-1&&title) return title;
}
return null;
}
function get_default_stylesheet() {
var links=document.getElementsByTagName("link");
for(var i=0;i<links.length;i++) {
var rel=links[i].getAttribute("rel");
var title=links[i].getAttribute("title");
if(rel.indexOf("style")!=-1&&title&&rel!='alternate stylesheet') return title;
}
return null;
}
function delandbanlinks() {
if (!kumod_set) return;
togglePassword();
var bottombox = document.getElementById("fileonly");
if (bottombox) {
bottombox = bottombox.parentNode;
bottombox.innerHTML = '[<input type="checkbox" name="fileonly" id="fileonly" value="on" /><label for="fileonly">File Only</label>] <input name="moddelete" onclick="return confirm(_(\'Are you sure you want to delete these posts?\'))" value="'+_('Delete')+'" type="submit" /> <input name="modban" value="'+_('Ban')+'" onclick="this.form.action=\''+ ku_cgipath + '/manage_page.php?action=bans\';" type="submit" />';
}
var dnbelements = document.getElementsByTagName('span');
var dnbelement;
var dnbinfo;
for(var i=0;i<dnbelements.length;i++){
dnbelement = dnbelements[i];
if (dnbelement.getAttribute('id')) {
if (dnbelement.getAttribute('id').substr(0, 3) == 'dnb') {
dnbinfo = dnbelement.getAttribute('id').split('-');
dnbelement.innerHTML = "";
var newhtml = "";
new Ajax.Request(ku_boardspath + "/manage_page.php?action=getip&boarddir=" + dnbinfo[1] + "&id=" + dnbinfo[2], {
method: "get",
onSuccess: function(ip) {
ipaddr = ip.responseText.split("=") || "what are you doing get out you don't even fit";
span = document.getElementById(ipaddr[0]);
span.innerHTML = "[IP: "+ipaddr[1]+" <a href=\"" + ku_boardspath + "/manage_page.php?action=deletepostsbyip&ip="+ipaddr[1]+"\" title=\"" + _('Delete all posts by this IP') + "\">D</a> / <a href=\"" + ku_boardspath + "/manage_page.php?action=ipsearch&ip="+ipaddr[1]+"\" title=\"" + _('Search for posts with this IP') + "\">S</a>] " + span.innerHTML;
},
onFailure: function() {
newhtml = "[what are you doing get out you don't even fit]";
dnbelement.innerHTML = newhtml
}
})
newhtml += '&#91;<a href="' + ku_cgipath + '/manage_page.php?action=delposts&boarddir=' + dnbinfo[1] + '&del';
if (dnbinfo[3] == 'y') {
newhtml += 'thread';
} else {
newhtml += 'post';
}
newhtml += 'id=' + dnbinfo[2] + '" title="' + _('Delete') + '" onclick="return confirm(_(\'Are you sure you want to delete this post/thread?\'));">D<\/a>&nbsp;<a href="' + ku_cgipath + '/manage_page.php?action=delposts&boarddir=' + dnbinfo[1] + '&del';
if (dnbinfo[3] == 'y') {
newhtml += 'thread';
} else {
newhtml += 'post';
}
newhtml += 'id=' + dnbinfo[2] + '&postid=' + dnbinfo[2] + '" title="' + _('Delete &amp; Ban') + '" onclick="return confirm(_(\'Are you sure you want to delete and ban the poster of this post/thread?\'));">&amp;<\/a>&nbsp;<a href="' + ku_cgipath + '/manage_page.php?action=bans&banboard=' + dnbinfo[1] + '&banpost=' + dnbinfo[2] + '" title="' + _('Ban') + '">B<\/a>&#93;&nbsp;&#91;<a href="' + ku_cgipath + '/manage_page.php?action=bans&banboard=' + dnbinfo[1] + '&banpost=' + dnbinfo[2] + '&instant=y" title="' + _('Instant Permanent Ban') + '" onclick="instantban(\'' + dnbinfo[1] + '\',' + dnbinfo[2] + '); return false;">P<\/a>&#93;&nbsp;&#91;<a href="' + ku_cgipath + '/manage_page.php?action=delposts&boarddir=' + dnbinfo[1] + '&del';
if (dnbinfo[3] == 'y') {
newhtml += 'thread';
} else {
newhtml += 'post';
}
newhtml += 'id=' + dnbinfo[2] + '&postid=' + dnbinfo[2] + '&cp=y" title="' + _('Child Pornography') + '" onclick="return confirm(_(\'Are you sure that this is child pornography?\'));">CP<\/a>&#93;';
dnbelements[i].innerHTML = newhtml;
}
}
}
}
function instantban(boardid, postid) {
var reason = prompt(_('Are you sure you want to permenently ban the poster of this post/thread?\nIf so enter a ban message or click OK to use the default ban reason. To cancel this operation, click "Cancel".'));
if (reason !== null) {
var url = ku_cgipath + '/manage_page.php?action=bans&banboard=' + boardid + '&banpost=' + postid + '&instant=y';
if (reason != '') {
url += '&reason=' + reason;
}
new Ajax.Request(url,{
method: "get",
onSuccess: function(ban) {
var message = ban.responseText || "Ban failed!";
if (message == "success")
alert(_("Ban was sucessful."));
else
alert(_("Ban failed!"));
},
onFailure: function() {
alert(_("Ban failed!"));
}
})
}
else {
alert(_("OK, no action taken."));
}
}
function togglethread(threadid) {
if (hiddenthreads.toString().indexOf(threadid)!==-1) {
document.getElementById('unhidethread' + threadid).style.display = 'none';
document.getElementById('thread' + threadid).style.display = 'block';
hiddenthreads.splice(hiddenthreads.indexOf(threadid),1);
set_cookie('hiddenthreads',hiddenthreads.join('!'),30);
} else {
document.getElementById('unhidethread' + threadid).style.display = 'block';
document.getElementById('thread' + threadid).style.display = 'none';
hiddenthreads.push(threadid);
set_cookie('hiddenthreads',hiddenthreads.join('!'),30);
}
return false;
}
function toggleblotter(save) {
var elem = document.getElementsByTagName('li');
var arr = new Array();
var blotterentry;
for(i = 0,iarr = 0; i < elem.length; i++) {
att = elem[i].getAttribute('class');
if(att == 'blotterentry') {
blotterentry = elem[i];
if (blotterentry.style.display == 'none') {
blotterentry.style.display = '';
if (save) {
set_cookie('ku_showblotter', '1', 365);
}
} else {
blotterentry.style.display = 'none';
if (save) {
set_cookie('ku_showblotter', '0', 365);
}
}
}
}
}
function expandthread(threadid, board) {
if (document.getElementById('replies' + threadid + board)) {
var repliesblock = document.getElementById('replies' + threadid + board);
repliesblock.innerHTML = _('Expanding thread') + '...<br><br>' + repliesblock.innerHTML;
new Ajax.Request(ku_boardspath + '/expand.php?board=' + board + '&threadid=' + threadid,
{
method:'get',
onSuccess: function(transport){
var response = transport.responseText || _("something went wrong (blank response)");
repliesblock.innerHTML = response;
delandbanlinks();
addpreviewevents();
},
onFailure: function(){ alert(_('Something went wrong...')) }
});
}
return false;
}
function quickreply(threadid) {
if (threadid == 0) {
quick_reply = false;
document.getElementById('posttypeindicator').innerHTML = 'new thread'
} else {
quick_reply = true;
document.getElementById('posttypeindicator').innerHTML = 'reply to ' + threadid + ' [<a href="#postbox" onclick="javascript:quickreply(\'0\');" title="Cancel">x</a>]';
}
document.postform.replythread.value = threadid;
}
function getwatchedthreads(threadid, board) {
if (document.getElementById('watchedthreadlist')) {
var watchedthreadbox = document.getElementById('watchedthreadlist');
watchedthreadbox.innerHTML = _('Loading watched threads...');
new Ajax.Request(ku_boardspath + '/threadwatch.php?board=' + board + '&threadid=' + threadid,
{
method:'get',
onSuccess: function(transport){
var response = transport.responseText || _("something went wrong (blank response)");
watchedthreadbox.innerHTML = response;
},
onFailure: function(){ alert(_('Something went wrong...')) }
});
}
}
function addtowatchedthreads(threadid, board) {
if (document.getElementById('watchedthreadlist')) {
new Ajax.Request(ku_boardspath + '/threadwatch.php?do=addthread&board=' + board + '&threadid=' + threadid,
{
method:'get',
onSuccess: function(transport){
var response = transport.responseText || _("something went wrong (blank response)");
alert('Thread successfully added to your watch list.');
getwatchedthreads('0', board);
},
onFailure: function(){ alert(_('Something went wrong...')) }
});
}
}
function removefromwatchedthreads(threadid, board) {
if (document.getElementById('watchedthreadlist')) {
new Ajax.Request(ku_boardspath + '/threadwatch.php?do=removethread&board=' + board + '&threadid=' + threadid,
{
method:'get',
onSuccess: function(transport){
var response = transport.responseText || _("something went wrong (blank response)");
getwatchedthreads('0', board);
},
onFailure: function(){ alert(_('Something went wrong...')) }
});
}
}
function hidewatchedthreads() {
set_cookie('showwatchedthreads','0',30);
if (document.getElementById('watchedthreads')) {
document.getElementById('watchedthreads').innerHTML = _('The Watched Threads box will be hidden the next time a page is loaded.') + ' [<a href="#" onclick="javascript:showwatchedthreads();return false">' + _('undo') + '</a>]';
}
}
function showwatchedthreads() {
set_cookie('showwatchedthreads','1',30);
window.location.reload(true);
}
function checkcaptcha(formid) {
if (document.getElementById(formid)) {
if (document.getElementById(formid).captcha) {
if (document.getElementById(formid).captcha.value == '') {
alert('Please enter the captcha image text.');
document.getElementById(formid).captcha.focus();
return false;
}
}
}
return true;
}
// Thanks to 7chan for this
function expandimg(PN, H, F, C, G, E, A) {
element = document.getElementById("thumb" + PN);
var D = '<img src="' + F + '" alt="' + PN + '" class="thumb" width="' + E + '" height="' + A + '">';
var J = '<img src="' + F + '" alt="' + PN + '" class="thumb" height="' + A + '" width="' + E + '">';
var K = '<img src="' + F + '" alt="' + PN + '" class="thumb" height="' + A + '" width="' + E + '"/>';
var B = "<img class=thumb height=" + A + " alt=" + PN + ' src="' + F + '" width=' + E + ">";
if (element.innerHTML.toLowerCase() != D && element.innerHTML.toLowerCase() != B && element.innerHTML.toLowerCase() != J && element.innerHTML.toLowerCase() != K) {
element.innerHTML = D
} else {
element.innerHTML = '<img src="' + H + '" alt="' + PN + '" class="thumb" height="' + G + '" width="' + C + '">'
}
}
function postpreview(divid, board, parentid, message) {
if (document.getElementById(divid)) {
new Ajax.Request(ku_boardspath + '/expand.php?preview&board=' + board + '&parentid=' + parentid + '&message=' + escape(message),
{
method:'get',
onSuccess: function(transport){
var response = transport.responseText || _("something went wrong (blank response)");
document.getElementById(divid).innerHTML = response;
},
onFailure: function(){ alert(_('Something went wrong...')) }
});
}
}
function set_inputs(id) {
if (document.getElementById(id)) {
with(document.getElementById(id)) {
if(!name.value) name.value = getCookie("name");
if(!em.value) em.value = getCookie("email");
if(!postpassword.value) postpassword.value = get_password("postpassword");
}
}
}
function set_delpass(id) {
if (document.getElementById(id).postpassword) {
with(document.getElementById(id)) {
if(!postpassword.value) postpassword.value = get_password("postpassword");
}
}
}
function addreflinkpreview(e) {
var e_out;
var ie_var = "srcElement";
var moz_var = "href";
this[moz_var] ? e_out = this : e_out = e[ie_var];
ainfo = e_out.className.split('|');
var previewdiv = document.createElement('div');
previewdiv.setAttribute("id", "preview" + e_out.className);
previewdiv.setAttribute('class', 'reflinkpreview');
previewdiv.setAttribute('className', 'reflinkpreview');
if (e.pageX) {
previewdiv.style.left = '' + (e.pageX + 50) + 'px';
} else {
previewdiv.style.left = (e.clientX + 50);
}
var previewdiv_content = document.createTextNode('');
previewdiv.appendChild(previewdiv_content);
var parentelement = e_out.parentNode;
var newelement = parentelement.insertBefore(previewdiv, e_out);
new Ajax.Request(ku_boardspath + '/read.php?b=' + ainfo[1] + '&t=' + ainfo[2] + '&p=' + ainfo[3] + '&single',
{
method:'get',
onSuccess: function(transport){
var response = transport.responseText || _("something went wrong (blank response)");
newelement.innerHTML = response;
},
onFailure: function(){ alert('wut'); }
});
}
function delreflinkpreview(e) {
var e_out;
var ie_var = "srcElement";
var moz_var = "href";
this[moz_var] ? e_out = this : e_out = e[ie_var];
var previewelement = document.getElementById("preview" + e_out.className);
if (previewelement) {
previewelement.parentNode.removeChild(previewelement);
}
}
function addpreviewevents() {
var aelements = document.getElementsByTagName('a');
var aelement;
var ainfo;
for(var i=0;i<aelements.length;i++){
aelement = aelements[i];
if (aelement.className) {
if (aelement.className.substr(0, 4) == "ref|") {
if (aelement.addEventListener){
aelement.addEventListener("mouseover", addreflinkpreview, false);
aelement.addEventListener("mouseout", delreflinkpreview, false);
}
else if (aelement.attachEvent){
aelement.attachEvent("onmouseover", addreflinkpreview);
aelement.attachEvent("onmouseout", delreflinkpreview);
}
}
}
}
}
function keypress(e) {
if (!e) e=window.event;
if (e.altKey) {
var docloc = document.location.toString();
if ((docloc.indexOf('catalog.html') == -1 && docloc.indexOf('/res/') == -1) || (docloc.indexOf('catalog.html') == -1 && e.keyCode == 80)) {
if (e.keyCode != 18 && e.keyCode != 16) {
if (docloc.indexOf('.html') == -1 || docloc.indexOf('board.html') != -1) {
var page = 0;
var docloc_trimmed = docloc.substr(0, docloc.lastIndexOf('/') + 1);
} else {
var page = docloc.substr((docloc.lastIndexOf('/') + 1));
page = (+page.substr(0, page.indexOf('.html')));
var docloc_trimmed = docloc.substr(0, docloc.lastIndexOf('/') + 1);
}
if (page == 0) {
var docloc_valid = docloc_trimmed;
} else {
var docloc_valid = docloc_trimmed + page + '.html';
}
if (e.keyCode == 222 || e.keyCode == 221) {
if(match=/#s([0-9])/.exec(docloc)) {
var relativepost = (+match[1]);
} else {
var relativepost = -1;
}
if (e.keyCode == 222) {
if (relativepost == -1 || relativepost == 9) {
var newrelativepost = 0;
} else {
var newrelativepost = relativepost + 1;
}
} else if (e.keyCode == 221) {
if (relativepost == -1 || relativepost == 0) {
var newrelativepost = 9;
} else {
var newrelativepost = relativepost - 1;
}
}
document.location.href = docloc_valid + '#s' + newrelativepost;
} else if (e.keyCode == 59 || e.keyCode == 219) {
if (e.keyCode == 59) {
page = page + 1;
} else if (e.keyCode == 219) {
if (page >= 1) {
page = page - 1;
}
}
if (page == 0) {
document.location.href = docloc_trimmed;
} else {
document.location.href = docloc_trimmed + page + '.html';
}
} else if (e.keyCode == 80) {
document.location.href = docloc_valid + '#postbox';
}
}
}
}
}
window.onload=function(e) {
if (getCookie("kumod") == "allboards") {
kumod_set = true
}
else if(getCookie("kumod") != "") {
var listofboards = getCookie("kumod").split('|');
var thisboard = document.getElementById("postform").board.value;
for (var cookieboard in listofboards) {
if (listofboards[cookieboard] == thisboard) {
kumod_set = true;
break
}
}
}
delandbanlinks();
addpreviewevents();
checkhighlight();
if (document.getElementById('watchedthreads')) {
var watchedthreadsdrag = new Draggable('watchedthreads', {handle:'watchedthreadsdraghandle',onEnd:function() { watchedthreadsdragend(); }})
var watchedthreadsresize = new Resizeable('watchedthreads', {resize:function() { watchedthreadsresizeend(); }})
function watchedthreadsdragend() {
set_cookie('watchedthreadstop',document.getElementById('watchedthreads').style.top,30);
set_cookie('watchedthreadsleft',document.getElementById('watchedthreads').style.left,30);
}
function watchedthreadsresizeend() {
var watchedthreadswidth = document.getElementById('watchedthreads').offsetWidth;
var watchedthreadsheight = document.getElementById('watchedthreads').offsetHeight;
set_cookie('watchedthreadswidth',watchedthreadswidth,30);
set_cookie('watchedthreadsheight',watchedthreadsheight,30);
}
}
document.onkeydown = keypress;
}
if(style_cookie) {
var cookie = getCookie(style_cookie);
var title = cookie ? cookie : get_preferred_stylesheet();
if (title != get_active_stylesheet())
set_stylesheet(title);
}
if(style_cookie_txt) {
var cookie=getCookie(style_cookie_txt);
var title=cookie?cookie:get_preferred_stylesheet();
set_stylesheet(title, true);
}
if(style_cookie_site) {
var cookie=getCookie(style_cookie_site);
var title=cookie?cookie:get_preferred_stylesheet();
set_stylesheet(title, false, true);
}
+23
View File
@@ -0,0 +1,23 @@
function toggle(button, area) {
var tog=document.getElementById(area);
if(tog.style.display) {
tog.style.display="";
} else {
tog.style.display="none";
}
button.innerHTML=(tog.style.display)?'+':'&minus;';
set_cookie('nav_show_'+area, tog.style.display?'0':'1', 30);
}
function removeframes() {
var boardlinks = document.getElementsByTagName("a");
for(var i=0;i<boardlinks.length;i++) if(boardlinks[i].className == "boardlink") boardlinks[i].target = "_top";
document.getElementById("removeframes").innerHTML = 'Frames removed.';
return false;
}
function reloadmain() {
if (parent.main) {
parent.main.location.reload();
}
}
+3277
View File
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,20 @@
Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,136 @@
// script.aculo.us builder.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007
// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/
var Builder = {
NODEMAP: {
AREA: 'map',
CAPTION: 'table',
COL: 'table',
COLGROUP: 'table',
LEGEND: 'fieldset',
OPTGROUP: 'select',
OPTION: 'select',
PARAM: 'object',
TBODY: 'table',
TD: 'table',
TFOOT: 'table',
TH: 'table',
THEAD: 'table',
TR: 'table'
},
// note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
// due to a Firefox bug
node: function(elementName) {
elementName = elementName.toUpperCase();
// try innerHTML approach
var parentTag = this.NODEMAP[elementName] || 'div';
var parentElement = document.createElement(parentTag);
try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
} catch(e) {}
var element = parentElement.firstChild || null;
// see if browser added wrapping tags
if(element && (element.tagName.toUpperCase() != elementName))
element = element.getElementsByTagName(elementName)[0];
// fallback to createElement approach
if(!element) element = document.createElement(elementName);
// abort if nothing could be created
if(!element) return;
// attributes (or text)
if(arguments[1])
if(this._isStringOrNumber(arguments[1]) ||
(arguments[1] instanceof Array) ||
arguments[1].tagName) {
this._children(element, arguments[1]);
} else {
var attrs = this._attributes(arguments[1]);
if(attrs.length) {
try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
parentElement.innerHTML = "<" +elementName + " " +
attrs + "></" + 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.toUpperCase() != elementName)
element = parentElement.getElementsByTagName(elementName)[0];
}
}
// text, or array of children
if(arguments[2])
this._children(element, arguments[2]);
return element;
},
_text: function(text) {
return document.createTextNode(text);
},
ATTR_MAP: {
'className': 'class',
'htmlFor': 'for'
},
_attributes: function(attributes) {
var attrs = [];
for(attribute in attributes)
attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
'="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'&quot;') + '"');
return attrs.join(" ");
},
_children: function(element, children) {
if(children.tagName) {
element.appendChild(children);
return;
}
if(typeof children=='object') { // array can hold nodes and text
children.flatten().each( function(e) {
if(typeof e=='object')
element.appendChild(e)
else
if(Builder._isStringOrNumber(e))
element.appendChild(Builder._text(e));
});
} else
if(Builder._isStringOrNumber(children))
element.appendChild(Builder._text(children));
},
_isStringOrNumber: function(param) {
return(typeof param=='string' || typeof param=='number');
},
build: function(html) {
var element = this.node('div');
$(element).update(html.strip());
return element.down();
},
dump: function(scope) {
if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope
var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " +
"BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " +
"FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+
"KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+
"PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+
"TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);
tags.each( function(tag){
scope[tag] = function() {
return Builder.node.apply(Builder, [tag].concat($A(arguments)));
}
});
}
}
+875
View File
@@ -0,0 +1,875 @@
// script.aculo.us controls.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007
// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
// (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
// Contributors:
// Richard Livsey
// Rahul Bhargava
// Rob Wills
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/
// Autocompleter.Base handles all the autocompletion functionality
// that's independent of the data source for autocompletion. This
// includes drawing the autocompletion menu, observing keyboard
// and mouse events, and similar.
//
// Specific autocompleters need to provide, at the very least,
// a getUpdatedChoices function that will be invoked every time
// the text inside the monitored textbox changes. This method
// should get the text for which to provide autocompletion by
// invoking this.getToken(), NOT by directly accessing
// this.element.value. This is to allow incremental tokenized
// autocompletion. Specific auto-completion logic (AJAX, etc)
// belongs in getUpdatedChoices.
//
// Tokenized incremental autocompletion is enabled automatically
// when an autocompleter is instantiated with the 'tokens' option
// in the options parameter, e.g.:
// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
// will incrementally autocomplete with a comma as the token.
// Additionally, ',' in the above example can be replaced with
// a token array, e.g. { tokens: [',', '\n'] } which
// enables autocompletion on multiple tokens. This is most
// useful when one of the tokens is \n (a newline), as it
// allows smart autocompletion after linebreaks.
if(typeof Effect == 'undefined')
throw("controls.js requires including script.aculo.us' effects.js library");
var Autocompleter = {}
Autocompleter.Base = function() {};
Autocompleter.Base.prototype = {
baseInitialize: function(element, update, options) {
element = $(element)
this.element = element;
this.update = $(update);
this.hasFocus = false;
this.changed = false;
this.active = false;
this.index = 0;
this.entryCount = 0;
if(this.setOptions)
this.setOptions(options);
else
this.options = options || {};
this.options.paramName = this.options.paramName || this.element.name;
this.options.tokens = this.options.tokens || [];
this.options.frequency = this.options.frequency || 0.4;
this.options.minChars = this.options.minChars || 1;
this.options.onShow = this.options.onShow ||
function(element, update){
if(!update.style.position || update.style.position=='absolute') {
update.style.position = 'absolute';
Position.clone(element, update, {
setHeight: false,
offsetTop: element.offsetHeight
});
}
Effect.Appear(update,{duration:0.15});
};
this.options.onHide = this.options.onHide ||
function(element, update){ new Effect.Fade(update,{duration:0.15}) };
if(typeof(this.options.tokens) == 'string')
this.options.tokens = new Array(this.options.tokens);
this.observer = null;
this.element.setAttribute('autocomplete','off');
Element.hide(this.update);
Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
Event.observe(this.element, 'keypress', this.onKeyPress.bindAsEventListener(this));
// Turn autocomplete back on when the user leaves the page, so that the
// field's value will be remembered on Mozilla-based browsers.
Event.observe(window, 'beforeunload', function(){
element.setAttribute('autocomplete', 'on');
});
},
show: function() {
if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
if(!this.iefix &&
(Prototype.Browser.IE) &&
(Element.getStyle(this.update, 'position')=='absolute')) {
new Insertion.After(this.update,
'<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, {setTop:(!this.update.style.height)});
this.iefix.style.zIndex = 1;
this.update.style.zIndex = 2;
Element.show(this.iefix);
},
hide: function() {
this.stopIndicator();
if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
if(this.iefix) Element.hide(this.iefix);
},
startIndicator: function() {
if(this.options.indicator) Element.show(this.options.indicator);
},
stopIndicator: function() {
if(this.options.indicator) Element.hide(this.options.indicator);
},
onKeyPress: function(event) {
if(this.active)
switch(event.keyCode) {
case Event.KEY_TAB:
case Event.KEY_RETURN:
this.selectEntry();
Event.stop(event);
case Event.KEY_ESC:
this.hide();
this.active = false;
Event.stop(event);
return;
case Event.KEY_LEFT:
case Event.KEY_RIGHT:
return;
case Event.KEY_UP:
this.markPrevious();
this.render();
if(Prototype.Browser.WebKit) Event.stop(event);
return;
case Event.KEY_DOWN:
this.markNext();
this.render();
if(Prototype.Browser.WebKit) Event.stop(event);
return;
}
else
if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
(Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
this.changed = true;
this.hasFocus = true;
if(this.observer) clearTimeout(this.observer);
this.observer =
setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
},
activate: function() {
this.changed = false;
this.hasFocus = true;
this.getUpdatedChoices();
},
onHover: function(event) {
var element = Event.findElement(event, 'LI');
if(this.index != element.autocompleteIndex)
{
this.index = element.autocompleteIndex;
this.render();
}
Event.stop(event);
},
onClick: function(event) {
var element = Event.findElement(event, 'LI');
this.index = element.autocompleteIndex;
this.selectEntry();
this.hide();
},
onBlur: function(event) {
// needed to make click events working
setTimeout(this.hide.bind(this), 250);
this.hasFocus = false;
this.active = false;
},
render: function() {
if(this.entryCount > 0) {
for (var i = 0; i < this.entryCount; i++)
this.index==i ?
Element.addClassName(this.getEntry(i),"selected") :
Element.removeClassName(this.getEntry(i),"selected");
if(this.hasFocus) {
this.show();
this.active = true;
}
} else {
this.active = false;
this.hide();
}
},
markPrevious: function() {
if(this.index > 0) this.index--
else this.index = this.entryCount-1;
this.getEntry(this.index).scrollIntoView(true);
},
markNext: function() {
if(this.index < this.entryCount-1) this.index++
else this.index = 0;
this.getEntry(this.index).scrollIntoView(false);
},
getEntry: function(index) {
return this.update.firstChild.childNodes[index];
},
getCurrentEntry: function() {
return this.getEntry(this.index);
},
selectEntry: function() {
this.active = false;
this.updateElement(this.getCurrentEntry());
},
updateElement: function(selectedElement) {
if (this.options.updateElement) {
this.options.updateElement(selectedElement);
return;
}
var value = '';
if (this.options.select) {
var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
} else
value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
var lastTokenPos = this.findLastToken();
if (lastTokenPos != -1) {
var newValue = this.element.value.substr(0, lastTokenPos + 1);
var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
if (whitespace)
newValue += whitespace[0];
this.element.value = newValue + value;
} else {
this.element.value = value;
}
this.element.focus();
if (this.options.afterUpdateElement)
this.options.afterUpdateElement(this.element, selectedElement);
},
updateChoices: function(choices) {
if(!this.changed && this.hasFocus) {
this.update.innerHTML = choices;
Element.cleanWhitespace(this.update);
Element.cleanWhitespace(this.update.down());
if(this.update.firstChild && this.update.down().childNodes) {
this.entryCount =
this.update.down().childNodes.length;
for (var i = 0; i < this.entryCount; i++) {
var entry = this.getEntry(i);
entry.autocompleteIndex = i;
this.addObservers(entry);
}
} else {
this.entryCount = 0;
}
this.stopIndicator();
this.index = 0;
if(this.entryCount==1 && this.options.autoSelect) {
this.selectEntry();
this.hide();
} else {
this.render();
}
}
},
addObservers: function(element) {
Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
Event.observe(element, "click", this.onClick.bindAsEventListener(this));
},
onObserverEvent: function() {
this.changed = false;
if(this.getToken().length>=this.options.minChars) {
this.getUpdatedChoices();
} else {
this.active = false;
this.hide();
}
},
getToken: function() {
var tokenPos = this.findLastToken();
if (tokenPos != -1)
var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
else
var ret = this.element.value;
return /\n/.test(ret) ? '' : ret;
},
findLastToken: function() {
var lastTokenPos = -1;
for (var i=0; i<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() {
this.startIndicator();
var entry = encodeURIComponent(this.options.paramName) + '=' +
encodeURIComponent(this.getToken());
this.options.parameters = this.options.callback ?
this.options.callback(this.element, entry) : entry;
if(this.options.defaultParams)
this.options.parameters += '&' + this.options.defaultParams;
new Ajax.Request(this.url, this.options);
},
onComplete: function(request) {
this.updateChoices(request.responseText);
}
});
// The local array autocompleter. Used when you'd prefer to
// inject an array of autocompletion options into the page, rather
// than sending out Ajax queries, which can be quite slow sometimes.
//
// The constructor takes four parameters. The first two are, as usual,
// the id of the monitored textbox, and id of the autocompletion menu.
// The third is the array you want to autocomplete from, and the fourth
// is the options block.
//
// Extra local autocompletion options:
// - choices - How many autocompletion choices to offer
//
// - partialSearch - If false, the autocompleter will match entered
// text only at the beginning of strings in the
// autocomplete array. Defaults to true, which will
// match text at the beginning of any *word* in the
// strings in the autocomplete array. If you want to
// search anywhere in the string, additionally set
// the option fullSearch to true (default: off).
//
// - fullSsearch - Search anywhere in autocomplete array strings.
//
// - partialChars - How many characters to enter before triggering
// a partial match (unlike minChars, which defines
// how many characters are required to do any match
// at all). Defaults to 2.
//
// - ignoreCase - Whether to ignore case when autocompleting.
// Defaults to true.
//
// It's possible to pass in a custom function as the 'selector'
// option, if you prefer to write your own autocompletion logic.
// In that case, the other options above will not apply unless
// you support them.
Autocompleter.Local = Class.create();
Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
initialize: function(element, update, array, options) {
this.baseInitialize(element, update, options);
this.options.array = array;
},
getUpdatedChoices: function() {
this.updateChoices(this.options.selector(this));
},
setOptions: function(options) {
this.options = Object.extend({
choices: 10,
partialSearch: true,
partialChars: 2,
ignoreCase: true,
fullSearch: false,
selector: function(instance) {
var ret = []; // Beginning matches
var partial = []; // Inside matches
var entry = instance.getToken();
var count = 0;
for (var i = 0; i < instance.options.array.length &&
ret.length < instance.options.choices ; i++) {
var elem = instance.options.array[i];
var foundPos = instance.options.ignoreCase ?
elem.toLowerCase().indexOf(entry.toLowerCase()) :
elem.indexOf(entry);
while (foundPos != -1) {
if (foundPos == 0 && elem.length != entry.length) {
ret.push("<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({
paramName: "value",
okButton: true,
okLink: false,
okText: "ok",
cancelButton: false,
cancelLink: true,
cancelText: "cancel",
textBeforeControls: '',
textBetweenControls: '',
textAfterControls: '',
savingText: "Saving...",
clickToEditText: "Click to edit",
okText: "ok",
rows: 1,
onComplete: function(transport, element) {
new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
},
onFailure: function(transport) {
alert("Error communicating with the server: " + transport.responseText.stripTags());
},
callback: function(form) {
return Form.serialize(form);
},
handleLineBreaks: true,
loadingText: 'Loading...',
savingClassName: 'inplaceeditor-saving',
loadingClassName: 'inplaceeditor-loading',
formClassName: 'inplaceeditor-form',
highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
highlightendcolor: "#FFFFFF",
externalControl: null,
submitOnBlur: false,
ajaxOptions: {},
evalScripts: false
}, options || {});
if(!this.options.formId && this.element.id) {
this.options.formId = this.element.id + "-inplaceeditor";
if ($(this.options.formId)) {
// there's already a form with that name, don't specify an id
this.options.formId = null;
}
}
if (this.options.externalControl) {
this.options.externalControl = $(this.options.externalControl);
}
this.originalBackground = Element.getStyle(this.element, 'background-color');
if (!this.originalBackground) {
this.originalBackground = "transparent";
}
this.element.title = this.options.clickToEditText;
this.onclickListener = this.enterEditMode.bindAsEventListener(this);
this.mouseoverListener = this.enterHover.bindAsEventListener(this);
this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
Event.observe(this.element, 'click', this.onclickListener);
Event.observe(this.element, 'mouseover', this.mouseoverListener);
Event.observe(this.element, 'mouseout', this.mouseoutListener);
if (this.options.externalControl) {
Event.observe(this.options.externalControl, 'click', this.onclickListener);
Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
}
},
enterEditMode: function(evt) {
if (this.saving) return;
if (this.editing) return;
this.editing = true;
this.onEnterEditMode();
if (this.options.externalControl) {
Element.hide(this.options.externalControl);
}
Element.hide(this.element);
this.createForm();
this.element.parentNode.insertBefore(this.form, this.element);
if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField);
// stop the event to avoid a page refresh in Safari
if (evt) {
Event.stop(evt);
}
return false;
},
createForm: function() {
this.form = document.createElement("form");
this.form.id = this.options.formId;
Element.addClassName(this.form, this.options.formClassName)
this.form.onsubmit = this.onSubmit.bind(this);
this.createEditField();
if (this.options.textarea) {
var br = document.createElement("br");
this.form.appendChild(br);
}
if (this.options.textBeforeControls)
this.form.appendChild(document.createTextNode(this.options.textBeforeControls));
if (this.options.okButton) {
var okButton = document.createElement("input");
okButton.type = "submit";
okButton.value = this.options.okText;
okButton.className = 'editor_ok_button';
this.form.appendChild(okButton);
}
if (this.options.okLink) {
var okLink = document.createElement("a");
okLink.href = "#";
okLink.appendChild(document.createTextNode(this.options.okText));
okLink.onclick = this.onSubmit.bind(this);
okLink.className = 'editor_ok_link';
this.form.appendChild(okLink);
}
if (this.options.textBetweenControls &&
(this.options.okLink || this.options.okButton) &&
(this.options.cancelLink || this.options.cancelButton))
this.form.appendChild(document.createTextNode(this.options.textBetweenControls));
if (this.options.cancelButton) {
var cancelButton = document.createElement("input");
cancelButton.type = "submit";
cancelButton.value = this.options.cancelText;
cancelButton.onclick = this.onclickCancel.bind(this);
cancelButton.className = 'editor_cancel_button';
this.form.appendChild(cancelButton);
}
if (this.options.cancelLink) {
var cancelLink = document.createElement("a");
cancelLink.href = "#";
cancelLink.appendChild(document.createTextNode(this.options.cancelText));
cancelLink.onclick = this.onclickCancel.bind(this);
cancelLink.className = 'editor_cancel editor_cancel_link';
this.form.appendChild(cancelLink);
}
if (this.options.textAfterControls)
this.form.appendChild(document.createTextNode(this.options.textAfterControls));
},
hasHTMLLineBreaks: function(string) {
if (!this.options.handleLineBreaks) return false;
return string.match(/<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 = this.options.paramName;
textField.value = text;
textField.style.backgroundColor = this.options.highlightcolor;
textField.className = 'editor_field';
var size = this.options.size || this.options.cols || 0;
if (size != 0) textField.size = size;
if (this.options.submitOnBlur)
textField.onblur = this.onSubmit.bind(this);
this.editField = textField;
} else {
this.options.textarea = true;
var textArea = document.createElement("textarea");
textArea.obj = this;
textArea.name = this.options.paramName;
textArea.value = this.convertHTMLLineBreaks(text);
textArea.rows = this.options.rows;
textArea.cols = this.options.cols || 40;
textArea.className = 'editor_field';
if (this.options.submitOnBlur)
textArea.onblur = this.onSubmit.bind(this);
this.editField = textArea;
}
if(this.options.loadTextURL) {
this.loadExternalText();
}
this.form.appendChild(this.editField);
},
getText: function() {
return this.element.innerHTML;
},
loadExternalText: function() {
Element.addClassName(this.form, this.options.loadingClassName);
this.editField.disabled = true;
new Ajax.Request(
this.options.loadTextURL,
Object.extend({
asynchronous: true,
onComplete: this.onLoadedExternalText.bind(this)
}, this.options.ajaxOptions)
);
},
onLoadedExternalText: function(transport) {
Element.removeClassName(this.form, this.options.loadingClassName);
this.editField.disabled = false;
this.editField.value = transport.responseText.stripTags();
Field.scrollFreeActivate(this.editField);
},
onclickCancel: function() {
this.onComplete();
this.leaveEditMode();
return false;
},
onFailure: function(transport) {
this.options.onFailure(transport);
if (this.oldInnerHTML) {
this.element.innerHTML = this.oldInnerHTML;
this.oldInnerHTML = null;
}
return false;
},
onSubmit: function() {
// onLoading resets these so we need to save them away for the Ajax call
var form = this.form;
var value = this.editField.value;
// do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
// which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
// to be displayed indefinitely
this.onLoading();
if (this.options.evalScripts) {
new Ajax.Request(
this.url, Object.extend({
parameters: this.options.callback(form, value),
onComplete: this.onComplete.bind(this),
onFailure: this.onFailure.bind(this),
asynchronous:true,
evalScripts:true
}, this.options.ajaxOptions));
} else {
new Ajax.Updater(
{ success: this.element,
// don't update on failure (this could be an option)
failure: null },
this.url, Object.extend({
parameters: this.options.callback(form, value),
onComplete: this.onComplete.bind(this),
onFailure: this.onFailure.bind(this)
}, this.options.ajaxOptions));
}
// stop the event to avoid a page refresh in Safari
if (arguments.length > 1) {
Event.stop(arguments[0]);
}
return false;
},
onLoading: function() {
this.saving = true;
this.removeForm();
this.leaveHover();
this.showSaving();
},
showSaving: function() {
this.oldInnerHTML = this.element.innerHTML;
this.element.innerHTML = this.options.savingText;
Element.addClassName(this.element, this.options.savingClassName);
this.element.style.backgroundColor = this.originalBackground;
Element.show(this.element);
},
removeForm: function() {
if(this.form) {
if (this.form.parentNode) Element.remove(this.form);
this.form = null;
}
},
enterHover: function() {
if (this.saving) return;
this.element.style.backgroundColor = this.options.highlightcolor;
if (this.effect) {
this.effect.cancel();
}
Element.addClassName(this.element, this.options.hoverClassName)
},
leaveHover: function() {
if (this.options.backgroundColor) {
this.element.style.backgroundColor = this.oldBackground;
}
Element.removeClassName(this.element, this.options.hoverClassName)
if (this.saving) return;
this.effect = new Effect.Highlight(this.element, {
startcolor: this.options.highlightcolor,
endcolor: this.options.highlightendcolor,
restorecolor: this.originalBackground
});
},
leaveEditMode: function() {
Element.removeClassName(this.element, this.options.savingClassName);
this.removeForm();
this.leaveHover();
this.element.style.backgroundColor = this.originalBackground;
Element.show(this.element);
if (this.options.externalControl) {
Element.show(this.options.externalControl);
}
this.editing = false;
this.saving = false;
this.oldInnerHTML = null;
this.onLeaveEditMode();
},
onComplete: function(transport) {
this.leaveEditMode();
this.options.onComplete.bind(this)(transport, this.element);
},
onEnterEditMode: function() {},
onLeaveEditMode: function() {},
dispose: function() {
if (this.oldInnerHTML) {
this.element.innerHTML = this.oldInnerHTML;
}
this.leaveEditMode();
Event.stopObserving(this.element, 'click', this.onclickListener);
Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
if (this.options.externalControl) {
Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
}
}
};
Ajax.InPlaceCollectionEditor = Class.create();
Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype);
Object.extend(Ajax.InPlaceCollectionEditor.prototype, {
createEditField: function() {
if (!this.cached_selectTag) {
var selectTag = document.createElement("select");
var collection = this.options.collection || [];
var optionTag;
collection.each(function(e,i) {
optionTag = document.createElement("option");
optionTag.value = (e instanceof Array) ? e[0] : e;
if((typeof this.options.value == 'undefined') &&
((e instanceof Array) ? this.element.innerHTML == e[1] : e == optionTag.value)) optionTag.selected = true;
if(this.options.value==optionTag.value) optionTag.selected = true;
optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e));
selectTag.appendChild(optionTag);
}.bind(this));
this.cached_selectTag = selectTag;
}
this.editField = this.cached_selectTag;
if(this.options.loadTextURL) this.loadExternalText();
this.form.appendChild(this.editField);
this.options.callback = function(form, value) {
return "value=" + encodeURIComponent(value);
}
}
});
// Delayed observer, like Form.Element.Observer,
// but waits for delay after last key input
// Ideal for live-search fields
Form.Element.DelayedObserver = Class.create();
Form.Element.DelayedObserver.prototype = {
initialize: function(element, delay, callback) {
this.delay = delay || 0.5;
this.element = $(element);
this.callback = callback;
this.timer = null;
this.lastValue = $F(this.element);
Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
},
delayedListener: function(event) {
if(this.lastValue == $F(this.element)) return;
if(this.timer) clearTimeout(this.timer);
this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
this.lastValue = $F(this.element);
},
onTimerEvent: function() {
this.timer = null;
this.callback(this.element, $F(this.element));
}
};
+970
View File
@@ -0,0 +1,970 @@
// script.aculo.us dragdrop.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007
// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/
if(typeof Effect == 'undefined')
throw("dragdrop.js requires including script.aculo.us' effects.js library");
var Droppables = {
drops: [],
remove: function(element) {
this.drops = this.drops.reject(function(d) { return d.element==$(element) });
},
add: function(element) {
element = $(element);
var options = Object.extend({
greedy: true,
hoverclass: null,
tree: false
}, arguments[1] || {});
// cache containers
if(options.containment) {
options._containers = [];
var containment = options.containment;
if((typeof containment == 'object') &&
(containment.constructor == Array)) {
containment.each( function(c) { options._containers.push($(c)) });
} else {
options._containers.push($(containment));
}
}
if(options.accept) options.accept = [options.accept].flatten();
Element.makePositioned(element); // fix IE
options.element = element;
this.drops.push(options);
},
findDeepestChild: function(drops) {
deepest = drops[0];
for (i = 1; i < drops.length; ++i)
if (Element.isParent(drops[i].element, deepest.element))
deepest = drops[i];
return deepest;
},
isContained: function(element, drop) {
var containmentNode;
if(drop.tree) {
containmentNode = element.treeNode;
} else {
containmentNode = element.parentNode;
}
return drop._containers.detect(function(c) { return containmentNode == c });
},
isAffected: function(point, element, drop) {
return (
(drop.element!=element) &&
((!drop._containers) ||
this.isContained(element, drop)) &&
((!drop.accept) ||
(Element.classNames(element).detect(
function(v) { return drop.accept.include(v) } ) )) &&
Position.within(drop.element, point[0], point[1]) );
},
deactivate: function(drop) {
if(drop.hoverclass)
Element.removeClassName(drop.element, drop.hoverclass);
this.last_active = null;
},
activate: function(drop) {
if(drop.hoverclass)
Element.addClassName(drop.element, drop.hoverclass);
this.last_active = drop;
},
show: function(point, element) {
if(!this.drops.length) return;
var affected = [];
if(this.last_active) this.deactivate(this.last_active);
this.drops.each( function(drop) {
if(Droppables.isAffected(point, element, drop))
affected.push(drop);
});
if(affected.length>0) {
drop = Droppables.findDeepestChild(affected);
Position.within(drop.element, point[0], point[1]);
if(drop.onHover)
drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
Droppables.activate(drop);
}
},
fire: function(event, element) {
if(!this.last_active) return;
Position.prepare();
if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
if (this.last_active.onDrop) {
this.last_active.onDrop(element, this.last_active.element, event);
return true;
}
},
reset: function() {
if(this.last_active)
this.deactivate(this.last_active);
}
}
var Draggables = {
drags: [],
observers: [],
register: function(draggable) {
if(this.drags.length == 0) {
this.eventMouseUp = this.endDrag.bindAsEventListener(this);
this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
this.eventKeypress = this.keyPress.bindAsEventListener(this);
Event.observe(document, "mouseup", this.eventMouseUp);
Event.observe(document, "mousemove", this.eventMouseMove);
Event.observe(document, "keypress", this.eventKeypress);
}
this.drags.push(draggable);
},
unregister: function(draggable) {
this.drags = this.drags.reject(function(d) { return d==draggable });
if(this.drags.length == 0) {
Event.stopObserving(document, "mouseup", this.eventMouseUp);
Event.stopObserving(document, "mousemove", this.eventMouseMove);
Event.stopObserving(document, "keypress", this.eventKeypress);
}
},
activate: function(draggable) {
if(draggable.options.delay) {
this._timeout = setTimeout(function() {
Draggables._timeout = null;
window.focus();
Draggables.activeDraggable = draggable;
}.bind(this), draggable.options.delay);
} else {
window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
this.activeDraggable = draggable;
}
},
deactivate: function() {
this.activeDraggable = null;
},
updateDrag: function(event) {
if(!this.activeDraggable) return;
var pointer = [Event.pointerX(event), Event.pointerY(event)];
// Mozilla-based browsers fire successive mousemove events with
// the same coordinates, prevent needless redrawing (moz bug?)
if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
this._lastPointer = pointer;
this.activeDraggable.updateDrag(event, pointer);
},
endDrag: function(event) {
if(this._timeout) {
clearTimeout(this._timeout);
this._timeout = null;
}
if(!this.activeDraggable) return;
this._lastPointer = null;
this.activeDraggable.endDrag(event);
this.activeDraggable = null;
},
keyPress: function(event) {
if(this.activeDraggable)
this.activeDraggable.keyPress(event);
},
addObserver: function(observer) {
this.observers.push(observer);
this._cacheObserverCallbacks();
},
removeObserver: function(element) { // element instead of observer fixes mem leaks
this.observers = this.observers.reject( function(o) { return o.element==element });
this._cacheObserverCallbacks();
},
notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag'
if(this[eventName+'Count'] > 0)
this.observers.each( function(o) {
if(o[eventName]) o[eventName](eventName, draggable, event);
});
if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
},
_cacheObserverCallbacks: function() {
['onStart','onEnd','onDrag'].each( function(eventName) {
Draggables[eventName+'Count'] = Draggables.observers.select(
function(o) { return o[eventName]; }
).length;
});
}
}
/*--------------------------------------------------------------------------*/
var Draggable = Class.create();
Draggable._dragging = {};
Draggable.prototype = {
initialize: function(element) {
var defaults = {
handle: false,
reverteffect: function(element, top_offset, left_offset) {
var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
queue: {scope:'_draggable', position:'end'}
});
},
endeffect: function(element) {
var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0;
new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
queue: {scope:'_draggable', position:'end'},
afterFinish: function(){
Draggable._dragging[element] = false
}
});
},
zindex: 1000,
revert: false,
quiet: false,
scroll: false,
scrollSensitivity: 20,
scrollSpeed: 15,
snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] }
delay: 0
};
if(!arguments[1] || typeof arguments[1].endeffect == 'undefined')
Object.extend(defaults, {
starteffect: function(element) {
element._opacity = Element.getOpacity(element);
Draggable._dragging[element] = true;
new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
}
});
var options = Object.extend(defaults, arguments[1] || {});
this.element = $(element);
if(options.handle && (typeof options.handle == 'string'))
this.handle = this.element.down('.'+options.handle, 0);
if(!this.handle) this.handle = $(options.handle);
if(!this.handle) this.handle = this.element;
if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
options.scroll = $(options.scroll);
this._isScrollChild = Element.childOf(this.element, options.scroll);
}
Element.makePositioned(this.element); // fix IE
this.delta = this.currentDelta();
this.options = options;
this.dragging = false;
this.eventMouseDown = this.initDrag.bindAsEventListener(this);
Event.observe(this.handle, "mousedown", this.eventMouseDown);
Draggables.register(this);
},
destroy: function() {
Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
Draggables.unregister(this);
},
currentDelta: function() {
return([
parseInt(Element.getStyle(this.element,'left') || '0'),
parseInt(Element.getStyle(this.element,'top') || '0')]);
},
initDrag: function(event) {
if(typeof Draggable._dragging[this.element] != 'undefined' &&
Draggable._dragging[this.element]) return;
if(Event.isLeftClick(event)) {
// abort on form elements, fixes a Firefox issue
var src = Event.element(event);
if((tag_name = src.tagName.toUpperCase()) && (
tag_name=='INPUT' ||
tag_name=='SELECT' ||
tag_name=='OPTION' ||
tag_name=='BUTTON' ||
tag_name=='TEXTAREA')) return;
var pointer = [Event.pointerX(event), Event.pointerY(event)];
var pos = Position.cumulativeOffset(this.element);
this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
Draggables.activate(this);
Event.stop(event);
}
},
startDrag: function(event) {
this.dragging = true;
if(this.options.zindex) {
this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
this.element.style.zIndex = this.options.zindex;
}
if(this.options.ghosting) {
this._clone = this.element.cloneNode(true);
Position.absolutize(this.element);
this.element.parentNode.insertBefore(this._clone, this.element);
}
if(this.options.scroll) {
if (this.options.scroll == window) {
var where = this._getWindowScroll(this.options.scroll);
this.originalScrollLeft = where.left;
this.originalScrollTop = where.top;
} else {
this.originalScrollLeft = this.options.scroll.scrollLeft;
this.originalScrollTop = this.options.scroll.scrollTop;
}
}
Draggables.notify('onStart', this, event);
if(this.options.starteffect) this.options.starteffect(this.element);
},
updateDrag: function(event, pointer) {
if(!this.dragging) this.startDrag(event);
if(!this.options.quiet){
Position.prepare();
Droppables.show(pointer, this.element);
}
Draggables.notify('onDrag', this, event);
this.draw(pointer);
if(this.options.change) this.options.change(this);
if(this.options.scroll) {
this.stopScrolling();
var p;
if (this.options.scroll == window) {
with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
} else {
p = Position.page(this.options.scroll);
p[0] += this.options.scroll.scrollLeft + Position.deltaX;
p[1] += this.options.scroll.scrollTop + Position.deltaY;
p.push(p[0]+this.options.scroll.offsetWidth);
p.push(p[1]+this.options.scroll.offsetHeight);
}
var speed = [0,0];
if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
this.startScrolling(speed);
}
// fix AppleWebKit rendering
if(Prototype.Browser.WebKit) window.scrollBy(0,0);
Event.stop(event);
},
finishDrag: function(event, success) {
this.dragging = false;
if(this.options.quiet){
Position.prepare();
var pointer = [Event.pointerX(event), Event.pointerY(event)];
Droppables.show(pointer, this.element);
}
if(this.options.ghosting) {
Position.relativize(this.element);
Element.remove(this._clone);
this._clone = null;
}
var dropped = false;
if(success) {
dropped = Droppables.fire(event, this.element);
if (!dropped) dropped = false;
}
if(dropped && this.options.onDropped) this.options.onDropped(this.element);
Draggables.notify('onEnd', this, event);
var revert = this.options.revert;
if(revert && typeof revert == 'function') revert = revert(this.element);
var d = this.currentDelta();
if(revert && this.options.reverteffect) {
if (dropped == 0 || revert != 'failure')
this.options.reverteffect(this.element,
d[1]-this.delta[1], d[0]-this.delta[0]);
} else {
this.delta = d;
}
if(this.options.zindex)
this.element.style.zIndex = this.originalZ;
if(this.options.endeffect)
this.options.endeffect(this.element);
Draggables.deactivate(this);
Droppables.reset();
},
keyPress: function(event) {
if(event.keyCode!=Event.KEY_ESC) return;
this.finishDrag(event, false);
Event.stop(event);
},
endDrag: function(event) {
if(!this.dragging) return;
this.stopScrolling();
this.finishDrag(event, true);
Event.stop(event);
},
draw: function(point) {
var pos = Position.cumulativeOffset(this.element);
if(this.options.ghosting) {
var r = Position.realOffset(this.element);
pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
}
var d = this.currentDelta();
pos[0] -= d[0]; pos[1] -= d[1];
if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
}
var p = [0,1].map(function(i){
return (point[i]-pos[i]-this.offset[i])
}.bind(this));
if(this.options.snap) {
if(typeof this.options.snap == 'function') {
p = this.options.snap(p[0],p[1],this);
} else {
if(this.options.snap instanceof Array) {
p = p.map( function(v, i) {
return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this))
} else {
p = p.map( function(v) {
return Math.round(v/this.options.snap)*this.options.snap }.bind(this))
}
}}
var style = this.element.style;
if((!this.options.constraint) || (this.options.constraint=='horizontal'))
style.left = p[0] + "px";
if((!this.options.constraint) || (this.options.constraint=='vertical'))
style.top = p[1] + "px";
if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
},
stopScrolling: function() {
if(this.scrollInterval) {
clearInterval(this.scrollInterval);
this.scrollInterval = null;
Draggables._lastScrollPointer = null;
}
},
startScrolling: function(speed) {
if(!(speed[0] || speed[1])) return;
this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
this.lastScrolled = new Date();
this.scrollInterval = setInterval(this.scroll.bind(this), 10);
},
scroll: function() {
var current = new Date();
var delta = current - this.lastScrolled;
this.lastScrolled = current;
if(this.options.scroll == window) {
with (this._getWindowScroll(this.options.scroll)) {
if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
var d = delta / 1000;
this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
}
}
} else {
this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
}
Position.prepare();
Droppables.show(Draggables._lastPointer, this.element);
Draggables.notify('onDrag', this);
if (this._isScrollChild) {
Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
if (Draggables._lastScrollPointer[0] < 0)
Draggables._lastScrollPointer[0] = 0;
if (Draggables._lastScrollPointer[1] < 0)
Draggables._lastScrollPointer[1] = 0;
this.draw(Draggables._lastScrollPointer);
}
if(this.options.change) this.options.change(this);
},
_getWindowScroll: function(w) {
var T, L, W, H;
with (w.document) {
if (w.document.documentElement && documentElement.scrollTop) {
T = documentElement.scrollTop;
L = documentElement.scrollLeft;
} else if (w.document.body) {
T = body.scrollTop;
L = body.scrollLeft;
}
if (w.innerWidth) {
W = w.innerWidth;
H = w.innerHeight;
} else if (w.document.documentElement && documentElement.clientWidth) {
W = documentElement.clientWidth;
H = documentElement.clientHeight;
} else {
W = body.offsetWidth;
H = body.offsetHeight
}
}
return { top: T, left: L, width: W, height: H };
}
}
/*--------------------------------------------------------------------------*/
var SortableObserver = Class.create();
SortableObserver.prototype = {
initialize: function(element, observer) {
this.element = $(element);
this.observer = observer;
this.lastValue = Sortable.serialize(this.element);
},
onStart: function() {
this.lastValue = Sortable.serialize(this.element);
},
onEnd: function() {
Sortable.unmark();
if(this.lastValue != Sortable.serialize(this.element))
this.observer(this.element)
}
}
var Sortable = {
SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
sortables: {},
_findRootElement: function(element) {
while (element.tagName.toUpperCase() != "BODY") {
if(element.id && Sortable.sortables[element.id]) return element;
element = element.parentNode;
}
},
options: function(element) {
element = Sortable._findRootElement($(element));
if(!element) return;
return Sortable.sortables[element.id];
},
destroy: function(element){
var s = Sortable.options(element);
if(s) {
Draggables.removeObserver(s.element);
s.droppables.each(function(d){ Droppables.remove(d) });
s.draggables.invoke('destroy');
delete Sortable.sortables[s.element.id];
}
},
create: function(element) {
element = $(element);
var options = Object.extend({
element: element,
tag: 'li', // assumes li children, override with tag: 'tagname'
dropOnEmpty: false,
tree: false,
treeTag: 'ul',
overlap: 'vertical', // one of 'vertical', 'horizontal'
constraint: 'vertical', // one of 'vertical', 'horizontal', false
containment: element, // also takes array of elements (or id's); or false
handle: false, // or a CSS class
only: false,
delay: 0,
hoverclass: null,
ghosting: false,
quiet: false,
scroll: false,
scrollSensitivity: 20,
scrollSpeed: 15,
format: this.SERIALIZE_RULE,
// these take arrays of elements or ids and can be
// used for better initialization performance
elements: false,
handles: false,
onChange: Prototype.emptyFunction,
onUpdate: Prototype.emptyFunction
}, arguments[1] || {});
// clear any old sortable with same element
this.destroy(element);
// build options for the draggables
var options_for_draggable = {
revert: true,
quiet: options.quiet,
scroll: options.scroll,
scrollSpeed: options.scrollSpeed,
scrollSensitivity: options.scrollSensitivity,
delay: options.delay,
ghosting: options.ghosting,
constraint: options.constraint,
handle: options.handle };
if(options.starteffect)
options_for_draggable.starteffect = options.starteffect;
if(options.reverteffect)
options_for_draggable.reverteffect = options.reverteffect;
else
if(options.ghosting) options_for_draggable.reverteffect = function(element) {
element.style.top = 0;
element.style.left = 0;
};
if(options.endeffect)
options_for_draggable.endeffect = options.endeffect;
if(options.zindex)
options_for_draggable.zindex = options.zindex;
// build options for the droppables
var options_for_droppable = {
overlap: options.overlap,
containment: options.containment,
tree: options.tree,
hoverclass: options.hoverclass,
onHover: Sortable.onHover
}
var options_for_tree = {
onHover: Sortable.onEmptyHover,
overlap: options.overlap,
containment: options.containment,
hoverclass: options.hoverclass
}
// fix for gecko engine
Element.cleanWhitespace(element);
options.draggables = [];
options.droppables = [];
// drop on empty handling
if(options.dropOnEmpty || options.tree) {
Droppables.add(element, options_for_tree);
options.droppables.push(element);
}
(options.elements || this.findElements(element, options) || []).each( function(e,i) {
var handle = options.handles ? $(options.handles[i]) :
(options.handle ? $(e).getElementsByClassName(options.handle)[0] : e);
options.draggables.push(
new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
Droppables.add(e, options_for_droppable);
if(options.tree) e.treeNode = element;
options.droppables.push(e);
});
if(options.tree) {
(Sortable.findTreeElements(element, options) || []).each( function(e) {
Droppables.add(e, options_for_tree);
e.treeNode = element;
options.droppables.push(e);
});
}
// keep reference
this.sortables[element.id] = options;
// for onupdate
Draggables.addObserver(new SortableObserver(element, options.onUpdate));
},
// return all suitable-for-sortable elements in a guaranteed order
findElements: function(element, options) {
return Element.findChildren(
element, options.only, options.tree ? true : false, options.tag);
},
findTreeElements: function(element, options) {
return Element.findChildren(
element, options.only, options.tree ? true : false, options.treeTag);
},
onHover: function(element, dropon, overlap) {
if(Element.isParent(dropon, element)) return;
if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
return;
} else if(overlap>0.5) {
Sortable.mark(dropon, 'before');
if(dropon.previousSibling != element) {
var oldParentNode = element.parentNode;
element.style.visibility = "hidden"; // fix gecko rendering
dropon.parentNode.insertBefore(element, dropon);
if(dropon.parentNode!=oldParentNode)
Sortable.options(oldParentNode).onChange(element);
Sortable.options(dropon.parentNode).onChange(element);
}
} else {
Sortable.mark(dropon, 'after');
var nextElement = dropon.nextSibling || null;
if(nextElement != element) {
var oldParentNode = element.parentNode;
element.style.visibility = "hidden"; // fix gecko rendering
dropon.parentNode.insertBefore(element, nextElement);
if(dropon.parentNode!=oldParentNode)
Sortable.options(oldParentNode).onChange(element);
Sortable.options(dropon.parentNode).onChange(element);
}
}
},
onEmptyHover: function(element, dropon, overlap) {
var oldParentNode = element.parentNode;
var droponOptions = Sortable.options(dropon);
if(!Element.isParent(dropon, element)) {
var index;
var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
var child = null;
if(children) {
var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
for (index = 0; index < children.length; index += 1) {
if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
offset -= Element.offsetSize (children[index], droponOptions.overlap);
} else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
child = index + 1 < children.length ? children[index + 1] : null;
break;
} else {
child = children[index];
break;
}
}
}
dropon.insertBefore(element, child);
Sortable.options(oldParentNode).onChange(element);
droponOptions.onChange(element);
}
},
unmark: function() {
if(Sortable._marker) Sortable._marker.hide();
},
mark: function(dropon, position) {
// mark on ghosting only
var sortable = Sortable.options(dropon.parentNode);
if(sortable && !sortable.ghosting) return;
if(!Sortable._marker) {
Sortable._marker =
($('dropmarker') || Element.extend(document.createElement('DIV'))).
hide().addClassName('dropmarker').setStyle({position:'absolute'});
document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
}
var offsets = Position.cumulativeOffset(dropon);
Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
if(position=='after')
if(sortable.overlap == 'horizontal')
Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
else
Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
Sortable._marker.show();
},
_tree: function(element, options, parent) {
var children = Sortable.findElements(element, options) || [];
for (var i = 0; i < children.length; ++i) {
var match = children[i].id.match(options.format);
if (!match) continue;
var child = {
id: encodeURIComponent(match ? match[1] : null),
element: element,
parent: parent,
children: [],
position: parent.children.length,
container: $(children[i]).down(options.treeTag)
}
/* Get the element containing the children and recurse over it */
if (child.container)
this._tree(child.container, options, child)
parent.children.push (child);
}
return parent;
},
tree: function(element) {
element = $(element);
var sortableOptions = this.options(element);
var options = Object.extend({
tag: sortableOptions.tag,
treeTag: sortableOptions.treeTag,
only: sortableOptions.only,
name: element.id,
format: sortableOptions.format
}, arguments[1] || {});
var root = {
id: null,
parent: null,
children: [],
container: element,
position: 0
}
return Sortable._tree(element, options, root);
},
/* Construct a [i] index for a particular node */
_constructIndex: function(node) {
var index = '';
do {
if (node.id) index = '[' + node.position + ']' + index;
} while ((node = node.parent) != null);
return index;
},
sequence: function(element) {
element = $(element);
var options = Object.extend(this.options(element), arguments[1] || {});
return $(this.findElements(element, options) || []).map( function(item) {
return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
});
},
setSequence: function(element, new_sequence) {
element = $(element);
var options = Object.extend(this.options(element), arguments[2] || {});
var nodeMap = {};
this.findElements(element, options).each( function(n) {
if (n.id.match(options.format))
nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
n.parentNode.removeChild(n);
});
new_sequence.each(function(ident) {
var n = nodeMap[ident];
if (n) {
n[1].appendChild(n[0]);
delete nodeMap[ident];
}
});
},
serialize: function(element) {
element = $(element);
var options = Object.extend(Sortable.options(element), arguments[1] || {});
var name = encodeURIComponent(
(arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
if (options.tree) {
return Sortable.tree(element, arguments[1]).children.map( function (item) {
return [name + Sortable._constructIndex(item) + "[id]=" +
encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
}).flatten().join('&');
} else {
return Sortable.sequence(element, arguments[1]).map( function(item) {
return name + "[]=" + encodeURIComponent(item);
}).join('&');
}
}
}
// Returns true if child is contained within element
Element.isParent = function(child, element) {
if (!child.parentNode || child == element) return false;
if (child.parentNode == element) return true;
return Element.isParent(child.parentNode, element);
}
Element.findChildren = function(element, only, recursive, tagName) {
if(!element.hasChildNodes()) return null;
tagName = tagName.toUpperCase();
if(only) only = [only].flatten();
var elements = [];
$A(element.childNodes).each( function(e) {
if(e.tagName && e.tagName.toUpperCase()==tagName &&
(!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
elements.push(e);
if(recursive) {
var grandchildren = Element.findChildren(e, only, recursive, tagName);
if(grandchildren) elements.push(grandchildren);
}
});
return (elements.length>0 ? elements.flatten() : []);
}
Element.offsetSize = function (element, type) {
return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,215 @@
// Copyright (c) 2005 Thomas Fakes (http://craz8.com)
//
// This code is substantially based on code from script.aculo.us which has the
// following copyright and permission notice
//
// 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 Resizeable = Class.create();
Resizeable.prototype = {
initialize: function(element) {
var options = Object.extend({
top: 6,
bottom: 6,
left: 6,
right: 6,
minHeight: 0,
minWidth: 0,
zindex: 1000,
resize: null
}, arguments[1] || {});
this.element = $(element);
this.handle = this.element;
Element.makePositioned(this.element); // fix IE
this.options = options;
this.active = false;
this.resizing = false;
this.currentDirection = '';
this.eventMouseDown = this.startResize.bindAsEventListener(this);
this.eventMouseUp = this.endResize.bindAsEventListener(this);
this.eventMouseMove = this.update.bindAsEventListener(this);
this.eventCursorCheck = this.cursor.bindAsEventListener(this);
this.eventKeypress = this.keyPress.bindAsEventListener(this);
this.registerEvents();
},
destroy: function() {
Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
this.unregisterEvents();
},
registerEvents: function() {
Event.observe(document, "mouseup", this.eventMouseUp);
Event.observe(document, "mousemove", this.eventMouseMove);
Event.observe(document, "keypress", this.eventKeypress);
Event.observe(this.handle, "mousedown", this.eventMouseDown);
Event.observe(this.element, "mousemove", this.eventCursorCheck);
},
unregisterEvents: function() {
//if(!this.active) return;
//Event.stopObserving(document, "mouseup", this.eventMouseUp);
//Event.stopObserving(document, "mousemove", this.eventMouseMove);
//Event.stopObserving(document, "mousemove", this.eventCursorCheck);
//Event.stopObserving(document, "keypress", this.eventKeypress);
},
startResize: 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=='BUTTON' ||
src.tagName=='TEXTAREA')) return;
var dir = this.directions(event);
if (dir.length > 0) {
this.active = true;
var offsets = Position.cumulativeOffset(this.element);
this.startTop = offsets[1];
this.startLeft = offsets[0];
this.startWidth = parseInt(Element.getStyle(this.element, 'width'));
this.startHeight = parseInt(Element.getStyle(this.element, 'height'));
this.startX = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
this.startY = event.clientY + document.body.scrollTop + document.documentElement.scrollTop;
this.currentDirection = dir;
Event.stop(event);
}
}
},
finishResize: function(event, success) {
// this.unregisterEvents();
this.active = false;
this.resizing = false;
if(this.options.zindex)
this.element.style.zIndex = this.originalZ;
if (this.options.resize) {
this.options.resize(this.element);
}
},
keyPress: function(event) {
if(this.active) {
if(event.keyCode==Event.KEY_ESC) {
this.finishResize(event, false);
Event.stop(event);
}
}
},
endResize: function(event) {
if(this.active && this.resizing) {
this.finishResize(event, true);
Event.stop(event);
}
this.active = false;
this.resizing = false;
},
draw: function(event) {
var pointer = [Event.pointerX(event), Event.pointerY(event)];
var style = this.element.style;
if (this.currentDirection.indexOf('n') != -1) {
var pointerMoved = this.startY - pointer[1];
var margin = Element.getStyle(this.element, 'margin-top') || "0";
var newHeight = this.startHeight + pointerMoved;
if (newHeight > this.options.minHeight) {
style.height = newHeight + "px";
style.top = (this.startTop - pointerMoved - parseInt(margin)) + "px";
}
}
if (this.currentDirection.indexOf('w') != -1) {
var pointerMoved = this.startX - pointer[0];
var margin = Element.getStyle(this.element, 'margin-left') || "0";
var newWidth = this.startWidth + pointerMoved;
if (newWidth > this.options.minWidth) {
style.left = (this.startLeft - pointerMoved - parseInt(margin)) + "px";
style.width = newWidth + "px";
}
}
if (this.currentDirection.indexOf('s') != -1) {
var newHeight = this.startHeight + pointer[1] - this.startY;
if (newHeight > this.options.minHeight) {
style.height = newHeight + "px";
}
}
if (this.currentDirection.indexOf('e') != -1) {
var newWidth = this.startWidth + pointer[0] - this.startX;
if (newWidth > this.options.minWidth) {
style.width = newWidth + "px";
}
}
if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
},
between: function(val, low, high) {
return (val >= low && val < high);
},
directions: function(event) {
var pointer = [Event.pointerX(event), Event.pointerY(event)];
var offsets = Position.cumulativeOffset(this.element);
var cursor = '';
if (this.between(pointer[1] - offsets[1], 0, this.options.top)) cursor += 'n';
if (this.between((offsets[1] + this.element.offsetHeight) - pointer[1], 0, this.options.bottom)) cursor += 's';
if (this.between(pointer[0] - offsets[0], 0, this.options.left)) cursor += 'w';
if (this.between((offsets[0] + this.element.offsetWidth) - pointer[0], 0, this.options.right)) cursor += 'e';
return cursor;
},
cursor: function(event) {
var cursor = this.directions(event);
if (cursor.length > 0) {
cursor += '-resize';
} else {
cursor = '';
}
this.element.style.cursor = cursor;
},
update: function(event) {
if(this.active) {
if(!this.resizing) {
var style = this.element.style;
this.resizing = true;
if(Element.getStyle(this.element,'position')=='')
style.position = "relative";
if(this.options.zindex) {
this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
style.zIndex = this.options.zindex;
}
}
this.draw(event);
// fix AppleWebKit rendering
if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
Event.stop(event);
return false;
}
}
}
@@ -0,0 +1,58 @@
// script.aculo.us scriptaculous.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007
// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// For details, see the script.aculo.us web site: http://script.aculo.us/
var Scriptaculous = {
Version: '1.7.1_beta3',
require: function(libraryName) {
// inserting via DOM fails in Safari 2.0, so brute force approach
document.write('<script type="text/javascript" src="'+libraryName+'"></script>');
},
REQUIRED_PROTOTYPE: '1.5.1',
load: function() {
function convertVersionString(versionString){
var r = versionString.split('.');
return parseInt(r[0])*100000 + parseInt(r[1])*1000 + parseInt(r[2]);
}
if((typeof Prototype=='undefined') ||
(typeof Element == 'undefined') ||
(typeof Element.Methods=='undefined') ||
(convertVersionString(Prototype.Version) <
convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE)))
throw("script.aculo.us requires the Prototype JavaScript framework >= " +
Scriptaculous.REQUIRED_PROTOTYPE);
$A(document.getElementsByTagName("script")).findAll( function(s) {
return (s.src && s.src.match(/scriptaculous\.js(\?.*)?$/))
}).each( function(s) {
var path = s.src.replace(/scriptaculous\.js(\?.*)?$/,'');
var includes = s.src.match(/\?.*load=([a-z,]*)/);
(includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each(
function(include) { Scriptaculous.require(path+include+'.js') });
});
}
}
Scriptaculous.load();
@@ -0,0 +1,277 @@
// script.aculo.us slider.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007
// Copyright (c) 2005-2007 Marty Haught, Thomas Fuchs
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/
if(!Control) var Control = {};
Control.Slider = Class.create();
// options:
// axis: 'vertical', or 'horizontal' (default)
//
// callbacks:
// onChange(value)
// onSlide(value)
Control.Slider.prototype = {
initialize: function(handle, track, options) {
var slider = this;
if(handle instanceof Array) {
this.handles = handle.collect( function(e) { return $(e) });
} else {
this.handles = [$(handle)];
}
this.track = $(track);
this.options = options || {};
this.axis = this.options.axis || 'horizontal';
this.increment = this.options.increment || 1;
this.step = parseInt(this.options.step || '1');
this.range = this.options.range || $R(0,1);
this.value = 0; // assure backwards compat
this.values = this.handles.map( function() { return 0 });
this.spans = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false;
this.options.startSpan = $(this.options.startSpan || null);
this.options.endSpan = $(this.options.endSpan || null);
this.restricted = this.options.restricted || false;
this.maximum = this.options.maximum || this.range.end;
this.minimum = this.options.minimum || this.range.start;
// Will be used to align the handle onto the track, if necessary
this.alignX = parseInt(this.options.alignX || '0');
this.alignY = parseInt(this.options.alignY || '0');
this.trackLength = this.maximumOffset() - this.minimumOffset();
this.handleLength = this.isVertical() ?
(this.handles[0].offsetHeight != 0 ?
this.handles[0].offsetHeight : this.handles[0].style.height.replace(/px$/,"")) :
(this.handles[0].offsetWidth != 0 ? this.handles[0].offsetWidth :
this.handles[0].style.width.replace(/px$/,""));
this.active = false;
this.dragging = false;
this.disabled = false;
if(this.options.disabled) this.setDisabled();
// Allowed values array
this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false;
if(this.allowedValues) {
this.minimum = this.allowedValues.min();
this.maximum = this.allowedValues.max();
}
this.eventMouseDown = this.startDrag.bindAsEventListener(this);
this.eventMouseUp = this.endDrag.bindAsEventListener(this);
this.eventMouseMove = this.update.bindAsEventListener(this);
// Initialize handles in reverse (make sure first handle is active)
this.handles.each( function(h,i) {
i = slider.handles.length-1-i;
slider.setValue(parseFloat(
(slider.options.sliderValue instanceof Array ?
slider.options.sliderValue[i] : slider.options.sliderValue) ||
slider.range.start), i);
Element.makePositioned(h); // fix IE
Event.observe(h, "mousedown", slider.eventMouseDown);
});
Event.observe(this.track, "mousedown", this.eventMouseDown);
Event.observe(document, "mouseup", this.eventMouseUp);
Event.observe(document, "mousemove", this.eventMouseMove);
this.initialized = true;
},
dispose: function() {
var slider = this;
Event.stopObserving(this.track, "mousedown", this.eventMouseDown);
Event.stopObserving(document, "mouseup", this.eventMouseUp);
Event.stopObserving(document, "mousemove", this.eventMouseMove);
this.handles.each( function(h) {
Event.stopObserving(h, "mousedown", slider.eventMouseDown);
});
},
setDisabled: function(){
this.disabled = true;
},
setEnabled: function(){
this.disabled = false;
},
getNearestValue: function(value){
if(this.allowedValues){
if(value >= this.allowedValues.max()) return(this.allowedValues.max());
if(value <= this.allowedValues.min()) return(this.allowedValues.min());
var offset = Math.abs(this.allowedValues[0] - value);
var newValue = this.allowedValues[0];
this.allowedValues.each( function(v) {
var currentOffset = Math.abs(v - value);
if(currentOffset <= offset){
newValue = v;
offset = currentOffset;
}
});
return newValue;
}
if(value > this.range.end) return this.range.end;
if(value < this.range.start) return this.range.start;
return value;
},
setValue: function(sliderValue, handleIdx){
if(!this.active) {
this.activeHandleIdx = handleIdx || 0;
this.activeHandle = this.handles[this.activeHandleIdx];
this.updateStyles();
}
handleIdx = handleIdx || this.activeHandleIdx || 0;
if(this.initialized && this.restricted) {
if((handleIdx>0) && (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 != 0 ? this.track.offsetHeight :
this.track.style.height.replace(/px$/,"")) - this.alignY :
(this.track.offsetWidth != 0 ? this.track.offsetWidth :
this.track.style.width.replace(/px$/,"")) - this.alignY);
},
isVertical: function(){
return (this.axis == 'vertical');
},
drawSpans: function() {
var slider = this;
if(this.spans)
$R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) });
if(this.options.startSpan)
this.setSpan(this.options.startSpan,
$R(0, this.values.length>1 ? this.getRange(0).min() : this.value ));
if(this.options.endSpan)
this.setSpan(this.options.endSpan,
$R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum));
},
setSpan: function(span, range) {
if(this.isVertical()) {
span.style.top = this.translateToPx(range.start);
span.style.height = this.translateToPx(range.end - range.start + this.range.start);
} else {
span.style.left = this.translateToPx(range.start);
span.style.width = this.translateToPx(range.end - range.start + this.range.start);
}
},
updateStyles: function() {
this.handles.each( function(h){ Element.removeClassName(h, 'selected') });
Element.addClassName(this.activeHandle, 'selected');
},
startDrag: function(event) {
if(Event.isLeftClick(event)) {
if(!this.disabled){
this.active = true;
var handle = Event.element(event);
var pointer = [Event.pointerX(event), Event.pointerY(event)];
var track = handle;
if(track==this.track) {
var offsets = Position.cumulativeOffset(this.track);
this.event = event;
this.setValue(this.translateToValue(
(this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2)
));
var offsets = Position.cumulativeOffset(this.activeHandle);
this.offsetX = (pointer[0] - offsets[0]);
this.offsetY = (pointer[1] - offsets[1]);
} else {
// find the handle (prevents issues with Safari)
while((this.handles.indexOf(handle) == -1) && handle.parentNode)
handle = handle.parentNode;
if(this.handles.indexOf(handle)!=-1) {
this.activeHandle = handle;
this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
this.updateStyles();
var offsets = Position.cumulativeOffset(this.activeHandle);
this.offsetX = (pointer[0] - offsets[0]);
this.offsetY = (pointer[1] - offsets[1]);
}
}
}
Event.stop(event);
}
},
update: function(event) {
if(this.active) {
if(!this.dragging) this.dragging = true;
this.draw(event);
if(Prototype.Browser.WebKit) window.scrollBy(0,0);
Event.stop(event);
}
},
draw: function(event) {
var pointer = [Event.pointerX(event), Event.pointerY(event)];
var offsets = Position.cumulativeOffset(this.track);
pointer[0] -= this.offsetX + offsets[0];
pointer[1] -= this.offsetY + offsets[1];
this.event = event;
this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
if(this.initialized && this.options.onSlide)
this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
},
endDrag: function(event) {
if(this.active && this.dragging) {
this.finishDrag(event, true);
Event.stop(event);
}
this.active = false;
this.dragging = false;
},
finishDrag: function(event, success) {
this.active = false;
this.dragging = false;
this.updateFinished();
},
updateFinished: function() {
if(this.initialized && this.options.onChange)
this.options.onChange(this.values.length>1 ? this.values : this.value, this);
this.event = null;
}
}
@@ -0,0 +1,60 @@
// script.aculo.us sound.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007
// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//
// Based on code created by Jules Gravinese (http://www.webveteran.com/)
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/
Sound = {
tracks: {},
_enabled: true,
template:
new Template('<embed style="height:0" id="sound_#{track}_#{id}" src="#{url}" loop="false" autostart="true" hidden="true"/>'),
enable: function(){
Sound._enabled = true;
},
disable: function(){
Sound._enabled = false;
},
play: function(url){
if(!Sound._enabled) return;
var options = Object.extend({
track: 'global', url: url, replace: false
}, arguments[1] || {});
if(options.replace && this.tracks[options.track]) {
$R(0, this.tracks[options.track].id).each(function(id){
var sound = $('sound_'+options.track+'_'+id);
sound.Stop && sound.Stop();
sound.remove();
})
this.tracks[options.track] = null;
}
if(!this.tracks[options.track])
this.tracks[options.track] = { id: 0 }
else
this.tracks[options.track].id++;
options.id = this.tracks[options.track].id;
if (Prototype.Browser.IE) {
var sound = document.createElement('bgsound');
sound.setAttribute('id','sound_'+options.track+'_'+options.id);
sound.setAttribute('src',options.url);
sound.setAttribute('loop','1');
sound.setAttribute('autostart','true');
$$('body')[0].appendChild(sound);
}
else
new Insertion.Bottom($$('body')[0], Sound.template.evaluate(options));
}
};
if(Prototype.Browser.Gecko && navigator.userAgent.indexOf("Win") > 0){
if(navigator.plugins && $A(navigator.plugins).detect(function(p){ return p.name.indexOf('QuickTime') != -1 }))
Sound.template = new Template('<object id="sound_#{track}_#{id}" width="0" height="0" type="audio/mpeg" data="#{url}"/>')
else
Sound.play = function(){}
}
@@ -0,0 +1,564 @@
// script.aculo.us unittest.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007
// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
// (c) 2005-2007 Michael Schuerig (http://www.schuerig.de/michael/)
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/
// experimental, Firefox-only
Event.simulateMouse = function(element, eventName) {
var options = Object.extend({
pointerX: 0,
pointerY: 0,
buttons: 0,
ctrlKey: false,
altKey: false,
shiftKey: false,
metaKey: false
}, arguments[2] || {});
var oEvent = document.createEvent("MouseEvents");
oEvent.initMouseEvent(eventName, true, true, document.defaultView,
options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY,
options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 0, $(element));
if(this.mark) Element.remove(this.mark);
this.mark = document.createElement('div');
this.mark.appendChild(document.createTextNode(" "));
document.body.appendChild(this.mark);
this.mark.style.position = 'absolute';
this.mark.style.top = options.pointerY + "px";
this.mark.style.left = options.pointerX + "px";
this.mark.style.width = "5px";
this.mark.style.height = "5px;";
this.mark.style.borderTop = "1px solid red;"
this.mark.style.borderLeft = "1px solid red;"
if(this.step)
alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options));
$(element).dispatchEvent(oEvent);
};
// Note: Due to a fix in Firefox 1.0.5/6 that probably fixed "too much", this doesn't work in 1.0.6 or DP2.
// You need to downgrade to 1.0.4 for now to get this working
// See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much
Event.simulateKey = function(element, eventName) {
var options = Object.extend({
ctrlKey: false,
altKey: false,
shiftKey: false,
metaKey: false,
keyCode: 0,
charCode: 0
}, arguments[2] || {});
var oEvent = document.createEvent("KeyEvents");
oEvent.initKeyEvent(eventName, true, true, window,
options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
options.keyCode, options.charCode );
$(element).dispatchEvent(oEvent);
};
Event.simulateKeys = function(element, command) {
for(var i=0; i<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.className = "nameCell";
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);
this.addLinksToResults();
},
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/>");
},
addLinksToResults: function(){
$$("tr.failed .nameCell").each( function(td){ // todo: limit to children of this.log
td.title = "Run only this test"
Event.observe(td, 'click', function(){ window.location.search = "?tests=" + td.innerHTML;});
});
$$("tr.passed .nameCell").each( function(td){ // todo: limit to children of this.log
td.title = "Run all tests"
Event.observe(td, 'click', function(){ window.location.search = "";});
});
}
}
Test.Unit.Runner = Class.create();
Test.Unit.Runner.prototype = {
initialize: function(testcases) {
this.options = Object.extend({
testLog: 'testlog'
}, arguments[1] || {});
this.options.resultsURL = this.parseResultsURLQueryParameter();
this.options.tests = this.parseTestsQueryParameter();
if (this.options.testLog) {
this.options.testLog = $(this.options.testLog) || null;
}
if(this.options.tests) {
this.tests = [];
for(var i = 0; i < this.options.tests.length; i++) {
if(/^test/.test(this.options.tests[i])) {
this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"]));
}
}
} else {
if (this.options.test) {
this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])];
} else {
this.tests = [];
for(var testcase in testcases) {
if(/^test/.test(testcase)) {
this.tests.push(
new Test.Unit.Testcase(
this.options.context ? ' -> ' + this.options.titles[testcase] : testcase,
testcases[testcase], testcases["setup"], testcases["teardown"]
));
}
}
}
}
this.currentTest = 0;
this.logger = new Test.Unit.Logger(this.options.testLog);
setTimeout(this.runTests.bind(this), 1000);
},
parseResultsURLQueryParameter: function() {
return window.location.search.parseQuery()["resultsURL"];
},
parseTestsQueryParameter: function(){
if (window.location.search.parseQuery()["tests"]){
return window.location.search.parseQuery()["tests"].split(',');
};
},
// Returns:
// "ERROR" if there was an error,
// "FAILURE" if there was a failure, or
// "SUCCESS" if there was neither
getResult: function() {
var hasFailure = false;
for(var i=0;i<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.options.context ? this.options.context + ': ': '') +
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); }
},
assertInspect: function(expected, actual) {
var message = arguments[2] || "assertInspect";
try { (expected == actual.inspect()) ? this.pass() :
this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
'", actual "' + Test.Unit.inspect(actual) + '"'); }
catch(e) { this.error(e); }
},
assertEnumEqual: function(expected, actual) {
var message = arguments[2] || "assertEnumEqual";
try { $A(expected).length == $A(actual).length &&
expected.zip(actual).all(function(pair) { return pair[0] == pair[1] }) ?
this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) +
', actual ' + Test.Unit.inspect(actual)); }
catch(e) { this.error(e); }
},
assertNotEqual: function(expected, actual) {
var message = arguments[2] || "assertNotEqual";
try { (expected != actual) ? this.pass() :
this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); }
catch(e) { this.error(e); }
},
assertIdentical: function(expected, actual) {
var message = arguments[2] || "assertIdentical";
try { (expected === actual) ? this.pass() :
this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
'", actual "' + Test.Unit.inspect(actual) + '"'); }
catch(e) { this.error(e); }
},
assertNotIdentical: function(expected, actual) {
var message = arguments[2] || "assertNotIdentical";
try { !(expected === actual) ? this.pass() :
this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
'", actual "' + Test.Unit.inspect(actual) + '"'); }
catch(e) { this.error(e); }
},
assertNull: function(obj) {
var message = arguments[1] || 'assertNull'
try { (obj==null) ? this.pass() :
this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); }
catch(e) { this.error(e); }
},
assertMatch: function(expected, actual) {
var message = arguments[2] || 'assertMatch';
var regex = new RegExp(expected);
try { (regex.exec(actual)) ? this.pass() :
this.fail(message + ' : regex: "' + Test.Unit.inspect(expected) + ' did not match: ' + Test.Unit.inspect(actual) + '"'); }
catch(e) { this.error(e); }
},
assertHidden: function(element) {
var message = arguments[1] || 'assertHidden';
this.assertEqual("none", element.style.display, message);
},
assertNotNull: function(object) {
var message = arguments[1] || 'assertNotNull';
this.assert(object != null, message);
},
assertType: function(expected, actual) {
var message = arguments[2] || 'assertType';
try {
(actual.constructor == expected) ? this.pass() :
this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
'", actual "' + (actual.constructor) + '"'); }
catch(e) { this.error(e); }
},
assertNotOfType: function(expected, actual) {
var message = arguments[2] || 'assertNotOfType';
try {
(actual.constructor != expected) ? this.pass() :
this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
'", actual "' + (actual.constructor) + '"'); }
catch(e) { this.error(e); }
},
assertInstanceOf: function(expected, actual) {
var message = arguments[2] || 'assertInstanceOf';
try {
(actual instanceof expected) ? this.pass() :
this.fail(message + ": object was not an instance of the expected type"); }
catch(e) { this.error(e); }
},
assertNotInstanceOf: function(expected, actual) {
var message = arguments[2] || 'assertNotInstanceOf';
try {
!(actual instanceof expected) ? this.pass() :
this.fail(message + ": object was an instance of the not expected type"); }
catch(e) { this.error(e); }
},
assertRespondsTo: function(method, obj) {
var message = arguments[2] || 'assertRespondsTo';
try {
(obj[method] && typeof obj[method] == 'function') ? this.pass() :
this.fail(message + ": object doesn't respond to [" + method + "]"); }
catch(e) { this.error(e); }
},
assertReturnsTrue: function(method, obj) {
var message = arguments[2] || 'assertReturnsTrue';
try {
var m = obj[method];
if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
m() ? this.pass() :
this.fail(message + ": method returned false"); }
catch(e) { this.error(e); }
},
assertReturnsFalse: function(method, obj) {
var message = arguments[2] || 'assertReturnsFalse';
try {
var m = obj[method];
if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
!m() ? this.pass() :
this.fail(message + ": method returned true"); }
catch(e) { this.error(e); }
},
assertRaise: function(exceptionName, method) {
var message = arguments[2] || 'assertRaise';
try {
method();
this.fail(message + ": exception expected but none was raised"); }
catch(e) {
((exceptionName == null) || (e.name==exceptionName)) ? this.pass() : this.error(e);
}
},
assertElementsMatch: function() {
var expressions = $A(arguments), elements = $A(expressions.shift());
if (elements.length != expressions.length) {
this.fail('assertElementsMatch: size mismatch: ' + elements.length + ' elements, ' + expressions.length + ' expressions');
return false;
}
elements.zip(expressions).all(function(pair, index) {
var element = $(pair.first()), expression = pair.last();
if (element.match(expression)) return true;
this.fail('assertElementsMatch: (in index ' + index + ') expected ' + expression.inspect() + ' but got ' + element.inspect());
}.bind(this)) && this.pass();
},
assertElementMatches: function(element, expression) {
this.assertElementsMatch([element], expression);
},
benchmark: function(operation, iterations) {
var startAt = new Date();
(iterations || 1).times(operation);
var timeTaken = ((new Date())-startAt);
this.info((arguments[2] || 'Operation') + ' finished ' +
iterations + ' iterations in ' + (timeTaken/1000)+'s' );
return timeTaken;
},
_isVisible: function(element) {
element = $(element);
if(!element.parentNode) return true;
this.assertNotNull(element);
if(element.style && Element.getStyle(element, 'display') == 'none')
return false;
return this._isVisible(element.parentNode);
},
assertNotVisible: function(element) {
this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1]));
},
assertVisible: function(element) {
this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1]));
},
benchmark: function(operation, iterations) {
var startAt = new Date();
(iterations || 1).times(operation);
var timeTaken = ((new Date())-startAt);
this.info((arguments[2] || 'Operation') + ' finished ' +
iterations + ' iterations in ' + (timeTaken/1000)+'s' );
return timeTaken;
}
}
Test.Unit.Testcase = Class.create();
Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), {
initialize: function(name, test, setup, teardown) {
Test.Unit.Assertions.prototype.initialize.bind(this)();
this.name = name;
if(typeof test == 'string') {
test = test.gsub(/(\.should[^\(]+\()/,'#{0}this,');
test = test.gsub(/(\.should[^\(]+)\(this,\)/,'#{1}(this)');
this.test = function() {
eval('with(this){'+test+'}');
}
} else {
this.test = test || function() {};
}
this.setup = setup || function() {};
this.teardown = teardown || function() {};
this.isWaiting = false;
this.timeToWait = 1000;
},
wait: function(time, nextPart) {
this.isWaiting = true;
this.test = nextPart;
this.timeToWait = time;
},
run: function() {
try {
try {
if (!this.isWaiting) this.setup.bind(this)();
this.isWaiting = false;
this.test.bind(this)();
} finally {
if(!this.isWaiting) {
this.teardown.bind(this)();
}
}
}
catch(e) { this.error(e); }
}
});
// *EXPERIMENTAL* BDD-style testing to please non-technical folk
// This draws many ideas from RSpec http://rspec.rubyforge.org/
Test.setupBDDExtensionMethods = function(){
var METHODMAP = {
shouldEqual: 'assertEqual',
shouldNotEqual: 'assertNotEqual',
shouldEqualEnum: 'assertEnumEqual',
shouldBeA: 'assertType',
shouldNotBeA: 'assertNotOfType',
shouldBeAn: 'assertType',
shouldNotBeAn: 'assertNotOfType',
shouldBeNull: 'assertNull',
shouldNotBeNull: 'assertNotNull',
shouldBe: 'assertReturnsTrue',
shouldNotBe: 'assertReturnsFalse',
shouldRespondTo: 'assertRespondsTo'
};
Test.BDDMethods = {};
for(m in METHODMAP) {
Test.BDDMethods[m] = eval(
'function(){'+
'var args = $A(arguments);'+
'var scope = args.shift();'+
'scope.'+METHODMAP[m]+'.apply(scope,(args || []).concat([this])); }');
}
[Array.prototype, String.prototype, Number.prototype].each(
function(p){ Object.extend(p, Test.BDDMethods) }
);
}
Test.context = function(name, spec, log){
Test.setupBDDExtensionMethods();
var compiledSpec = {};
var titles = {};
for(specName in spec) {
switch(specName){
case "setup":
case "teardown":
compiledSpec[specName] = spec[specName];
break;
default:
var testName = 'test'+specName.gsub(/\s+/,'-').camelize();
var body = spec[specName].toString().split('\n').slice(1);
if(/^\{/.test(body[0])) body = body.slice(1);
body.pop();
body = body.map(function(statement){
return statement.strip()
});
compiledSpec[testName] = body.join('\n');
titles[testName] = specName;
}
}
new Test.Unit.Runner(compiledSpec, { titles: titles, testLog: log || 'testlog', context: name });
};