aeonscope-rest 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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);