best_in_place 2.1.0 → 3.0.0.rc1

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