aeonscope-rest 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,63 +1,146 @@
1
1
  module ResourceHelper
2
- # Builds a DOM ID for a given record. Works the same as the dom_id helper found in Rails except that it returns a record ID with
3
- # a "_0" suffix for new records instead of a "new_" prefix. This makes attaching JavaScript events easier since all DOM IDs are numbers.
2
+ # Renders a label for the current controller resource and action. Useful for labeling index, show, new, and edit views.
3
+ # Format: <action> <suffix || resource name>, Example: "New Post"
4
+ # *suffix* - Optional. The suffix to be applied to the action name. Defaults to the controller name (i.e. resource).
5
+ def render_action_label suffix = nil
6
+ [params[:action].capitalize, (suffix || controller_name.singularize.capitalize)] * ' '
7
+ end
8
+
9
+ # Answers whether the given action is equal to the current action.
10
+ def is_action? action
11
+ params[:action] == action.to_s
12
+ end
13
+
14
+ # Answers if the current action is an index action.
15
+ def index_action?
16
+ is_action? :index
17
+ end
18
+
19
+ # Answers if the current action is show action.
20
+ def show_action?
21
+ is_action? :show
22
+ end
23
+
24
+ # Answers if the current action is a new action.
25
+ def new_action?
26
+ is_action? :new
27
+ end
28
+
29
+ # Answers if the current action is a create action.
30
+ def create_action?
31
+ is_action? :create
32
+ end
33
+
34
+ # Answers if the current action is an edit action.
35
+ def edit_action?
36
+ is_action? :edit
37
+ end
38
+
39
+ # Answers if the current action is an update action.
40
+ def update_action?
41
+ is_action? :update
42
+ end
43
+
44
+ # Answers if the current action is a destroy action.
45
+ def destroy_action?
46
+ is_action? :destroy
47
+ end
48
+
49
+ # Toggles hiding a group of records if the given collection is empty. Use as a class attribute.
50
+ # * *collection* - The collection of records.
51
+ def toggle_group_class collection
52
+ "hidden" if collection.empty?
53
+ end
54
+
55
+ # Builds a DOM ID for a given record. Works the same as the Rails dom_id helper except that it returns a record ID with
56
+ # a "_0" suffix for new records instead of a "new_" prefix. This makes record IDs consistent and attaching JavaScript
57
+ # events easier.
4
58
  def build_dom_id record
5
59
  name = record.class.name.underscore
6
- record.new_record? ? name + "_0" : name + '_' + record.id.to_s
60
+ id = record.new_record? ? 0 : record.id
61
+ [name, id].compact * '_'
7
62
  end
8
63
 
9
- # Show a descriptive label based on the current controller action. Useful for new/edit actions. Accepts the following parameters:
10
- # * *label* - The action label.
11
- def show_action_label label
12
- [params[:action].capitalize, label].compact.join ' '
64
+ # Renders a descriptive label based on the current controller action. Useful for new/edit actions.
65
+ # * *suffix* - The action label.
66
+ def render_render_action_label suffix
67
+ [params[:action].capitalize, suffix].compact * ' '
13
68
  end
14
-
15
- # Shows an unobtrusive jQuery link where the UJS event is attached to the link via the "destroy" class.
16
- # If JavaScript is disabled then the _show_ action will be called instead of the _destroy_ action. Accepts
17
- # the following hash arguments:
18
- # * *parent_id* - The ID of the parent element for which the link is a child of. Example: post_1. *NOTE:* The parent ID takes precidence over the link ID if defined.
19
- # * *id* - The link ID. Example: post_1_link. *NOTE:* The link ID _must_ include the parent ID.
20
- # * *label* - The link label. Defaults to "Delete".
21
- # * *url* - The destroy URL. Example: /posts/1. *NOTE:* The proper HTTP POST request will be handled by JavaScript.
22
- def show_destroy_link options = {}
23
- options.reverse_merge! :id => options[:parent_id].to_s + "_destroy"
24
- options.reverse_merge! :label => "Delete", :class => "destroy", :url => '#' + options[:id].to_s
25
- link_to options[:label], options[:url], :id => options[:id], :class => options[:class]
26
- end
27
-
28
- # Shows a nested, unobtrusive jQuery link where the UJS event is attached to the link via the "destroy-nested" class.
29
- # If JavaScript is disabled then the _show_ action will be called instead of the _destroy_ action. Accepts
30
- # the following hash arguments:
31
- # * *object* - The model object to destroy.
32
- # * *parent_id* - The ID of the parent element for which the link is a child of. Example: post_1. *NOTE:* The parent ID takes precidence over the link ID if defined.
33
- # * *id* - The link ID. Example: post_1_link. *NOTE:* The link ID _must_ include the parent ID.
34
- # * *label* - The link label. Defaults to "Delete".
35
- # * *url* - The destroy URL. Example: /posts/1. *NOTE:* The proper HTTP POST request will be handled by JavaScript.
36
- def show_destroy_nested_link options = {}
37
- unless options[:object].new_record?
38
- options.reverse_merge! :id => options[:parent_id].to_s + "_destroy-nested", :class => "destroy-nested"
39
- show_destroy_link options
40
- end
41
- end
42
69
 
43
- # Shows an unobtrusive jQuery link based on an array of resource hashes. See the show_destroy_link above
44
- # for further details. Accepts the following arguments:
45
- # * *resources* - The array of resource hashes.
46
- def show_destroy_resource_link resources, parent_dom_id
47
- show_destroy_link :parent_id => parent_dom_id, :url => build_resource_url(resources, :destroy)
70
+ # Renders a label or an icon.
71
+ # * *label* - Optional (if use_icon=true). The link label. Defaults to "Unknown".
72
+ # * *use_icon* - Optional. Boolean for using a text or icon link. Defaults to false.
73
+ # * *icon_path* - Optional. The icon path. Defaults to "#{THEME_ROOT}/images/icons/unknown.png".
74
+ # * *icon_alt* - Optional. The icon text. Defaults to "Unknown Icon".
75
+ # * *icon_size* - Optional. The icon dimensions. Example: "{width}x{height}". Defaults to nil.
76
+ def render_label_or_icon options = {}
77
+ options.delete_if {|key, value| value.blank?}
78
+ options.reverse_merge! :label => "Uknown", :icon_path => "#{THEME_ROOT}/images/icons/unknown.png", :icon_alt => "Unknown Icon"
79
+ options[:use_icon] ? image_tag(options[:icon_path], :alt => options[:icon_alt], :size => options[:icon_size]) : options[:label]
48
80
  end
49
81
 
50
- # Shows an unobtrusive jQuery link based on an array of resource hashes. See the show_destroy_link above
51
- # for further details. Accepts the following arguments:
52
- # * *resources* - The array of resource hashes.
53
- def show_destroy_nested_resource_link resources, parent_dom_id
54
- show_destroy_link(:id => parent_dom_id.to_s + "_destroy-nested", :class => "destroy-nested") unless resources.last[:record].new_record?
82
+ # Renders an UJS show link.
83
+ # * *label* - Optional. The link label. Defaults to "Show".
84
+ # * *id* - Optional. The link ID. Defaults to nil.
85
+ # * *class* - Optional. The link class. Defaults to "show".
86
+ # * *url* - Required. The link URL (example: /posts/1). Defaults to "#show".
87
+ # * *use_icon* - Optional. Boolean for using a text or icon link. Defaults to false.
88
+ # * *icon_path* - Optional. The icon path. Defaults to "#{THEME_ROOT}/images/icons/rest/show.png".
89
+ # * *icon_alt* - Optional. The icon text. Defaults to "Show Icon".
90
+ # * *icon_size* - Optional. The icon dimensions. Example: "{width}x{height}". Defaults to nil.
91
+ def render_show_link options = {}
92
+ options.delete_if {|key, value| value.blank?}
93
+ options.reverse_merge! :label => "Show", :icon_path => "#{THEME_ROOT}/images/icons/rest/show.png", :icon_alt => "Show Icon", :class => "show", :url => "#show"
94
+ link_to render_label_or_icon(options), options[:url], :id => options[:id], :class => options[:class]
95
+ end
96
+
97
+ # Renders an UJS new link.
98
+ # * *label* - Optional. The link label. Defaults to "New".
99
+ # * *id* - Optional. The link ID. Defaults to nil.
100
+ # * *class* - Optional. The link class. Defaults to "new".
101
+ # * *type* - Optional. Determines the type of object be created. Specifiy "input" as the type for form additions. Defaults to nil.
102
+ # * *ogid* = Optional (if type=nil). The outer group ID. Defaults to nil.
103
+ # * *igid* = Optional (if type=nil). The inner group ID. Defaults to nil.
104
+ # * *position* = Optional (if type=nil). A number position of the ID to be modified. Useful when dealing with complex nested forms. Defaults to 0.
105
+ # * *url* - Optional (if type="input"). The link URL (example: /posts/new). Defaults to "#new".
106
+ # * *use_icon* - Optional. Boolean for using a text or icon link. Defaults to false.
107
+ # * *icon_path* - Optional. The icon path. Defaults to "#{THEME_ROOT}/images/icons/rest/new.png".
108
+ # * *icon_alt* - Optional. The icon text. Defaults to "New Icon".
109
+ # * *icon_size* - Optional. The icon dimensions. Example: "{width}x{height}". Defaults to nil.
110
+ def render_new_link options = {}
111
+ options.delete_if {|key, value| value.blank?}
112
+ options.reverse_merge! :label => "New", :icon_path => "#{THEME_ROOT}/images/icons/rest/new.png", :icon_alt => "New Icon", :class => "new", :url => "#new", "data-position" => 0
113
+ link_to render_label_or_icon(options), options[:url], :id => options[:id], :class => options[:class], "data-type" => options[:type], "data-ogid" => options[:ogid], "data-igid" => options[:igid], "data-position" => options[:position]
55
114
  end
56
115
 
57
- # Shows edit and delete links for resources. Accepts the following parameters:
58
- # * *resources* - The array of resource hashes.
59
- # * *parent_dom_id* - The parent ID of the dom object for which the edit and destroy links belong to for UJS manipulation.
60
- def show_edit_and_destroy_resource_links resources, parent_dom_id
61
- [link_to("Edit", build_resource_url(resources, :edit)), show_destroy_resource_link(resources, parent_dom_id)].join(" | ")
116
+ # Renders an UJS edit link.
117
+ # * *label* - Optional. The link label. Defaults to "Edit".
118
+ # * *id* - Optional. The link ID. Defaults to nil.
119
+ # * *class* - Optional. The link class. Defaults to "edit".
120
+ # * *url* - Required. The link URL (example: /posts/1/edit). Defaults to "#edit".
121
+ # * *use_icon* - Optional. Boolean for using a text or icon link. Defaults to false.
122
+ # * *icon_path* - Optional. The icon path. Defaults to "#{THEME_ROOT}/images/icons/rest/edit.png".
123
+ # * *icon_alt* - Optional. The icon text. Defaults to "Edit Icon".
124
+ # * *icon_size* - Optional. The icon dimensions. Example: "{width}x{height}". Defaults to nil.
125
+ def render_edit_link options = {}
126
+ options.delete_if {|key, value| value.blank?}
127
+ options.reverse_merge! :label => "Edit", :icon_path => "#{THEME_ROOT}/images/icons/rest/edit.png", :icon_alt => "Edit Icon", :class => "edit", :url => "#edit"
128
+ link_to render_label_or_icon(options), options[:url], :id => options[:id], :class => options[:class]
129
+ end
130
+
131
+ # Renders an UJS destroy link.
132
+ # * *label* - Optional. The link label. Defaults to "Delete".
133
+ # * *id* - Optional. The link ID. Defaults to nil.
134
+ # * *class* - Optional. The link class. Defaults to "destroy".
135
+ # * *type* - Optional. Determines the type of object be deleted. Specifiy "input" as the type for form deletions. Defaults to nil.
136
+ # * *url* - Required. The link URL (example: /posts/1). Defaults to "#destroy".
137
+ # * *use_icon* - Optional. Boolean for using a text or icon link. Defaults to false.
138
+ # * *icon_path* - Optional. The icon path. Defaults to "#{THEME_ROOT}/images/icons/rest/destroy.png".
139
+ # * *icon_alt* - Optional. The icon text. Defaults to "Delete Icon".
140
+ # * *icon_size* - Optional. The icon dimensions. Example: "{width}x{height}". Defaults to nil.
141
+ def render_destroy_link options = {}
142
+ options.delete_if {|key, value| value.blank?}
143
+ options.reverse_merge! :label => "Delete", :icon_path => "#{THEME_ROOT}/images/icons/rest/destroy.png", :icon_alt => "Delete Icon", :class => "destroy", :url => "#destroy"
144
+ link_to render_label_or_icon(options), options[:url], :id => options[:id], :class => options[:class], "data-type" => options[:type]
62
145
  end
63
146
  end
@@ -1,5 +1,5 @@
1
1
  require File.join(File.dirname(__FILE__), "class_methods.rb")
2
- require File.join(File.dirname(__FILE__), "actions.rb")
2
+ require File.join(File.dirname(__FILE__), "instance_methods.rb")
3
3
  require File.join(File.dirname(__FILE__), "resource_helper.rb")
4
4
 
5
5
  module Rest
@@ -8,5 +8,5 @@ module Rest
8
8
  base.helper "resource"
9
9
  base.helper_method :build_resource_url
10
10
  end
11
- include Actions
11
+ include InstanceMethods
12
12
  end
@@ -1,4 +1,4 @@
1
- Berserk Technologies UJS Setup Generator
1
+ Berserk Technologies REST Setup Generator
2
2
 
3
3
  Description:
4
4
  Applies unobtrusive jQuery support to your Ruby on Rails application.
@@ -8,4 +8,4 @@ Requirements
8
8
  2. jQuery UI 1.7 or higher.
9
9
 
10
10
  Usage:
11
- script/generate ujs_setup
11
+ script/generate rest_setup
@@ -0,0 +1,28 @@
1
+ class RestSetupGenerator < Rails::Generator::Base
2
+ def manifest
3
+ record do |m|
4
+ # Configuration
5
+ m.file "config/initializers/pagination.rb", "config/initializers/pagination.rb"
6
+
7
+ # Controllers
8
+ m.file "app/controllers/javascripts_controller.rb", "app/controllers/javascripts_controller.rb"
9
+
10
+ # Views
11
+ m.directory "app/views/javascripts"
12
+ m.file "app/views/javascripts/ujs.js.erb", "app/views/javascripts/ujs.js.erb"
13
+
14
+ # JavaScript
15
+ m.file "public/javascripts/jquery.rest.js", "public/javascripts/jquery.rest.js"
16
+
17
+ # Views
18
+ m.directory "public/themes/default/images/icons/rest"
19
+ m.file "public/themes/default/images/icons/rest/show.png", "public/themes/default/images/icons/rest/show.png"
20
+ m.file "public/themes/default/images/icons/rest/new.png", "public/themes/default/images/icons/rest/new.png"
21
+ m.file "public/themes/default/images/icons/rest/edit.png", "public/themes/default/images/icons/rest/edit.png"
22
+ m.file "public/themes/default/images/icons/rest/destroy.png", "public/themes/default/images/icons/rest/destroy.png"
23
+
24
+ # Instructions
25
+ m.readme "README"
26
+ end
27
+ end
28
+ end
@@ -4,8 +4,8 @@ Tasks You Need to Complete:
4
4
 
5
5
  <%= javascript_include_tag "jquery" %>
6
6
  <%= javascript_include_tag "jquery-ui" %>
7
+ <%= javascript_include_tag "jquery.rest" %>
7
8
  <%= javascript_include_tag "ujs" %>
8
- <%= javascript_include_tag "rest" %>
9
9
 
10
10
  2. Add the following to the very bottom of your config/routes.rb file:
11
11
 
@@ -11,8 +11,8 @@ $.ajaxSetup({
11
11
  */
12
12
  $(document).ajaxSend(function(event, request, settings){
13
13
  var authToken = "<%= form_authenticity_token %>";
14
- if (authToken != null){
15
- settings.data = settings.data || "";
16
- settings.data += (settings.data ? "&" : "") + "authenticity_token=" + encodeURIComponent(authToken);
14
+ if (authToken != null) {
15
+ settings.data = settings.data || '';
16
+ settings.data += (settings.data ? "&" : '') + "authenticity_token=" + encodeURIComponent(authToken);
17
17
  };
18
18
  });
@@ -0,0 +1,10 @@
1
+ # Default options for the Will Paginate gem: http://github.com/mislav/will_paginate
2
+ require "will_paginate"
3
+ WillPaginate::ViewHelpers.pagination_options[:previous_label] = '<<'
4
+ WillPaginate::ViewHelpers.pagination_options[:next_label] = '>>'
5
+ WillPaginate::ViewHelpers.pagination_options[:inner_window] = 2
6
+ WillPaginate::ViewHelpers.pagination_options[:container] = false
7
+
8
+ # Default options for the REST gem: http://github.com/aeonscope/rest
9
+ PAGINATION_PARAM_NAME = :page
10
+ PAGINATION_PAGE_COUNT = 15
@@ -0,0 +1,325 @@
1
+ (function($) {
2
+ // Global plugin settings.
3
+ var settings = null;
4
+
5
+ /* Compares the first number found in a pair of strings in order to determine sort order.
6
+ * @a - The first number.
7
+ * @b - The second number.
8
+ */
9
+ function compareFirstNumber(a, b) {
10
+ a = new Number(a.match(/\d+/));
11
+ b = new Number(b.match(/\d+/));
12
+ if (a < b) { return -1; }
13
+ if (a > b) { return 1; }
14
+ return 0;
15
+ }
16
+
17
+ /* Chops off the last string segment designated by delimiter.
18
+ * If no delimiter is found then the original string is returned instead.
19
+ * @string - Required. The string to chop.
20
+ * @delimiter - Optional. The delimiter used to chop up the string. Defaults to '_'.
21
+ */
22
+ function stringChop(string, delimiter) {
23
+ var chopped = string;
24
+ if (delimiter == undefined) { delimiter = '_'; }
25
+ var endIndex = string.lastIndexOf(delimiter);
26
+ if (endIndex > 1) {chopped = string.slice(0, endIndex);}
27
+ return chopped;
28
+ }
29
+
30
+ /* Answers the ID found in the string based off of a certain delimiter.
31
+ * @string - Required. The string to obtain the ID from.
32
+ * @delimiter - Optional. The delimiter used to distinquish the ID from the string. Defaults to: '_'.
33
+ */
34
+ function getId(string, delimiter) {
35
+ var id = string;
36
+ if (delimiter == undefined) { delimiter = '_'; }
37
+ var endIndex = string.lastIndexOf(delimiter) + 1;
38
+ if (endIndex < string.length) {id = string.slice(endIndex);}
39
+ return id;
40
+ }
41
+
42
+ /* Replaces an existing number within a string with a new number based on position in the string (either first or last postion).
43
+ * @string - The string for which to replace an old number with a new number.
44
+ * @newNumber - The new number to be replaced in the string.
45
+ * @position - The position of the old number in the string to be replaced.
46
+ */
47
+ function replaceNumber(string, newNumber, position) {
48
+ if (string != undefined) {
49
+ if (position == undefined) { position = 0; }
50
+ var numbers = string.match(/\d+/g);
51
+ if (numbers != null && numbers.length > 1) {
52
+ var oldNumber;
53
+ var index;
54
+ switch(position) {
55
+ case 0:
56
+ oldNumber = numbers[0];
57
+ index = string.indexOf(oldNumber);
58
+ break;
59
+ case 1:
60
+ oldNumber = numbers.reverse()[0];
61
+ index = string.lastIndexOf(oldNumber);
62
+ break;
63
+ }
64
+ var prefix = string.substring(0, index);
65
+ var suffix = string.substring(index + oldNumber.length, string.length);
66
+ string = prefix + newNumber + suffix;
67
+ }
68
+ }
69
+ return string;
70
+ }
71
+
72
+ /* Increments the first or last number in a string (if any, defaults to "first").
73
+ * If no number is found then the original string is returned.
74
+ * @string - The string to manipulate.
75
+ * @position - The position of the number (first or last) to increment.
76
+ */
77
+ function incrementNumber(string, position) {
78
+ if (string != undefined) {
79
+ if (position == undefined) { position = "first"; }
80
+ var numbers = string.match(/\d+/g);
81
+ if (numbers != null) {
82
+ var oldNumber;
83
+ var newNumber;
84
+ var index;
85
+ switch(position) {
86
+ case "first":
87
+ oldNumber = numbers[0];
88
+ newNumber = new Number(oldNumber) + 1;
89
+ index = string.indexOf(oldNumber);
90
+ break;
91
+ case "last":
92
+ oldNumber = numbers.reverse()[0];
93
+ newNumber = new Number(oldNumber) + 1;
94
+ index = string.lastIndexOf(oldNumber);
95
+ break;
96
+ }
97
+ var prefix = string.substring(0, index);
98
+ var suffix = string.substring(index + oldNumber.length, string.length);
99
+ string = prefix + newNumber + suffix;
100
+ }
101
+ }
102
+ return string;
103
+ }
104
+
105
+ /* Builds an array of all body element attributes (if not already set).
106
+ * @attribute - The attribute to find.
107
+ */
108
+ function buildBodyAttributes(attribute) {
109
+ bodyAttributes = $.map($("body").find('[' + attribute + ']'), function(element) {
110
+ return $(element).attr(attribute);
111
+ });
112
+ return bodyAttributes;
113
+ }
114
+
115
+ /* Makes an array of attributes unique by comparing each attribute in the array against all body element attributes.
116
+ * @attributes - The attributes to make unique.
117
+ * @attribute - The attribute to search for.
118
+ * @index - The starting index in the arravy of attributes.
119
+ */
120
+ function uniquifyAttributes(attributes, attribute, index) {
121
+ if (index < attributes.length) {
122
+ var numbers = attributes[index].match(/\d+/g);
123
+ var strings = attributes[index].split(/\d+/);
124
+ if (numbers != null && numbers.length == 1) {
125
+ var constant = attributes[index].split(/\d+/).join('-');
126
+ var familiars = [];
127
+ var bodyAttributes = buildBodyAttributes(attribute);
128
+ for (x in bodyAttributes) {
129
+ var current = bodyAttributes[x].split(/\d+/).join('-');
130
+ if (current == constant) {
131
+ familiars.push(bodyAttributes[x]);
132
+ }
133
+ }
134
+ var last = incrementNumber(familiars.sort(compareFirstNumber).pop());
135
+ attributes.splice(index, 1, last);
136
+ bodyAttributes.push(last);
137
+ }
138
+ index++;
139
+ uniquifyAttributes(attributes, attribute, index);
140
+ }
141
+ bodyAttributes = null;
142
+ return attributes;
143
+ }
144
+
145
+ /* Ensures all child element attibutes are unique for a given root element.
146
+ * @element - TODO...
147
+ * @attribute - TODO...
148
+ * @selector - TODO...
149
+ */
150
+ function uniquifyChildren(element, attribute, selector) {
151
+ // Acquire the child elements.
152
+ var children = $(element).find(selector);
153
+ var attributes = [];
154
+ // Extract the attributes from the child elements.
155
+ children.each(function() {
156
+ attributes.push($(this).attr(attribute));
157
+ });
158
+ // Make the attributes unique.
159
+ attributes = uniquifyAttributes(attributes, attribute, 0);
160
+ // Update the child elements with the unique attributes.
161
+ for (x in attributes) {
162
+ $(children[x]).attr(attribute, attributes[x]);
163
+ }
164
+ }
165
+
166
+ /* Generates a hidden input field based off original input field data that instructs ActiveRecord to delete
167
+ * a record based on the ID of the input field.
168
+ * @input - The input element from which to build the deletion input element.
169
+ */
170
+ function generateDestroyInput(input) {
171
+ if (input == undefined || $(input).length) {
172
+ $(input).attr("id", stringChop($(input).attr("id")) + "__delete");
173
+ $(input).attr("name", stringChop($(input).attr("name"), '[') + "[_delete]");
174
+ $(input).val(1);
175
+ return input;
176
+ } else {
177
+ return null;
178
+ }
179
+ }
180
+
181
+ /* Animates the deletion of a DOM element. Defaults to simply fading and hiding the element from view but
182
+ * can be forced to remove the element from the DOM completely.
183
+ * @element - The element to destroy.
184
+ * @remove - Boolean for whether to remove or just hide the element.
185
+ */
186
+ function animateDestroy(element, remove) {
187
+ if (remove == undefined) { remove = false; }
188
+ $(element).fadeOut(500, function() {
189
+ if (remove) { $(this).remove(); }
190
+ });
191
+ }
192
+
193
+ /* Executes the new action.
194
+ * @element - The element that spawned the new action.
195
+ */
196
+ function newInputAction(element) {
197
+ // Outer group ID
198
+ var ogid = '#' + $(element).attr("data-ogid");
199
+ // Inner group ID
200
+ var igid = '#' + $(element).attr("data-igid");
201
+ // Position
202
+ var position = $(element).attr("data-position");
203
+
204
+ if ($(ogid).hasClass("hidden")) {
205
+ $(ogid).removeAttr("style").fadeIn(500, function() {
206
+ $(this).removeClass("hidden");
207
+ });
208
+ // Delete hidden metadata (if any).
209
+ $(settings.recordSelector + ":visible input:hidden[id$=__delete]").remove();
210
+ } else {
211
+ var count = $(igid).children(settings.recordSelector + ":visible").size();
212
+ var record = $(igid).children(settings.recordSelector + ":visible:last").clone(true);
213
+ record.attr("id", uniquifyAttributes([record.attr("id")], "id", 0).toString());
214
+ $(igid).append(record);
215
+ // Remove excess cloned records.
216
+ var records = $.makeArray($(record).find(settings.recordSelector));
217
+ if (records.length > 1) {
218
+ records.shift();
219
+ for (x in records) {
220
+ $(records[x]).remove();
221
+ }
222
+ }
223
+ // Ensure the cloned children are unique.
224
+ uniquifyChildren(record, "id", "[id]:not([id*=attributes])");
225
+ uniquifyChildren(record, "for", "[for]");
226
+ uniquifyChildren(record, "data-ogid", "[data-ogid]");
227
+ uniquifyChildren(record, "data-igid", "[data-igid]");
228
+
229
+ // Increment the cloned, nested children.
230
+ $(record).find("[id*=attributes]").each(function() {
231
+ if (position == 0) {
232
+ $(this).attr("id", replaceNumber($(this).attr("id"), count));
233
+ }
234
+ $(this).attr("id", incrementNumber($(this).attr("id"), "last"));
235
+ return this;
236
+ });
237
+ $(record).find("[name*=attributes]").each(function() {
238
+ if (position == 0) {
239
+ $(this).attr("name", replaceNumber($(this).attr("name"), count));
240
+ }
241
+ $(this).attr("name", incrementNumber($(this).attr("name"), "last"));
242
+ return this;
243
+ });
244
+
245
+ // Clear inputs.
246
+ $(record).find("input:visible").val('');
247
+ $(record).find("select:visible").val('');
248
+ // Delete hidden metadata.
249
+ $(record).find("input:hidden[id$=_id]").remove();
250
+ }
251
+
252
+ return false;
253
+ }
254
+
255
+ // Executes the destroy action.
256
+ function destroyAction(element, message) {
257
+ var result = confirm(message);
258
+ if (result) {
259
+ $.post($(element).attr("href"), "_method=delete");
260
+ animateDestroy($(element).closest(settings.recordSelector + ":visible"));
261
+ }
262
+ return false;
263
+ }
264
+
265
+ // Executes the destroy input action.
266
+ function destroyInputAction(element) {
267
+ var group = $(element).closest(settings.groupSelector);
268
+ var record = $(element).closest(settings.recordSelector);
269
+ // Create hidden deletion input from original identifier input so Rails knows to delete the record.
270
+ // NOTE: The following three lines are a workaround to a Safari and IE bug where the hidden input
271
+ // is not found using: $(record).prev("input");
272
+ var idName = stringChop($(record).attr("id"));
273
+ var idNumber = getId($(record).attr("id"));
274
+ var hiddenInput = $(group).find("input:hidden").filter("[name$=[id]][value=" + idNumber + ']');
275
+ // $("#rule_1_table").find("input:hidden").filter("[name$=[id]][value=6]");
276
+
277
+ if (hiddenInput.length > 0) {
278
+ $(record).prepend(generateDestroyInput($(hiddenInput).clone()));
279
+ }
280
+ // Remove the record from view or hide entire group if only one record left.
281
+ if ($(group).find(".record:visible").size() > 1) {
282
+ if (hiddenInput.length > 0) {
283
+ animateDestroy(record);
284
+ } else {
285
+ animateDestroy(record, true);
286
+ }
287
+ } else {
288
+ $(record).find("input:visible").val('');
289
+ $(record).find("select:visible").val('');
290
+ $(group).fadeOut(500, function() {
291
+ $(group).addClass("hidden");
292
+ });
293
+ }
294
+ return false;
295
+ }
296
+
297
+ // Plugin
298
+ $.rest = function(options) {
299
+ settings = $.extend(true, {}, $.rest.defaults, options);
300
+ // New
301
+ $(settings.newSelector).live("click", function() {
302
+ return $(this).attr("data-type") == "input" ? newInputAction(this) : true;
303
+ });
304
+ // Edit
305
+ $(settings.editSelector).live("click", function() {
306
+ // TODO - Need to supply confirm/worning dialog here.
307
+ return true;
308
+ });
309
+ // Destroy
310
+ $(settings.destroySelector).live("click", function(event) {
311
+ return $(event.target).attr("data-type") == "input" ? destroyInputAction(this) : destroyAction(this, settings.destroyConfirm);
312
+ });
313
+ return false;
314
+ };
315
+
316
+ // Plugin Public Defaults
317
+ $.rest.defaults = {
318
+ newSelector: "a.new",
319
+ editSelector: "a.edit",
320
+ destroySelector: "a.destroy",
321
+ destroyConfirm: "Are you sure you want to delete this?",
322
+ groupSelector: ".group",
323
+ recordSelector: ".record"
324
+ };
325
+ }) (jQuery);