Only in agility-gitorial: .git Only in agility-gitorial: .gitignore Only in agility-gitorial: .gitmodules diff -r agility-gitorial/README agility/README 1c1 < # Agility --- > == Welcome to Rails 3,4c3,4 < This is the source code to accompany the [Agility gitorial for < Hobo](http://cookbook.hobocentral.net/gitorials/agility). --- > Rails is a web-application framework that includes everything needed to create > database-backed web applications according to the Model-View-Control pattern. 6,23c6,243 < Agility is a simple "Agile Development" application – Agility. The < application tracks projects which consist of a number of user < stories. Stories have a status (e.g. accepted, under development…) as < well as a number of associated tasks. Tasks can be assigned to users, < and each user can see a heads-up of all the tasks they’ve been < assigned to on their home page. < < To keep the gitorial clean, updates to the gitorial **rewrite < history**. That means that you cannot `git pull`. If you wish to < update to a later version, you must `git clone` the repository again < and move any changes you have made from your old clone to the new one. < < The "real" history is kept in our [source < repository](http://github.com/bryanlarsen/agility-gitorial-patches/tree/master). < < There is a [version of < agility](http://github.com/tablatom/agility/tree/master) that is not < in gitorial format. This tree is mainly used for integration testing Hobo. --- > This pattern splits the view (also called the presentation) into "dumb" templates > that are primarily responsible for inserting pre-built data in between HTML tags. > The model contains the "smart" domain objects (such as Account, Product, Person, > Post) that holds all the business logic and knows how to persist themselves to > a database. The controller handles the incoming requests (such as Save New Account, > Update Product, Show Post) by manipulating the model and directing data to the view. > > In Rails, the model is handled by what's called an object-relational mapping > layer entitled Active Record. This layer allows you to present the data from > database rows as objects and embellish these data objects with business logic > methods. You can read more about Active Record in > link:files/vendor/rails/activerecord/README.html. > > The controller and view are handled by the Action Pack, which handles both > layers by its two parts: Action View and Action Controller. These two layers > are bundled in a single package due to their heavy interdependence. This is > unlike the relationship between the Active Record and Action Pack that is much > more separate. Each of these packages can be used independently outside of > Rails. You can read more about Action Pack in > link:files/vendor/rails/actionpack/README.html. > > > == Getting Started > > 1. At the command prompt, start a new Rails application using the rails command > and your application name. Ex: rails myapp > 2. Change directory into myapp and start the web server: script/server (run with --help for options) > 3. Go to http://localhost:3000/ and get "Welcome aboard: You're riding the Rails!" > 4. Follow the guidelines to start developing your application > > > == Web Servers > > By default, Rails will try to use Mongrel if it's are installed when started with script/server, otherwise Rails will use WEBrick, the webserver that ships with Ruby. But you can also use Rails > with a variety of other web servers. > > Mongrel is a Ruby-based webserver with a C component (which requires compilation) that is > suitable for development and deployment of Rails applications. If you have Ruby Gems installed, > getting up and running with mongrel is as easy as: gem install mongrel. > More info at: http://mongrel.rubyforge.org > > Say other Ruby web servers like Thin and Ebb or regular web servers like Apache or LiteSpeed or > Lighttpd or IIS. The Ruby web servers are run through Rack and the latter can either be setup to use > FCGI or proxy to a pack of Mongrels/Thin/Ebb servers. > > == Apache .htaccess example for FCGI/CGI > > # General Apache options > AddHandler fastcgi-script .fcgi > AddHandler cgi-script .cgi > Options +FollowSymLinks +ExecCGI > > # If you don't want Rails to look in certain directories, > # use the following rewrite rules so that Apache won't rewrite certain requests > # > # Example: > # RewriteCond %{REQUEST_URI} ^/notrails.* > # RewriteRule .* - [L] > > # Redirect all requests not available on the filesystem to Rails > # By default the cgi dispatcher is used which is very slow > # > # For better performance replace the dispatcher with the fastcgi one > # > # Example: > # RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] > RewriteEngine On > > # If your Rails application is accessed via an Alias directive, > # then you MUST also set the RewriteBase in this htaccess file. > # > # Example: > # Alias /myrailsapp /path/to/myrailsapp/public > # RewriteBase /myrailsapp > > RewriteRule ^$ index.html [QSA] > RewriteRule ^([^.]+)$ $1.html [QSA] > RewriteCond %{REQUEST_FILENAME} !-f > RewriteRule ^(.*)$ dispatch.cgi [QSA,L] > > # In case Rails experiences terminal errors > # Instead of displaying this message you can supply a file here which will be rendered instead > # > # Example: > # ErrorDocument 500 /500.html > > ErrorDocument 500 "

Application error

Rails application failed to start properly" > > > == Debugging Rails > > Sometimes your application goes wrong. Fortunately there are a lot of tools that > will help you debug it and get it back on the rails. > > First area to check is the application log files. Have "tail -f" commands running > on the server.log and development.log. Rails will automatically display debugging > and runtime information to these files. Debugging info will also be shown in the > browser on requests from 127.0.0.1. > > You can also log your own messages directly into the log file from your code using > the Ruby logger class from inside your controllers. Example: > > class WeblogController < ActionController::Base > def destroy > @weblog = Weblog.find(params[:id]) > @weblog.destroy > logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") > end > end > > The result will be a message in your log file along the lines of: > > Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1 > > More information on how to use the logger is at http://www.ruby-doc.org/core/ > > Also, Ruby documentation can be found at http://www.ruby-lang.org/ including: > > * The Learning Ruby (Pickaxe) Book: http://www.ruby-doc.org/docs/ProgrammingRuby/ > * Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) > > These two online (and free) books will bring you up to speed on the Ruby language > and also on programming in general. > > > == Debugger > > Debugger support is available through the debugger command when you start your Mongrel or > Webrick server with --debugger. This means that you can break out of execution at any point > in the code, investigate and change the model, AND then resume execution! > You need to install ruby-debug to run the server in debugging mode. With gems, use 'gem install ruby-debug' > Example: > > class WeblogController < ActionController::Base > def index > @posts = Post.find(:all) > debugger > end > end > > So the controller will accept the action, run the first line, then present you > with a IRB prompt in the server window. Here you can do things like: > > >> @posts.inspect > => "[#nil, \"body\"=>nil, \"id\"=>\"1\"}>, > #\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]" > >> @posts.first.title = "hello from a debugger" > => "hello from a debugger" > > ...and even better is that you can examine how your runtime objects actually work: > > >> f = @posts.first > => #nil, "body"=>nil, "id"=>"1"}> > >> f. > Display all 152 possibilities? (y or n) > > Finally, when you're ready to resume execution, you enter "cont" > > > == Console > > You can interact with the domain model by starting the console through script/console. > Here you'll have all parts of the application configured, just like it is when the > application is running. You can inspect domain models, change values, and save to the > database. Starting the script without arguments will launch it in the development environment. > Passing an argument will specify a different environment, like script/console production. > > To reload your controllers and models after launching the console run reload! > > == dbconsole > > You can go to the command line of your database directly through script/dbconsole. > You would be connected to the database with the credentials defined in database.yml. > Starting the script without arguments will connect you to the development database. Passing an > argument will connect you to a different database, like script/dbconsole production. > Currently works for mysql, postgresql and sqlite. > > == Description of Contents > > app > Holds all the code that's specific to this particular application. > > app/controllers > Holds controllers that should be named like weblogs_controller.rb for > automated URL mapping. All controllers should descend from ApplicationController > which itself descends from ActionController::Base. > > app/models > Holds models that should be named like post.rb. > Most models will descend from ActiveRecord::Base. > > app/views > Holds the template files for the view that should be named like > weblogs/index.html.erb for the WeblogsController#index action. All views use eRuby > syntax. > > app/views/layouts > Holds the template files for layouts to be used with views. This models the common > header/footer method of wrapping views. In your views, define a layout using the > layout :default and create a file named default.html.erb. Inside default.html.erb, > call <% yield %> to render the view using this layout. > > app/helpers > Holds view helpers that should be named like weblogs_helper.rb. These are generated > for you automatically when using script/generate for controllers. Helpers can be used to > wrap functionality for your views into methods. > > config > Configuration files for the Rails environment, the routing map, the database, and other dependencies. > > db > Contains the database schema in schema.rb. db/migrate contains all > the sequence of Migrations for your schema. > > doc > This directory is where your application documentation will be stored when generated > using rake doc:app > > lib > Application specific libraries. Basically, any kind of custom code that doesn't > belong under controllers, models, or helpers. This directory is in the load path. > > public > The directory available for the web server. Contains subdirectories for images, stylesheets, > and javascripts. Also contains the dispatchers and the default HTML files. This should be > set as the DOCUMENT_ROOT of your web server. > > script > Helper scripts for automation and generation. > > test > Unit and functional tests along with fixtures. When using the script/generate scripts, template > test files will be generated for you and placed in this directory. > > vendor > External libraries that the application depends on. Also includes the plugins subdirectory. > If the app has frozen rails, those gems also go here, under vendor/rails/. > This directory is in the load path. diff -r agility-gitorial/app/controllers/projects_controller.rb agility/app/controllers/projects_controller.rb 6c6 < --- > 11,14c11 < @stories = < @project.stories.apply_scopes(:search => [params[:search], :title], < :status_is => params[:status], < :order_by => parse_sort_param(:title, :status)) --- > @stories = @project.stories.apply_scopes(:search => [params[:search], :title], :status_is => params[:status], :order_by => parse_sort_param(:title_status)) 17c14 < end --- > end \ No newline at end of file diff -r agility-gitorial/app/controllers/stories_controller.rb agility/app/controllers/stories_controller.rb 6d5 < diff -r agility-gitorial/app/controllers/tasks_controller.rb agility/app/controllers/tasks_controller.rb 6c6 < --- > 8d7 < diff -r agility-gitorial/app/controllers/users_controller.rb agility/app/controllers/users_controller.rb 6c6 < --- > 11,17c11,12 < if this.errors.blank? < flash[:notice] << "You must activate your account before you can log in. Please check your email." < < # FIXME: remove these two lines after you get email working reliably < # and before your application leaves its sandbox... < secret_path = user_activate_path :id=>this.id, :key => this.lifecycle.key < flash[:notice] << "The 'secret' link that was just emailed was: #{secret_path}." --- > if(this.errors.blank?) > flash[:notice] << "You must activate your account before you log in. Please check your email." 20c15 < end --- > end diff -r agility-gitorial/app/models/project.rb agility/app/models/project.rb 13c13 < --- > 16,23d15 < has_many :contributor_memberships, :class_name => "ProjectMembership", :scope => :contributor < has_many :contributors, :through => :contributor_memberships, :source => :user < < # permission helper < def accepts_changes_from?(user) < user.administrator? || user == owner || user.in?(contributors) < end < 31c23 < accepts_changes_from?(acting_user) && !owner_changed? --- > acting_user.administrator? || (owner_is?(acting_user) && !owner_changed?) diff -r agility-gitorial/app/models/project_membership.rb agility/app/models/project_membership.rb 6d5 < contributor :boolean, :default => false 16c15 < acting_user.administrator? || project.owner_is?(acting_user) --- > acting_user.administrator? 20c19 < acting_user.administrator? || project.owner_is?(acting_user) --- > acting_user.administrator? 24c23 < acting_user.administrator? || project.owner_is?(acting_user) --- > acting_user.administrator? diff -r agility-gitorial/app/models/story.rb agility/app/models/story.rb 7c7 < body :markdown # or :textile --- > body :string 13d12 < diff -r agility-gitorial/app/models/task.rb agility/app/models/task.rb 11c11 < --- > 13,14c13,14 < has_many :users, :through => :task_assignments, :accessible => true, :dependent => :destroy < --- > has_many :users, :through => :task_assignments, :accessible => :true, :dependent => :destroy > 16c16 < --- > diff -r agility-gitorial/app/models/task_assignment.rb agility/app/models/task_assignment.rb 23c23 < task.destroyable_by?(acting_user) --- > acting_user.administrator? 27c27 < task.viewable_by?(acting_user) --- > true diff -r agility-gitorial/app/models/user.rb agility/app/models/user.rb 6c6 < name :string, :unique --- > name :string, :required, :unique 12c12,14 < validates_presence_of :name --- > # This gives admin rights to the first sign-up. > # Just remove it if you don't want that > before_create { |user| user.administrator = true if !Rails.env.test? && count == 0 } 19,23c21 < < # This gives admin rights to the first sign-up. < # Just remove it if you don't want that < before_create { |user| user.administrator = true if !Rails.env.test? && count == 0 } < --- > 27a26 > 33,38c32,35 < :become => :inactive, :new_key => true do < UserMailer.deliver_activation(self, lifecycle.key) unless email_address.blank? < end < < transition :activate, { :inactive => :active }, :available_to => :key_holder < --- > :become => :inactive, :new_key => :true do > UserMailer.deliver_activation(self, lifecycle.key) unless(email_address.blank?) > end > 48a46,47 > > transition :activate, { :inactive => :active }, :available_to => :key_holder diff -r agility-gitorial/app/viewhints/project_hints.rb agility/app/viewhints/project_hints.rb 0a1 > 4c5 < --- > diff -r agility-gitorial/app/viewhints/project_membership_hints.rb agility/app/viewhints/project_membership_hints.rb 3c3,6 < --- > # model_name "My Model" > # field_names :field1 => "First Field", :field2 => "Second Field" > # field_help :field1 => "Enter what you want in this field" > # children :primary_collection1, :aside_collection1, :aside_collection2 diff -r agility-gitorial/app/viewhints/story_hints.rb agility/app/viewhints/story_hints.rb 2c2 < --- > 4c4 < --- > diff -r agility-gitorial/app/views/front/index.dryml agility/app/views/front/index.dryml 26,32c26,31 <

Your Projects

< < < New Project < <

Projects you have joined

< --- >

Your Projects

> > New Project > >

Projects you have joined

> Only in agility/app/views: layouts Only in agility/app/views: project_memberships diff -r agility-gitorial/app/views/projects/show.dryml agility/app/views/projects/show.dryml 2,26c2,25 < < < <
< Display by status: <
<
< No stories match your criteria <
<
< < <

Project Members

< < < < < <
< Add a member: < <
< <
< --- > > > >
> Display by status: >
>
> No stories match your criteria >
>
> > >

Project Members

> > > > >
> Add a member: > >
> >
> diff -r agility-gitorial/app/views/stories/show.dryml agility/app/views/stories/show.dryml 2c2 < --- > Only in agility/app/views: story_statuses diff -r agility-gitorial/app/views/taglibs/application.dryml agility/app/views/taglibs/application.dryml 12,18c12,18 < < <
< Assigned users: None <
<
<
--- > > > > > 22,40c22,25 < < < < < < < < < Contributor? < < < < < < < < Home < < --- > > > > \ No newline at end of file Only in agility/app/views/taglibs: auto diff -r agility-gitorial/app/views/tasks/edit.dryml agility/app/views/tasks/edit.dryml 2,4c2,4 < < < --- > > > diff -r agility-gitorial/app/views/user_mailer/activation.erb agility/app/views/user_mailer/activation.erb 9c9 < The <%= @app_name %> team. --- > The <%= @app_name %> team. \ No newline at end of file diff -r agility-gitorial/app/views/users/show.dryml agility/app/views/users/show.dryml 2,9c2,9 < <

Assigned Tasks

< <

Story:

< < < < --- > >

Assigned Tasks

> >

Story:

> > > > \ No newline at end of file diff -r agility-gitorial/config/database.yml agility/config/database.yml 4,7c4,8 < adapter: sqlite3 < database: db/development.sqlite3 < pool: 5 < timeout: 5000 --- > adapter: jdbcmysql > database: hobotest > username: rails > password: rails > host: localhost diff -r agility-gitorial/config/environment.rb agility/config/environment.rb 4c4 < RAILS_GEM_VERSION = '2.3.4' unless defined? RAILS_GEM_VERSION --- > RAILS_GEM_VERSION = '2.3.5' unless defined? RAILS_GEM_VERSION 11,13c11,13 < < config.gem 'bluecloth' < --- > > config.gem 'maruku' > 44,57c44,45 < config.i18n.default_locale = :fr < end < < HOBO_VERBOSE_TRANSLATIONS = true < < #ActionMailer::Base.delivery_method = :smtp < #ActionMailer::Base.smtp_settings = { < # :address => "smtp.example.com", < # :port => 25, < # :domain => "example.com", < # :authentication => :login, < # :user_name => "username", < # :password => "password", < #} --- > # config.i18n.default_locale = :de > end \ No newline at end of file diff -r agility-gitorial/config/initializers/session_store.rb agility/config/initializers/session_store.rb 9c9 < :secret => 'e59cb998eddcd8e05117294501c12453ef266b86151ac7a9ba59274270079d07d130728d2201e35f77231ea449290e7e7a49c378ee9d2dd4295186b4ac7ad68d' --- > :secret => '9268fc1ffe1ba1ce1f55ed1de5a3daa1be0625d66ca6ea3fd2c1c0161966dca007f5b104ad7abdd95e22c455bdb737e2d63f6952bfd9ee64fb9ea5575361ca20' Only in agility-gitorial/config/locales: fr.yml Only in agility-gitorial/config: selenium.yml Only in agility-gitorial/db/migrate: 20091201020248_initial_migration.rb Only in agility-gitorial/db/migrate: 20091201020837_hobo_migration_initial_models.rb Only in agility-gitorial/db/migrate: 20091201134408_hobo_migration_story_status_model.rb Only in agility-gitorial/db/migrate: 20091201134811_hobo_migration_acts_as_list.rb Only in agility-gitorial/db/migrate: 20091201135235_hobo_migration_project_membership.rb Only in agility-gitorial/db/migrate: 20091201135345_hobo_migration_project_memberships.rb Only in agility-gitorial/db/migrate: 20091201135804_hobo_migration_project_contributors.rb Only in agility/db/migrate: 20100114124734_hobo_migration_1.rb Only in agility/db/migrate: 20100114162416_hobo_migration_2.rb Only in agility/db/migrate: 20100114173648_hobo_migration_3.rb Only in agility/db/migrate: 20100114181934_hobo_migration_4.rb Only in agility/db/migrate: 20100114183604_hobo_migration_5.rb diff -r agility-gitorial/db/schema.rb agility/db/schema.rb 12c12 < ActiveRecord::Schema.define(:version => 20091201135804) do --- > ActiveRecord::Schema.define(:version => 20100114183604) do 19d18 < t.boolean "contributor", :default => false 36c35 < t.text "body" --- > t.string "body" Only in agility: lib Only in agility: log diff -r agility-gitorial/public/hobothemes/clean/stylesheets/clean.css agility/public/hobothemes/clean/stylesheets/clean.css 114,115d113 < .article {margin: 20px 0; border-top: 1px dotted #ccc;} < 319a318 > li.input-many-template { display:none; } diff -r agility-gitorial/public/javascripts/hobo-rapid.js agility/public/javascripts/hobo-rapid.js 294c294 < if (options.confirm == null) { options.fade = "Are you sure?"; } --- > if (options.confirm == null) { options.confirm = "Are you sure?"; } 483a484,491 > Element.prototype.childWithClass = function(klass) { > var ret=null; > this.childElements().each(function(el2) { > if(ret==null && el2.hasClassName(klass)) ret=el2; > }); > return ret; > } > 521a530,532 > if (features.initialize) { > document.observe("dom:loaded", features.initialize); > } 560,579c571,589 < < addOne: function(ev, el) { < Event.stop(ev) < var ul = el.up('ul'), li = el.up('li') < < var thisItem = li.down('div.input-many-item') < var newItem = "
  • " + < thisItem.innerHTML + < "
    " + < "
    " + < "
  • " < var newItem = DOM.Builder.fromHTML(newItem) < ul.appendChild(newItem); < this.clearInputs(newItem); < < this.updateButtons() < this.updateInputNames() < < ul.fire("rapid:add", { element: newItem }) < ul.fire("rapid:change", { element: newItem }) --- > > initialize: function(ul) { > // disable all elements inside our template, and mark them so we can find them later. > $$(".input-many-template input:enabled, .input-many-template select:enabled, .input-many-template textarea:enabled, .input-many-template button:enabled").each(function(input) { > input.disabled = true; > input.addClassName("input_many_template_input"); > }); > }, > > // given this==the input-many, returns a lambda that updates the name & id for an element > getNameUpdater: function(new_index) { > var name_prefix = Hobo.getClassData(this, 'input-many-prefix'); > var id_prefix = name_prefix.replace(/\[/g, "_").replace(/\]/g, ""); > var name_re = RegExp("^" + RegExp.escape(name_prefix)+ "\[\-?[0-9]+\]"); > var name_sub = name_prefix + '[' + new_index.toString() + ']'; > var id_re = RegExp("^" + RegExp.escape(id_prefix)+ "_\-?[0-9]+"); > var id_sub = id_prefix + '_' + new_index.toString(); > var class_re = RegExp(RegExp.escape(name_prefix)+ "\[\-?[0-9]+\]"); > var class_sub = name_sub; 581c591,609 < new Effect.BlindDown(newItem, {duration: 0.3}) --- > return function() { > if(this.name) { > this.name = this.name.replace(name_re, name_sub); > } > if (id_prefix==this.id.slice(0, id_prefix.length)) { > this.id = this.id.replace(id_re, id_sub); > } else { > // silly rails. text_area_tag and text_field_tag use different conventions for the id. > if(name_prefix==this.id.slice(0, name_prefix.length)) { > this.id = this.id.replace(name_re, name_sub); > } /* else { > hjq.util.log("hjq.input_many.update_id: id_prefix "+id_prefix+" didn't match input "+this.id); > } */ > } > if (class_re.test(this.className)) { > this.className = this.className.replace(class_re, class_sub); > } > return this; > }; 583,596c611,621 < < removeOne: function(ev, el) { < Event.stop(ev) < var self = this; < var ul = el.up('ul'), li = el.up('li') < if (li.parentNode.childElements().length == 1) { < // It's the last one - don't remove it, just clear it < this.clearInputs(li) < } else { < new Effect.BlindUp(li, { duration: 0.3, afterFinish: function (ef) { < li.remove() < self.updateButtons() < self.updateInputNames() < } }); --- > > // given this==an input-many item, get the submit index > getIndex: function() { > return Number(this.id.match(/\[([0-9])+\]$/)[1]); > }, > > /* For some reason, select() and down() and all those useful functions aren't working for us. Roll our own replacement. */ > recurse_elements_with_class: function(el, klass, f) { > var that=this; > if(klass==null || el.hasClassName(klass)) { > f(el); 598,599c623 < ul.fire("rapid:remove") < ul.fire("rapid:change") --- > el.childElements().each(function(el2) {that.recurse_elements_with_class.call(that, el2, klass, f);}); 602,609c626,639 < < clearInputs: function(item) { < $(item).select('input,select,textarea').each(function(input){ < t = input.getAttribute('type') < if (t && t.match(/hidden/i)) { < input.remove() < } else { < input.value = "" --- > addOne: function(ev, el) { > Event.stop(ev); > var ul = el.up('ul.input-many'), li = el.up('li.input-many-li'); > > var template = ul.down("li.input-many-template"); > var clone = $(template.cloneNode(true)); > clone.removeClassName("input-many-template"); > // length-2 because ignore the template li and the empty li > var name_updater = this.getNameUpdater.call(ul, ul.childElements().length-2); > > function reenable_inputs(el) { > if(el.hasClassName("input_many_template_input")) { > el.disabled = false; > el.removeClassName("input_many_template_input"); 611c641,672 < }) --- > el.childElements().each(function(el2) { > if(!el2.hasClassName("input-many-template")) reenable_inputs(el2); > }); > } > reenable_inputs(clone); > > // update id & name > this.recurse_elements_with_class.call(this, clone, null, function(el) { > name_updater.call(el); > }); > > // do the add with anim > clone.setStyle("display", "none") > li.insert({after: clone}); > new Effect.BlindDown(clone, {duration: 0.3}) > > // visibility > if(li.hasClassName("empty")) { > li.addClassName("hidden"); > li.childWithClass("empty-input").disabled = true; > } else { > // now that we've added an element after us, we should only have a '-' button > li.childWithClass("buttons").childWithClass("remove-item").removeClassName("hidden"); > li.childWithClass("buttons").childWithClass("add-item").addClassName("hidden"); > } > > Event.addBehavior.reload(); > > ul.fire("rapid:add", { element: clone }) > ul.fire("rapid:change", { element: clone }) > > return; 613,623c674,694 < < updateButtons: function() { < var removeButton = "" < var addButton = "" < < var ul = this.element < var children = ul.childElements(); < // assumption: only get here after add or remove, so only second last button needs the "+" removed < if(children.length > 1) { < // cannot use .down() because that's a depth-first search. Did I mention that I hate Prototype? < children[children.length-2].childElements().last().innerHTML = removeButton; --- > > removeOne: function(ev, el) { > Event.stop(ev); > var that = this; > var ul = el.up('ul.input-many'), li = el.up('li.input-many-li') > var minimum = parseInt(Hobo.getClassData(ul, 'minimum')); > > ul.fire("rapid:remove", { element: li }) > > // rename everybody from me onwards > var i=this.getIndex.call(li) > var n=li.next(); > for(; n; i+=1, n=n.next()) { > var name_updater = this.getNameUpdater.call(ul, i); > this.recurse_elements_with_class.call(this, n, null, function(el) {name_updater.call(el);}); > } > > // adjust +/- buttons on the button element as appropriate > var last=ul.childElements()[ul.childElements().length-1]; > if(last==li) { > last = last.previous(); 625,626c696,707 < if(children.length > 0) { < children[children.length-1].childElements().last().innerHTML = removeButton + ' ' + addButton; --- > > if(last.hasClassName("empty")) { > last.removeClassName("hidden"); > this.recurse_elements_with_class.call(this, last, "empty-input", function(el) {el.disabled=false;}); > } else { > // if we've reached the minimum, we don't want to add the '-' button > if(ul.childElements().length-3 <= minimum||0) { > last.childWithClass("buttons").childWithClass("remove-item").addClassName("hidden"); > } else { > last.childWithClass("buttons").childWithClass("remove-item").removeClassName("hidden"); > } > last.childWithClass("buttons").childWithClass("add-item").removeClassName("hidden"); 628c709,714 < Event.addBehavior.reload() --- > > new Effect.BlindUp(li, { duration: 0.3, afterFinish: function (ef) { > li.remove() > } }); > > ul.fire("rapid:change") 630,644c716,718 < < updateInputNames: function() { < var prefix = Hobo.getClassData(this.element, 'input-many-prefix') < < this.element.selectChildren('li').each(function(li, index) { < li.select('*[name]').each(function(control) { < if(control.name) { < var changeId = control.id == control.name; < control.name = control.name.sub(new RegExp("^" + RegExp.escape(prefix) + "\[[0-9]+\]"), prefix + '[' + index +']'); < if (changeId) control.id = control.name; < } < }) < }) < } < --- > > > diff -r agility-gitorial/public/stylesheets/application.css agility/public/stylesheets/application.css 1,2c1,2 < .show-page.project .filter {float: left;} < .show-page.project .filter form, .show-page.project .filter form div {display: inline;} --- > .show-page.project .filter { float: left; } > .show-page.project .filter form, .show-page.project .filter form div {display: inline;} \ No newline at end of file Only in agility-gitorial/test/fixtures: blank Only in agility/test: integration Only in agility-gitorial/test: selenium Only in agility: tmp Only in agility/vendor/plugins/acts_as_list: README Only in agility/vendor/plugins/acts_as_list: init.rb Only in agility/vendor/plugins/acts_as_list: lib Only in agility/vendor/plugins/acts_as_list: test Only in agility/vendor/plugins: plugin Only in agility-gitorial/vendor/plugins: selenium-on-rails