diff options
author | Jean-Philippe Lang <jp_lang@yahoo.fr> | 2007-01-26 17:59:06 +0000 |
---|---|---|
committer | Jean-Philippe Lang <jp_lang@yahoo.fr> | 2007-01-26 17:59:06 +0000 |
commit | 190fef513feaeee9a4f72302690ba95d7d392c4b (patch) | |
tree | ddb2cef869c8638f19558ee4570dd4e9f51c7e36 | |
parent | b9ef10fe3fb6e76e1c913a7c260d48b5feed8260 (diff) | |
download | redmine-190fef513feaeee9a4f72302690ba95d7d392c4b.tar.gz redmine-190fef513feaeee9a4f72302690ba95d7d392c4b.zip |
* project settings split in 4 tabs
* prototype.js upgraded to 1.5.0_rc1
git-svn-id: http://redmine.rubyforge.org/svn/trunk@181 e93f8b46-1217-0410-a6f0-8f06a7374b81
-rw-r--r-- | app/controllers/issue_categories_controller.rb | 6 | ||||
-rw-r--r-- | app/controllers/members_controller.rb | 26 | ||||
-rw-r--r-- | app/controllers/projects_controller.rb | 6 | ||||
-rw-r--r-- | app/controllers/versions_controller.rb | 6 | ||||
-rw-r--r-- | app/views/projects/settings.rhtml | 32 | ||||
-rw-r--r-- | public/javascripts/application.js | 14 | ||||
-rw-r--r-- | public/javascripts/prototype.js | 1132 | ||||
-rw-r--r-- | public/stylesheets/application.css | 24 |
8 files changed, 918 insertions, 328 deletions
diff --git a/app/controllers/issue_categories_controller.rb b/app/controllers/issue_categories_controller.rb index 7f2e4cbe2..1658e98c7 100644 --- a/app/controllers/issue_categories_controller.rb +++ b/app/controllers/issue_categories_controller.rb @@ -22,16 +22,16 @@ class IssueCategoriesController < ApplicationController def edit if request.post? and @category.update_attributes(params[:category]) flash[:notice] = l(:notice_successful_update) - redirect_to :controller => 'projects', :action => 'settings', :id => @project + redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project end end def destroy @category.destroy - redirect_to :controller => 'projects', :action => 'settings', :id => @project
+ redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project
rescue
flash[:notice] = "Categorie can't be deleted" - redirect_to :controller => 'projects', :action => 'settings', :id => @project
+ redirect_to :controller => 'projects', :action => 'settings', :tab => 'categories', :id => @project
end
private
diff --git a/app/controllers/members_controller.rb b/app/controllers/members_controller.rb index f595f2cf6..089a18e6a 100644 --- a/app/controllers/members_controller.rb +++ b/app/controllers/members_controller.rb @@ -16,21 +16,21 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class MembersController < ApplicationController - layout 'base'
- before_filter :find_project, :authorize
+ layout 'base'
+ before_filter :find_project, :authorize
- def edit - if request.post? and @member.update_attributes(params[:member]) - flash[:notice] = l(:notice_successful_update) - redirect_to :controller => 'projects', :action => 'settings', :id => @project
- end - end - - def destroy - @member.destroy
+ def edit + if request.post? and @member.update_attributes(params[:member])
+ flash[:notice] = l(:notice_successful_update) + redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project
+ end + end +
+ def destroy + @member.destroy
flash[:notice] = l(:notice_successful_delete) - redirect_to :controller => 'projects', :action => 'settings', :id => @project - end
+ redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project + end
private
def find_project
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 3979d7f64..48b4be205 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -138,7 +138,7 @@ class ProjectsController < ApplicationController @issue_category = @project.issue_categories.build(params[:issue_category])
if @issue_category.save
flash[:notice] = l(:notice_successful_create)
- redirect_to :action => 'settings', :id => @project
+ redirect_to :action => 'settings', :tab => 'categories', :id => @project
else
settings
render :action => 'settings'
@@ -151,7 +151,7 @@ class ProjectsController < ApplicationController @version = @project.versions.build(params[:version])
if request.post? and @version.save
flash[:notice] = l(:notice_successful_create)
- redirect_to :action => 'settings', :id => @project
+ redirect_to :action => 'settings', :tab => 'versions', :id => @project
end
end
@@ -161,7 +161,7 @@ class ProjectsController < ApplicationController if request.post?
if @member.save
flash[:notice] = l(:notice_successful_create)
- redirect_to :action => 'settings', :id => @project
+ redirect_to :action => 'settings', :tab => 'members', :id => @project
else
settings
render :action => 'settings'
diff --git a/app/controllers/versions_controller.rb b/app/controllers/versions_controller.rb index 5c2dcc7f6..f5fd4e233 100644 --- a/app/controllers/versions_controller.rb +++ b/app/controllers/versions_controller.rb @@ -22,16 +22,16 @@ class VersionsController < ApplicationController def edit if request.post? and @version.update_attributes(params[:version]) flash[:notice] = l(:notice_successful_update) - redirect_to :controller => 'projects', :action => 'settings', :id => @project + redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project end end def destroy
@version.destroy - redirect_to :controller => 'projects', :action => 'settings', :id => @project
+ redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
rescue
flash[:notice] = "Unable to delete version"
- redirect_to :controller => 'projects', :action => 'settings', :id => @project
+ redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project
end
def download
diff --git a/app/views/projects/settings.rhtml b/app/views/projects/settings.rhtml index 605946576..6a2652c07 100644 --- a/app/views/projects/settings.rhtml +++ b/app/views/projects/settings.rhtml @@ -1,13 +1,24 @@ <h2><%=l(:label_settings)%></h2> +<div class="tabs"> +<ul> +<li><%= link_to l(:label_information_plural), {}, :id=> "tab-info", :onclick => "showTab('info'); this.blur(); return false;" %></li> +<li><%= link_to l(:label_member_plural), {}, :id=> "tab-members", :onclick => "showTab('members'); this.blur(); return false;" %></li> +<li><%= link_to l(:label_version_plural), {}, :id=> "tab-versions", :onclick => "showTab('versions'); this.blur(); return false;" %></li> +<li><%= link_to l(:label_issue_category_plural), {}, :id=> "tab-categories", :onclick => "showTab('categories'); this.blur(); return false;" %></li> +</ul> +</div> + +<div id="tab-content-info" class="tab-content"> <% if authorize_for('projects', 'edit') %>
<% labelled_tabular_form_for :project, @project, :url => { :action => "edit", :id => @project } do |f| %> <%= render :partial => 'form', :locals => { :f => f } %> <%= submit_tag l(:button_save) %> <% end %> - <br /> <% end %> -
+</div> + +<div id="tab-content-members" class="tab-content" style="display:none;"> <div class="box"> <h3><%=l(:label_member_plural)%></h3>
<%= error_messages_for 'member' %>
@@ -36,7 +47,7 @@ <% if authorize_for('projects', 'add_member') %>
<hr />
<label><%=l(:label_member_new)%></label><br/>
- <%= start_form_tag :controller => 'projects', :action => 'add_member', :id => @project %>
+ <%= start_form_tag :controller => 'projects', :action => 'add_member', :tab => 'members', :id => @project %>
<select name="member[user_id]">
<%= options_from_collection_for_select @users, "id", "display_name", @member.user_id %> </select>
@@ -47,7 +58,9 @@ <%= end_form_tag %> <% end %>
</div>
-
+</div> + +<div id="tab-content-versions" class="tab-content" style="display:none;"> <div class="box">
<h3><%=l(:label_version_plural)%></h3>
<table>
@@ -68,8 +81,9 @@ <%= link_to l(:label_version_new), :controller => 'projects', :action => 'add_version', :id => @project %>
<% end %>
</div>
-
-
+</div>
+ +<div id="tab-content-categories" class="tab-content" style="display:none;"> <div class="box">
<h3><%=l(:label_issue_category_plural)%></h3> <table>
@@ -95,7 +109,7 @@ </table> <% if authorize_for('projects', 'add_issue_category') %>
<hr />
- <%= start_form_tag :action => 'add_issue_category', :id => @project %> + <%= start_form_tag :action => 'add_issue_category', :tab => 'categories', :id => @project %> <label for="issue_category_name"><%=l(:label_issue_category_new)%></label><br/> <%= error_messages_for 'issue_category' %>
<%= text_field 'issue_category', 'name', :size => 25 %> @@ -103,3 +117,7 @@ <%= end_form_tag %>
<% end %>
</div>
+</div> + +<%= tab = params[:tab] ? h(params[:tab]) : 'info' +javascript_tag "showTab('#{tab}');" %>
\ No newline at end of file diff --git a/public/javascripts/application.js b/public/javascripts/application.js index 3625914ab..c8a790472 100644 --- a/public/javascripts/application.js +++ b/public/javascripts/application.js @@ -16,4 +16,18 @@ function addFileField() { p = document.getElementById("attachments_p");
p.appendChild(document.createElement("br"));
p.appendChild(f);
+}
+
+function showTab(name) {
+ var f = $$('div#content .tab-content');
+ for(var i=0; i<f.length; i++){
+ Element.hide(f[i]);
+ }
+ var f = $$('div.tabs a');
+ for(var i=0; i<f.length; i++){
+ Element.removeClassName(f[i], "selected");
+ }
+ Element.show('tab-content-' + name);
+ Element.addClassName('tab-' + name, "selected");
+ return false;
}
\ No newline at end of file diff --git a/public/javascripts/prototype.js b/public/javascripts/prototype.js index e9ccd3c88..e48775468 100644 --- a/public/javascripts/prototype.js +++ b/public/javascripts/prototype.js @@ -1,21 +1,20 @@ -/* Prototype JavaScript framework, version 1.4.0 +/* Prototype JavaScript framework, version 1.5.0_rc1 * (c) 2005 Sam Stephenson <sam@conio.net> * - * THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff - * against the source tree, available from the Prototype darcs repository. - * * Prototype is freely distributable under the terms of an MIT-style license. - * * For details, see the Prototype web site: http://prototype.conio.net/ * /*--------------------------------------------------------------------------*/ var Prototype = { - Version: '1.4.0', - ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)', + Version: '1.5.0_rc1', + BrowserFeatures: { + XPath: !!document.evaluate + }, + ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)', emptyFunction: function() {}, - K: function(x) {return x} + K: function(x) { return x } } var Class = { @@ -29,22 +28,42 @@ var Class = { var Abstract = new Object(); Object.extend = function(destination, source) { - for (property in source) { + for (var property in source) { destination[property] = source[property]; } return destination; } -Object.inspect = function(object) { - try { - if (object == undefined) return 'undefined'; - if (object == null) return 'null'; - return object.inspect ? object.inspect() : object.toString(); - } catch (e) { - if (e instanceof RangeError) return '...'; - throw e; +Object.extend(Object, { + inspect: function(object) { + try { + if (object === undefined) return 'undefined'; + if (object === null) return 'null'; + return object.inspect ? object.inspect() : object.toString(); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } + }, + + keys: function(object) { + var keys = []; + for (var property in object) + keys.push(property); + return keys; + }, + + values: function(object) { + var values = []; + for (var property in object) + values.push(object[property]); + return values; + }, + + clone: function(object) { + return Object.extend({}, object); } -} +}); Function.prototype.bind = function() { var __method = this, args = $A(arguments), object = args.shift(); @@ -54,9 +73,9 @@ Function.prototype.bind = function() { } Function.prototype.bindAsEventListener = function(object) { - var __method = this; + var __method = this, args = $A(arguments), object = args.shift(); return function(event) { - return __method.call(object, event || window.event); + return __method.apply(object, [( event || window.event)].concat(args).concat($A(arguments))); } } @@ -106,40 +125,69 @@ PeriodicalExecuter.prototype = { }, registerCallback: function() { - setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + stop: function() { + if (!this.timer) return; + clearInterval(this.timer); + this.timer = null; }, onTimerEvent: function() { if (!this.currentlyExecuting) { try { this.currentlyExecuting = true; - this.callback(); + this.callback(this); } finally { this.currentlyExecuting = false; } } } } +Object.extend(String.prototype, { + gsub: function(pattern, replacement) { + var result = '', source = this, match; + replacement = arguments.callee.prepareReplacement(replacement); + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += (replacement(match) || '').toString(); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + }, -/*--------------------------------------------------------------------------*/ + sub: function(pattern, replacement, count) { + replacement = this.gsub.prepareReplacement(replacement); + count = count === undefined ? 1 : count; -function $() { - var elements = new Array(); + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + }, - for (var i = 0; i < arguments.length; i++) { - var element = arguments[i]; - if (typeof element == 'string') - element = document.getElementById(element); + scan: function(pattern, iterator) { + this.gsub(pattern, iterator); + return this; + }, - if (arguments.length == 1) - return element; + truncate: function(length, truncation) { + length = length || 30; + truncation = truncation === undefined ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : this; + }, - elements.push(element); - } + strip: function() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + }, - return elements; -} -Object.extend(String.prototype, { stripTags: function() { return this.replace(/<\/?[^>]+>/gi, ''); }, @@ -157,7 +205,7 @@ Object.extend(String.prototype, { }, evalScripts: function() { - return this.extractScripts().map(eval); + return this.extractScripts().map(function(script) { return eval(script) }); }, escapeHTML: function() { @@ -174,10 +222,13 @@ Object.extend(String.prototype, { }, toQueryParams: function() { - var pairs = this.match(/^\??(.*)$/)[1].split('&'); + var match = this.strip().match(/[^?]*$/)[0]; + if (!match) return {}; + var pairs = match.split('&'); return pairs.inject({}, function(params, pairString) { - var pair = pairString.split('='); - params[pair[0]] = pair[1]; + var pair = pairString.split('='); + var value = pair[1] ? decodeURIComponent(pair[1]) : undefined; + params[decodeURIComponent(pair[0])] = value; return params; }); }, @@ -194,7 +245,7 @@ Object.extend(String.prototype, { ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) : oStringList[0]; - for (var i = 1, len = oStringList.length; i < len; i++) { + for (var i = 1, length = oStringList.length; i < length; i++) { var s = oStringList[i]; camelizedString += s.charAt(0).toUpperCase() + s.substring(1); } @@ -202,13 +253,40 @@ Object.extend(String.prototype, { return camelizedString; }, - inspect: function() { - return "'" + this.replace('\\', '\\\\').replace("'", '\\\'') + "'"; + inspect: function(useDoubleQuotes) { + var escapedString = this.replace(/\\/g, '\\\\'); + if (useDoubleQuotes) + return '"' + escapedString.replace(/"/g, '\\"') + '"'; + else + return "'" + escapedString.replace(/'/g, '\\\'') + "'"; } }); +String.prototype.gsub.prepareReplacement = function(replacement) { + if (typeof replacement == 'function') return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; +} + String.prototype.parseQuery = String.prototype.toQueryParams; +var Template = Class.create(); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; +Template.prototype = { + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + return this.template.gsub(this.pattern, function(match) { + var before = match[1]; + if (before == '\\') return match[2]; + return before + (object[match[3]] || '').toString(); + }); + } +} + var $break = new Object(); var $continue = new Object(); @@ -226,6 +304,14 @@ var Enumerable = { } catch (e) { if (e != $break) throw e; } + return this; + }, + + eachSlice: function(number, iterator) { + var index = -number, slices = [], array = this.toArray(); + while ((index += number) < array.length) + slices.push(array.slice(index, index+number)); + return slices.collect(iterator || Prototype.K); }, all: function(iterator) { @@ -238,7 +324,7 @@ var Enumerable = { }, any: function(iterator) { - var result = true; + var result = false; this.each(function(value, index) { if (result = !!(iterator || Prototype.K)(value, index)) throw $break; @@ -254,7 +340,7 @@ var Enumerable = { return results; }, - detect: function (iterator) { + detect: function(iterator) { var result; this.each(function(value, index) { if (iterator(value, index)) { @@ -295,6 +381,15 @@ var Enumerable = { return found; }, + inGroupsOf: function(number, fillWith) { + fillWith = fillWith || null; + var results = this.eachSlice(number); + if (results.length > 0) (number - results.last().length).times(function() { + results.last().push(fillWith) + }); + return results; + }, + inject: function(memo, iterator) { this.each(function(value, index) { memo = iterator(memo, value, index); @@ -313,7 +408,7 @@ var Enumerable = { var result; this.each(function(value, index) { value = (iterator || Prototype.K)(value, index); - if (value >= (result || value)) + if (result == undefined || value >= result) result = value; }); return result; @@ -323,7 +418,7 @@ var Enumerable = { var result; this.each(function(value, index) { value = (iterator || Prototype.K)(value, index); - if (value <= (result || value)) + if (result == undefined || value < result) result = value; }); return result; @@ -375,8 +470,7 @@ var Enumerable = { var collections = [this].concat(args).map($A); return this.map(function(value, index) { - iterator(value = collections.pluck(index)); - return value; + return iterator(collections.pluck(index)); }); }, @@ -398,7 +492,7 @@ var $A = Array.from = function(iterable) { return iterable.toArray(); } else { var results = []; - for (var i = 0; i < iterable.length; i++) + for (var i = 0, length = iterable.length; i < length; i++) results.push(iterable[i]); return results; } @@ -406,11 +500,12 @@ var $A = Array.from = function(iterable) { Object.extend(Array.prototype, Enumerable); -Array.prototype._reverse = Array.prototype.reverse; +if (!Array.prototype._reverse) + Array.prototype._reverse = Array.prototype.reverse; Object.extend(Array.prototype, { _each: function(iterator) { - for (var i = 0; i < this.length; i++) + for (var i = 0, length = this.length; i < length; i++) iterator(this[i]); }, @@ -435,7 +530,7 @@ Object.extend(Array.prototype, { flatten: function() { return this.inject([], function(array, value) { - return array.concat(value.constructor == Array ? + return array.concat(value && value.constructor == Array ? value.flatten() : [value]); }); }, @@ -448,7 +543,7 @@ Object.extend(Array.prototype, { }, indexOf: function(object) { - for (var i = 0; i < this.length; i++) + for (var i = 0, length = this.length; i < length; i++) if (this[i] == object) return i; return -1; }, @@ -457,21 +552,29 @@ Object.extend(Array.prototype, { return (inline !== false ? this : this.toArray())._reverse(); }, - shift: function() { - var result = this[0]; - for (var i = 0; i < this.length - 1; i++) - this[i] = this[i + 1]; - this.length--; - return result; + reduce: function() { + return this.length > 1 ? this : this[0]; + }, + + uniq: function() { + return this.inject([], function(array, value) { + return array.include(value) ? array : array.concat([value]); + }); + }, + + clone: function() { + return [].concat(this); }, inspect: function() { return '[' + this.map(Object.inspect).join(', ') + ']'; } }); + +Array.prototype.toArray = Array.prototype.clone; var Hash = { _each: function(iterator) { - for (key in this) { + for (var key in this) { var value = this[key]; if (typeof value == 'function') continue; @@ -491,7 +594,7 @@ var Hash = { }, merge: function(hash) { - return $H(hash).inject($H(this), function(mergedHash, pair) { + return $H(hash).inject(this, function(mergedHash, pair) { mergedHash[pair.key] = pair.value; return mergedHash; }); @@ -499,6 +602,8 @@ var Hash = { toQueryString: function() { return this.map(function(pair) { + if (!pair.value && pair.value !== 0) pair[1] = ''; + if (!pair.key) return; return pair.map(encodeURIComponent).join('='); }).join('&'); }, @@ -527,10 +632,10 @@ Object.extend(ObjectRange.prototype, { _each: function(iterator) { var value = this.start; - do { + while (this.include(value)) { iterator(value); value = value.succ(); - } while (this.include(value)); + } }, include: function(value) { @@ -549,9 +654,9 @@ var $R = function(start, end, exclusive) { var Ajax = { getTransport: function() { return Try.these( + function() {return new XMLHttpRequest()}, function() {return new ActiveXObject('Msxml2.XMLHTTP')}, - function() {return new ActiveXObject('Microsoft.XMLHTTP')}, - function() {return new XMLHttpRequest()} + function() {return new ActiveXObject('Microsoft.XMLHTTP')} ) || false; }, @@ -565,18 +670,18 @@ Ajax.Responders = { this.responders._each(iterator); }, - register: function(responderToAdd) { - if (!this.include(responderToAdd)) - this.responders.push(responderToAdd); + register: function(responder) { + if (!this.include(responder)) + this.responders.push(responder); }, - unregister: function(responderToRemove) { - this.responders = this.responders.without(responderToRemove); + unregister: function(responder) { + this.responders = this.responders.without(responder); }, dispatch: function(callback, request, transport, json) { this.each(function(responder) { - if (responder[callback] && typeof responder[callback] == 'function') { + if (typeof responder[callback] == 'function') { try { responder[callback].apply(responder, [request, transport, json]); } catch (e) {} @@ -591,7 +696,6 @@ Ajax.Responders.register({ onCreate: function() { Ajax.activeRequestCount++; }, - onComplete: function() { Ajax.activeRequestCount--; } @@ -603,19 +707,15 @@ Ajax.Base.prototype = { this.options = { method: 'post', asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + encoding: 'UTF-8', parameters: '' } Object.extend(this.options, options || {}); - }, - - responseIsSuccess: function() { - return this.transport.status == undefined - || this.transport.status == 0 - || (this.transport.status >= 200 && this.transport.status < 300); - }, - responseIsFailure: function() { - return !this.responseIsSuccess(); + this.options.method = this.options.method.toLowerCase(); + this.options.parameters = $H(typeof this.options.parameters == 'string' ? + this.options.parameters.toQueryParams() : this.options.parameters); } } @@ -631,111 +731,145 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), { }, request: function(url) { - var parameters = this.options.parameters || ''; - if (parameters.length > 0) parameters += '&_='; + var params = this.options.parameters; + if (params.any()) params['_'] = ''; - try { - this.url = url; - if (this.options.method == 'get' && parameters.length > 0) - this.url += (this.url.match(/\?/) ? '&' : '?') + parameters; + if (!['get', 'post'].include(this.options.method)) { + // simulate other verbs over post + params['_method'] = this.options.method; + this.options.method = 'post'; + } + + this.url = url; + + // when GET, append parameters to URL + if (this.options.method == 'get' && params.any()) + this.url += (this.url.indexOf('?') >= 0 ? '&' : '?') + + params.toQueryString(); + try { Ajax.Responders.dispatch('onCreate', this, this.transport); - this.transport.open(this.options.method, this.url, - this.options.asynchronous); + this.transport.open(this.options.method.toUpperCase(), this.url, + this.options.asynchronous, this.options.username, + this.options.password); - if (this.options.asynchronous) { - this.transport.onreadystatechange = this.onStateChange.bind(this); - setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10); - } + if (this.options.asynchronous) + setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10); + this.transport.onreadystatechange = this.onStateChange.bind(this); this.setRequestHeaders(); - var body = this.options.postBody ? this.options.postBody : parameters; - this.transport.send(this.options.method == 'post' ? body : null); + var body = this.options.method == 'post' ? + (this.options.postBody || params.toQueryString()) : null; - } catch (e) { + this.transport.send(body); + + /* Force Firefox to handle ready state 4 for synchronous requests */ + if (!this.options.asynchronous && this.transport.overrideMimeType) + this.onStateChange(); + } + catch (e) { this.dispatchException(e); } }, + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1) + this.respondToReadyState(this.transport.readyState); + }, + setRequestHeaders: function() { - var requestHeaders = - ['X-Requested-With', 'XMLHttpRequest', - 'X-Prototype-Version', Prototype.Version]; + var headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Prototype-Version': Prototype.Version, + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }; if (this.options.method == 'post') { - requestHeaders.push('Content-type', - 'application/x-www-form-urlencoded'); + headers['Content-type'] = this.options.contentType + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); - /* Force "Connection: close" for Mozilla browsers to work around - * a bug where XMLHttpReqeuest sends an incorrect Content-length - * header. See Mozilla Bugzilla #246651. + /* Force "Connection: close" for older Mozilla browsers to work + * around a bug where XMLHttpRequest sends an incorrect + * Content-length header. See Mozilla Bugzilla #246651. */ - if (this.transport.overrideMimeType) - requestHeaders.push('Connection', 'close'); + if (this.transport.overrideMimeType && + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) + headers['Connection'] = 'close'; } - if (this.options.requestHeaders) - requestHeaders.push.apply(requestHeaders, this.options.requestHeaders); + // user-defined headers + if (typeof this.options.requestHeaders == 'object') { + var extras = this.options.requestHeaders; - for (var i = 0; i < requestHeaders.length; i += 2) - this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]); - }, - - onStateChange: function() { - var readyState = this.transport.readyState; - if (readyState != 1) - this.respondToReadyState(this.transport.readyState); - }, - - header: function(name) { - try { - return this.transport.getResponseHeader(name); - } catch (e) {} - }, + if (typeof extras.push == 'function') + for (var i = 0; i < extras.length; i += 2) + headers[extras[i]] = extras[i+1]; + else + $H(extras).each(function(pair) { headers[pair.key] = pair.value }); + } - evalJSON: function() { - try { - return eval(this.header('X-JSON')); - } catch (e) {} + for (var name in headers) + this.transport.setRequestHeader(name, headers[name]); }, - evalResponse: function() { - try { - return eval(this.transport.responseText); - } catch (e) { - this.dispatchException(e); - } + success: function() { + return !this.transport.status + || (this.transport.status >= 200 && this.transport.status < 300); }, respondToReadyState: function(readyState) { - var event = Ajax.Request.Events[readyState]; + var state = Ajax.Request.Events[readyState]; var transport = this.transport, json = this.evalJSON(); - if (event == 'Complete') { + if (state == 'Complete') { try { (this.options['on' + this.transport.status] - || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')] + || this.options['on' + (this.success() ? 'Success' : 'Failure')] || Prototype.emptyFunction)(transport, json); } catch (e) { this.dispatchException(e); } - - if ((this.header('Content-type') || '').match(/^text\/javascript/i)) - this.evalResponse(); } try { - (this.options['on' + event] || Prototype.emptyFunction)(transport, json); - Ajax.Responders.dispatch('on' + event, this, transport, json); + (this.options['on' + state] || Prototype.emptyFunction)(transport, json); + Ajax.Responders.dispatch('on' + state, this, transport, json); } catch (e) { this.dispatchException(e); } - /* Avoid memory leak in MSIE: clean up the oncomplete event handler */ - if (event == 'Complete') + if (state == 'Complete') { + if ((this.getHeader('Content-type') || '').strip(). + match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i)) + this.evalResponse(); + + // avoid memory leak in MSIE: clean up this.transport.onreadystatechange = Prototype.emptyFunction; + } + }, + + getHeader: function(name) { + try { + return this.transport.getResponseHeader(name); + } catch (e) { return null } + }, + + evalJSON: function() { + try { + var json = this.getHeader('X-JSON'); + return json ? eval('(' + json + ')') : null; + } catch (e) { return null } + }, + + evalResponse: function() { + try { + return eval(this.transport.responseText); + } catch (e) { + this.dispatchException(e); + } }, dispatchException: function(exception) { @@ -748,41 +882,37 @@ Ajax.Updater = Class.create(); Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { initialize: function(container, url, options) { - this.containers = { - success: container.success ? $(container.success) : $(container), - failure: container.failure ? $(container.failure) : - (container.success ? null : $(container)) + this.container = { + success: (container.success || container), + failure: (container.failure || (container.success ? null : container)) } this.transport = Ajax.getTransport(); this.setOptions(options); var onComplete = this.options.onComplete || Prototype.emptyFunction; - this.options.onComplete = (function(transport, object) { + this.options.onComplete = (function(transport, param) { this.updateContent(); - onComplete(transport, object); + onComplete(transport, param); }).bind(this); this.request(url); }, updateContent: function() { - var receiver = this.responseIsSuccess() ? - this.containers.success : this.containers.failure; + var receiver = this.container[this.success() ? 'success' : 'failure']; var response = this.transport.responseText; - if (!this.options.evalScripts) - response = response.stripScripts(); + if (!this.options.evalScripts) response = response.stripScripts(); - if (receiver) { - if (this.options.insertion) { + if (receiver = $(receiver)) { + if (this.options.insertion) new this.options.insertion(receiver, response); - } else { - Element.update(receiver, response); - } + else + receiver.update(response); } - if (this.responseIsSuccess()) { + if (this.success()) { if (this.onComplete) setTimeout(this.onComplete.bind(this), 10); } @@ -811,7 +941,7 @@ Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { }, stop: function() { - this.updater.onComplete = undefined; + this.updater.options.onComplete = undefined; clearTimeout(this.timer); (this.onComplete || Prototype.emptyFunction).apply(this, arguments); }, @@ -831,55 +961,208 @@ Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { this.updater = new Ajax.Updater(this.container, this.url, this.options); } }); +function $(element) { + if (arguments.length > 1) { + for (var i = 0, elements = [], length = arguments.length; i < length; i++) + elements.push($(arguments[i])); + return elements; + } + if (typeof element == 'string') + element = document.getElementById(element); + return Element.extend(element); +} + +if (Prototype.BrowserFeatures.XPath) { + document._getElementsByXPath = function(expression, parentElement) { + var results = []; + var query = document.evaluate(expression, $(parentElement) || document, + null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0, len = query.snapshotLength; i < len; i++) + results.push(query.snapshotItem(i)); + return results; + } +} + document.getElementsByClassName = function(className, parentElement) { - var children = ($(parentElement) || document.body).getElementsByTagName('*'); - return $A(children).inject([], function(elements, child) { - if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) - elements.push(child); + if (Prototype.BrowserFeatures.XPath) { + var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]"; + return document._getElementsByXPath(q, parentElement); + } else { + var children = ($(parentElement) || document.body).getElementsByTagName('*'); + var elements = [], child; + for (var i = 0, length = children.length; i < length; i++) { + child = children[i]; + if (Element.hasClassName(child, className)) + elements.push(Element.extend(child)); + } return elements; - }); + } } /*--------------------------------------------------------------------------*/ -if (!window.Element) { +if (!window.Element) var Element = new Object(); + +Element.extend = function(element) { + if (!element) return; + if (_nativeExtensions || element.nodeType == 3) return element; + + if (!element._extended && element.tagName && element != window) { + var methods = Object.clone(Element.Methods), cache = Element.extend.cache; + + if (element.tagName == 'FORM') + Object.extend(methods, Form.Methods); + if (['INPUT', 'TEXTAREA', 'SELECT'].include(element.tagName)) + Object.extend(methods, Form.Element.Methods); + + for (var property in methods) { + var value = methods[property]; + if (typeof value == 'function') + element[property] = cache.findOrStore(value); + } + + var methods = Object.clone(Element.Methods.Simulated), cache = Element.extend.cache; + for (var property in methods) { + var value = methods[property]; + if ('function' == typeof value && !(property in element)) + element[property] = cache.findOrStore(value); + } + } + + element._extended = true; + return element; } -Object.extend(Element, { +Element.extend.cache = { + findOrStore: function(value) { + return this[value] = this[value] || function() { + return value.apply(null, [this].concat($A(arguments))); + } + } +} + +Element.Methods = { visible: function(element) { return $(element).style.display != 'none'; }, - toggle: function() { - for (var i = 0; i < arguments.length; i++) { - var element = $(arguments[i]); - Element[Element.visible(element) ? 'hide' : 'show'](element); - } + toggle: function(element) { + element = $(element); + Element[Element.visible(element) ? 'hide' : 'show'](element); + return element; }, - hide: function() { - for (var i = 0; i < arguments.length; i++) { - var element = $(arguments[i]); - element.style.display = 'none'; - } + hide: function(element) { + $(element).style.display = 'none'; + return element; }, - show: function() { - for (var i = 0; i < arguments.length; i++) { - var element = $(arguments[i]); - element.style.display = ''; - } + show: function(element) { + $(element).style.display = ''; + return element; }, remove: function(element) { element = $(element); element.parentNode.removeChild(element); + return element; }, update: function(element, html) { + html = typeof html == 'undefined' ? '' : html.toString(); $(element).innerHTML = html.stripScripts(); setTimeout(function() {html.evalScripts()}, 10); + return element; + }, + + replace: function(element, html) { + element = $(element); + if (element.outerHTML) { + element.outerHTML = html.stripScripts(); + } else { + var range = element.ownerDocument.createRange(); + range.selectNodeContents(element); + element.parentNode.replaceChild( + range.createContextualFragment(html.stripScripts()), element); + } + setTimeout(function() {html.evalScripts()}, 10); + return element; + }, + + inspect: function(element) { + element = $(element); + var result = '<' + element.tagName.toLowerCase(); + $H({'id': 'id', 'className': 'class'}).each(function(pair) { + var property = pair.first(), attribute = pair.last(); + var value = (element[property] || '').toString(); + if (value) result += ' ' + attribute + '=' + value.inspect(true); + }); + return result + '>'; + }, + + recursivelyCollect: function(element, property) { + element = $(element); + var elements = []; + while (element = element[property]) + if (element.nodeType == 1) + elements.push(Element.extend(element)); + return elements; + }, + + ancestors: function(element) { + return $(element).recursivelyCollect('parentNode'); + }, + + descendants: function(element) { + element = $(element); + return $A(element.getElementsByTagName('*')); + }, + + previousSiblings: function(element) { + return $(element).recursivelyCollect('previousSibling'); + }, + + nextSiblings: function(element) { + return $(element).recursivelyCollect('nextSibling'); + }, + + siblings: function(element) { + element = $(element); + return element.previousSiblings().reverse().concat(element.nextSiblings()); + }, + + match: function(element, selector) { + element = $(element); + if (typeof selector == 'string') + selector = new Selector(selector); + return selector.match(element); + }, + + up: function(element, expression, index) { + return Selector.findElement($(element).ancestors(), expression, index); + }, + + down: function(element, expression, index) { + return Selector.findElement($(element).descendants(), expression, index); + }, + + previous: function(element, expression, index) { + return Selector.findElement($(element).previousSiblings(), expression, index); + }, + + next: function(element, expression, index) { + return Selector.findElement($(element).nextSiblings(), expression, index); + }, + + getElementsBySelector: function() { + var args = $A(arguments), element = $(args.shift()); + return Selector.findChildElements(element, args); + }, + + getElementsByClassName: function(element, className) { + element = $(element); + return document.getElementsByClassName(className, element); }, getHeight: function(element) { @@ -893,38 +1176,66 @@ Object.extend(Element, { hasClassName: function(element, className) { if (!(element = $(element))) return; - return Element.classNames(element).include(className); + var elementClassName = element.className; + if (elementClassName.length == 0) return false; + if (elementClassName == className || + elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) + return true; + return false; }, addClassName: function(element, className) { if (!(element = $(element))) return; - return Element.classNames(element).add(className); + Element.classNames(element).add(className); + return element; }, removeClassName: function(element, className) { if (!(element = $(element))) return; - return Element.classNames(element).remove(className); + Element.classNames(element).remove(className); + return element; + }, + + observe: function() { + Event.observe.apply(Event, arguments); + return $A(arguments).first(); + }, + + stopObserving: function() { + Event.stopObserving.apply(Event, arguments); + return $A(arguments).first(); }, // removes whitespace-only text node children cleanWhitespace: function(element) { element = $(element); - for (var i = 0; i < element.childNodes.length; i++) { - var node = element.childNodes[i]; + var node = element.firstChild; + while (node) { + var nextNode = node.nextSibling; if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) - Element.remove(node); + element.removeChild(node); + node = nextNode; } + return element; }, empty: function(element) { return $(element).innerHTML.match(/^\s*$/); }, + childOf: function(element, ancestor) { + element = $(element), ancestor = $(ancestor); + while (element = element.parentNode) + if (element == ancestor) return true; + return false; + }, + scrollTo: function(element) { element = $(element); var x = element.x ? element.x : element.offsetLeft, y = element.y ? element.y : element.offsetTop; window.scrollTo(x, y); + return element; }, getStyle: function(element, style) { @@ -947,8 +1258,9 @@ Object.extend(Element, { setStyle: function(element, style) { element = $(element); - for (name in style) + for (var name in style) element.style[name.camelize()] = style[name]; + return element; }, getDimensions: function(element) { @@ -985,6 +1297,7 @@ Object.extend(Element, { element.style.left = 0; } } + return element; }, undoPositioned: function(element) { @@ -997,23 +1310,105 @@ Object.extend(Element, { element.style.bottom = element.style.right = ''; } + return element; }, makeClipping: function(element) { element = $(element); - if (element._overflow) return; - element._overflow = element.style.overflow; + if (element._overflow) return element; + element._overflow = element.style.overflow || 'auto'; if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') element.style.overflow = 'hidden'; + return element; }, undoClipping: function(element) { element = $(element); - if (element._overflow) return; - element.style.overflow = element._overflow; - element._overflow = undefined; + if (!element._overflow) return element; + element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; + element._overflow = null; + return element; } -}); +} + +Element.Methods.Simulated = { + hasAttribute: function(element, attribute) { + return $(element).getAttributeNode(attribute).specified; + } +} + +// IE is missing .innerHTML support for TABLE-related elements +if(document.all){ + Element.Methods.update = function(element, html) { + element = $(element); + html = typeof html == 'undefined' ? '' : html.toString(); + var tagName = element.tagName.toUpperCase(); + if (['THEAD','TBODY','TR','TD'].indexOf(tagName) > -1) { + var div = document.createElement('div'); + switch (tagName) { + case 'THEAD': + case 'TBODY': + div.innerHTML = '<table><tbody>' + html.stripScripts() + '</tbody></table>'; + depth = 2; + break; + case 'TR': + div.innerHTML = '<table><tbody><tr>' + html.stripScripts() + '</tr></tbody></table>'; + depth = 3; + break; + case 'TD': + div.innerHTML = '<table><tbody><tr><td>' + html.stripScripts() + '</td></tr></tbody></table>'; + depth = 4; + } + $A(element.childNodes).each(function(node){ + element.removeChild(node) + }); + depth.times(function(){ div = div.firstChild }); + + $A(div.childNodes).each( + function(node){ element.appendChild(node) }); + } else { + element.innerHTML = html.stripScripts(); + } + setTimeout(function() {html.evalScripts()}, 10); + return element; + } +} + +Object.extend(Element, Element.Methods); + +var _nativeExtensions = false; + +if(/Konqueror|Safari|KHTML/.test(navigator.userAgent)) + ['', 'Form', 'Input', 'TextArea', 'Select'].each(function(tag) { + var className = 'HTML' + tag + 'Element'; + if(window[className]) return; + var klass = window[className] = {}; + klass.prototype = document.createElement(tag ? tag.toLowerCase() : 'div').__proto__; + }); + +Element.addMethods = function(methods) { + Object.extend(Element.Methods, methods || {}); + + function copy(methods, destination, onlyIfAbsent) { + onlyIfAbsent = onlyIfAbsent || false; + var cache = Element.extend.cache; + for (var property in methods) { + var value = methods[property]; + if (!onlyIfAbsent || !(property in destination)) + destination[property] = cache.findOrStore(value); + } + } + + if (typeof HTMLElement != 'undefined') { + copy(Element.Methods, HTMLElement.prototype); + copy(Element.Methods.Simulated, HTMLElement.prototype, true); + copy(Form.Methods, HTMLFormElement.prototype); + [HTMLInputElement, HTMLTextAreaElement, HTMLSelectElement].each(function(klass) { + copy(Form.Element.Methods, klass.prototype); + }); + _nativeExtensions = true; + } +} var Toggle = new Object(); Toggle.display = Element.toggle; @@ -1033,7 +1428,8 @@ Abstract.Insertion.prototype = { try { this.element.insertAdjacentHTML(this.adjacency, this.content); } catch (e) { - if (this.element.tagName.toLowerCase() == 'tbody') { + var tagName = this.element.tagName.toLowerCase(); + if (tagName == 'tbody' || tagName == 'tr') { this.insertContent(this.contentFromAnonymousTable()); } else { throw e; @@ -1132,76 +1528,176 @@ Element.ClassNames.prototype = { add: function(classNameToAdd) { if (this.include(classNameToAdd)) return; - this.set(this.toArray().concat(classNameToAdd).join(' ')); + this.set($A(this).concat(classNameToAdd).join(' ')); }, remove: function(classNameToRemove) { if (!this.include(classNameToRemove)) return; - this.set(this.select(function(className) { - return className != classNameToRemove; - }).join(' ')); + this.set($A(this).without(classNameToRemove).join(' ')); }, toString: function() { - return this.toArray().join(' '); + return $A(this).join(' '); } } Object.extend(Element.ClassNames.prototype, Enumerable); -var Field = { - clear: function() { - for (var i = 0; i < arguments.length; i++) - $(arguments[i]).value = ''; +var Selector = Class.create(); +Selector.prototype = { + initialize: function(expression) { + this.params = {classNames: []}; + this.expression = expression.toString().strip(); + this.parseExpression(); + this.compileMatcher(); }, - focus: function(element) { - $(element).focus(); + parseExpression: function() { + function abort(message) { throw 'Parse error in selector: ' + message; } + + if (this.expression == '') abort('empty expression'); + + var params = this.params, expr = this.expression, match, modifier, clause, rest; + while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) { + params.attributes = params.attributes || []; + params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''}); + expr = match[1]; + } + + if (expr == '*') return this.params.wildcard = true; + + while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) { + modifier = match[1], clause = match[2], rest = match[3]; + switch (modifier) { + case '#': params.id = clause; break; + case '.': params.classNames.push(clause); break; + case '': + case undefined: params.tagName = clause.toUpperCase(); break; + default: abort(expr.inspect()); + } + expr = rest; + } + + if (expr.length > 0) abort(expr.inspect()); + }, + + buildMatchExpression: function() { + var params = this.params, conditions = [], clause; + + if (params.wildcard) + conditions.push('true'); + if (clause = params.id) + conditions.push('element.id == ' + clause.inspect()); + if (clause = params.tagName) + conditions.push('element.tagName.toUpperCase() == ' + clause.inspect()); + if ((clause = params.classNames).length > 0) + for (var i = 0; i < clause.length; i++) + conditions.push('Element.hasClassName(element, ' + clause[i].inspect() + ')'); + if (clause = params.attributes) { + clause.each(function(attribute) { + var value = 'element.getAttribute(' + attribute.name.inspect() + ')'; + var splitValueBy = function(delimiter) { + return value + ' && ' + value + '.split(' + delimiter.inspect() + ')'; + } + + switch (attribute.operator) { + case '=': conditions.push(value + ' == ' + attribute.value.inspect()); break; + case '~=': conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break; + case '|=': conditions.push( + splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect() + ); break; + case '!=': conditions.push(value + ' != ' + attribute.value.inspect()); break; + case '': + case undefined: conditions.push(value + ' != null'); break; + default: throw 'Unknown operator ' + attribute.operator + ' in selector'; + } + }); + } + + return conditions.join(' && '); }, - present: function() { - for (var i = 0; i < arguments.length; i++) - if ($(arguments[i]).value == '') return false; - return true; + compileMatcher: function() { + this.match = new Function('element', 'if (!element.tagName) return false; \ + return ' + this.buildMatchExpression()); }, - select: function(element) { - $(element).select(); + findElements: function(scope) { + var element; + + if (element = $(this.params.id)) + if (this.match(element)) + if (!scope || Element.childOf(element, scope)) + return [element]; + + scope = (scope || document).getElementsByTagName(this.params.tagName || '*'); + + var results = []; + for (var i = 0, length = scope.length; i < length; i++) + if (this.match(element = scope[i])) + results.push(Element.extend(element)); + + return results; }, - activate: function(element) { - element = $(element); - element.focus(); - if (element.select) - element.select(); + toString: function() { + return this.expression; } } -/*--------------------------------------------------------------------------*/ +Object.extend(Selector, { + matchElements: function(elements, expression) { + var selector = new Selector(expression); + return elements.select(selector.match.bind(selector)).collect(Element.extend); + }, + findElement: function(elements, expression, index) { + if (typeof expression == 'number') index = expression, expression = false; + return Selector.matchElements(elements, expression || '*')[index || 0]; + }, + + findChildElements: function(element, expressions) { + return expressions.map(function(expression) { + return expression.strip().split(/\s+/).inject([null], function(results, expr) { + var selector = new Selector(expr); + return results.inject([], function(elements, result) { + return elements.concat(selector.findElements(result || element)); + }); + }); + }).flatten(); + } +}); + +function $$() { + return Selector.findChildElements(document, $A(arguments)); +} var Form = { - serialize: function(form) { - var elements = Form.getElements($(form)); - var queryComponents = new Array(); + reset: function(form) { + $(form).reset(); + return form; + }, - for (var i = 0; i < elements.length; i++) { - var queryComponent = Form.Element.serialize(elements[i]); - if (queryComponent) - queryComponents.push(queryComponent); - } + serializeElements: function(elements) { + return elements.inject([], function(queryComponents, element) { + var queryComponent = Form.Element.serialize(element); + if (queryComponent) queryComponents.push(queryComponent); + return queryComponents; + }).join('&'); + } +}; - return queryComponents.join('&'); +Form.Methods = { + serialize: function(form) { + return Form.serializeElements($(form).getElements()); }, getElements: function(form) { - form = $(form); - var elements = new Array(); - - for (tagName in Form.Element.Serializers) { - var tagElements = form.getElementsByTagName(tagName); - for (var j = 0; j < tagElements.length; j++) - elements.push(tagElements[j]); - } - return elements; + return $A($(form).getElementsByTagName('*')).inject([], + function(elements, child) { + if (Form.Element.Serializers[child.tagName.toLowerCase()]) + elements.push(Element.extend(child)); + return elements; + } + ); }, getInputs: function(form, typeName, name) { @@ -1212,53 +1708,68 @@ var Form = { return inputs; var matchingInputs = new Array(); - for (var i = 0; i < inputs.length; i++) { + for (var i = 0, length = inputs.length; i < length; i++) { var input = inputs[i]; if ((typeName && input.type != typeName) || (name && input.name != name)) continue; - matchingInputs.push(input); + matchingInputs.push(Element.extend(input)); } return matchingInputs; }, disable: function(form) { - var elements = Form.getElements(form); - for (var i = 0; i < elements.length; i++) { - var element = elements[i]; + form = $(form); + form.getElements().each(function(element) { element.blur(); element.disabled = 'true'; - } + }); + return form; }, enable: function(form) { - var elements = Form.getElements(form); - for (var i = 0; i < elements.length; i++) { - var element = elements[i]; + form = $(form); + form.getElements().each(function(element) { element.disabled = ''; - } + }); + return form; }, findFirstElement: function(form) { - return Form.getElements(form).find(function(element) { + return $(form).getElements().find(function(element) { return element.type != 'hidden' && !element.disabled && ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); }); }, focusFirstElement: function(form) { - Field.activate(Form.findFirstElement(form)); + form = $(form); + form.findFirstElement().activate(); + return form; + } +} + +Object.extend(Form, Form.Methods); + +/*--------------------------------------------------------------------------*/ + +Form.Element = { + focus: function(element) { + $(element).focus(); + return element; }, - reset: function(form) { - $(form).reset(); + select: function(element) { + $(element).select(); + return element; } } -Form.Element = { +Form.Element.Methods = { serialize: function(element) { element = $(element); + if (element.disabled) return ''; var method = element.tagName.toLowerCase(); var parameter = Form.Element.Serializers[method](element); @@ -1282,20 +1793,52 @@ Form.Element = { if (parameter) return parameter[1]; + }, + + clear: function(element) { + $(element).value = ''; + return element; + }, + + present: function(element) { + return $(element).value != ''; + }, + + activate: function(element) { + element = $(element); + element.focus(); + if (element.select) + element.select(); + return element; + }, + + disable: function(element) { + element = $(element); + element.disabled = true; + return element; + }, + + enable: function(element) { + element = $(element); + element.blur(); + element.disabled = false; + return element; } } +Object.extend(Form.Element, Form.Element.Methods); +var Field = Form.Element; + +/*--------------------------------------------------------------------------*/ + Form.Element.Serializers = { input: function(element) { switch (element.type.toLowerCase()) { - case 'submit': - case 'hidden': - case 'password': - case 'text': - return Form.Element.Serializers.textarea(element); case 'checkbox': case 'radio': return Form.Element.Serializers.inputSelector(element); + default: + return Form.Element.Serializers.textarea(element); } return false; }, @@ -1317,24 +1860,20 @@ Form.Element.Serializers = { selectOne: function(element) { var value = '', opt, index = element.selectedIndex; if (index >= 0) { - opt = element.options[index]; - value = opt.value; - if (!value && !('value' in opt)) - value = opt.text; + opt = Element.extend(element.options[index]); + // Uses the new potential extension if hasAttribute isn't native. + value = opt.hasAttribute('value') ? opt.value : opt.text; } return [element.name, value]; }, selectMany: function(element) { - var value = new Array(); + var value = []; for (var i = 0; i < element.length; i++) { - var opt = element.options[i]; - if (opt.selected) { - var optValue = opt.value; - if (!optValue && !('value' in opt)) - optValue = opt.text; - value.push(optValue); - } + var opt = Element.extend(element.options[i]); + if (opt.selected) + // Uses the new potential extension if hasAttribute isn't native. + value.push(opt.hasAttribute('value') ? opt.value : opt.text); } return [element.name, value]; } @@ -1408,9 +1947,7 @@ Abstract.EventObserver.prototype = { }, registerFormCallbacks: function() { - var elements = Form.getElements(this.element); - for (var i = 0; i < elements.length; i++) - this.registerCallback(elements[i]); + Form.getElements(this.element).each(this.registerCallback.bind(this)); }, registerCallback: function(element) { @@ -1420,11 +1957,7 @@ Abstract.EventObserver.prototype = { case 'radio': Event.observe(element, 'click', this.onElementEvent.bind(this)); break; - case 'password': - case 'text': - case 'textarea': - case 'select-one': - case 'select-multiple': + default: Event.observe(element, 'change', this.onElementEvent.bind(this)); break; } @@ -1459,6 +1992,10 @@ Object.extend(Event, { KEY_RIGHT: 39, KEY_DOWN: 40, KEY_DELETE: 46, + KEY_HOME: 36, + KEY_END: 35, + KEY_PAGEUP: 33, + KEY_PAGEDOWN: 34, element: function(event) { return event.target || event.srcElement; @@ -1514,7 +2051,7 @@ Object.extend(Event, { unloadCache: function() { if (!Event.observers) return; - for (var i = 0; i < Event.observers.length; i++) { + for (var i = 0, length = Event.observers.length; i < length; i++) { Event.stopObserving.apply(this, Event.observers[i]); Event.observers[i][0] = null; } @@ -1522,7 +2059,7 @@ Object.extend(Event, { }, observe: function(element, name, observer, useCapture) { - var element = $(element); + element = $(element); useCapture = useCapture || false; if (name == 'keypress' && @@ -1530,11 +2067,11 @@ Object.extend(Event, { || element.attachEvent)) name = 'keydown'; - this._observeAndCache(element, name, observer, useCapture); + Event._observeAndCache(element, name, observer, useCapture); }, stopObserving: function(element, name, observer, useCapture) { - var element = $(element); + element = $(element); useCapture = useCapture || false; if (name == 'keypress' && @@ -1545,13 +2082,16 @@ Object.extend(Event, { if (element.removeEventListener) { element.removeEventListener(name, observer, useCapture); } else if (element.detachEvent) { - element.detachEvent('on' + name, observer); + try { + element.detachEvent('on' + name, observer); + } catch (e) {} } } }); /* prevent memory leaks in IE */ -Event.observe(window, 'unload', Event.unloadCache, false); +if (navigator.appVersion.match(/\bMSIE\b/)) + Event.observe(window, 'unload', Event.unloadCache, false); var Position = { // set to true if needed, warning: firefox performance problems // NOT neeeded for page scrolling, only if draggable contained in @@ -1598,7 +2138,8 @@ var Position = { valueL += element.offsetLeft || 0; element = element.offsetParent; if (element) { - p = Element.getStyle(element, 'position'); + if(element.tagName=='BODY') break; + var p = Element.getStyle(element, 'position'); if (p == 'relative' || p == 'absolute') break; } } while (element); @@ -1654,17 +2195,6 @@ var Position = { element.offsetWidth; }, - clone: function(source, target) { - source = $(source); - target = $(target); - target.style.position = 'absolute'; - var offsets = this.cumulativeOffset(source); - target.style.top = offsets[1] + 'px'; - target.style.left = offsets[0] + 'px'; - target.style.width = source.offsetWidth + 'px'; - target.style.height = source.offsetHeight + 'px'; - }, - page: function(forElement) { var valueT = 0, valueL = 0; @@ -1681,8 +2211,10 @@ var Position = { element = forElement; do { - valueT -= element.scrollTop || 0; - valueL -= element.scrollLeft || 0; + if (!window.opera || element.tagName=='BODY') { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } } while (element = element.parentNode); return [valueL, valueT]; @@ -1782,4 +2314,6 @@ if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) { return [valueL, valueT]; } -}
\ No newline at end of file +} + +Element.addMethods();
\ No newline at end of file diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index 0a3a3f400..afe4eefd2 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -215,6 +215,30 @@ font-family: Trebuchet MS,Georgia,"Times New Roman",serif; #content dt{font-weight:bold; margin-bottom:5px;}
#content dd{margin:0 0 10px 15px;}
+#content .tabs{height: 2.6em;}
+#content .tabs ul{margin:0;}
+#content .tabs ul li{
+float:left;
+list-style-type:none;
+white-space:nowrap;
+margin-right:8px;
+background:#fff;
+}
+#content .tabs ul li a{
+display:block;
+font-size: 0.9em;
+text-decoration:none;
+line-height:1em;
+padding:4px;
+border: 1px solid #c0c0c0;
+}
+
+#content .tabs ul li a.selected, #content .tabs ul li a:hover{
+background-color: #80b0da;
+border: 1px solid #80b0da;
+color: #fff;
+text-decoration:none;
+}
/***********************************************/
|