From: Art Cancro Date: Fri, 9 Sep 2005 04:43:31 +0000 (+0000) Subject: * Checked in a copy of the "script.aculo.us" library by Thomas Fuchs. X-Git-Tag: v7.86~4678 X-Git-Url: https://code.citadel.org/?a=commitdiff_plain;h=81958371a25a49f12b80139817f14d6180f27c65;p=citadel.git * Checked in a copy of the "script.aculo.us" library by Thomas Fuchs. NOTE: I had to search-and-replace "Effect" to "ScriptaculousEffect" in all of their files, to avoid a conflict with the name "Effect" in Rico. * Implemented recipient autocompletion when composing mail, using the script.aculo.us drop-down box and an ajax fetch. Cool!! --- diff --git a/webcit/po/Makefile.in b/webcit/po/Makefile.in deleted file mode 100644 index 84f09b2e9..000000000 --- a/webcit/po/Makefile.in +++ /dev/null @@ -1,366 +0,0 @@ -# Makefile for PO directory in any package using GNU gettext. -# Copyright (C) 1995-1997, 2000-2004 by Ulrich Drepper -# -# This file can be copied and used freely without restrictions. It can -# be used in projects which are not available under the GNU General Public -# License but which still want to provide support for the GNU gettext -# functionality. -# Please note that the actual code of GNU gettext is covered by the GNU -# General Public License and is *not* in the public domain. -# -# Origin: gettext-0.14 - -PACKAGE = webcit -VERSION = 6.21 - -SHELL = /bin/sh - - -srcdir = . -top_srcdir = .. - - -prefix = /appl/citadel -exec_prefix = ${prefix} -datadir = ${prefix}/share -localedir = $(datadir)/locale -gettextsrcdir = $(datadir)/gettext/po - -INSTALL = /usr/bin/install -c -INSTALL_DATA = ${INSTALL} -m 644 -MKINSTALLDIRS = $(top_builddir)/./mkinstalldirs -mkinstalldirs = $(SHELL) $(MKINSTALLDIRS) - -GMSGFMT = /usr/bin/msgfmt -MSGFMT = /usr/bin/msgfmt -XGETTEXT = /usr/bin/xgettext -MSGMERGE = msgmerge -MSGMERGE_UPDATE = /usr/bin/msgmerge --update -MSGINIT = msginit -MSGCONV = msgconv -MSGFILTER = msgfilter - -POFILES = @POFILES@ -GMOFILES = @GMOFILES@ -UPDATEPOFILES = @UPDATEPOFILES@ -DUMMYPOFILES = @DUMMYPOFILES@ -DISTFILES.common = Makefile.in.in remove-potcdate.sin \ -$(DISTFILES.common.extra1) $(DISTFILES.common.extra2) $(DISTFILES.common.extra3) -DISTFILES = $(DISTFILES.common) Makevars POTFILES.in $(DOMAIN).pot stamp-po \ -$(POFILES) $(GMOFILES) \ -$(DISTFILES.extra1) $(DISTFILES.extra2) $(DISTFILES.extra3) - -POTFILES = \ - -CATALOGS = @CATALOGS@ - -# Makevars gets inserted here. (Don't remove this line!) - -.SUFFIXES: -.SUFFIXES: .po .gmo .mo .sed .sin .nop .po-create .po-update - -.po.mo: - @echo "$(MSGFMT) -c -o $@ $<"; \ - $(MSGFMT) -c -o t-$@ $< && mv t-$@ $@ - -.po.gmo: - @lang=`echo $* | sed -e 's,.*/,,'`; \ - test "$(srcdir)" = . && cdcmd="" || cdcmd="cd $(srcdir) && "; \ - echo "$${cdcmd}rm -f $${lang}.gmo && $(GMSGFMT) -c --statistics -o $${lang}.gmo $${lang}.po"; \ - cd $(srcdir) && rm -f $${lang}.gmo && $(GMSGFMT) -c --statistics -o t-$${lang}.gmo $${lang}.po && mv t-$${lang}.gmo $${lang}.gmo - -.sin.sed: - sed -e '/^#/d' $< > t-$@ - mv t-$@ $@ - - -all: all-yes - -all-yes: stamp-po -all-no: - -# stamp-po is a timestamp denoting the last time at which the CATALOGS have -# been loosely updated. Its purpose is that when a developer or translator -# checks out the package via CVS, and the $(DOMAIN).pot file is not in CVS, -# "make" will update the $(DOMAIN).pot and the $(CATALOGS), but subsequent -# invocations of "make" will do nothing. This timestamp would not be necessary -# if updating the $(CATALOGS) would always touch them; however, the rule for -# $(POFILES) has been designed to not touch files that don't need to be -# changed. -stamp-po: $(srcdir)/$(DOMAIN).pot - test -z "$(GMOFILES)" || $(MAKE) $(GMOFILES) - @echo "touch stamp-po" - @echo timestamp > stamp-poT - @mv stamp-poT stamp-po - -# Note: Target 'all' must not depend on target '$(DOMAIN).pot-update', -# otherwise packages like GCC can not be built if only parts of the source -# have been downloaded. - -# This target rebuilds $(DOMAIN).pot; it is an expensive operation. -# Note that $(DOMAIN).pot is not touched if it doesn't need to be changed. -$(DOMAIN).pot-update: $(POTFILES) $(srcdir)/POTFILES.in remove-potcdate.sed - $(XGETTEXT) --default-domain=$(DOMAIN) --directory=$(top_srcdir) \ - --add-comments=TRANSLATORS: $(XGETTEXT_OPTIONS) \ - --files-from=$(srcdir)/POTFILES.in \ - --copyright-holder='$(COPYRIGHT_HOLDER)' \ - --msgid-bugs-address='$(MSGID_BUGS_ADDRESS)' - test ! -f $(DOMAIN).po || { \ - if test -f $(srcdir)/$(DOMAIN).pot; then \ - sed -f remove-potcdate.sed < $(srcdir)/$(DOMAIN).pot > $(DOMAIN).1po && \ - sed -f remove-potcdate.sed < $(DOMAIN).po > $(DOMAIN).2po && \ - if cmp $(DOMAIN).1po $(DOMAIN).2po >/dev/null 2>&1; then \ - rm -f $(DOMAIN).1po $(DOMAIN).2po $(DOMAIN).po; \ - else \ - rm -f $(DOMAIN).1po $(DOMAIN).2po $(srcdir)/$(DOMAIN).pot && \ - mv $(DOMAIN).po $(srcdir)/$(DOMAIN).pot; \ - fi; \ - else \ - mv $(DOMAIN).po $(srcdir)/$(DOMAIN).pot; \ - fi; \ - } - -# This rule has no dependencies: we don't need to update $(DOMAIN).pot at -# every "make" invocation, only create it when it is missing. -# Only "make $(DOMAIN).pot-update" or "make dist" will force an update. -$(srcdir)/$(DOMAIN).pot: - $(MAKE) $(DOMAIN).pot-update - -# This target rebuilds a PO file if $(DOMAIN).pot has changed. -# Note that a PO file is not touched if it doesn't need to be changed. -$(POFILES): $(srcdir)/$(DOMAIN).pot - @lang=`echo $@ | sed -e 's,.*/,,' -e 's/\.po$$//'`; \ - if test -f "$(srcdir)/$${lang}.po"; then \ - test "$(srcdir)" = . && cdcmd="" || cdcmd="cd $(srcdir) && "; \ - echo "$${cdcmd}$(MSGMERGE_UPDATE) $${lang}.po $(DOMAIN).pot"; \ - cd $(srcdir) && $(MSGMERGE_UPDATE) $${lang}.po $(DOMAIN).pot; \ - else \ - $(MAKE) $${lang}.po-create; \ - fi - - -install: install-exec install-data -install-exec: -install-data: install-data-yes - if test "$(PACKAGE)" = "gettext-tools"; then \ - $(mkinstalldirs) $(DESTDIR)$(gettextsrcdir); \ - for file in $(DISTFILES.common) Makevars.template; do \ - $(INSTALL_DATA) $(srcdir)/$$file \ - $(DESTDIR)$(gettextsrcdir)/$$file; \ - done; \ - for file in Makevars; do \ - rm -f $(DESTDIR)$(gettextsrcdir)/$$file; \ - done; \ - else \ - : ; \ - fi -install-data-no: all -install-data-yes: all - $(mkinstalldirs) $(DESTDIR)$(datadir) - @catalogs='$(CATALOGS)'; \ - for cat in $$catalogs; do \ - cat=`basename $$cat`; \ - lang=`echo $$cat | sed -e 's/\.gmo$$//'`; \ - dir=$(localedir)/$$lang/LC_MESSAGES; \ - $(mkinstalldirs) $(DESTDIR)$$dir; \ - if test -r $$cat; then realcat=$$cat; else realcat=$(srcdir)/$$cat; fi; \ - $(INSTALL_DATA) $$realcat $(DESTDIR)$$dir/$(DOMAIN).mo; \ - echo "installing $$realcat as $(DESTDIR)$$dir/$(DOMAIN).mo"; \ - for lc in '' $(EXTRA_LOCALE_CATEGORIES); do \ - if test -n "$$lc"; then \ - if (cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc 2>/dev/null) | grep ' -> ' >/dev/null; then \ - link=`cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc | sed -e 's/^.* -> //'`; \ - mv $(DESTDIR)$(localedir)/$$lang/$$lc $(DESTDIR)$(localedir)/$$lang/$$lc.old; \ - mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \ - (cd $(DESTDIR)$(localedir)/$$lang/$$lc.old && \ - for file in *; do \ - if test -f $$file; then \ - ln -s ../$$link/$$file $(DESTDIR)$(localedir)/$$lang/$$lc/$$file; \ - fi; \ - done); \ - rm -f $(DESTDIR)$(localedir)/$$lang/$$lc.old; \ - else \ - if test -d $(DESTDIR)$(localedir)/$$lang/$$lc; then \ - :; \ - else \ - rm -f $(DESTDIR)$(localedir)/$$lang/$$lc; \ - mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \ - fi; \ - fi; \ - rm -f $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo; \ - ln -s ../LC_MESSAGES/$(DOMAIN).mo $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo 2>/dev/null || \ - ln $(DESTDIR)$(localedir)/$$lang/LC_MESSAGES/$(DOMAIN).mo $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo 2>/dev/null || \ - cp -p $(DESTDIR)$(localedir)/$$lang/LC_MESSAGES/$(DOMAIN).mo $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo; \ - echo "installing $$realcat link as $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo"; \ - fi; \ - done; \ - done - -install-strip: install - -installdirs: installdirs-exec installdirs-data -installdirs-exec: -installdirs-data: installdirs-data-yes - if test "$(PACKAGE)" = "gettext-tools"; then \ - $(mkinstalldirs) $(DESTDIR)$(gettextsrcdir); \ - else \ - : ; \ - fi -installdirs-data-no: -installdirs-data-yes: - $(mkinstalldirs) $(DESTDIR)$(datadir) - @catalogs='$(CATALOGS)'; \ - for cat in $$catalogs; do \ - cat=`basename $$cat`; \ - lang=`echo $$cat | sed -e 's/\.gmo$$//'`; \ - dir=$(localedir)/$$lang/LC_MESSAGES; \ - $(mkinstalldirs) $(DESTDIR)$$dir; \ - for lc in '' $(EXTRA_LOCALE_CATEGORIES); do \ - if test -n "$$lc"; then \ - if (cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc 2>/dev/null) | grep ' -> ' >/dev/null; then \ - link=`cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc | sed -e 's/^.* -> //'`; \ - mv $(DESTDIR)$(localedir)/$$lang/$$lc $(DESTDIR)$(localedir)/$$lang/$$lc.old; \ - mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \ - (cd $(DESTDIR)$(localedir)/$$lang/$$lc.old && \ - for file in *; do \ - if test -f $$file; then \ - ln -s ../$$link/$$file $(DESTDIR)$(localedir)/$$lang/$$lc/$$file; \ - fi; \ - done); \ - rm -f $(DESTDIR)$(localedir)/$$lang/$$lc.old; \ - else \ - if test -d $(DESTDIR)$(localedir)/$$lang/$$lc; then \ - :; \ - else \ - rm -f $(DESTDIR)$(localedir)/$$lang/$$lc; \ - mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \ - fi; \ - fi; \ - fi; \ - done; \ - done - -# Define this as empty until I found a useful application. -installcheck: - -uninstall: uninstall-exec uninstall-data -uninstall-exec: -uninstall-data: uninstall-data-yes - if test "$(PACKAGE)" = "gettext-tools"; then \ - for file in $(DISTFILES.common) Makevars.template; do \ - rm -f $(DESTDIR)$(gettextsrcdir)/$$file; \ - done; \ - else \ - : ; \ - fi -uninstall-data-no: -uninstall-data-yes: - catalogs='$(CATALOGS)'; \ - for cat in $$catalogs; do \ - cat=`basename $$cat`; \ - lang=`echo $$cat | sed -e 's/\.gmo$$//'`; \ - for lc in LC_MESSAGES $(EXTRA_LOCALE_CATEGORIES); do \ - rm -f $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo; \ - done; \ - done - -check: all - -info dvi ps pdf html tags TAGS ctags CTAGS ID: - -mostlyclean: - rm -f remove-potcdate.sed - rm -f stamp-poT - rm -f core core.* $(DOMAIN).po $(DOMAIN).1po $(DOMAIN).2po *.new.po - rm -fr *.o - -clean: mostlyclean - -distclean: clean - rm -f Makefile Makefile.in POTFILES *.mo - -maintainer-clean: distclean - @echo "This command is intended for maintainers to use;" - @echo "it deletes files that may require special tools to rebuild." - rm -f stamp-po $(GMOFILES) - -distdir = $(top_builddir)/$(PACKAGE)-$(VERSION)/$(subdir) -dist distdir: - $(MAKE) update-po - @$(MAKE) dist2 -# This is a separate target because 'update-po' must be executed before. -dist2: $(DISTFILES) - dists="$(DISTFILES)"; \ - if test "$(PACKAGE)" = "gettext-tools"; then \ - dists="$$dists Makevars.template"; \ - fi; \ - if test -f $(srcdir)/ChangeLog; then \ - dists="$$dists ChangeLog"; \ - fi; \ - for i in 0 1 2 3 4 5 6 7 8 9; do \ - if test -f $(srcdir)/ChangeLog.$$i; then \ - dists="$$dists ChangeLog.$$i"; \ - fi; \ - done; \ - if test -f $(srcdir)/LINGUAS; then dists="$$dists LINGUAS"; fi; \ - for file in $$dists; do \ - if test -f $$file; then \ - cp -p $$file $(distdir); \ - else \ - cp -p $(srcdir)/$$file $(distdir); \ - fi; \ - done - -update-po: Makefile - $(MAKE) $(DOMAIN).pot-update - test -z "$(UPDATEPOFILES)" || $(MAKE) $(UPDATEPOFILES) - $(MAKE) update-gmo - -# General rule for creating PO files. - -.nop.po-create: - @lang=`echo $@ | sed -e 's/\.po-create$$//'`; \ - echo "File $$lang.po does not exist. If you are a translator, you can create it through 'msginit'." 1>&2; \ - exit 1 - -# General rule for updating PO files. - -.nop.po-update: - @lang=`echo $@ | sed -e 's/\.po-update$$//'`; \ - if test "$(PACKAGE)" = "gettext-tools"; then PATH=`pwd`/../src:$$PATH; fi; \ - tmpdir=`pwd`; \ - echo "$$lang:"; \ - test "$(srcdir)" = . && cdcmd="" || cdcmd="cd $(srcdir) && "; \ - echo "$${cdcmd}$(MSGMERGE) $$lang.po $(DOMAIN).pot -o $$lang.new.po"; \ - cd $(srcdir); \ - if $(MSGMERGE) $$lang.po $(DOMAIN).pot -o $$tmpdir/$$lang.new.po; then \ - if cmp $$lang.po $$tmpdir/$$lang.new.po >/dev/null 2>&1; then \ - rm -f $$tmpdir/$$lang.new.po; \ - else \ - if mv -f $$tmpdir/$$lang.new.po $$lang.po; then \ - :; \ - else \ - echo "msgmerge for $$lang.po failed: cannot move $$tmpdir/$$lang.new.po to $$lang.po" 1>&2; \ - exit 1; \ - fi; \ - fi; \ - else \ - echo "msgmerge for $$lang.po failed!" 1>&2; \ - rm -f $$tmpdir/$$lang.new.po; \ - fi - -$(DUMMYPOFILES): - -update-gmo: Makefile $(GMOFILES) - @: - -Makefile: Makefile.in.in $(top_builddir)/config.status @POMAKEFILEDEPS@ - cd $(top_builddir) \ - && CONFIG_FILES=$(subdir)/$@.in CONFIG_HEADERS= \ - $(SHELL) ./config.status - -force: - -# Tell versions [3.59,3.63) of GNU make not to export all variables. -# Otherwise a system limit (for SysV at least) may be exceeded. -.NOEXPORT: diff --git a/webcit/static/controls.js b/webcit/static/controls.js new file mode 100644 index 000000000..d8c9cf5f9 --- /dev/null +++ b/webcit/static/controls.js @@ -0,0 +1,699 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// (c) 2005 Jon Tirsen (http://www.tirsen.com) +// Contributors: +// Richard Livsey +// Rahul Bhargava +// +// 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. + +// Autocompleter.Base handles all the autocompletion functionality +// that's independent of the data source for autocompletion. This +// includes drawing the autocompletion menu, observing keyboard +// and mouse events, and similar. +// +// Specific autocompleters need to provide, at the very least, +// a getUpdatedChoices function that will be invoked every time +// the text inside the monitored textbox changes. This method +// should get the text for which to provide autocompletion by +// invoking this.getToken(), NOT by directly accessing +// this.element.value. This is to allow incremental tokenized +// autocompletion. Specific auto-completion logic (AJAX, etc) +// belongs in getUpdatedChoices. +// +// Tokenized incremental autocompletion is enabled automatically +// when an autocompleter is instantiated with the 'tokens' option +// in the options parameter, e.g.: +// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); +// will incrementally autocomplete with a comma as the token. +// Additionally, ',' in the above example can be replaced with +// a token array, e.g. { tokens: [',', '\n'] } which +// enables autocompletion on multiple tokens. This is most +// useful when one of the tokens is \n (a newline), as it +// allows smart autocompletion after linebreaks. + +var Autocompleter = {} +Autocompleter.Base = function() {}; +Autocompleter.Base.prototype = { + baseInitialize: function(element, update, options) { + this.element = $(element); + this.update = $(update); + this.hasFocus = false; + this.changed = false; + this.active = false; + this.index = 0; + this.entryCount = 0; + + if (this.setOptions) + this.setOptions(options); + else + this.options = options || {}; + + this.options.paramName = this.options.paramName || this.element.name; + this.options.tokens = this.options.tokens || []; + this.options.frequency = this.options.frequency || 0.4; + this.options.minChars = this.options.minChars || 1; + this.options.onShow = this.options.onShow || + function(element, update){ + if(!update.style.position || update.style.position=='absolute') { + update.style.position = 'absolute'; + Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight}); + } + new ScriptaculousEffect.Appear(update,{duration:0.15}); + }; + this.options.onHide = this.options.onHide || + function(element, update){ new ScriptaculousEffect.Fade(update,{duration:0.15}) }; + + if (typeof(this.options.tokens) == 'string') + this.options.tokens = new Array(this.options.tokens); + + this.observer = null; + + this.element.setAttribute('autocomplete','off'); + + Element.hide(this.update); + + Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this)); + Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this)); + }, + + show: function() { + if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); + if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0) && (Element.getStyle(this.update, 'position')=='absolute')) { + new Insertion.After(this.update, + ''); + this.iefix = $(this.update.id+'_iefix'); + } + if(this.iefix) { + Position.clone(this.update, this.iefix); + this.iefix.style.zIndex = 1; + this.update.style.zIndex = 2; + Element.show(this.iefix); + } + }, + + hide: function() { + if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); + if(this.iefix) Element.hide(this.iefix); + }, + + startIndicator: function() { + if(this.options.indicator) Element.show(this.options.indicator); + }, + + stopIndicator: function() { + if(this.options.indicator) Element.hide(this.options.indicator); + }, + + onKeyPress: function(event) { + if(this.active) + switch(event.keyCode) { + case Event.KEY_TAB: + case Event.KEY_RETURN: + this.selectEntry(); + Event.stop(event); + case Event.KEY_ESC: + this.hide(); + this.active = false; + Event.stop(event); + return; + case Event.KEY_LEFT: + case Event.KEY_RIGHT: + return; + case Event.KEY_UP: + this.markPrevious(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + case Event.KEY_DOWN: + this.markNext(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + } + else + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN) + return; + + this.changed = true; + this.hasFocus = true; + + if(this.observer) clearTimeout(this.observer); + this.observer = + setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); + }, + + 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.hide(); + }, + + markPrevious: function() { + if(this.index > 0) this.index-- + else this.index = this.entryCcount-1; + }, + + markNext: function() { + if(this.index < this.entryCount-1) this.index++ + else this.index = 0; + }, + + getEntry: function(index) { + return this.update.firstChild.childNodes[index]; + }, + + getCurrentEntry: function() { + return this.getEntry(this.index); + }, + + selectEntry: function() { + this.active = false; + this.updateElement(this.getCurrentEntry()); + }, + + updateElement: function(selectedElement) { + if (this.options.updateElement) { + this.options.updateElement(selectedElement); + return; + } + + var value = 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(); + }, + + updateChoices: function(choices) { + if(!this.changed && this.hasFocus) { + this.update.innerHTML = choices; + Element.cleanWhitespace(this.update); + Element.cleanWhitespace(this.update.firstChild); + + if(this.update.firstChild && this.update.firstChild.childNodes) { + this.entryCount = + this.update.firstChild.childNodes.length; + for (var i = 0; i < this.entryCount; i++) { + var entry = this.getEntry(i); + entry.autocompleteIndex = i; + this.addObservers(entry); + } + } else { + this.entryCount = 0; + } + + this.stopIndicator(); + + this.index = 0; + this.render(); + } + }, + + addObservers: function(element) { + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); + Event.observe(element, "click", this.onClick.bindAsEventListener(this)); + }, + + onObserverEvent: function() { + this.changed = false; + if(this.getToken().length>=this.options.minChars) { + this.startIndicator(); + this.getUpdatedChoices(); + } else { + this.active = false; + this.hide(); + } + }, + + getToken: function() { + var tokenPos = this.findLastToken(); + if (tokenPos != -1) + var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,''); + else + var ret = this.element.value; + + return /\n/.test(ret) ? '' : ret; + }, + + findLastToken: function() { + var lastTokenPos = -1; + + for (var i=0; i lastTokenPos) + lastTokenPos = thisTokenPos; + } + return lastTokenPos; + } +} + +Ajax.Autocompleter = Class.create(); +Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), { + initialize: function(element, update, url, options) { + this.baseInitialize(element, update, options); + this.options.asynchronous = true; + this.options.onComplete = this.onComplete.bind(this); + this.options.defaultParams = this.options.parameters || null; + this.url = url; + }, + + getUpdatedChoices: function() { + entry = encodeURIComponent(this.options.paramName) + '=' + + encodeURIComponent(this.getToken()); + + this.options.parameters = this.options.callback ? + this.options.callback(this.element, entry) : entry; + + if(this.options.defaultParams) + this.options.parameters += '&' + this.options.defaultParams; + + new Ajax.Request(this.url, this.options); + }, + + onComplete: function(request) { + this.updateChoices(request.responseText); + } + +}); + +// The local array autocompleter. Used when you'd prefer to +// inject an array of autocompletion options into the page, rather +// than sending out Ajax queries, which can be quite slow sometimes. +// +// The constructor takes four parameters. The first two are, as usual, +// the id of the monitored textbox, and id of the autocompletion menu. +// The third is the array you want to autocomplete from, and the fourth +// is the options block. +// +// Extra local autocompletion options: +// - choices - How many autocompletion choices to offer +// +// - partialSearch - If false, the autocompleter will match entered +// text only at the beginning of strings in the +// autocomplete array. Defaults to true, which will +// match text at the beginning of any *word* in the +// strings in the autocomplete array. If you want to +// search anywhere in the string, additionally set +// the option fullSearch to true (default: off). +// +// - fullSsearch - Search anywhere in autocomplete array strings. +// +// - partialChars - How many characters to enter before triggering +// a partial match (unlike minChars, which defines +// how many characters are required to do any match +// at all). Defaults to 2. +// +// - ignoreCase - Whether to ignore case when autocompleting. +// Defaults to true. +// +// It's possible to pass in a custom function as the 'selector' +// option, if you prefer to write your own autocompletion logic. +// In that case, the other options above will not apply unless +// you support them. + +Autocompleter.Local = Class.create(); +Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { + initialize: function(element, update, array, options) { + this.baseInitialize(element, update, options); + this.options.array = array; + }, + + getUpdatedChoices: function() { + this.updateChoices(this.options.selector(this)); + }, + + setOptions: function(options) { + this.options = Object.extend({ + choices: 10, + partialSearch: true, + partialChars: 2, + ignoreCase: true, + fullSearch: false, + selector: function(instance) { + var ret = []; // Beginning matches + var partial = []; // Inside matches + var entry = instance.getToken(); + var count = 0; + + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { + + var elem = instance.options.array[i]; + var foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase()) : + elem.indexOf(entry); + + while (foundPos != -1) { + if (foundPos == 0 && elem.length != entry.length) { + ret.push("
  • " + elem.substr(0, entry.length) + "" + + elem.substr(entry.length) + "
  • "); + break; + } else if (entry.length >= instance.options.partialChars && + instance.options.partialSearch && foundPos != -1) { + if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { + partial.push("
  • " + elem.substr(0, foundPos) + "" + + elem.substr(foundPos, entry.length) + "" + elem.substr( + foundPos + entry.length) + "
  • "); + break; + } + } + + foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : + elem.indexOf(entry, foundPos + 1); + + } + } + if (partial.length) + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) + return "
      " + ret.join('') + "
    "; + } + }, options || {}); + } +}); + +// AJAX in-place editor +// +// The constructor takes three parameters. The first is the element +// that should support in-place editing. The second is the url to submit +// the changed value to. The server should respond with the updated +// value (the server might have post-processed it or validation might +// have prevented it from changing). The third is a hash of options. +// +// Supported options are (all are optional and have sensible defaults): +// - okText - The text of the submit button that submits the changed value +// to the server (default: "ok") +// - cancelText - The text of the link that cancels editing (default: "cancel") +// - savingText - The text being displayed as the AJAX engine communicates +// with the server (default: "Saving...") +// - formId - The id given to the
    element +// (default: the id of the element to edit plus '-inplaceeditor') + +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({ + okText: "ok", + cancelText: "cancel", + savingText: "Saving...", + clickToEditText: "Click to edit", + okText: "ok", + rows: 1, + onComplete: function(transport, element) { + new ScriptaculousEffect.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); + }, + loadingText: 'Loading...', + savingClassName: 'inplaceeditor-saving', + formClassName: 'inplaceeditor-form', + highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, + highlightendcolor: "#FFFFFF", + externalControl: null, + ajaxOptions: {} + }, 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() { + 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.form = this.getForm(); + this.element.parentNode.insertBefore(this.form, this.element); + }, + getForm: function() { + form = document.createElement("form"); + form.id = this.options.formId; + Element.addClassName(form, this.options.formClassName) + form.onsubmit = this.onSubmit.bind(this); + + this.createEditField(form); + + if (this.options.textarea) { + var br = document.createElement("br"); + form.appendChild(br); + } + + okButton = document.createElement("input"); + okButton.type = "submit"; + okButton.value = this.options.okText; + form.appendChild(okButton); + + cancelLink = document.createElement("a"); + cancelLink.href = "#"; + cancelLink.appendChild(document.createTextNode(this.options.cancelText)); + cancelLink.onclick = this.onclickCancel.bind(this); + form.appendChild(cancelLink); + return form; + }, + createEditField: function(form) { + if (this.options.rows == 1) { + this.options.textarea = false; + var textField = document.createElement("input"); + textField.type = "text"; + textField.name = "value"; + textField.value = this.getText(); + textField.style.backgroundColor = this.options.highlightcolor; + var size = this.options.size || this.options.cols || 0; + if (size != 0) + textField.size = size; + form.appendChild(textField); + this.editField = textField; + } else { + this.options.textarea = true; + var textArea = document.createElement("textarea"); + textArea.name = "value"; + textArea.value = this.getText(); + textArea.rows = this.options.rows; + textArea.cols = this.options.cols || 40; + form.appendChild(textArea); + this.editField = textArea; + } + }, + getText: function() { + if (this.options.loadTextURL) { + this.loadExternalText(); + return this.options.loadingText; + } else { + return this.element.innerHTML; + } + }, + loadExternalText: function() { + new Ajax.Request( + this.options.loadTextURL, + { + asynchronous: true, + onComplete: this.onLoadedExternalText.bind(this) + } + ); + }, + onLoadedExternalText: function(transport) { + this.form.value.value = transport.responseText.stripTags(); + }, + onclickCancel: function() { + this.onComplete(); + this.leaveEditMode(); + return false; + }, + onFailure: function(transport) { + this.options.onFailure(transport); + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + this.oldInnerHTML = null; + } + return false; + }, + onSubmit: function() { + this.saving = true; + 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(this.form, this.editField.value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this) + }, this.options.ajaxOptions) + ); + this.onLoading(); + 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) { + 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 ScriptaculousEffect.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); + } + } +}; \ No newline at end of file diff --git a/webcit/static/dragdrop.js b/webcit/static/dragdrop.js new file mode 100644 index 000000000..9113f3daa --- /dev/null +++ b/webcit/static/dragdrop.js @@ -0,0 +1,545 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// +// Element.Class part Copyright (c) 2005 by Rick Olson +// +// 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 Droppables = { + drops: false, + + remove: function(element) { + for(var i = 0; i < this.drops.length; i++) + if(this.drops[i].element == element) + this.drops.splice(i,1); + }, + + add: function(element) { + element = $(element); + var options = Object.extend({ + greedy: true, + hoverclass: null + }, arguments[1] || {}); + + // cache containers + if(options.containment) { + options._containers = new Array(); + var containment = options.containment; + if((typeof containment == 'object') && + (containment.constructor == Array)) { + for(var i=0; i0) window.scrollBy(0,0); + + Event.stop(event); + } + } +} + +/*--------------------------------------------------------------------------*/ + +var SortableObserver = Class.create(); +SortableObserver.prototype = { + initialize: function(element, observer) { + this.element = $(element); + this.observer = observer; + this.lastValue = Sortable.serialize(this.element); + }, + onStart: function() { + this.lastValue = Sortable.serialize(this.element); + }, + onEnd: function() { + Sortable.unmark(); + if(this.lastValue != Sortable.serialize(this.element)) + this.observer(this.element) + } +} + +var Sortable = { + sortables: new Array(), + options: function(element){ + element = $(element); + for(var i=0;i0 ? elements.flatten() : null); + }, + + onHover: function(element, dropon, overlap) { + 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) { + if(element.parentNode!=dropon) { + dropon.appendChild(element); + } + }, + + unmark: function() { + if(Sortable._marker) Element.hide(Sortable._marker); + }, + + mark: function(dropon, position) { + // mark on ghosting only + var sortable = Sortable.options(dropon.parentNode); + if(sortable && !sortable.ghosting) return; + + if(!Sortable._marker) { + Sortable._marker = $('dropmarker') || document.createElement('DIV'); + Element.hide(Sortable._marker); + Element.Class.add(Sortable._marker, 'dropmarker'); + Sortable._marker.style.position = 'absolute'; + document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); + } + var offsets = Position.cumulativeOffset(dropon); + Sortable._marker.style.top = offsets[1] + 'px'; + if(position=='after') Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px'; + Sortable._marker.style.left = offsets[0] + 'px'; + Element.show(Sortable._marker); + }, + + serialize: function(element) { + element = $(element); + var sortableOptions = this.options(element); + var options = Object.extend({ + tag: sortableOptions.tag, + only: sortableOptions.only, + name: element.id + }, arguments[1] || {}); + + var items = $(element).childNodes; + var queryComponents = new Array(); + + for(var i=0; itimestamp)) + timestamp = this.effects[i].finishOn; + return timestamp; + }, + add: function(effect) { + var timestamp = new Date().getTime(); + + switch(effect.options.queue) { + case 'front': + // move unstarted effects after this effect + for(var i = 0; i < this.effects.length; i++) + if(this.effects[i].state == 'idle') { + this.effects[i].startOn += effect.finishOn; + this.effects[i].finishOn += effect.finishOn; + } + break; + case 'end': + // start effect after last queued effect has finished + timestamp = this.findLast() || timestamp; + break; + } + + effect.startOn += timestamp; + effect.finishOn += timestamp; + + this.effects.push(effect); + + if(!this.interval) + this.interval = setInterval(this.loop.bind(this), 40); + }, + remove: function(effect) { + for(var i = 0; i < this.effects.length; i++) + if(this.effects[i]==effect) this.effects.splice(i,1); + if(this.effects.length == 0) { + clearInterval(this.interval); + this.interval = null; + } + }, + loop: function() { + var timePos = new Date().getTime(); + for(var i = 0; i < this.effects.length; i++) { + this.effects[i].loop(timePos); + } + } +} + +ScriptaculousEffect.Base = function() {}; +ScriptaculousEffect.Base.prototype = { + setOptions: function(options) { + this.options = Object.extend({ + transition: ScriptaculousEffect.Transitions.sinoidal, + duration: 1.0, // seconds + fps: 25.0, // max. 25fps due to ScriptaculousEffect.Queue implementation + sync: false, // true for combining + from: 0.0, + to: 1.0, + delay: 0.0, + queue: 'parallel' + }, options || {}); + }, + start: function(options) { + this.setOptions(options || {}); + this.currentFrame = 0; + this.state = 'idle'; + this.startOn = this.options.delay*1000; + this.finishOn = this.startOn + (this.options.duration*1000); + if(this.options.beforeStart) this.options.beforeStart(this); + if(!this.options.sync) ScriptaculousEffect.Queue.add(this); + }, + loop: function(timePos) { + if(timePos >= this.startOn) { + if(timePos >= this.finishOn) { + this.render(1.0); + this.cancel(); + if(this.finish) this.finish(); + if(this.options.afterFinish) this.options.afterFinish(this); + return; + } + var pos = (timePos - this.startOn) / (this.finishOn - this.startOn); + var frame = Math.round(pos * this.options.fps * this.options.duration); + if(frame > this.currentFrame) { + this.render(pos); + this.currentFrame = frame; + } + } + }, + render: function(pos) { + if(this.state == 'idle') { + this.state = 'running'; + if(this.setup) this.setup(); + } + if(this.options.transition) pos = this.options.transition(pos); + pos *= (this.options.to-this.options.from); + pos += this.options.from; + if(this.options.beforeUpdate) this.options.beforeUpdate(this); + if(this.update) this.update(pos); + if(this.options.afterUpdate) this.options.afterUpdate(this); + }, + cancel: function() { + if(!this.options.sync) ScriptaculousEffect.Queue.remove(this); + this.state = 'finished'; + } +} + +ScriptaculousEffect.Parallel = Class.create(); +Object.extend(Object.extend(ScriptaculousEffect.Parallel.prototype, ScriptaculousEffect.Base.prototype), { + initialize: function(effects) { + this.effects = effects || []; + this.start(arguments[1]); + }, + update: function(position) { + for (var i = 0; i < this.effects.length; i++) + this.effects[i].render(position); + }, + finish: function(position) { + for (var i = 0; i < this.effects.length; i++) { + this.effects[i].cancel(); + if(this.effects[i].finish) this.effects[i].finish(position); + } + } +}); + +// Internet Explorer caveat: works only on elements that have +// a 'layout', meaning having a given width or height. +// There is no way to safely set this automatically. +ScriptaculousEffect.Opacity = Class.create(); +Object.extend(Object.extend(ScriptaculousEffect.Opacity.prototype, ScriptaculousEffect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + var options = Object.extend({ + from: 0.0, + to: 1.0 + }, arguments[1] || {}); + this.start(options); + }, + update: function(position) { + this.setOpacity(position); + }, + setOpacity: function(opacity) { + if(opacity<0.0001) opacity = 0; // fix errors with things like 6.152242992829571e-8 + if(opacity==1.0) { + this.element.style.opacity = '0.999999'; + this.element.style.filter = null; + } else { + this.element.style.opacity = opacity; + this.element.style.filter = "alpha(opacity:"+opacity*100+")"; + } + } +}); + +ScriptaculousEffect.MoveBy = Class.create(); +Object.extend(Object.extend(ScriptaculousEffect.MoveBy.prototype, ScriptaculousEffect.Base.prototype), { + initialize: function(element, toTop, toLeft) { + this.element = $(element); + this.toTop = toTop; + this.toLeft = toLeft; + this.start(arguments[3]); + }, + setup: function() { + this.originalTop = parseFloat(Element.getStyle(this.element,'top') || '0'); + this.originalLeft = parseFloat(Element.getStyle(this.element,'left') || '0'); + Element.makePositioned(this.element); + }, + update: function(position) { + topd = this.toTop * position + this.originalTop; + leftd = this.toLeft * position + this.originalLeft; + this.setPosition(topd, leftd); + }, + setPosition: function(topd, leftd) { + this.element.style.top = topd + "px"; + this.element.style.left = leftd + "px"; + } +}); + +ScriptaculousEffect.Scale = Class.create(); +Object.extend(Object.extend(ScriptaculousEffect.Scale.prototype, ScriptaculousEffect.Base.prototype), { + initialize: function(element, percent) { + this.element = $(element) + var options = Object.extend({ + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or {} with provided values + scaleFrom: 100.0, + scaleTo: percent + }, arguments[2] || {}); + this.start(options); + }, + setup: function() { + this.originalTop = this.element.offsetTop; + this.originalLeft = this.element.offsetLeft; + if(Element.getStyle(this.element,'font-size')=="") this.sizeEm = 1.0; + if(Element.getStyle(this.element,'font-size') && Element.getStyle(this.element,'font-size').indexOf("em")>0) + this.sizeEm = parseFloat(Element.getStyle(this.element,'font-size')); + this.factor = (this.options.scaleTo/100.0) - (this.options.scaleFrom/100.0); + if(this.options.scaleMode=='box') { + this.originalHeight = this.element.clientHeight; + this.originalWidth = this.element.clientWidth; + } else + if(this.options.scaleMode=='contents') { + this.originalHeight = this.element.scrollHeight; + this.originalWidth = this.element.scrollWidth; + } else { + this.originalHeight = this.options.scaleMode.originalHeight; + this.originalWidth = this.options.scaleMode.originalWidth; + } + }, + update: function(position) { + var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); + if(this.options.scaleContent && this.sizeEm) + this.element.style.fontSize = this.sizeEm*currentScale + "em"; + this.setDimensions( + this.originalWidth * currentScale, + this.originalHeight * currentScale); + }, + setDimensions: function(width, height) { + if(this.options.scaleX) this.element.style.width = width + 'px'; + if(this.options.scaleY) this.element.style.height = height + 'px'; + if(this.options.scaleFromCenter) { + var topd = (height - this.originalHeight)/2; + var leftd = (width - this.originalWidth)/2; + if(Element.getStyle(this.element,'position')=='absolute') { + if(this.options.scaleY) this.element.style.top = this.originalTop-topd + "px"; + if(this.options.scaleX) this.element.style.left = this.originalLeft-leftd + "px"; + } else { + if(this.options.scaleY) this.element.style.top = -topd + "px"; + if(this.options.scaleX) this.element.style.left = -leftd + "px"; + } + } + } +}); + +ScriptaculousEffect.Highlight = Class.create(); +Object.extend(Object.extend(ScriptaculousEffect.Highlight.prototype, ScriptaculousEffect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + var options = Object.extend({ + startcolor: "#ffff99" + }, arguments[1] || {}); + this.start(options); + }, + setup: function() { + // try to parse current background color as default for endcolor + // browser stores this as: "rgb(255, 255, 255)", convert to "#ffffff" format + if(!this.options.endcolor) { + var endcolor = "#ffffff"; + var current = Element.getStyle(this.element, 'background-color'); + if(current && current.slice(0,4) == "rgb(") { + endcolor = "#"; + var cols = current.slice(4,current.length-1).split(','); + var i=0; do { endcolor += parseInt(cols[i]).toColorPart() } while (++i<3); + } + this.options.endcolor = endcolor; + } + // init color calculations + this.colors_base = [ + parseInt(this.options.startcolor.slice(1,3),16), + parseInt(this.options.startcolor.slice(3,5),16), + parseInt(this.options.startcolor.slice(5),16) ]; + this.colors_delta = [ + parseInt(this.options.endcolor.slice(1,3),16)-this.colors_base[0], + parseInt(this.options.endcolor.slice(3,5),16)-this.colors_base[1], + parseInt(this.options.endcolor.slice(5),16)-this.colors_base[2]]; + }, + update: function(position) { + var colors = [ + Math.round(this.colors_base[0]+(this.colors_delta[0]*position)), + Math.round(this.colors_base[1]+(this.colors_delta[1]*position)), + Math.round(this.colors_base[2]+(this.colors_delta[2]*position)) ]; + this.element.style.backgroundColor = "#" + + colors[0].toColorPart() + colors[1].toColorPart() + colors[2].toColorPart(); + }, + finish: function() { + this.element.style.backgroundColor = this.options.restorecolor; + } +}); + +ScriptaculousEffect.ScrollTo = Class.create(); +Object.extend(Object.extend(ScriptaculousEffect.ScrollTo.prototype, ScriptaculousEffect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + this.start(arguments[1] || {}); + }, + setup: function() { + Position.prepare(); + var offsets = Position.cumulativeOffset(this.element); + var max = window.innerHeight ? + window.height - window.innerHeight : + document.body.scrollHeight - + (document.documentElement.clientHeight ? + document.documentElement.clientHeight : document.body.clientHeight); + this.scrollStart = Position.deltaY; + this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart; + }, + update: function(position) { + Position.prepare(); + window.scrollTo(Position.deltaX, + this.scrollStart + (position*this.delta)); + } +}); + +/* ------------- combination effects ------------- */ + +ScriptaculousEffect.Fade = function(element) { + var options = Object.extend({ + from: 1.0, + to: 0.0, + afterFinish: function(effect) + { Element.hide(effect.element); + effect.setOpacity(1); } + }, arguments[1] || {}); + return new ScriptaculousEffect.Opacity(element,options); +} + +ScriptaculousEffect.Appear = function(element) { + var options = Object.extend({ + from: 0.0, + to: 1.0, + beforeStart: function(effect) + { effect.setOpacity(0); + Element.show(effect.element); }, + afterUpdate: function(effect) + { Element.show(effect.element); } + }, arguments[1] || {}); + return new ScriptaculousEffect.Opacity(element,options); +} + +ScriptaculousEffect.Puff = function(element) { + return new ScriptaculousEffect.Parallel( + [ new ScriptaculousEffect.Scale(element, 200, { sync: true, scaleFromCenter: true }), + new ScriptaculousEffect.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ], + Object.extend({ duration: 1.0, + beforeUpdate: function(effect) + { effect.effects[0].element.style.position = 'absolute'; }, + afterFinish: function(effect) + { Element.hide(effect.effects[0].element); } + }, arguments[1] || {}) + ); +} + +ScriptaculousEffect.BlindUp = function(element) { + element = $(element); + Element.makeClipping(element); + return new ScriptaculousEffect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + afterFinish: function(effect) + { + Element.hide(effect.element); + Element.undoClipping(effect.element); + } + }, arguments[1] || {}) + ); +} + +ScriptaculousEffect.BlindDown = function(element) { + element = $(element); + element.style.height = '0px'; + Element.makeClipping(element); + Element.show(element); + return new ScriptaculousEffect.Scale(element, 100, + Object.extend({ scaleContent: false, + scaleX: false, + scaleMode: 'contents', + scaleFrom: 0, + afterFinish: function(effect) { + Element.undoClipping(effect.element); + } + }, arguments[1] || {}) + ); +} + +ScriptaculousEffect.SwitchOff = function(element) { + return new ScriptaculousEffect.Appear(element, + { duration: 0.4, + transition: ScriptaculousEffect.Transitions.flicker, + afterFinish: function(effect) + { effect.element.style.overflow = 'hidden'; + new ScriptaculousEffect.Scale(effect.element, 1, + { duration: 0.3, scaleFromCenter: true, + scaleX: false, scaleContent: false, + afterUpdate: function(effect) { + if(effect.element.style.position=="") + effect.element.style.position = 'relative'; }, + afterFinish: function(effect) { Element.hide(effect.element); } + } ) + } + } ); +} + +ScriptaculousEffect.DropOut = function(element) { + return new ScriptaculousEffect.Parallel( + [ new ScriptaculousEffect.MoveBy(element, 100, 0, { sync: true }), + new ScriptaculousEffect.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ], + Object.extend( + { duration: 0.5, + afterFinish: function(effect) + { Element.hide(effect.effects[0].element); } + }, arguments[1] || {})); +} + +ScriptaculousEffect.Shake = function(element) { + return new ScriptaculousEffect.MoveBy(element, 0, 20, + { duration: 0.05, afterFinish: function(effect) { + new ScriptaculousEffect.MoveBy(effect.element, 0, -40, + { duration: 0.1, afterFinish: function(effect) { + new ScriptaculousEffect.MoveBy(effect.element, 0, 40, + { duration: 0.1, afterFinish: function(effect) { + new ScriptaculousEffect.MoveBy(effect.element, 0, -40, + { duration: 0.1, afterFinish: function(effect) { + new ScriptaculousEffect.MoveBy(effect.element, 0, 40, + { duration: 0.1, afterFinish: function(effect) { + new ScriptaculousEffect.MoveBy(effect.element, 0, -20, + { duration: 0.05, afterFinish: function(effect) { + }}) }}) }}) }}) }}) }}); +} + +ScriptaculousEffect.SlideDown = function(element) { + element = $(element); + element.style.height = '0px'; + Element.makeClipping(element); + Element.cleanWhitespace(element); + Element.makePositioned(element.firstChild); + Element.show(element); + return new ScriptaculousEffect.Scale(element, 100, + Object.extend({ scaleContent: false, + scaleX: false, + scaleMode: 'contents', + scaleFrom: 0, + afterUpdate: function(effect) + { effect.element.firstChild.style.bottom = + (effect.originalHeight - effect.element.clientHeight) + 'px'; }, + afterFinish: function(effect) + { Element.undoClipping(effect.element); } + }, arguments[1] || {}) + ); +} + +ScriptaculousEffect.SlideUp = function(element) { + element = $(element); + Element.makeClipping(element); + Element.cleanWhitespace(element); + Element.makePositioned(element.firstChild); + Element.show(element); + return new ScriptaculousEffect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + afterUpdate: function(effect) + { effect.element.firstChild.style.bottom = + (effect.originalHeight - effect.element.clientHeight) + 'px'; }, + afterFinish: function(effect) + { + Element.hide(effect.element); + Element.undoClipping(effect.element); + } + }, arguments[1] || {}) + ); +} + +ScriptaculousEffect.Squish = function(element) { + return new ScriptaculousEffect.Scale(element, 0, + { afterFinish: function(effect) { Element.hide(effect.element); } }); +} + +ScriptaculousEffect.Grow = function(element) { + element = $(element); + var options = arguments[1] || {}; + + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + element.style.overflow = 'hidden'; + Element.show(element); + + var direction = options.direction || 'center'; + var moveTransition = options.moveTransition || ScriptaculousEffect.Transitions.sinoidal; + var scaleTransition = options.scaleTransition || ScriptaculousEffect.Transitions.sinoidal; + var opacityTransition = options.opacityTransition || ScriptaculousEffect.Transitions.full; + + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = originalWidth; + initialMoveY = moveY = 0; + moveX = -originalWidth; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = originalHeight; + moveY = -originalHeight; + break; + case 'bottom-right': + initialMoveX = originalWidth; + initialMoveY = originalHeight; + moveX = -originalWidth; + moveY = -originalHeight; + break; + case 'center': + initialMoveX = originalWidth / 2; + initialMoveY = originalHeight / 2; + moveX = -originalWidth / 2; + moveY = -originalHeight / 2; + break; + } + + return new ScriptaculousEffect.MoveBy(element, initialMoveY, initialMoveX, { + duration: 0.01, + beforeUpdate: function(effect) { $(element).style.height = '0px'; }, + afterFinish: function(effect) { + new ScriptaculousEffect.Parallel( + [ new ScriptaculousEffect.Opacity(element, { sync: true, to: 1.0, from: 0.0, transition: opacityTransition }), + new ScriptaculousEffect.MoveBy(element, moveY, moveX, { sync: true, transition: moveTransition }), + new ScriptaculousEffect.Scale(element, 100, { + scaleMode: { originalHeight: originalHeight, originalWidth: originalWidth }, + sync: true, scaleFrom: 0, scaleTo: 100, transition: scaleTransition })], + options); } + }); +} + +ScriptaculousEffect.Shrink = function(element) { + element = $(element); + var options = arguments[1] || {}; + + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + element.style.overflow = 'hidden'; + Element.show(element); + + var direction = options.direction || 'center'; + var moveTransition = options.moveTransition || ScriptaculousEffect.Transitions.sinoidal; + var scaleTransition = options.scaleTransition || ScriptaculousEffect.Transitions.sinoidal; + var opacityTransition = options.opacityTransition || ScriptaculousEffect.Transitions.none; + + var moveX, moveY; + + switch (direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = originalWidth; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = originalHeight; + break; + case 'bottom-right': + moveX = originalWidth; + moveY = originalHeight; + break; + case 'center': + moveX = originalWidth / 2; + moveY = originalHeight / 2; + break; + } + + return new ScriptaculousEffect.Parallel( + [ new ScriptaculousEffect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: opacityTransition }), + new ScriptaculousEffect.Scale(element, 0, { sync: true, transition: moveTransition }), + new ScriptaculousEffect.MoveBy(element, moveY, moveX, { sync: true, transition: scaleTransition }) ], + options); +} + +ScriptaculousEffect.Pulsate = function(element) { + element = $(element); + var options = arguments[1] || {}; + var transition = options.transition || ScriptaculousEffect.Transitions.sinoidal; + var reverser = function(pos){ return transition(1-ScriptaculousEffect.Transitions.pulse(pos)) }; + reverser.bind(transition); + return new ScriptaculousEffect.Opacity(element, + Object.extend(Object.extend({ duration: 3.0, + afterFinish: function(effect) { Element.show(effect.element); } + }, options), {transition: reverser})); +} + +ScriptaculousEffect.Fold = function(element) { + element = $(element); + element.style.overflow = 'hidden'; + return new ScriptaculousEffect.Scale(element, 5, Object.extend({ + scaleContent: false, + scaleTo: 100, + scaleX: false, + afterFinish: function(effect) { + new ScriptaculousEffect.Scale(element, 1, { + scaleContent: false, + scaleTo: 0, + scaleY: false, + afterFinish: function(effect) { Element.hide(effect.element) } }); + }}, arguments[1] || {})); +} + +// old: new ScriptaculousEffect.ContentZoom(element, percent) +// new: Element.setContentZoom(element, percent) + +Element.setContentZoom = function(element, percent) { + element = $(element); + element.style.fontSize = (percent/100) + "em"; + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); +} diff --git a/webcit/static/head.html b/webcit/static/head.html index 03aa1db3b..cfe9b9857 100644 --- a/webcit/static/head.html +++ b/webcit/static/head.html @@ -11,5 +11,8 @@ + + + diff --git a/webcit/static/prototype.js b/webcit/static/prototype.js index 006d60332..ed7d920cb 100644 --- a/webcit/static/prototype.js +++ b/webcit/static/prototype.js @@ -1,4 +1,4 @@ -/* Prototype: an object-oriented Javascript library, version 1.2.1 +/* Prototype JavaScript framework, version 1.4.0_pre4 * (c) 2005 Sam Stephenson * * THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff @@ -11,7 +11,10 @@ /*--------------------------------------------------------------------------*/ var Prototype = { - Version: '1.2.1' + Version: '1.4.0_pre4', + + emptyFunction: function() {}, + K: function(x) {return x} } var Class = { @@ -24,24 +27,24 @@ var Class = { var Abstract = new Object(); -Object.prototype.extend = function(object) { - for (property in object) { - this[property] = object[property]; +Object.extend = function(destination, source) { + for (property in source) { + destination[property] = source[property]; } - return this; + return destination; } Function.prototype.bind = function(object) { - var method = this; + var __method = this; return function() { - method.apply(object, arguments); + return __method.apply(object, arguments); } } Function.prototype.bindAsEventListener = function(object) { - var method = this; + var __method = this; return function(event) { - method.call(object, event || window.event); + return __method.call(object, event || window.event); } } @@ -54,7 +57,7 @@ Number.prototype.toColorPart = function() { var Try = { these: function() { var returnValue; - + for (var i = 0; i < arguments.length; i++) { var lambda = arguments[i]; try { @@ -62,7 +65,7 @@ var Try = { break; } catch (e) {} } - + return returnValue; } } @@ -75,14 +78,14 @@ PeriodicalExecuter.prototype = { this.callback = callback; this.frequency = frequency; this.currentlyExecuting = false; - + this.registerCallback(); }, - + registerCallback: function() { - setTimeout(this.onTimerEvent.bind(this), this.frequency * 1000); + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); }, - + onTimerEvent: function() { if (!this.currentlyExecuting) { try { @@ -92,8 +95,6 @@ PeriodicalExecuter.prototype = { this.currentlyExecuting = false; } } - - this.registerCallback(); } } @@ -101,7 +102,7 @@ PeriodicalExecuter.prototype = { function $() { var elements = new Array(); - + for (var i = 0; i < arguments.length; i++) { var element = arguments[i]; if (typeof element == 'string') @@ -109,15 +110,13 @@ function $() { if (arguments.length == 1) return element; - + elements.push(element); } - + return elements; } -/*--------------------------------------------------------------------------*/ - if (!Array.prototype.push) { Array.prototype.push = function() { var startLength = this.length; @@ -135,18 +134,255 @@ if (!Function.prototype.apply) { if (!parameters) parameters = new Array(); for (var i = 0; i < parameters.length; i++) - parameterStrings[i] = 'x[' + i + ']'; + parameterStrings[i] = 'parameters[' + i + ']'; object.__apply__ = this; - var result = eval('obj.__apply__(' + - parameterStrings[i].join(', ') + ')'); + var result = eval('object.__apply__(' + + parameterStrings.join(', ') + ')'); object.__apply__ = null; return result; } } -/*--------------------------------------------------------------------------*/ +Object.extend(String.prototype, { + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + escapeHTML: function() { + var div = document.createElement('div'); + var text = document.createTextNode(this); + div.appendChild(text); + return div.innerHTML; + }, + + unescapeHTML: function() { + var div = document.createElement('div'); + div.innerHTML = this.stripTags(); + return div.childNodes[0].nodeValue; + }, + + parseQuery: function() { + var str = this; + if (str.substring(0,1) == '?') { + str = this.substring(1); + } + var result = {}; + var pairs = str.split('&'); + for (var i = 0; i < pairs.length; i++) { + var pair = pairs[i].split('='); + result[pair[0]] = pair[1]; + } + return result; + } +}); + + +var _break = new Object(); +var _continue = new Object(); + +var Enumerable = { + each: function(iterator) { + var index = 0; + try { + this._each(function(value) { + try { + iterator(value, index++); + } catch (e) { + if (e != _continue) throw e; + } + }); + } catch (e) { + if (e != _break) throw e; + } + }, + + all: function(iterator) { + var result = true; + this.each(function(value, index) { + if (!(result &= (iterator || Prototype.K)(value, index))) + throw _break; + }); + return result; + }, + + any: function(iterator) { + var result = true; + this.each(function(value, index) { + if (result &= (iterator || Prototype.K)(value, index)) + throw _break; + }); + return result; + }, + + collect: function(iterator) { + var results = []; + this.each(function(value, index) { + results.push(iterator(value, index)); + }); + return results; + }, + + detect: function (iterator) { + var result; + this.each(function(value, index) { + if (iterator(value, index)) { + result = value; + throw _break; + } + }); + return result; + }, + + findAll: function(iterator) { + var results = []; + this.each(function(value, index) { + if (iterator(value, index)) + results.push(value); + }); + return results; + }, + + grep: function(pattern, iterator) { + var results = []; + this.each(function(value, index) { + var stringValue = value.toString(); + if (stringValue.match(pattern)) + results.push((iterator || Prototype.K)(value, index)); + }) + return results; + }, + + include: function(object) { + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw _break; + } + }); + return found; + }, + + inject: function(memo, iterator) { + this.each(function(value, index) { + memo = iterator(memo, value, index); + }); + return memo; + }, + + invoke: function(method) { + var args = $A(arguments).slice(1); + return this.collect(function(value) { + return value[method].apply(value, args); + }); + }, + + max: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (value >= (result || value)) + result = value; + }); + return result; + }, + + min: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (value <= (result || value)) + result = value; + }); + return result; + }, + + partition: function(iterator) { + var trues = [], falses = []; + this.each(function(value, index) { + ((iterator || Prototype.K)(value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + }, + + pluck: function(property) { + var results = []; + this.each(function(value, index) { + results.push(value[property]); + }); + return results; + }, + + reject: function(iterator) { + var results = []; + this.each(function(value, index) { + if (!iterator(value, index)) + results.push(value); + }); + return results; + }, + + sortBy: function(iterator) { + return this.collect(function(value, index) { + return {value: value, criteria: iterator(value, index)}; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + }, + + toArray: function() { + return this.collect(Prototype.K); + }, + + zip: function() { + var iterator = Prototype.K, args = $A(arguments); + if (typeof args.last() == 'function') + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + iterator(value = collections.pluck(index)); + return value; + }); + } +} + +Object.extend(Enumerable, { + map: Enumerable.collect, + find: Enumerable.detect, + select: Enumerable.findAll, + member: Enumerable.include, + entries: Enumerable.toArray +}); + +$A = Array.from = function(iterable) { + var results = []; + for (var i = 0; i < iterable.length; i++) + results.push(iterable[i]); + return results; +} + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0; i < this.length; i++) + iterator(this[i]); + }, + + first: function() { + return this[0]; + }, + + last: function() { + return this[this.length - 1]; + } +}); + +Object.extend(Array.prototype, Enumerable); + var Ajax = { getTransport: function() { @@ -155,9 +391,7 @@ var Ajax = { function() {return new ActiveXObject('Microsoft.XMLHTTP')}, function() {return new XMLHttpRequest()} ) || false; - }, - - emptyFunction: function() {} + } } Ajax.Base = function() {}; @@ -167,7 +401,18 @@ Ajax.Base.prototype = { method: 'post', asynchronous: true, parameters: '' - }.extend(options || {}); + } + Object.extend(this.options, options || {}); + }, + + responseIsSuccess: function() { + return this.transport.status == undefined + || this.transport.status == 0 + || (this.transport.status >= 200 && this.transport.status < 300); + }, + + responseIsFailure: function() { + return !this.responseIsSuccess(); } } @@ -175,97 +420,358 @@ Ajax.Request = Class.create(); Ajax.Request.Events = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; -Ajax.Request.prototype = (new Ajax.Base()).extend({ +Ajax.Request.prototype = Object.extend(new Ajax.Base(), { initialize: function(url, options) { this.transport = Ajax.getTransport(); this.setOptions(options); - + this.request(url); + }, + + request: function(url) { + var parameters = this.options.parameters || ''; + if (parameters.length > 0) parameters += '&_='; + try { if (this.options.method == 'get') - url += '?' + this.options.parameters + '&_='; - + url += '?' + parameters; + this.transport.open(this.options.method, url, this.options.asynchronous); - + if (this.options.asynchronous) { this.transport.onreadystatechange = this.onStateChange.bind(this); setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10); } - - this.transport.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); - this.transport.setRequestHeader('X-Prototype-Version', - Prototype.Version); - - if (this.options.method == 'post') { - this.transport.setRequestHeader('Connection', 'close'); - this.transport.setRequestHeader('Content-type', - 'application/x-www-form-urlencoded'); - } - if (this.options.requestHeaders) { - for (var i=0; i< (this.options.requestHeaders.length-1);i+=2) - this.transport.setRequestHeader(this.options.requestHeaders[i], - this.options.requestHeaders[i+1]); - } + this.setRequestHeaders(); - var sendData = this.options.postBody ? this.options.postBody - : this.options.parameters ? this.options.parameters + '&_' - : null; + var body = this.options.postBody ? this.options.postBody : parameters; + this.transport.send(this.options.method == 'post' ? body : null); - this.transport.send(this.options.method == 'post' ? sendData : null ); - } catch (e) { - } + } }, - + + setRequestHeaders: function() { + var requestHeaders = + ['X-Requested-With', 'XMLHttpRequest', + 'X-Prototype-Version', Prototype.Version]; + + if (this.options.method == 'post') { + requestHeaders.push('Content-type', + 'application/x-www-form-urlencoded'); + + /* Force "Connection: close" for Mozilla browsers to work around + * a bug where XMLHttpReqeuest sends an incorrect Content-length + * header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType) + requestHeaders.push('Connection', 'close'); + } + + if (this.options.requestHeaders) + requestHeaders.push.apply(requestHeaders, this.options.requestHeaders); + + for (var i = 0; i < requestHeaders.length; i += 2) + this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]); + }, + onStateChange: function() { var readyState = this.transport.readyState; if (readyState != 1) this.respondToReadyState(this.transport.readyState); }, - + respondToReadyState: function(readyState) { var event = Ajax.Request.Events[readyState]; - (this.options['on' + event] || Ajax.emptyFunction)(this.transport); + + if (event == 'Complete') + (this.options['on' + this.transport.status] + || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(this.transport); + + (this.options['on' + event] || Prototype.emptyFunction)(this.transport); + + /* Avoid memory leak in MSIE: clean up the oncomplete event handler */ + if (event == 'Complete') + this.transport.onreadystatechange = Prototype.emptyFunction; } }); Ajax.Updater = Class.create(); -Ajax.Updater.prototype = (new Ajax.Base()).extend({ +Ajax.Updater.ScriptFragment = '(?:)((\n|.)*?)(?:<\/script>)'; + +Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { initialize: function(container, url, options) { - this.container = $(container); - this.setOptions(options); - - if (this.options.asynchronous) { - this.onComplete = this.options.onComplete; - this.options.onComplete = this.updateContent.bind(this); + this.containers = { + success: container.success ? $(container.success) : $(container), + failure: container.failure ? $(container.failure) : + (container.success ? null : $(container)) } - this.request = new Ajax.Request(url, this.options); - - if (!this.options.asynchronous) + this.transport = Ajax.getTransport(); + this.setOptions(options); + + var onComplete = this.options.onComplete || Prototype.emptyFunction; + this.options.onComplete = (function() { this.updateContent(); + onComplete(this.transport); + }).bind(this); + + this.request(url); }, - + updateContent: function() { - if (this.request.transport.status == 200) { + var receiver = this.responseIsSuccess() ? + this.containers.success : this.containers.failure; + + var match = new RegExp(Ajax.Updater.ScriptFragment, 'img'); + var response = this.transport.responseText.replace(match, ''); + var scripts = this.transport.responseText.match(match); + + if (receiver) { if (this.options.insertion) { - new this.options.insertion(this.container, - this.request.transport.responseText); + new this.options.insertion(receiver, response); } else { - this.container.innerHTML = this.request.transport.responseText; + receiver.innerHTML = response; } - } + } + + if (this.responseIsSuccess()) { + if (this.onComplete) + setTimeout((function() {this.onComplete( + this.transport)}).bind(this), 10); + } + + if (this.options.evalScripts && scripts) { + match = new RegExp(Ajax.Updater.ScriptFragment, 'im'); + setTimeout((function() { + for (var i = 0; i < scripts.length; i++) + eval(scripts[i].match(match)[1]); + }).bind(this), 10); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(); +Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { + initialize: function(container, url, options) { + this.setOptions(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = 1; + + this.updater = {}; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Ajax.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(request) { + if (this.options.decay) { + this.decay = (request.responseText == this.lastText ? + this.decay * this.options.decay : 1); - if (this.onComplete) { - setTimeout((function() {this.onComplete( - this.request.transport)}).bind(this), 10); + this.lastText = request.responseText; } + this.timer = setTimeout(this.onTimerEvent.bind(this), + this.decay * this.frequency * 1000); + }, + + onTimerEvent: function() { + this.updater = new Ajax.Updater(this.container, this.url, this.options); } }); +document.getElementsByClassName = function(className) { + var children = document.getElementsByTagName('*') || document.all; + var elements = new Array(); + + for (var i = 0; i < children.length; i++) { + var child = children[i]; + var classNames = child.className.split(' '); + for (var j = 0; j < classNames.length; j++) { + if (classNames[j] == className) { + elements.push(child); + break; + } + } + } + + return elements; +} + /*--------------------------------------------------------------------------*/ +if (!window.Element) { + var Element = new Object(); +} + +Object.extend(Element, { + toggle: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = + (element.style.display == 'none' ? '' : 'none'); + } + }, + + hide: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = 'none'; + } + }, + + show: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = ''; + } + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + }, + + getHeight: function(element) { + element = $(element); + return element.offsetHeight; + }, + + hasClassName: function(element, className) { + element = $(element); + if (!element) + return; + var a = element.className.split(' '); + for (var i = 0; i < a.length; i++) { + if (a[i] == className) + return true; + } + return false; + }, + + addClassName: function(element, className) { + element = $(element); + Element.removeClassName(element, className); + element.className += ' ' + className; + }, + + removeClassName: function(element, className) { + element = $(element); + if (!element) + return; + var newClassName = ''; + var a = element.className.split(' '); + for (var i = 0; i < a.length; i++) { + if (a[i] != className) { + if (i > 0) + newClassName += ' '; + newClassName += a[i]; + } + } + element.className = newClassName; + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + var element = $(element); + for (var i = 0; i < element.childNodes.length; i++) { + var node = element.childNodes[i]; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + Element.remove(node); + } + } +}); + +var Toggle = new Object(); +Toggle.display = Element.toggle; + +/*--------------------------------------------------------------------------*/ + +Abstract.Insertion = function(adjacency) { + this.adjacency = adjacency; +} + +Abstract.Insertion.prototype = { + initialize: function(element, content) { + this.element = $(element); + this.content = content; + + if (this.adjacency && this.element.insertAdjacentHTML) { + this.element.insertAdjacentHTML(this.adjacency, this.content); + } else { + this.range = this.element.ownerDocument.createRange(); + if (this.initializeRange) this.initializeRange(); + this.fragment = this.range.createContextualFragment(this.content); + this.insertContent(); + } + } +} + +var Insertion = new Object(); + +Insertion.Before = Class.create(); +Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { + initializeRange: function() { + this.range.setStartBefore(this.element); + }, + + insertContent: function() { + this.element.parentNode.insertBefore(this.fragment, this.element); + } +}); + +Insertion.Top = Class.create(); +Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(true); + }, + + insertContent: function() { + this.element.insertBefore(this.fragment, this.element.firstChild); + } +}); + +Insertion.Bottom = Class.create(); +Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(this.element); + }, + + insertContent: function() { + this.element.appendChild(this.fragment); + } +}); + +Insertion.After = Class.create(); +Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { + initializeRange: function() { + this.range.setStartAfter(this.element); + }, + + insertContent: function() { + this.element.parentNode.insertBefore(this.fragment, + this.element.nextSibling); + } +}); + var Field = { clear: function() { for (var i = 0; i < arguments.length; i++) @@ -309,7 +815,7 @@ var Form = { }, getElements: function(form) { - form = $(form); + var form = $(form); var elements = new Array(); for (tagName in Form.Element.Serializers) { @@ -320,17 +826,44 @@ var Form = { return elements; }, + getInputs: function(form, typeName, name) { + var form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) + return inputs; + + var matchingInputs = new Array(); + for (var i = 0; i < inputs.length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || + (name && input.name != name)) + continue; + matchingInputs.push(input); + } + + return matchingInputs; + }, + disable: function(form) { var elements = Form.getElements(form); for (var i = 0; i < elements.length; i++) { var element = elements[i]; element.blur(); - element.disable = 'true'; + element.disabled = 'true'; + } + }, + + enable: function(form) { + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + element.disabled = ''; } }, focusFirstElement: function(form) { - form = $(form); + var form = $(form); var elements = Form.getElements(form); for (var i = 0; i < elements.length; i++) { var element = elements[i]; @@ -348,7 +881,7 @@ var Form = { Form.Element = { serialize: function(element) { - element = $(element); + var element = $(element); var method = element.tagName.toLowerCase(); var parameter = Form.Element.Serializers[method](element); @@ -358,7 +891,7 @@ Form.Element = { }, getValue: function(element) { - element = $(element); + var element = $(element); var method = element.tagName.toLowerCase(); var parameter = Form.Element.Serializers[method](element); @@ -370,6 +903,7 @@ Form.Element = { Form.Element.Serializers = { input: function(element) { switch (element.type.toLowerCase()) { + case 'submit': case 'hidden': case 'password': case 'text': @@ -391,9 +925,20 @@ Form.Element.Serializers = { }, select: function(element) { - var index = element.selectedIndex; - var value = element.options[index].value || element.options[index].text; - return [element.name, (index >= 0) ? value : '']; + var value = ''; + if (element.type == 'select-one') { + var index = element.selectedIndex; + if (index >= 0) + value = element.options[index].value || element.options[index].text; + } else { + value = new Array(); + for (var i = 0; i < element.length; i++) { + var opt = element.options[i]; + if (opt.selected) + value.push(opt.value || opt.text); + } + } + return [element.name, value]; } } @@ -415,7 +960,7 @@ Abstract.TimedObserver.prototype = { }, registerCallback: function() { - setTimeout(this.onTimerEvent.bind(this), this.frequency * 1000); + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); }, onTimerEvent: function() { @@ -424,355 +969,289 @@ Abstract.TimedObserver.prototype = { this.callback(this.element, value); this.lastValue = value; } - - this.registerCallback(); } } Form.Element.Observer = Class.create(); -Form.Element.Observer.prototype = (new Abstract.TimedObserver()).extend({ +Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { getValue: function() { return Form.Element.getValue(this.element); } }); Form.Observer = Class.create(); -Form.Observer.prototype = (new Abstract.TimedObserver()).extend({ +Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { getValue: function() { return Form.serialize(this.element); } }); - /*--------------------------------------------------------------------------*/ -document.getElementsByClassName = function(className) { - var children = document.getElementsByTagName('*') || document.all; - var elements = new Array(); - - for (var i = 0; i < children.length; i++) { - var child = children[i]; - var classNames = child.className.split(' '); - for (var j = 0; j < classNames.length; j++) { - if (classNames[j] == className) { - elements.push(child); - break; - } - } - } - - return elements; -} - -/*--------------------------------------------------------------------------*/ - -var Element = { - toggle: function() { - for (var i = 0; i < arguments.length; i++) { - var element = $(arguments[i]); - element.style.display = - (element.style.display == 'none' ? '' : 'none'); - } - }, - - hide: function() { - for (var i = 0; i < arguments.length; i++) { - var element = $(arguments[i]); - element.style.display = 'none'; - } +Abstract.EventObserver = function() {} +Abstract.EventObserver.prototype = { + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); }, - - show: function() { - for (var i = 0; i < arguments.length; i++) { - var element = $(arguments[i]); - element.style.display = ''; + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; } }, - - remove: function(element) { - element = $(element); - element.parentNode.removeChild(element); + + registerFormCallbacks: function() { + var elements = Form.getElements(this.element); + for (var i = 0; i < elements.length; i++) + this.registerCallback(elements[i]); }, - - getHeight: function(element) { - element = $(element); - return element.offsetHeight; + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + element.target = this; + element.prev_onclick = element.onclick || Prototype.emptyFunction; + element.onclick = function() { + this.prev_onclick(); + this.target.onElementEvent(); + } + break; + case 'password': + case 'text': + case 'textarea': + case 'select-one': + case 'select-multiple': + element.target = this; + element.prev_onchange = element.onchange || Prototype.emptyFunction; + element.onchange = function() { + this.prev_onchange(); + this.target.onElementEvent(); + } + break; + } + } } } -var Toggle = new Object(); -Toggle.display = Element.toggle; +Form.Element.EventObserver = Class.create(); +Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); -/*--------------------------------------------------------------------------*/ +Form.EventObserver = Class.create(); +Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); -Abstract.Insertion = function(adjacency) { - this.adjacency = adjacency; -} -Abstract.Insertion.prototype = { - initialize: function(element, content) { - this.element = $(element); - this.content = content; - - if (this.adjacency && this.element.insertAdjacentHTML) { - this.element.insertAdjacentHTML(this.adjacency, this.content); - } else { - this.range = this.element.ownerDocument.createRange(); - if (this.initializeRange) this.initializeRange(); - this.fragment = this.range.createContextualFragment(this.content); - this.insertContent(); - } - } +if (!window.Event) { + var Event = new Object(); } -var Insertion = new Object(); - -Insertion.Before = Class.create(); -Insertion.Before.prototype = (new Abstract.Insertion('beforeBegin')).extend({ - initializeRange: function() { - this.range.setStartBefore(this.element); +Object.extend(Event, { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + + element: function(event) { + return event.target || event.srcElement; }, - - insertContent: function() { - this.element.parentNode.insertBefore(this.fragment, this.element); - } -}); -Insertion.Top = Class.create(); -Insertion.Top.prototype = (new Abstract.Insertion('afterBegin')).extend({ - initializeRange: function() { - this.range.selectNodeContents(this.element); - this.range.collapse(true); + isLeftClick: function(event) { + return (((event.which) && (event.which == 1)) || + ((event.button) && (event.button == 1))); }, - - insertContent: function() { - this.element.insertBefore(this.fragment, this.element.firstChild); - } -}); -Insertion.Bottom = Class.create(); -Insertion.Bottom.prototype = (new Abstract.Insertion('beforeEnd')).extend({ - initializeRange: function() { - this.range.selectNodeContents(this.element); - this.range.collapse(this.element); + pointerX: function(event) { + return event.pageX || (event.clientX + + (document.documentElement.scrollLeft || document.body.scrollLeft)); }, - - insertContent: function() { - this.element.appendChild(this.fragment); - } -}); -Insertion.After = Class.create(); -Insertion.After.prototype = (new Abstract.Insertion('afterEnd')).extend({ - initializeRange: function() { - this.range.setStartAfter(this.element); + pointerY: function(event) { + return event.pageY || (event.clientY + + (document.documentElement.scrollTop || document.body.scrollTop)); }, - - insertContent: function() { - this.element.parentNode.insertBefore(this.fragment, - this.element.nextSibling); - } -}); - -/*--------------------------------------------------------------------------*/ -var Effect = new Object(); + stop: function(event) { + if (event.preventDefault) { + event.preventDefault(); + event.stopPropagation(); + } else { + event.returnValue = false; + } + }, -Effect.Highlight = Class.create(); -Effect.Highlight.prototype = { - initialize: function(element) { - this.element = $(element); - this.start = 153; - this.finish = 255; - this.current = this.start; - this.fade(); + // find the first node with the given tagName, starting from the + // node the event was triggered on; traverses the DOM upwards + findElement: function(event, tagName) { + var element = Event.element(event); + while (element.parentNode && (!element.tagName || + (element.tagName.toUpperCase() != tagName.toUpperCase()))) + element = element.parentNode; + return element; }, + + observers: false, - fade: function() { - if (this.isFinished()) return; - if (this.timer) clearTimeout(this.timer); - this.highlight(this.element, this.current); - this.current += 17; - this.timer = setTimeout(this.fade.bind(this), 250); + _observeAndCache: function(element, name, observer, useCapture) { + if (!this.observers) this.observers = []; + if (element.addEventListener) { + this.observers.push([element, name, observer, useCapture]); + element.addEventListener(name, observer, useCapture); + } else if (element.attachEvent) { + this.observers.push([element, name, observer, useCapture]); + element.attachEvent('on' + name, observer); + } }, - isFinished: function() { - return this.current > this.finish; + unloadCache: function() { + if (!Event.observers) return; + for (var i = 0; i < Event.observers.length; i++) { + Event.stopObserving.apply(this, Event.observers[i]); + Event.observers[i][0] = null; + } + Event.observers = false; }, - - highlight: function(element, current) { - element.style.backgroundColor = "#ffff" + current.toColorPart(); - } -} - -Effect.Fade = Class.create(); -Effect.Fade.prototype = { - initialize: function(element) { - this.element = $(element); - this.start = 100; - this.finish = 0; - this.current = this.start; - this.fade(); - }, - - fade: function() { - if (this.isFinished()) { this.element.style.display = 'none'; return; } - if (this.timer) clearTimeout(this.timer); - this.setOpacity(this.element, this.current); - this.current -= 10; - this.timer = setTimeout(this.fade.bind(this), 50); - }, - - isFinished: function() { - return this.current <= this.finish; + observe: function(element, name, observer, useCapture) { + var element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + ((/Konqueror|Safari|KHTML/.test(navigator.userAgent)) + || element.attachEvent)) + name = 'keydown'; + + this._observeAndCache(element, name, observer, useCapture); }, - - setOpacity: function(element, opacity) { - opacity = (opacity == 100) ? 99.999 : opacity; - element.style.filter = "alpha(opacity:"+opacity+")"; - element.style.opacity = opacity/100 /*//*/; - } -} -Effect.Scale = Class.create(); -Effect.Scale.prototype = { - initialize: function(element, percent) { - this.element = $(element); - this.startScale = 1.0; - this.startHeight = this.element.offsetHeight; - this.startWidth = this.element.offsetWidth; - this.currentHeight = this.startHeight; - this.currentWidth = this.startWidth; - this.finishScale = (percent/100) /*//*/; - if (this.element.style.fontSize=="") this.sizeEm = 1.0; - if (this.element.style.fontSize.indexOf("em")>0) - this.sizeEm = parseFloat(this.element.style.fontSize); - if(this.element.effect_scale) { - clearTimeout(this.element.effect_scale.timer); - this.startScale = this.element.effect_scale.currentScale; - this.startHeight = this.element.effect_scale.startHeight; - this.startWidth = this.element.effect_scale.startWidth; - if(this.element.effect_scale.sizeEm) - this.sizeEm = this.element.effect_scale.sizeEm; - } - this.element.effect_scale = this; - this.currentScale = this.startScale; - this.factor = this.finishScale - this.startScale; - this.options = arguments[2] || {}; - this.scale(); - }, - - scale: function() { - if (this.isFinished()) { - this.setDimensions(this.element, this.startWidth*this.finishScale, this.startHeight*this.finishScale); - if(this.sizeEm) this.element.style.fontSize = this.sizeEm*this.finishScale + "em"; - if(this.options.complete) this.options.complete(this); - return; - } - if (this.timer) clearTimeout(this.timer); - if (this.options.step) this.options.step(this); - this.setDimensions(this.element, this.currentWidth, this.currentHeight); - if(this.sizeEm) this.element.style.fontSize = this.sizeEm*this.currentScale + "em"; - this.currentScale += (this.factor/10) /*//*/; - this.currentWidth = this.startWidth * this.currentScale; - this.currentHeight = this.startHeight * this.currentScale; - this.timer = setTimeout(this.scale.bind(this), 50); - }, - - isFinished: function() { - return (this.factor < 0) ? - this.currentScale <= this.finishScale : this.currentScale >= this.finishScale; - }, - - setDimensions: function(element, width, height) { - element.style.width = width + 'px'; - element.style.height = height + 'px'; + stopObserving: function(element, name, observer, useCapture) { + var element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + ((/Konqueror|Safari|KHTML/.test(navigator.userAgent)) + || element.detachEvent)) + name = 'keydown'; + + if (element.removeEventListener) { + element.removeEventListener(name, observer, useCapture); + } else if (element.detachEvent) { + element.detachEvent('on' + name, observer); + } } -} +}); -Effect.Squish = Class.create(); -Effect.Squish.prototype = { - initialize: function(element) { - this.element = $(element); - new Effect.Scale(this.element, 1, { complete: this.hide.bind(this) } ); +/* prevent memory leaks in IE */ +Event.observe(window, 'unload', Event.unloadCache, false); + +var Position = { + + // set to true if needed, warning: firefox performance problems + // NOT neeeded for page scrolling, only if draggable contained in + // scrollable elements + includeScrollOffsets: false, + + // must be called before calling withinIncludingScrolloffset, every time the + // page is scrolled + prepare: function() { + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; }, - hide: function() { - this.element.style.display = 'none'; - } -} -Effect.Puff = Class.create(); -Effect.Puff.prototype = { - initialize: function(element) { - this.element = $(element); - this.opacity = 100; - this.startTop = this.element.top || this.element.offsetTop; - this.startLeft = this.element.left || this.element.offsetLeft; - new Effect.Scale(this.element, 200, { step: this.fade.bind(this), complete: this.hide.bind(this) } ); - }, - fade: function(effect) { - topd = (((effect.currentScale)*effect.startHeight) - effect.startHeight)/2; - leftd = (((effect.currentScale)*effect.startWidth) - effect.startWidth)/2; - this.element.style.position='absolute'; - this.element.style.top = this.startTop-topd + "px"; - this.element.style.left = this.startLeft-leftd + "px"; - this.opacity -= 10; - this.setOpacity(this.element, this.opacity); - if(navigator.appVersion.indexOf('AppleWebKit')>0) this.element.innerHTML += ''; //force redraw on safari + realOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return [valueL, valueT]; }, - hide: function() { - this.element.style.display = 'none'; + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return [valueL, valueT]; }, - setOpacity: function(element, opacity) { - opacity = (opacity == 100) ? 99.999 : opacity; - element.style.filter = "alpha(opacity:"+opacity+")"; - element.style.opacity = opacity/100 /*//*/; - } -} -Effect.Appear = Class.create(); -Effect.Appear.prototype = { - initialize: function(element) { - this.element = $(element); - this.start = 0; - this.finish = 100; - this.current = this.start; - this.fade(); + // caches x/y coordinate pair to use with overlap + within: function(element, x, y) { + if (this.includeScrollOffsets) + return this.withinIncludingScrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + this.offset = this.cumulativeOffset(element); + + return (y >= this.offset[1] && + y < this.offset[1] + element.offsetHeight && + x >= this.offset[0] && + x < this.offset[0] + element.offsetWidth); }, - - fade: function() { - if (this.isFinished()) return; - if (this.timer) clearTimeout(this.timer); - this.setOpacity(this.element, this.current); - this.current += 10; - this.timer = setTimeout(this.fade.bind(this), 50); + + withinIncludingScrolloffsets: function(element, x, y) { + var offsetcache = this.realOffset(element); + + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.offset = this.cumulativeOffset(element); + + return (this.ycomp >= this.offset[1] && + this.ycomp < this.offset[1] + element.offsetHeight && + this.xcomp >= this.offset[0] && + this.xcomp < this.offset[0] + element.offsetWidth); }, - - isFinished: function() { - return this.current > this.finish; + + // within must be called directly before + overlap: function(mode, element) { + if (!mode) return 0; + if (mode == 'vertical') + return ((this.offset[1] + element.offsetHeight) - this.ycomp) / + element.offsetHeight; + if (mode == 'horizontal') + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + element.offsetWidth; }, - - setOpacity: function(element, opacity) { - opacity = (opacity == 100) ? 99.999 : opacity; - element.style.filter = "alpha(opacity:"+opacity+")"; - element.style.opacity = opacity/100 /*//*/; - element.style.display = ''; - } -} -Effect.ContentZoom = Class.create(); -Effect.ContentZoom.prototype = { - initialize: function(element, percent) { - this.element = $(element); - if (this.element.style.fontSize=="") this.sizeEm = 1.0; - if (this.element.style.fontSize.indexOf("em")>0) - this.sizeEm = parseFloat(this.element.style.fontSize); - if(this.element.effect_contentzoom) { - this.sizeEm = this.element.effect_contentzoom.sizeEm; - } - this.element.effect_contentzoom = this; - this.element.style.fontSize = this.sizeEm*(percent/100) + "em" /*//*/; - if(navigator.appVersion.indexOf('AppleWebKit')>0) { this.element.scrollTop -= 1; }; + clone: function(source, target) { + source = $(source); + target = $(target); + target.style.position = 'absolute'; + var offsets = this.cumulativeOffset(source); + target.style.top = offsets[1] + 'px'; + target.style.left = offsets[0] + 'px'; + target.style.width = source.offsetWidth + 'px'; + target.style.height = source.offsetHeight + 'px'; } } diff --git a/webcit/static/rico.js b/webcit/static/rico.js index d17acb1e1..ed07dad9e 100644 --- a/webcit/static/rico.js +++ b/webcit/static/rico.js @@ -1,5 +1,3 @@ -/* openrico.org rico.js */ - /** * * Copyright 2005 Sabre Airline Solutions @@ -15,10 +13,10 @@ * and limitations under the License. **/ -// rico.js -------------------- +//-------------------- rico.js var Rico = { - Version: '1.1-beta' + Version: '1.1-beta2' } Rico.ArrayExtensions = new Array(); @@ -122,10 +120,9 @@ document.getElementsByTagAndClassName = function(tagName, className) { return elements; } - - -// ricoAccordion.js -------------------- - + + +//-------------------- ricoAccordion.js Rico.Accordion = Class.create(); @@ -305,10 +302,9 @@ Rico.Accordion.Tab.prototype = { } }; - - -// ricoAjaxEngine.js -------------------- - + + +//-------------------- ricoAjaxEngine.js Rico.AjaxEngine = Class.create(); @@ -469,34 +465,15 @@ Rico.AjaxEngine.prototype = { }, _processAjaxElementUpdate: function( ajaxElement, responseElement ) { - if ( responseElement.xml != undefined ) - this._processAjaxElementUpdateIE( ajaxElement, responseElement ); - else - this._processAjaxElementUpdateMozilla( ajaxElement, responseElement ); - }, - - _processAjaxElementUpdateIE: function( ajaxElement, responseElement ) { - var newHTML = ""; - for ( var i = 0 ; i < responseElement.childNodes.length ; i++ ) - newHTML += responseElement.childNodes[i].xml; + ajaxElement.innerHTML = RicoUtil.getContentAsString(responseElement); + } - ajaxElement.innerHTML = newHTML; - }, +} - _processAjaxElementUpdateMozilla: function( ajaxElement, responseElement ) { - var xmlSerializer = new XMLSerializer(); - var newHTML = ""; - for ( var i = 0 ; i < responseElement.childNodes.length ; i++ ) - newHTML += xmlSerializer.serializeToString(responseElement.childNodes[i]); +var ajaxEngine = new Rico.AjaxEngine(); - ajaxElement.innerHTML = newHTML; - } -} -var ajaxEngine = new Rico.AjaxEngine(); - -// ricoColor.js -------------------- - +//-------------------- ricoColor.js Rico.Color = Class.create(); Rico.Color.prototype = { @@ -725,10 +702,10 @@ Rico.Color.RGBtoHSB = function(r, g, b) { } return { h : hue, s : saturation, b : brightness }; -} - -// ricoCorner.js -------------------- - +} + + +//-------------------- ricoCorner.js Rico.Corner = { @@ -951,10 +928,10 @@ Rico.Corner = { _isTopRounded: function() { return this._hasString(this.options.corners, "all", "top", "tl", "tr"); }, _isBottomRounded: function() { return this._hasString(this.options.corners, "all", "bottom", "bl", "br"); }, _hasSingleTextChild: function(el) { return el.childNodes.length == 1 && el.childNodes[0].nodeType == 3; } -} - -// ricoDragAndDrop.js -------------------- - +} + + +//-------------------- ricoDragAndDrop.js Rico.DragAndDrop = Class.create(); Rico.DragAndDrop.prototype = { @@ -1042,8 +1019,17 @@ Rico.DragAndDrop.prototype = { if ( (nsEvent && e.which != 1) || (!nsEvent && e.button != 1)) return; - var eventTarget = e.target ? e.target : e.srcElement; - var draggableObject = eventTarget.draggable; + var eventTarget = e.target ? e.target : e.srcElement; + var draggableObject = eventTarget.draggable; + + var candidate = eventTarget; + while (draggableObject == null && candidate.parentNode) { + candidate = candidate.parentNode; + draggableObject = candidate.draggable; + } + + if ( draggableObject == null ) + return; this.updateSelection( draggableObject, e.ctrlKey ); @@ -1308,10 +1294,10 @@ Rico.DragAndDrop.prototype = { } var dndMgr = new Rico.DragAndDrop(); -dndMgr.initializeEventHandlers(); - -// ricoDraggable.js -------------------- - +dndMgr.initializeEventHandlers(); + + +//-------------------- ricoDraggable.js Rico.Draggable = Class.create(); Rico.Draggable.prototype = { @@ -1387,10 +1373,10 @@ Rico.Draggable.prototype = { return this.type + ":" + this.htmlElement + ":"; } -} - -// ricoDropzone.js -------------------- - +} + + +//-------------------- ricoDropzone.js Rico.Dropzone = Class.create(); Rico.Dropzone.prototype = { @@ -1500,10 +1486,18 @@ Rico.Dropzone.prototype = { htmlElement.appendChild(theGUI); } } -} - -// ricoEffects.js -------------------- - +} + + +//-------------------- ricoEffects.js + +/** + * Use the Effect namespace for effects. If using scriptaculous effects + * this will already be defined, otherwise we'll just create an empty + * object for it... + **/ +if ( window.Effect == undefined ) + Effect = {}; Effect.SizeAndPosition = Class.create(); Effect.SizeAndPosition.prototype = { @@ -1736,10 +1730,9 @@ Effect.AccordionSize.prototype = { } }; - - -// ricoLiveGrid.js -------------------- - + + +//-------------------- ricoLiveGrid.js // Rico.LiveGridMetaData ----------------------------------------------------- @@ -1747,17 +1740,17 @@ Rico.LiveGridMetaData = Class.create(); Rico.LiveGridMetaData.prototype = { - initialize: function( pageSize, totalRows, options ) { + initialize: function( pageSize, totalRows, columnCount, options ) { this.pageSize = pageSize; this.totalRows = totalRows; this.setOptions(options); this.scrollArrowHeight = 16; + this.columnCount = columnCount; }, setOptions: function(options) { this.options = { largeBufferSize : 7.0, // 7 pages - smallBufferSize : 1.0, // 1 page nearLimitFactor : 0.2 // 20% of buffer }.extend(options || {}); }, @@ -1778,17 +1771,9 @@ Rico.LiveGridMetaData.prototype = { return parseInt(this.options.largeBufferSize * this.pageSize); }, - getSmallBufferSize: function() { - return parseInt(this.options.smallBufferSize * this.pageSize); - }, - getLimitTolerance: function() { return parseInt(this.getLargeBufferSize() * this.options.nearLimitFactor); - }, - - getBufferSize: function(isFull) { - return isFull ? this.getLargeBufferSize() : this.getSmallBufferSize(); - } + } }; // Rico.LiveGridScroller ----------------------------------------------------- @@ -1797,14 +1782,15 @@ Rico.LiveGridScroller = Class.create(); Rico.LiveGridScroller.prototype = { - initialize: function(liveGrid) { + initialize: function(liveGrid, viewPort) { this.isIE = navigator.userAgent.toLowerCase().indexOf("msie") >= 0; this.liveGrid = liveGrid; this.metaData = liveGrid.metaData; this.createScrollBar(); this.scrollTimeout = null; - //this.sizeIEHeaderHack(); this.lastScrollPos = 0; + this.viewPort = viewPort; + this.rows = new Array(); }, isUnPlugged: function() { @@ -1828,9 +1814,7 @@ Rico.LiveGridScroller.prototype = { }, createScrollBar: function() { - var table = this.liveGrid.table; - var visibleHeight = table.offsetHeight; - + var visibleHeight = this.liveGrid.viewPort.visibleHeight(); // create the outer div... this.scrollerDiv = document.createElement("div"); var scrollerStyle = this.scrollerDiv.style; @@ -1844,45 +1828,43 @@ Rico.LiveGridScroller.prototype = { // create the inner div... this.heightDiv = document.createElement("div"); this.heightDiv.style.width = "1px"; + this.heightDiv.style.height = parseInt(visibleHeight * this.metaData.getTotalRows()/this.metaData.getPageSize()) + "px" ; - this.lineHeight = visibleHeight/this.metaData.getPageSize(); - this.scrollerDiv.appendChild(this.heightDiv); this.scrollerDiv.onscroll = this.handleScroll.bindAsEventListener(this); - table.parentNode.insertBefore( this.scrollerDiv, table.nextSibling ); + + var table = this.liveGrid.table; + table.parentNode.parentNode.insertBefore( this.scrollerDiv, table.parentNode.nextSibling ); }, updateSize: function() { var table = this.liveGrid.table; - var visibleHeight = table.offsetHeight; + var visibleHeight = this.viewPort.visibleHeight(); this.heightDiv.style.height = parseInt(visibleHeight * this.metaData.getTotalRows()/this.metaData.getPageSize()) + "px"; }, - adjustScrollTop: function() { - this.unplug(); - var rem = this.scrollerDiv.scrollTop % this.lineHeight; - if (rem != 0) { - if (this.lastScrollPos < this.scrollerDiv.scrollTop) - this.scrollerDiv.scrollTop = this.scrollerDiv.scrollTop + this.lineHeight -rem; - else - this.scrollerDiv.scrollTop = this.scrollerDiv.scrollTop - rem; - } - this.lastScrollPos = this.scrollerDiv.scrollTop; - this.plugin(); + rowToPixel: function(rowOffset) { + return (rowOffset / this.metaData.getTotalRows()) * this.heightDiv.offsetHeight + }, + + moveScroll: function(rowOffset) { + this.scrollerDiv.scrollTop = this.rowToPixel(rowOffset); + if ( this.metaData.options.onscroll ) + this.metaData.options.onscroll( this.liveGrid, rowOffset ); }, handleScroll: function() { - if ( this.scrollTimeout ) + if ( this.scrollTimeout ) clearTimeout( this.scrollTimeout ); - //this.adjustScrollTop(); - var contentOffset = parseInt(this.scrollerDiv.scrollTop * - this.metaData.getTotalRows() / this.heightDiv.offsetHeight); + var contentOffset = parseInt(this.scrollerDiv.scrollTop / this.viewPort.rowHeight); this.liveGrid.requestContentRefresh(contentOffset); + this.viewPort.scrollTo(this.scrollerDiv.scrollTop); + if ( this.metaData.options.onscroll ) - this.metaData.options.onscroll( contentOffset, this.metaData ); + this.metaData.options.onscroll( this.liveGrid, contentOffset ); this.scrollTimeout = setTimeout( this.scrollIdle.bind(this), 1200 ); }, @@ -1899,60 +1881,105 @@ Rico.LiveGridBuffer = Class.create(); Rico.LiveGridBuffer.prototype = { - initialize: function(metaData) { + initialize: function(metaData, viewPort) { this.startPos = 0; this.size = 0; this.metaData = metaData; this.rows = new Array(); this.updateInProgress = false; - }, - - update: function(ajaxResponse,start) { - - this.startPos = start; - this.rows = new Array(); - + this.viewPort = viewPort; + this.maxBufferSize = metaData.getLargeBufferSize() * 2; + this.maxFetchSize = metaData.getLargeBufferSize(); + this.lastOffset = 0; + }, + + getBlankRow: function() { + if (!this.blankRow ) { + this.blankRow = new Array(); + for ( var i=0; i < this.metaData.columnCount ; i++ ) + this.blankRow[i] = " "; + } + return this.blankRow; + }, + + loadRows: function(ajaxResponse) { var rowsElement = ajaxResponse.getElementsByTagName('rows')[0]; this.updateUI = rowsElement.getAttribute("update_ui") == "true" + var newRows = new Array() var trs = rowsElement.getElementsByTagName("tr"); for ( var i=0 ; i < trs.length; i++ ) { - var row = this.rows[i] = new Array(); + var row = newRows[i] = new Array(); var cells = trs[i].getElementsByTagName("td"); for ( var j=0; j < cells.length ; j++ ) { var cell = cells[j]; var convertSpaces = cell.getAttribute("convert_spaces") == "true"; - var cellContent = cell.text != undefined ? cell.text : cell.textContent; + var cellContent = RicoUtil.getContentAsString(cell); row[j] = convertSpaces ? this.convertSpaces(cellContent) : cellContent; + if (!row[j]) + row[j] = ' '; } } - this.size = trs.length; + return newRows; + }, + + update: function(ajaxResponse, start) { + var newRows = this.loadRows(ajaxResponse); + if (this.rows.length == 0) { // initial load + this.rows = newRows; + this.size = this.rows.length; + this.startPos = start; + return; + } + if (start > this.startPos) { //appending + if (this.startPos + this.rows.length < start) { + this.rows = newRows; + this.startPos = start;// + } else { + this.rows = this.rows.concat( newRows.slice(0, newRows.length)); + if (this.rows.length > this.maxBufferSize) { + var fullSize = this.rows.length; + this.rows = this.rows.slice(this.rows.length - this.maxBufferSize, this.rows.length) + this.startPos = this.startPos + (fullSize - this.rows.length); + } + } + } else { //prepending + if (start + newRows.length < this.startPos) { + this.rows = newRows; + } else { + this.rows = newRows.slice(0, this.startPos).concat(this.rows); + if (this.rows.length > this.maxBufferSize) + this.rows = this.rows.slice(0, this.maxBufferSize) + } + this.startPos = start; + } + this.size = this.rows.length; + }, + + clear: function() { + this.rows = new Array(); + this.startPos = 0; + this.size = 0; }, - isFullP: function() { - return this.metaData.pageSize != this.size; - }, - - isClose: function(start) { - return (start < this.startPos + this.size + (this.size/2)) && - (start + this.size + (this.size/2) > this.startPos) - }, - - isInRange: function(start, count) { - return (start < this.startPos + this.size) && (start + count > this.startPos) + isOverlapping: function(start, size) { + return ((start < this.endPos()) && (this.startPos < start + size)) || (this.endPos() == 0) }, - isFullyInRange: function(position) { - return (position >= this.startPos) && (position+this.metaData.getPageSize()) <= (this.startPos + this.size) + isInRange: function(position) { + return (position >= this.startPos) && (position + this.metaData.getPageSize() <= this.endPos()); + //&& this.size() != 0; }, isNearingTopLimit: function(position) { return position - this.startPos < this.metaData.getLimitTolerance(); }, + endPos: function() { + return this.startPos + this.rows.length; + }, + isNearingBottomLimit: function(position) { - var myEnd = position + this.metaData.getPageSize(); - var bufferEnd = this.startPos + this.size; - return bufferEnd - myEnd < this.metaData.getLimitTolerance(); + return this.endPos() - (position + this.metaData.getPageSize()) < this.metaData.getLimitTolerance(); }, isAtTop: function() { @@ -1960,7 +1987,7 @@ Rico.LiveGridBuffer.prototype = { }, isAtBottom: function() { - return this.startPos + this.size == this.metaData.getTotalRows(); + return this.endPos() == this.metaData.getTotalRows(); }, isNearingLimit: function(position) { @@ -1968,6 +1995,37 @@ Rico.LiveGridBuffer.prototype = { ( !this.isAtBottom() && this.isNearingBottomLimit(position) ) }, + getFetchSize: function(offset) { + var adjustedOffset = this.getFetchOffset(offset); + var adjustedSize = 0; + if (adjustedOffset >= this.startPos) { //apending + var endFetchOffset = this.maxFetchSize + adjustedOffset; + if (endFetchOffset > this.metaData.totalRows) + endFetchOffset = this.metaData.totalRows; + adjustedSize = endFetchOffset - adjustedOffset; + } else {//prepending + var adjustedSize = this.startPos - adjustedOffset; + if (adjustedSize > this.maxFetchSize) + adjustedSize = this.maxFetchSize; + } + return adjustedSize; + }, + + getFetchOffset: function(offset) { + var adjustedOffset = offset; + if (offset > this.startPos) //apending + adjustedOffset = (offset > this.endPos()) ? offset : this.endPos(); + else { //prepending + if (offset + this.maxFetchSize >= this.startPos) { + var adjustedOffset = this.startPos - this.maxFetchSize; + if (adjustedOffset < 0) + adjustedOffset = 0; + } + } + this.lastOffset = adjustedOffset; + return adjustedOffset; + }, + getRows: function(start, count) { var begPos = start - this.startPos var endPos = begPos + count @@ -1990,10 +2048,105 @@ Rico.LiveGridBuffer.prototype = { }; + +//Rico.GridViewPort -------------------------------------------------- +Rico.GridViewPort = Class.create(); + +Rico.GridViewPort.prototype = { + + initialize: function(table, rowHeight, visibleRows, buffer, liveGrid) { + this.lastDisplayedStartPos = 0; + this.div = table.parentNode; + this.table = table + this.rowHeight = rowHeight; + this.div.style.height = this.rowHeight * visibleRows; + this.div.style.overflow = "hidden"; + this.buffer = buffer; + this.liveGrid = liveGrid; + this.visibleRows = visibleRows + 1; + this.lastPixelOffset = 0; + this.startPos = 0; + }, + + populateRow: function(htmlRow, row) { + for (var j=0; j < row.length; j++) { + htmlRow.cells[j].innerHTML = row[j] + } + }, + + bufferChanged: function() { + this.refreshContents( parseInt(this.lastPixelOffset / this.rowHeight)); + }, + + clearRows: function() { + if (!this.isBlank) { + for (var i=0; i < this.visibleRows; i++) + this.populateRow(this.table.rows[i], this.buffer.getBlankRow()); + this.isBlank = true; + } + }, + + clearContents: function() { + this.clearRows(); + this.scrollTo(0); + this.startPos = 0; + this.lastStartPos = -1; + }, + + refreshContents: function(startPos) { + if (startPos == this.lastRowPos && !this.isPartialBlank && !this.isBlank) { + return; + } + if ((startPos + this.visibleRows < this.buffer.startPos) + || (this.buffer.startPos + this.buffer.size < startPos) + || (this.buffer.size == 0)) { + this.clearRows(); + return; + } + this.isBlank = false; + var viewPrecedesBuffer = this.buffer.startPos > startPos + var contentStartPos = viewPrecedesBuffer ? this.buffer.startPos: startPos; + + var contentEndPos = (this.buffer.startPos + this.buffer.size < startPos + this.visibleRows) + ? this.buffer.startPos + this.buffer.size + : startPos + this.visibleRows; + var rowSize = contentEndPos - contentStartPos; + var rows = this.buffer.getRows(contentStartPos, rowSize ); + var blankSize = this.visibleRows - rowSize; + var blankOffset = viewPrecedesBuffer ? 0: rowSize; + var contentOffset = viewPrecedesBuffer ? blankSize: 0; + + for (var i=0; i < rows.length; i++) {//initialize what we have + this.populateRow(this.table.rows[i + contentOffset], rows[i]); + } + for (var i=0; i < blankSize; i++) {// blank out the rest + this.populateRow(this.table.rows[i + blankOffset], this.buffer.getBlankRow()); + } + this.isPartialBlank = blankSize > 0; + this.lastRowPos = startPos; + }, + + scrollTo: function(pixelOffset) { + if (this.lastPixelOffset == pixelOffset) + return; + + this.refreshContents(parseInt(pixelOffset / this.rowHeight)) + this.div.scrollTop = pixelOffset % this.rowHeight + + this.lastPixelOffset = pixelOffset; + }, + + visibleHeight: function() { + return parseInt(this.div.style.height); + } + +}; + + Rico.LiveGridRequest = Class.create(); Rico.LiveGridRequest.prototype = { initialize: function( requestOffset, options ) { - this.requestOffset = requestOffset; + this.requestOffset = requestOffset; } }; @@ -2004,28 +2157,62 @@ Rico.LiveGrid = Class.create(); Rico.LiveGrid.prototype = { initialize: function( tableId, visibleRows, totalRows, url, options ) { - if ( options == null ) options = {}; - this.tableId = tableId; + this.tableId = tableId; this.table = $(tableId); - this.metaData = new Rico.LiveGridMetaData(visibleRows, totalRows, options); + var columnCount = this.table.rows[0].cells.length + this.metaData = new Rico.LiveGridMetaData(visibleRows, totalRows, columnCount, options); this.buffer = new Rico.LiveGridBuffer(this.metaData); - this.scroller = new Rico.LiveGridScroller(this); - this.lastDisplayedStartPos = 0; - this.timeoutHander = null; + var rowCount = this.table.rows.length; + this.viewPort = new Rico.GridViewPort(this.table, + this.table.offsetHeight/rowCount, + visibleRows, + this.buffer, this); + this.scroller = new Rico.LiveGridScroller(this,this.viewPort); + this.additionalParms = options.requestParameters || []; + + options.sortHandler = this.sortHandler.bind(this); + + if ( $(tableId + '_header') ) + this.sort = new Rico.LiveGridSort(tableId + '_header', options) this.processingRequest = null; this.unprocessedRequest = null; this.initAjax(url); - if ( options.prefetchBuffer ) - this.fetchBuffer(0, true); + if ( options.prefetchBuffer || options.prefetchOffset > 0) { + var offset = 0; + if (options.offset ) { + offset = options.offset; + this.scroller.moveScroll(offset); + this.viewPort.scrollTo(this.scroller.rowToPixel(offset)); + } + if (options.sortCol) { + this.sortCol = options.sortCol; + this.sortDir = options.sortDir; + } + this.requestContentRefresh(offset); + } }, + resetContents: function() { + this.scroller.moveScroll(0); + this.buffer.clear(); + this.viewPort.clearContents(); + }, + + sortHandler: function(column) { + this.sortCol = column.name; + this.sortDir = column.currentSort; + + this.resetContents(); + this.requestContentRefresh(0) + }, + setRequestParams: function() { this.additionalParms = []; for ( var i=0 ; i < arguments.length ; i++ ) @@ -2033,6 +2220,7 @@ Rico.LiveGrid.prototype = { }, setTotalRows: function( newTotalRows ) { + this.resetContents(); this.metaData.setTotalRows(newTotalRows); this.scroller.updateSize(); }, @@ -2045,95 +2233,265 @@ Rico.LiveGrid.prototype = { invokeAjax: function() { }, - largeBufferWindowStart: function(offset) { - val = offset - ( (.5 * this.metaData.getLargeBufferSize()) - (.5 * this.metaData.getPageSize()) ) - return Math.max(parseInt(val), 0); - }, - handleTimedOut: function() { //server did not respond in 4 seconds... assume that there could have been //an error or something, and allow requests to be processed again... this.processingRequest = null; + this.processQueuedRequest(); }, - fetchBuffer: function(offset, fullBufferp) { + fetchBuffer: function(offset) { + if ( this.buffer.isInRange(offset) && + !this.buffer.isNearingLimit(offset)) { + return; + } if (this.processingRequest) { - this.unprocessedRequest = new Rico.LiveGridRequest(offset); - return; + this.unprocessedRequest = new Rico.LiveGridRequest(offset); + return; } - - var fetchSize = this.metaData.getBufferSize(fullBufferp); - bufferStartPos = Math.max(0,fullBufferp ? this.largeBufferWindowStart(offset) : offset); - + var bufferStartPos = this.buffer.getFetchOffset(offset); this.processingRequest = new Rico.LiveGridRequest(offset); - this.processingRequest.bufferOffset = bufferStartPos; - - var callParms = []; + this.processingRequest.bufferOffset = bufferStartPos; + var fetchSize = this.buffer.getFetchSize(offset); + var partialLoaded = false; + var callParms = []; callParms.push(this.tableId + '_request'); callParms.push('id=' + this.tableId); callParms.push('page_size=' + fetchSize); callParms.push('offset=' + bufferStartPos); - + if ( this.sortCol) { + callParms.push('sort_col=' + this.sortCol); + callParms.push('sort_dir=' + this.sortDir); + } + for( var i=0 ; i < this.additionalParms.length ; i++ ) callParms.push(this.additionalParms[i]); - ajaxEngine.sendRequest.apply( ajaxEngine, callParms ); - this.timeoutHandler = setTimeout( this.handleTimedOut.bind(this), 4000 ); + + this.timeoutHandler = setTimeout( this.handleTimedOut.bind(this), 20000 ); //todo: make as option }, requestContentRefresh: function(contentOffset) { - if ( this.buffer && this.buffer.isFullyInRange(contentOffset) ) { - this.updateContent(contentOffset); - if (this.buffer.isNearingLimit(contentOffset)) - this.fetchBuffer(contentOffset, true); - } - else if (this.buffer && this.buffer.isClose(contentOffset)) - this.fetchBuffer(contentOffset, true); - else - this.fetchBuffer(contentOffset, false); + this.fetchBuffer(contentOffset); }, ajaxUpdate: function(ajaxResponse) { try { clearTimeout( this.timeoutHandler ); - this.buffer = new Rico.LiveGridBuffer(this.metaData); this.buffer.update(ajaxResponse,this.processingRequest.bufferOffset); - if (this.unprocessedRequest == null) { - offset = this.processingRequest.requestOffset; - this.updateContent (offset); - } - this.processingRequest = null; - if (this.unprocessedRequest != null) { - this.requestContentRefresh(this.unprocessedRequest.requestOffset); - this.unprocessedRequest = null + this.viewPort.bufferChanged(); + } + catch(err) {} + finally {this.processingRequest = null; } + this.processQueuedRequest(); + }, + + processQueuedRequest: function() { + if (this.unprocessedRequest != null) { + this.requestContentRefresh(this.unprocessedRequest.requestOffset); + this.unprocessedRequest = null + } + } + +}; + + +//-------------------- ricoLiveGridSort.js +Rico.LiveGridSort = Class.create(); + +Rico.LiveGridSort.prototype = { + + initialize: function(headerTableId, options) { + this.headerTableId = headerTableId; + this.headerTable = $(headerTableId); + this.setOptions(options); + this.applySortBehavior(); + + if ( this.options.sortCol ) { + this.setSortUI( this.options.sortCol, this.options.sortDir ); + } + }, + + setSortUI: function( columnName, sortDirection ) { + var cols = this.options.columns; + for ( var i = 0 ; i < cols.length ; i++ ) { + if ( cols[i].name == columnName ) { + this.setColumnSort(i, sortDirection); + break; } } - catch(err) { + }, + + setOptions: function(options) { + this.options = { + sortAscendImg: 'images/sort_asc.gif', + sortDescendImg: 'images/sort_desc.gif', + imageWidth: 9, + imageHeight: 5, + ajaxSortURLParms: [] + }.extend(options); + + // preload the images... + new Image().src = this.options.sortAscendImg; + new Image().src = this.options.sortDescendImg; + + this.sort = options.sortHandler; + if ( !this.options.columns ) + this.options.columns = this.introspectForColumnInfo(); + else { + // allow client to pass { columns: [ ["a", true], ["b", false] ] } + // and convert to an array of Rico.TableColumn objs... + this.options.columns = this.convertToTableColumns(this.options.columns); } }, - updateContent: function( offset ) { - this.replaceCellContents(this.buffer, offset); + applySortBehavior: function() { + var headerRow = this.headerTable.rows[0]; + var headerCells = headerRow.cells; + for ( var i = 0 ; i < headerCells.length ; i++ ) { + this.addSortBehaviorToColumn( i, headerCells[i] ); + } }, - replaceCellContents: function(buffer, startPos) { - if (startPos == this.lastDisplayedStartPos){ - return; + addSortBehaviorToColumn: function( n, cell ) { + if ( this.options.columns[n].isSortable() ) { + cell.id = this.headerTableId + '_' + n; + cell.style.cursor = 'pointer'; + cell.onclick = this.headerCellClicked.bindAsEventListener(this); + cell.innerHTML = cell.innerHTML + '' + + '   '; } + }, - this.lastDisplayedStartPos = startPos - var rows = buffer.getRows(startPos, this.metaData.getPageSize()); - for (var i=0; i < rows.length; i++) { - var row = rows[i]; - for (var j=0; j < row.length; j++) { - this.table.rows[i].cells[j].innerHTML = rows[i][j] + // event handler.... + headerCellClicked: function(evt) { + var eventTarget = evt.target ? evt.target : evt.srcElement; + var cellId = eventTarget.id; + var columnNumber = parseInt(cellId.substring( cellId.lastIndexOf('_') + 1 )); + var sortedColumnIndex = this.getSortedColumnIndex(); + if ( sortedColumnIndex != -1 ) { + if ( sortedColumnIndex != columnNumber ) { + this.removeColumnSort(sortedColumnIndex); + this.setColumnSort(columnNumber, Rico.TableColumn.SORT_ASC); } + else + this.toggleColumnSort(sortedColumnIndex); + } + else + this.setColumnSort(columnNumber, Rico.TableColumn.SORT_ASC); + + if (this.options.sortHandler) { + this.options.sortHandler(this.options.columns[columnNumber]); + } + }, + + removeColumnSort: function(n) { + this.options.columns[n].setUnsorted(); + this.setSortImage(n); + }, + + setColumnSort: function(n, direction) { + this.options.columns[n].setSorted(direction); + this.setSortImage(n); + }, + + toggleColumnSort: function(n) { + this.options.columns[n].toggleSort(); + this.setSortImage(n); + }, + + setSortImage: function(n) { + var sortDirection = this.options.columns[n].getSortDirection(); + + var sortImageSpan = $( this.headerTableId + '_img_' + n ); + if ( sortDirection == Rico.TableColumn.UNSORTED ) + sortImageSpan.innerHTML = '  '; + else if ( sortDirection == Rico.TableColumn.SORT_ASC ) + sortImageSpan.innerHTML = '  '; + else if ( sortDirection == Rico.TableColumn.SORT_DESC ) + sortImageSpan.innerHTML = '  '; + }, + + getSortedColumnIndex: function() { + var cols = this.options.columns; + for ( var i = 0 ; i < cols.length ; i++ ) { + if ( cols[i].isSorted() ) + return i; } + + return -1; + }, + + introspectForColumnInfo: function() { + var columns = new Array(); + var headerRow = this.headerTable.rows[0]; + var headerCells = headerRow.cells; + for ( var i = 0 ; i < headerCells.length ; i++ ) + columns.push( new Rico.TableColumn( this.deriveColumnNameFromCell(headerCells[i],i), true ) ); + return columns; + }, + + convertToTableColumns: function(cols) { + var columns = new Array(); + for ( var i = 0 ; i < cols.length ; i++ ) + columns.push( new Rico.TableColumn( cols[i][0], cols[i][1] ) ); + }, + + deriveColumnNameFromCell: function(cell,columnNumber) { + var cellContent = cell.innerText != undefined ? cell.innerText : cell.textContent; + return cellContent ? cellContent.toLowerCase().split(' ').join('_') : "col_" + columnNumber; } -}; - -// ricoUtil.js -------------------- - +}; + +Rico.TableColumn = Class.create(); + +Rico.TableColumn.UNSORTED = 0; +Rico.TableColumn.SORT_ASC = "ASC"; +Rico.TableColumn.SORT_DESC = "DESC"; + +Rico.TableColumn.prototype = { + initialize: function(name, sortable) { + this.name = name; + this.sortable = sortable; + this.currentSort = Rico.TableColumn.UNSORTED; + }, + + isSortable: function() { + return this.sortable; + }, + + isSorted: function() { + return this.currentSort != Rico.TableColumn.UNSORTED; + }, + + getSortDirection: function() { + return this.currentSort; + }, + + toggleSort: function() { + if ( this.currentSort == Rico.TableColumn.UNSORTED || this.currentSort == Rico.TableColumn.SORT_DESC ) + this.currentSort = Rico.TableColumn.SORT_ASC; + else if ( this.currentSort == Rico.TableColumn.SORT_ASC ) + this.currentSort = Rico.TableColumn.SORT_DESC; + }, + + setUnsorted: function(direction) { + this.setSorted(Rico.TableColumn.UNSORTED); + }, + + setSorted: function(direction) { + // direction must by one of Rico.TableColumn.UNSORTED, .SORT_ASC, or .SET_DESC... + this.currentSort = direction; + } + +}; + + +//-------------------- ricoUtil.js var RicoUtil = { @@ -2175,6 +2533,27 @@ var RicoUtil = { return null; }, + getContentAsString: function( parentNode ) { + return parentNode.xml != undefined ? + this._getContentAsStringIE(parentNode) : + this._getContentAsStringMozilla(parentNode); + }, + + _getContentAsStringIE: function(parentNode) { + var contentStr = ""; + for ( var i = 0 ; i < parentNode.childNodes.length ; i++ ) + contentStr += parentNode.childNodes[i].xml; + return contentStr; + }, + + _getContentAsStringMozilla: function(parentNode) { + var xmlSerializer = new XMLSerializer(); + var contentStr = ""; + for ( var i = 0 ; i < parentNode.childNodes.length ; i++ ) + contentStr += xmlSerializer.serializeToString(parentNode.childNodes[i]); + return contentStr; + }, + toViewportPosition: function(element) { return this._toAbsolute(element,true); }, @@ -2284,4 +2663,4 @@ var RicoUtil = { return 0; } -}; \ No newline at end of file +}; diff --git a/webcit/static/scriptaculous.js b/webcit/static/scriptaculous.js new file mode 100644 index 000000000..ea1ad40ae --- /dev/null +++ b/webcit/static/scriptaculous.js @@ -0,0 +1,26 @@ +var Scriptaculous = { + Version: '1.5_pre4', + require: function(libraryName) { + // inserting via DOM fails in Safari 2.0, so brute force approach + document.write(''); + }, + load: function() { + if((typeof Prototype=='undefined') || + parseFloat(Prototype.Version.split(".")[0] + "." + + Prototype.Version.split(".")[1]) < 1.4) + throw("script.aculo.us requires the Prototype JavaScript framework >= 1.4.0"); + var scriptTags = document.getElementsByTagName("script"); + for(var i=0;i ' + + (typeof obj[property] == "string" ? + '"' + obj[property] + '"' : + obj[property])); + } + + return ("'" + obj + "' #" + typeof obj + + ": {" + info.join(", ") + "}"); +} + +Test.Unit.Logger = Class.create(); +Test.Unit.Logger.prototype = { + initialize: function(log) { + this.log = $(log); + if (this.log) { + this._createLogTable(); + } + }, + start: function(testName) { + if (!this.log) return; + this.testName = testName; + this.lastLogLine = document.createElement('tr'); + this.statusCell = document.createElement('td'); + this.nameCell = document.createElement('td'); + this.nameCell.appendChild(document.createTextNode(testName)); + this.messageCell = document.createElement('td'); + this.lastLogLine.appendChild(this.statusCell); + this.lastLogLine.appendChild(this.nameCell); + this.lastLogLine.appendChild(this.messageCell); + this.loglines.appendChild(this.lastLogLine); + }, + finish: function(status, summary) { + if (!this.log) return; + this.lastLogLine.className = status; + this.statusCell.innerHTML = status; + this.messageCell.innerHTML = this._toHTML(summary); + }, + message: function(message) { + if (!this.log) return; + this.messageCell.innerHTML = this._toHTML(message); + }, + summary: function(summary) { + if (!this.log) return; + this.logsummary.innerHTML = this._toHTML(summary); + }, + _createLogTable: function() { + this.log.innerHTML = + '
    ' + + '' + + '' + + '' + + '
    StatusTestMessage
    '; + this.logsummary = $('logsummary') + this.loglines = $('loglines'); + }, + _toHTML: function(txt) { + return txt.escapeHTML().replace(/\n/g,"
    "); + } +} + +Test.Unit.Runner = Class.create(); +Test.Unit.Runner.prototype = { + initialize: function(testcases) { + this.options = Object.extend({ + testLog: 'testlog' + }, arguments[1] || {}); + this.options.resultsURL = this.parseResultsURLQueryParameter(); + if (this.options.testLog) { + this.options.testLog = $(this.options.testLog) || null; + } + if(this.options.tests) { + this.tests = []; + for(var i = 0; i < this.options.tests.length; i++) { + if(/^test/.test(this.options.tests[i])) { + this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"])); + } + } + } else { + if (this.options.test) { + this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])]; + } else { + this.tests = []; + for(var testcase in testcases) { + if(/^test/.test(testcase)) { + this.tests.push(new Test.Unit.Testcase(testcase, testcases[testcase], testcases["setup"], testcases["teardown"])); + } + } + } + } + this.currentTest = 0; + this.logger = new Test.Unit.Logger(this.options.testLog); + setTimeout(this.runTests.bind(this), 1000); + }, + parseResultsURLQueryParameter: function() { + return window.location.search.parseQuery()["resultsURL"]; + }, + // Returns: + // "ERROR" if there was an error, + // "FAILURE" if there was a failure, or + // "SUCCESS" if there was neither + getResult: function() { + var hasFailure = false; + for(var i=0;i 0) { + return "ERROR"; + } + if (this.tests[i].failures > 0) { + hasFailure = true; + } + } + if (hasFailure) { + return "FAILURE"; + } else { + return "SUCCESS"; + } + }, + postResults: function() { + if (this.options.resultsURL) { + new Ajax.Request(this.options.resultsURL, + { method: 'get', parameters: 'result=' + this.getResult(), asynchronous: false }); + } + }, + runTests: function() { + var test = this.tests[this.currentTest]; + if (!test) { + // finished! + this.postResults(); + this.logger.summary(this.summary()); + return; + } + if(!test.isWaiting) { + this.logger.start(test.name); + } + test.run(); + if(test.isWaiting) { + this.logger.message("Waiting for " + test.timeToWait + "ms"); + setTimeout(this.runTests.bind(this), test.timeToWait || 1000); + } else { + this.logger.finish(test.status(), test.summary()); + this.currentTest++; + // tail recursive, hopefully the browser will skip the stackframe + this.runTests(); + } + }, + summary: function() { + var assertions = 0; + var failures = 0; + var errors = 0; + var messages = []; + for(var i=0;i 0) return 'failed'; + if (this.errors > 0) return 'error'; + return 'passed'; + }, + assert: function(expression) { + var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"'; + try { expression ? this.pass() : + this.fail(message); } + catch(e) { this.error(e); } + }, + assertEqual: function(expected, actual) { + var message = arguments[2] || "assertEqual"; + try { (expected == actual) ? this.pass() : + this.fail(message + ': expected "' + Test.Unit.inspect(expected) + + '", actual "' + Test.Unit.inspect(actual) + '"'); } + catch(e) { this.error(e); } + }, + assertNotEqual: function(expected, actual) { + var message = arguments[2] || "assertNotEqual"; + try { (expected != actual) ? this.pass() : + this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); } + catch(e) { this.error(e); } + }, + assertNull: function(obj) { + var message = arguments[1] || 'assertNull' + try { (obj==null) ? this.pass() : + this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); } + catch(e) { this.error(e); } + }, + assertHidden: function(element) { + var message = arguments[1] || 'assertHidden'; + this.assertEqual("none", element.style.display, message); + }, + assertNotNull: function(object) { + var message = arguments[1] || 'assertNotNull'; + this.assert(object != null, message); + }, + assertInstanceOf: function(expected, actual) { + var message = arguments[2] || 'assertInstanceOf'; + try { + (actual instanceof expected) ? this.pass() : + this.fail(message + ": object was not an instance of the expected type"); } + catch(e) { this.error(e); } + }, + assertNotInstanceOf: function(expected, actual) { + var message = arguments[2] || 'assertNotInstanceOf'; + try { + !(actual instanceof expected) ? this.pass() : + this.fail(message + ": object was an instance of the not expected type"); } + catch(e) { this.error(e); } + }, + _isVisible: function(element) { + element = $(element); + if(!element.parentNode) return true; + this.assertNotNull(element); + if(element.style && Element.getStyle(element, 'display') == 'none') + return false; + + return this._isVisible(element.parentNode); + }, + assertNotVisible: function(element) { + this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1])); + }, + assertVisible: function(element) { + this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1])); + } +} + +Test.Unit.Testcase = Class.create(); +Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), { + initialize: function(name, test, setup, teardown) { + Test.Unit.Assertions.prototype.initialize.bind(this)(); + this.name = name; + this.test = test || function() {}; + this.setup = setup || function() {}; + this.teardown = teardown || function() {}; + this.isWaiting = false; + this.timeToWait = 1000; + }, + wait: function(time, nextPart) { + this.isWaiting = true; + this.test = nextPart; + this.timeToWait = time; + }, + run: function() { + try { + try { + if (!this.isWaiting) this.setup.bind(this)(); + this.isWaiting = false; + this.test.bind(this)(); + } finally { + if(!this.isWaiting) { + this.teardown.bind(this)(); + } + } + } + catch(e) { this.error(e); } + } +}); \ No newline at end of file diff --git a/webcit/static/util.js b/webcit/static/util.js new file mode 100644 index 000000000..9170a95d3 --- /dev/null +++ b/webcit/static/util.js @@ -0,0 +1,429 @@ +// small but works-for-me stuff for testing javascripts +// not ready for "production" use + +Object.inspect = function(obj) { + var info = []; + + if(typeof obj in ["string","number"]) { + return obj; + } else { + for(property in obj) + if(typeof obj[property]!="function") + info.push(property + ' => ' + + (typeof obj[property] == "string" ? + '"' + obj[property] + '"' : + obj[property])); + } + + return ("'" + obj + "' #" + typeof obj + + ": {" + info.join(", ") + "}"); +} + +// borrowed from http://www.schuerig.de/michael/javascript/stdext.js +// Copyright (c) 2005, Michael Schuerig, michael@schuerig.de + +Array.flatten = function(array, excludeUndefined) { + if (excludeUndefined === undefined) { + excludeUndefined = false; + } + var result = []; + var len = array.length; + for (var i = 0; i < len; i++) { + var el = array[i]; + if (el instanceof Array) { + var flat = el.flatten(excludeUndefined); + result = result.concat(flat); + } else if (!excludeUndefined || el != undefined) { + result.push(el); + } + } + return result; +}; + +if (!Array.prototype.flatten) { + Array.prototype.flatten = function(excludeUndefined) { + return Array.flatten(this, excludeUndefined); + } +} + +/*--------------------------------------------------------------------------*/ + +var Builder = { + node: function(elementName) { + var element = document.createElement('div'); + element.innerHTML = + "<" + elementName + ">"; + + // attributes (or text) + if(arguments[1]) + if(this._isStringOrNumber(arguments[1]) || + (arguments[1] instanceof Array)) { + this._children(element.firstChild, arguments[1]); + } else { + var attrs = this._attributes(arguments[1]); + if(attrs.length) + element.innerHTML = "<" +elementName + " " + + attrs + ">"; + } + + // text, or array of children + if(arguments[2]) + this._children(element.firstChild, arguments[2]); + + return element.firstChild; + }, + _text: function(text) { + return document.createTextNode(text); + }, + _attributes: function(attributes) { + var attrs = []; + for(attribute in attributes) + attrs.push((attribute=='className' ? 'class' : attribute) + + '="' + attributes[attribute].toString().escapeHTML() + '"'); + return attrs.join(" "); + }, + _children: function(element, children) { + if(typeof children=='object') { // array can hold nodes and text + children = children.flatten(); + for(var i = 0; i 0 ? ' ' : '') + arguments[i]; + } + }, + + // returns true if all given classes exist in said element + has: function(element) { + element = $(element); + if(!element || !element.className) return false; + var regEx; + for(var i = 1; i < arguments.length; i++) { + if((typeof arguments[i] == 'object') && + (arguments[i].constructor == Array)) { + for(var j = 0; j < arguments[i].length; j++) { + regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)"); + if(!regEx.test(element.className)) return false; + } + } else { + regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)"); + if(!regEx.test(element.className)) return false; + } + } + return true; + }, + + // expects arrays of strings and/or strings as optional paramters + // Element.Class.has_any(element, ['classA','classB','classC'], 'classD') + has_any: function(element) { + element = $(element); + if(!element || !element.className) return false; + var regEx; + for(var i = 1; i < arguments.length; i++) { + if((typeof arguments[i] == 'object') && + (arguments[i].constructor == Array)) { + for(var j = 0; j < arguments[i].length; j++) { + regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)"); + if(regEx.test(element.className)) return true; + } + } else { + regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)"); + if(regEx.test(element.className)) return true; + } + } + return false; + }, + + childrenWith: function(element, className) { + var children = $(element).getElementsByTagName('*'); + var elements = new Array(); + + for (var i = 0; i < children.length; i++) { + if (Element.Class.has(children[i], className)) { + elements.push(children[i]); + break; + } + } + + return elements; + } +} + +/*--------------------------------------------------------------------------*/ + +String.prototype.parseQuery = function() { + var str = this; + if(str.substring(0,1) == '?') { + str = this.substring(1); + } + var result = {}; + var pairs = str.split('&'); + for(var i = 0; i < pairs.length; i++) { + var pair = pairs[i].split('='); + result[pair[0]] = pair[1]; + } + return result; +} \ No newline at end of file diff --git a/webcit/static/wclib.js b/webcit/static/wclib.js index c42449997..90266777e 100644 --- a/webcit/static/wclib.js +++ b/webcit/static/wclib.js @@ -41,6 +41,6 @@ function hide_imsg_popup_old() { } function hide_imsg_popup() { - new Effect.FadeTo('important_message', 0.0, 1000, 20, {complete:function() { hide_imsg_popup_old(); }} ); - // we still do it the old way afterwards, just in case the browser didn't dazzle us enough + // new Effect.FadeTo('important_message', 0.0, 1000, 20, {complete:function() { hide_imsg_popup_old(); }} ); + hide_imsg_popup_old(); // Do it the old way for now, to avoid library conflicts }