From: Michael M Slusarz Date: Tue, 31 Aug 2010 22:28:39 +0000 (-0600) Subject: Move JS files dependent on horde/Core into the package X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=03cb6fc1a115dcdee0fecfb9d38b80c81e5a0693;p=horde.git Move JS files dependent on horde/Core into the package --- diff --git a/framework/Core/js/addressbooksprefs.js b/framework/Core/js/addressbooksprefs.js new file mode 100644 index 000000000..3600b6c17 --- /dev/null +++ b/framework/Core/js/addressbooksprefs.js @@ -0,0 +1,71 @@ +/** + * Provides the javascript for managing addressbooks. + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Horde + * @package Core + */ + +var HordeAddressbooksPrefs = { + + // Variables set by other code: fields, nonetext + + updateSearchFields: function() + { + var tmp, + sv = $F('selected_sources'), + sf = $('search_fields_select'); + + sf.childElements().invoke('remove'); + + if (sv.size() == 1) { + tmp = this.fields.get(sv.first()); + tmp.entries.each(function(o) { + var opt = new Option(o.label, o.name); + if (tmp.selected.include(o.name)) { + opt.selected = true; + } + sf.insert(opt); + }); + } else { + tmp = new Option(this.nonetext, ''); + tmp.disabled = true; + sf.insert(tmp); + } + }, + + changeSearchFields: function() + { + var tmp, + out = $H(), + sv = $F('selected_sources'); + + if (sv.size() == 1) { + tmp = this.fields.get(sv.first()); + tmp.selected = $F('search_fields_select'); + this.fields.set(sv.first(), tmp); + + this.fields.each(function(f) { + out.set(f.key, f.value.selected); + }); + + $('search_fields').setValue(Object.toJSON(out)); + } + }, + + onDomLoad: function() + { + this.fields = $H(this.fields); + + this.updateSearchFields(); + + $('search_fields_select').observe('change', this.changeSearchFields.bind(this)); + $('selected_sources').observe('change', this.updateSearchFields.bind(this)); + $('selected_sources').observe('HordeSourceSelectPrefs:remove', this.updateSearchFields.bind(this)); + } + +}; + +document.observe('dom:loaded', HordeAddressbooksPrefs.onDomLoad.bind(HordeAddressbooksPrefs)); diff --git a/framework/Core/js/alarmprefs.js b/framework/Core/js/alarmprefs.js new file mode 100644 index 000000000..1a93b9d09 --- /dev/null +++ b/framework/Core/js/alarmprefs.js @@ -0,0 +1,37 @@ +/** + * Provides the javascript for managing alarms. + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Horde + * @package Core + */ + +var HordeAlarmPrefs = { + + // Variables defaulting to null: pref + + updateParams: function() + { + [ 'notify', 'mail', 'sms' ].each(function(method) { + var p = $(method + 'Params'); + if (p) { + if ($(this.pref).getValue().include(method)) { + p.show(); + } else { + p.hide(); + } + } + }, this); + }, + + onDomLoad: function() + { + $(this.pref).observe('change', this.updateParams.bind(this)); + this.updateParams(); + } + +}; + +document.observe('dom:load', HordeAlarmPrefs.onDomLoad.bind(HordeAlarmPrefs)); diff --git a/framework/Core/js/autocomplete.js b/framework/Core/js/autocomplete.js new file mode 100644 index 000000000..6834be777 --- /dev/null +++ b/framework/Core/js/autocomplete.js @@ -0,0 +1,333 @@ +/** + * autocomplete.js - A javascript library which implements autocomplete. + * Requires prototype.js v1.6.0.2+, scriptaculous v1.8.0+ (effects.js), + * and keynavlist.js. + * + * Adapted from script.aculo.us controls.js v1.8.0 + * (c) 2005-2007 Thomas Fuchs, Ivan Krstic, and Jon Tirsen + * Contributors: Richard Livsey, Rahul Bhargava, Rob Wills + * http://script.aculo.us/ + * + * The original script was freely distributable under the terms of an + * MIT-style license. + * + * Usage: + * ------ + * TODO: options = autoSelect, frequency, minChars, onSelect, onShow, onType, + * paramName, tokens + * + * Copyright 2007-2010 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Horde + * @package Core + */ + +var Autocompleter = {}; + +Autocompleter.Base = Class.create({ + baseInitialize: function(elt, opts) + { + this.elt = elt = $(elt); + this.changed = false; + this.observer = null; + this.oldval = $F(elt); + this.opts = Object.extend({ + frequency: 0.4, + indicator: null, + minChars: 1, + onSelect: Prototype.K, + onShow: Prototype.K, + onType: Prototype.K, + filterCallback: Prototype.K, + paramName: elt.readAttribute('name'), + tokens: [], + keydownObserver: this.elt + }, (this._setOptions ? this._setOptions(opts) : (opts || {}))); + + // Force carriage returns as token delimiters anyway + if (!this.opts.tokens.include('\n')) { + this.opts.tokens.push('\n'); + } + + elt.writeAttribute('autocomplete', 'off'); + elt.observe("keydown", this._onKeyDown.bindAsEventListener(this)); + }, + + _onKeyDown: function(e) + { + var a = document.activeElement; + + if (Object.isUndefined(a) || a == this.elt) { + switch (e.keyCode) { + case 0: + if (!Prototype.Browser.WebKit) { + break; + } + // Fall-through + + // Ignore events caught by KevNavList + case Event.KEY_DOWN: + case Event.KEY_ESC: + case Event.KEY_RETURN: + case Event.KEY_TAB: + case Event.KEY_UP: + return; + } + + this.changed = true; + + if (this.observer) { + clearTimeout(this.observer); + } + + this.observer = this.onObserverEvent.bind(this).delay(this.opts.frequency); + } + }, + + updateChoices: function(choices) + { + var a = document.activeElement, c = [], re; + + if (this.changed || + (Object.isUndefined(a) || a != this.elt)) { + return; + } + + if (this.opts.indicator) { + $(this.opts.indicator).hide(); + } + + choices = this.opts.filterCallback(choices); + if (!choices.size()) { + if (this.knl) { + this.knl.hide(); + } + this.getNewVal(this.lastentry); + } else if (choices.size() == 1 && this.opts.autoSelect) { + this.onSelect(choices.first()); + if (this.knl) { + this.knl.hide(); + } + } else { + re = new RegExp("(" + this.getToken() + ")", "i"); + + choices.each(function(n) { + c.push({ + l: n.escapeHTML().gsub(re, '#{1}'), + v: n + }); + }); + + if (!this.knl) { + this.knl = new KeyNavList(this.elt, { onChoose: this.onSelect.bind(this), + onShow: this.opts.onShow.bind(this), + domParent: this.opts.domParent, + keydownObserver: this.opts.keydownObserver}); + } + + this.knl.show(c); + } + }, + + onObserverEvent: function() + { + this.changed = false; + + var entry = this.getToken(); + + if (entry.length >= this.opts.minChars) { + entry = this.opts.onType(entry); + } + + if (entry.length) { + if (this.opts.indicator) { + $(this.opts.indicator).show(); + } + this.lastentry = entry; + this.getUpdatedChoices(entry); + } else if (this.knl) { + this.knl.hide(); + } + }, + + getToken: function() + { + var bounds = this.getTokenBounds(); + return $F(this.elt).substring(bounds[0], bounds[1]).strip(); + }, + + getTokenBounds: function() + { + var diff, i, index, l, offset, tp, + t = this.opts.tokens, + value = $F(this.elt), + nextTokenPos = value.length, + prevTokenPos = -1, + boundary = Math.min(nextTokenPos, this.oldval.length); + + if (value.strip().empty()) { + return [ -1, 0 ]; + } + + diff = boundary; + for (i = 0; i < boundary; ++i) { + if (value[i] != this.oldval[i]) { + diff = i; + break; + } + } + + offset = (diff == this.oldval.length ? 1 : 0); + + for (index = 0, l = t.length; index < l; ++index) { + tp = value.lastIndexOf(t[index], diff + offset - 1); + if (tp > prevTokenPos) { + prevTokenPos = tp; + } + tp = value.indexOf(t[index], diff + offset); + if (tp != -1 && tp < nextTokenPos) { + nextTokenPos = tp; + } + } + return [ prevTokenPos + 1, nextTokenPos ]; + }, + + onSelect: function(entry) + { + if (entry) { + this.elt.setValue(this.opts.onSelect(this.getNewVal(entry))).focus(); + if (this.knl) { + this.knl.markSelected(); + } + } + }, + + getNewVal: function(entry) + { + var bounds = this.getTokenBounds(), newval, v, ws; + + if (bounds[0] == -1) { + newval = entry; + } else { + v = $F(this.elt); + newval = v.substr(0, bounds[0]); + ws = v.substr(bounds[0]).match(/^\s+/); + if (ws) { + newval += ws[0]; + } + newval += entry + v.substr(bounds[1]); + } + + this.oldval = newval; + + return newval; + } + +}); + +Ajax.Autocompleter = Class.create(Autocompleter.Base, { + + initialize: function(element, url, opts) + { + this.baseInitialize(element, opts); + this.opts = Object.extend(this.opts, { + asynchronous: true, + onComplete: this._onComplete.bind(this), + defaultParams: $H(this.opts.parameters) + }); + this.url = url; + this.cache = $H(); + }, + + getUpdatedChoices: function(t) + { + var p, + o = this.opts, + c = this.cache.get(t); + + if (c) { + this.updateChoices(c); + } else { + p = Object.clone(o.defaultParams); + p.set(o.paramName, t); + o.parameters = p.toQueryString(); + new Ajax.Request(this.url, o); + } + }, + + _onComplete: function(request) + { + this.updateChoices(this.cache.set(this.getToken(), request.responseJSON)); + } +}); + +Autocompleter.Local = Class.create(Autocompleter.Base, { + + initialize: function(element, arr, opts) + { + this.baseInitialize(element, opts); + this.opts.arr = arr; + }, + + getUpdatedChoices: function(entry) + { + var choices, + csort = [], + entry_len = entry.length, + i = 0, + o = this.opts; + + if (o.ignoreCase) { + entry = entry.toLowerCase(); + } + + choices = o.arr.findAll(function(t) { + if (i == o.choices) { + throw $break; + } + + if (o.ignoreCase) { + t = t.toLowerCase(); + } + t = t.unescapeHTML(); + + var pos = t.indexOf(entry); + if (pos != -1 && + ((pos == 0 && t.length != entry_len) || + (entry_len >= o.partialChars && + o.partialSearch && + (o.fullSearch || /\s/.test(t.substr(pos - 1, 1)))))) { + ++i; + return true; + } + return false; + }, this); + + if (o.score) { + choices.each(function(term) { + csort.push({ s: LiquidMetal.score(term, entry), t: term }); + }.bind(this)); + // Sort the terms + csort.sort(function(a, b) { return b.s - a.s; }); + choices = csort.pluck('t'); + } + + this.updateChoices(choices); + }, + + _setOptions: function(opts) + { + return Object.extend({ + choices: 10, + fullSearch: false, + ignoreCase: true, + partialChars: 2, + partialSearch: true, + score: false + }, opts || {}); + } + +}); diff --git a/framework/Core/js/calendar.js b/framework/Core/js/calendar.js new file mode 100644 index 000000000..fc390ec4a --- /dev/null +++ b/framework/Core/js/calendar.js @@ -0,0 +1,335 @@ +/** + * Horde javascript calendar widget. + * + * Custom Events: + * -------------- + * Horde_Calendar:select + * params: Date object + * Fired when a date is selected. + * + * Horde_Calendar:selectMonth + * params: Date object + * Fired when a month is selected. + * + * Horde_Calendar:selectWeek + * params: Date object + * Fired when a week is selected. + * + * Horde_Calendar:selectYear + * params: Date object + * Fired when a year is selected. + * + * @category Horde + * @package Core + */ + +var Horde_Calendar = +{ + // Variables set externally: click_month, click_week, click_year, + // firstDayOfWeek, fullweekdays, months, + // weekdays + // Variables defaulting to null: date, month, openDate, trigger, year + + open: function(trigger, data) + { + var date = data ? data : new Date(); + + this.openDate = date.getTime(); + this.trigger = $(trigger); + this.draw(this.openDate, true); + }, + + /** + * Days in the month (month is a zero-indexed javascript month). + */ + daysInMonth: function(month, year) + { + switch (month) { + case 3: + case 5: + case 8: + case 10: + return 30; + + case 1: + return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) + ? 29 + : 28; + + default: + return 31; + } + }, + + weeksInMonth: function(month, year) + { + var firstWeekDays, weeks, + firstOfMonth = (new Date(year, month, 1)).getDay(); + + if ((this.firstDayOfWeek == 1 && firstOfMonth == 0) || + (this.firstDayOfWeek == 0 && firstOfMonth == 6)) { + firstWeekDays = 7 - firstOfMonth + this.firstDayOfWeek; + weeks = 1; + } else { + firstWeekDays = this.firstDayOfWeek - firstOfMonth; + weeks = 0; + } + + firstWeekDays %= 7; + + return Math.ceil((this.daysInMonth(month, year) - firstWeekDays) / 7) + weeks; + }, + + // http://javascript.about.com/library/blstdweek.htm + weekOfYear: function(d) + { + var newYear = new Date(d.getFullYear(), 0, 1), + day = newYear.getDay(); + if (this.firstDayOfWeek != 0) { + day = ((day + (7 - this.firstDayOfWeek)) % 7); + } + return Math.ceil((((d - newYear) / 86400000) + day + 1) / 7); + }, + + draw: function(timestamp, init) + { + this.date = new Date(timestamp); + this.month = this.date.getMonth(); + this.year = this.date.getFullYear(); + + var cell, i, i_max, p, row, startOfView, vp, + count = 1, + div = $('hordeCalendar'), + tbody = div.down('TBODY'), + + // Requires init above + daysInMonth = this.daysInMonth(this.month, this.year), + daysInView = this.weeksInMonth(this.month, this.year) * 7, + firstOfMonth = (new Date(this.year, this.month, 1)).getDay(), + + // Cache today and open date. + today = new Date(), + today_year = today.getFullYear(), + today_month = today.getMonth(), + today_day = today.getDate(), + open = new Date(this.openDate), + open_year = open.getFullYear(), + open_month = open.getMonth(), + open_day = open.getDate(); + + if (this.firstDayOfWeek == 0) { + startOfView = 1 - firstOfMonth; + } else { + // @TODO Adjust this for days other than Monday. + startOfView = (firstOfMonth == 0) + ? -5 + : 2 - firstOfMonth; + } + + div.down('.hordeCalendarYear').update(this.year); + div.down('.hordeCalendarMonth').update(this.months[this.month]); + tbody.update(''); + + for (i = startOfView, i_max = startOfView + daysInView; i < i_max; ++i) { + if (count == 1) { + row = new Element('TR'); + if (this.click_week) { + row.insert(new Element('TD').insert(new Element('A', { className: 'hordeCalendarWeek' }).insert(this.weekOfYear(new Date(this.year, this.month, (i < 1) ? 1 : i))))); + } + } + + cell = new Element('TD'); + + if (i < 1 || i > daysInMonth) { + cell.addClassName('hordeCalendarEmpty'); + row.insert(cell); + } else { + if (today_year == this.year && + today_month == this.month && + today_day == i) { + cell.writeAttribute({ className: 'hordeCalendarToday' }); + } + + if (open_year == this.year && + open_month == this.month && + open_day == i) { + cell.addClassName('hordeCalendarCurrent'); + } + + row.insert(cell.insert(new Element('A', { className: 'hordeCalendarDay', href: '#' }).insert(i))); + } + + if (count == 7) { + tbody.insert(row); + count = 0; + } + ++count; + } + + if (count > 1) { + tbody.insert(row); + } + + div.show(); + + // Position the popup every time in case of a different input, + // window sizing changes, etc. + if (init) { + p = this.trigger.cumulativeOffset(); + vp = document.viewport.getDimensions(); + + if (p.left + div.offsetWidth > vp.width) { + div.setStyle({ left: (vp.width - 10 - div.offsetWidth) + 'px' }); + } else { + div.setStyle({ left: p.left + 'px' }); + } + + if (p.top + div.offsetHeight > vp.height) { + div.setStyle({ top: (vp.height - 10 - div.offsetHeight) + 'px' }); + } else { + div.setStyle({ top: p.top + 'px' }); + } + } + + // IE 6 only. + if (Prototype.Browser.IE && !window.XMLHttpRequest) { + iframe = $('hordeCalendarIframe'); + if (!iframe) { + iframe = new Element('IFRAME', { name: 'hordeCalendarIframe', id: 'hordeCalendarIframe', src: 'javascript:false;', scrolling: 'no', frameborder: 0 }).hide(); + $(document.body).insert(iframe); + } + iframe.clonePosition(div).setStyle({ + position: 'absolute', + display: 'block', + zIndex: 1 + }); + } + + div.setStyle({ zIndex: 999 }); + }, + + hideCal: function() + { + var iefix = $('hordeCalendarIframe'); + + $('hordeCalendar').hide(); + + if (iefix) { + iefix.hide(); + } + }, + + changeYear: function(by) + { + this.draw((new Date(this.date.getFullYear() + by, this.date.getMonth(), 1)).getTime()); + }, + + changeMonth: function(by) + { + var newMonth = this.date.getMonth() + by, + newYear = this.date.getFullYear(); + + if (newMonth == -1) { + newMonth = 11; + newYear -= 1; + } + + this.draw((new Date(newYear, newMonth, 1)).getTime()); + }, + + init: function() + { + var i, link, row, + offset = this.click_week ? 1 : 0, + thead = new Element('THEAD'), + table = new Element('TABLE', { className: 'hordeCalendarPopup', cellSpacing: 0 }).insert(thead).insert(new Element('TBODY')); + + // Title bar. + link = new Element('A', { href: '#', className: 'hordeCalendarClose rightAlign' }).insert('x'); + thead.insert(new Element('TR').insert(new Element('TD', { colspan: 6 + offset })).insert(new Element('TD').insert(link))); + + // Year. + row = new Element('TR'); + link = new Element('A', { className: 'hordeCalendarPrevYear', href: '#' }).insert('«'); + row.insert(new Element('TD').insert(link)); + + tmp = new Element('TD', { align: 'center', colspan: 5 + offset }); + if (this.click_year) { + tmp.insert(new Element('A', { className: 'hordeCalendarYear' })); + } else { + tmp.addClassName('hordeCalendarYear'); + } + row.insert(tmp); + + link = new Element('A', { className: 'hordeCalendarNextYear', href: '#' }).insert('»'); + row.insert(new Element('TD', { className: 'rightAlign' }).insert(link)); + + thead.insert(row); + + // Month name. + row = new Element('TR'); + link = new Element('A', { className: 'hordeCalendarPrevMonth', href: '#' }).insert('«'); + row.insert(new Element('TD').insert(link)); + + tmp = new Element('TD', { align: 'center', colspan: 5 + offset }); + if (this.click_year) { + tmp.insert(new Element('A', { className: 'hordeCalendarMonth' })); + } else { + tmp.addClassName('hordeCalendarMonth'); + } + row.insert(tmp); + + link = new Element('A', { className: 'hordeCalendarNextMonth', href: '#' }).insert('»'); + row.insert(new Element('TD', { className: 'rightAlign' }).insert(link)); + + thead.insert(row); + + // Weekdays. + row = new Element('TR'); + if (this.click_week) { + row.insert(new Element('TH')); + } + for (i = 0; i < 7; ++i) { + row.insert(new Element('TH').insert(this.weekdays[(i + this.firstDayOfWeek) % 7])); + } + thead.insert(row); + + $(document.body).insert({ bottom: new Element('DIV', { id: 'hordeCalendar' }).setStyle({ position: 'absolute', 'z-index': 999 }).hide().insert(table) }); + + $('hordeCalendar').observe('click', this.clickHandler.bindAsEventListener(this)); + }, + + clickHandler: function(e) + { + var elt = e.element(); + + if (elt.hasClassName('hordeCalendarDay')) { + this.hideCal(); + this.trigger.fire('Horde_Calendar:select', new Date(this.year, this.month, parseInt(e.element().textContent, 10))); + } else if (elt.hasClassName('hordeCalendarClose')) { + this.hideCal(); + } else if (elt.hasClassName('hordeCalendarPrevYear')) { + this.changeYear(-1); + } else if (elt.hasClassName('hordeCalendarNextYear')) { + this.changeYear(1); + } else if (elt.hasClassName('hordeCalendarPrevMonth')) { + this.changeMonth(-1); + } else if (elt.hasClassName('hordeCalendarNextMonth')) { + this.changeMonth(1); + } else if (this.click_year && elt.hasClassName('hordeCalendarYear')) { + this.trigger.fire('Horde_Calendar:selectYear', new Date(this.year, this.month, 1)); + this.hideCal(); + } else if (this.click_month && elt.hasClassName('hordeCalendarMonth')) { + this.trigger.fire('Horde_Calendar:selectMonth', new Date(this.year, this.month, 1)); + this.hideCal(); + } else if (this.click_week && elt.hasClassName('hordeCalendarWeek')) { + this.trigger.fire('Horde_Calendar:selectWeek', new Date(this.year, this.month, elt.up('TR').down('A.hordeCalendarDay').textContent)); + this.hideCal(); + } + + e.stop(); + } + +}; + +document.observe('dom:loaded', Horde_Calendar.init.bind(Horde_Calendar)); diff --git a/framework/Core/js/hordetree.js b/framework/Core/js/hordetree.js new file mode 100644 index 000000000..aba38e11b --- /dev/null +++ b/framework/Core/js/hordetree.js @@ -0,0 +1,512 @@ +/** + * Provides the javascript class to create dynamic trees. + * + * Optionally uses the Horde_Tooltip class (tooltips.js). + * + * Custom Events + * ------------- + * The 'memo' property of the Event object contains the original event object. + * + * 'Horde_Tree:expand' + * Fired when a tree element is expanded. + * + * 'Horde_Tree:collapse' + * Fired when a tree element is collapsed. + * + * Copyright 2003-2010 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @author Marko Djukic + * @author Michael Slusarz + * @category Horde + * @package Core + */ + +var Horde_Tree = Class.create({ + + initialize: function(opts) + { + this.opts = opts; + + if (this.opts.initTree) { + this.renderTree(this.opts.initTree.nodes, this.opts.initTree.root_nodes, this.opts.initTree.is_static); + this.opts.initTree = null; + } + + $(this.opts.target).observe('click', this._onClick.bindAsEventListener(this)); + + this.opts.ie6 = (navigator.userAgent.toLowerCase().substr(25,6)=="msie 6"); + }, + + renderTree: function(nodes, rootNodes, renderStatic) + { + this.nodes = nodes; + this.rootNodes = rootNodes; + this.renderStatic = renderStatic; + this.dropline = []; + this.output = document.createDocumentFragment(); + + this._buildHeader(); + + this.rootNodes.each(function(r) { + this.buildTree(r, this.output); + }, this); + + $(this.opts.target).update(''); + $(this.opts.target).appendChild(this.output); + + if (this.opts.ie6) { + /* Correct for frame scrollbar in IE6 by determining if a scrollbar + * is present, and if not readjusting the marginRight property to + * 0. See http://www.xs4all.nl/~ppk/js/doctypes.html for why this + * works */ + if (document.documentElement.clientHeight == document.documentElement.offsetHeight) { + // no scrollbar present, take away extra margin + $(document.body).setStyle({ marginRight: 0 }); + } else { + $(document.body).setStyle({ marginRight: '15px' }); + } + } + + // If using alternating row shading, work out correct shade. + if (this.opts.options.alternate) { + this.stripe(); + } + + if (window.Horde_Tooltips) { + window.Horde_ToolTips.attachBehavior(); + } + }, + + _buildHeader: function() + { + if (this.opts.options.hideHeaders || + !this.opts.header.size()) { + return; + } + + var div = new Element('DIV'); + + this.opts.header.each(function(h) { + var tmp = new Element('SPAN').insert(h.html ? h.html : ' '); + + if (h['class']) { + tmp.addClassName(h['class']); + } + + div.appendChild(tmp); + }, this); + + this.output.appendChild(div); + }, + + // Recursive function to walk through the tree array and build + // the output. + buildTree: function(nodeId, p) + { + var last_subnode, tmp, + node = this.nodes[nodeId]; + + this.buildLine(nodeId, p); + + if (!Object.isUndefined(node.children)) { + last_subnode = node.children.last(); + tmp = new Element('DIV', { id: 'nodeChildren_' + nodeId }); + [ tmp ].invoke(node.expanded ? 'show' : 'hide'); + + node.children.each(function(c) { + if (c == last_subnode) { + this.nodes[c].node_last = true; + } + this.buildTree(c, tmp); + }, this); + + p.appendChild(tmp); + } + }, + + buildLine: function(nodeId, p) + { + var div, label, tmp, + column = 0, + node = this.nodes[nodeId]; + + div = new Element('DIV', { className: 'treeRow' }); + if (node['class']) { + div.addClassName(node['class']); + } + + // If we have headers, track which logical "column" we're in for + // any given cell of content. + if (node.extra && node.extra[0]) { + node.extra[0].each(function(n) { + div.insert(this._divClass(new Element('SPAN').update(n), column++)); + }, this); + } + + for (; column < this.opts.extraColsLeft; ++column) { + div.insert(this._divClass(new Element('SPAN').update(' '), column)); + } + + div.insert(this._divClass(new Element('SPAN'), column)); + + tmp = document.createDocumentFragment(); + for (i = Number(this.renderStatic); i < node.indent; ++i) { + tmp.appendChild(new Element('SPAN').addClassName('treeImg').addClassName( + 'treeImg' + ((this.dropline[i] && this.opts.options.lines) + ? this.opts.imgLine + : this.opts.imgBlank) + )); + } + + tmp.appendChild(this._setNodeToggle(nodeId)); + + if (node.url) { + label = new Element('A', { href: node.url }).insert( + this._setNodeIcon(nodeId) + ).insert( + node.label + ); + + if (node.urlclass) { + label.addClassName(node.urlclass); + } else if (this.opts.options.urlclass) { + label.addClassName(this.opts.options.urlclass); + } + + if (node.title) { + label.writeAttribute('title', node.title); + } + + if (node.target) { + label.writeAttribute('target', node.target); + } else if (this.opts.options.target) { + label.writeAttribute('target', this.opts.options.target); + } + + //if (node.onclick) { + // label.push(' onclick="' + node.onclick + '"'); + //} + + label = label.wrap('SPAN'); + } else { + label = new Element('SPAN').addClassName('toggle').insert( + this._setNodeIcon(nodeId) + ).insert( + node.label + ); + } + + if (this.opts.options.multiline) { + div.insert(new Element('TABLE').insert( + new Element('TR').insert( + new Element('TD').appendChild(tmp) + ).insert( + new Element('TD').insert(label) + ) + )); + } else { + div.appendChild(tmp); + div.insert(label) + } + + ++column; + + if (node.extra && node.extra[1]) { + node.extra[1].each(function(n) { + div.insert(this._divClass(new Element('SPAN').update(n), column++)); + }, this); + } + + for (; column < this.opts.extraColsRight; ++column) { + div.insert(this._divClass(new Element('SPAN').update(' '), column)); + } + + p.appendChild(div); + }, + + _divClass: function(div, c) + { + if (this.opts.header[c] && this.opts.header[c]['class']) { + div.addClassName(this.opts.header[c]['class']); + } + + return div; + }, + + _setNodeToggle: function(nodeId) + { + var node = this.nodes[nodeId]; + + if (node.indent == '0') { + // Top level with children. + this.dropline[0] = false; + + if (this.renderStatic) { + return ''; + } + + switch (this._getLineType(nodeId)) { + case 1: + case 2: + this.dropline[0] = true; + break; + } + } else { + this.dropline[node.indent] = !node.node_last; + } + + return new Element('SPAN', { id: "nodeToggle_" + nodeId }).addClassName('treeToggle').addClassName('treeImg').addClassName('treeImg' + this._getNodeToggle(nodeId)); + }, + + _getNodeToggle: function(nodeId) + { + var type, + node = this.nodes[nodeId]; + + if (node.indent == '0') { + if (this.renderStatic) { + return ''; + } + + if (!this.opts.options.lines) { + return this.opts.imgBlank; + } + + type = this._getLineType(nodeId); + + if (node.children) { + // Top level with children. + if (node.expanded) { + return type + ? ((type == 2) ? this.opts.imgMinus : this.opts.imgMinusBottom) + : this.opts.imgMinusOnly; + } + + return type + ? ((type == 2) ? this.opts.imgPlus : this.opts.imgPlusBottom) + : this.opts.imgPlusOnly; + } + + switch (type) { + case 0: + return this.opts.imgNullOnly; + + case 1: + return this.opts.imgJoinTop; + + case 2: + return this.opts.imgJoin; + + case 3: + return this.opts.imgJoinBottom; + } + } + + if (node.children) { + // Node with children. + if (!node.node_last) { + // Not last node. + if (!this.opts.options.lines) { + return this.opts.imgBlank; + } else if (this.renderStatic) { + return this.opts.imgJoin; + } else if (node.expanded) { + return this.opts.imgMinus; + } + + return this.opts.imgPlus; + } + + // Last node. + if (!this.opts.options.lines) { + return this.opts.imgBlank; + } else if (this.renderStatic) { + return this.opts.imgJoinBottom; + } else if (node.expanded) { + return this.opts.imgMinusBottom; + } + + return this.opts.imgPlusBottom; + } + + // Node no children. + if (!node.node_last) { + // Not last node. + return this.opts.options.lines + ? this.opts.imgJoin + : this.opts.imgBlank; + } + + // Last node. + return this.opts.options.lines + ? this.opts.imgJoinBottom + : this.opts.imgBlank; + }, + + _getLineType: function(nodeId) + { + if (this.opts.options.lines_base && + this.rootNodes.size() > 1) { + switch (this.rootNodes.indexOf(nodeId)) { + case 0: + return 1; + + case (this.rootNodes.size() - 1): + return 3; + + default: + return 2; + } + } + + return 0; + }, + + _setNodeIcon: function(nodeId) + { + var img, + node = this.nodes[nodeId]; + + // Image. + if (node.icon) { + // Node has a user defined icon. + img = new Element('IMG', { id: "nodeIcon_" + nodeId, src: (node.iconopen && node.expanded ? node.iconopen : node.icon) }).addClassName('treeIcon') + } else { + img = new Element('SPAN', { id: "nodeIcon_" + nodeId }).addClassName('treeIcon'); + if (node.children) { + // Standard icon: node with children. + img.addClassName('treeImg' + (node.expanded ? this.opts.imgFolderOpen : this.opts.imgFolder)); + } else { + // Standard icon: node, no children. + img.addClassName('treeImg' + this.opts.imgLeaf); + } + } + + if (node.iconalt) { + img.writeAttribute('alt', node.iconalt); + } + + return img; + }, + + toggle: function(nodeId) + { + var icon, nodeToggle, toggle, children, + node = this.nodes[nodeId]; + + node.expanded = !node.expanded; + if (children = $('nodeChildren_' + nodeId)) { + children.setStyle({ display: node.expanded ? 'block' : 'none' }); + } + + // Toggle the node's icon if it has separate open and closed + // icons. + if (icon = $('nodeIcon_' + nodeId)) { + // Image. + if (node.icon) { + icon.writeAttribute('src', (node.expanded && node.iconopen) ? node.iconopen : node.icon); + } else { + // Use standard icon set. + icon.writeAttribute('src', node.expanded ? this.opts.imgFolderOpen : this.opts.imgFolder); + } + } + + // If using alternating row shading, work out correct shade. + if (this.opts.options.alternate) { + this.stripe(); + } + + if (toggle = $('nodeToggle_' + nodeId)) { + toggle.writeAttribute('class', 'treeToggle treeImg').addClassName('treeImg' + this._getNodeToggle(nodeId)); + } + + $(this.opts.target).fire(node.expanded ? 'Horde_Tree:expand' : 'Horde_Tree:collapse', nodeId); + + this.saveState(nodeId, node.expanded) + }, + + stripe: function() + { + var classes = [ 'rowEven', 'rowOdd' ], + i = 0; + + $(this.opts.target).select('DIV.treeRow').each(function(r) { + classes.each(r.removeClassName.bind(r)); + if (r.clientHeight) { + r.addClassName(classes[++i % 2]); + } + }); + }, + + saveState: function(nodeId, expanded) + { + if (this.opts.nocookie) { + return; + } + + var newCookie = '', + newNodes = [], + oldCookie = this._getCookie(this.opts.target + '_expanded'); + + if (expanded) { + // Expand requested so add to cookie. + newCookie = (oldCookie ? oldCookie + ',' : '') + nodeId; + } else { + // Collapse requested so remove from cookie. + oldCookie.split(',').each(function(n) { + if (n != nodeId) { + newNodes[newNodes.length] = n + } + }); + newCookie = newNodes.join(','); + } + + document.cookie = this.opts.target + '_expanded=exp' + escape(newCookie) + ';DOMAIN=' + this.opts.cookieDomain + ';PATH=' + this.opts.cookiePath + ';'; + }, + + _getCookie: function(name) + { + var end, + dc = document.cookie, + prefix = name + '=exp', + begin = dc.indexOf('; ' + prefix); + + if (begin == -1) { + begin = dc.indexOf(prefix); + if (begin != 0) { + return ''; + } + } else { + begin += 2; + } + + end = document.cookie.indexOf(';', begin); + if (end == -1) { + end = dc.length; + } + + return unescape(dc.substring(begin + prefix.length, end)); + }, + + _onClick: function(e) + { + var elt = e.element(), + id = elt.readAttribute('id'); + + if (elt.hasClassName('treeIcon')) { + elt = elt.up().previous(); + } else if (elt.hasClassName('toggle')) { + elt = elt.previous(); + } + + id = elt.readAttribute('id'); + if (id && id.startsWith('nodeToggle_')) { + this.toggle(id.substr(11)); + e.stop(); + } + } + +}); diff --git a/framework/Core/js/identityselect.js b/framework/Core/js/identityselect.js new file mode 100644 index 000000000..ea337ada6 --- /dev/null +++ b/framework/Core/js/identityselect.js @@ -0,0 +1,49 @@ +/** + * Horde identity selection javascript. + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Horde + * @package Core + */ + +var HordeIdentitySelect = { + + newChoice: function() + { + var identity = $('identity'), + id = Number($F(identity)); + + if (id < 0) { + identity.up('FORM').reset(); + identity.setValue(id); + return; + } + + this.identities[id].each(function(a) { + var field = $(a[0]); + + switch (a[1]) { + case "special": + identity.fire('HordeIdentitySelect:change', { + i: id, + pref: a[0] + }); + break; + + default: + field.setValue(a[2]); + break; + } + }); + }, + + onDomLoad: function() + { + $('identity').observe('change', this.newChoice.bind(this)); + } + +}; + +document.observe('dom:loaded', HordeIdentitySelect.onDomLoad.bind(HordeIdentitySelect)); diff --git a/framework/Core/js/liquidmetal.js b/framework/Core/js/liquidmetal.js new file mode 100644 index 000000000..bbb4a6187 --- /dev/null +++ b/framework/Core/js/liquidmetal.js @@ -0,0 +1,105 @@ +/* + * LiquidMetal, version: 0.1 (2009-02-05) + * + * A mimetic poly-alloy of Quicksilver's scoring algorithm, essentially + * LiquidMetal. + * + * For usage and examples, visit: + * http://github.com/rmm5t/liquidmetal + * + * Licensed under the MIT: + * http://www.opensource.org/licenses/mit-license.php + * + * Copyright (c) 2009, Ryan McGeary (ryanonjavascript -[at]- mcgeary [*dot*] org) + * Modified by the Horde Project to use compressibility. + * + * @category Horde + * @package Core + */ +var LiquidMetal = { + + SCORE_NO_MATCH: 0.0, + SCORE_MATCH: 1.0, + SCORE_TRAILING: 0.8, + SCORE_TRAILING_BUT_STARTED: 0.9, + SCORE_BUFFER: 0.85, + + score: function(str, abbr) + { + // Short circuits + if (abbr.length == 0) { + return this.SCORE_TRAILING; + } + if (abbr.length > str.length) { + return this.SCORE_NO_MATCH; + } + + var scores = this.buildScoreArray(str, abbr), + sum = 0.0; + + scores.each(function(i) { + sum += i; + }); + + return (sum / scores.size()); + }, + + buildScoreArray: function(str, abbr) + { + var lastIndex = -1, + lower = str.toLowerCase(), + scores = new Array(str.length), + started = false; + + abbr.toLowerCase().split("").each(function(c) { + var index = to = lower.indexOf(c, lastIndex + 1), + val = this.SCORE_BUFFER; + + if (index < 0) { + return this.fillArray(scores, this.SCORE_NO_MATCH); + } + + if (index == 0) { + started = true; + } + + if (this.isNewWord(str, index)) { + scores[index - 1] = 1; + to = index - 1; + } else if (!this.isUpperCase(str, index)) { + val = this.SCORE_NO_MATCH; + } + + this.fillArray(scores, val, lastIndex + 1, val); + + scores[index] = this.SCORE_MATCH; + lastIndex = index; + }.bind(this)); + + this.fillArray(scores, started ? this.SCORE_TRAILING_BUT_STARTED : this.SCORE_TRAILING, lastIndex + 1); + + return scores; + }, + + isUpperCase: function(str, index) + { + var c = str.charAt(index); + return ("A" <= c && c <= "Z"); + }, + + isNewWord: function(str, index) + { + var c = str.charAt(index - 1); + return (c == " " || c == "\t"); + }, + + fillArray: function(arr, value, from, to) + { + from = Math.max(from || 0, 0); + to = Math.min(to || arr.length, arr.length); + for (var i = from; i < to; ++i) { + arr[i] = value; + } + return arr; + } +}; diff --git a/framework/Core/js/open_html_helper.js b/framework/Core/js/open_html_helper.js new file mode 100644 index 000000000..b57ab513d --- /dev/null +++ b/framework/Core/js/open_html_helper.js @@ -0,0 +1,67 @@ +/** + * Horde Html Helper Javascript Class + * + * Provides the javascript class insert html tags by clicking on icons. + * + * The helpers available: + * emoticons - for inserting emoticons strings + * + * Copyright 2003-2010 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @author Marko Djukic + * @category Horde + * @package Core + * @todo Add handling for font tags, tables, etc. + */ + +var Horde_Html_Helper = { + + iconlist: [], + targetElement: null, + + open: function(type, target) + { + var cell, row, table, tbody, + lay = $('htmlhelper_' + target); + this.targetElement = $(target); + + if (lay.getStyle('display') == 'block') { + lay.hide(); + return false; + } + + if (lay.firstChild) { + lay.removeChild(lay.firstChild); + } + + tbody = new Element('TBODY'); + table = new Element('TABLE', { border: 0, cellSpacing: 0 }).insert(tbody); + + if (type == 'emoticons') { + row = new Element('TR'); + cell = new Element('TD'); + + iconlist.each(function(i) { + var link = + new Element('A', { href: '#' }).insert( + new Element('IMG', { align: 'middle', border: 0, src: i[0] }) + ); + cell.appendChild(link); + + link.observe('click', function(e) { + this.targetElement.setValue($F(this.targetElement) + i[1] + ' '); + e.stop(); + }.bindAsEventListener(this)); + }); + + row.insert(cell); + tbody.insert(row); + table.insert(tbody); + } + + lay.insert(table).setStyle({ display: 'block' }); + } +}; diff --git a/framework/Core/js/prefs.js b/framework/Core/js/prefs.js new file mode 100644 index 000000000..b7173432b --- /dev/null +++ b/framework/Core/js/prefs.js @@ -0,0 +1,17 @@ +/** + * Provides the javascript for the prefs page. + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Horde + * @package Core + */ + +document.observe('dom:loaded', function() { + $('appsubmit').hide(); + + $('app').observe('change', function() { + $('appswitch').submit(); + }); +}); diff --git a/framework/Core/js/prettyautocomplete.js b/framework/Core/js/prettyautocomplete.js new file mode 100644 index 000000000..3dbfdac5f --- /dev/null +++ b/framework/Core/js/prettyautocomplete.js @@ -0,0 +1,284 @@ +/** + * @category Horde + * @package Core + */ +var PrettyAutocompleter = Class.create({ + + initialize: function(element, params) + { + this.p = Object.extend({ + // Outer div/fake input box and CSS class + box: 'HordeACBox', + boxClass: 'hordeACBox', + //
    CSS class + listClass: 'hordeACList', + // CSS class for real input field + growingInputClass: 'hordeACTrigger', + // Dom id for
  • that holds the input field. + triggerContainer: 'hordeACTriggerContainer', + // Min pixel width of input field + minTriggerWidth: 100, + // Allow for a function that filters the display value + // This function should *always* return escaped HTML + displayFilter: function(t) { return t.escapeHTML() }, + filterCallback: this._filterChoices.bind(this), + onAdd: Prototype.K, + onRemove: Prototype.K + }, params || {}); + + // Array to hold the currently selected items to ease with removing + // them, assuring no duplicates etc.. + this.selectedItems = []; + + // The original input element is transformed into the hidden input + // field that hold the text values (p.items), while p.trigger is + // the borderless input field located in p.box + this.p.items = element; + this.p.trigger = element + 'real'; + this.initialized = false; + this._enabled = true; + }, + + /** + * Initializes the autocompleter, builds the dom structure, registers + * events, etc... + */ + init: function() + { + if (this.initialized) { + return; + } + + // Build the DOM structure + this.buildStructure(); + + // Remember the bound method to unregister later. + this._boundProcessValue = this._processValue.bind(this); + var trigger = $(this.p.trigger); + trigger.observe('keydown', this._onKeyDown.bindAsEventListener(this)); + trigger.observe('blur', this._boundProcessValue); + + // Make sure the p.items element is hidden + if (!this.p.debug) { + $(this.p.items).hide(); + } + + // Set the updateElement callback + this.p.onSelect = this._updateElement.bind(this); + + // Look for clicks on the box to simulate clicking in an input box + $(this.p.box).observe('click', function() { trigger.focus() }); + trigger.observe('blur', this._resize.bind(this)); + trigger.observe('keydown', this._resize.bind(this)); + trigger.observe('keypress', this._resize.bind(this)); + trigger.observe('keyup', this._resize.bind(this)); + + // Create the underlaying Autocompleter + this.p.uri += '/input=' + this.p.trigger; + + this.p.onShow = this._knvShow.bind(this); + this.p.onHide = this._knvHide.bind(this); + + // Make sure the knl is contained in the overlay + this.p.domParent = this.p.box; + new Ajax.Autocompleter(this.p.trigger, this.p.uri, this.p); + + // Prepopulate the items and the container elements? + if (typeof this.p.existing != 'undefined') { + this.init(this.p.existing); + } + + this.initialized = true; + }, + + /** + * Resets the autocompleter's state. + */ + reset: function(existing) + { + if (!this.initialized) { + this.init(); + } + + // TODO: Resize the trigger field to fill the current line? + // Clear any existing values + if (this.selectedItems.length) { + $(this.p.box).select('li.' + this.p.listClass + 'Item').each(function(item) { + this.removeItemNode(item); + }.bind(this)); + } + + // Clear the hidden items field + $(this.p.items).value = ''; + + // Add any initial values + if (typeof existing != 'undefined' && existing.length) { + for (var i = 0, l = existing.length; i < l; i++) { + this.addNewItemNode(existing[i]); + } + } + this._enabled = true; + }, + + buildStructure: function() + { + // Build the outter box + var box = new Element('div', { id: this.p.box, className: this.p.boxClass }).setStyle({ position: 'relative' }); + + // The list - where the choosen items are placed as
  • nodes + var list = new Element('ul', { className: this.p.listClass }); + + // The input element and the
  • wraper + var inputListItem = new Element('li', { + className: this.p.listClass + 'Member', + id: this.p.triggerContainer }), + growingInput = new Element('input', { + className: this.p.growingInputClass, + id: this.p.trigger, + name: this.p.trigger, + autocomplete: 'off' }); + + // Create a hidden span node to help calculate the needed size + // of the input field. + this.sizer = new Element('span').setStyle({ float: 'left', display: 'inline-block', position: 'absolute', left: '-1000px' }); + + inputListItem.update(growingInput); + list.update(inputListItem); + box.update(list); + box.insert(this.sizer); + + // Replace the single input element with the new structure and + // move the old element into the structure while making sure it's + // hidden. (Use the long form to play nice with Opera) + box.insert(Element.replace($(this.p.items), box)); + }, + + shutdown: function() + { + this._processValue(); + }, + + _onKeyDown: function(e) + { + // Check for a comma + if (e.keyCode == 188) { + this._processValue(); + e.stop(); + } + }, + + _processValue: function() + { + var value = $F(this.p.trigger).replace(/^,/, '').strip(); + if (value.length) { + this.addNewItemNode(value); + this.p.onAdd(value); + } + }, + + _resize: function() + { + this.sizer.update($(this.p.trigger).value); + newSize = Math.min(this.sizer.getWidth(), $(this.p.box).getWidth()); + newSize = Math.max(newSize, this.p.minTriggerWidth); + $(this.p.trigger).setStyle({ width: newSize + 'px' }); + }, + + // Used as the updateElement callback. + _updateElement: function(item) + { + this.addNewItemNode(item); + this.p.onAdd(item); + }, + + addNewItemNode: function(value) + { + // Don't add if it's already present. + for (var x = 0, len = this.selectedItems.length; x < len; x++) { + if (this.selectedItems[x].rawValue == value) { + $(this.p.trigger).value = ''; + return; + } + } + + var displayValue = this.p.displayFilter(value), + newItem = new Element('li', { className: this.p.listClass + 'Member ' + this.p.listClass + 'Item' }).update(displayValue), + x = new Element('img', { className: 'hordeACItemRemove', src: this.p.deleteIcon }); + x.observe('click', this._removeItemHandler.bindAsEventListener(this)); + newItem.insert(x); + $(this.p.triggerContainer).insert({ before: newItem }); + $(this.p.trigger).value = ''; + + // Add to hidden input field. + if ($(this.p.items).value) { + $(this.p.items).value = $(this.p.items).value + ', ' + value; + } else { + $(this.p.items).value = value; + } + + // ...and keep the selectedItems array up to date. + this.selectedItems.push({ rawValue: value, displayValue: displayValue }); + }, + + removeItemNode: function(item) + { + var value = item.collectTextNodesIgnoreClass('informal'); + for (var x = 0, len = this.selectedItems.length; x < len; x++) { + if (this.selectedItems[x].displayValue.unescapeHTML() == value) { + this.selectedItems.splice(x, 1); + break; + } + } + item.remove(); + this.p.onRemove(value); + }, + + disable: function() + { + if (!this._enabled || !this.initialized) { + return; + } + + this._enabled = false; + $(this.p.box).select('.hordeACItemRemove').invoke('toggle'); + $(this.p.trigger).disable(); + }, + + enable: function() + { + if (this._enabled) { + return; + } + this._enabled = true; + $(this.p.box).select('.hordeACItemRemove').invoke('toggle'); + $(this.p.trigger).enable(); + }, + + _removeItemHandler: function(e) + { + var realValues = [], x, len; + this.removeItemNode(e.element().up()); + for (x = 0, len = this.selectedItems.length; x < len; x++) { + realValues.push(this.selectedItems[x].rawValue); + } + $(this.p.items).value = realValues.join(','); + }, + + _filterChoices: function(c) + { + this.selectedItems.each(function(item) { + c = c.without(item.rawValue); + }); + return c; + }, + + _knvShow: function(l) + { + $(this.p.trigger).stopObserving('blur', this._boundProcessValue); + }, + + _knvHide: function(l) + { + $(this.p.trigger).observe('blur', this._boundProcessValue); + } +}); diff --git a/framework/Core/js/sourceselect.js b/framework/Core/js/sourceselect.js new file mode 100644 index 000000000..b7cdf6211 --- /dev/null +++ b/framework/Core/js/sourceselect.js @@ -0,0 +1,138 @@ +/** + * Provides the javascript for managing the source selection widget. + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Horde + * @package Core + */ + +var HordeSourceSelectPrefs = { + + // Vars defaulting to null: source_list + + setSourcesHidden: function() + { + var out = [], ss; + + if (this.source_list) { + ss = $F('source_select'); + if (ss) { + this.source_list.each(function(s) { + if (s.source == ss) { + s.selected = $F('selected_sources'); + } + out.push([ s.source, s.selected ]); + }); + } + } else { + $A($('selected_sources').options).slice(1).each(function(s) { + out.push(s.value); + }); + } + + $('sources').setValue(Object.toJSON(out)); + }, + + moveAction: function(from, to) + { + var moved = false; + + $(from).childElements().each(function(c) { + if (c.selected) { + c.remove(); + c.selected = false; + $(to).insert(c); + moved = true; + } + }); + + + if (moved) { + $(to).fire('HordeSourceSelectPrefs:add'); + $(from).fire('HordeSourceSelectPrefs:remove'); + this.setSourcesHidden(); + } + }, + + moveSource: function(e, mode) + { + var sa = $('selected_sources'), sel, tmp; + + if (sa.selectedIndex < 1 || sa.length < 3) { + return; + } + + // Deselect everything but the first selected item + sa.childElements().each(function(s) { + if (sel) { + s.selected = false; + } else if (s.selected) { + sel = s; + } + }); + + switch (mode) { + case 'down': + tmp = sel.next(); + if (tmp) { + sel.remove(); + tmp.insert({ after: sel }); + } + break; + + case 'up': + tmp = sel.previous(); + if (tmp && tmp.value) { + sel.remove(); + tmp.insert({ before: sel }); + } + break; + } + + this.setSourcesHidden(); + e.stop(); + }, + + changeSource: function() + { + var source, + sel = $('selected_sources'), + ss = $('source_select'), + unsel = $('unselected_sources'), + val = $F(ss); + + sel.down().siblings().invoke('remove'); + unsel.down().siblings().invoke('remove'); + + if (val) { + source = this.source_list.find(function(s) { + return val == s.source; + }); + source.selected.each(function(s) { + sel.insert(new Option(s.v, s.l)); + }); + source.unselected.each(function(u) { + unsel.insert(new Option(s.v, s.l)); + }); + } + }, + + onDomLoad: function() + { + if (this.source_list) { + $('source_select').observe('change', this.changeSource.bind(this)); + } + + this.setSourcesHidden(); + + $('addsource').observe('click', this.moveAction.bind(this, 'unselected_sources', 'selected_sources')); + $('removesource').observe('click', this.moveAction.bind(this, 'selected_sources', 'unselected_sources')); + $('moveup').observe('click', this.moveSource.bindAsEventListener(this, 'up')); + $('movedown').observe('click', this.moveSource.bindAsEventListener(this, 'down')); + } + +}; + +document.observe('dom:loaded', HordeSourceSelectPrefs.onDomLoad.bind(HordeSourceSelectPrefs)); diff --git a/framework/Core/js/spellchecker.js b/framework/Core/js/spellchecker.js new file mode 100644 index 000000000..e3ae778d5 --- /dev/null +++ b/framework/Core/js/spellchecker.js @@ -0,0 +1,287 @@ +/** + * This spell checker was inspired by work done by Garrison Locke, but + * was rewritten almost completely by Chuck Hagenbuch to use + * Prototype/Scriptaculous. + * + * Requires: prototype.js (v1.6.1+), KeyNavList.js + * + * Copyright 2005-2010 The Horde Project (http://www.horde.org/) + * + * Custom Events: + * -------------- + * Custom events are triggered on the target element. + * + * 'SpellChecker:after' + * Fired when the spellcheck processing ends. + * + * 'SpellChecker:before' + * Fired before the spellcheck is performed. + * + * 'SpellChecker:noerror' + * Fired when no spellcheck errors are found. + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Horde + * @package Core + */ + +var SpellChecker = Class.create({ + + // Vars used and defaulting to null: + // bad, choices, disabled, htmlAreaParent, lc, locale, reviewDiv, + // statusButton, statusClass, suggestions, target, url + options: {}, + resumeOnDblClick: true, + state: 'CheckSpelling', + + // Options: + // bs = (array) Button states + // locales = (array) List of locales. See KeyNavList for structure. + // sc = (string) Status class + // statusButton = (string/element) DOM ID or element of the status + // button + // target = (string|Element) DOM element containing data + // url = (string) URL of specllchecker handler + initialize: function(opts) + { + var d, lc, tmp, ul; + + this.url = opts.url; + this.target = $(opts.target); + this.statusButton = $(opts.statusButton); + this.buttonStates = opts.bs; + this.statusClass = opts.sc || ''; + this.disabled = false; + + this.options.onComplete = this.onComplete.bind(this); + + document.observe('click', this.onClick.bindAsEventListener(this)); + + if (opts.locales) { + this.lc = new KeyNavList(this.statusButton, { + list: opts.locales, + onChoose: this.setLocale.bindAsEventListener(this) + }); + + this.statusButton.insert({ after: new Element('SPAN', { className: 'spellcheckPopdownImg' }) }); + } + + this.setStatus('CheckSpelling'); + }, + + setLocale: function(locale) + { + this.locale = locale; + }, + + targetValue: function() + { + return Object.isUndefined(this.target.value) + ? this.target.innerHTML + : this.target.value; + }, + + spellCheck: function() + { + this.target.fire('SpellChecker:before'); + + var opts = Object.clone(this.options), + p = $H(), + url = this.url; + + this.setStatus('Checking'); + + p.set(this.target.identify(), this.targetValue()); + opts.parameters = p.toQueryString(); + + if (this.locale) { + url += '/locale=' + this.locale; + } + if (this.htmlAreaParent) { + url += '/html=1'; + } + + new Ajax.Request(url, opts); + }, + + onComplete: function(request) + { + var bad, content, washidden, + i = 0, + result = request.responseJSON; + + if (Object.isUndefined(result)) { + this.setStatus('Error'); + return; + } + + this.suggestions = result.suggestions || []; + + if (!this.suggestions.size()) { + this.setStatus('CheckSpelling'); + this.target.fire('SpellChecker:noerror'); + return; + } + + bad = result.bad || []; + + content = this.targetValue(); + content = this.htmlAreaParent + ? content.replace(/\r?\n/g, '') + : content.replace(/\r?\n/g, '~~~').escapeHTML(); + + $A(bad).each(function(node) { + var re_text = '' + node + ''; + content = content.replace(new RegExp("(?:^|\\b)" + RegExp.escape(node) + "(?:\\b|$)", 'g'), re_text); + + // Go through and see if we matched anything inside a tag (i.e. + // class/spellcheckIncorrect is often matched if using a + // non-English lang). + content = content.replace(new RegExp("(<[^>]*)" + RegExp.escape(re_text) + "([^>]*>)", 'g'), '\$1' + node + '\$2'); + }, this); + + if (!this.reviewDiv) { + this.reviewDiv = new Element('DIV', { className: this.target.readAttribute('class') }).addClassName('spellcheck').setStyle({ overflow: 'auto' }); + if (this.resumeOnDblClick) { + this.reviewDiv.observe('dblclick', this.resume.bind(this)); + } + } + + if (!this.target.visible()) { + this.target.show(); + washidden = true; + } + this.reviewDiv.setStyle({ width: this.target.clientWidth + 'px', height: this.target.clientHeight + 'px'}); + if (washidden) { + this.target.hide(); + } + + if (!this.htmlAreaParent) { + content = content.replace(/~~~/g, '
    '); + } + this.reviewDiv.update(content); + + if (this.htmlAreaParent) { + $(this.htmlAreaParent).insert({ bottom: this.reviewDiv }); + } else { + this.target.hide().insert({ before: this.reviewDiv }); + } + + this.setStatus('ResumeEdit'); + }, + + onClick: function(e) + { + var data = [], index, elt = e.element(); + + if (this.disabled) { + return; + } + + if (elt == this.statusButton) { + switch (this.state) { + case 'CheckSpelling': + this.spellCheck(); + break; + + case 'ResumeEdit': + this.resume(); + break; + } + + e.stop(); + } else if (elt.hasClassName('spellcheckPopdownImg')) { + this.lc.show(); + this.lc.ignoreClick(e); + e.stop(); + } else if (elt.hasClassName('spellcheckIncorrect')) { + index = e.element().readAttribute('index'); + + $A(this.suggestions[index]).each(function(node) { + data.push({ l: node, v: node }); + }); + + if (this.choices) { + this.choices.updateBase(elt); + this.choices.opts.onChoose = function(val) {elt.update(val).writeAttribute({ className: 'spellcheckCorrected' });}; + } else { + this.choices = new KeyNavList(elt, { + esc: true, + onChoose: function(val) { + elt.update(val).writeAttribute({ className: 'spellcheckCorrected' }); + } + }); + } + + this.choices.show(data); + this.choices.ignoreClick(e); + e.stop(); + } + }, + + resume: function() + { + if (!this.reviewDiv) { + return; + } + + var t; + + this.reviewDiv.select('span.spellcheckIncorrect').each(function(n) { + n.replace(n.innerHTML); + }); + + t = this.reviewDiv.innerHTML; + if (!this.htmlAreaParent) { + t = t.replace(/
    /gi, '~~~').unescapeHTML().replace(/~~~/g, "\n"); + } + this.target.setValue(t); + this.target.enable(); + + if (this.resumeOnDblClick) { + this.reviewDiv.stopObserving('dblclick'); + } + this.reviewDiv.remove(); + this.reviewDiv = null; + + this.setStatus('CheckSpelling'); + + if (!this.htmlAreaParent) { + this.target.show(); + } + + this.target.fire('SpellChecker:after'); + }, + + setStatus: function(state) + { + if (!this.statusButton) { + return; + } + + this.state = state; + switch (this.statusButton.tagName) { + case 'INPUT': + this.statusButton.setValue(this.buttonStates[state]); + break; + + case 'A': + this.statusButton.update(this.buttonStates[state]); + break; + } + this.statusButton.className = this.statusClass + ' spellcheck' + state; + }, + + isActive: function() + { + return this.reviewDiv; + }, + + disable: function(disable) + { + this.disabled = disable; + } + +}); diff --git a/framework/Core/package.xml b/framework/Core/package.xml index 69926c54f..a179722ed 100644 --- a/framework/Core/package.xml +++ b/framework/Core/package.xml @@ -55,6 +55,20 @@ Application Framework. + + + + + + + + + + + + + + @@ -373,8 +387,25 @@ Application Framework. + + horde + Role + pear.horde.org + + + + + + + + + + + + + diff --git a/horde/js/addressbooksprefs.js b/horde/js/addressbooksprefs.js deleted file mode 100644 index 2014254fe..000000000 --- a/horde/js/addressbooksprefs.js +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Provides the javascript for managing addressbooks. - * - * See the enclosed file COPYING for license information (LGPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. - */ - -var HordeAddressbooksPrefs = { - - // Variables set by other code: fields, nonetext - - updateSearchFields: function() - { - var tmp, - sv = $F('selected_sources'), - sf = $('search_fields_select'); - - sf.childElements().invoke('remove'); - - if (sv.size() == 1) { - tmp = this.fields.get(sv.first()); - tmp.entries.each(function(o) { - var opt = new Option(o.label, o.name); - if (tmp.selected.include(o.name)) { - opt.selected = true; - } - sf.insert(opt); - }); - } else { - tmp = new Option(this.nonetext, ''); - tmp.disabled = true; - sf.insert(tmp); - } - }, - - changeSearchFields: function() - { - var tmp, - out = $H(), - sv = $F('selected_sources'); - - if (sv.size() == 1) { - tmp = this.fields.get(sv.first()); - tmp.selected = $F('search_fields_select'); - this.fields.set(sv.first(), tmp); - - this.fields.each(function(f) { - out.set(f.key, f.value.selected); - }); - - $('search_fields').setValue(Object.toJSON(out)); - } - }, - - onDomLoad: function() - { - this.fields = $H(this.fields); - - this.updateSearchFields(); - - $('search_fields_select').observe('change', this.changeSearchFields.bind(this)); - $('selected_sources').observe('change', this.updateSearchFields.bind(this)); - $('selected_sources').observe('HordeSourceSelectPrefs:remove', this.updateSearchFields.bind(this)); - } - -}; - -document.observe('dom:loaded', HordeAddressbooksPrefs.onDomLoad.bind(HordeAddressbooksPrefs)); diff --git a/horde/js/alarmprefs.js b/horde/js/alarmprefs.js deleted file mode 100644 index c226b90fd..000000000 --- a/horde/js/alarmprefs.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Provides the javascript for managing alarms. - * - * See the enclosed file COPYING for license information (LGPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. - */ - -var HordeAlarmPrefs = { - - // Variables defaulting to null: pref - - updateParams: function() - { - [ 'notify', 'mail', 'sms' ].each(function(method) { - var p = $(method + 'Params'); - if (p) { - if ($(this.pref).getValue().include(method)) { - p.show(); - } else { - p.hide(); - } - } - }, this); - }, - - onDomLoad: function() - { - $(this.pref).observe('change', this.updateParams.bind(this)); - this.updateParams(); - } - -}; - -document.observe('dom:load', HordeAlarmPrefs.onDomLoad.bind(HordeAlarmPrefs)); diff --git a/horde/js/autocomplete.js b/horde/js/autocomplete.js deleted file mode 100644 index 0cb4df0eb..000000000 --- a/horde/js/autocomplete.js +++ /dev/null @@ -1,330 +0,0 @@ -/** - * autocomplete.js - A javascript library which implements autocomplete. - * Requires prototype.js v1.6.0.2+, scriptaculous v1.8.0+ (effects.js), - * and keynavlist.js. - * - * Adapted from script.aculo.us controls.js v1.8.0 - * (c) 2005-2007 Thomas Fuchs, Ivan Krstic, and Jon Tirsen - * Contributors: Richard Livsey, Rahul Bhargava, Rob Wills - * http://script.aculo.us/ - * - * The original script was freely distributable under the terms of an - * MIT-style license. - * - * Usage: - * ------ - * TODO: options = autoSelect, frequency, minChars, onSelect, onShow, onType, - * paramName, tokens - * - * Copyright 2007-2010 The Horde Project (http://www.horde.org/) - * - * See the enclosed file COPYING for license information (GPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/gpl.html. - */ - -var Autocompleter = {}; - -Autocompleter.Base = Class.create({ - baseInitialize: function(elt, opts) - { - this.elt = elt = $(elt); - this.changed = false; - this.observer = null; - this.oldval = $F(elt); - this.opts = Object.extend({ - frequency: 0.4, - indicator: null, - minChars: 1, - onSelect: Prototype.K, - onShow: Prototype.K, - onType: Prototype.K, - filterCallback: Prototype.K, - paramName: elt.readAttribute('name'), - tokens: [], - keydownObserver: this.elt - }, (this._setOptions ? this._setOptions(opts) : (opts || {}))); - - // Force carriage returns as token delimiters anyway - if (!this.opts.tokens.include('\n')) { - this.opts.tokens.push('\n'); - } - - elt.writeAttribute('autocomplete', 'off'); - elt.observe("keydown", this._onKeyDown.bindAsEventListener(this)); - }, - - _onKeyDown: function(e) - { - var a = document.activeElement; - - if (Object.isUndefined(a) || a == this.elt) { - switch (e.keyCode) { - case 0: - if (!Prototype.Browser.WebKit) { - break; - } - // Fall-through - - // Ignore events caught by KevNavList - case Event.KEY_DOWN: - case Event.KEY_ESC: - case Event.KEY_RETURN: - case Event.KEY_TAB: - case Event.KEY_UP: - return; - } - - this.changed = true; - - if (this.observer) { - clearTimeout(this.observer); - } - - this.observer = this.onObserverEvent.bind(this).delay(this.opts.frequency); - } - }, - - updateChoices: function(choices) - { - var a = document.activeElement, c = [], re; - - if (this.changed || - (Object.isUndefined(a) || a != this.elt)) { - return; - } - - if (this.opts.indicator) { - $(this.opts.indicator).hide(); - } - - choices = this.opts.filterCallback(choices); - if (!choices.size()) { - if (this.knl) { - this.knl.hide(); - } - this.getNewVal(this.lastentry); - } else if (choices.size() == 1 && this.opts.autoSelect) { - this.onSelect(choices.first()); - if (this.knl) { - this.knl.hide(); - } - } else { - re = new RegExp("(" + this.getToken() + ")", "i"); - - choices.each(function(n) { - c.push({ - l: n.escapeHTML().gsub(re, '#{1}'), - v: n - }); - }); - - if (!this.knl) { - this.knl = new KeyNavList(this.elt, { onChoose: this.onSelect.bind(this), - onShow: this.opts.onShow.bind(this), - domParent: this.opts.domParent, - keydownObserver: this.opts.keydownObserver}); - } - - this.knl.show(c); - } - }, - - onObserverEvent: function() - { - this.changed = false; - - var entry = this.getToken(); - - if (entry.length >= this.opts.minChars) { - entry = this.opts.onType(entry); - } - - if (entry.length) { - if (this.opts.indicator) { - $(this.opts.indicator).show(); - } - this.lastentry = entry; - this.getUpdatedChoices(entry); - } else if (this.knl) { - this.knl.hide(); - } - }, - - getToken: function() - { - var bounds = this.getTokenBounds(); - return $F(this.elt).substring(bounds[0], bounds[1]).strip(); - }, - - getTokenBounds: function() - { - var diff, i, index, l, offset, tp, - t = this.opts.tokens, - value = $F(this.elt), - nextTokenPos = value.length, - prevTokenPos = -1, - boundary = Math.min(nextTokenPos, this.oldval.length); - - if (value.strip().empty()) { - return [ -1, 0 ]; - } - - diff = boundary; - for (i = 0; i < boundary; ++i) { - if (value[i] != this.oldval[i]) { - diff = i; - break; - } - } - - offset = (diff == this.oldval.length ? 1 : 0); - - for (index = 0, l = t.length; index < l; ++index) { - tp = value.lastIndexOf(t[index], diff + offset - 1); - if (tp > prevTokenPos) { - prevTokenPos = tp; - } - tp = value.indexOf(t[index], diff + offset); - if (tp != -1 && tp < nextTokenPos) { - nextTokenPos = tp; - } - } - return [ prevTokenPos + 1, nextTokenPos ]; - }, - - onSelect: function(entry) - { - if (entry) { - this.elt.setValue(this.opts.onSelect(this.getNewVal(entry))).focus(); - if (this.knl) { - this.knl.markSelected(); - } - } - }, - - getNewVal: function(entry) - { - var bounds = this.getTokenBounds(), newval, v, ws; - - if (bounds[0] == -1) { - newval = entry; - } else { - v = $F(this.elt); - newval = v.substr(0, bounds[0]); - ws = v.substr(bounds[0]).match(/^\s+/); - if (ws) { - newval += ws[0]; - } - newval += entry + v.substr(bounds[1]); - } - - this.oldval = newval; - - return newval; - } - -}); - -Ajax.Autocompleter = Class.create(Autocompleter.Base, { - - initialize: function(element, url, opts) - { - this.baseInitialize(element, opts); - this.opts = Object.extend(this.opts, { - asynchronous: true, - onComplete: this._onComplete.bind(this), - defaultParams: $H(this.opts.parameters) - }); - this.url = url; - this.cache = $H(); - }, - - getUpdatedChoices: function(t) - { - var p, - o = this.opts, - c = this.cache.get(t); - - if (c) { - this.updateChoices(c); - } else { - p = Object.clone(o.defaultParams); - p.set(o.paramName, t); - o.parameters = p.toQueryString(); - new Ajax.Request(this.url, o); - } - }, - - _onComplete: function(request) - { - this.updateChoices(this.cache.set(this.getToken(), request.responseJSON)); - } -}); - -Autocompleter.Local = Class.create(Autocompleter.Base, { - - initialize: function(element, arr, opts) - { - this.baseInitialize(element, opts); - this.opts.arr = arr; - }, - - getUpdatedChoices: function(entry) - { - var choices, - csort = [], - entry_len = entry.length, - i = 0, - o = this.opts; - - if (o.ignoreCase) { - entry = entry.toLowerCase(); - } - - choices = o.arr.findAll(function(t) { - if (i == o.choices) { - throw $break; - } - - if (o.ignoreCase) { - t = t.toLowerCase(); - } - t = t.unescapeHTML(); - - var pos = t.indexOf(entry); - if (pos != -1 && - ((pos == 0 && t.length != entry_len) || - (entry_len >= o.partialChars && - o.partialSearch && - (o.fullSearch || /\s/.test(t.substr(pos - 1, 1)))))) { - ++i; - return true; - } - return false; - }, this); - - if (o.score) { - choices.each(function(term) { - csort.push({ s: LiquidMetal.score(term, entry), t: term }); - }.bind(this)); - // Sort the terms - csort.sort(function(a, b) { return b.s - a.s; }); - choices = csort.pluck('t'); - } - - this.updateChoices(choices); - }, - - _setOptions: function(opts) - { - return Object.extend({ - choices: 10, - fullSearch: false, - ignoreCase: true, - partialChars: 2, - partialSearch: true, - score: false - }, opts || {}); - } - -}); diff --git a/horde/js/calendar.js b/horde/js/calendar.js deleted file mode 100644 index d104a7d73..000000000 --- a/horde/js/calendar.js +++ /dev/null @@ -1,332 +0,0 @@ -/** - * Horde javascript calendar widget. - * - * Custom Events: - * -------------- - * Horde_Calendar:select - * params: Date object - * Fired when a date is selected. - * - * Horde_Calendar:selectMonth - * params: Date object - * Fired when a month is selected. - * - * Horde_Calendar:selectWeek - * params: Date object - * Fired when a week is selected. - * - * Horde_Calendar:selectYear - * params: Date object - * Fired when a year is selected. - */ - -var Horde_Calendar = -{ - // Variables set externally: click_month, click_week, click_year, - // firstDayOfWeek, fullweekdays, months, - // weekdays - // Variables defaulting to null: date, month, openDate, trigger, year - - open: function(trigger, data) - { - var date = data ? data : new Date(); - - this.openDate = date.getTime(); - this.trigger = $(trigger); - this.draw(this.openDate, true); - }, - - /** - * Days in the month (month is a zero-indexed javascript month). - */ - daysInMonth: function(month, year) - { - switch (month) { - case 3: - case 5: - case 8: - case 10: - return 30; - - case 1: - return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) - ? 29 - : 28; - - default: - return 31; - } - }, - - weeksInMonth: function(month, year) - { - var firstWeekDays, weeks, - firstOfMonth = (new Date(year, month, 1)).getDay(); - - if ((this.firstDayOfWeek == 1 && firstOfMonth == 0) || - (this.firstDayOfWeek == 0 && firstOfMonth == 6)) { - firstWeekDays = 7 - firstOfMonth + this.firstDayOfWeek; - weeks = 1; - } else { - firstWeekDays = this.firstDayOfWeek - firstOfMonth; - weeks = 0; - } - - firstWeekDays %= 7; - - return Math.ceil((this.daysInMonth(month, year) - firstWeekDays) / 7) + weeks; - }, - - // http://javascript.about.com/library/blstdweek.htm - weekOfYear: function(d) - { - var newYear = new Date(d.getFullYear(), 0, 1), - day = newYear.getDay(); - if (this.firstDayOfWeek != 0) { - day = ((day + (7 - this.firstDayOfWeek)) % 7); - } - return Math.ceil((((d - newYear) / 86400000) + day + 1) / 7); - }, - - draw: function(timestamp, init) - { - this.date = new Date(timestamp); - this.month = this.date.getMonth(); - this.year = this.date.getFullYear(); - - var cell, i, i_max, p, row, startOfView, vp, - count = 1, - div = $('hordeCalendar'), - tbody = div.down('TBODY'), - - // Requires init above - daysInMonth = this.daysInMonth(this.month, this.year), - daysInView = this.weeksInMonth(this.month, this.year) * 7, - firstOfMonth = (new Date(this.year, this.month, 1)).getDay(), - - // Cache today and open date. - today = new Date(), - today_year = today.getFullYear(), - today_month = today.getMonth(), - today_day = today.getDate(), - open = new Date(this.openDate), - open_year = open.getFullYear(), - open_month = open.getMonth(), - open_day = open.getDate(); - - if (this.firstDayOfWeek == 0) { - startOfView = 1 - firstOfMonth; - } else { - // @TODO Adjust this for days other than Monday. - startOfView = (firstOfMonth == 0) - ? -5 - : 2 - firstOfMonth; - } - - div.down('.hordeCalendarYear').update(this.year); - div.down('.hordeCalendarMonth').update(this.months[this.month]); - tbody.update(''); - - for (i = startOfView, i_max = startOfView + daysInView; i < i_max; ++i) { - if (count == 1) { - row = new Element('TR'); - if (this.click_week) { - row.insert(new Element('TD').insert(new Element('A', { className: 'hordeCalendarWeek' }).insert(this.weekOfYear(new Date(this.year, this.month, (i < 1) ? 1 : i))))); - } - } - - cell = new Element('TD'); - - if (i < 1 || i > daysInMonth) { - cell.addClassName('hordeCalendarEmpty'); - row.insert(cell); - } else { - if (today_year == this.year && - today_month == this.month && - today_day == i) { - cell.writeAttribute({ className: 'hordeCalendarToday' }); - } - - if (open_year == this.year && - open_month == this.month && - open_day == i) { - cell.addClassName('hordeCalendarCurrent'); - } - - row.insert(cell.insert(new Element('A', { className: 'hordeCalendarDay', href: '#' }).insert(i))); - } - - if (count == 7) { - tbody.insert(row); - count = 0; - } - ++count; - } - - if (count > 1) { - tbody.insert(row); - } - - div.show(); - - // Position the popup every time in case of a different input, - // window sizing changes, etc. - if (init) { - p = this.trigger.cumulativeOffset(); - vp = document.viewport.getDimensions(); - - if (p.left + div.offsetWidth > vp.width) { - div.setStyle({ left: (vp.width - 10 - div.offsetWidth) + 'px' }); - } else { - div.setStyle({ left: p.left + 'px' }); - } - - if (p.top + div.offsetHeight > vp.height) { - div.setStyle({ top: (vp.height - 10 - div.offsetHeight) + 'px' }); - } else { - div.setStyle({ top: p.top + 'px' }); - } - } - - // IE 6 only. - if (Prototype.Browser.IE && !window.XMLHttpRequest) { - iframe = $('hordeCalendarIframe'); - if (!iframe) { - iframe = new Element('IFRAME', { name: 'hordeCalendarIframe', id: 'hordeCalendarIframe', src: 'javascript:false;', scrolling: 'no', frameborder: 0 }).hide(); - $(document.body).insert(iframe); - } - iframe.clonePosition(div).setStyle({ - position: 'absolute', - display: 'block', - zIndex: 1 - }); - } - - div.setStyle({ zIndex: 999 }); - }, - - hideCal: function() - { - var iefix = $('hordeCalendarIframe'); - - $('hordeCalendar').hide(); - - if (iefix) { - iefix.hide(); - } - }, - - changeYear: function(by) - { - this.draw((new Date(this.date.getFullYear() + by, this.date.getMonth(), 1)).getTime()); - }, - - changeMonth: function(by) - { - var newMonth = this.date.getMonth() + by, - newYear = this.date.getFullYear(); - - if (newMonth == -1) { - newMonth = 11; - newYear -= 1; - } - - this.draw((new Date(newYear, newMonth, 1)).getTime()); - }, - - init: function() - { - var i, link, row, - offset = this.click_week ? 1 : 0, - thead = new Element('THEAD'), - table = new Element('TABLE', { className: 'hordeCalendarPopup', cellSpacing: 0 }).insert(thead).insert(new Element('TBODY')); - - // Title bar. - link = new Element('A', { href: '#', className: 'hordeCalendarClose rightAlign' }).insert('x'); - thead.insert(new Element('TR').insert(new Element('TD', { colspan: 6 + offset })).insert(new Element('TD').insert(link))); - - // Year. - row = new Element('TR'); - link = new Element('A', { className: 'hordeCalendarPrevYear', href: '#' }).insert('«'); - row.insert(new Element('TD').insert(link)); - - tmp = new Element('TD', { align: 'center', colspan: 5 + offset }); - if (this.click_year) { - tmp.insert(new Element('A', { className: 'hordeCalendarYear' })); - } else { - tmp.addClassName('hordeCalendarYear'); - } - row.insert(tmp); - - link = new Element('A', { className: 'hordeCalendarNextYear', href: '#' }).insert('»'); - row.insert(new Element('TD', { className: 'rightAlign' }).insert(link)); - - thead.insert(row); - - // Month name. - row = new Element('TR'); - link = new Element('A', { className: 'hordeCalendarPrevMonth', href: '#' }).insert('«'); - row.insert(new Element('TD').insert(link)); - - tmp = new Element('TD', { align: 'center', colspan: 5 + offset }); - if (this.click_year) { - tmp.insert(new Element('A', { className: 'hordeCalendarMonth' })); - } else { - tmp.addClassName('hordeCalendarMonth'); - } - row.insert(tmp); - - link = new Element('A', { className: 'hordeCalendarNextMonth', href: '#' }).insert('»'); - row.insert(new Element('TD', { className: 'rightAlign' }).insert(link)); - - thead.insert(row); - - // Weekdays. - row = new Element('TR'); - if (this.click_week) { - row.insert(new Element('TH')); - } - for (i = 0; i < 7; ++i) { - row.insert(new Element('TH').insert(this.weekdays[(i + this.firstDayOfWeek) % 7])); - } - thead.insert(row); - - $(document.body).insert({ bottom: new Element('DIV', { id: 'hordeCalendar' }).setStyle({ position: 'absolute', 'z-index': 999 }).hide().insert(table) }); - - $('hordeCalendar').observe('click', this.clickHandler.bindAsEventListener(this)); - }, - - clickHandler: function(e) - { - var elt = e.element(); - - if (elt.hasClassName('hordeCalendarDay')) { - this.hideCal(); - this.trigger.fire('Horde_Calendar:select', new Date(this.year, this.month, parseInt(e.element().textContent, 10))); - } else if (elt.hasClassName('hordeCalendarClose')) { - this.hideCal(); - } else if (elt.hasClassName('hordeCalendarPrevYear')) { - this.changeYear(-1); - } else if (elt.hasClassName('hordeCalendarNextYear')) { - this.changeYear(1); - } else if (elt.hasClassName('hordeCalendarPrevMonth')) { - this.changeMonth(-1); - } else if (elt.hasClassName('hordeCalendarNextMonth')) { - this.changeMonth(1); - } else if (this.click_year && elt.hasClassName('hordeCalendarYear')) { - this.trigger.fire('Horde_Calendar:selectYear', new Date(this.year, this.month, 1)); - this.hideCal(); - } else if (this.click_month && elt.hasClassName('hordeCalendarMonth')) { - this.trigger.fire('Horde_Calendar:selectMonth', new Date(this.year, this.month, 1)); - this.hideCal(); - } else if (this.click_week && elt.hasClassName('hordeCalendarWeek')) { - this.trigger.fire('Horde_Calendar:selectWeek', new Date(this.year, this.month, elt.up('TR').down('A.hordeCalendarDay').textContent)); - this.hideCal(); - } - - e.stop(); - } - -}; - -document.observe('dom:loaded', Horde_Calendar.init.bind(Horde_Calendar)); diff --git a/horde/js/hordetree.js b/horde/js/hordetree.js deleted file mode 100644 index 3659e62af..000000000 --- a/horde/js/hordetree.js +++ /dev/null @@ -1,511 +0,0 @@ -/** - * Provides the javascript class to create dynamic trees. - * - * Optionally uses the Horde_Tooltip class (tooltips.js). - * - * Custom Events - * ------------- - * The 'memo' property of the Event object contains the original event object. - * - * 'Horde_Tree:expand' - * Fired when a tree element is expanded. - * - * 'Horde_Tree:collapse' - * Fired when a tree element is collapsed. - * - * Copyright 2003-2010 The Horde Project (http://www.horde.org/) - * - * See the enclosed file COPYING for license information (GPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/gpl.html. - * - * @author Marko Djukic - * @author Michael Slusarz - * @category Horde - */ - -var Horde_Tree = Class.create({ - - initialize: function(opts) - { - this.opts = opts; - - if (this.opts.initTree) { - this.renderTree(this.opts.initTree.nodes, this.opts.initTree.root_nodes, this.opts.initTree.is_static); - this.opts.initTree = null; - } - - $(this.opts.target).observe('click', this._onClick.bindAsEventListener(this)); - - this.opts.ie6 = (navigator.userAgent.toLowerCase().substr(25,6)=="msie 6"); - }, - - renderTree: function(nodes, rootNodes, renderStatic) - { - this.nodes = nodes; - this.rootNodes = rootNodes; - this.renderStatic = renderStatic; - this.dropline = []; - this.output = document.createDocumentFragment(); - - this._buildHeader(); - - this.rootNodes.each(function(r) { - this.buildTree(r, this.output); - }, this); - - $(this.opts.target).update(''); - $(this.opts.target).appendChild(this.output); - - if (this.opts.ie6) { - /* Correct for frame scrollbar in IE6 by determining if a scrollbar - * is present, and if not readjusting the marginRight property to - * 0. See http://www.xs4all.nl/~ppk/js/doctypes.html for why this - * works */ - if (document.documentElement.clientHeight == document.documentElement.offsetHeight) { - // no scrollbar present, take away extra margin - $(document.body).setStyle({ marginRight: 0 }); - } else { - $(document.body).setStyle({ marginRight: '15px' }); - } - } - - // If using alternating row shading, work out correct shade. - if (this.opts.options.alternate) { - this.stripe(); - } - - if (window.Horde_Tooltips) { - window.Horde_ToolTips.attachBehavior(); - } - }, - - _buildHeader: function() - { - if (this.opts.options.hideHeaders || - !this.opts.header.size()) { - return; - } - - var div = new Element('DIV'); - - this.opts.header.each(function(h) { - var tmp = new Element('SPAN').insert(h.html ? h.html : ' '); - - if (h['class']) { - tmp.addClassName(h['class']); - } - - div.appendChild(tmp); - }, this); - - this.output.appendChild(div); - }, - - // Recursive function to walk through the tree array and build - // the output. - buildTree: function(nodeId, p) - { - var last_subnode, tmp, - node = this.nodes[nodeId]; - - this.buildLine(nodeId, p); - - if (!Object.isUndefined(node.children)) { - last_subnode = node.children.last(); - tmp = new Element('DIV', { id: 'nodeChildren_' + nodeId }); - [ tmp ].invoke(node.expanded ? 'show' : 'hide'); - - node.children.each(function(c) { - if (c == last_subnode) { - this.nodes[c].node_last = true; - } - this.buildTree(c, tmp); - }, this); - - p.appendChild(tmp); - } - }, - - buildLine: function(nodeId, p) - { - var div, label, tmp, - column = 0, - node = this.nodes[nodeId]; - - div = new Element('DIV', { className: 'treeRow' }); - if (node['class']) { - div.addClassName(node['class']); - } - - // If we have headers, track which logical "column" we're in for - // any given cell of content. - if (node.extra && node.extra[0]) { - node.extra[0].each(function(n) { - div.insert(this._divClass(new Element('SPAN').update(n), column++)); - }, this); - } - - for (; column < this.opts.extraColsLeft; ++column) { - div.insert(this._divClass(new Element('SPAN').update(' '), column)); - } - - div.insert(this._divClass(new Element('SPAN'), column)); - - tmp = document.createDocumentFragment(); - for (i = Number(this.renderStatic); i < node.indent; ++i) { - tmp.appendChild(new Element('SPAN').addClassName('treeImg').addClassName( - 'treeImg' + ((this.dropline[i] && this.opts.options.lines) - ? this.opts.imgLine - : this.opts.imgBlank) - )); - } - - tmp.appendChild(this._setNodeToggle(nodeId)); - - if (node.url) { - label = new Element('A', { href: node.url }).insert( - this._setNodeIcon(nodeId) - ).insert( - node.label - ); - - if (node.urlclass) { - label.addClassName(node.urlclass); - } else if (this.opts.options.urlclass) { - label.addClassName(this.opts.options.urlclass); - } - - if (node.title) { - label.writeAttribute('title', node.title); - } - - if (node.target) { - label.writeAttribute('target', node.target); - } else if (this.opts.options.target) { - label.writeAttribute('target', this.opts.options.target); - } - - //if (node.onclick) { - // label.push(' onclick="' + node.onclick + '"'); - //} - - label = label.wrap('SPAN'); - } else { - label = new Element('SPAN').addClassName('toggle').insert( - this._setNodeIcon(nodeId) - ).insert( - node.label - ); - } - - if (this.opts.options.multiline) { - div.insert(new Element('TABLE').insert( - new Element('TR').insert( - new Element('TD').appendChild(tmp) - ).insert( - new Element('TD').insert(label) - ) - )); - } else { - div.appendChild(tmp); - div.insert(label) - } - - ++column; - - if (node.extra && node.extra[1]) { - node.extra[1].each(function(n) { - div.insert(this._divClass(new Element('SPAN').update(n), column++)); - }, this); - } - - for (; column < this.opts.extraColsRight; ++column) { - div.insert(this._divClass(new Element('SPAN').update(' '), column)); - } - - p.appendChild(div); - }, - - _divClass: function(div, c) - { - if (this.opts.header[c] && this.opts.header[c]['class']) { - div.addClassName(this.opts.header[c]['class']); - } - - return div; - }, - - _setNodeToggle: function(nodeId) - { - var node = this.nodes[nodeId]; - - if (node.indent == '0') { - // Top level with children. - this.dropline[0] = false; - - if (this.renderStatic) { - return ''; - } - - switch (this._getLineType(nodeId)) { - case 1: - case 2: - this.dropline[0] = true; - break; - } - } else { - this.dropline[node.indent] = !node.node_last; - } - - return new Element('SPAN', { id: "nodeToggle_" + nodeId }).addClassName('treeToggle').addClassName('treeImg').addClassName('treeImg' + this._getNodeToggle(nodeId)); - }, - - _getNodeToggle: function(nodeId) - { - var type, - node = this.nodes[nodeId]; - - if (node.indent == '0') { - if (this.renderStatic) { - return ''; - } - - if (!this.opts.options.lines) { - return this.opts.imgBlank; - } - - type = this._getLineType(nodeId); - - if (node.children) { - // Top level with children. - if (node.expanded) { - return type - ? ((type == 2) ? this.opts.imgMinus : this.opts.imgMinusBottom) - : this.opts.imgMinusOnly; - } - - return type - ? ((type == 2) ? this.opts.imgPlus : this.opts.imgPlusBottom) - : this.opts.imgPlusOnly; - } - - switch (type) { - case 0: - return this.opts.imgNullOnly; - - case 1: - return this.opts.imgJoinTop; - - case 2: - return this.opts.imgJoin; - - case 3: - return this.opts.imgJoinBottom; - } - } - - if (node.children) { - // Node with children. - if (!node.node_last) { - // Not last node. - if (!this.opts.options.lines) { - return this.opts.imgBlank; - } else if (this.renderStatic) { - return this.opts.imgJoin; - } else if (node.expanded) { - return this.opts.imgMinus; - } - - return this.opts.imgPlus; - } - - // Last node. - if (!this.opts.options.lines) { - return this.opts.imgBlank; - } else if (this.renderStatic) { - return this.opts.imgJoinBottom; - } else if (node.expanded) { - return this.opts.imgMinusBottom; - } - - return this.opts.imgPlusBottom; - } - - // Node no children. - if (!node.node_last) { - // Not last node. - return this.opts.options.lines - ? this.opts.imgJoin - : this.opts.imgBlank; - } - - // Last node. - return this.opts.options.lines - ? this.opts.imgJoinBottom - : this.opts.imgBlank; - }, - - _getLineType: function(nodeId) - { - if (this.opts.options.lines_base && - this.rootNodes.size() > 1) { - switch (this.rootNodes.indexOf(nodeId)) { - case 0: - return 1; - - case (this.rootNodes.size() - 1): - return 3; - - default: - return 2; - } - } - - return 0; - }, - - _setNodeIcon: function(nodeId) - { - var img, - node = this.nodes[nodeId]; - - // Image. - if (node.icon) { - // Node has a user defined icon. - img = new Element('IMG', { id: "nodeIcon_" + nodeId, src: (node.iconopen && node.expanded ? node.iconopen : node.icon) }).addClassName('treeIcon') - } else { - img = new Element('SPAN', { id: "nodeIcon_" + nodeId }).addClassName('treeIcon'); - if (node.children) { - // Standard icon: node with children. - img.addClassName('treeImg' + (node.expanded ? this.opts.imgFolderOpen : this.opts.imgFolder)); - } else { - // Standard icon: node, no children. - img.addClassName('treeImg' + this.opts.imgLeaf); - } - } - - if (node.iconalt) { - img.writeAttribute('alt', node.iconalt); - } - - return img; - }, - - toggle: function(nodeId) - { - var icon, nodeToggle, toggle, children, - node = this.nodes[nodeId]; - - node.expanded = !node.expanded; - if (children = $('nodeChildren_' + nodeId)) { - children.setStyle({ display: node.expanded ? 'block' : 'none' }); - } - - // Toggle the node's icon if it has separate open and closed - // icons. - if (icon = $('nodeIcon_' + nodeId)) { - // Image. - if (node.icon) { - icon.writeAttribute('src', (node.expanded && node.iconopen) ? node.iconopen : node.icon); - } else { - // Use standard icon set. - icon.writeAttribute('src', node.expanded ? this.opts.imgFolderOpen : this.opts.imgFolder); - } - } - - // If using alternating row shading, work out correct shade. - if (this.opts.options.alternate) { - this.stripe(); - } - - if (toggle = $('nodeToggle_' + nodeId)) { - toggle.writeAttribute('class', 'treeToggle treeImg').addClassName('treeImg' + this._getNodeToggle(nodeId)); - } - - $(this.opts.target).fire(node.expanded ? 'Horde_Tree:expand' : 'Horde_Tree:collapse', nodeId); - - this.saveState(nodeId, node.expanded) - }, - - stripe: function() - { - var classes = [ 'rowEven', 'rowOdd' ], - i = 0; - - $(this.opts.target).select('DIV.treeRow').each(function(r) { - classes.each(r.removeClassName.bind(r)); - if (r.clientHeight) { - r.addClassName(classes[++i % 2]); - } - }); - }, - - saveState: function(nodeId, expanded) - { - if (this.opts.nocookie) { - return; - } - - var newCookie = '', - newNodes = [], - oldCookie = this._getCookie(this.opts.target + '_expanded'); - - if (expanded) { - // Expand requested so add to cookie. - newCookie = (oldCookie ? oldCookie + ',' : '') + nodeId; - } else { - // Collapse requested so remove from cookie. - oldCookie.split(',').each(function(n) { - if (n != nodeId) { - newNodes[newNodes.length] = n - } - }); - newCookie = newNodes.join(','); - } - - document.cookie = this.opts.target + '_expanded=exp' + escape(newCookie) + ';DOMAIN=' + this.opts.cookieDomain + ';PATH=' + this.opts.cookiePath + ';'; - }, - - _getCookie: function(name) - { - var end, - dc = document.cookie, - prefix = name + '=exp', - begin = dc.indexOf('; ' + prefix); - - if (begin == -1) { - begin = dc.indexOf(prefix); - if (begin != 0) { - return ''; - } - } else { - begin += 2; - } - - end = document.cookie.indexOf(';', begin); - if (end == -1) { - end = dc.length; - } - - return unescape(dc.substring(begin + prefix.length, end)); - }, - - _onClick: function(e) - { - var elt = e.element(), - id = elt.readAttribute('id'); - - if (elt.hasClassName('treeIcon')) { - elt = elt.up().previous(); - } else if (elt.hasClassName('toggle')) { - elt = elt.previous(); - } - - id = elt.readAttribute('id'); - if (id && id.startsWith('nodeToggle_')) { - this.toggle(id.substr(11)); - e.stop(); - } - } - -}); diff --git a/horde/js/identityselect.js b/horde/js/identityselect.js deleted file mode 100644 index 19685cc73..000000000 --- a/horde/js/identityselect.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Horde identity selection javascript. - * - * See the enclosed file COPYING for license information (LGPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. - */ - -var HordeIdentitySelect = { - - newChoice: function() - { - var identity = $('identity'), - id = Number($F(identity)); - - if (id < 0) { - identity.up('FORM').reset(); - identity.setValue(id); - return; - } - - this.identities[id].each(function(a) { - var field = $(a[0]); - - switch (a[1]) { - case "special": - identity.fire('HordeIdentitySelect:change', { - i: id, - pref: a[0] - }); - break; - - default: - field.setValue(a[2]); - break; - } - }); - }, - - onDomLoad: function() - { - $('identity').observe('change', this.newChoice.bind(this)); - } - -}; - -document.observe('dom:loaded', HordeIdentitySelect.onDomLoad.bind(HordeIdentitySelect)); diff --git a/horde/js/liquidmetal.js b/horde/js/liquidmetal.js deleted file mode 100644 index 74cde8d90..000000000 --- a/horde/js/liquidmetal.js +++ /dev/null @@ -1,102 +0,0 @@ -/* - * LiquidMetal, version: 0.1 (2009-02-05) - * - * A mimetic poly-alloy of Quicksilver's scoring algorithm, essentially - * LiquidMetal. - * - * For usage and examples, visit: - * http://github.com/rmm5t/liquidmetal - * - * Licensed under the MIT: - * http://www.opensource.org/licenses/mit-license.php - * - * Copyright (c) 2009, Ryan McGeary (ryanonjavascript -[at]- mcgeary [*dot*] org) - * Modified by the Horde Project to use compressibility. - */ -var LiquidMetal = { - - SCORE_NO_MATCH: 0.0, - SCORE_MATCH: 1.0, - SCORE_TRAILING: 0.8, - SCORE_TRAILING_BUT_STARTED: 0.9, - SCORE_BUFFER: 0.85, - - score: function(str, abbr) - { - // Short circuits - if (abbr.length == 0) { - return this.SCORE_TRAILING; - } - if (abbr.length > str.length) { - return this.SCORE_NO_MATCH; - } - - var scores = this.buildScoreArray(str, abbr), - sum = 0.0; - - scores.each(function(i) { - sum += i; - }); - - return (sum / scores.size()); - }, - - buildScoreArray: function(str, abbr) - { - var lastIndex = -1, - lower = str.toLowerCase(), - scores = new Array(str.length), - started = false; - - abbr.toLowerCase().split("").each(function(c) { - var index = to = lower.indexOf(c, lastIndex + 1), - val = this.SCORE_BUFFER; - - if (index < 0) { - return this.fillArray(scores, this.SCORE_NO_MATCH); - } - - if (index == 0) { - started = true; - } - - if (this.isNewWord(str, index)) { - scores[index - 1] = 1; - to = index - 1; - } else if (!this.isUpperCase(str, index)) { - val = this.SCORE_NO_MATCH; - } - - this.fillArray(scores, val, lastIndex + 1, val); - - scores[index] = this.SCORE_MATCH; - lastIndex = index; - }.bind(this)); - - this.fillArray(scores, started ? this.SCORE_TRAILING_BUT_STARTED : this.SCORE_TRAILING, lastIndex + 1); - - return scores; - }, - - isUpperCase: function(str, index) - { - var c = str.charAt(index); - return ("A" <= c && c <= "Z"); - }, - - isNewWord: function(str, index) - { - var c = str.charAt(index - 1); - return (c == " " || c == "\t"); - }, - - fillArray: function(arr, value, from, to) - { - from = Math.max(from || 0, 0); - to = Math.min(to || arr.length, arr.length); - for (var i = from; i < to; ++i) { - arr[i] = value; - } - return arr; - } -}; diff --git a/horde/js/open_html_helper.js b/horde/js/open_html_helper.js deleted file mode 100644 index c4835811f..000000000 --- a/horde/js/open_html_helper.js +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Horde Html Helper Javascript Class - * - * Provides the javascript class insert html tags by clicking on icons. - * - * The helpers available: - * emoticons - for inserting emoticons strings - * - * Copyright 2003-2010 The Horde Project (http://www.horde.org/) - * - * See the enclosed file COPYING for license information (GPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/gpl.html. - * - * @author Marko Djukic - * @package Horde - * @todo Add handling for font tags, tables, etc. - */ - -var Horde_Html_Helper = { - - iconlist: [], - targetElement: null, - - open: function(type, target) - { - var cell, row, table, tbody, - lay = $('htmlhelper_' + target); - this.targetElement = $(target); - - if (lay.getStyle('display') == 'block') { - lay.hide(); - return false; - } - - if (lay.firstChild) { - lay.removeChild(lay.firstChild); - } - - tbody = new Element('TBODY'); - table = new Element('TABLE', { border: 0, cellSpacing: 0 }).insert(tbody); - - if (type == 'emoticons') { - row = new Element('TR'); - cell = new Element('TD'); - - iconlist.each(function(i) { - var link = - new Element('A', { href: '#' }).insert( - new Element('IMG', { align: 'middle', border: 0, src: i[0] }) - ); - cell.appendChild(link); - - link.observe('click', function(e) { - this.targetElement.setValue($F(this.targetElement) + i[1] + ' '); - e.stop(); - }.bindAsEventListener(this)); - }); - - row.insert(cell); - tbody.insert(row); - table.insert(tbody); - } - - lay.insert(table).setStyle({ display: 'block' }); - } -}; diff --git a/horde/js/prefs.js b/horde/js/prefs.js deleted file mode 100644 index fc6089777..000000000 --- a/horde/js/prefs.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Provides the javascript for the prefs page. - * - * See the enclosed file COPYING for license information (LGPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. - */ - -document.observe('dom:loaded', function() { - $('appsubmit').hide(); - - $('app').observe('change', function() { - $('appswitch').submit(); - }); -}); diff --git a/horde/js/prettyautocomplete.js b/horde/js/prettyautocomplete.js deleted file mode 100644 index cc1be4fdd..000000000 --- a/horde/js/prettyautocomplete.js +++ /dev/null @@ -1,280 +0,0 @@ -var PrettyAutocompleter = Class.create({ - - initialize: function(element, params) - { - this.p = Object.extend({ - // Outer div/fake input box and CSS class - box: 'HordeACBox', - boxClass: 'hordeACBox', - //
      CSS class - listClass: 'hordeACList', - // CSS class for real input field - growingInputClass: 'hordeACTrigger', - // Dom id for
    • that holds the input field. - triggerContainer: 'hordeACTriggerContainer', - // Min pixel width of input field - minTriggerWidth: 100, - // Allow for a function that filters the display value - // This function should *always* return escaped HTML - displayFilter: function(t) { return t.escapeHTML() }, - filterCallback: this._filterChoices.bind(this), - onAdd: Prototype.K, - onRemove: Prototype.K - }, params || {}); - - // Array to hold the currently selected items to ease with removing - // them, assuring no duplicates etc.. - this.selectedItems = []; - - // The original input element is transformed into the hidden input - // field that hold the text values (p.items), while p.trigger is - // the borderless input field located in p.box - this.p.items = element; - this.p.trigger = element + 'real'; - this.initialized = false; - this._enabled = true; - }, - - /** - * Initializes the autocompleter, builds the dom structure, registers - * events, etc... - */ - init: function() - { - if (this.initialized) { - return; - } - - // Build the DOM structure - this.buildStructure(); - - // Remember the bound method to unregister later. - this._boundProcessValue = this._processValue.bind(this); - var trigger = $(this.p.trigger); - trigger.observe('keydown', this._onKeyDown.bindAsEventListener(this)); - trigger.observe('blur', this._boundProcessValue); - - // Make sure the p.items element is hidden - if (!this.p.debug) { - $(this.p.items).hide(); - } - - // Set the updateElement callback - this.p.onSelect = this._updateElement.bind(this); - - // Look for clicks on the box to simulate clicking in an input box - $(this.p.box).observe('click', function() { trigger.focus() }); - trigger.observe('blur', this._resize.bind(this)); - trigger.observe('keydown', this._resize.bind(this)); - trigger.observe('keypress', this._resize.bind(this)); - trigger.observe('keyup', this._resize.bind(this)); - - // Create the underlaying Autocompleter - this.p.uri += '/input=' + this.p.trigger; - - this.p.onShow = this._knvShow.bind(this); - this.p.onHide = this._knvHide.bind(this); - - // Make sure the knl is contained in the overlay - this.p.domParent = this.p.box; - new Ajax.Autocompleter(this.p.trigger, this.p.uri, this.p); - - // Prepopulate the items and the container elements? - if (typeof this.p.existing != 'undefined') { - this.init(this.p.existing); - } - - this.initialized = true; - }, - - /** - * Resets the autocompleter's state. - */ - reset: function(existing) - { - if (!this.initialized) { - this.init(); - } - - // TODO: Resize the trigger field to fill the current line? - // Clear any existing values - if (this.selectedItems.length) { - $(this.p.box).select('li.' + this.p.listClass + 'Item').each(function(item) { - this.removeItemNode(item); - }.bind(this)); - } - - // Clear the hidden items field - $(this.p.items).value = ''; - - // Add any initial values - if (typeof existing != 'undefined' && existing.length) { - for (var i = 0, l = existing.length; i < l; i++) { - this.addNewItemNode(existing[i]); - } - } - this._enabled = true; - }, - - buildStructure: function() - { - // Build the outter box - var box = new Element('div', { id: this.p.box, className: this.p.boxClass }).setStyle({ position: 'relative' }); - - // The list - where the choosen items are placed as
    • nodes - var list = new Element('ul', { className: this.p.listClass }); - - // The input element and the
    • wraper - var inputListItem = new Element('li', { - className: this.p.listClass + 'Member', - id: this.p.triggerContainer }), - growingInput = new Element('input', { - className: this.p.growingInputClass, - id: this.p.trigger, - name: this.p.trigger, - autocomplete: 'off' }); - - // Create a hidden span node to help calculate the needed size - // of the input field. - this.sizer = new Element('span').setStyle({ float: 'left', display: 'inline-block', position: 'absolute', left: '-1000px' }); - - inputListItem.update(growingInput); - list.update(inputListItem); - box.update(list); - box.insert(this.sizer); - - // Replace the single input element with the new structure and - // move the old element into the structure while making sure it's - // hidden. (Use the long form to play nice with Opera) - box.insert(Element.replace($(this.p.items), box)); - }, - - shutdown: function() - { - this._processValue(); - }, - - _onKeyDown: function(e) - { - // Check for a comma - if (e.keyCode == 188) { - this._processValue(); - e.stop(); - } - }, - - _processValue: function() - { - var value = $F(this.p.trigger).replace(/^,/, '').strip(); - if (value.length) { - this.addNewItemNode(value); - this.p.onAdd(value); - } - }, - - _resize: function() - { - this.sizer.update($(this.p.trigger).value); - newSize = Math.min(this.sizer.getWidth(), $(this.p.box).getWidth()); - newSize = Math.max(newSize, this.p.minTriggerWidth); - $(this.p.trigger).setStyle({ width: newSize + 'px' }); - }, - - // Used as the updateElement callback. - _updateElement: function(item) - { - this.addNewItemNode(item); - this.p.onAdd(item); - }, - - addNewItemNode: function(value) - { - // Don't add if it's already present. - for (var x = 0, len = this.selectedItems.length; x < len; x++) { - if (this.selectedItems[x].rawValue == value) { - $(this.p.trigger).value = ''; - return; - } - } - - var displayValue = this.p.displayFilter(value), - newItem = new Element('li', { className: this.p.listClass + 'Member ' + this.p.listClass + 'Item' }).update(displayValue), - x = new Element('img', { className: 'hordeACItemRemove', src: this.p.deleteIcon }); - x.observe('click', this._removeItemHandler.bindAsEventListener(this)); - newItem.insert(x); - $(this.p.triggerContainer).insert({ before: newItem }); - $(this.p.trigger).value = ''; - - // Add to hidden input field. - if ($(this.p.items).value) { - $(this.p.items).value = $(this.p.items).value + ', ' + value; - } else { - $(this.p.items).value = value; - } - - // ...and keep the selectedItems array up to date. - this.selectedItems.push({ rawValue: value, displayValue: displayValue }); - }, - - removeItemNode: function(item) - { - var value = item.collectTextNodesIgnoreClass('informal'); - for (var x = 0, len = this.selectedItems.length; x < len; x++) { - if (this.selectedItems[x].displayValue.unescapeHTML() == value) { - this.selectedItems.splice(x, 1); - break; - } - } - item.remove(); - this.p.onRemove(value); - }, - - disable: function() - { - if (!this._enabled || !this.initialized) { - return; - } - - this._enabled = false; - $(this.p.box).select('.hordeACItemRemove').invoke('toggle'); - $(this.p.trigger).disable(); - }, - - enable: function() - { - if (this._enabled) { - return; - } - this._enabled = true; - $(this.p.box).select('.hordeACItemRemove').invoke('toggle'); - $(this.p.trigger).enable(); - }, - - _removeItemHandler: function(e) - { - var realValues = [], x, len; - this.removeItemNode(e.element().up()); - for (x = 0, len = this.selectedItems.length; x < len; x++) { - realValues.push(this.selectedItems[x].rawValue); - } - $(this.p.items).value = realValues.join(','); - }, - - _filterChoices: function(c) - { - this.selectedItems.each(function(item) { - c = c.without(item.rawValue); - }); - return c; - }, - - _knvShow: function(l) - { - $(this.p.trigger).stopObserving('blur', this._boundProcessValue); - }, - - _knvHide: function(l) - { - $(this.p.trigger).observe('blur', this._boundProcessValue); - } -}); diff --git a/horde/js/sourceselect.js b/horde/js/sourceselect.js deleted file mode 100644 index a2b75708b..000000000 --- a/horde/js/sourceselect.js +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Provides the javascript for managing the source selection widget. - * - * See the enclosed file COPYING for license information (LGPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. - */ - -var HordeSourceSelectPrefs = { - - // Vars defaulting to null: source_list - - setSourcesHidden: function() - { - var out = [], ss; - - if (this.source_list) { - ss = $F('source_select'); - if (ss) { - this.source_list.each(function(s) { - if (s.source == ss) { - s.selected = $F('selected_sources'); - } - out.push([ s.source, s.selected ]); - }); - } - } else { - $A($('selected_sources').options).slice(1).each(function(s) { - out.push(s.value); - }); - } - - $('sources').setValue(Object.toJSON(out)); - }, - - moveAction: function(from, to) - { - var moved = false; - - $(from).childElements().each(function(c) { - if (c.selected) { - c.remove(); - c.selected = false; - $(to).insert(c); - moved = true; - } - }); - - - if (moved) { - $(to).fire('HordeSourceSelectPrefs:add'); - $(from).fire('HordeSourceSelectPrefs:remove'); - this.setSourcesHidden(); - } - }, - - moveSource: function(e, mode) - { - var sa = $('selected_sources'), sel, tmp; - - if (sa.selectedIndex < 1 || sa.length < 3) { - return; - } - - // Deselect everything but the first selected item - sa.childElements().each(function(s) { - if (sel) { - s.selected = false; - } else if (s.selected) { - sel = s; - } - }); - - switch (mode) { - case 'down': - tmp = sel.next(); - if (tmp) { - sel.remove(); - tmp.insert({ after: sel }); - } - break; - - case 'up': - tmp = sel.previous(); - if (tmp && tmp.value) { - sel.remove(); - tmp.insert({ before: sel }); - } - break; - } - - this.setSourcesHidden(); - e.stop(); - }, - - changeSource: function() - { - var source, - sel = $('selected_sources'), - ss = $('source_select'), - unsel = $('unselected_sources'), - val = $F(ss); - - sel.down().siblings().invoke('remove'); - unsel.down().siblings().invoke('remove'); - - if (val) { - source = this.source_list.find(function(s) { - return val == s.source; - }); - source.selected.each(function(s) { - sel.insert(new Option(s.v, s.l)); - }); - source.unselected.each(function(u) { - unsel.insert(new Option(s.v, s.l)); - }); - } - }, - - onDomLoad: function() - { - if (this.source_list) { - $('source_select').observe('change', this.changeSource.bind(this)); - } - - this.setSourcesHidden(); - - $('addsource').observe('click', this.moveAction.bind(this, 'unselected_sources', 'selected_sources')); - $('removesource').observe('click', this.moveAction.bind(this, 'selected_sources', 'unselected_sources')); - $('moveup').observe('click', this.moveSource.bindAsEventListener(this, 'up')); - $('movedown').observe('click', this.moveSource.bindAsEventListener(this, 'down')); - } - -}; - -document.observe('dom:loaded', HordeSourceSelectPrefs.onDomLoad.bind(HordeSourceSelectPrefs)); diff --git a/horde/js/spellchecker.js b/horde/js/spellchecker.js deleted file mode 100644 index 2b8a8c67b..000000000 --- a/horde/js/spellchecker.js +++ /dev/null @@ -1,284 +0,0 @@ -/** - * This spell checker was inspired by work done by Garrison Locke, but - * was rewritten almost completely by Chuck Hagenbuch to use - * Prototype/Scriptaculous. - * - * Requires: prototype.js (v1.6.1+), KeyNavList.js - * - * Copyright 2005-2010 The Horde Project (http://www.horde.org/) - * - * Custom Events: - * -------------- - * Custom events are triggered on the target element. - * - * 'SpellChecker:after' - * Fired when the spellcheck processing ends. - * - * 'SpellChecker:before' - * Fired before the spellcheck is performed. - * - * 'SpellChecker:noerror' - * Fired when no spellcheck errors are found. - * - * See the enclosed file COPYING for license information (GPL). If you - * did not receive this file, see http://www.fsf.org/copyleft/gpl.html. - */ - -var SpellChecker = Class.create({ - - // Vars used and defaulting to null: - // bad, choices, disabled, htmlAreaParent, lc, locale, reviewDiv, - // statusButton, statusClass, suggestions, target, url - options: {}, - resumeOnDblClick: true, - state: 'CheckSpelling', - - // Options: - // bs = (array) Button states - // locales = (array) List of locales. See KeyNavList for structure. - // sc = (string) Status class - // statusButton = (string/element) DOM ID or element of the status - // button - // target = (string|Element) DOM element containing data - // url = (string) URL of specllchecker handler - initialize: function(opts) - { - var d, lc, tmp, ul; - - this.url = opts.url; - this.target = $(opts.target); - this.statusButton = $(opts.statusButton); - this.buttonStates = opts.bs; - this.statusClass = opts.sc || ''; - this.disabled = false; - - this.options.onComplete = this.onComplete.bind(this); - - document.observe('click', this.onClick.bindAsEventListener(this)); - - if (opts.locales) { - this.lc = new KeyNavList(this.statusButton, { - list: opts.locales, - onChoose: this.setLocale.bindAsEventListener(this) - }); - - this.statusButton.insert({ after: new Element('SPAN', { className: 'spellcheckPopdownImg' }) }); - } - - this.setStatus('CheckSpelling'); - }, - - setLocale: function(locale) - { - this.locale = locale; - }, - - targetValue: function() - { - return Object.isUndefined(this.target.value) - ? this.target.innerHTML - : this.target.value; - }, - - spellCheck: function() - { - this.target.fire('SpellChecker:before'); - - var opts = Object.clone(this.options), - p = $H(), - url = this.url; - - this.setStatus('Checking'); - - p.set(this.target.identify(), this.targetValue()); - opts.parameters = p.toQueryString(); - - if (this.locale) { - url += '/locale=' + this.locale; - } - if (this.htmlAreaParent) { - url += '/html=1'; - } - - new Ajax.Request(url, opts); - }, - - onComplete: function(request) - { - var bad, content, washidden, - i = 0, - result = request.responseJSON; - - if (Object.isUndefined(result)) { - this.setStatus('Error'); - return; - } - - this.suggestions = result.suggestions || []; - - if (!this.suggestions.size()) { - this.setStatus('CheckSpelling'); - this.target.fire('SpellChecker:noerror'); - return; - } - - bad = result.bad || []; - - content = this.targetValue(); - content = this.htmlAreaParent - ? content.replace(/\r?\n/g, '') - : content.replace(/\r?\n/g, '~~~').escapeHTML(); - - $A(bad).each(function(node) { - var re_text = '' + node + ''; - content = content.replace(new RegExp("(?:^|\\b)" + RegExp.escape(node) + "(?:\\b|$)", 'g'), re_text); - - // Go through and see if we matched anything inside a tag (i.e. - // class/spellcheckIncorrect is often matched if using a - // non-English lang). - content = content.replace(new RegExp("(<[^>]*)" + RegExp.escape(re_text) + "([^>]*>)", 'g'), '\$1' + node + '\$2'); - }, this); - - if (!this.reviewDiv) { - this.reviewDiv = new Element('DIV', { className: this.target.readAttribute('class') }).addClassName('spellcheck').setStyle({ overflow: 'auto' }); - if (this.resumeOnDblClick) { - this.reviewDiv.observe('dblclick', this.resume.bind(this)); - } - } - - if (!this.target.visible()) { - this.target.show(); - washidden = true; - } - this.reviewDiv.setStyle({ width: this.target.clientWidth + 'px', height: this.target.clientHeight + 'px'}); - if (washidden) { - this.target.hide(); - } - - if (!this.htmlAreaParent) { - content = content.replace(/~~~/g, '
      '); - } - this.reviewDiv.update(content); - - if (this.htmlAreaParent) { - $(this.htmlAreaParent).insert({ bottom: this.reviewDiv }); - } else { - this.target.hide().insert({ before: this.reviewDiv }); - } - - this.setStatus('ResumeEdit'); - }, - - onClick: function(e) - { - var data = [], index, elt = e.element(); - - if (this.disabled) { - return; - } - - if (elt == this.statusButton) { - switch (this.state) { - case 'CheckSpelling': - this.spellCheck(); - break; - - case 'ResumeEdit': - this.resume(); - break; - } - - e.stop(); - } else if (elt.hasClassName('spellcheckPopdownImg')) { - this.lc.show(); - this.lc.ignoreClick(e); - e.stop(); - } else if (elt.hasClassName('spellcheckIncorrect')) { - index = e.element().readAttribute('index'); - - $A(this.suggestions[index]).each(function(node) { - data.push({ l: node, v: node }); - }); - - if (this.choices) { - this.choices.updateBase(elt); - this.choices.opts.onChoose = function(val) {elt.update(val).writeAttribute({ className: 'spellcheckCorrected' });}; - } else { - this.choices = new KeyNavList(elt, { - esc: true, - onChoose: function(val) { - elt.update(val).writeAttribute({ className: 'spellcheckCorrected' }); - } - }); - } - - this.choices.show(data); - this.choices.ignoreClick(e); - e.stop(); - } - }, - - resume: function() - { - if (!this.reviewDiv) { - return; - } - - var t; - - this.reviewDiv.select('span.spellcheckIncorrect').each(function(n) { - n.replace(n.innerHTML); - }); - - t = this.reviewDiv.innerHTML; - if (!this.htmlAreaParent) { - t = t.replace(/
      /gi, '~~~').unescapeHTML().replace(/~~~/g, "\n"); - } - this.target.setValue(t); - this.target.enable(); - - if (this.resumeOnDblClick) { - this.reviewDiv.stopObserving('dblclick'); - } - this.reviewDiv.remove(); - this.reviewDiv = null; - - this.setStatus('CheckSpelling'); - - if (!this.htmlAreaParent) { - this.target.show(); - } - - this.target.fire('SpellChecker:after'); - }, - - setStatus: function(state) - { - if (!this.statusButton) { - return; - } - - this.state = state; - switch (this.statusButton.tagName) { - case 'INPUT': - this.statusButton.setValue(this.buttonStates[state]); - break; - - case 'A': - this.statusButton.update(this.buttonStates[state]); - break; - } - this.statusButton.className = this.statusClass + ' spellcheck' + state; - }, - - isActive: function() - { - return this.reviewDiv; - }, - - disable: function(disable) - { - this.disabled = disable; - } - -});