governor_comments 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/Gemfile +3 -1
  2. data/Gemfile.lock +11 -3
  3. data/README.rdoc +5 -2
  4. data/Rakefile +1 -0
  5. data/VERSION +1 -1
  6. data/app/controllers/governor/comments_controller.rb +2 -14
  7. data/app/views/governor/articles/_comments.html.erb +1 -11
  8. data/app/views/governor/articles/edit_comments.html.erb +43 -0
  9. data/app/views/governor/comments/_form.html.erb +11 -0
  10. data/app/views/governor/comments/edit.html.erb +4 -0
  11. data/config/locales/en.yml +8 -2
  12. data/governor_comments.gemspec +28 -14
  13. data/lib/generators/governor/add_assets_generator.rb +15 -0
  14. data/lib/generators/governor/create_comments_generator.rb +2 -0
  15. data/lib/generators/governor/templates/assets/javascripts/governor-comments.js +7 -0
  16. data/lib/generators/governor/templates/assets/stylesheets/governor-comments.css +18 -0
  17. data/lib/generators/governor/templates/migrations/create_comments.rb +1 -1
  18. data/lib/generators/governor/templates/models/comment.rb +1 -0
  19. data/lib/governor/controllers/methods.rb +18 -0
  20. data/lib/governor_comments/comment.rb +13 -5
  21. data/lib/governor_comments/rails.rb +3 -1
  22. data/lib/governor_comments.rb +12 -3
  23. data/spec/controllers/governor/articles_controller_spec.rb +48 -0
  24. data/spec/controllers/governor/comments_controller_spec.rb +1 -1
  25. data/spec/rails_app/Gemfile +1 -1
  26. data/spec/rails_app/Gemfile.lock +10 -5
  27. data/spec/rails_app/app/models/comment.rb +1 -0
  28. data/spec/rails_app/app/views/layouts/application.html.erb +3 -2
  29. data/spec/rails_app/config/application.rb +11 -1
  30. data/spec/rails_app/db/migrate/{20110405030324_governor_create_comments.rb → 20110501055541_governor_create_comments.rb} +1 -1
  31. data/spec/rails_app/db/schema.rb +2 -2
  32. data/spec/rails_app/public/javascripts/jquery.js +16 -0
  33. data/spec/rails_app/public/javascripts/rails.js +255 -157
  34. data/spec/rails_app/spec/factories.rb +1 -0
  35. data/spec/views/governor/articles/show.html.erb_spec.rb +8 -1
  36. data/spec/views/layouts/application.html.erb_spec.rb +18 -0
  37. data/{log/development.log → tmp/.gitkeep} +0 -0
  38. metadata +66 -30
  39. data/spec/rails_app/public/javascripts/controls.js +0 -965
  40. data/spec/rails_app/public/javascripts/dragdrop.js +0 -974
  41. data/spec/rails_app/public/javascripts/effects.js +0 -1123
  42. data/spec/rails_app/public/javascripts/prototype.js +0 -6001
@@ -1,191 +1,289 @@
1
- (function() {
2
- // Technique from Juriy Zaytsev
3
- // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
4
- function isEventSupported(eventName) {
5
- var el = document.createElement('div');
6
- eventName = 'on' + eventName;
7
- var isSupported = (eventName in el);
8
- if (!isSupported) {
9
- el.setAttribute(eventName, 'return;');
10
- isSupported = typeof el[eventName] == 'function';
11
- }
12
- el = null;
13
- return isSupported;
14
- }
1
+ /**
2
+ * Unobtrusive scripting adapter for jQuery
3
+ *
4
+ * Requires jQuery 1.4.4 or later.
5
+ * https://github.com/rails/jquery-ujs
15
6
 
16
- function isForm(element) {
17
- return Object.isElement(element) && element.nodeName.toUpperCase() == 'FORM'
18
- }
7
+ * Uploading file using rails.js
8
+ * =============================
9
+ *
10
+ * By default, browsers do not allow files to be uploaded via AJAX. As a result, if there are any non-blank file fields
11
+ * in the remote form, this adapter aborts the AJAX submission and allows the form to submit through standard means.
12
+ *
13
+ * The `ajax:aborted:file` event allows you to bind your own handler to process the form submission however you wish.
14
+ *
15
+ * Ex:
16
+ * $('form').live('ajax:aborted:file', function(event, elements){
17
+ * // Implement own remote file-transfer handler here for non-blank file inputs passed in `elements`.
18
+ * // Returning false in this handler tells rails.js to disallow standard form submission
19
+ * return false;
20
+ * });
21
+ *
22
+ * The `ajax:aborted:file` event is fired when a file-type input is detected with a non-blank value.
23
+ *
24
+ * Third-party tools can use this hook to detect when an AJAX file upload is attempted, and then use
25
+ * techniques like the iframe method to upload the file instead.
26
+ *
27
+ * Required fields in rails.js
28
+ * ===========================
29
+ *
30
+ * If any blank required inputs (required="required") are detected in the remote form, the whole form submission
31
+ * is canceled. Note that this is unlike file inputs, which still allow standard (non-AJAX) form submission.
32
+ *
33
+ * The `ajax:aborted:required` event allows you to bind your own handler to inform the user of blank required inputs.
34
+ *
35
+ * !! Note that Opera does not fire the form's submit event if there are blank required inputs, so this event may never
36
+ * get fired in Opera. This event is what causes other browsers to exhibit the same submit-aborting behavior.
37
+ *
38
+ * Ex:
39
+ * $('form').live('ajax:aborted:required', function(event, elements){
40
+ * // Returning false in this handler tells rails.js to submit the form anyway.
41
+ * // The blank required inputs are passed to this function in `elements`.
42
+ * return ! confirm("Would you like to submit the form with missing info?");
43
+ * });
44
+ */
19
45
 
20
- function isInput(element) {
21
- if (Object.isElement(element)) {
22
- var name = element.nodeName.toUpperCase()
23
- return name == 'INPUT' || name == 'SELECT' || name == 'TEXTAREA'
24
- }
25
- else return false
26
- }
46
+ (function($) {
47
+ // Shorthand to make it a little easier to call public rails functions from within rails.js
48
+ var rails;
27
49
 
28
- var submitBubbles = isEventSupported('submit'),
29
- changeBubbles = isEventSupported('change')
30
-
31
- if (!submitBubbles || !changeBubbles) {
32
- // augment the Event.Handler class to observe custom events when needed
33
- Event.Handler.prototype.initialize = Event.Handler.prototype.initialize.wrap(
34
- function(init, element, eventName, selector, callback) {
35
- init(element, eventName, selector, callback)
36
- // is the handler being attached to an element that doesn't support this event?
37
- if ( (!submitBubbles && this.eventName == 'submit' && !isForm(this.element)) ||
38
- (!changeBubbles && this.eventName == 'change' && !isInput(this.element)) ) {
39
- // "submit" => "emulated:submit"
40
- this.eventName = 'emulated:' + this.eventName
41
- }
42
- }
43
- )
44
- }
50
+ $.rails = rails = {
51
+ // Link elements bound by jquery-ujs
52
+ linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote]',
45
53
 
46
- if (!submitBubbles) {
47
- // discover forms on the page by observing focus events which always bubble
48
- document.on('focusin', 'form', function(focusEvent, form) {
49
- // special handler for the real "submit" event (one-time operation)
50
- if (!form.retrieve('emulated:submit')) {
51
- form.on('submit', function(submitEvent) {
52
- var emulated = form.fire('emulated:submit', submitEvent, true)
53
- // if custom event received preventDefault, cancel the real one too
54
- if (emulated.returnValue === false) submitEvent.preventDefault()
55
- })
56
- form.store('emulated:submit', true)
57
- }
58
- })
59
- }
54
+ // Form elements bound by jquery-ujs
55
+ formSubmitSelector: 'form',
56
+
57
+ // Form input elements bound by jquery-ujs
58
+ formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not([type])',
59
+
60
+ // Form input elements disabled during form submission
61
+ disableSelector: 'input[data-disable-with], button[data-disable-with], textarea[data-disable-with]',
62
+
63
+ // Form input elements re-enabled after form submission
64
+ enableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled',
60
65
 
61
- if (!changeBubbles) {
62
- // discover form inputs on the page
63
- document.on('focusin', 'input, select, texarea', function(focusEvent, input) {
64
- // special handler for real "change" events
65
- if (!input.retrieve('emulated:change')) {
66
- input.on('change', function(changeEvent) {
67
- input.fire('emulated:change', changeEvent, true)
68
- })
69
- input.store('emulated:change', true)
66
+ // Form required input elements
67
+ requiredInputSelector: 'input[name][required],textarea[name][required]',
68
+
69
+ // Form file input elements
70
+ fileInputSelector: 'input:file',
71
+
72
+ // Make sure that every Ajax request sends the CSRF token
73
+ CSRFProtection: function(xhr) {
74
+ var token = $('meta[name="csrf-token"]').attr('content');
75
+ if (token) xhr.setRequestHeader('X-CSRF-Token', token);
76
+ },
77
+
78
+ // Triggers an event on an element and returns false if the event result is false
79
+ fire: function(obj, name, data) {
80
+ var event = $.Event(name);
81
+ obj.trigger(event, data);
82
+ return event.result !== false;
83
+ },
84
+
85
+ // Submits "remote" forms and links with ajax
86
+ handleRemote: function(element) {
87
+ var method, url, data,
88
+ dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType);
89
+
90
+ if (rails.fire(element, 'ajax:before')) {
91
+
92
+ if (element.is('form')) {
93
+ method = element.attr('method');
94
+ url = element.attr('action');
95
+ data = element.serializeArray();
96
+ // memoized value from clicked submit button
97
+ var button = element.data('ujs:submit-button');
98
+ if (button) {
99
+ data.push(button);
100
+ element.data('ujs:submit-button', null);
101
+ }
102
+ } else {
103
+ method = element.data('method');
104
+ url = element.attr('href');
105
+ data = null;
106
+ }
107
+
108
+ $.ajax({
109
+ url: url, type: method || 'GET', data: data, dataType: dataType,
110
+ // stopping the "ajax:beforeSend" event will cancel the ajax request
111
+ beforeSend: function(xhr, settings) {
112
+ if (settings.dataType === undefined) {
113
+ xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script);
114
+ }
115
+ return rails.fire(element, 'ajax:beforeSend', [xhr, settings]);
116
+ },
117
+ success: function(data, status, xhr) {
118
+ element.trigger('ajax:success', [data, status, xhr]);
119
+ },
120
+ complete: function(xhr, status) {
121
+ element.trigger('ajax:complete', [xhr, status]);
122
+ },
123
+ error: function(xhr, status, error) {
124
+ element.trigger('ajax:error', [xhr, status, error]);
125
+ }
126
+ });
70
127
  }
71
- })
72
- }
128
+ },
73
129
 
74
- function handleRemote(element) {
75
- var method, url, params;
130
+ // Handles "data-method" on links such as:
131
+ // <a href="/users/5" data-method="delete" rel="nofollow" data-confirm="Are you sure?">Delete</a>
132
+ handleMethod: function(link) {
133
+ var href = link.attr('href'),
134
+ method = link.data('method'),
135
+ csrf_token = $('meta[name=csrf-token]').attr('content'),
136
+ csrf_param = $('meta[name=csrf-param]').attr('content'),
137
+ form = $('<form method="post" action="' + href + '"></form>'),
138
+ metadata_input = '<input name="_method" value="' + method + '" type="hidden" />';
76
139
 
77
- var event = element.fire("ajax:before");
78
- if (event.stopped) return false;
140
+ if (csrf_param !== undefined && csrf_token !== undefined) {
141
+ metadata_input += '<input name="' + csrf_param + '" value="' + csrf_token + '" type="hidden" />';
142
+ }
79
143
 
80
- if (element.tagName.toLowerCase() === 'form') {
81
- method = element.readAttribute('method') || 'post';
82
- url = element.readAttribute('action');
83
- params = element.serialize();
84
- } else {
85
- method = element.readAttribute('data-method') || 'get';
86
- url = element.readAttribute('href');
87
- params = {};
88
- }
144
+ form.hide().append(metadata_input).appendTo('body');
145
+ form.submit();
146
+ },
89
147
 
90
- new Ajax.Request(url, {
91
- method: method,
92
- parameters: params,
93
- evalScripts: true,
148
+ /* Disables form elements:
149
+ - Caches element value in 'ujs:enable-with' data store
150
+ - Replaces element text with value of 'data-disable-with' attribute
151
+ - Adds disabled=disabled attribute
152
+ */
153
+ disableFormElements: function(form) {
154
+ form.find(rails.disableSelector).each(function() {
155
+ var element = $(this), method = element.is('button') ? 'html' : 'val';
156
+ element.data('ujs:enable-with', element[method]());
157
+ element[method](element.data('disable-with'));
158
+ element.attr('disabled', 'disabled');
159
+ });
160
+ },
94
161
 
95
- onComplete: function(request) { element.fire("ajax:complete", request); },
96
- onSuccess: function(request) { element.fire("ajax:success", request); },
97
- onFailure: function(request) { element.fire("ajax:failure", request); }
98
- });
162
+ /* Re-enables disabled form elements:
163
+ - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`)
164
+ - Removes disabled attribute
165
+ */
166
+ enableFormElements: function(form) {
167
+ form.find(rails.enableSelector).each(function() {
168
+ var element = $(this), method = element.is('button') ? 'html' : 'val';
169
+ if (element.data('ujs:enable-with')) element[method](element.data('ujs:enable-with'));
170
+ element.removeAttr('disabled');
171
+ });
172
+ },
99
173
 
100
- element.fire("ajax:after");
101
- }
174
+ // If message provided in 'data-confirm' attribute, fires `confirm` event and returns result of confirm dialog.
175
+ // Attaching a handler to the element's `confirm` event that returns false cancels the confirm dialog.
176
+ allowAction: function(element) {
177
+ var message = element.data('confirm');
178
+ return !message || (rails.fire(element, 'confirm') && confirm(message));
179
+ },
102
180
 
103
- function handleMethod(element) {
104
- var method = element.readAttribute('data-method'),
105
- url = element.readAttribute('href'),
106
- csrf_param = $$('meta[name=csrf-param]')[0],
107
- csrf_token = $$('meta[name=csrf-token]')[0];
181
+ // Helper function which checks for blank inputs in a form that match the specified CSS selector
182
+ blankInputs: function(form, specifiedSelector, nonBlank) {
183
+ var inputs = $(), input,
184
+ selector = specifiedSelector || 'input,textarea';
185
+ form.find(selector).each(function() {
186
+ input = $(this);
187
+ // Collect non-blank inputs if nonBlank option is true, otherwise, collect blank inputs
188
+ if (nonBlank ? input.val() : !input.val()) {
189
+ inputs = inputs.add(input);
190
+ }
191
+ });
192
+ return inputs.length ? inputs : false;
193
+ },
108
194
 
109
- var form = new Element('form', { method: "POST", action: url, style: "display: none;" });
110
- element.parentNode.insert(form);
195
+ // Helper function which checks for non-blank inputs in a form that match the specified CSS selector
196
+ nonBlankInputs: function(form, specifiedSelector) {
197
+ return rails.blankInputs(form, specifiedSelector, true); // true specifies nonBlank
198
+ },
111
199
 
112
- if (method !== 'post') {
113
- var field = new Element('input', { type: 'hidden', name: '_method', value: method });
114
- form.insert(field);
115
- }
200
+ // Helper function, needed to provide consistent behavior in IE
201
+ stopEverything: function(e) {
202
+ e.stopImmediatePropagation();
203
+ return false;
204
+ },
116
205
 
117
- if (csrf_param) {
118
- var param = csrf_param.readAttribute('content'),
119
- token = csrf_token.readAttribute('content'),
120
- field = new Element('input', { type: 'hidden', name: param, value: token });
121
- form.insert(field);
206
+ // find all the submit events directly bound to the form and
207
+ // manually invoke them. If anyone returns false then stop the loop
208
+ callFormSubmitBindings: function(form) {
209
+ var events = form.data('events'), continuePropagation = true;
210
+ if (events !== undefined && events['submit'] !== undefined) {
211
+ $.each(events['submit'], function(i, obj){
212
+ if (typeof obj.handler === 'function') return continuePropagation = obj.handler(obj.data);
213
+ });
214
+ }
215
+ return continuePropagation;
122
216
  }
217
+ };
123
218
 
124
- form.submit();
219
+ // ajaxPrefilter is a jQuery 1.5 feature
220
+ if ('ajaxPrefilter' in $) {
221
+ $.ajaxPrefilter(function(options, originalOptions, xhr){ rails.CSRFProtection(xhr); });
222
+ } else {
223
+ $(document).ajaxSend(function(e, xhr){ rails.CSRFProtection(xhr); });
125
224
  }
126
225
 
226
+ $(rails.linkClickSelector).live('click.rails', function(e) {
227
+ var link = $(this);
228
+ if (!rails.allowAction(link)) return rails.stopEverything(e);
127
229
 
128
- document.on("click", "*[data-confirm]", function(event, element) {
129
- var message = element.readAttribute('data-confirm');
130
- if (!confirm(message)) event.stop();
230
+ if (link.data('remote') !== undefined) {
231
+ rails.handleRemote(link);
232
+ return false;
233
+ } else if (link.data('method')) {
234
+ rails.handleMethod(link);
235
+ return false;
236
+ }
131
237
  });
132
238
 
133
- document.on("click", "a[data-remote]", function(event, element) {
134
- if (event.stopped) return;
135
- handleRemote(element);
136
- event.stop();
137
- });
239
+ $(rails.formSubmitSelector).live('submit.rails', function(e) {
240
+ var form = $(this),
241
+ remote = form.data('remote') !== undefined,
242
+ blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector),
243
+ nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector);
138
244
 
139
- document.on("click", "a[data-method]", function(event, element) {
140
- if (event.stopped) return;
141
- handleMethod(element);
142
- event.stop();
143
- });
245
+ if (!rails.allowAction(form)) return rails.stopEverything(e);
144
246
 
145
- document.on("submit", function(event) {
146
- var element = event.findElement(),
147
- message = element.readAttribute('data-confirm');
148
- if (message && !confirm(message)) {
149
- event.stop();
150
- return false;
247
+ // skip other logic when required values are missing or file upload is present
248
+ if (blankRequiredInputs && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) {
249
+ return !remote;
151
250
  }
152
251
 
153
- var inputs = element.select("input[type=submit][data-disable-with]");
154
- inputs.each(function(input) {
155
- input.disabled = true;
156
- input.writeAttribute('data-original-value', input.value);
157
- input.value = input.readAttribute('data-disable-with');
158
- });
159
-
160
- var element = event.findElement("form[data-remote]");
161
- if (element) {
162
- handleRemote(element);
163
- event.stop();
252
+ if (remote) {
253
+ if (nonBlankFileInputs) {
254
+ return rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]);
255
+ }
256
+
257
+ // If browser does not support submit bubbling, then this live-binding will be called before direct
258
+ // bindings. Therefore, we should directly call any direct bindings before remotely submitting form.
259
+ if (!$.support.submitBubbles && rails.callFormSubmitBindings(form) === false) return rails.stopEverything(e);
260
+
261
+ rails.handleRemote(form);
262
+ return false;
263
+ } else {
264
+ // slight timeout so that the submit button gets properly serialized
265
+ setTimeout(function(){ rails.disableFormElements(form); }, 13);
164
266
  }
165
267
  });
166
268
 
167
- document.on("ajax:after", "form", function(event, element) {
168
- var inputs = element.select("input[type=submit][disabled=true][data-disable-with]");
169
- inputs.each(function(input) {
170
- input.value = input.readAttribute('data-original-value');
171
- input.removeAttribute('data-original-value');
172
- input.disabled = false;
173
- });
174
- });
269
+ $(rails.formInputClickSelector).live('click.rails', function(event) {
270
+ var button = $(this);
175
271
 
176
- Ajax.Responders.register({
177
- onCreate: function(request) {
178
- var csrf_meta_tag = $$('meta[name=csrf-token]')[0];
272
+ if (!rails.allowAction(button)) return rails.stopEverything(event);
179
273
 
180
- if (csrf_meta_tag) {
181
- var header = 'X-CSRF-Token',
182
- token = csrf_meta_tag.readAttribute('content');
274
+ // register the pressed submit button
275
+ var name = button.attr('name'),
276
+ data = name ? {name:name, value:button.val()} : null;
183
277
 
184
- if (!request.options.requestHeaders) {
185
- request.options.requestHeaders = {};
186
- }
187
- request.options.requestHeaders[header] = token;
188
- }
189
- }
278
+ button.closest('form').data('ujs:submit-button', data);
279
+ });
280
+
281
+ $(rails.formSubmitSelector).live('ajax:beforeSend.rails', function(event) {
282
+ if (this == event.target) rails.disableFormElements($(this));
190
283
  });
191
- })();
284
+
285
+ $(rails.formSubmitSelector).live('ajax:complete.rails', function(event) {
286
+ if (this == event.target) rails.enableFormElements($(this));
287
+ });
288
+
289
+ })( jQuery );
@@ -1,6 +1,7 @@
1
1
  FactoryGirl.define do
2
2
  factory :article do |a|
3
3
  a.title "Some article"
4
+ a.post "This is the post. It shouldn't be blank."
4
5
  end
5
6
 
6
7
  factory :user do |u|
@@ -2,10 +2,15 @@ require 'spec_helper'
2
2
 
3
3
  class ActionView::Base
4
4
  include Governor::Controllers::Helpers
5
+ include GovernorCommentsHelper
5
6
 
6
7
  def params
7
8
  {:governor_mapping => :articles}
8
9
  end
10
+
11
+ def action_name
12
+ 'new'
13
+ end
9
14
  end
10
15
 
11
16
  module Governor
@@ -15,15 +20,17 @@ module Governor
15
20
  before(:each) do
16
21
  @user = Factory(:user)
17
22
  @article = Factory(:article, :author => @user)
18
- @comment = Factory(:comment, :article => @article, :commenter => @user)
23
+ @comment = Factory(:comment, :resource => @article, :commenter => @user)
19
24
  assign(:article, @article)
20
25
  assign(:comment, @comment)
21
26
  end
22
27
 
28
+
23
29
  it "shows the comments" do
24
30
  sign_in @user
25
31
  render
26
32
 
33
+ rendered.should =~ /1 comment/
27
34
  rendered.should =~ /add a comment/
28
35
  end
29
36
  end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ describe "layouts/application.html.erb" do
4
+ include Devise::TestHelpers
5
+
6
+ before(:each) do
7
+ sign_in Factory(:user)
8
+ end
9
+
10
+ it "includes the helper" do
11
+ view.should respond_to(:governor_header)
12
+ end
13
+
14
+ it "links to comment review page" do
15
+ render
16
+ rendered.should have_selector('a', :href => edit_comments_articles_path, :content => 'Manage Comments')
17
+ end
18
+ end
File without changes