Merge pull request #30 from silent-e/expand-insertion-methods
Expand insertion methods and formatting tweaks
This commit is contained in:
commit
bb97c88994
148
README.markdown
148
README.markdown
|
@ -22,29 +22,39 @@ I have a sample project where I demonstrate the use of cocoon with formtastic.
|
||||||
|
|
||||||
Inside your `Gemfile` add the following:
|
Inside your `Gemfile` add the following:
|
||||||
|
|
||||||
gem "cocoon"
|
````ruby
|
||||||
|
gem "cocoon"
|
||||||
|
````
|
||||||
|
|
||||||
### Rails 3.1
|
### Rails 3.1
|
||||||
|
|
||||||
Add the following to `application.js` so it compiles to the
|
Add the following to `application.js` so it compiles to the
|
||||||
asset_pipeline
|
asset_pipeline
|
||||||
|
|
||||||
`//= require cocoon`
|
````ruby
|
||||||
|
//= require cocoon
|
||||||
|
````
|
||||||
|
|
||||||
### Rails 3.x
|
### Rails 3.x
|
||||||
|
|
||||||
If you are using Rails 3.0.x, you need to run the installation task (since rails 3.1 this is no longer needed):
|
If you are using Rails 3.0.x, you need to run the installation task (since rails 3.1 this is no longer needed):
|
||||||
|
|
||||||
rails g cocoon:install
|
````ruby
|
||||||
|
rails g cocoon:install
|
||||||
|
````
|
||||||
|
|
||||||
This will install the needed javascript file.
|
This will install the needed javascript file.
|
||||||
Inside your `application.html.haml` you will need to add below the default javascripts:
|
Inside your `application.html.haml` you will need to add below the default javascripts:
|
||||||
|
|
||||||
= javascript_include_tag :cocoon
|
````haml
|
||||||
|
= javascript_include_tag :cocoon
|
||||||
|
````
|
||||||
|
|
||||||
or using erb, you write
|
or using erb, you write
|
||||||
|
|
||||||
<%= javascript_include_tag :cocoon %>
|
````ruby
|
||||||
|
<%= javascript_include_tag :cocoon %>
|
||||||
|
````
|
||||||
|
|
||||||
That is all you need to do to start using it!
|
That is all you need to do to start using it!
|
||||||
|
|
||||||
|
@ -52,22 +62,28 @@ That is all you need to do to start using it!
|
||||||
|
|
||||||
Suppose you have a model `Project`:
|
Suppose you have a model `Project`:
|
||||||
|
|
||||||
rails g scaffold Project name:string description:string
|
````ruby
|
||||||
|
rails g scaffold Project name:string description:string
|
||||||
|
````
|
||||||
|
|
||||||
and a project has many `tasks`:
|
and a project has many `tasks`:
|
||||||
|
|
||||||
rails g model Task description:string done:boolean project_id:integer
|
````ruby
|
||||||
|
rails g model Task description:string done:boolean project_id:integer
|
||||||
|
````
|
||||||
|
|
||||||
Edit the models to code the relation:
|
Edit the models to code the relation:
|
||||||
|
|
||||||
class Project < ActiveRecord::Base
|
````ruby
|
||||||
has_many :tasks
|
class Project < ActiveRecord::Base
|
||||||
accepts_nested_attributes_for :tasks
|
has_many :tasks
|
||||||
end
|
accepts_nested_attributes_for :tasks
|
||||||
|
end
|
||||||
|
|
||||||
class Task < ActiveRecord::Base
|
class Task < ActiveRecord::Base
|
||||||
belongs_to :project
|
belongs_to :project
|
||||||
end
|
end
|
||||||
|
````
|
||||||
|
|
||||||
What we want to achieve is to get a form where we can add and remove the tasks dynamically.
|
What we want to achieve is to get a form where we can add and remove the tasks dynamically.
|
||||||
What we need for this, is that the fields for a new/existing `task` are defined in a partial
|
What we need for this, is that the fields for a new/existing `task` are defined in a partial
|
||||||
|
@ -79,25 +95,29 @@ We will show the sample usage with the different possible form-builders.
|
||||||
|
|
||||||
Inside our `projects/_form` partial we then write:
|
Inside our `projects/_form` partial we then write:
|
||||||
|
|
||||||
- f.inputs do
|
````haml
|
||||||
= f.input :name
|
- f.inputs do
|
||||||
= f.input :description
|
= f.input :name
|
||||||
%h3 Tasks
|
= f.input :description
|
||||||
#tasks
|
%h3 Tasks
|
||||||
= f.semantic_fields_for :tasks do |task|
|
#tasks
|
||||||
= render 'task_fields', :f => task
|
= f.semantic_fields_for :tasks do |task|
|
||||||
.links
|
= render 'task_fields', :f => task
|
||||||
= link_to_add_association 'add task', f, :tasks
|
.links
|
||||||
-f.buttons do
|
= link_to_add_association 'add task', f, :tasks
|
||||||
= f.submit 'Save'
|
-f.buttons do
|
||||||
|
= f.submit 'Save'
|
||||||
|
````
|
||||||
|
|
||||||
and inside the `_task_fields` partial we write:
|
and inside the `_task_fields` partial we write:
|
||||||
|
|
||||||
.nested-fields
|
````haml
|
||||||
= f.inputs do
|
.nested-fields
|
||||||
= f.input :description
|
= f.inputs do
|
||||||
= f.input :done, :as => :boolean
|
= f.input :description
|
||||||
= link_to_remove_association "remove task", f
|
= f.input :done, :as => :boolean
|
||||||
|
= link_to_remove_association "remove task", f
|
||||||
|
````
|
||||||
|
|
||||||
That is all there is to it!
|
That is all there is to it!
|
||||||
|
|
||||||
|
@ -129,9 +149,11 @@ It takes four parameters:
|
||||||
- f: referring to the containing form-object
|
- f: referring to the containing form-object
|
||||||
- association: the name of the association (plural) of which a new instance needs to be added (symbol or string).
|
- association: the name of the association (plural) of which a new instance needs to be added (symbol or string).
|
||||||
- html_options: extra html-options (see `link_to`)
|
- html_options: extra html-options (see `link_to`)
|
||||||
There are two extra options that allow to conrol the placement of the new link-data:
|
There are three extra options that allow to control the placement of the new link-data:
|
||||||
- `data-association-insertion-node` : the jquery selector of the node
|
- `data-association-insertion-node` : the jquery selector of the node
|
||||||
- `data-association-insertion-position` : insert the new data `before` or `after` the given node.
|
- `data-association-insertion-method` : jquery method that inserts the new data. `before`, `after`, `append`, `prepend`, etc. Default: `before`
|
||||||
|
- `data-association-insertion-position` : old method specifying where to insert new data.
|
||||||
|
- this setting still works but `data-association-insertion-method` takes precedence. may be removed in a future version.
|
||||||
|
|
||||||
Optionally you could also leave out the name and supply a block that is captured to give the name (if you want to do something more complicated).
|
Optionally you could also leave out the name and supply a block that is captured to give the name (if you want to do something more complicated).
|
||||||
|
|
||||||
|
@ -153,55 +175,61 @@ Inside the `html_options` you can add an option `:render_options`, and the conta
|
||||||
form. E.g. especially when using `twitter-bootstrap` and `simple_form` together, the `simple_fields_for` needs the option `:wrapper => 'inline'` which can
|
form. E.g. especially when using `twitter-bootstrap` and `simple_form` together, the `simple_fields_for` needs the option `:wrapper => 'inline'` which can
|
||||||
be handed down as follows:
|
be handed down as follows:
|
||||||
|
|
||||||
= link_to_add_association 'add something', f, :something, :render_options => {:wrapper => 'inline' }
|
````haml
|
||||||
|
= link_to_add_association 'add something', f, :something, :render_options => {:wrapper => 'inline' }
|
||||||
|
````
|
||||||
|
|
||||||
### Callbacks (upon insert and remove of items)
|
### Callbacks (upon insert and remove of items)
|
||||||
|
|
||||||
There is an option to add a callback on insertion or removal. If in your view you have the following snippet to select an `onwer`
|
There is an option to add a callback on insertion or removal. If in your view you have the following snippet to select an `owner`
|
||||||
(we use slim for demonstration purposes)
|
(we use slim for demonstration purposes)
|
||||||
|
|
||||||
#owner
|
````haml
|
||||||
#owner_from_list
|
#owner
|
||||||
= f.association :owner, :collection => Person.all(:order => 'name'), :prompt => 'Choose an existing owner'
|
#owner_from_list
|
||||||
= link_to_add_association 'add a new person as owner', f, :owner
|
= f.association :owner, :collection => Person.all(:order => 'name'), :prompt => 'Choose an existing owner'
|
||||||
|
= link_to_add_association 'add a new person as owner', f, :owner
|
||||||
|
````
|
||||||
|
|
||||||
This view part will either let you select an owner from the list of persons, or show the fields to add a new person as owner.
|
This view part will either let you select an owner from the list of persons, or show the fields to add a new person as owner.
|
||||||
|
|
||||||
|
|
||||||
The callbacks can be added as follows:
|
The callbacks can be added as follows:
|
||||||
|
|
||||||
$(document).ready(function() {
|
````javascript
|
||||||
$('#owner').bind('insertion-callback',
|
$(document).ready(function() {
|
||||||
function() {
|
$('#owner').bind('insertion-callback',
|
||||||
$("#owner_from_list").hide();
|
function() {
|
||||||
$("#owner a.add_fields").hide();
|
$("#owner_from_list").hide();
|
||||||
});
|
$("#owner a.add_fields").hide();
|
||||||
$('#owner').bind("removal-callback",
|
});
|
||||||
function() {
|
$('#owner').bind("removal-callback",
|
||||||
$("#owner_from_list").show();
|
function() {
|
||||||
$("#owner a.add_fields").show();
|
$("#owner_from_list").show();
|
||||||
});
|
$("#owner a.add_fields").show();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
````
|
||||||
|
|
||||||
Do note that for the callbacks to work there has to be a surrounding container (div), where you can bind the callbacks to.
|
Do note that for the callbacks to work there has to be a surrounding container (div), where you can bind the callbacks to.
|
||||||
|
|
||||||
### Control the Insertion behaviour
|
### Control the Insertion behaviour
|
||||||
|
|
||||||
The default insertion location is at the back of the current container. But we have added two `data`-attributes that are read to determine the insertion-node and -location.
|
The default insertion location is at the back of the current container. But we have added two `data`-attributes that are read to determine the insertion-node and -method.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
$(document).ready(function() {
|
````javascript
|
||||||
$("#owner a.add_fields").
|
$(document).ready(function() {
|
||||||
data("association-insertion-position", 'before').
|
$("#owner a.add_fields").
|
||||||
data("association-insertion-node", 'this');
|
data("association-insertion-method", 'before').
|
||||||
});
|
data("association-insertion-node", 'this');
|
||||||
|
});
|
||||||
|
````
|
||||||
|
|
||||||
The `association-insertion-node` will determine where to add it. You can choose any selector here, or specify this (default it is the parent-container).
|
The `association-insertion-node` will determine where to add it. You can choose any selector here, or specify this (default it is the parent-container).
|
||||||
|
|
||||||
The `association-insertion-position` will determine where to add it in relation with the node. Only two options: `before` or `after`.
|
The `association-insertion-method` will determine where to add it in relation with the node. Any jQuery DOM Manipulation method can be set but we recommend sticking to any of the following: `before`, `after`, `append`, `prepend`. It is unknown at this time what others would do.
|
||||||
|
|
||||||
|
|
||||||
### Partial
|
### Partial
|
||||||
|
@ -224,7 +252,7 @@ There is no limit to the amount of nesting, though.
|
||||||
|
|
||||||
## Todo
|
## Todo
|
||||||
|
|
||||||
* add more sample relations: has_many :through, belongs_to, ...
|
* add more sample relations: `has_many :through`, `belongs_to`, ...
|
||||||
* improve the tests (test the javascript too)(if anybody wants to lend a hand ...?)
|
* improve the tests (test the javascript too)(if anybody wants to lend a hand ...?)
|
||||||
|
|
||||||
## Copyright
|
## Copyright
|
||||||
|
|
|
@ -11,56 +11,58 @@
|
||||||
|
|
||||||
$('.add_fields').live('click', function(e) {
|
$('.add_fields').live('click', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var assoc = $(this).data('association'),
|
var $this = $(this),
|
||||||
assocs = $(this).data('associations'),
|
assoc = $this.data('association'),
|
||||||
content = $(this).data('template'),
|
assocs = $this.data('associations'),
|
||||||
insertionPosition = $(this).data('association-insertion-position'),
|
content = $this.data('template'),
|
||||||
insertionNode = $(this).data('association-insertion-node'),
|
insertionMethod = $this.data('association-insertion-method') || $this.data('association-insertion-position') || 'before';
|
||||||
insertionCallback = $(this).data('insertion-callback'),
|
insertionNode = $this.data('association-insertion-node'),
|
||||||
removalCallback = $(this).data('removal-callback'),
|
insertionCallback = $this.data('insertion-callback'),
|
||||||
regexp_braced = new RegExp('\\[new_' + assoc + '\\]', 'g'),
|
removalCallback = $this.data('removal-callback'),
|
||||||
regexp_underscord = new RegExp('_new_' + assoc + '_', 'g'),
|
regexp_braced = new RegExp('\\[new_' + assoc + '\\]', 'g'),
|
||||||
new_id = new Date().getTime(),
|
regexp_underscord = new RegExp('_new_' + assoc + '_', 'g'),
|
||||||
newcontent_braced = '[' + new_id + ']',
|
new_id = new Date().getTime(),
|
||||||
|
newcontent_braced = '[' + new_id + ']',
|
||||||
newcontent_underscord = '_' + new_id + '_',
|
newcontent_underscord = '_' + new_id + '_',
|
||||||
new_content = content.replace(regexp_braced, '[' + new_id + ']');
|
new_content = content.replace(regexp_braced, '[' + new_id + ']');
|
||||||
|
|
||||||
if (new_content == content) {
|
if (new_content == content) {
|
||||||
regexp_braced = new RegExp('\\[new_' + assocs + '\\]', 'g');
|
regexp_braced = new RegExp('\\[new_' + assocs + '\\]', 'g');
|
||||||
regexp_underscord = new RegExp('_new_' + assocs + '_', 'g');
|
regexp_underscord = new RegExp('_new_' + assocs + '_', 'g');
|
||||||
new_content = content.replace(regexp_braced, '[' + new_id + ']');
|
new_content = content.replace(regexp_braced, '[' + new_id + ']');
|
||||||
}
|
}
|
||||||
|
|
||||||
new_content = new_content.replace(regexp_underscord, newcontent_underscord);
|
new_content = new_content.replace(regexp_underscord, newcontent_underscord);
|
||||||
|
|
||||||
if (insertionNode){
|
if (insertionNode){
|
||||||
insertionNode = insertionNode == "this" ? $(this) : $(insertionNode);
|
insertionNode = insertionNode == "this" ? $this : $(insertionNode);
|
||||||
} else {
|
} else {
|
||||||
insertionNode = $(this).parent();
|
insertionNode = $this.parent();
|
||||||
}
|
}
|
||||||
|
|
||||||
var contentNode = $(new_content);
|
var contentNode = $(new_content);
|
||||||
|
|
||||||
if (insertionPosition == 'after'){
|
// allow any of the jquery dom manipulation methods (after, before, append, prepend, etc)
|
||||||
insertionNode.after(contentNode);
|
// to be called on the node. allows the insertion node to be the parent of the inserted
|
||||||
} else {
|
// code and doesn't force it to be a sibling like after/before does. default: 'before'
|
||||||
insertionNode.before(contentNode);
|
insertionNode[insertionMethod](contentNode);
|
||||||
}
|
|
||||||
|
|
||||||
$(this).parent().trigger('insertion-callback');
|
$this.parent().trigger('insertion-callback');
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.remove_fields.dynamic').live('click', function(e) {
|
$('.remove_fields.dynamic').live('click', function(e) {
|
||||||
trigger_removal_callback($(this));
|
var $this = $(this);
|
||||||
|
trigger_removal_callback($this);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
$(this).closest(".nested-fields").remove();
|
$this.closest(".nested-fields").remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.remove_fields.existing').live('click', function(e) {
|
$('.remove_fields.existing').live('click', function(e) {
|
||||||
trigger_removal_callback($(this));
|
var $this = $(this);
|
||||||
|
trigger_removal_callback($this);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
$(this).prev("input[type=hidden]").val("1");
|
$this.prev("input[type=hidden]").val("1");
|
||||||
$(this).closest(".nested-fields").hide();
|
$this.closest(".nested-fields").hide();
|
||||||
});
|
});
|
||||||
|
|
||||||
})(jQuery);
|
})(jQuery);
|
Loading…
Reference in New Issue