crest_in_place 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. data/.gitignore +12 -0
  2. data/.rspec +1 -0
  3. data/.travis.yml +13 -0
  4. data/CHANGELOG.md +36 -0
  5. data/Gemfile +9 -0
  6. data/README.md +440 -0
  7. data/Rakefile +8 -0
  8. data/crest_in_place-2.1.0.gem +0 -0
  9. data/crest_in_place.gemspec +28 -0
  10. data/lib/assets/javascripts/best_in_place.js +740 -0
  11. data/lib/assets/javascripts/best_in_place.purr.js +10 -0
  12. data/lib/assets/javascripts/jquery.purr.js +135 -0
  13. data/lib/crest_in_place.rb +12 -0
  14. data/lib/crest_in_place/check_version.rb +8 -0
  15. data/lib/crest_in_place/controller_extensions.rb +28 -0
  16. data/lib/crest_in_place/display_methods.rb +44 -0
  17. data/lib/crest_in_place/engine.rb +8 -0
  18. data/lib/crest_in_place/helper.rb +132 -0
  19. data/lib/crest_in_place/railtie.rb +7 -0
  20. data/lib/crest_in_place/test_helpers.rb +42 -0
  21. data/lib/crest_in_place/utils.rb +21 -0
  22. data/lib/crest_in_place/version.rb +3 -0
  23. data/spec/helpers/best_in_place_spec.rb +429 -0
  24. data/spec/integration/double_init_spec.rb +34 -0
  25. data/spec/integration/js_spec.rb +1041 -0
  26. data/spec/integration/live_spec.rb +40 -0
  27. data/spec/integration/text_area_spec.rb +40 -0
  28. data/spec/spec_helper.rb +26 -0
  29. data/spec/support/retry_on_timeout.rb +10 -0
  30. data/test_app/Gemfile +16 -0
  31. data/test_app/README +256 -0
  32. data/test_app/Rakefile +7 -0
  33. data/test_app/app/assets/images/no.png +0 -0
  34. data/test_app/app/assets/images/red_pen.png +0 -0
  35. data/test_app/app/assets/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
  36. data/test_app/app/assets/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
  37. data/test_app/app/assets/images/ui-bg_flat_10_000000_40x100.png +0 -0
  38. data/test_app/app/assets/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
  39. data/test_app/app/assets/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
  40. data/test_app/app/assets/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  41. data/test_app/app/assets/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
  42. data/test_app/app/assets/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
  43. data/test_app/app/assets/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
  44. data/test_app/app/assets/images/ui-icons_222222_256x240.png +0 -0
  45. data/test_app/app/assets/images/ui-icons_228ef1_256x240.png +0 -0
  46. data/test_app/app/assets/images/ui-icons_ef8c08_256x240.png +0 -0
  47. data/test_app/app/assets/images/ui-icons_ffd27a_256x240.png +0 -0
  48. data/test_app/app/assets/images/ui-icons_ffffff_256x240.png +0 -0
  49. data/test_app/app/assets/images/yes.png +0 -0
  50. data/test_app/app/assets/javascripts/application.js +35 -0
  51. data/test_app/app/assets/stylesheets/.gitkeep +0 -0
  52. data/test_app/app/assets/stylesheets/jquery-ui-1.8.16.custom.css.erb +357 -0
  53. data/test_app/app/assets/stylesheets/scaffold.css +60 -0
  54. data/test_app/app/assets/stylesheets/style.css.erb +87 -0
  55. data/test_app/app/controllers/admin/users_controller.rb +14 -0
  56. data/test_app/app/controllers/application_controller.rb +3 -0
  57. data/test_app/app/controllers/cuca/cars_controller.rb +16 -0
  58. data/test_app/app/controllers/users_controller.rb +99 -0
  59. data/test_app/app/helpers/application_helper.rb +2 -0
  60. data/test_app/app/helpers/users_helper.rb +29 -0
  61. data/test_app/app/models/cuca/car.rb +5 -0
  62. data/test_app/app/models/user.rb +27 -0
  63. data/test_app/app/views/admin/users/show.html.erb +20 -0
  64. data/test_app/app/views/cuca/cars/show.html.erb +13 -0
  65. data/test_app/app/views/layouts/application.html.erb +14 -0
  66. data/test_app/app/views/users/_form.html.erb +51 -0
  67. data/test_app/app/views/users/double_init.html.erb +72 -0
  68. data/test_app/app/views/users/edit.html.erb +5 -0
  69. data/test_app/app/views/users/email_field.html.erb +1 -0
  70. data/test_app/app/views/users/index.html.erb +25 -0
  71. data/test_app/app/views/users/new.html.erb +5 -0
  72. data/test_app/app/views/users/show.html.erb +141 -0
  73. data/test_app/app/views/users/show_ajax.html.erb +12 -0
  74. data/test_app/config.ru +4 -0
  75. data/test_app/config/application.rb +51 -0
  76. data/test_app/config/boot.rb +13 -0
  77. data/test_app/config/database.yml +22 -0
  78. data/test_app/config/environment.rb +5 -0
  79. data/test_app/config/environments/development.rb +25 -0
  80. data/test_app/config/environments/production.rb +49 -0
  81. data/test_app/config/environments/test.rb +35 -0
  82. data/test_app/config/initializers/backtrace_silencers.rb +7 -0
  83. data/test_app/config/initializers/countries.rb +1 -0
  84. data/test_app/config/initializers/default_date_format.rb +2 -0
  85. data/test_app/config/initializers/inflections.rb +10 -0
  86. data/test_app/config/initializers/mime_types.rb +5 -0
  87. data/test_app/config/initializers/secret_token.rb +7 -0
  88. data/test_app/config/initializers/session_store.rb +8 -0
  89. data/test_app/config/locales/en.yml +5 -0
  90. data/test_app/config/routes.rb +20 -0
  91. data/test_app/db/migrate/20101206205922_create_users.rb +18 -0
  92. data/test_app/db/migrate/20101212170114_add_receive_email_to_user.rb +9 -0
  93. data/test_app/db/migrate/20110115204441_add_description_to_user.rb +9 -0
  94. data/test_app/db/migrate/20111210084202_add_favorite_color_to_users.rb +5 -0
  95. data/test_app/db/migrate/20111210084251_add_favorite_books_to_users.rb +5 -0
  96. data/test_app/db/migrate/20111217215935_add_birth_date_to_users.rb +5 -0
  97. data/test_app/db/migrate/20111224181356_add_money_to_user.rb +5 -0
  98. data/test_app/db/migrate/20120513003308_create_cars.rb +11 -0
  99. data/test_app/db/migrate/20120607172609_add_favorite_movie_to_users.rb +5 -0
  100. data/test_app/db/migrate/20120616170454_add_money_proc_to_users.rb +6 -0
  101. data/test_app/db/migrate/20120620165212_add_height_to_user.rb +5 -0
  102. data/test_app/db/migrate/20130213224102_add_favorite_locale_to_users.rb +5 -0
  103. data/test_app/db/schema.rb +41 -0
  104. data/test_app/db/seeds.rb +19 -0
  105. data/test_app/doc/README_FOR_APP +2 -0
  106. data/test_app/lib/tasks/.gitkeep +0 -0
  107. data/test_app/lib/tasks/cron.rake +7 -0
  108. data/test_app/public/404.html +26 -0
  109. data/test_app/public/422.html +26 -0
  110. data/test_app/public/500.html +26 -0
  111. data/test_app/public/favicon.ico +0 -0
  112. data/test_app/public/robots.txt +5 -0
  113. data/test_app/script/rails +6 -0
  114. data/test_app/test/fixtures/users.yml +17 -0
  115. data/test_app/test/functional/users_controller_test.rb +49 -0
  116. data/test_app/test/performance/browsing_test.rb +9 -0
  117. data/test_app/test/test_helper.rb +13 -0
  118. data/test_app/test/unit/helpers/users_helper_test.rb +4 -0
  119. data/test_app/test/unit/user_test.rb +8 -0
  120. data/test_app/vendor/plugins/.gitkeep +0 -0
  121. metadata +256 -0
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core'
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task :default => :spec
Binary file
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "crest_in_place/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "crest_in_place"
7
+ s.version = CrestInPlace::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Chris Danzig"]
10
+ s.email = ["chris@danzigmedia.com"]
11
+ s.homepage = "https://github.com/cdanzig/best_in_place"
12
+ s.summary = %q{It makes any field in place editable by clicking on it, it works for inputs, textareas, select dropdowns and checkboxes}
13
+ s.description = %q{CrestInPlace is a jQuery script and a Rails 3 helper that provide the method best_in_place to display any object field easily editable for the user by just clicking on it. It supports input data, text data, boolean data and custom dropdown data. It works with RESTful controllers.}
14
+
15
+ s.rubyforge_project = "crest_in_place"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency "rails", ">= 3.1"
23
+ s.add_dependency "jquery-rails"
24
+
25
+ s.add_development_dependency "rspec-rails", "~> 2.8.0"
26
+ s.add_development_dependency "nokogiri"
27
+ s.add_development_dependency "capybara", "~> 1.1.2"
28
+ end
@@ -0,0 +1,740 @@
1
+ /*
2
+ BestInPlace (for jQuery)
3
+ version: 0.1.0 (01/01/2011)
4
+ @requires jQuery >= v1.4
5
+ @requires jQuery.purr to display pop-up windows
6
+
7
+ By Bernat Farrero based on the work of Jan Varwig.
8
+ Examples at http://bernatfarrero.com
9
+
10
+ Licensed under the MIT:
11
+ http://www.opensource.org/licenses/mit-license.php
12
+
13
+ Usage:
14
+
15
+ Attention.
16
+ The format of the JSON object given to the select inputs is the following:
17
+ [["key", "value"],["key", "value"]]
18
+ The format of the JSON object given to the checkbox inputs is the following:
19
+ ["falseValue", "trueValue"]
20
+ */
21
+
22
+ function BestInPlaceEditor(e) {
23
+ this.element = e;
24
+ this.initOptions();
25
+ this.bindForm();
26
+ this.initNil();
27
+ jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
28
+ }
29
+
30
+ BestInPlaceEditor.prototype = {
31
+ // Public Interface Functions //////////////////////////////////////////////
32
+
33
+ activate : function() {
34
+ var to_display = "";
35
+ if (this.isNil()) {
36
+ to_display = "";
37
+ }
38
+ else if (this.original_content) {
39
+ to_display = this.original_content;
40
+ }
41
+ else {
42
+ if (this.sanitize) {
43
+ to_display = this.element.text();
44
+ } else {
45
+ to_display = this.element.html().replace('&', '&');
46
+ }
47
+ }
48
+
49
+ this.oldValue = this.isNil() ? "" : this.element.html();
50
+ this.display_value = to_display;
51
+ jQuery(this.activator).unbind("click", this.clickHandler);
52
+ this.activateForm();
53
+ this.element.trigger(jQuery.Event("best_in_place:activate"));
54
+ },
55
+
56
+ abort : function() {
57
+ this.activateText(this.oldValue);
58
+ jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
59
+ this.element.trigger(jQuery.Event("best_in_place:abort"));
60
+ this.element.trigger(jQuery.Event("best_in_place:deactivate"));
61
+ },
62
+
63
+ abortIfConfirm : function () {
64
+ if (!this.useConfirm) {
65
+ this.abort();
66
+ return;
67
+ }
68
+
69
+ if (confirm("Are you sure you want to discard your changes?")) {
70
+ this.abort();
71
+ }
72
+ },
73
+
74
+ update : function() {
75
+ var editor = this;
76
+ if (this.formType in {"input":1, "textarea":1} && this.getValue() == this.oldValue)
77
+ { // Avoid request if no change is made
78
+ this.abort();
79
+ return true;
80
+ }
81
+ editor.ajax({
82
+ "type" : "post",
83
+ "dataType" : "text",
84
+ "data" : editor.requestData(),
85
+ "success" : function(data){ editor.loadSuccessCallback(data); },
86
+ "error" : function(request, error){ editor.loadErrorCallback(request, error); }
87
+ });
88
+ if (this.formType == "select") {
89
+ var value = this.getValue();
90
+ this.previousCollectionValue = value;
91
+
92
+ jQuery.each(this.values, function(i, v) {
93
+ if (value == v[0]) {
94
+ editor.element.html(v[1]);
95
+ }
96
+ }
97
+ );
98
+ } else if (this.formType == "checkbox") {
99
+ editor.element.html(this.getValue() ? this.values[1] : this.values[0]);
100
+ } else {
101
+ if (this.getValue() !== "") {
102
+ editor.element.text(this.getValue());
103
+ } else {
104
+ editor.element.html(this.nil);
105
+ }
106
+ }
107
+ editor.element.trigger(jQuery.Event("best_in_place:update"));
108
+ },
109
+
110
+ activateForm : function() {
111
+ alert("The form was not properly initialized. activateForm is unbound");
112
+ },
113
+
114
+ activateText : function(value){
115
+ this.element.html(value);
116
+ if(this.isNil()) this.element.html(this.nil);
117
+ },
118
+
119
+ // Helper Functions ////////////////////////////////////////////////////////
120
+
121
+ initOptions : function() {
122
+ // Try parent supplied info
123
+ var self = this;
124
+ self.element.parents().each(function(){
125
+ $parent = jQuery(this);
126
+ self.url = self.url || $parent.attr("data-url");
127
+ self.collection = self.collection || $parent.attr("data-collection");
128
+ self.formType = self.formType || $parent.attr("data-type");
129
+ self.objectName = self.objectName || $parent.attr("data-object");
130
+ self.attributeName = self.attributeName || $parent.attr("data-attribute");
131
+ self.activator = self.activator || $parent.attr("data-activator");
132
+ self.okButton = self.okButton || $parent.attr("data-ok-button");
133
+ self.okButtonClass = self.okButtonClass || $parent.attr("data-ok-button-class");
134
+ self.cancelButton = self.cancelButton || $parent.attr("data-cancel-button");
135
+ self.cancelButtonClass = self.cancelButtonClass || $parent.attr("data-cancel-button-class");
136
+ self.nil = self.nil || $parent.attr("data-nil");
137
+ self.inner_class = self.inner_class || $parent.attr("data-inner-class");
138
+ self.html_attrs = self.html_attrs || $parent.attr("data-html-attrs");
139
+ self.original_content = self.original_content || $parent.attr("data-original-content");
140
+ self.collectionValue = self.collectionValue || $parent.attr("data-value");
141
+ });
142
+
143
+ // Try Rails-id based if parents did not explicitly supply something
144
+ self.element.parents().each(function(){
145
+ var res = this.id.match(/^(\w+)_(\d+)$/i);
146
+ if (res) {
147
+ self.objectName = self.objectName || res[1];
148
+ }
149
+ });
150
+
151
+ // Load own attributes (overrides all others)
152
+ self.url = self.element.attr("data-url") || self.url || document.location.pathname;
153
+ self.collection = self.element.attr("data-collection") || self.collection;
154
+ self.formType = self.element.attr("data-type") || self.formtype || "input";
155
+ self.objectName = self.element.attr("data-object") || self.objectName;
156
+ self.attributeName = self.element.attr("data-attribute") || self.attributeName;
157
+ self.activator = self.element.attr("data-activator") || self.element;
158
+ self.okButton = self.element.attr("data-ok-button") || self.okButton;
159
+ self.okButtonClass = self.element.attr("data-ok-button-class") || self.okButtonClass || "";
160
+ self.cancelButton = self.element.attr("data-cancel-button") || self.cancelButton;
161
+ self.cancelButtonClass = self.element.attr("data-cancel-button-class") || self.cancelButtonClass || "";
162
+ self.nil = self.element.attr("data-nil") || self.nil || "—";
163
+ self.inner_class = self.element.attr("data-inner-class") || self.inner_class || null;
164
+ self.html_attrs = self.element.attr("data-html-attrs") || self.html_attrs;
165
+ self.original_content = self.element.attr("data-original-content") || self.original_content;
166
+ self.collectionValue = self.element.attr("data-value") || self.collectionValue;
167
+
168
+ if (!self.element.attr("data-sanitize")) {
169
+ self.sanitize = true;
170
+ }
171
+ else {
172
+ self.sanitize = (self.element.attr("data-sanitize") == "true");
173
+ }
174
+
175
+ if (!self.element.attr("data-use-confirm")) {
176
+ self.useConfirm = true;
177
+ } else {
178
+ self.useConfirm = (self.element.attr("data-use-confirm") != "false");
179
+ }
180
+
181
+ if ((self.formType == "select" || self.formType == "checkbox") && self.collection !== null)
182
+ {
183
+ self.values = jQuery.parseJSON(self.collection);
184
+ }
185
+
186
+ },
187
+
188
+ bindForm : function() {
189
+ this.activateForm = BestInPlaceEditor.forms[this.formType].activateForm;
190
+ this.getValue = BestInPlaceEditor.forms[this.formType].getValue;
191
+ },
192
+
193
+ initNil: function() {
194
+ if (this.element.html() === "")
195
+ {
196
+ this.element.html(this.nil);
197
+ }
198
+ },
199
+
200
+ isNil: function() {
201
+ // TODO: It only work when form is deactivated.
202
+ // Condition will fail when form is activated
203
+ return this.element.html() === "" || this.element.html() === this.nil;
204
+ },
205
+
206
+ getValue : function() {
207
+ alert("The form was not properly initialized. getValue is unbound");
208
+ },
209
+
210
+ // Trim and Strips HTML from text
211
+ sanitizeValue : function(s) {
212
+ return jQuery.trim(s);
213
+ },
214
+
215
+ /* Generate the data sent in the POST request */
216
+ requestData : function() {
217
+ // To prevent xss attacks, a csrf token must be defined as a meta attribute
218
+ csrf_token = jQuery('meta[name=csrf-token]').attr('content');
219
+ csrf_param = jQuery('meta[name=csrf-param]').attr('content');
220
+
221
+ var data = "_method=put";
222
+ data += "&" + this.objectName + '[' + this.attributeName + ']=' + encodeURIComponent(this.getValue());
223
+
224
+ if (csrf_param !== undefined && csrf_token !== undefined) {
225
+ data += "&" + csrf_param + "=" + encodeURIComponent(csrf_token);
226
+ }
227
+ return data;
228
+ },
229
+
230
+ ajax : function(options) {
231
+ options.url = this.url;
232
+ options.beforeSend = function(xhr){ xhr.setRequestHeader("Accept", "application/json"); };
233
+ return jQuery.ajax(options);
234
+ },
235
+
236
+ // Handlers ////////////////////////////////////////////////////////////////
237
+
238
+ loadSuccessCallback : function(data) {
239
+ data = jQuery.trim(data);
240
+
241
+ if(data && data!=""){
242
+ var response = jQuery.parseJSON(jQuery.trim(data));
243
+ if (response !== null && response.hasOwnProperty("display_as")) {
244
+ this.element.attr("data-original-content", this.element.text());
245
+ this.original_content = this.element.text();
246
+ this.element.html(response["display_as"]);
247
+ }
248
+
249
+ this.element.trigger(jQuery.Event("best_in_place:success"), data);
250
+ this.element.trigger(jQuery.Event("ajax:success"), data);
251
+ } else {
252
+ this.element.trigger(jQuery.Event("best_in_place:success"));
253
+ this.element.trigger(jQuery.Event("ajax:success"));
254
+ }
255
+
256
+ // Binding back after being clicked
257
+ jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
258
+ this.element.trigger(jQuery.Event("best_in_place:deactivate"));
259
+
260
+ if (this.collectionValue !== null && this.formType == "select") {
261
+ this.collectionValue = this.previousCollectionValue;
262
+ this.previousCollectionValue = null;
263
+ }
264
+ },
265
+
266
+ loadErrorCallback : function(request, error) {
267
+ this.activateText(this.oldValue);
268
+
269
+ this.element.trigger(jQuery.Event("best_in_place:error"), [request, error]);
270
+ this.element.trigger(jQuery.Event("ajax:error"), request, error);
271
+
272
+ // Binding back after being clicked
273
+ jQuery(this.activator).bind('click', {editor: this}, this.clickHandler);
274
+ this.element.trigger(jQuery.Event("best_in_place:deactivate"));
275
+ },
276
+
277
+ clickHandler : function(event) {
278
+ event.preventDefault();
279
+ event.data.editor.activate();
280
+ },
281
+
282
+ setHtmlAttributes : function() {
283
+ var formField = this.element.find(this.formType);
284
+
285
+ if(this.html_attrs){
286
+ var attrs = jQuery.parseJSON(this.html_attrs);
287
+ for(var key in attrs){
288
+ formField.attr(key, attrs[key]);
289
+ }
290
+ }
291
+ }
292
+ };
293
+
294
+
295
+ // Button cases:
296
+ // If no buttons, then blur saves, ESC cancels
297
+ // If just Cancel button, then blur saves, ESC or clicking Cancel cancels (careful of blur event!)
298
+ // If just OK button, then clicking OK saves (careful of blur event!), ESC or blur cancels
299
+ // If both buttons, then clicking OK saves, ESC or clicking Cancel or blur cancels
300
+ BestInPlaceEditor.forms = {
301
+ "input" : {
302
+ activateForm : function() {
303
+ var output = jQuery(document.createElement('form'))
304
+ .addClass('form_in_place')
305
+ .attr('action', 'javascript:void(0);')
306
+ .attr('style', 'display:inline');
307
+ var input_elt = jQuery(document.createElement('input'))
308
+ .attr('type', 'text')
309
+ .attr('name', this.attributeName)
310
+ .val(this.display_value);
311
+ if(this.inner_class !== null) {
312
+ input_elt.addClass(this.inner_class);
313
+ }
314
+ output.append(input_elt);
315
+ if(this.okButton) {
316
+ output.append(
317
+ jQuery(document.createElement('input'))
318
+ .attr('type', 'submit')
319
+ .attr('class', this.okButtonClass)
320
+ .attr('value', this.okButton)
321
+ )
322
+ }
323
+ if(this.cancelButton) {
324
+ output.append(
325
+ jQuery(document.createElement('input'))
326
+ .attr('type', 'button')
327
+ .attr('class', this.cancelButtonClass)
328
+ .attr('value', this.cancelButton)
329
+ )
330
+ }
331
+
332
+ this.element.html(output);
333
+ this.setHtmlAttributes();
334
+ this.element.find("input[type='text']")[0].select();
335
+ this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.input.submitHandler);
336
+ if (this.cancelButton) {
337
+ this.element.find("input[type='button']").bind('click', {editor: this}, BestInPlaceEditor.forms.input.cancelButtonHandler);
338
+ }
339
+ this.element.find("input[type='text']").bind('blur', {editor: this}, BestInPlaceEditor.forms.input.inputBlurHandler);
340
+ this.element.find("input[type='text']").bind('keyup', {editor: this}, BestInPlaceEditor.forms.input.keyupHandler);
341
+ this.blurTimer = null;
342
+ this.userClicked = false;
343
+ },
344
+
345
+ getValue : function() {
346
+ return this.sanitizeValue(this.element.find("input").val());
347
+ },
348
+
349
+ // When buttons are present, use a timer on the blur event to give precedence to clicks
350
+ inputBlurHandler : function(event) {
351
+ if (event.data.editor.okButton) {
352
+ event.data.editor.blurTimer = setTimeout(function () {
353
+ if (!event.data.editor.userClicked) {
354
+ event.data.editor.abort();
355
+ }
356
+ }, 500);
357
+ } else {
358
+ if (event.data.editor.cancelButton) {
359
+ event.data.editor.blurTimer = setTimeout(function () {
360
+ if (!event.data.editor.userClicked) {
361
+ event.data.editor.update();
362
+ }
363
+ }, 500);
364
+ } else {
365
+ event.data.editor.update();
366
+ }
367
+ }
368
+ },
369
+
370
+ submitHandler : function(event) {
371
+ event.data.editor.userClicked = true;
372
+ clearTimeout(event.data.editor.blurTimer);
373
+ event.data.editor.update();
374
+ },
375
+
376
+ cancelButtonHandler : function(event) {
377
+ event.data.editor.userClicked = true;
378
+ clearTimeout(event.data.editor.blurTimer);
379
+ event.data.editor.abort();
380
+ event.stopPropagation(); // Without this, click isn't handled
381
+ },
382
+
383
+ keyupHandler : function(event) {
384
+ if (event.keyCode == 27) {
385
+ event.data.editor.abort();
386
+ }
387
+ }
388
+ },
389
+
390
+ "date" : {
391
+ activateForm : function() {
392
+ var that = this,
393
+ output = jQuery(document.createElement('form'))
394
+ .addClass('form_in_place')
395
+ .attr('action', 'javascript:void(0);')
396
+ .attr('style', 'display:inline'),
397
+ input_elt = jQuery(document.createElement('input'))
398
+ .attr('type', 'text')
399
+ .attr('name', this.attributeName)
400
+ .attr('value', this.sanitizeValue(this.display_value));
401
+ if(this.inner_class !== null) {
402
+ input_elt.addClass(this.inner_class);
403
+ }
404
+ output.append(input_elt)
405
+
406
+ this.element.html(output);
407
+ this.setHtmlAttributes();
408
+ this.element.find('input')[0].select();
409
+ this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.input.submitHandler);
410
+ this.element.find("input").bind('keyup', {editor: this}, BestInPlaceEditor.forms.input.keyupHandler);
411
+
412
+ this.element.find('input')
413
+ .datepicker({
414
+ onClose: function() {
415
+ that.update();
416
+ }
417
+ })
418
+ .datepicker('show');
419
+ },
420
+
421
+ getValue : function() {
422
+ return this.sanitizeValue(this.element.find("input").val());
423
+ },
424
+
425
+ submitHandler : function(event) {
426
+ event.data.editor.update();
427
+ },
428
+
429
+ keyupHandler : function(event) {
430
+ if (event.keyCode == 27) {
431
+ event.data.editor.abort();
432
+ }
433
+ }
434
+ },
435
+
436
+ "select" : {
437
+ activateForm : function() {
438
+ var output = jQuery(document.createElement('form'))
439
+ .attr('action', 'javascript:void(0)')
440
+ .attr('style', 'display:inline');
441
+ selected = '',
442
+ oldValue = this.oldValue,
443
+ select_elt = jQuery(document.createElement('select'))
444
+ .attr('class', this.inned_class !== null ? this.inner_class : '' ),
445
+ currentCollectionValue = this.collectionValue;
446
+
447
+ jQuery.each(this.values, function (index, value) {
448
+ var option_elt = jQuery(document.createElement('option'))
449
+ // .attr('value', value[0])
450
+ .val(value[0])
451
+ .html(value[1]);
452
+ if(value[0] == currentCollectionValue) {
453
+ option_elt.attr('selected', 'selected');
454
+ }
455
+ select_elt.append(option_elt);
456
+ });
457
+ output.append(select_elt);
458
+
459
+ this.element.html(output);
460
+ this.setHtmlAttributes();
461
+ this.element.find("select").bind('change', {editor: this}, BestInPlaceEditor.forms.select.blurHandler);
462
+ this.element.find("select").bind('blur', {editor: this}, BestInPlaceEditor.forms.select.blurHandler);
463
+ this.element.find("select").bind('keyup', {editor: this}, BestInPlaceEditor.forms.select.keyupHandler);
464
+ this.element.find("select")[0].focus();
465
+ },
466
+
467
+ getValue : function() {
468
+ return this.sanitizeValue(this.element.find("select").val());
469
+ // return this.element.find("select").val();
470
+ },
471
+
472
+ blurHandler : function(event) {
473
+ event.data.editor.update();
474
+ },
475
+
476
+ keyupHandler : function(event) {
477
+ if (event.keyCode == 27) event.data.editor.abort();
478
+ }
479
+ },
480
+
481
+ "checkbox" : {
482
+ activateForm : function() {
483
+ this.collectionValue = !this.getValue();
484
+ this.setHtmlAttributes();
485
+ this.update();
486
+ },
487
+
488
+ getValue : function() {
489
+ return this.collectionValue;
490
+ }
491
+ },
492
+
493
+ "textarea" : {
494
+ activateForm : function() {
495
+ // grab width and height of text
496
+ width = this.element.css('width');
497
+ height = this.element.css('height');
498
+
499
+ // construct form
500
+ var output = jQuery(document.createElement('form'))
501
+ .attr('action', 'javascript:void(0)')
502
+ .attr('style', 'display:inline')
503
+ .append(jQuery(document.createElement('textarea'))
504
+ .val(this.sanitizeValue(this.display_value)));
505
+ if(this.okButton) {
506
+ output.append(
507
+ jQuery(document.createElement('input'))
508
+ .attr('type', 'submit')
509
+ .attr('value', this.okButton)
510
+ );
511
+ }
512
+ if(this.cancelButton) {
513
+ output.append(
514
+ jQuery(document.createElement('input'))
515
+ .attr('type', 'button')
516
+ .attr('value', this.cancelButton)
517
+ )
518
+ }
519
+
520
+ this.element.html(output);
521
+ this.setHtmlAttributes();
522
+
523
+ // set width and height of textarea
524
+ jQuery(this.element.find("textarea")[0]).css({ 'min-width': width, 'min-height': height });
525
+ jQuery(this.element.find("textarea")[0]).elastic();
526
+
527
+ this.element.find("textarea")[0].focus();
528
+ this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.textarea.submitHandler);
529
+ if (this.cancelButton) {
530
+ this.element.find("input[type='button']").bind('click', {editor: this}, BestInPlaceEditor.forms.textarea.cancelButtonHandler);
531
+ }
532
+ this.element.find("textarea").bind('blur', {editor: this}, BestInPlaceEditor.forms.textarea.blurHandler);
533
+ this.element.find("textarea").bind('keyup', {editor: this}, BestInPlaceEditor.forms.textarea.keyupHandler);
534
+ this.blurTimer = null;
535
+ this.userClicked = false;
536
+ },
537
+
538
+ getValue : function() {
539
+ return this.sanitizeValue(this.element.find("textarea").val());
540
+ },
541
+
542
+ // When buttons are present, use a timer on the blur event to give precedence to clicks
543
+ blurHandler : function(event) {
544
+ if (event.data.editor.okButton) {
545
+ event.data.editor.blurTimer = setTimeout(function () {
546
+ if (!event.data.editor.userClicked) {
547
+ event.data.editor.abortIfConfirm();
548
+ }
549
+ }, 500);
550
+ } else {
551
+ if (event.data.editor.cancelButton) {
552
+ event.data.editor.blurTimer = setTimeout(function () {
553
+ if (!event.data.editor.userClicked) {
554
+ event.data.editor.update();
555
+ }
556
+ }, 500);
557
+ } else {
558
+ event.data.editor.update();
559
+ }
560
+ }
561
+ },
562
+
563
+ submitHandler : function(event) {
564
+ event.data.editor.userClicked = true;
565
+ clearTimeout(event.data.editor.blurTimer);
566
+ event.data.editor.update();
567
+ },
568
+
569
+ cancelButtonHandler : function(event) {
570
+ event.data.editor.userClicked = true;
571
+ clearTimeout(event.data.editor.blurTimer);
572
+ event.data.editor.abortIfConfirm();
573
+ event.stopPropagation(); // Without this, click isn't handled
574
+ },
575
+
576
+ keyupHandler : function(event) {
577
+ if (event.keyCode == 27) {
578
+ event.data.editor.abortIfConfirm();
579
+ }
580
+ }
581
+ }
582
+ };
583
+
584
+ jQuery.fn.best_in_place = function() {
585
+
586
+ function setBestInPlace(element) {
587
+ if (!element.data('bestInPlaceEditor')) {
588
+ element.data('bestInPlaceEditor', new BestInPlaceEditor(element));
589
+ return true;
590
+ }
591
+ }
592
+
593
+ jQuery(this.context).delegate(this.selector, 'click', function () {
594
+ var el = jQuery(this);
595
+ if (setBestInPlace(el))
596
+ el.click();
597
+ });
598
+
599
+ this.each(function () {
600
+ setBestInPlace(jQuery(this));
601
+ });
602
+
603
+ return this;
604
+ };
605
+
606
+
607
+
608
+ /**
609
+ * @name Elastic
610
+ * @descripton Elastic is Jquery plugin that grow and shrink your textareas automaticliy
611
+ * @version 1.6.5
612
+ * @requires Jquery 1.2.6+
613
+ *
614
+ * @author Jan Jarfalk
615
+ * @author-email jan.jarfalk@unwrongest.com
616
+ * @author-website http://www.unwrongest.com
617
+ *
618
+ * @licens MIT License - http://www.opensource.org/licenses/mit-license.php
619
+ */
620
+
621
+ (function(jQuery){
622
+ if (typeof jQuery.fn.elastic !== 'undefined') return;
623
+
624
+ jQuery.fn.extend({
625
+ elastic: function() {
626
+ // We will create a div clone of the textarea
627
+ // by copying these attributes from the textarea to the div.
628
+ var mimics = [
629
+ 'paddingTop',
630
+ 'paddingRight',
631
+ 'paddingBottom',
632
+ 'paddingLeft',
633
+ 'fontSize',
634
+ 'lineHeight',
635
+ 'fontFamily',
636
+ 'width',
637
+ 'fontWeight'];
638
+
639
+ return this.each( function() {
640
+
641
+ // Elastic only works on textareas
642
+ if ( this.type != 'textarea' ) {
643
+ return false;
644
+ }
645
+
646
+ var $textarea = jQuery(this),
647
+ $twin = jQuery('<div />').css({'position': 'absolute','display':'none','word-wrap':'break-word'}),
648
+ lineHeight = parseInt($textarea.css('line-height'),10) || parseInt($textarea.css('font-size'),'10'),
649
+ minheight = parseInt($textarea.css('height'),10) || lineHeight*3,
650
+ maxheight = parseInt($textarea.css('max-height'),10) || Number.MAX_VALUE,
651
+ goalheight = 0,
652
+ i = 0;
653
+
654
+ // Opera returns max-height of -1 if not set
655
+ if (maxheight < 0) { maxheight = Number.MAX_VALUE; }
656
+
657
+ // Append the twin to the DOM
658
+ // We are going to meassure the height of this, not the textarea.
659
+ $twin.appendTo($textarea.parent());
660
+
661
+ // Copy the essential styles (mimics) from the textarea to the twin
662
+ i = mimics.length;
663
+ while(i--){
664
+ $twin.css(mimics[i].toString(),$textarea.css(mimics[i].toString()));
665
+ }
666
+
667
+
668
+ // Sets a given height and overflow state on the textarea
669
+ function setHeightAndOverflow(height, overflow){
670
+ curratedHeight = Math.floor(parseInt(height,10));
671
+ if($textarea.height() != curratedHeight){
672
+ $textarea.css({'height': curratedHeight + 'px','overflow':overflow});
673
+
674
+ }
675
+ }
676
+
677
+
678
+ // This function will update the height of the textarea if necessary
679
+ function update() {
680
+
681
+ // Get curated content from the textarea.
682
+ var textareaContent = $textarea.val().replace(/&/g,'&amp;').replace(/ /g, '&nbsp;').replace(/<|>/g, '&gt;').replace(/\n/g, '<br />');
683
+
684
+ // Compare curated content with curated twin.
685
+ var twinContent = $twin.html().replace(/<br>/ig,'<br />');
686
+
687
+ if(textareaContent+'&nbsp;' != twinContent){
688
+
689
+ // Add an extra white space so new rows are added when you are at the end of a row.
690
+ $twin.html(textareaContent+'&nbsp;');
691
+
692
+ // Change textarea height if twin plus the height of one line differs more than 3 pixel from textarea height
693
+ if(Math.abs($twin.height() + lineHeight - $textarea.height()) > 3){
694
+
695
+ var goalheight = $twin.height()+lineHeight;
696
+ if(goalheight >= maxheight) {
697
+ setHeightAndOverflow(maxheight,'auto');
698
+ } else if(goalheight <= minheight) {
699
+ setHeightAndOverflow(minheight,'hidden');
700
+ } else {
701
+ setHeightAndOverflow(goalheight,'hidden');
702
+ }
703
+
704
+ }
705
+
706
+ }
707
+
708
+ }
709
+
710
+ // Hide scrollbars
711
+ $textarea.css({'overflow':'hidden'});
712
+
713
+ // Update textarea size on keyup, change, cut and paste
714
+ $textarea.bind('keyup change cut paste', function(){
715
+ update();
716
+ });
717
+
718
+ // Compact textarea on blur
719
+ // Lets animate this....
720
+ $textarea.bind('blur',function(){
721
+ if($twin.height() < maxheight){
722
+ if($twin.height() > minheight) {
723
+ $textarea.height($twin.height());
724
+ } else {
725
+ $textarea.height(minheight);
726
+ }
727
+ }
728
+ });
729
+
730
+ // And this line is to catch the browser paste event
731
+ $textarea.on("input paste", function(e){ setTimeout( update, 250); });
732
+
733
+ // Run update once when elastic is initialized
734
+ update();
735
+
736
+ });
737
+
738
+ }
739
+ });
740
+ })(jQuery);