From eb830a82787939a1ca999b0b406da88f82dc8280 Mon Sep 17 00:00:00 2001 From: Christopher Rosset Date: Sat, 1 Jul 2023 13:30:02 -0400 Subject: [PATCH] UI Autocomplete Rework This PR rips out the current autocomplete control with its two big legacy dependencies: 1. `prototype.js`[^1] (no updates since 2015) 2. `scriptalicious.js` (no updates since 2010) and replaces them with the much much smaller, modern, and actively maintained https://github.com/kraaden/autocomplete. We use https://github.com/kraaden/autocomplete/commit/589da824ecd48d486e38e43447ecdf03e2e8b30e which is versioned as 8.0.4 on NPM (https://www.npmjs.com/package/autocompleter/v/8.0.4). The end result replicates the functionality almost exactly. Thanks to @reedy, the CSS matches the existing color palette too! The reason for this change is that the current autocomplete framework blocks multiple other improvements. `autocomplete.php` currently returns an HTML fragment as the response which is then embedded into the DOM. Request payload: `src_ap=new` Response payload: ```html ``` This is super tight coupling between the UI and the server. This UI dependency has to be removed before we can upgrade the endpoint. I use `fetch` to send the request to `autocomplete.php` but then process the response as data instead of embedding it in the DOM. I parse the response as XML using `DOM.Parser` (this part will become `JSON.parse()` once the API starts returning JSON) and pass the data to `autocomplete.js` which deals with showing the list of items. [^1]: We still need `prototype.js` in the map widget. See #1224. --- about.html | 4 +- css/autocomplete.min.css | 1 + html/settings.php | 1 - index.php | 22 +- js/autocomplete.min.js | 10 + js/autocomplete.min.js.map | 1 + js/controls.js | 965 ------------------------------------- js/scriptaculous.js | 68 --- js/settings.js | 12 +- openflights.css | 6 +- openflights.js | 181 ++++--- 11 files changed, 154 insertions(+), 1117 deletions(-) create mode 100644 css/autocomplete.min.css create mode 100644 js/autocomplete.min.js create mode 100644 js/autocomplete.min.js.map delete mode 100644 js/controls.js delete mode 100644 js/scriptaculous.js diff --git a/about.html b/about.html index 90d2a986..ce0ad226 100644 --- a/about.html +++ b/about.html @@ -47,9 +47,7 @@

Credits

Open-source packages used to create this software include: OpenLayers, - Prototype, - script.aculo.us, - WiseGuysOnly Autocompletion, + kraaden/autocomplete, Sortable Table, Simple Calendar Widget, Movable Type Scripts (Great Circle), diff --git a/css/autocomplete.min.css b/css/autocomplete.min.css new file mode 100644 index 00000000..73632b43 --- /dev/null +++ b/css/autocomplete.min.css @@ -0,0 +1 @@ +.autocomplete{background:#fff;z-index:1000;font:14px/22px "-apple-system",BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;overflow:auto;box-sizing:border-box;border:1px solid rgba(50,50,50,.6)}.autocomplete *{font:inherit}.autocomplete>div{padding:0 4px}.autocomplete .group{background:#eee}.autocomplete>div.selected,.autocomplete>div:hover:not(.group){background:#81ca91;cursor:pointer} \ No newline at end of file diff --git a/html/settings.php b/html/settings.php index 972bbe4e..da6e902f 100644 --- a/html/settings.php +++ b/html/settings.php @@ -36,7 +36,6 @@ function condOut($arr, $key, $value, $true, $false) { - diff --git a/index.php b/index.php index 84021b33..88935315 100644 --- a/index.php +++ b/index.php @@ -12,6 +12,7 @@ + " type="text/css"> @@ -21,9 +22,7 @@ - - - + @@ -48,8 +47,7 @@

@@ -217,7 +215,7 @@ - "/>
+ "/> @@ -227,7 +225,7 @@ - ">
+ "> @@ -237,7 +235,7 @@ - "/>
+ "/> @@ -283,7 +281,7 @@ - "/>
+ "/> @@ -335,7 +333,7 @@ for ($row = 1; $row <= 4; $row++) { echo " 1 ? "style='display: none;'" : "") . "> \n"; printf( - "\n", + "\n", $row, $row, $row, @@ -352,7 +350,7 @@ _("Airport search") ); printf( - "\n", + "\n", $row, $row, $row, @@ -370,7 +368,7 @@ _("Airport search") ); printf( - "\n", + "\n", $row, $row, $row, diff --git a/js/autocomplete.min.js b/js/autocomplete.min.js new file mode 100644 index 00000000..f803f340 --- /dev/null +++ b/js/autocomplete.min.js @@ -0,0 +1,10 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).autocomplete=t()}(this,(function(){"use strict"; +/** + * Copyright (c) 2016 Denys Krasnoshchok + * + * Homepage: https://smartscheduling.com/en/documentation/autocomplete + * Source: https://github.com/kraaden/autocomplete + * + * MIT License + */return function(e){var t=document,n=e.container||t.createElement("div");n.id=n.id||"autocomplete-"+h();var i,o,r=n.style,a=e.debounceWaitMs||0,u=e.preventSubmit||!1,s=e.disableAutoSelect||!1,c=n.parentElement,d=[],l="",f=2,p=e.showOnFocus,v=0,m=!1,b=!1;if(void 0!==e.minLength&&(f=e.minLength),!e.input)throw new Error("input undefined");var g=e.input;function h(){return Date.now().toString(36)+Math.random().toString(36).substring(2)}function E(){o&&window.clearTimeout(o)}function w(){return!!n.parentNode}function A(){var e;v++,d=[],l="",i=void 0,g.setAttribute("aria-activedescendant",""),g.setAttribute("aria-expanded","false"),(e=n.parentNode)&&e.removeChild(n)}function y(){n.innerHTML="",g.setAttribute("aria-activedescendant","");var o=function(e,n,i){var o=t.createElement("div");return o.textContent=e.label||"",o};e.render&&(o=e.render);var a=function(e,n){var i=t.createElement("div");return i.textContent=e,i};e.renderGroup&&(a=e.renderGroup);var u=t.createDocumentFragment(),s=h();if(d.forEach((function(t,r){if(t.group&&t.group!==s){s=t.group;var c=a(t.group,l);c&&(c.className+=" group",u.appendChild(c))}var d=o(t,l,r);d&&(d.id=n.id+"_"+r,d.setAttribute("role","option"),d.addEventListener("click",(function(n){b=!0;try{e.onSelect(t,g)}finally{b=!1}A(),n.preventDefault(),n.stopPropagation()})),t===i&&(d.className+=" selected",d.setAttribute("aria-selected","true"),g.setAttribute("aria-activedescendant",d.id)),u.appendChild(d))})),n.appendChild(u),d.length<1){if(!e.emptyMsg)return void A();var f=t.createElement("div");f.id=n.id+"_"+h(),f.className="empty",f.textContent=e.emptyMsg,n.appendChild(f),g.setAttribute("aria-activedescendant",f.id)}n.parentNode||(c||t.body).appendChild(n),function(){if(w()){g.setAttribute("aria-expanded","true"),r.height="auto",r.width=g.offsetWidth+"px";var i,o=0;a(),a(),e.customize&&i&&e.customize(g,i,n,o)}function a(){var e=t.documentElement,n=e.clientTop||t.body.clientTop||0,a=e.clientLeft||t.body.clientLeft||0,u=window.pageYOffset||e.scrollTop,s=window.pageXOffset||e.scrollLeft,c=(i=g.getBoundingClientRect()).top+g.offsetHeight+u-n,d=i.left+s-a;r.top=c+"px",r.left=d+"px",(o=window.innerHeight-(i.top+g.offsetHeight))<0&&(o=0),r.top=c+"px",r.bottom="",r.left=d+"px",r.maxHeight=o+"px"}}(),function(){var e=n.getElementsByClassName("selected");if(e.length>0){var t=e[0],i=t.previousElementSibling;if(i&&-1!==i.className.indexOf("group")&&!i.previousElementSibling&&(t=i),t.offsetTopr&&(n.scrollTop+=o-r)}}}()}function L(){w()&&y()}function x(){L()}function T(e){e.target!==n?L():e.preventDefault()}function k(){b||D(0)}function C(e,t){var n,o=w();if("Escape"===t)A();else{if(!o||d.length<1)return;"ArrowUp"===t?(n=d.indexOf(i),i=-1===n?void 0:d[(n+d.length-1)%d.length]):function(){var e=d.indexOf(i);i=d.length<1?void 0:-1===e?d[0]:d[(e+1)%d.length]}(),y()}e.preventDefault(),o&&e.stopPropagation()}function N(t){var n=t.key;switch(n){case"ArrowUp":case"ArrowDown":case"Escape":C(t,n);break;case"Enter":!function(t){if(i){b=!0;try{e.onSelect(i,g)}finally{b=!1}A()}u&&t.preventDefault()}(t)}}function S(){p&&D(1)}function D(e){g.value.length>=f||1===e?(E(),o=window.setTimeout((function(){return H(g.value,e,g.selectionStart||0)}),0===e||2===e?a:0)):A()}function H(t,n,o){if(!m){var r=++v;e.fetch(t,(function(e){v===r&&e&&(l=t,i=(d=e).length<1||s?void 0:d[0],y())}),n,o)}}function O(t){e.keyup?e.keyup({event:t,fetch:function(){return D(0)}}):w()||"ArrowDown"!==t.key||D(0)}function M(t){e.click&&e.click({event:t,fetch:function(){return D(2)}})}function z(){setTimeout((function(){t.activeElement!==g&&A()}),200)}return n.className="autocomplete "+(e.className||""),n.setAttribute("role","listbox"),g.setAttribute("role","combobox"),g.setAttribute("aria-expanded","false"),g.setAttribute("aria-autocomplete","list"),g.setAttribute("aria-controls",n.id),g.setAttribute("aria-owns",n.id),g.setAttribute("aria-activedescendant",""),g.setAttribute("aria-haspopup","listbox"),r.position="absolute",n.addEventListener("mousedown",(function(e){e.stopPropagation(),e.preventDefault()})),n.addEventListener("focus",(function(){return g.focus()})),g.addEventListener("keyup",O),g.addEventListener("click",M),g.addEventListener("keydown",N),g.addEventListener("input",k),g.addEventListener("blur",z),g.addEventListener("focus",S),window.addEventListener("resize",x),t.addEventListener("scroll",T,!0),{destroy:function(){g.removeEventListener("focus",S),g.removeEventListener("keyup",O),g.removeEventListener("click",M),g.removeEventListener("keydown",N),g.removeEventListener("input",k),g.removeEventListener("blur",z),window.removeEventListener("resize",x),t.removeEventListener("scroll",T,!0),g.removeAttribute("role"),g.removeAttribute("aria-expanded"),g.removeAttribute("aria-autocomplete"),g.removeAttribute("aria-controls"),g.removeAttribute("aria-activedescendant"),g.removeAttribute("aria-owns"),g.removeAttribute("aria-haspopup"),E(),A(),m=!0},fetch:function(){H(g.value,3,g.selectionStart||0)}}}})); +//# sourceMappingURL=autocomplete.min.js.map diff --git a/js/autocomplete.min.js.map b/js/autocomplete.min.js.map new file mode 100644 index 00000000..50c2aa38 --- /dev/null +++ b/js/autocomplete.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"autocomplete.min.js","sources":["autocomplete.ts"],"sourcesContent":["/**\n * Copyright (c) 2016 Denys Krasnoshchok\n * \n * Homepage: https://smartscheduling.com/en/documentation/autocomplete\n * Source: https://github.com/kraaden/autocomplete\n * \n * MIT License\n */\n\nexport const enum EventTrigger {\n Keyboard = 0,\n Focus = 1,\n Mouse = 2,\n /**\n * Fetch is triggered manually by calling `fetch` function returned in `AutocompleteResult`\n */\n Manual = 3\n}\n\nexport interface AutocompleteItem {\n label?: string;\n group?: string;\n}\n\nexport interface AutocompleteEvent {\n /**\n * Native event object passed by browser to the event handler\n */\n event: T;\n\n /**\n * Fetch data and display autocomplete\n */\n fetch: () => void;\n}\n\nexport interface AutocompleteSettings {\n /**\n * Autocomplete will be attached to this element.\n */\n input: HTMLInputElement | HTMLTextAreaElement;\n\n /**\n * Provide your own container for the widget.\n * If not specified, a new DIV element will be created.\n */\n container?: HTMLDivElement;\n\n /**\n * This method allows you to override the default rendering function for items.\n * It must return a DIV element or undefined to skip rendering.\n */\n render?: (item: T, currentValue: string, index: number) => HTMLDivElement | undefined;\n\n /**\n * This method allows you to override the default rendering function for item groups.\n * It must return a DIV element or undefined to skip rendering.\n */\n renderGroup?: (name: string, currentValue: string) => HTMLDivElement | undefined;\n\n /**\n * If specified, the autocomplete DOM element will have this class assigned to it.\n */\n className?: string;\n\n /**\n * Specify the minimum text length required to show autocomplete.\n */\n minLength?: number;\n\n /**\n * The message that will be showed when there are no suggestions that match the entered value.\n */\n emptyMsg?: string;\n\n /**\n * This method will be called when user choose an item in autocomplete. The selected item will be passed as the first parameter.\n */\n onSelect: (item: T, input: HTMLInputElement | HTMLTextAreaElement) => void;\n\n /**\n * Show autocomplete on focus event. Focus event will ignore the `minLength` property and will always call `fetch`.\n */\n showOnFocus?: boolean;\n\n /**\n * This method will be called to prepare suggestions and then pass them to autocomplete.\n * @param {string} text - text in the input field\n * @param {(items: T[] | false) => void} update - a callback function that must be called after suggestions are prepared\n * @param {EventTrigger} trigger - type of the event that triggered the fetch\n * @param {number} cursorPos - position of the cursor in the input field\n */\n fetch: (text: string, update: (items: T[] | false) => void, trigger: EventTrigger, cursorPos: number) => void;\n\n /**\n * Enforces that the fetch function will only be called once within the specified time frame (in milliseconds) and\n * delays execution. This prevents flooding your server with AJAX requests.\n */\n debounceWaitMs?: number;\n\n /**\n * Callback for additional autocomplete customization\n * @param {HTMLInputElement | HTMLTextAreaElement} input - input box associated with autocomplete\n * @param {ClientRect | DOMRect} inputRect - size of the input box and its position relative to the viewport\n * @param {HTMLDivElement} container - container with suggestions\n * @param {number} maxHeight - max height that can be used by autocomplete\n */\n customize?: (input: HTMLInputElement | HTMLTextAreaElement, inputRect: ClientRect | DOMRect, container: HTMLDivElement, maxHeight: number) => void;\n\n /**\n * Prevents automatic form submit when ENTER is pressed\n */\n preventSubmit?: boolean;\n\n /**\n * Prevents the first item in the list from being selected automatically. This option allows you\n * to submit a custom text by pressing ENTER even when autocomplete is displayed.\n */\n disableAutoSelect?: boolean;\n\n /**\n * Provide your keyup event handler to display autocomplete when a key is pressed that doesn't modify the content. You can also perform some additional actions.\n */\n keyup?: (e: AutocompleteEvent) => void;\n\n /**\n * Allows to display autocomplete on mouse clicks or perform some additional actions.\n */\n click?: (e: AutocompleteEvent) => void;\n}\n\nexport interface AutocompleteResult {\n /**\n * Remove event handlers, DOM elements and ARIA/accessibility attributes created by the widget.\n */\n destroy: () => void;\n\n /**\n * This function allows to manually start data fetching and display autocomplete. Note that\n * it does not automatically place focus on the input field, so you may need to do so manually\n * in certain situations.\n */\n fetch: () => void;\n}\n\nexport default function autocomplete(settings: AutocompleteSettings): AutocompleteResult {\n\n // just an alias to minimize JS file size\n const doc = document;\n\n const container: HTMLDivElement = settings.container || doc.createElement('div');\n container.id = container.id || 'autocomplete-' + uid();\n const containerStyle = container.style;\n const debounceWaitMs = settings.debounceWaitMs || 0;\n const preventSubmit = settings.preventSubmit || false;\n const disableAutoSelect = settings.disableAutoSelect || false;\n const customContainerParent = container.parentElement;\n\n let items: T[] = [];\n let inputValue = '';\n let minLen = 2;\n const showOnFocus = settings.showOnFocus;\n let selected: T | undefined;\n let fetchCounter = 0;\n let debounceTimer: number | undefined;\n let destroyed = false;\n let suppressAutocomplete = false;\n\n if (settings.minLength !== undefined) {\n minLen = settings.minLength;\n }\n\n if (!settings.input) {\n throw new Error('input undefined');\n }\n\n const input: HTMLInputElement | HTMLTextAreaElement = settings.input;\n\n container.className = 'autocomplete ' + (settings.className || '');\n container.setAttribute('role', 'listbox');\n\n input.setAttribute('role', 'combobox');\n input.setAttribute('aria-expanded', 'false');\n input.setAttribute('aria-autocomplete', 'list');\n input.setAttribute('aria-controls', container.id);\n input.setAttribute('aria-owns', container.id);\n input.setAttribute('aria-activedescendant', '');\n input.setAttribute('aria-haspopup', 'listbox');\n\n // IOS implementation for fixed positioning has many bugs, so we will use absolute positioning\n containerStyle.position = 'absolute';\n\n /**\n * Generate a very complex textual ID that greatly reduces the chance of a collision with another ID or text.\n */\n function uid(): string {\n return Date.now().toString(36) + Math.random().toString(36).substring(2);\n }\n\n /**\n * Detach the container from DOM\n */\n function detach() {\n const parent = container.parentNode;\n if (parent) {\n parent.removeChild(container);\n }\n }\n\n /**\n * Clear debouncing timer if assigned\n */\n function clearDebounceTimer() {\n if (debounceTimer) {\n window.clearTimeout(debounceTimer);\n }\n }\n\n /**\n * Attach the container to DOM\n */\n function attach() {\n if (!container.parentNode) {\n (customContainerParent || doc.body).appendChild(container);\n }\n }\n\n /**\n * Check if container for autocomplete is displayed\n */\n function containerDisplayed(): boolean {\n return !!container.parentNode;\n }\n\n /**\n * Clear autocomplete state and hide container\n */\n function clear() {\n // prevent the update call if there are pending AJAX requests\n fetchCounter++;\n\n items = [];\n inputValue = '';\n selected = undefined;\n input.setAttribute('aria-activedescendant', '');\n input.setAttribute('aria-expanded', 'false');\n detach();\n }\n\n /**\n * Update autocomplete position\n */\n function updatePosition() {\n if (!containerDisplayed()) {\n return;\n }\n\n input.setAttribute('aria-expanded', 'true');\n\n containerStyle.height = 'auto';\n containerStyle.width = input.offsetWidth + 'px';\n\n let maxHeight = 0;\n let inputRect: DOMRect | undefined;\n\n function calc() {\n const docEl = doc.documentElement as HTMLElement;\n const clientTop = docEl.clientTop || doc.body.clientTop || 0;\n const clientLeft = docEl.clientLeft || doc.body.clientLeft || 0;\n const scrollTop = window.pageYOffset || docEl.scrollTop;\n const scrollLeft = window.pageXOffset || docEl.scrollLeft;\n\n inputRect = input.getBoundingClientRect();\n\n const top = inputRect.top + input.offsetHeight + scrollTop - clientTop;\n const left = inputRect.left + scrollLeft - clientLeft;\n\n containerStyle.top = top + 'px';\n containerStyle.left = left + 'px';\n\n maxHeight = window.innerHeight - (inputRect.top + input.offsetHeight);\n\n if (maxHeight < 0) {\n maxHeight = 0;\n }\n\n containerStyle.top = top + 'px';\n containerStyle.bottom = '';\n containerStyle.left = left + 'px';\n containerStyle.maxHeight = maxHeight + 'px';\n }\n\n // the calc method must be called twice, otherwise the calculation may be wrong on resize event (chrome browser)\n calc();\n calc();\n\n if (settings.customize && inputRect) {\n settings.customize(input, inputRect, container, maxHeight);\n }\n }\n\n /**\n * Redraw the autocomplete div element with suggestions\n */\n function update() {\n\n container.innerHTML = '';\n input.setAttribute('aria-activedescendant', '');\n\n // function for rendering autocomplete suggestions\n let render = function (item: T, _: string, __: number): HTMLDivElement | undefined {\n const itemElement = doc.createElement('div');\n itemElement.textContent = item.label || '';\n return itemElement;\n };\n if (settings.render) {\n render = settings.render;\n }\n\n // function to render autocomplete groups\n let renderGroup = function (groupName: string, _: string): HTMLDivElement | undefined {\n const groupDiv = doc.createElement('div');\n groupDiv.textContent = groupName;\n return groupDiv;\n };\n if (settings.renderGroup) {\n renderGroup = settings.renderGroup;\n }\n\n const fragment = doc.createDocumentFragment();\n let prevGroup = uid();\n\n items.forEach(function (item: T, index: number): void {\n if (item.group && item.group !== prevGroup) {\n prevGroup = item.group;\n const groupDiv = renderGroup(item.group, inputValue);\n if (groupDiv) {\n groupDiv.className += ' group';\n fragment.appendChild(groupDiv);\n }\n }\n const div = render(item, inputValue, index);\n if (div) {\n div.id = `${container.id}_${index}`;\n div.setAttribute('role', 'option');\n div.addEventListener('click', function (ev: MouseEvent): void {\n suppressAutocomplete = true;\n try {\n settings.onSelect(item, input);\n } finally {\n suppressAutocomplete = false;\n }\n clear();\n ev.preventDefault();\n ev.stopPropagation();\n });\n if (item === selected) {\n div.className += ' selected';\n div.setAttribute('aria-selected', 'true');\n input.setAttribute('aria-activedescendant', div.id);\n }\n fragment.appendChild(div);\n }\n });\n container.appendChild(fragment);\n if (items.length < 1) {\n if (settings.emptyMsg) {\n const empty = doc.createElement('div');\n empty.id = `${container.id}_${uid()}`;\n empty.className = 'empty';\n empty.textContent = settings.emptyMsg;\n container.appendChild(empty);\n input.setAttribute('aria-activedescendant', empty.id);\n } else {\n clear();\n return;\n }\n }\n\n attach();\n updatePosition();\n\n updateScroll();\n }\n\n function updateIfDisplayed() {\n if (containerDisplayed()) {\n update();\n }\n }\n\n function resizeEventHandler() {\n updateIfDisplayed();\n }\n\n function scrollEventHandler(e: Event) {\n if (e.target !== container) {\n updateIfDisplayed();\n } else {\n e.preventDefault();\n }\n }\n\n function inputEventHandler() {\n if (!suppressAutocomplete) {\n fetch(EventTrigger.Keyboard);\n }\n }\n\n /**\n * Automatically move scroll bar if selected item is not visible\n */\n function updateScroll() {\n const elements = container.getElementsByClassName('selected');\n if (elements.length > 0) {\n let element = elements[0] as HTMLDivElement;\n\n // make group visible\n const previous = element.previousElementSibling as HTMLDivElement;\n if (previous && previous.className.indexOf('group') !== -1 && !previous.previousElementSibling) {\n element = previous;\n }\n\n if (element.offsetTop < container.scrollTop) {\n container.scrollTop = element.offsetTop;\n } else {\n const selectBottom = element.offsetTop + element.offsetHeight;\n const containerBottom = container.scrollTop + container.offsetHeight;\n if (selectBottom > containerBottom) {\n container.scrollTop += selectBottom - containerBottom;\n }\n }\n }\n }\n\n function selectPreviousSuggestion() {\n const index = items.indexOf(selected!);\n\n selected = index === -1\n ? undefined\n : items[(index + items.length - 1) % items.length];\n }\n\n function selectNextSuggestion() {\n const index = items.indexOf(selected!);\n\n selected = items.length < 1\n ? undefined\n : index === -1\n ? items[0]\n : items[(index + 1) % items.length];\n }\n\n function handleArrowAndEscapeKeys(ev: KeyboardEvent, key: 'ArrowUp' | 'ArrowDown' | 'Escape') {\n const containerIsDisplayed = containerDisplayed();\n\n if (key === 'Escape') {\n clear();\n } else {\n if (!containerIsDisplayed || items.length < 1) {\n return;\n }\n key === 'ArrowUp'\n ? selectPreviousSuggestion()\n : selectNextSuggestion();\n update();\n }\n\n ev.preventDefault();\n\n if (containerIsDisplayed) {\n ev.stopPropagation();\n }\n }\n\n function handleEnterKey(ev: KeyboardEvent) {\n if (selected) {\n suppressAutocomplete = true;\n try {\n settings.onSelect(selected, input);\n } finally {\n suppressAutocomplete = false;\n }\n clear();\n }\n\n if (preventSubmit) {\n ev.preventDefault();\n }\n }\n\n function keydownEventHandler(ev: KeyboardEvent) {\n const key = ev.key;\n\n switch (key) {\n case 'ArrowUp':\n case 'ArrowDown':\n case 'Escape':\n handleArrowAndEscapeKeys(ev, key);\n break;\n case 'Enter':\n handleEnterKey(ev);\n break;\n default:\n break;\n }\n }\n\n function focusEventHandler() {\n if (showOnFocus) {\n fetch(EventTrigger.Focus);\n }\n }\n\n function fetch(trigger: EventTrigger) {\n if (input.value.length >= minLen || trigger === EventTrigger.Focus) {\n clearDebounceTimer();\n debounceTimer = window.setTimeout(\n () => startFetch(input.value, trigger, input.selectionStart || 0),\n trigger === EventTrigger.Keyboard || trigger === EventTrigger.Mouse ? debounceWaitMs : 0);\n } else {\n clear();\n }\n }\n\n function startFetch(inputText: string, trigger: EventTrigger, cursorPos: number) {\n if (destroyed) return;\n const savedFetchCounter = ++fetchCounter;\n settings.fetch(inputText, function (elements: T[] | false): void {\n if (fetchCounter === savedFetchCounter && elements) {\n items = elements;\n inputValue = inputText;\n selected = (items.length < 1 || disableAutoSelect) ? undefined : items[0];\n update();\n }\n }, trigger, cursorPos);\n }\n\n function keyupEventHandler(e: KeyboardEvent) {\n if (settings.keyup) {\n settings.keyup({\n event: e,\n fetch: () => fetch(EventTrigger.Keyboard)\n });\n return;\n }\n\n if (!containerDisplayed() && e.key === 'ArrowDown') {\n fetch(EventTrigger.Keyboard);\n }\n }\n\n function clickEventHandler(e: MouseEvent) {\n settings.click && settings.click({\n event: e,\n fetch: () => fetch(EventTrigger.Mouse)\n });\n }\n\n function blurEventHandler() {\n // when an item is selected by mouse click, the blur event will be initiated before the click event and remove DOM elements,\n // so that the click event will never be triggered. In order to avoid this issue, DOM removal should be delayed.\n setTimeout(() => {\n if (doc.activeElement !== input) {\n clear();\n }\n }, 200);\n }\n\n function manualFetch() {\n startFetch(input.value, EventTrigger.Manual, input.selectionStart || 0);\n }\n\n /**\n * Fixes #26: on long clicks focus will be lost and onSelect method will not be called\n */\n container.addEventListener('mousedown', function (evt: Event) {\n evt.stopPropagation();\n evt.preventDefault();\n });\n\n /**\n * Fixes #30: autocomplete closes when scrollbar is clicked in IE\n * See: https://stackoverflow.com/a/9210267/13172349\n */\n container.addEventListener('focus', () => input.focus());\n\n /**\n * This function will remove DOM elements and clear event handlers\n */\n function destroy() {\n input.removeEventListener('focus', focusEventHandler);\n input.removeEventListener('keyup', keyupEventHandler as EventListenerOrEventListenerObject)\n input.removeEventListener('click', clickEventHandler as EventListenerOrEventListenerObject)\n input.removeEventListener('keydown', keydownEventHandler as EventListenerOrEventListenerObject);\n input.removeEventListener('input', inputEventHandler as EventListenerOrEventListenerObject);\n input.removeEventListener('blur', blurEventHandler);\n window.removeEventListener('resize', resizeEventHandler);\n doc.removeEventListener('scroll', scrollEventHandler, true);\n input.removeAttribute('role');\n input.removeAttribute('aria-expanded');\n input.removeAttribute('aria-autocomplete');\n input.removeAttribute('aria-controls');\n input.removeAttribute('aria-activedescendant');\n input.removeAttribute('aria-owns');\n input.removeAttribute('aria-haspopup');\n clearDebounceTimer();\n clear();\n destroyed = true;\n }\n\n // setup event handlers\n input.addEventListener('keyup', keyupEventHandler as EventListenerOrEventListenerObject);\n input.addEventListener('click', clickEventHandler as EventListenerOrEventListenerObject);\n input.addEventListener('keydown', keydownEventHandler as EventListenerOrEventListenerObject);\n input.addEventListener('input', inputEventHandler as EventListenerOrEventListenerObject);\n input.addEventListener('blur', blurEventHandler);\n input.addEventListener('focus', focusEventHandler);\n window.addEventListener('resize', resizeEventHandler);\n doc.addEventListener('scroll', scrollEventHandler, true);\n\n return {\n destroy,\n fetch: manualFetch\n };\n}\n"],"names":["settings","doc","document","container","createElement","id","uid","selected","debounceTimer","containerStyle","style","debounceWaitMs","preventSubmit","disableAutoSelect","customContainerParent","parentElement","items","inputValue","minLen","showOnFocus","fetchCounter","destroyed","suppressAutocomplete","undefined","minLength","input","Error","Date","now","toString","Math","random","substring","clearDebounceTimer","window","clearTimeout","containerDisplayed","parentNode","clear","parent","setAttribute","removeChild","update","innerHTML","render","item","_","__","itemElement","textContent","label","renderGroup","groupName","groupDiv","fragment","createDocumentFragment","prevGroup","forEach","index","group","className","appendChild","div","addEventListener","ev","onSelect","preventDefault","stopPropagation","length","emptyMsg","empty","body","height","width","offsetWidth","inputRect","maxHeight","calc","customize","docEl","documentElement","clientTop","clientLeft","scrollTop","pageYOffset","scrollLeft","pageXOffset","top","getBoundingClientRect","offsetHeight","left","innerHeight","bottom","updatePosition","elements","getElementsByClassName","element","previous","previousElementSibling","indexOf","offsetTop","selectBottom","containerBottom","updateScroll","updateIfDisplayed","resizeEventHandler","scrollEventHandler","e","target","inputEventHandler","fetch","handleArrowAndEscapeKeys","key","containerIsDisplayed","selectNextSuggestion","keydownEventHandler","handleEnterKey","focusEventHandler","trigger","value","setTimeout","startFetch","selectionStart","inputText","cursorPos","savedFetchCounter","keyupEventHandler","keyup","event","clickEventHandler","click","blurEventHandler","activeElement","position","evt","focus","destroy","removeEventListener","removeAttribute"],"mappings":";;;;;;;;uBAiJiEA,GAG7D,IAAMC,EAAMC,SAENC,EAA4BH,EAASG,WAAaF,EAAIG,cAAc,OAC1ED,EAAUE,GAAKF,EAAUE,IAAM,gBAAkBC,IACjD,IAUIC,EAEAC,EAZEC,EAAiBN,EAAUO,MAC3BC,EAAiBX,EAASW,gBAAkB,EAC5CC,EAAgBZ,EAASY,gBAAiB,EAC1CC,EAAoBb,EAASa,oBAAqB,EAClDC,EAAwBX,EAAUY,cAEpCC,EAAa,GACbC,EAAa,GACbC,EAAS,EACPC,EAAcnB,EAASmB,YAEzBC,EAAe,EAEfC,GAAY,EACZC,GAAuB,EAM3B,QAJ2BC,IAAvBvB,EAASwB,YACTN,EAASlB,EAASwB,YAGjBxB,EAASyB,MACV,MAAM,IAAIC,MAAM,mBAGpB,IAAMD,EAAgDzB,EAASyB,MAmB/D,SAASnB,IACL,OAAOqB,KAAKC,MAAMC,SAAS,IAAMC,KAAKC,SAASF,SAAS,IAAIG,UAAU,GAgB1E,SAASC,IACDzB,GACA0B,OAAOC,aAAa3B,GAgB5B,SAAS4B,IACL,QAASjC,EAAUkC,WAMvB,SAASC,IAnCT,IACUC,EAoCNnB,IAEAJ,EAAQ,GACRC,EAAa,GACbV,OAAWgB,EACXE,EAAMe,aAAa,wBAAyB,IAC5Cf,EAAMe,aAAa,gBAAiB,UA1C9BD,EAASpC,EAAUkC,aAErBE,EAAOE,YAAYtC,GAmG3B,SAASuC,IAELvC,EAAUwC,UAAY,GACtBlB,EAAMe,aAAa,wBAAyB,IAG5C,IAAII,EAAS,SAAUC,EAASC,EAAWC,GACvC,IAAMC,EAAc/C,EAAIG,cAAc,OAEtC,OADA4C,EAAYC,YAAcJ,EAAKK,OAAS,GACjCF,GAEPhD,EAAS4C,SACTA,EAAS5C,EAAS4C,QAItB,IAAIO,EAAc,SAAUC,EAAmBN,GAC3C,IAAMO,EAAWpD,EAAIG,cAAc,OAEnC,OADAiD,EAASJ,YAAcG,EAChBC,GAEPrD,EAASmD,cACTA,EAAcnD,EAASmD,aAG3B,IAAMG,EAAWrD,EAAIsD,yBACjBC,EAAYlD,IAmChB,GAjCAU,EAAMyC,SAAQ,SAAUZ,EAASa,GAC7B,GAAIb,EAAKc,OAASd,EAAKc,QAAUH,EAAW,CACxCA,EAAYX,EAAKc,MACjB,IAAMN,EAAWF,EAAYN,EAAKc,MAAO1C,GACrCoC,IACAA,EAASO,WAAa,SACtBN,EAASO,YAAYR,IAG7B,IAAMS,EAAMlB,EAAOC,EAAM5B,EAAYyC,GACjCI,IACAA,EAAIzD,GAAQF,EAAUE,OAAMqD,EAC5BI,EAAItB,aAAa,OAAQ,UACzBsB,EAAIC,iBAAiB,SAAS,SAAUC,GACpC1C,GAAuB,EACvB,IACItB,EAASiE,SAASpB,EAAMpB,WAExBH,GAAuB,EAE3BgB,IACA0B,EAAGE,iBACHF,EAAGG,qBAEHtB,IAAStC,IACTuD,EAAIF,WAAa,YACjBE,EAAItB,aAAa,gBAAiB,QAClCf,EAAMe,aAAa,wBAAyBsB,EAAIzD,KAEpDiD,EAASO,YAAYC,OAG7B3D,EAAU0D,YAAYP,GAClBtC,EAAMoD,OAAS,EAAG,CAClB,IAAIpE,EAASqE,SAST,YADA/B,IAPA,IAAMgC,EAAQrE,EAAIG,cAAc,OAChCkE,EAAMjE,GAAQF,EAAUE,OAAMC,IAC9BgE,EAAMV,UAAY,QAClBU,EAAMrB,YAAcjD,EAASqE,SAC7BlE,EAAU0D,YAAYS,GACtB7C,EAAMe,aAAa,wBAAyB8B,EAAMjE,IAtJrDF,EAAUkC,aACVvB,GAAyBb,EAAIsE,MAAMV,YAAY1D,GA6BxD,WACI,GAAKiC,IAAL,CAIAX,EAAMe,aAAa,gBAAiB,QAEpC/B,EAAe+D,OAAS,OACxB/D,EAAegE,MAAQhD,EAAMiD,YAAc,KAE3C,IACIC,EADAC,EAAY,EA+BhBC,IACAA,IAEI7E,EAAS8E,WAAaH,GACtB3E,EAAS8E,UAAUrD,EAAOkD,EAAWxE,EAAWyE,GAhCpD,SAASC,IACL,IAAME,EAAQ9E,EAAI+E,gBACZC,EAAYF,EAAME,WAAahF,EAAIsE,KAAKU,WAAa,EACrDC,EAAaH,EAAMG,YAAcjF,EAAIsE,KAAKW,YAAc,EACxDC,EAAYjD,OAAOkD,aAAeL,EAAMI,UACxCE,EAAanD,OAAOoD,aAAeP,EAAMM,WAIzCE,GAFNZ,EAAYlD,EAAM+D,yBAEID,IAAM9D,EAAMgE,aAAeN,EAAYF,EACvDS,EAAOf,EAAUe,KAAOL,EAAaH,EAE3CzE,EAAe8E,IAAMA,EAAM,KAC3B9E,EAAeiF,KAAOA,EAAO,MAE7Bd,EAAY1C,OAAOyD,aAAehB,EAAUY,IAAM9D,EAAMgE,eAExC,IACZb,EAAY,GAGhBnE,EAAe8E,IAAMA,EAAM,KAC3B9E,EAAemF,OAAS,GACxBnF,EAAeiF,KAAOA,EAAO,KAC7BjF,EAAemE,UAAYA,EAAY,MA2F3CiB,GAgCJ,WACI,IAAMC,EAAW3F,EAAU4F,uBAAuB,YAClD,GAAID,EAAS1B,OAAS,EAAG,CACrB,IAAI4B,EAAUF,EAAS,GAGjBG,EAAWD,EAAQE,uBAKzB,GAJID,IAAqD,IAAzCA,EAASrC,UAAUuC,QAAQ,WAAoBF,EAASC,yBACpEF,EAAUC,GAGVD,EAAQI,UAAYjG,EAAUgF,UAC9BhF,EAAUgF,UAAYa,EAAQI,cAC3B,CACH,IAAMC,EAAeL,EAAQI,UAAYJ,EAAQP,aAC3Ca,EAAkBnG,EAAUgF,UAAYhF,EAAUsF,aACpDY,EAAeC,IACfnG,EAAUgF,WAAakB,EAAeC,KA/ClDC,GAGJ,SAASC,IACDpE,KACAM,IAIR,SAAS+D,IACLD,IAGJ,SAASE,EAAmBC,GACpBA,EAAEC,SAAWzG,EACbqG,IAEAG,EAAEzC,iBAIV,SAAS2C,IACAvF,GACDwF,KAgDR,SAASC,EAAyB/C,EAAmBgD,GACjD,IAlBMtD,EAkBAuD,EAAuB7E,IAE7B,GAAY,WAAR4E,EACA1E,QACG,CACH,IAAK2E,GAAwBjG,EAAMoD,OAAS,EACxC,OAEI,YAAR4C,GA1BEtD,EAAQ1C,EAAMmF,QAAQ5F,GAE5BA,GAAsB,IAAXmD,OACLnC,EACAP,GAAO0C,EAAQ1C,EAAMoD,OAAS,GAAKpD,EAAMoD,SAGnD,WACI,IAAMV,EAAQ1C,EAAMmF,QAAQ5F,GAE5BA,EAAWS,EAAMoD,OAAS,OACpB7C,GACW,IAAXmC,EACI1C,EAAM,GACNA,GAAO0C,EAAQ,GAAK1C,EAAMoD,QAc1B8C,GACNxE,IAGJsB,EAAGE,iBAEC+C,GACAjD,EAAGG,kBAoBX,SAASgD,EAAoBnD,GACzB,IAAMgD,EAAMhD,EAAGgD,IAEf,OAAQA,GACJ,IAAK,UACL,IAAK,YACL,IAAK,SACDD,EAAyB/C,EAAIgD,GAC7B,MACJ,IAAK,SAzBb,SAAwBhD,GACpB,GAAIzD,EAAU,CACVe,GAAuB,EACvB,IACItB,EAASiE,SAAS1D,EAAUkB,WAE5BH,GAAuB,EAE3BgB,IAGA1B,GACAoD,EAAGE,iBAcCkD,CAAepD,IAO3B,SAASqD,IACDlG,GACA2F,KAIR,SAASA,EAAMQ,GACP7F,EAAM8F,MAAMnD,QAAUlD,OAAUoG,GAChCrF,IACAzB,EAAgB0B,OAAOsF,YACnB,WAAM,OAAAC,EAAWhG,EAAM8F,MAAOD,EAAS7F,EAAMiG,gBAAkB,SAC/DJ,OAAqCA,EAAiC3G,EAAiB,IAE3F2B,IAIR,SAASmF,EAAWE,EAAmBL,EAAuBM,GAC1D,IAAIvG,EAAJ,CACA,IAAMwG,IAAsBzG,EAC5BpB,EAAS8G,MAAMa,GAAW,SAAU7B,GAC5B1E,IAAiByG,GAAqB/B,IAEtC7E,EAAa0G,EACbpH,GAFAS,EAAQ8E,GAEU1B,OAAS,GAAKvD,OAAqBU,EAAYP,EAAM,GACvE0B,OAEL4E,EAASM,IAGhB,SAASE,EAAkBnB,GACnB3G,EAAS+H,MACT/H,EAAS+H,MAAM,CACXC,MAAOrB,EACPG,MAAO,WAAM,OAAAA,QAKhB1E,KAAkC,cAAVuE,EAAEK,KAC3BF,KAIR,SAASmB,EAAkBtB,GACvB3G,EAASkI,OAASlI,EAASkI,MAAM,CAC7BF,MAAOrB,EACPG,MAAO,WAAM,OAAAA,QAIrB,SAASqB,IAGLX,YAAW,WACHvH,EAAImI,gBAAkB3G,GACtBa,MAEL,KAuDP,OA3bAnC,EAAUyD,UAAY,iBAAmB5D,EAAS4D,WAAa,IAC/DzD,EAAUqC,aAAa,OAAQ,WAE/Bf,EAAMe,aAAa,OAAQ,YAC3Bf,EAAMe,aAAa,gBAAiB,SACpCf,EAAMe,aAAa,oBAAqB,QACxCf,EAAMe,aAAa,gBAAiBrC,EAAUE,IAC9CoB,EAAMe,aAAa,YAAarC,EAAUE,IAC1CoB,EAAMe,aAAa,wBAAyB,IAC5Cf,EAAMe,aAAa,gBAAiB,WAGpC/B,EAAe4H,SAAW,WAkY1BlI,EAAU4D,iBAAiB,aAAa,SAAUuE,GAC9CA,EAAInE,kBACJmE,EAAIpE,oBAOR/D,EAAU4D,iBAAiB,SAAS,WAAM,OAAAtC,EAAM8G,WA2BhD9G,EAAMsC,iBAAiB,QAAS+D,GAChCrG,EAAMsC,iBAAiB,QAASkE,GAChCxG,EAAMsC,iBAAiB,UAAWoD,GAClC1F,EAAMsC,iBAAiB,QAAS8C,GAChCpF,EAAMsC,iBAAiB,OAAQoE,GAC/B1G,EAAMsC,iBAAiB,QAASsD,GAChCnF,OAAO6B,iBAAiB,SAAU0C,GAClCxG,EAAI8D,iBAAiB,SAAU2C,GAAoB,GAE5C,CACH8B,QAhCJ,WACI/G,EAAMgH,oBAAoB,QAASpB,GACnC5F,EAAMgH,oBAAoB,QAASX,GACnCrG,EAAMgH,oBAAoB,QAASR,GACnCxG,EAAMgH,oBAAoB,UAAWtB,GACrC1F,EAAMgH,oBAAoB,QAAS5B,GACnCpF,EAAMgH,oBAAoB,OAAQN,GAClCjG,OAAOuG,oBAAoB,SAAUhC,GACrCxG,EAAIwI,oBAAoB,SAAU/B,GAAoB,GACtDjF,EAAMiH,gBAAgB,QACtBjH,EAAMiH,gBAAgB,iBACtBjH,EAAMiH,gBAAgB,qBACtBjH,EAAMiH,gBAAgB,iBACtBjH,EAAMiH,gBAAgB,yBACtBjH,EAAMiH,gBAAgB,aACtBjH,EAAMiH,gBAAgB,iBACtBzG,IACAK,IACAjB,GAAY,GAeZyF,MAtDJ,WACIW,EAAWhG,EAAM8F,QAA4B9F,EAAMiG,gBAAkB,IAuD7E"} \ No newline at end of file diff --git a/js/controls.js b/js/controls.js deleted file mode 100644 index 5137ab51..00000000 --- a/js/controls.js +++ /dev/null @@ -1,965 +0,0 @@ -// script.aculo.us controls.js v1.9.0, Thu Dec 23 16:54:48 -0500 2010 - -// Copyright (c) 2005-2010 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) -// (c) 2005-2010 Ivan Krstic (http://blogs.law.harvard.edu/ivan) -// (c) 2005-2010 Jon Tirsen (http://www.tirsen.com) -// Contributors: -// Richard Livsey -// Rahul Bhargava -// Rob Wills -// -// script.aculo.us is freely distributable under the terms of an MIT-style license. -// For details, see the script.aculo.us web site: http://script.aculo.us/ - -// Autocompleter.Base handles all the autocompletion functionality -// that's independent of the data source for autocompletion. This -// includes drawing the autocompletion menu, observing keyboard -// and mouse events, and similar. -// -// Specific autocompleters need to provide, at the very least, -// a getUpdatedChoices function that will be invoked every time -// the text inside the monitored textbox changes. This method -// should get the text for which to provide autocompletion by -// invoking this.getToken(), NOT by directly accessing -// this.element.value. This is to allow incremental tokenized -// autocompletion. Specific auto-completion logic (AJAX, etc) -// belongs in getUpdatedChoices. -// -// Tokenized incremental autocompletion is enabled automatically -// when an autocompleter is instantiated with the 'tokens' option -// in the options parameter, e.g.: -// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); -// will incrementally autocomplete with a comma as the token. -// Additionally, ',' in the above example can be replaced with -// a token array, e.g. { tokens: [',', '\n'] } which -// enables autocompletion on multiple tokens. This is most -// useful when one of the tokens is \n (a newline), as it -// allows smart autocompletion after linebreaks. - -if(typeof Effect == 'undefined') - throw("controls.js requires including script.aculo.us' effects.js library"); - -var Autocompleter = { }; -Autocompleter.Base = Class.create({ - baseInitialize: function(element, update, options) { - element = $(element); - this.element = element; - this.update = $(update); - this.hasFocus = false; - this.changed = false; - this.active = false; - this.index = 0; - this.entryCount = 0; - this.oldElementValue = this.element.value; - - if(this.setOptions) - this.setOptions(options); - else - this.options = options || { }; - - this.options.paramName = this.options.paramName || this.element.name; - this.options.tokens = this.options.tokens || []; - this.options.frequency = this.options.frequency || 0.4; - this.options.minChars = this.options.minChars || 1; - this.options.onShow = this.options.onShow || - function(element, update){ - if(!update.style.position || update.style.position=='absolute') { - update.style.position = 'absolute'; - Position.clone(element, update, { - setHeight: false, - offsetTop: element.offsetHeight - }); - } - Effect.Appear(update,{duration:0.15}); - }; - this.options.onHide = this.options.onHide || - function(element, update){ new Effect.Fade(update,{duration:0.15}) }; - - if(typeof(this.options.tokens) == 'string') - this.options.tokens = new Array(this.options.tokens); - // Force carriage returns as token delimiters anyway - if (!this.options.tokens.include('\n')) - this.options.tokens.push('\n'); - - this.observer = null; - - this.element.setAttribute('autocomplete','off'); - - Element.hide(this.update); - - Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this)); - Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this)); - }, - - show: function() { - if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); - if(!this.iefix && - (Prototype.Browser.IE) && - (Element.getStyle(this.update, 'position')=='absolute')) { - new Insertion.After(this.update, - ''); - this.iefix = $(this.update.id+'_iefix'); - } - if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); - }, - - fixIEOverlapping: function() { - Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)}); - this.iefix.style.zIndex = 1; - this.update.style.zIndex = 2; - Element.show(this.iefix); - }, - - hide: function() { - this.stopIndicator(); - if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); - if(this.iefix) Element.hide(this.iefix); - }, - - startIndicator: function() { - if(this.options.indicator) Element.show(this.options.indicator); - }, - - stopIndicator: function() { - if(this.options.indicator) Element.hide(this.options.indicator); - }, - - onKeyPress: function(event) { - if(this.active) - switch(event.keyCode) { - case Event.KEY_TAB: - case Event.KEY_RETURN: - this.selectEntry(); - Event.stop(event); - case Event.KEY_ESC: - this.hide(); - this.active = false; - Event.stop(event); - return; - case Event.KEY_LEFT: - case Event.KEY_RIGHT: - return; - case Event.KEY_UP: - this.markPrevious(); - this.render(); - Event.stop(event); - return; - case Event.KEY_DOWN: - this.markNext(); - this.render(); - Event.stop(event); - return; - } - else - if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || - (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return; - - this.changed = true; - this.hasFocus = true; - - if(this.observer) clearTimeout(this.observer); - this.observer = - setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); - }, - - activate: function() { - this.changed = false; - this.hasFocus = true; - this.getUpdatedChoices(); - }, - - onHover: function(event) { - var element = Event.findElement(event, 'LI'); - if(this.index != element.autocompleteIndex) - { - this.index = element.autocompleteIndex; - this.render(); - } - Event.stop(event); - }, - - onClick: function(event) { - var element = Event.findElement(event, 'LI'); - this.index = element.autocompleteIndex; - this.selectEntry(); - this.hide(); - }, - - onBlur: function(event) { - // needed to make click events working - setTimeout(this.hide.bind(this), 250); - this.hasFocus = false; - this.active = false; - }, - - render: function() { - if(this.entryCount > 0) { - for (var i = 0; i < this.entryCount; i++) - this.index==i ? - Element.addClassName(this.getEntry(i),"selected") : - Element.removeClassName(this.getEntry(i),"selected"); - if(this.hasFocus) { - this.show(); - this.active = true; - } - } else { - this.active = false; - this.hide(); - } - }, - - markPrevious: function() { - if(this.index > 0) this.index--; - else this.index = this.entryCount-1; - this.getEntry(this.index).scrollIntoView(true); - }, - - markNext: function() { - if(this.index < this.entryCount-1) this.index++; - else this.index = 0; - this.getEntry(this.index).scrollIntoView(false); - }, - - getEntry: function(index) { - return this.update.firstChild.childNodes[index]; - }, - - getCurrentEntry: function() { - return this.getEntry(this.index); - }, - - selectEntry: function() { - this.active = false; - this.updateElement(this.getCurrentEntry()); - }, - - updateElement: function(selectedElement) { - if (this.options.updateElement) { - this.options.updateElement(selectedElement); - return; - } - var value = ''; - if (this.options.select) { - var nodes = $(selectedElement).select('.' + this.options.select) || []; - if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); - } else - value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); - - var bounds = this.getTokenBounds(); - if (bounds[0] != -1) { - var newValue = this.element.value.substr(0, bounds[0]); - var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/); - if (whitespace) - newValue += whitespace[0]; - this.element.value = newValue + value + this.element.value.substr(bounds[1]); - } else { - this.element.value = value; - } - this.oldElementValue = this.element.value; - this.element.focus(); - - if (this.options.afterUpdateElement) - this.options.afterUpdateElement(this.element, selectedElement); - }, - - updateChoices: function(choices) { - if(!this.changed && this.hasFocus) { - this.update.innerHTML = choices; - Element.cleanWhitespace(this.update); - Element.cleanWhitespace(this.update.down()); - - if(this.update.firstChild && this.update.down().childNodes) { - this.entryCount = - this.update.down().childNodes.length; - for (var i = 0; i < this.entryCount; i++) { - var entry = this.getEntry(i); - entry.autocompleteIndex = i; - this.addObservers(entry); - } - } else { - this.entryCount = 0; - } - - this.stopIndicator(); - this.index = 0; - - if(this.entryCount==1 && this.options.autoSelect) { - this.selectEntry(); - this.hide(); - } else { - this.render(); - } - } - }, - - addObservers: function(element) { - Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); - Event.observe(element, "click", this.onClick.bindAsEventListener(this)); - }, - - onObserverEvent: function() { - this.changed = false; - this.tokenBounds = null; - if(this.getToken().length>=this.options.minChars) { - this.getUpdatedChoices(); - } else { - this.active = false; - this.hide(); - } - this.oldElementValue = this.element.value; - }, - - getToken: function() { - var bounds = this.getTokenBounds(); - return this.element.value.substring(bounds[0], bounds[1]).strip(); - }, - - getTokenBounds: function() { - if (null != this.tokenBounds) return this.tokenBounds; - var value = this.element.value; - if (value.strip().empty()) return [-1, 0]; - var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue); - var offset = (diff == this.oldElementValue.length ? 1 : 0); - var prevTokenPos = -1, nextTokenPos = value.length; - var tp; - for (var index = 0, l = this.options.tokens.length; index < l; ++index) { - tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1); - if (tp > prevTokenPos) prevTokenPos = tp; - tp = value.indexOf(this.options.tokens[index], diff + offset); - if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp; - } - return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]); - } -}); - -Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) { - var boundary = Math.min(newS.length, oldS.length); - for (var index = 0; index < boundary; ++index) - if (newS[index] != oldS[index]) - return index; - return boundary; -}; - -Ajax.Autocompleter = Class.create(Autocompleter.Base, { - initialize: function(element, update, url, options) { - this.baseInitialize(element, update, options); - this.options.asynchronous = true; - this.options.onComplete = this.onComplete.bind(this); - this.options.defaultParams = this.options.parameters || null; - this.url = url; - }, - - getUpdatedChoices: function() { - this.startIndicator(); - - var entry = encodeURIComponent(this.options.paramName) + '=' + - encodeURIComponent(this.getToken()); - - this.options.parameters = this.options.callback ? - this.options.callback(this.element, entry) : entry; - - if(this.options.defaultParams) - this.options.parameters += '&' + this.options.defaultParams; - - new Ajax.Request(this.url, this.options); - }, - - onComplete: function(request) { - this.updateChoices(request.responseText); - } -}); - -// The local array autocompleter. Used when you'd prefer to -// inject an array of autocompletion options into the page, rather -// than sending out Ajax queries, which can be quite slow sometimes. -// -// The constructor takes four parameters. The first two are, as usual, -// the id of the monitored textbox, and id of the autocompletion menu. -// The third is the array you want to autocomplete from, and the fourth -// is the options block. -// -// Extra local autocompletion options: -// - choices - How many autocompletion choices to offer -// -// - partialSearch - If false, the autocompleter will match entered -// text only at the beginning of strings in the -// autocomplete array. Defaults to true, which will -// match text at the beginning of any *word* in the -// strings in the autocomplete array. If you want to -// search anywhere in the string, additionally set -// the option fullSearch to true (default: off). -// -// - fullSsearch - Search anywhere in autocomplete array strings. -// -// - partialChars - How many characters to enter before triggering -// a partial match (unlike minChars, which defines -// how many characters are required to do any match -// at all). Defaults to 2. -// -// - ignoreCase - Whether to ignore case when autocompleting. -// Defaults to true. -// -// It's possible to pass in a custom function as the 'selector' -// option, if you prefer to write your own autocompletion logic. -// In that case, the other options above will not apply unless -// you support them. - -Autocompleter.Local = Class.create(Autocompleter.Base, { - initialize: function(element, update, array, options) { - this.baseInitialize(element, update, options); - this.options.array = array; - }, - - getUpdatedChoices: function() { - this.updateChoices(this.options.selector(this)); - }, - - setOptions: function(options) { - this.options = Object.extend({ - choices: 10, - partialSearch: true, - partialChars: 2, - ignoreCase: true, - fullSearch: false, - selector: function(instance) { - var ret = []; // Beginning matches - var partial = []; // Inside matches - var entry = instance.getToken(); - var count = 0; - - for (var i = 0; i < instance.options.array.length && - ret.length < instance.options.choices ; i++) { - - var elem = instance.options.array[i]; - var foundPos = instance.options.ignoreCase ? - elem.toLowerCase().indexOf(entry.toLowerCase()) : - elem.indexOf(entry); - - while (foundPos != -1) { - if (foundPos == 0 && elem.length != entry.length) { - ret.push("
  • " + elem.substr(0, entry.length) + "" + - elem.substr(entry.length) + "
  • "); - break; - } else if (entry.length >= instance.options.partialChars && - instance.options.partialSearch && foundPos != -1) { - if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { - partial.push("
  • " + elem.substr(0, foundPos) + "" + - elem.substr(foundPos, entry.length) + "" + elem.substr( - foundPos + entry.length) + "
  • "); - break; - } - } - - foundPos = instance.options.ignoreCase ? - elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : - elem.indexOf(entry, foundPos + 1); - - } - } - if (partial.length) - ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)); - return "
      " + ret.join('') + "
    "; - } - }, options || { }); - } -}); - -// AJAX in-place editor and collection editor -// Full rewrite by Christophe Porteneuve (April 2007). - -// Use this if you notice weird scrolling problems on some browsers, -// the DOM might be a bit confused when this gets called so do this -// waits 1 ms (with setTimeout) until it does the activation -Field.scrollFreeActivate = function(field) { - setTimeout(function() { - Field.activate(field); - }, 1); -}; - -Ajax.InPlaceEditor = Class.create({ - initialize: function(element, url, options) { - this.url = url; - this.element = element = $(element); - this.prepareOptions(); - this._controls = { }; - arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!! - Object.extend(this.options, options || { }); - if (!this.options.formId && this.element.id) { - this.options.formId = this.element.id + '-inplaceeditor'; - if ($(this.options.formId)) - this.options.formId = ''; - } - if (this.options.externalControl) - this.options.externalControl = $(this.options.externalControl); - if (!this.options.externalControl) - this.options.externalControlOnly = false; - this._originalBackground = this.element.getStyle('background-color') || 'transparent'; - this.element.title = this.options.clickToEditText; - this._boundCancelHandler = this.handleFormCancellation.bind(this); - this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this); - this._boundFailureHandler = this.handleAJAXFailure.bind(this); - this._boundSubmitHandler = this.handleFormSubmission.bind(this); - this._boundWrapperHandler = this.wrapUp.bind(this); - this.registerListeners(); - }, - checkForEscapeOrReturn: function(e) { - if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return; - if (Event.KEY_ESC == e.keyCode) - this.handleFormCancellation(e); - else if (Event.KEY_RETURN == e.keyCode) - this.handleFormSubmission(e); - }, - createControl: function(mode, handler, extraClasses) { - var control = this.options[mode + 'Control']; - var text = this.options[mode + 'Text']; - if ('button' == control) { - var btn = document.createElement('input'); - btn.type = 'submit'; - btn.value = text; - btn.className = 'editor_' + mode + '_button'; - if ('cancel' == mode) - btn.onclick = this._boundCancelHandler; - this._form.appendChild(btn); - this._controls[mode] = btn; - } else if ('link' == control) { - var link = document.createElement('a'); - link.href = '#'; - link.appendChild(document.createTextNode(text)); - link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler; - link.className = 'editor_' + mode + '_link'; - if (extraClasses) - link.className += ' ' + extraClasses; - this._form.appendChild(link); - this._controls[mode] = link; - } - }, - createEditField: function() { - var text = (this.options.loadTextURL ? this.options.loadingText : this.getText()); - var fld; - if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) { - fld = document.createElement('input'); - fld.type = 'text'; - var size = this.options.size || this.options.cols || 0; - if (0 < size) fld.size = size; - } else { - fld = document.createElement('textarea'); - fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows); - fld.cols = this.options.cols || 40; - } - fld.name = this.options.paramName; - fld.value = text; // No HTML breaks conversion anymore - fld.className = 'editor_field'; - if (this.options.submitOnBlur) - fld.onblur = this._boundSubmitHandler; - this._controls.editor = fld; - if (this.options.loadTextURL) - this.loadExternalText(); - this._form.appendChild(this._controls.editor); - }, - createForm: function() { - var ipe = this; - function addText(mode, condition) { - var text = ipe.options['text' + mode + 'Controls']; - if (!text || condition === false) return; - ipe._form.appendChild(document.createTextNode(text)); - }; - this._form = $(document.createElement('form')); - this._form.id = this.options.formId; - this._form.addClassName(this.options.formClassName); - this._form.onsubmit = this._boundSubmitHandler; - this.createEditField(); - if ('textarea' == this._controls.editor.tagName.toLowerCase()) - this._form.appendChild(document.createElement('br')); - if (this.options.onFormCustomization) - this.options.onFormCustomization(this, this._form); - addText('Before', this.options.okControl || this.options.cancelControl); - this.createControl('ok', this._boundSubmitHandler); - addText('Between', this.options.okControl && this.options.cancelControl); - this.createControl('cancel', this._boundCancelHandler, 'editor_cancel'); - addText('After', this.options.okControl || this.options.cancelControl); - }, - destroy: function() { - if (this._oldInnerHTML) - this.element.innerHTML = this._oldInnerHTML; - this.leaveEditMode(); - this.unregisterListeners(); - }, - enterEditMode: function(e) { - if (this._saving || this._editing) return; - this._editing = true; - this.triggerCallback('onEnterEditMode'); - if (this.options.externalControl) - this.options.externalControl.hide(); - this.element.hide(); - this.createForm(); - this.element.parentNode.insertBefore(this._form, this.element); - if (!this.options.loadTextURL) - this.postProcessEditField(); - if (e) Event.stop(e); - }, - enterHover: function(e) { - if (this.options.hoverClassName) - this.element.addClassName(this.options.hoverClassName); - if (this._saving) return; - this.triggerCallback('onEnterHover'); - }, - getText: function() { - return this.element.innerHTML.unescapeHTML(); - }, - handleAJAXFailure: function(transport) { - this.triggerCallback('onFailure', transport); - if (this._oldInnerHTML) { - this.element.innerHTML = this._oldInnerHTML; - this._oldInnerHTML = null; - } - }, - handleFormCancellation: function(e) { - this.wrapUp(); - if (e) Event.stop(e); - }, - handleFormSubmission: function(e) { - var form = this._form; - var value = $F(this._controls.editor); - this.prepareSubmission(); - var params = this.options.callback(form, value) || ''; - if (Object.isString(params)) - params = params.toQueryParams(); - params.editorId = this.element.id; - if (this.options.htmlResponse) { - var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions); - Object.extend(options, { - parameters: params, - onComplete: this._boundWrapperHandler, - onFailure: this._boundFailureHandler - }); - new Ajax.Updater({ success: this.element }, this.url, options); - } else { - var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); - Object.extend(options, { - parameters: params, - onComplete: this._boundWrapperHandler, - onFailure: this._boundFailureHandler - }); - new Ajax.Request(this.url, options); - } - if (e) Event.stop(e); - }, - leaveEditMode: function() { - this.element.removeClassName(this.options.savingClassName); - this.removeForm(); - this.leaveHover(); - this.element.style.backgroundColor = this._originalBackground; - this.element.show(); - if (this.options.externalControl) - this.options.externalControl.show(); - this._saving = false; - this._editing = false; - this._oldInnerHTML = null; - this.triggerCallback('onLeaveEditMode'); - }, - leaveHover: function(e) { - if (this.options.hoverClassName) - this.element.removeClassName(this.options.hoverClassName); - if (this._saving) return; - this.triggerCallback('onLeaveHover'); - }, - loadExternalText: function() { - this._form.addClassName(this.options.loadingClassName); - this._controls.editor.disabled = true; - var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); - Object.extend(options, { - parameters: 'editorId=' + encodeURIComponent(this.element.id), - onComplete: Prototype.emptyFunction, - onSuccess: function(transport) { - this._form.removeClassName(this.options.loadingClassName); - var text = transport.responseText; - if (this.options.stripLoadedTextTags) - text = text.stripTags(); - this._controls.editor.value = text; - this._controls.editor.disabled = false; - this.postProcessEditField(); - }.bind(this), - onFailure: this._boundFailureHandler - }); - new Ajax.Request(this.options.loadTextURL, options); - }, - postProcessEditField: function() { - var fpc = this.options.fieldPostCreation; - if (fpc) - $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate'](); - }, - prepareOptions: function() { - this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions); - Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks); - [this._extraDefaultOptions].flatten().compact().each(function(defs) { - Object.extend(this.options, defs); - }.bind(this)); - }, - prepareSubmission: function() { - this._saving = true; - this.removeForm(); - this.leaveHover(); - this.showSaving(); - }, - registerListeners: function() { - this._listeners = { }; - var listener; - $H(Ajax.InPlaceEditor.Listeners).each(function(pair) { - listener = this[pair.value].bind(this); - this._listeners[pair.key] = listener; - if (!this.options.externalControlOnly) - this.element.observe(pair.key, listener); - if (this.options.externalControl) - this.options.externalControl.observe(pair.key, listener); - }.bind(this)); - }, - removeForm: function() { - if (!this._form) return; - this._form.remove(); - this._form = null; - this._controls = { }; - }, - showSaving: function() { - this._oldInnerHTML = this.element.innerHTML; - this.element.innerHTML = this.options.savingText; - this.element.addClassName(this.options.savingClassName); - this.element.style.backgroundColor = this._originalBackground; - this.element.show(); - }, - triggerCallback: function(cbName, arg) { - if ('function' == typeof this.options[cbName]) { - this.options[cbName](this, arg); - } - }, - unregisterListeners: function() { - $H(this._listeners).each(function(pair) { - if (!this.options.externalControlOnly) - this.element.stopObserving(pair.key, pair.value); - if (this.options.externalControl) - this.options.externalControl.stopObserving(pair.key, pair.value); - }.bind(this)); - }, - wrapUp: function(transport) { - this.leaveEditMode(); - // Can't use triggerCallback due to backward compatibility: requires - // binding + direct element - this._boundComplete(transport, this.element); - } -}); - -Object.extend(Ajax.InPlaceEditor.prototype, { - dispose: Ajax.InPlaceEditor.prototype.destroy -}); - -Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, { - initialize: function($super, element, url, options) { - this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions; - $super(element, url, options); - }, - - createEditField: function() { - var list = document.createElement('select'); - list.name = this.options.paramName; - list.size = 1; - this._controls.editor = list; - this._collection = this.options.collection || []; - if (this.options.loadCollectionURL) - this.loadCollection(); - else - this.checkForExternalText(); - this._form.appendChild(this._controls.editor); - }, - - loadCollection: function() { - this._form.addClassName(this.options.loadingClassName); - this.showLoadingText(this.options.loadingCollectionText); - var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); - Object.extend(options, { - parameters: 'editorId=' + encodeURIComponent(this.element.id), - onComplete: Prototype.emptyFunction, - onSuccess: function(transport) { - var js = transport.responseText.strip(); - if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check - throw('Server returned an invalid collection representation.'); - this._collection = eval(js); - this.checkForExternalText(); - }.bind(this), - onFailure: this.onFailure - }); - new Ajax.Request(this.options.loadCollectionURL, options); - }, - - showLoadingText: function(text) { - this._controls.editor.disabled = true; - var tempOption = this._controls.editor.firstChild; - if (!tempOption) { - tempOption = document.createElement('option'); - tempOption.value = ''; - this._controls.editor.appendChild(tempOption); - tempOption.selected = true; - } - tempOption.update((text || '').stripScripts().stripTags()); - }, - - checkForExternalText: function() { - this._text = this.getText(); - if (this.options.loadTextURL) - this.loadExternalText(); - else - this.buildOptionList(); - }, - - loadExternalText: function() { - this.showLoadingText(this.options.loadingText); - var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); - Object.extend(options, { - parameters: 'editorId=' + encodeURIComponent(this.element.id), - onComplete: Prototype.emptyFunction, - onSuccess: function(transport) { - this._text = transport.responseText.strip(); - this.buildOptionList(); - }.bind(this), - onFailure: this.onFailure - }); - new Ajax.Request(this.options.loadTextURL, options); - }, - - buildOptionList: function() { - this._form.removeClassName(this.options.loadingClassName); - this._collection = this._collection.map(function(entry) { - return 2 === entry.length ? entry : [entry, entry].flatten(); - }); - var marker = ('value' in this.options) ? this.options.value : this._text; - var textFound = this._collection.any(function(entry) { - return entry[0] == marker; - }.bind(this)); - this._controls.editor.update(''); - var option; - this._collection.each(function(entry, index) { - option = document.createElement('option'); - option.value = entry[0]; - option.selected = textFound ? entry[0] == marker : 0 == index; - option.appendChild(document.createTextNode(entry[1])); - this._controls.editor.appendChild(option); - }.bind(this)); - this._controls.editor.disabled = false; - Field.scrollFreeActivate(this._controls.editor); - } -}); - -//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! **** -//**** This only exists for a while, in order to let **** -//**** users adapt to the new API. Read up on the new **** -//**** API and convert your code to it ASAP! **** - -Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) { - if (!options) return; - function fallback(name, expr) { - if (name in options || expr === undefined) return; - options[name] = expr; - }; - fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' : - options.cancelLink == options.cancelButton == false ? false : undefined))); - fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' : - options.okLink == options.okButton == false ? false : undefined))); - fallback('highlightColor', options.highlightcolor); - fallback('highlightEndColor', options.highlightendcolor); -}; - -Object.extend(Ajax.InPlaceEditor, { - DefaultOptions: { - ajaxOptions: { }, - autoRows: 3, // Use when multi-line w/ rows == 1 - cancelControl: 'link', // 'link'|'button'|false - cancelText: 'cancel', - clickToEditText: 'Click to edit', - externalControl: null, // id|elt - externalControlOnly: false, - fieldPostCreation: 'activate', // 'activate'|'focus'|false - formClassName: 'inplaceeditor-form', - formId: null, // id|elt - highlightColor: '#ffff99', - highlightEndColor: '#ffffff', - hoverClassName: '', - htmlResponse: true, - loadingClassName: 'inplaceeditor-loading', - loadingText: 'Loading...', - okControl: 'button', // 'link'|'button'|false - okText: 'ok', - paramName: 'value', - rows: 1, // If 1 and multi-line, uses autoRows - savingClassName: 'inplaceeditor-saving', - savingText: 'Saving...', - size: 0, - stripLoadedTextTags: false, - submitOnBlur: false, - textAfterControls: '', - textBeforeControls: '', - textBetweenControls: '' - }, - DefaultCallbacks: { - callback: function(form) { - return Form.serialize(form); - }, - onComplete: function(transport, element) { - // For backward compatibility, this one is bound to the IPE, and passes - // the element directly. It was too often customized, so we don't break it. - new Effect.Highlight(element, { - startcolor: this.options.highlightColor, keepBackgroundImage: true }); - }, - onEnterEditMode: null, - onEnterHover: function(ipe) { - ipe.element.style.backgroundColor = ipe.options.highlightColor; - if (ipe._effect) - ipe._effect.cancel(); - }, - onFailure: function(transport, ipe) { - alert('Error communication with the server: ' + transport.responseText.stripTags()); - }, - onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls. - onLeaveEditMode: null, - onLeaveHover: function(ipe) { - ipe._effect = new Effect.Highlight(ipe.element, { - startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor, - restorecolor: ipe._originalBackground, keepBackgroundImage: true - }); - } - }, - Listeners: { - click: 'enterEditMode', - keydown: 'checkForEscapeOrReturn', - mouseover: 'enterHover', - mouseout: 'leaveHover' - } -}); - -Ajax.InPlaceCollectionEditor.DefaultOptions = { - loadingCollectionText: 'Loading options...' -}; - -// Delayed observer, like Form.Element.Observer, -// but waits for delay after last key input -// Ideal for live-search fields - -Form.Element.DelayedObserver = Class.create({ - initialize: function(element, delay, callback) { - this.delay = delay || 0.5; - this.element = $(element); - this.callback = callback; - this.timer = null; - this.lastValue = $F(this.element); - Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); - }, - delayedListener: function(event) { - if(this.lastValue == $F(this.element)) return; - if(this.timer) clearTimeout(this.timer); - this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); - this.lastValue = $F(this.element); - }, - onTimerEvent: function() { - this.timer = null; - this.callback(this.element, $F(this.element)); - } -}); \ No newline at end of file diff --git a/js/scriptaculous.js b/js/scriptaculous.js deleted file mode 100644 index 0ea5c445..00000000 --- a/js/scriptaculous.js +++ /dev/null @@ -1,68 +0,0 @@ -// script.aculo.us scriptaculous.js v1.9.0, Thu Dec 23 16:54:48 -0500 2010 - -// Copyright (c) 2005-2010 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// -// For details, see the script.aculo.us web site: http://script.aculo.us/ - -var Scriptaculous = { - Version: '1.9.0', - require: function(libraryName) { - try{ - // inserting via DOM fails in Safari 2.0, so brute force approach - document.write('