_updateFields js should start with first list item position of 1, not 0 #1

Open
aepstein wants to merge 16 commits from aepstein/patch-1 into master
2 changed files with 101 additions and 23 deletions
Showing only changes of commit 53074502b1 - Show all commits

View File

@ -272,6 +272,7 @@ It takes four parameters:
- `partial`: explicitly declare the name of the partial that will be used - `partial`: explicitly declare the name of the partial that will be used
- `render_options` : options passed through to the form-builder function (e.g. `simple_fields_for`, `semantic_fields_for` or `fields_for`). - `render_options` : options passed through to the form-builder function (e.g. `simple_fields_for`, `semantic_fields_for` or `fields_for`).
If it contains a `:locals` option containing a hash, that is handed to the partial. If it contains a `:locals` option containing a hash, that is handed to the partial.
- `wrap_object` : a proc that will allow to wrap your object, especially useful if you are using decorators (e.g. draper). See example lower.
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).
@ -301,6 +302,49 @@ To overrule the default partial name, e.g. because it shared between multiple vi
= link_to_add_association 'add something', f, :something, :partial => 'shared/something_fields' = link_to_add_association 'add something', f, :something, :partial => 'shared/something_fields'
```` ````
#### :wrap_object
If you are using decorators, the normal instantiation of the associated will not be enough, actually you want to generate the decorated object.
A simple decorator would look like:
```
class CommentDecorator
def initialize(comment)
@comment = comment
end
def formatted_created_at
@comment.created_at.to_formatted_s(:short)
end
def method_missing(method_sym, *args)
if @comment.respond_to?(method_sym)
@comment.send(method_sym, *args)
else
super
end
end
end
```
To use this, write
```
link_to_add_association('add something', @form_obj, :comments, :wrap_object => Proc.new {|comment| CommentDecorator.new(comment) })
```
Note that the `:wrap_object` expects an object that is _callable_, so any `Proc` will do. So you could as well use it to do some fancy extra initialisation (if needed).
But note you will have to return the (nested) object you want used.
E.g.
```
link_to_add_association('add something', @form_obj, :comments,
:wrap_object => Proc.new { |comment| comment.name = current_user.name; comment })
```
> A cleaner option would be to call a function that performs this initialisation and returns `self` at the end.
### link_to_remove_association ### link_to_remove_association
@ -325,6 +369,16 @@ On insertion or removal the following events are triggered:
* `cocoon:before-remove`: called before removing the nested child * `cocoon:before-remove`: called before removing the nested child
* `cocoon:after-remove`: called after removal * `cocoon:after-remove`: called after removal
To listen to the events, you to have the following code in your javascript:
$('#container').bind('cocoon:before-insert', function(e, inserted_item) {
// ... do something
});
where `e` is the event and the second parameter is the inserted or removed item. This allows you to change markup, or
add effects/animations (see example below).
If in your view you have the following snippet to select an `owner` 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)
@ -360,12 +414,38 @@ $(document).ready(function() {
function() { function() {
/* e.g. recalculate order of child items */ /* e.g. recalculate order of child items */
}); });
// example showing manipulating the inserted/removed item
$('#tasks').bind('cocoon:before-insert', function(e,task_to_be_added) {
task_to_be_added.fadeIn('slow');
});
$('#tasks').bind('cocoon:after-insert', function(e, added_task) {
// e.g. set the background of inserted task
added_task.css("background","red");
});
$('#tasks').bind('cocoon:before-remove', function(e, task) {
// allow some time for the animation to complete
$(this).data('remove-timeout', 1000);
task.fadeOut('slow');
})
}); });
```` ````
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.
When adding animations and effects to make the removal of items more interesting, you will also have to provide a timeout.
This is accomplished by the following line:
$(this).data('remove-timeout', 1000);
Note that you could also immediately add this to your view (on the `.nested-fields` container).
### 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 -method. 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.

View File

@ -7,13 +7,6 @@
content.replace(reg_exp, with_str); content.replace(reg_exp, with_str);
} }
function trigger_before_removal_callback(node) {
node.trigger('cocoon:before-remove');
}
function trigger_after_removal_callback(node) {
node.trigger('cocoon:after-remove');
}
$('.add_fields').live('click', function(e) { $('.add_fields').live('click', function(e) {
e.preventDefault(); e.preventDefault();
@ -56,28 +49,33 @@
// allow any of the jquery dom manipulation methods (after, before, append, prepend, etc) // allow any of the jquery dom manipulation methods (after, before, append, prepend, etc)
// to be called on the node. allows the insertion node to be the parent of the inserted // to be called on the node. allows the insertion node to be the parent of the inserted
// code and doesn't force it to be a sibling like after/before does. default: 'before' // code and doesn't force it to be a sibling like after/before does. default: 'before'
insertionNode[insertionMethod](contentNode); var addedContent = insertionNode[insertionMethod](contentNode);
insertionNode.trigger('cocoon:after-insert', contentNode); insertionNode.trigger('cocoon:after-insert', [contentNode]);
}); });
$('.remove_fields.dynamic').live('click', function(e) {
$('.remove_fields.dynamic, .remove_fields.existing').live('click', function(e) {
var $this = $(this); var $this = $(this);
var trigger_node = $this.closest(".nested-fields").parent(); var node_to_delete = $this.closest(".nested-fields");
trigger_before_removal_callback(trigger_node); var trigger_node = node_to_delete.parent();
e.preventDefault(); e.preventDefault();
trigger_node.trigger('cocoon:before-remove', [node_to_delete]);
var timeout = trigger_node.data('remove-timeout') || 0;
setTimeout(function() {
if ($this.hasClass('dynamic')) {
$this.closest(".nested-fields").remove(); $this.closest(".nested-fields").remove();
trigger_after_removal_callback(trigger_node); } else {
});
$('.remove_fields.existing').live('click', function(e) {
var $this = $(this);
var trigger_node = $this.closest(".nested-fields").parent();
trigger_before_removal_callback(trigger_node);
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();
trigger_after_removal_callback(trigger_node); }
trigger_node.trigger('cocoon:after-remove', [node_to_delete]);
}, timeout);
}); });
})(jQuery); })(jQuery);