You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ode.apache.org by as...@apache.org on 2007/12/12 00:14:43 UTC

svn commit: r603406 [2/3] - in /ode/sandbox/singleshot: ./ app/controllers/ app/helpers/ app/models/ app/views/layouts/ app/views/sandwiches/ app/views/sessions/ app/views/tasks/ config/ config/environments/ config/initializers/ db/migrate/ doc/pages/ ...

Modified: ode/sandbox/singleshot/lib/extensions/json_request.rb
URL: http://svn.apache.org/viewvc/ode/sandbox/singleshot/lib/extensions/json_request.rb?rev=603406&r1=603405&r2=603406&view=diff
==============================================================================
--- ode/sandbox/singleshot/lib/extensions/json_request.rb (original)
+++ ode/sandbox/singleshot/lib/extensions/json_request.rb Tue Dec 11 15:14:39 2007
@@ -1,34 +1,35 @@
 module ActionController
   # Handles JSON requests.
-  #
-  # When receiving a request with a JSON object/array, parses the request into a hash/array.
-  # Since the request is unnamed, figures the parameter name based on the controller name.
-  # For example, for the controller ItemsController, maps a JSON object as Hash into the
-  # parameter :item, and JSON array into the parameter :items.  If you need to pick a different
-  # parameter name, override #unwrapped_parameter_name.
   module JsonRequest
     def self.included(mod)
-      super
-      mod.param_parsers[Mime::JSON] = lambda { |body| { :_unwrapped => ActiveSupport::JSON.decode(body) }.with_indifferent_access }
-      mod.alias_method_chain :assign_names, :unwrapped_parameter
+      mod.param_parsers[Mime::JSON] = lambda { |body| { :_json => ActiveSupport::JSON.decode(body) }.with_indifferent_access }
+      mod.extend ClassMethods
+      mod.json_request
     end
 
-    def assign_names_with_unwrapped_parameter
-      assign_names_without_unwrapped_parameter
-      if data = params.delete('_unwrapped')
-        name = unwrapped_parameter_name(data.is_a?(Array))
-        params.update(name=>data)
+    module ClassMethods
+
+      # Use this to handle JSON requests.  Adds a MIME handler for requests with the content type +application/json+,
+      # and maps an incoming request to the proper parameter.
+      #
+      # All controllers inherit this bahavior using the controller name to determine the parameter name.  For example,
+      # the ItemsController will store the JSON request in the parameter 'item'.
+      #
+      # You can also configure this as a filter and specify a specific parameter name, for example:
+      #   class FooController < ActionController::Base
+      #     json_request :bar, :only=>[:update]
+      #   end
+      def json_request(*args)
+        options = args.extract_options!
+        prepend_before_filter options do |controller|
+          if data = controller.params.delete(:_json)
+            name = args.first || controller_name
+            name = name.to_s.singularize unless data.is_a?(Array)
+            controller.params.update(name=>data)
+          end
+        end
       end
-    end
-    private :assign_names_with_unwrapped_parameter
 
-    # Picks a name for the unwrapped parameter.  The default implementation uses the controller name
-    # when +plural+ is true, and controller name made singular otherwise.
-    def unwrapped_parameter_name(plural)
-      plural ? controller_name : controller_name.singularize
     end
-    protected :unwrapped_parameter_name
   end
 end 
-
-ActionController::Base.send :include, ActionController::JsonRequest

Modified: ode/sandbox/singleshot/lib/extensions/validators.rb
URL: http://svn.apache.org/viewvc/ode/sandbox/singleshot/lib/extensions/validators.rb?rev=603406&r1=603405&r2=603406&view=diff
==============================================================================
--- ode/sandbox/singleshot/lib/extensions/validators.rb (original)
+++ ode/sandbox/singleshot/lib/extensions/validators.rb Tue Dec 11 15:14:39 2007
@@ -1,53 +1,76 @@
 module ActiveRecord
-  class Base
-    class << self
+  module Validators
+    module Url
 
-      Errors.default_error_messages[:invalid_url] = 'Not a valid URL'
-      Errors.default_error_messages[:invalid_email] = 'Not a valid e-mail address'
-     
-      # Validates that each attribute is a URL and also normalizes the URL before saving it.
-      #
-      # The URL is checked to be valid, include a schema and host name (and therefore be absolute),
-      # and only uses an allowed scheme.  The allowed schemes are specified by the :schemes option,
-      # defaulting to HTTP and HTTPS.  The normalized URL has its scheme in all lower case, and so
-      # should the names passed to :scheme.
-      #
-      # For example:
-      #   # Only allow HTPS
-      #   validates_url :secure_url, :schemes=>['https']
-      def validates_url(*attr_names)
-        configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid_url], :on => :save,
-                          :schemes=>['http', 'https'] }
-        configuration.update(attr_names.extract_options!)
-
-        before_validation do |record|
-          attr_names.each do |attr_name|
-            url = record.send(attr_name) 
-            if url && uri = URI(url) rescue nil
-              uri.normalize!
-              uri.scheme = uri.scheme.downcase if uri.scheme
-              record.send "#{attr_name}=", uri.to_s
+      def self.included(mod)
+        ActiveRecord::Errors.default_error_messages[:invalid_url] = 'Not a valid URL'
+        mod.extend ClassMethods
+      end
+
+      module ClassMethods
+ 
+        # Validates that each attribute is a URL and also normalizes the URL before saving it.
+        #
+        # The URL is checked to be valid, include a schema and host name (and therefore be absolute),
+        # and only uses an allowed scheme.  The allowed schemes are specified by the :schemes option,
+        # defaulting to HTTP and HTTPS.  The normalized URL has its scheme in all lower case, and so
+        # should the names passed to :scheme.
+        #
+        # For example:
+        #   # Only allow HTPS
+        #   validates_url :secure_url, :schemes=>['https']
+        def validates_url(*attr_names)
+          configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid_url], :on=>:save,
+                            :schemes=>['http', 'https'] }
+          configuration.update(attr_names.extract_options!)
+
+          # Normalize URL.
+          before_validation do |record|
+            attr_names.each do |attr_name|
+              url = record.send(attr_name) 
+              if url && uri = URI(url) rescue nil
+                uri.normalize!
+                uri.scheme = uri.scheme.downcase if uri.scheme
+                record.send "#{attr_name}=", uri.to_s
+              end
             end
           end
-        end
 
-        validates_each(attr_names, configuration) do |record, attr_name, value|
-          uri = URI.parse(value) rescue nil
-          record.errors.add attr_name, configuration[:message] unless uri && uri.scheme && uri.host &&
-            (configuration[:schemes].nil? || configuration[:schemes].include?(uri.scheme.downcase))
+          # Validate URL.
+          validates_each(attr_names, configuration) do |record, attr_name, value|
+            uri = URI.parse(value) rescue nil
+            record.errors.add attr_name, configuration[:message] unless uri && uri.scheme && uri.host &&
+              configuration[:schemes].include?(uri.scheme.downcase)
+          end
         end
+
+      end
+
+    end
+
+    module Email
+
+      def self.included(mod)
+        ActiveRecord::Errors.default_error_messages[:invalid_email] = 'Not a valid e-mail address'
+        mod.extend ClassMethods
       end
+     
+      module ClassMethods
+
+        # Validates that each attribute looks like a valid e-mail address.  Does not check that the
+        # e-mail address makes sense, only that it is more likely to be an e-mail address than a phone number.
+        def validates_email(*attr_names)
+          configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid_email], :on => :save }
+          configuration.update(attr_names.extract_options!)
+          configuration.update(:with => /^([^@\s]+)@[-a-z0-9]+(\.[-a-z0-9]+)*$/)
+          attr_names << configuration
+          validates_format_of *attr_names
+        end
 
-      # Validates that each attribute looks like a valid e-mail address.  Does not check that the
-      # e-mail address makes sense, only that it is more likely to be an e-mail address than a phone number.
-      def validates_email(*attr_names)
-        configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid_email], :on => :save }
-        configuration.update(attr_names.extract_options!)
-        configuration.update(:with => /^([^@\s]+)@[-a-z0-9]+(\.[-a-z0-9]+)*$/)
-        attr_names << configuration
-        validates_format_of *attr_names
       end
 
     end
+  
   end
+
 end

Added: ode/sandbox/singleshot/lib/patches.rb
URL: http://svn.apache.org/viewvc/ode/sandbox/singleshot/lib/patches.rb?rev=603406&view=auto
==============================================================================
--- ode/sandbox/singleshot/lib/patches.rb (added)
+++ ode/sandbox/singleshot/lib/patches.rb Tue Dec 11 15:14:39 2007
@@ -0,0 +1,4 @@
+# Patches to Rails.
+require File.expand_path('patches/to_xml_primitive', File.dirname(__FILE__))
+require File.expand_path('patches/http_basic', File.dirname(__FILE__))
+require File.expand_path('patches/client_error', File.dirname(__FILE__))

Added: ode/sandbox/singleshot/lib/patches/client_error.rb
URL: http://svn.apache.org/viewvc/ode/sandbox/singleshot/lib/patches/client_error.rb?rev=603406&view=auto
==============================================================================
--- ode/sandbox/singleshot/lib/patches/client_error.rb (added)
+++ ode/sandbox/singleshot/lib/patches/client_error.rb Tue Dec 11 15:14:39 2007
@@ -0,0 +1,7 @@
+module ActionController #:nodoc:
+  module TestResponseBehavior #:nodoc:
+    def client_error?
+      (400..499).include?(response_code)
+    end
+  end
+end

Added: ode/sandbox/singleshot/lib/patches/http_basic.rb
URL: http://svn.apache.org/viewvc/ode/sandbox/singleshot/lib/patches/http_basic.rb?rev=603406&view=auto
==============================================================================
--- ode/sandbox/singleshot/lib/patches/http_basic.rb (added)
+++ ode/sandbox/singleshot/lib/patches/http_basic.rb Tue Dec 11 15:14:39 2007
@@ -0,0 +1,5 @@
+module ActionController::HttpAuthentication::Basic
+  def encode_credentials(user_name, password)
+    %{Basic #{Base64.encode64("#{user_name}:#{password}").gsub(/\n/,'')}}
+  end
+end

Added: ode/sandbox/singleshot/lib/patches/to_xml_primitive.rb
URL: http://svn.apache.org/viewvc/ode/sandbox/singleshot/lib/patches/to_xml_primitive.rb?rev=603406&view=auto
==============================================================================
--- ode/sandbox/singleshot/lib/patches/to_xml_primitive.rb (added)
+++ ode/sandbox/singleshot/lib/patches/to_xml_primitive.rb Tue Dec 11 15:14:39 2007
@@ -0,0 +1,61 @@
+module ActiveSupport #:nodoc:
+  module CoreExtensions #:nodoc:
+    module Array #:nodoc:
+      module Conversions
+
+        def to_xml(options = {})
+          options[:root]     ||= all? { |e| e.is_a?(first.class) && first.class.to_s != "Hash" } ? first.class.to_s.underscore.pluralize : "records"
+          options[:children] ||= options[:root].singularize
+          options[:indent]   ||= 2
+
+          root     = options.delete(:root).to_s
+          children = options.delete(:children)
+
+          if !options.has_key?(:dasherize) || options[:dasherize]
+            root = root.dasherize
+            children = children.dasherize
+          end
+
+          options[:builder].instruct! unless options.delete(:skip_instruct)
+
+          opts = options.merge({ :root => children })
+
+          xml = options[:builder]
+          if empty?
+            xml.tag!(root, options[:skip_types] ? {} : {:type => "array"})
+          else
+            xml.tag!(root, options[:skip_types] ? {} : {:type => "array"}) do
+               yield xml if block_given?
+
+              each do |value|
+                case value
+                  when ::Hash
+                    value.to_xml(opts.merge!({:skip_instruct => true }))
+                  when ::Method, ::Proc
+                    value.call(opts.merge!({ :skip_instruct => true }))
+                  else
+                    if value.respond_to?(:to_xml)
+                      value.to_xml(opts.merge!({ :skip_instruct => true }))
+                    else
+                      type_name = Hash::Conversions::XML_TYPE_NAMES[value.class.name]
+
+                      attributes = opts[:skip_types] || value.nil? || type_name.nil? ? { } : { :type => type_name }
+                      if value.nil?
+                        attributes[:nil] = true
+                      end
+
+                      options[:builder].tag!(children,
+                        Hash::Conversions::XML_FORMATTING[type_name] ? Hash::Conversions::XML_FORMATTING[type_name].call(value).to_s : value.to_s,
+                        attributes
+                      )
+                  end
+                end
+              end
+            end
+          end
+        end
+
+      end
+    end
+  end
+end

Added: ode/sandbox/singleshot/lib/singleshot.rb
URL: http://svn.apache.org/viewvc/ode/sandbox/singleshot/lib/singleshot.rb?rev=603406&view=auto
==============================================================================
--- ode/sandbox/singleshot/lib/singleshot.rb (added)
+++ ode/sandbox/singleshot/lib/singleshot.rb Tue Dec 11 15:14:39 2007
@@ -0,0 +1,3 @@
+require 'singleshot/resource'
+require 'singleshot/task'
+Singleshot::Resource.logger = ActiveRecord::Base.logger

Added: ode/sandbox/singleshot/lib/singleshot/resource.rb
URL: http://svn.apache.org/viewvc/ode/sandbox/singleshot/lib/singleshot/resource.rb?rev=603406&view=auto
==============================================================================
--- ode/sandbox/singleshot/lib/singleshot/resource.rb (added)
+++ ode/sandbox/singleshot/lib/singleshot/resource.rb Tue Dec 11 15:14:39 2007
@@ -0,0 +1,156 @@
+module Singleshot
+  class Resource
+
+    cattr_accessor :logger, :instance_writer => false
+    cattr_accessor :new_url, :instance_writer => false
+
+    class << self
+
+      def reference(url)
+        returning(new) do |instance|
+          instance.url = url
+        end
+      end
+
+      def load(url)
+        reference(url).reload
+      end
+
+      def create(attributes = {})
+        new(attributes).save
+      end
+
+      def create!(attributes = {})
+        new(attributes).save!
+      end
+
+      def attributes(*attributes)
+        write_inheritable_attribute(:attributes, Set.new(attributes.map(&:to_s)) + (attribute_names || []))
+        attributes.each do |attr_name|
+          unless methods.include?(attr_name.to_s)
+            define_method(attr_name) { self[attr_name] }
+          end
+          unless methods.include?("#{attr_name}=")
+            define_method("#{attr_name}=") { |value| self[attr_name] = value }
+          end
+        end
+      end
+
+      def attribute_names
+        read_inheritable_attribute(:attributes)
+      end
+
+      def human_attribute_name(attribute)
+        attribute.humanize
+      end
+
+    end
+
+    def initialize(attributes = {})
+      @attributes = {}
+      self.attributes = attributes if attributes
+    end
+
+    attr_accessor :url
+
+    def new_record?
+      url.nil?
+    end
+
+    def [](name)
+      @attributes[name.to_s]
+    end
+
+    def []=(name, value)
+      @attributes[name.to_s] = value
+    end
+
+    def attributes=(attributes)
+      attributes.each do |name, value|
+        send "#{name}=", value
+      end
+    end
+
+    def attributes
+      self.class.attribute_names.inject({}) { |hash, name| hash.update(name=>send(name)) }
+    end
+
+    def reload
+      begin
+        reload_from_response request(url, 'Accept'=>'application/json', :method=>:get)
+        self
+      rescue OpenURI::HTTPError=>ex
+        raise ActiveRecord::RecordNotFound, ex.message
+      end
+    end
+
+    def save
+      new_record? ? create : update
+    end
+
+    def save!
+      save or raise ActiveRecord::RecordNotSaved
+    end
+
+    def update_attribute(name, value)
+      send "#{name}=", value
+      save
+    end
+
+    def update_attribute!(name, value)
+      send "#{name}=", value
+      save!
+    end
+
+    include ActiveRecord::Validations
+
+  private
+
+    def create
+      begin
+        response = request(new_url, 'Accept'=>'application/json', 'Content-Type'=>'application/json',
+                                    :method=>:post, :body=>attributes.to_json)
+        # TODO: need more handling for response type
+        reload_from_response response, response['Location']
+      rescue OpenURI::HTTPError=>ex
+        map_error ex
+      end
+    end
+
+    def update
+      begin
+        reload_from_response request(url, 'Accept'=>'application/json', 'Content-Type'=>'application/json',
+                                          :method=>:put, :body=>attributes.to_json)
+      rescue OpenURI::HTTPError=>ex
+        map_error ex
+      end
+    end
+
+    def reload_from_response(response, url = response.base_uri)
+      json = ActiveSupport::JSON.decode(response)
+      self.class.attribute_names.each { |name| send "#{name}=", json[name] }
+      true
+    end
+
+    def map_error(exception)
+      body = exception.io.read
+      if body.blank?
+        errors.add_to_base exception.message
+      else
+        errors.add_to_base body
+      end
+      false
+    end
+
+    def request(url, options = {})
+      uri = URI(url.to_s)
+      raise ActiveRecord::RecordNotFound, 'Only HTTP(S) URLs allowed' unless uri.scheme =~ /^http(s?)$/i
+      raise ActiveRecord::RecordNotFound, 'Must be an absolute URL' unless uri.absolute?
+      uri.normalize!
+      uri.scheme = uri.scheme.downcase
+      options[:http_basic_authentication] = [uri.user, uri.password] if uri.user
+      uri.read(options)
+    end
+
+  end
+end

Added: ode/sandbox/singleshot/lib/singleshot/task.rb
URL: http://svn.apache.org/viewvc/ode/sandbox/singleshot/lib/singleshot/task.rb?rev=603406&view=auto
==============================================================================
--- ode/sandbox/singleshot/lib/singleshot/task.rb (added)
+++ ode/sandbox/singleshot/lib/singleshot/task.rb Tue Dec 11 15:14:39 2007
@@ -0,0 +1,73 @@
+require 'singleshot/resource'
+
+module Singleshot
+  class Task < Resource
+
+    class << self
+
+      def validation_method(on)
+        case on
+          when :save   then :validate
+          when :create then :validate_on_create
+          when :update then :validate_on_update
+          when :complete then :validate_on_complete
+        end
+      end
+
+      def validate_on_complete(*methods, &block)
+        methods << block if block_given?
+        write_inheritable_set(:validate_on_complete, methods)
+      end
+
+      def data_fields(*attributes)
+        write_inheritable_attribute(:data_fields, Set.new(attributes.map(&:to_s)) + (data_field_names || []))
+        attributes.each do |attr_name|
+          unless methods.include?(attr_name.to_s)
+            define_method(attr_name) do
+              if data = self.data
+                data[attr_name.to_s]
+              end
+            end
+          end
+          unless methods.include?("#{attr_name}=")
+            define_method("#{attr_name}=") do |value|
+              self.data ||= {}
+              self.data[attr_name.to_s] = value
+            end
+          end
+        end
+      end
+
+      def data_field_names
+        read_inheritable_attribute(:data_fields)
+      end
+
+    end
+
+    attributes :id, :status, :updated_at, :title, :priority, :version, :cancellation, :due_on, :created_at, :data
+
+    def complete
+      errors.clear
+      run_validations(:validate)
+      validate
+      run_validations(:validate_on_complete)
+      validate_on_complete
+      return false unless errors.empty?
+
+      begin
+        reload_from_response request(url, 'Accept'=>'application/json', 'Content-Type'=>'application/json',
+                                           :method=>:post, :body=>attributes.to_json)
+      rescue OpenURI::HTTPError=>ex
+        map_error ex
+      end
+    end
+
+    def complete!
+      complete or raise ActiveRecord::RecordNotSaved
+    end
+
+    def validate_on_complete
+    end
+
+  end
+end

Modified: ode/sandbox/singleshot/public/javascripts/application.js
URL: http://svn.apache.org/viewvc/ode/sandbox/singleshot/public/javascripts/application.js?rev=603406&r1=603405&r2=603406&view=diff
==============================================================================
--- ode/sandbox/singleshot/public/javascripts/application.js (original)
+++ ode/sandbox/singleshot/public/javascripts/application.js Tue Dec 11 15:14:39 2007
@@ -1,4 +1,47 @@
 // Place your application-specific JavaScript functions and classes here
 // This file is automatically included by javascript_include_tag :defaults
 
-var Singleshot = new Object();
+var Singleshot = {
+  // Returns the SingleShot.TaskView object.
+  taskView: function() {
+    var taskView = new Singleshot.TaskView();
+    Singleshot.taskView = function() { return taskView };
+    return taskView;
+  },
+
+  expand: function(event, target, alternative) {
+    event = event || window.event;
+    var source = Event.element(event);
+    target = $(target);
+    if (target.visible()) {
+      source.innerHTML = source.originalText; 
+      target.hide();
+    } else if (event.shiftKey || event.ctrlKey || event.metaKey) {
+      return;
+    } else {
+      source.originalText = source.innerHTML;
+      if (alternative)
+        source.innerHTML = alternative;
+      target.show();
+    }
+    Event.stop(event);
+  }
+}
+
+Singleshot.TaskView = Class.create({
+  initialize: function() {
+    this.adjustFrame('task_frame');
+  },
+
+  adjustFrame: function(ifr) {
+    // Adjust lower frame to expand and fit the reminder of the window.
+    // Do it once now, and each time the window is resized.
+    if (ifr = $(ifr)) {
+      var adjust = function() {
+        ifr.style.height = window.innerHeight - ifr.offsetTop;
+      }
+      Event.observe(window, 'resize', adjust);
+      adjust();
+    }
+  }
+});

Modified: ode/sandbox/singleshot/public/javascripts/prototype.js
URL: http://svn.apache.org/viewvc/ode/sandbox/singleshot/public/javascripts/prototype.js?rev=603406&r1=603405&r2=603406&view=diff
==============================================================================
--- ode/sandbox/singleshot/public/javascripts/prototype.js (original)
+++ ode/sandbox/singleshot/public/javascripts/prototype.js Tue Dec 11 15:14:39 2007
@@ -36,8 +36,6 @@
 if (Prototype.Browser.MobileSafari)
   Prototype.BrowserFeatures.SpecificElementExtensions = false;
 
-if (Prototype.Browser.WebKit)
-  Prototype.BrowserFeatures.XPath = false;
 
 /* Based on Alex Arnell's inheritance implementation. */
 var Class = {
@@ -110,7 +108,7 @@
 Object.extend(Object, {
   inspect: function(object) {
     try {
-      if (object === undefined) return 'undefined';
+      if (Object.isUndefined(object)) return 'undefined';
       if (object === null) return 'null';
       return object.inspect ? object.inspect() : object.toString();
     } catch (e) {
@@ -135,7 +133,7 @@
     var results = [];
     for (var property in object) {
       var value = Object.toJSON(object[property]);
-      if (value !== undefined)
+      if (!Object.isUndefined(value))
         results.push(property.toJSON() + ': ' + value);
     }
 
@@ -204,7 +202,7 @@
   },
 
   bind: function() {
-    if (arguments.length < 2 && arguments[0] === undefined) return this;
+    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
     var __method = this, args = $A(arguments), object = args.shift();
     return function() {
       return __method.apply(object, args.concat($A(arguments)));
@@ -351,7 +349,7 @@
 
   sub: function(pattern, replacement, count) {
     replacement = this.gsub.prepareReplacement(replacement);
-    count = count === undefined ? 1 : count;
+    count = Object.isUndefined(count) ? 1 : count;
 
     return this.gsub(pattern, function(match) {
       if (--count < 0) return match[0];
@@ -366,7 +364,7 @@
 
   truncate: function(length, truncation) {
     length = length || 30;
-    truncation = truncation === undefined ? '...' : truncation;
+    truncation = Object.isUndefined(truncation) ? '...' : truncation;
     return this.length > length ?
       this.slice(0, length - truncation.length) + truncation : String(this);
   },
@@ -486,7 +484,9 @@
   },
 
   isJSON: function() {
-    var str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
+    var str = this;
+    if (str.blank()) return false;
+    str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
     return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
   },
 
@@ -565,7 +565,8 @@
       if (before == '\\') return match[2];
 
       var ctx = object, expr = match[3];
-      var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/, match = pattern.exec(expr);
+      var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
+      match = pattern.exec(expr);
       if (match == null) return before;
 
       while (match != null) {
@@ -686,7 +687,7 @@
   },
 
   inGroupsOf: function(number, fillWith) {
-    fillWith = fillWith === undefined ? null : fillWith;
+    fillWith = Object.isUndefined(fillWith) ? null : fillWith;
     return this.eachSlice(number, function(slice) {
       while(slice.length < number) slice.push(fillWith);
       return slice;
@@ -713,7 +714,7 @@
     var result;
     this.each(function(value, index) {
       value = iterator(value, index);
-      if (result == undefined || value >= result)
+      if (result == null || value >= result)
         result = value;
     });
     return result;
@@ -724,7 +725,7 @@
     var result;
     this.each(function(value, index) {
       value = iterator(value, index);
-      if (result == undefined || value < result)
+      if (result == null || value < result)
         result = value;
     });
     return result;
@@ -904,7 +905,7 @@
     var results = [];
     this.each(function(object) {
       var value = Object.toJSON(object);
-      if (value !== undefined) results.push(value);
+      if (!Object.isUndefined(value)) results.push(value);
     });
     return '[' + results.join(', ') + ']';
   }
@@ -984,34 +985,6 @@
 };
 
 var Hash = Class.create(Enumerable, (function() {
-  if (function() {
-    var i = 0, Test = function(value) { this.key = value };
-    Test.prototype.key = 'foo';
-    for (var property in new Test('bar')) i++;
-    return i > 1;
-  }()) {
-    function each(iterator) {
-      var cache = [];
-      for (var key in this._object) {
-        var value = this._object[key];
-        if (cache.include(key)) continue;
-        cache.push(key);
-        var pair = [key, value];
-        pair.key = key;
-        pair.value = value;
-        iterator(pair);
-      }
-    }
-  } else {
-    function each(iterator) {
-      for (var key in this._object) {
-        var value = this._object[key], pair = [key, value];
-        pair.key = key;
-        pair.value = value;
-        iterator(pair);
-      }
-    }
-  }
 
   function toQueryPair(key, value) {
     if (Object.isUndefined(value)) return key;
@@ -1023,7 +996,14 @@
       this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
     },
 
-    _each: each,
+    _each: function(iterator) {
+      for (var key in this._object) {
+        var value = this._object[key], pair = [key, value];
+        pair.key = key;
+        pair.value = value;
+        iterator(pair);
+      }
+    },
 
     set: function(key, value) {
       return this._object[key] = value;
@@ -1187,8 +1167,11 @@
     Object.extend(this.options, options || { });
 
     this.options.method = this.options.method.toLowerCase();
+
     if (Object.isString(this.options.parameters))
       this.options.parameters = this.options.parameters.toQueryParams();
+    else if (Object.isHash(this.options.parameters))
+      this.options.parameters = this.options.parameters.toObject();
   }
 });
 
@@ -1371,7 +1354,7 @@
 
     if(readyState == 4) {
       var xml = transport.responseXML;
-      this.responseXML  = xml === undefined ? null : xml;
+      this.responseXML  = Object.isUndefined(xml) ? null : xml;
       this.responseJSON = this._getResponseJSON();
     }
   },
@@ -1417,10 +1400,11 @@
   _getResponseJSON: function() {
     var options = this.request.options;
     if (!options.evalJSON || (options.evalJSON != 'force' &&
-      !(this.getHeader('Content-type') || '').include('application/json')))
-        return null;
+      !(this.getHeader('Content-type') || '').include('application/json')) ||
+        this.responseText.blank())
+          return null;
     try {
-      return this.transport.responseText.evalJSON(options.sanitizeJSON);
+      return this.responseText.evalJSON(options.sanitizeJSON);
     } catch (e) {
       this.request.dispatchException(e);
     }
@@ -1434,11 +1418,11 @@
       failure: (container.failure || (container.success ? null : container))
     };
 
-    options = options || { };
+    options = Object.clone(options);
     var onComplete = options.onComplete;
-    options.onComplete = (function(response, param) {
+    options.onComplete = (function(response, json) {
       this.updateContent(response.responseText);
-      if (Object.isFunction(onComplete)) onComplete(response, param);
+      if (Object.isFunction(onComplete)) onComplete(response, json);
     }).bind(this);
 
     $super(url, options);
@@ -1460,10 +1444,6 @@
       }
       else receiver.update(responseText);
     }
-
-    if (this.success()) {
-      if (this.onComplete) this.onComplete.bind(this).defer();
-    }
   }
 });
 
@@ -1690,7 +1670,7 @@
   },
 
   descendants: function(element) {
-    return $A($(element).getElementsByTagName('*')).each(Element.extend);
+    return $(element).getElementsBySelector("*");
   },
 
   firstDescendant: function(element) {
@@ -1795,10 +1775,11 @@
     var attributes = { }, t = Element._attributeTranslations.write;
 
     if (typeof name == 'object') attributes = name;
-    else attributes[name] = value === undefined ? true : value;
+    else attributes[name] = Object.isUndefined(value) ? true : value;
 
     for (var attr in attributes) {
-      var name = t.names[attr] || attr, value = attributes[attr];
+      name = t.names[attr] || attr;
+      value = attributes[attr];
       if (t.values[attr]) name = t.values[attr](element, value);
       if (value === false || value === null)
         element.removeAttribute(name);
@@ -1867,6 +1848,7 @@
 
   descendantOf: function(element, ancestor) {
     element = $(element), ancestor = $(ancestor);
+    var originalAncestor = ancestor;
 
     if (element.compareDocumentPosition)
       return (element.compareDocumentPosition(ancestor) & 8) === 8;
@@ -1882,7 +1864,7 @@
     }
 
     while (element = element.parentNode)
-      if (element == ancestor) return true;
+      if (element == originalAncestor) return true;
     return false;
   },
 
@@ -1921,7 +1903,7 @@
       if (property == 'opacity') element.setOpacity(styles[property]);
       else
         elementStyle[(property == 'float' || property == 'cssFloat') ?
-          (elementStyle.styleFloat === undefined ? 'cssFloat' : 'styleFloat') :
+          (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :
             property] = styles[property];
 
     return element;
@@ -2301,7 +2283,7 @@
           return node ? node.value : "";
         },
         _getEv: function(element, attribute) {
-          var attribute = element.getAttribute(attribute);
+          attribute = element.getAttribute(attribute);
           return attribute ? attribute.toString().slice(23, -2) : null;
         },
         _flag: function(element, attribute) {
@@ -2719,9 +2701,26 @@
     this.compileMatcher();
   },
 
+  shouldUseXPath: function() {
+    if (!Prototype.BrowserFeatures.XPath) return false;
+
+    var e = this.expression;
+
+    // Safari 3 chokes on :*-of-type and :empty
+    if (Prototype.Browser.WebKit &&
+     (e.include("-of-type") || e.include(":empty")))
+      return false;
+
+    // XPath can't do namespaced attributes, nor can it read
+    // the "checked" property from DOM nodes
+    if ((/(\[[\w-]*?:|:checked)/).test(this.expression))
+      return false;
+
+    return true;
+  },
+
   compileMatcher: function() {
-    // Selectors with namespaced attributes can't use the XPath version
-    if (Prototype.BrowserFeatures.XPath && !(/(\[[\w-]*?:|:checked)/).test(this.expression))
+    if (this.shouldUseXPath())
       return this.compileXPathMatcher();
 
     var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
@@ -2844,8 +2843,12 @@
     },
     className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]",
     id:           "[@id='#{1}']",
-    attrPresence: "[@#{1}]",
+    attrPresence: function(m) {
+      m[1] = m[1].toLowerCase();
+      return new Template("[@#{1}]").evaluate(m);
+    },
     attr: function(m) {
+      m[1] = m[1].toLowerCase();
       m[3] = m[5] || m[6];
       return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
     },
@@ -2874,7 +2877,7 @@
       'enabled':     "[not(@disabled)]",
       'not': function(m) {
         var e = m[6], p = Selector.patterns,
-            x = Selector.xpath, le, m, v;
+            x = Selector.xpath, le, v;
 
         var exclusion = [];
         while (e && le != e && (/\S/).test(e)) {
@@ -3051,7 +3054,7 @@
     child: function(nodes) {
       var h = Selector.handlers;
       for (var i = 0, results = [], node; node = nodes[i]; i++) {
-        for (var j = 0, children = [], child; child = node.childNodes[j]; j++)
+        for (var j = 0, child; child = node.childNodes[j]; j++)
           if (child.nodeType == 1 && child.tagName != '!') results.push(child);
       }
       return results;
@@ -3323,7 +3326,8 @@
   },
 
   findChildElements: function(element, expressions) {
-    var exprs = expressions.join(','), expressions = [];
+    var exprs = expressions.join(',');
+    expressions = [];
     exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
       expressions.push(m[1].strip());
     });
@@ -3336,6 +3340,16 @@
   }
 });
 
+if (Prototype.Browser.IE) {
+  // IE returns comment nodes on getElementsByTagName("*").
+  // Filter them out.
+  Selector.handlers.concat = function(a, b) {
+    for (var i = 0, node; node = b[i]; i++)
+      if (node.tagName !== "!") a.push(node);
+    return a;
+  };
+}
+
 function $$() {
   return Selector.findChildElements(document, $A(arguments));
 }
@@ -3347,7 +3361,7 @@
 
   serializeElements: function(elements, options) {
     if (typeof options != 'object') options = { hash: !!options };
-    else if (options.hash === undefined) options.hash = true;
+    else if (Object.isUndefined(options.hash)) options.hash = true;
     var key, value, submitted = false, submit = options.submit;
 
     var data = elements.inject({ }, function(result, element) {
@@ -3545,17 +3559,17 @@
   },
 
   inputSelector: function(element, value) {
-    if (value === undefined) return element.checked ? element.value : null;
+    if (Object.isUndefined(value)) return element.checked ? element.value : null;
     else element.checked = !!value;
   },
 
   textarea: function(element, value) {
-    if (value === undefined) return element.value;
+    if (Object.isUndefined(value)) return element.value;
     else element.value = value;
   },
 
   select: function(element, index) {
-    if (index === undefined)
+    if (Object.isUndefined(index))
       return this[element.type == 'select-one' ?
         'selectOne' : 'selectMany'](element);
     else {
@@ -3746,7 +3760,9 @@
 
     findElement: function(event, expression) {
       var element = Event.element(event);
-      return element.match(expression) ? element : element.up(expression);
+      if (!expression) return element;
+      var elements = [element].concat(element.ancestors());
+      return Selector.findElement(elements, expression, 0);
     },
 
     pointer: function(event) {
@@ -3938,7 +3954,7 @@
         element.fireEvent(event.eventType, event);
       }
 
-      return event;
+      return Event.extend(event);
     }
   };
 })());

Added: ode/sandbox/singleshot/public/stylesheets/buttons.css
URL: http://svn.apache.org/viewvc/ode/sandbox/singleshot/public/stylesheets/buttons.css?rev=603406&view=auto
==============================================================================
--- ode/sandbox/singleshot/public/stylesheets/buttons.css (added)
+++ ode/sandbox/singleshot/public/stylesheets/buttons.css Tue Dec 11 15:14:39 2007
@@ -0,0 +1,98 @@
+/* -------------------------------------------------------------- 
+  
+   buttons.css
+   * Gives you some great CSS-only buttons.
+   
+   Created by Kevin Hale [particletree.com]
+   * particletree.com/features/rediscovering-the-button-element
+
+   See Readme.txt in this folder for instructions.
+
+-------------------------------------------------------------- */
+
+a.button, button, .button-to input {
+  display:block;
+  float:left;
+  margin:0 0.583em 0.667em 0;
+  padding:5px 10px 5px 7px;   /* Links */
+  
+  border:1px solid #dedede;
+  border-top:1px solid #eee;
+  border-left:1px solid #eee;
+
+  background-color:#f5f5f5;
+  font-family:"Lucida Grande", Tahoma, Arial, Verdana, sans-serif;
+  font-size:100%;
+  line-height:130%;
+  text-decoration:none;
+  font-weight:bold;
+  color:#565656;
+  cursor:pointer;
+}
+button, .button-to input {
+  width:auto;
+  overflow:visible;
+  padding:4px 10px 3px 7px;   /* IE6 */
+}
+button[type] {
+  padding:4px 10px 4px 7px;   /* Firefox */
+  line-height:17px;           /* Safari */
+}
+*:first-child+html button[type] {
+  padding:4px 10px 3px 7px;   /* IE7 */
+}
+button img, a.button img {
+  margin:0 3px -3px 0 !important;
+  padding:0;
+  border:none;
+  width:16px;
+  height:16px;
+  float:none;
+}
+
+
+
+/* Button colors
+-------------------------------------------------------------- */
+
+/* Standard */
+button:hover, a.button:hover, .button-to input:hover {
+  background-color:#dff4ff;
+  border:1px solid #c2e1ef;
+  color:#336699;
+}
+a.button:active{
+  background-color:#6299c5;
+  border:1px solid #6299c5;
+  color:#fff;
+}
+
+/* Positive */
+body .positive {
+  color:#529214;
+}
+a.positive:hover, button.positive:hover, .button-to.positive input:hover {
+  background-color:#E6EFC2;
+  border:1px solid #C6D880;
+  color:#529214;
+}
+a.positive:active {
+  background-color:#529214;
+  border:1px solid #529214;
+  color:#fff;
+}
+
+/* Negative */
+body .negative {
+  color:#d12f19;
+}
+a.negative:hover, button.negative:hover, .button-to.negative input:hover {
+  background:#fbe3e4;
+  border:1px solid #fbc2c4;
+  color:#d12f19;
+}
+a.negative:active {
+  background-color:#d12f19;
+  border:1px solid #d12f19;
+  color:#fff;
+}

Propchange: ode/sandbox/singleshot/public/stylesheets/buttons.css
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ode/sandbox/singleshot/public/stylesheets/buttons.css
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: ode/sandbox/singleshot/public/stylesheets/buttons.css
------------------------------------------------------------------------------
    svn:mime-type = text/css

Modified: ode/sandbox/singleshot/public/stylesheets/default.css
URL: http://svn.apache.org/viewvc/ode/sandbox/singleshot/public/stylesheets/default.css?rev=603406&r1=603405&r2=603406&view=diff
==============================================================================
--- ode/sandbox/singleshot/public/stylesheets/default.css (original)
+++ ode/sandbox/singleshot/public/stylesheets/default.css Tue Dec 11 15:14:39 2007
@@ -1,38 +1,14 @@
-body {
-  color: #000;
-  background-color: #fff;
-  font-family: Tahoma, Arial, Helvetica, sans-serif;
-	line-height: 140%;
-  margin: 0;
-  padding: 0;
-}
-
-a:link, a:visited{
-  color: #046380;
-  text-decoration: none;
-}
-
-a:hover{
-  color: #069BC9;
-  text-decoration: underline;
-}
-
-img {
-  border: none;
-}
-
-abbr {
-  cursor: help;
-  border: none;
-}
+@import 'screen.css';
+@import 'buttons.css';
 
 /** Form controls **/
 
-input[type=text], input[type=password] {
+input[type=text], input[type=password], select {
   background: transparent url(/images/textfield.png) bottom left repeat-x;
   padding: 0.25em 0.5em;
   border: 1px solid #ccc;
   margin: 0;
+  width:30em;
 }
 input[type=text]:hover, input[type=password]:hover, select:hover {
   border: 1px solid #888;
@@ -40,23 +16,6 @@
 input[type=text]:focus, input[type=password]:focus, select:focus {
   border: 1px solid #000;
 }
-select {
-  padding: 0.25em;
-  border: 1px outset #ccc;
-}
-input[type=submit], input[type=reset], button {
-  border: outset 1px #ccc;
-  background: #999;
-  color: #444;
-  padding: 0.1em 0.2em 0.1em 0.2em;
-  margin: 0;
-  background: url(/images/submit.gif) repeat-x left top;
-  text-decoration: none;
-}
-input[type=submit]:disabled, input[type=reset]:disabled, button:disabled {
-  color: #ccc;
-  cursor: default;
-}
 option {
   margin-top: 0.3em;
 }
@@ -71,6 +30,25 @@
 form.button-to div {
   display: inline;
 }
+/*
+form div.fieldWithErrors {
+  color: red;
+  display: inline;
+}
+*/
+div#errorExplanation p, div#errorExplanation ul {
+  margin: 0;
+  list-style: none;
+}
+
+form dl {
+  margin: 0 0 1em 0;
+}
+form dl dt {
+}
+form dl dd {
+  margin: 0 0 1em 0;
+}
 
 
 /** Header **/
@@ -92,40 +70,72 @@
 
 
 /** Login **/
-div.login {
-  width: 25em;
+form.login {
+  width: 30em;
   margin: 2em auto 2em auto;
-  border: 1px solid #ccc;
-  padding: 1em 2em 2em 2em;
 }
-div.login dt {
-  width: 6em;
-  float: left;
-  font-weight: bolder;
+form.login dl {
+  width: 25em;
+  margin: 0 auto 0 auto;
 }
-div.login dd {
-  margin: 0 0 1em 7em;
-}
-div.login .error {
-  color: red;
+form.login input {
+  width: 100%;
 }
 
 
-/** Tabular forms **/
 
-form table {
-  margin: 0 0 1em 0;
+/** Task view: bar and frame **/
+body#task {
+  margin:0;
+  font-size:100%;
 }
-form table td {
-  text-align: left;
-  vertical-align: top;
-  padding: 0.5em;
-}
-form table td label {
-  font-weight: bold;
-}
-form table td span.tip {
-  display: block;
-  color: #444;
-  font-size: 0.9em;
+body#task #task_bar {
+  position:absolute;
+  width:100%;
+  margin:0;
+  padding:0;
+}
+
+body#task #summary {
+  height:2em;
+  padding-top:0.3em;
+  background:#383838;
+  color:#d8d8d8;
+}
+body#task #summary a {
+  color:white;
+  text-decoration:none;
+}
+body#task #summary a:hover {
+  text-decoration:underline;
+}
+body#task #summary form.button-to input {
+  border:none;
+  font-size:100%;
+  line-height:100%;
+  background:transparent;
+  cursor:pointer;
+  color:white;
+  padding:0;
+  margin:0 1em 0 0;
+}
+body#task #summary form.button-to input:hover {
+  text-decoration:underline;
+}
+
+body#task #expanded {
+  clear:both;
+  background:#eee;
+  padding:1em;
+  border-bottom:0.05em solid #383838;
+  margin:0;
+}
+
+body#task iframe#task_frame {
+  border:0;
+  width:100%;
+  height:0;
+  padding:0;
+  margin:2em 0 0 0;
+  background:transparent;
 }

Added: ode/sandbox/singleshot/public/stylesheets/print.css
URL: http://svn.apache.org/viewvc/ode/sandbox/singleshot/public/stylesheets/print.css?rev=603406&view=auto
==============================================================================
--- ode/sandbox/singleshot/public/stylesheets/print.css (added)
+++ ode/sandbox/singleshot/public/stylesheets/print.css Tue Dec 11 15:14:39 2007
@@ -0,0 +1,76 @@
+body {
+line-height:1.5;
+font-family:"Helvetica Neue", "Lucida Grande", Arial, Verdana, sans-serif;
+color:#000;
+background:none;
+font-size:10pt;
+}
+
+.container {
+background:none;
+}
+
+h1,h2,h3,h4,h5,h6 {
+font-family:"Helvetica Neue", Arial, "Lucida Grande", sans-serif;
+}
+
+code {
+font:.9em "Courier New", Monaco, Courier, monospace;
+}
+
+img {
+float:left;
+margin:1.5em 1.5em 1.5em 0;
+}
+
+a img {
+border:none;
+}
+
+p img.top {
+margin-top:0;
+}
+
+hr {
+background:#ccc;
+color:#ccc;
+width:100%;
+height:2px;
+border:none;
+margin:2em 0;
+padding:0;
+}
+
+blockquote {
+font-style:italic;
+font-size:.9em;
+margin:1.5em;
+padding:1em;
+}
+
+.small {
+font-size:.9em;
+}
+
+.large {
+font-size:1.1em;
+}
+
+.quiet {
+color:#999;
+}
+
+.hide {
+display:none;
+}
+
+a:link,a:visited {
+background:transparent;
+font-weight:700;
+text-decoration:underline;
+}
+
+a:link:after,a:visited:after {
+content:" (" attr(href) ") ";
+font-size:90%;
+}
\ No newline at end of file

Propchange: ode/sandbox/singleshot/public/stylesheets/print.css
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ode/sandbox/singleshot/public/stylesheets/print.css
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: ode/sandbox/singleshot/public/stylesheets/print.css
------------------------------------------------------------------------------
    svn:mime-type = text/css

Added: ode/sandbox/singleshot/public/stylesheets/screen.css
URL: http://svn.apache.org/viewvc/ode/sandbox/singleshot/public/stylesheets/screen.css?rev=603406&view=auto
==============================================================================
--- ode/sandbox/singleshot/public/stylesheets/screen.css (added)
+++ ode/sandbox/singleshot/public/stylesheets/screen.css Tue Dec 11 15:14:39 2007
@@ -0,0 +1,697 @@
+html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,code,del,dfn,em,img,q,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td {
+border:0;
+font-weight:inherit;
+font-style:inherit;
+font-size:100%;
+font-family:inherit;
+vertical-align:baseline;
+margin:0;
+padding:0;
+}
+
+body {
+line-height:1.5;
+background:#fff;
+font-size:75%;
+color:#222;
+font-family:"Helvetica Neue", "Lucida Grande", Helvetica, Arial, Verdana, sans-serif;
+margin:1.5em 0;
+}
+
+table {
+border-collapse:separate;
+border-spacing:0;
+margin-bottom:1.4em;
+}
+
+caption,th,td {
+text-align:left;
+font-weight:400;
+}
+
+blockquote:before,blockquote:after,q:before,q:after {
+content:"";
+}
+
+blockquote,q {
+quotes:;
+}
+
+a img {
+border:none;
+}
+
+h1,h2,h3,h4,h5,h6 {
+color:#111;
+font-family:"Helvetica Neue", Helvetica, Arial, sans-serif;
+font-weight:400;
+}
+
+h1 {
+font-size:3em;
+line-height:1;
+margin-bottom:.5em;
+}
+
+h2 {
+font-size:2em;
+margin-bottom:.75em;
+}
+
+h3 {
+font-size:1.5em;
+line-height:1;
+margin-bottom:1em;
+}
+
+h4 {
+font-size:1.2em;
+line-height:1.25;
+margin-bottom:1.25em;
+}
+
+h5 {
+font-size:1em;
+font-weight:700;
+margin-bottom:1.5em;
+}
+
+h6 {
+font-size:1em;
+font-weight:700;
+}
+
+p.last {
+margin-bottom:0;
+}
+
+p img {
+float:left;
+margin:1.5em 1.5em 1.5em 0;
+padding:0;
+}
+
+p img.top {
+margin-top:0;
+}
+
+ul,ol {
+margin:0 1.5em 1.5em;
+}
+
+ul {
+list-style-type:circle;
+}
+
+ol {
+list-style-type:decimal;
+}
+
+dd {
+margin-left:1.5em;
+}
+
+abbr,acronym {
+cursor:help;
+border:none;
+}
+
+address {
+margin-top:1.5em;
+font-style:italic;
+}
+
+a:focus,a:hover {
+color:#000;
+}
+
+a {
+color:#009;
+text-decoration:underline;
+}
+
+blockquote {
+color:#666;
+font-style:italic;
+margin:1.5em;
+}
+
+em,dfn {
+font-style:italic;
+background:#ffc;
+}
+
+pre,code {
+white-space:pre;
+margin:1.5em 0;
+}
+
+pre,code,tt {
+font:1em 'andale mono', monotype.com, 'lucida console', monospace;
+line-height:1.5;
+}
+
+tt {
+display:block;
+line-height:1.5;
+margin:1.5em 0;
+}
+
+th {
+border-bottom:2px solid #ccc;
+font-weight:700;
+}
+
+td {
+border-bottom:1px solid #ddd;
+}
+
+th,td {
+padding:4px 10px 4px 0;
+}
+
+tfoot {
+font-style:italic;
+}
+
+caption {
+background:#ffc;
+}
+
+table .last {
+padding-right:0;
+}
+
+.small {
+font-size:.8em;
+margin-bottom:1.875em;
+line-height:1.875em;
+}
+
+.large {
+font-size:1.2em;
+line-height:2.5em;
+margin-bottom:1.25em;
+}
+
+.hide {
+display:none;
+}
+
+.highlight {
+background:#ff0;
+}
+
+.added {
+color:#060;
+}
+
+.removed {
+color:#900;
+}
+
+.top {
+margin-top:0;
+padding-top:0;
+}
+
+.bottom {
+margin-bottom:0;
+padding-bottom:0;
+}
+
+.container {
+width:950px;
+margin:0 auto;
+}
+
+.column {
+float:left;
+margin-right:10px;
+}
+
+.last {
+margin-right:0;
+}
+
+.span-1 {
+width:30px;
+}
+
+.span-2 {
+width:70px;
+}
+
+.span-3 {
+width:110px;
+}
+
+.span-4 {
+width:150px;
+}
+
+.span-5 {
+width:190px;
+}
+
+.span-6 {
+width:230px;
+}
+
+.span-7 {
+width:270px;
+}
+
+.span-8 {
+width:310px;
+}
+
+.span-9 {
+width:350px;
+}
+
+.span-10 {
+width:390px;
+}
+
+.span-11 {
+width:430px;
+}
+
+.span-12 {
+width:470px;
+}
+
+.span-13 {
+width:510px;
+}
+
+.span-14 {
+width:550px;
+}
+
+.span-15 {
+width:590px;
+}
+
+.span-16 {
+width:630px;
+}
+
+.span-17 {
+width:670px;
+}
+
+.span-18 {
+width:710px;
+}
+
+.span-19 {
+width:750px;
+}
+
+.span-20 {
+width:790px;
+}
+
+.span-21 {
+width:830px;
+}
+
+.span-22 {
+width:870px;
+}
+
+.span-23 {
+width:910px;
+}
+
+.span-24 {
+width:950px;
+margin:0;
+}
+
+.append-1 {
+padding-right:40px;
+}
+
+.append-2 {
+padding-right:80px;
+}
+
+.append-3 {
+padding-right:120px;
+}
+
+.append-4 {
+padding-right:160px;
+}
+
+.append-5 {
+padding-right:200px;
+}
+
+.append-6 {
+padding-right:240px;
+}
+
+.append-7 {
+padding-right:280px;
+}
+
+.append-8 {
+padding-right:320px;
+}
+
+.append-9 {
+padding-right:360px;
+}
+
+.append-10 {
+padding-right:400px;
+}
+
+.append-11 {
+padding-right:440px;
+}
+
+.append-12 {
+padding-right:480px;
+}
+
+.append-13 {
+padding-right:520px;
+}
+
+.append-14 {
+padding-right:560px;
+}
+
+.append-15 {
+padding-right:600px;
+}
+
+.append-16 {
+padding-right:640px;
+}
+
+.append-17 {
+padding-right:680px;
+}
+
+.append-18 {
+padding-right:720px;
+}
+
+.append-19 {
+padding-right:760px;
+}
+
+.append-20 {
+padding-right:800px;
+}
+
+.append-21 {
+padding-right:840px;
+}
+
+.append-22 {
+padding-right:880px;
+}
+
+.append-23 {
+padding-right:920px;
+}
+
+.prepend-1 {
+padding-left:40px;
+}
+
+.prepend-2 {
+padding-left:80px;
+}
+
+.prepend-3 {
+padding-left:120px;
+}
+
+.prepend-4 {
+padding-left:160px;
+}
+
+.prepend-5 {
+padding-left:200px;
+}
+
+.prepend-6 {
+padding-left:240px;
+}
+
+.prepend-7 {
+padding-left:280px;
+}
+
+.prepend-8 {
+padding-left:320px;
+}
+
+.prepend-9 {
+padding-left:360px;
+}
+
+.prepend-10 {
+padding-left:400px;
+}
+
+.prepend-11 {
+padding-left:440px;
+}
+
+.prepend-12 {
+padding-left:480px;
+}
+
+.prepend-13 {
+padding-left:520px;
+}
+
+.prepend-14 {
+padding-left:560px;
+}
+
+.prepend-15 {
+padding-left:600px;
+}
+
+.prepend-16 {
+padding-left:640px;
+}
+
+.prepend-17 {
+padding-left:680px;
+}
+
+.prepend-18 {
+padding-left:720px;
+}
+
+.prepend-19 {
+padding-left:760px;
+}
+
+.prepend-20 {
+padding-left:800px;
+}
+
+.prepend-21 {
+padding-left:840px;
+}
+
+.prepend-22 {
+padding-left:880px;
+}
+
+.prepend-23 {
+padding-left:920px;
+}
+
+.border {
+padding-right:4px;
+margin-right:5px;
+border-right:1px solid #eee;
+}
+
+.colborder {
+padding-right:24px;
+margin-right:25px;
+border-right:1px solid #eee;
+}
+
+.pull-1 {
+margin-left:-40px;
+}
+
+.pull-2 {
+margin-left:-80px;
+}
+
+.pull-3 {
+margin-left:-120px;
+}
+
+.pull-4 {
+margin-left:-160px;
+}
+
+.push-0 {
+margin:0 0 0 18px;
+}
+
+.push-1 {
+margin:0 -40px 0 18px;
+}
+
+.push-2 {
+margin:0 -80px 0 18px;
+}
+
+.push-3 {
+margin:0 -120px 0 18px;
+}
+
+.push-4 {
+margin:0 -160px 0 18px;
+}
+
+.push-0,.push-1,.push-2,.push-3,.push-4 {
+float:right;
+}
+
+.box {
+margin-bottom:1.5em;
+background:#eee;
+padding:1.5em;
+}
+
+hr {
+background:#ddd;
+color:#ddd;
+clear:both;
+float:none;
+width:100%;
+height:.1em;
+border:none;
+margin:0 0 1.4em;
+}
+
+hr.space {
+background:#fff;
+color:#fff;
+}
+
+.clear {
+display:block;
+}
+
+.clear:after,.container:after {
+content:".";
+display:block;
+height:0;
+clear:both;
+visibility:hidden;
+}
+
+* html .clear {
+height:1%;
+}
+
+fieldset {
+border:1px solid #ccc;
+margin:0 0 1.5em;
+padding:1.4em;
+}
+
+legend {
+font-weight:700;
+font-size:1.2em;
+}
+
+input.text,input.title {
+width:300px;
+border:1px solid #bbb;
+background:#f6f6f6;
+margin:.5em .5em .5em 0;
+padding:5px;
+}
+
+input.title {
+font-size:1.5em;
+}
+
+textarea {
+width:400px;
+height:250px;
+border:1px solid #bbb;
+background:#eee;
+margin:.5em .5em .5em 0;
+padding:5px;
+}
+
+select {
+border:1px solid #ccc;
+background:#f6f6f6;
+width:200px;
+}
+
+.error,.notice,.success {
+margin-bottom:1em;
+border:2px solid #ddd;
+padding:.8em;
+}
+
+.error {
+background:#FBE3E4;
+color:#D12F19;
+border-color:#FBC2C4;
+}
+
+.notice {
+background:#FFF6BF;
+color:#817134;
+border-color:#FFD324;
+}
+
+.success {
+background:#E6EFC2;
+color:#529214;
+border-color:#C6D880;
+}
+
+.error a {
+color:#D12F19;
+}
+
+.notice a {
+color:#817134;
+}
+
+.success a {
+color:#529214;
+}
+
+p,img,dl {
+margin:0 0 1.5em;
+}
+
+dl dt,strong,dfn,label {
+font-weight:700;
+}
+
+del,.quiet {
+color:#666;
+}
+
+input.text:focus,input.title:focus,textarea:focus,select:focus {
+background:#fff;
+border:1px solid #999;
+}

Propchange: ode/sandbox/singleshot/public/stylesheets/screen.css
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ode/sandbox/singleshot/public/stylesheets/screen.css
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: ode/sandbox/singleshot/public/stylesheets/screen.css
------------------------------------------------------------------------------
    svn:mime-type = text/css

Modified: ode/sandbox/singleshot/spec/common.rb
URL: http://svn.apache.org/viewvc/ode/sandbox/singleshot/spec/common.rb?rev=603406&r1=603405&r2=603406&view=diff
==============================================================================
--- ode/sandbox/singleshot/spec/common.rb (original)
+++ ode/sandbox/singleshot/spec/common.rb Tue Dec 11 15:14:39 2007
@@ -5,27 +5,37 @@
     def self.included(mod)
       mod.after :all do
         Person.delete_all
-        Task.admins = nil
+        Stakeholder.delete_all
       end
     end
 
-    def person(nickname)
+    def person(identity)
       @_people ||= {}
-      @_people[nickname.to_s] ||= Person.find_by_nickname(nickname) ||
-        Person.create(:email=>"#{nickname}@apache.org", :password=>'secret')
+      @_people[identity.to_s] ||= Person.identify(identity) ||
+        Person.create(:email=>"#{identity}@apache.org", :password=>'secret')
+    end
+
+    def people(*identities)
+      identities.map { |identity| person(identity) }
+    end
+
+    def su
+      @_su ||= Person.create(:email=>'super@apache.org', :admin=>true)
     end
 
     def authenticate(person)
       @authenticated = person
       session[:person_id] = person.id
-      request.headers['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(person.nickname, 'secret')
+      credentials = [person.identity, 'secret']
+      request.headers['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(*credentials)
     end
 
     attr_reader :authenticated
 
     def all_roles
       singular = Task::SINGULAR_ROLES.inject({}) { |h, role| h.update(role=>person(role)) }
-      Task::PLURAL_ROLES.inject(singular) { |h, role| h.update(role.to_s.pluralize.to_sym=>person(role)) }
+      (Task::PLURAL_ROLES - [:excluded_owner]).
+        inject(singular) { |h, role| h.update(role.to_s.pluralize.to_sym=>Array.new(3) { |i| person("#{role}#{i}") }) }
     end
 
   end
@@ -36,14 +46,13 @@
       mod.after :all do
         Task.delete_all
       end
+      mod.send :include, Authentication
     end
 
-    def default_values(merge = nil)
-      base = {
-        :title=>'Test Task model',
-        :form_url=>'http://test.host/do',
+    def default_task
+      { :title=>'Test this',
+        :frame_url=>'http://test.host/fill_me',
         :outcome_url=>'http://test.host/outcome' }
-      merge ? base.merge(merge) : base
     end
 
   end