From bbba9dc4c0353ec2c0990dec64c24e56bd6ffe9c Mon Sep 17 00:00:00 2001 From: nathanvda Date: Thu, 18 Oct 2012 07:28:29 +0200 Subject: [PATCH 1/3] Add the (to be) inserted or (to be) removed html to the event. The event gets an extra parameter: for `before-insert` and `after-insert` the inserted piece of html. For `before-remove` and `after-remove` the removed piece of html. This way one can add effects/animations if needed. The remove code has been refactored (less code duplication), and checks for a possible timeout, which can be set in the `before-remove` callback. --- app/assets/javascripts/cocoon.js | 46 +++++++++++++++----------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/app/assets/javascripts/cocoon.js b/app/assets/javascripts/cocoon.js index 2418e6f..b4ec934 100644 --- a/app/assets/javascripts/cocoon.js +++ b/app/assets/javascripts/cocoon.js @@ -7,13 +7,6 @@ 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) { e.preventDefault(); @@ -51,33 +44,38 @@ var contentNode = $(new_content); - insertionNode.trigger('cocoon:before-insert'); + insertionNode.trigger('cocoon:before-insert', [contentNode]); // 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 // 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'); + insertionNode.trigger('cocoon:after-insert', [contentNode]); }); - $('.remove_fields.dynamic').live('click', function(e) { - var $this = $(this); - var trigger_node = $this.closest(".nested-fields").parent(); - trigger_before_removal_callback(trigger_node); - e.preventDefault(); - $this.closest(".nested-fields").remove(); - trigger_after_removal_callback(trigger_node); - }); - $('.remove_fields.existing').live('click', function(e) { + $('.remove_fields.dynamic, .remove_fields.existing').live('click', function(e) { var $this = $(this); - var trigger_node = $this.closest(".nested-fields").parent(); - trigger_before_removal_callback(trigger_node); + var node_to_delete = $this.closest(".nested-fields"); + var trigger_node = node_to_delete.parent(); + e.preventDefault(); - $this.prev("input[type=hidden]").val("1"); - $this.closest(".nested-fields").hide(); - trigger_after_removal_callback(trigger_node); + + 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(); + } else { + $this.prev("input[type=hidden]").val("1"); + $this.closest(".nested-fields").hide(); + } + trigger_node.trigger('cocoon:after-remove', [node_to_delete]); + }, timeout); }); })(jQuery); From 2287dddcf9023b3a2cd091136eb33c5ec30c0f7b Mon Sep 17 00:00:00 2001 From: nathanvda Date: Fri, 19 Oct 2012 20:00:50 +0200 Subject: [PATCH 2/3] Updated README to document the `:wrap_object` option and the changed callback signature (allowing to add animations, effects, markup to the inserted items). --- README.markdown | 77 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/README.markdown b/README.markdown index d6c93f8..94eb97a 100644 --- a/README.markdown +++ b/README.markdown @@ -216,6 +216,7 @@ It takes four parameters: - `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`). 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). @@ -245,6 +246,46 @@ 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' ```` +#### :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). +E.g. + + +``` +link_to_add_association('add something', @form_obj, :comments, :wrap_object => Proc.new {|comment| comment.name = current_user.name }) +``` + ### link_to_remove_association @@ -269,6 +310,16 @@ On insertion or removal the following events are triggered: * `cocoon:before-remove`: called before removing the nested child * `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` (we use slim for demonstration purposes) @@ -304,12 +355,38 @@ $(document).ready(function() { function() { /* 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. +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 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. From 22cfa784d46ae178318dd14bb175300bd05b7e21 Mon Sep 17 00:00:00 2001 From: Nathan Van der Auwera Date: Thu, 25 Oct 2012 10:11:33 +0300 Subject: [PATCH 3/3] Clarified the :wrap_object documentation. It was not made clear enough that the callable needs to return the object. Fixes #103. --- README.markdown | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 94eb97a..3a798e6 100644 --- a/README.markdown +++ b/README.markdown @@ -279,13 +279,16 @@ link_to_add_association('add something', @form_obj, :comments, :wrap_object => P ``` 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 }) +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