best_in_place_mongoid 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +11 -0
- data/.rspec +1 -0
- data/Gemfile +7 -0
- data/README.md +163 -0
- data/Rakefile +8 -0
- data/best_in_place_mongoid.gemspec +27 -0
- data/lib/assets/javascripts/best_in_place.js +481 -0
- data/lib/assets/javascripts/jquery.purr.js +161 -0
- data/lib/best_in_place_mongoid.rb +7 -0
- data/lib/best_in_place_mongoid/engine.rb +7 -0
- data/lib/best_in_place_mongoid/helper.rb +53 -0
- data/lib/best_in_place_mongoid/test_helpers.rb +39 -0
- data/lib/best_in_place_mongoid/utils.rb +15 -0
- data/lib/best_in_place_mongoid/version.rb +3 -0
- data/spec/helpers/best_in_place_mongoid_spec.rb +239 -0
- data/spec/integration/double_init_spec.rb +32 -0
- data/spec/integration/js_spec.rb +165 -0
- data/spec/integration/text_area_spec.rb +30 -0
- data/spec/spec_helper.rb +23 -0
- metadata +181 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
# Best In Place Mongoid
|
2
|
+
|
3
|
+
##Description
|
4
|
+
|
5
|
+
**Best in Place Mongoid** is a fork of **Best in Place** prepared for Mongoid documents.
|
6
|
+
**Best in place** jQuery based AJAX Inplace-Editor that takes profit of RESTful server-side controllers to allow users to edit stuff with
|
7
|
+
no need of forms. If the server have standard defined REST methods, particularly those to UPDATE your objects (HTTP PUT), then by adding the
|
8
|
+
Javascript file to the application it is making all the fields with the proper defined classes to become user in-place editable.
|
9
|
+
|
10
|
+
The editor works by PUTting the updated value to the server and GETting the updated record afterwards to display the updated value.
|
11
|
+
|
12
|
+
[**SEE DEMO**](http://bipapp.heroku.com/)
|
13
|
+
|
14
|
+
---
|
15
|
+
|
16
|
+
##Features
|
17
|
+
|
18
|
+
- Compatible with text **inputs**
|
19
|
+
- Compatible with **textarea**
|
20
|
+
- Compatible with **select** dropdown with custom collections
|
21
|
+
- Compatible with custom boolean values (same usage of **checkboxes**)
|
22
|
+
- Sanitize HTML and trim spaces of user's input on user's choice
|
23
|
+
- Displays server-side **validation** errors
|
24
|
+
- Allows external activator
|
25
|
+
- ESC key destroys changes (requires user confirmation)
|
26
|
+
- Autogrowing textarea
|
27
|
+
|
28
|
+
---
|
29
|
+
|
30
|
+
##Usage of Rails 3 Gem
|
31
|
+
|
32
|
+
**best_in_place object, field, OPTIONS**
|
33
|
+
|
34
|
+
Params:
|
35
|
+
|
36
|
+
- **object** (Mandatory): The Object parameter represents the object itself you are about to modify
|
37
|
+
- **field** (Mandatory): The field (passed as symbol) is the attribute of the Object you are going to display/edit.
|
38
|
+
|
39
|
+
Options:
|
40
|
+
|
41
|
+
- **:type** It can be only [:input, :textarea, :select, :checkbox] or if undefined it defaults to :input.
|
42
|
+
- **:collection**: In case you are using the :select type then you must specify the collection of values it takes. In case you are
|
43
|
+
using the :checkbox type you can specify the two values it can take, or otherwise they will default to Yes and No.
|
44
|
+
- **:path**: URL to which the updating action will be sent. If not defined it defaults to the :object path.
|
45
|
+
- **:nil**: The nil param defines the content displayed in case no value is defined for that field. It can be something like "click me to edit".
|
46
|
+
If not defined it will show *"-"*.
|
47
|
+
- **:activator**: Is the DOM object that can activate the field. If not defined the user will making editable by clicking on it.
|
48
|
+
- **:sanitize**: True by default. If set to false the input/textarea will accept html tags.
|
49
|
+
- **:html_args**: Hash of html arguments, such as maxlength, default-value etc.
|
50
|
+
|
51
|
+
|
52
|
+
Examples (code in the views):
|
53
|
+
|
54
|
+
### Input
|
55
|
+
|
56
|
+
<%= best_in_place @user, :name, :type => :input %>
|
57
|
+
|
58
|
+
<%= best_in_place @user, :name, :type => :input, :nil => "Click me to add content!" %>
|
59
|
+
|
60
|
+
### Textarea
|
61
|
+
|
62
|
+
<%= best_in_place @user, :description, :type => :textarea %>
|
63
|
+
|
64
|
+
### Select
|
65
|
+
|
66
|
+
<%= best_in_place @user, :country, :type => :select, :collection => [[1, "Spain"], [2, "Italy"], [3, "Germany"], [4, "France"]] %>
|
67
|
+
|
68
|
+
Of course it can take an instance or global variable for the collection, just remember the structure [[key, value], [key, value],...].
|
69
|
+
The key can be a string or an integer.
|
70
|
+
|
71
|
+
### Checkbox
|
72
|
+
|
73
|
+
<%= best_in_place @user, :receive_emails, :type => :checkbox, :collection => ["No, thanks", "Yes, of course!"] %>
|
74
|
+
|
75
|
+
The first value is always the negative boolean value and the second the positive. Structure: ["false value", "true value"].
|
76
|
+
If not defined, it will default to *Yes* and *No* options.
|
77
|
+
|
78
|
+
### Display server validation errors
|
79
|
+
|
80
|
+
If you are using a Rails application, your controller's should respond to json in case of error.
|
81
|
+
Example:
|
82
|
+
|
83
|
+
def update
|
84
|
+
@user = User.find(params[:id])
|
85
|
+
|
86
|
+
respond_to do |format|
|
87
|
+
if @user.update_attributes(params[:user])
|
88
|
+
format.html { redirect_to(@user, :notice => 'User was successfully updated.') }
|
89
|
+
format.json { head :ok }
|
90
|
+
else
|
91
|
+
format.html { render :action => "edit" }
|
92
|
+
format.json { render :json => @user.errors.full_messages, :status => :unprocessable_entity }
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
At the same time, you must define the restrictions, validations and error messages in the model, as the example below:
|
98
|
+
|
99
|
+
# TODO - example
|
100
|
+
|
101
|
+
When the user tries to introduce invalid data, the error messages defined in the model will be displayed in pop-up windows using the jQuery.purr plugin.
|
102
|
+
|
103
|
+
---
|
104
|
+
|
105
|
+
##Installation
|
106
|
+
|
107
|
+
It works by simply copying and loading the files from the folder **/public/javascripts** to your application and loading them in your layouts
|
108
|
+
in the following order:
|
109
|
+
|
110
|
+
- jquery-1.4.4.js
|
111
|
+
- jquery.purr.js
|
112
|
+
- **best_in_place.js**
|
113
|
+
|
114
|
+
The last one you can copy it (and keeping up to date to the last version) by running the following generator in your application's root.
|
115
|
+
Remember to do it every time you update the gem (or you will see no change).
|
116
|
+
|
117
|
+
rails g best_in_place:setup
|
118
|
+
|
119
|
+
To be able to use the script the following block must be added as well:
|
120
|
+
|
121
|
+
$(document).ready(function() {
|
122
|
+
/* Activating Best In Place */
|
123
|
+
jQuery(".best_in_place").best_in_place()
|
124
|
+
});
|
125
|
+
|
126
|
+
In order to use the Rails 3 gem, just add the following line to the gemfile:
|
127
|
+
|
128
|
+
gem "best_in_place"
|
129
|
+
|
130
|
+
----
|
131
|
+
|
132
|
+
## Security
|
133
|
+
|
134
|
+
If the script is used with the Rails Gem no html tags will be allowed unless the sanitize option is set to true, in that case only the tags [*b i u s a strong em p h1 h2 h3 h4 h5 ul li ol hr pre span img*] will be allowed. If the script is used without the gem and with frameworks other than Rails, then you should make sure you are providing the csrf authenticity params as meta tags and you should always escape undesired html tags such as script, object and so forth.
|
135
|
+
|
136
|
+
<meta name="csrf-param" content="authenticity_token"/>
|
137
|
+
<meta name="csrf-token" content="YOUR UNIQUE TOKEN HERE"/>
|
138
|
+
|
139
|
+
##TODO
|
140
|
+
|
141
|
+
- Client Side Validation definitions
|
142
|
+
- Accepting more than one handler to activate best_in_place fields
|
143
|
+
- Specs *(sacrilege!!)*
|
144
|
+
|
145
|
+
---
|
146
|
+
|
147
|
+
##Changelog
|
148
|
+
|
149
|
+
- v.0.1.0 Initial deploy
|
150
|
+
- v.0.1.2 Fixing errors in collections (taken value[0] instead of index) and fixing test_app controller responses
|
151
|
+
- v.0.1.3 Bug in Rails Helper. Key wrongly considered an Integer.
|
152
|
+
- v.0.1.4 Adding two new parameters for further customization urlObject and nilValue and making input update on blur.
|
153
|
+
- v.0.1.5 **Attention: this release is not backwards compatible**. Changing params from list to option hash, helper's refactoring,
|
154
|
+
fixing bug with objects inside namespaces, adding feature for passing an external activator handler as param. Adding feature
|
155
|
+
of key ESCAPE for destroying changes before they are made permanent (in inputs and textarea).
|
156
|
+
- v.0.1.6-0.1.7 Avoiding request when the input is not modified and allowing the user to not sanitize input data.
|
157
|
+
- v.0.1.8 jslint compliant, sanitizing tags in the gem, getting right csrf params, controlling size of textarea (elastic script, for autogrowing textarea)
|
158
|
+
|
159
|
+
##Authors, License and Stuff
|
160
|
+
|
161
|
+
Code by [Bernat Farrero](http://bernatfarrero.com) from [Itnig Web Services](http://itnig.net) (it was based on the [original project](http://github.com/janv/rest_in_place/) of Jan Varwig) and released under [MIT license](http://www.opensource.org/licenses/mit-license.php).
|
162
|
+
|
163
|
+
Many thanks to the contributors: [Roger Campos](http://github.com/rogercampos) and [Jack Senechal](https://github.com/jacksenechal)
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "best_in_place_mongoid/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "best_in_place_mongoid"
|
7
|
+
s.version = BestInPlaceMongoid::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Bernat Farrero", "Bartłomiej Danek"]
|
10
|
+
s.email = ["bernat@itnig.net", "bartek.danek@gmail.com"]
|
11
|
+
s.homepage = "http://github.com/bartekd/best_in_place_mongoid"
|
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{BestInPlaceMongoid is a fork of BestInPlace 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.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.add_dependency "rails", "~> 3.1.0"
|
21
|
+
s.add_dependency "mongoid", "~> 2.3.3"
|
22
|
+
s.add_dependency "jquery-rails"
|
23
|
+
|
24
|
+
s.add_development_dependency "rspec-rails", "~> 2.7.0"
|
25
|
+
s.add_development_dependency "nokogiri", ">= 1.5.0"
|
26
|
+
s.add_development_dependency "capybara", ">= 1.0.1"
|
27
|
+
end
|
@@ -0,0 +1,481 @@
|
|
1
|
+
/*
|
2
|
+
BestInPlaceMongoid (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 BestInPlaceMongoidEditor(e) {
|
23
|
+
this.element = jQuery(e);
|
24
|
+
this.initOptions();
|
25
|
+
this.bindForm();
|
26
|
+
this.initNil();
|
27
|
+
$(this.activator).bind('click', {editor: this}, this.clickHandler);
|
28
|
+
}
|
29
|
+
|
30
|
+
BestInPlaceMongoidEditor.prototype = {
|
31
|
+
// Public Interface Functions //////////////////////////////////////////////
|
32
|
+
|
33
|
+
activate : function() {
|
34
|
+
var elem = this.isNil ? "" : this.element.html();
|
35
|
+
this.oldValue = elem;
|
36
|
+
$(this.activator).unbind("click", this.clickHandler);
|
37
|
+
this.activateForm();
|
38
|
+
},
|
39
|
+
|
40
|
+
abort : function() {
|
41
|
+
if (this.isNil) this.element.html(this.nil);
|
42
|
+
else this.element.html(this.oldValue);
|
43
|
+
$(this.activator).bind('click', {editor: this}, this.clickHandler);
|
44
|
+
},
|
45
|
+
|
46
|
+
update : function() {
|
47
|
+
var editor = this;
|
48
|
+
if (this.formType in {"input":1, "textarea":1} && this.getValue() == this.oldValue)
|
49
|
+
{ // Avoid request if no change is made
|
50
|
+
this.abort();
|
51
|
+
return true;
|
52
|
+
}
|
53
|
+
this.isNil = false;
|
54
|
+
editor.ajax({
|
55
|
+
"type" : "post",
|
56
|
+
"dataType" : "text",
|
57
|
+
"data" : editor.requestData(),
|
58
|
+
"success" : function(data){ editor.loadSuccessCallback(data); },
|
59
|
+
"error" : function(request, error){ editor.loadErrorCallback(request, error); }
|
60
|
+
});
|
61
|
+
if (this.formType == "select") {
|
62
|
+
var value = this.getValue();
|
63
|
+
$.each(this.values, function(i, v) {
|
64
|
+
if (value == v[0]) {
|
65
|
+
editor.element.html(v[1]);
|
66
|
+
}
|
67
|
+
}
|
68
|
+
);
|
69
|
+
} else if (this.formType == "checkbox") {
|
70
|
+
editor.element.html(this.getValue() ? this.values[1] : this.values[0]);
|
71
|
+
} else {
|
72
|
+
editor.element.html(this.getValue() != "" ? this.getValue() : this.nil);
|
73
|
+
}
|
74
|
+
},
|
75
|
+
|
76
|
+
activateForm : function() {
|
77
|
+
alert("The form was not properly initialized. activateForm is unbound");
|
78
|
+
},
|
79
|
+
|
80
|
+
// Helper Functions ////////////////////////////////////////////////////////
|
81
|
+
|
82
|
+
initOptions : function() {
|
83
|
+
// Try parent supplied info
|
84
|
+
var self = this;
|
85
|
+
self.element.parents().each(function(){
|
86
|
+
self.url = self.url || jQuery(this).attr("data-url");
|
87
|
+
self.collection = self.collection || jQuery(this).attr("data-collection");
|
88
|
+
self.formType = self.formType || jQuery(this).attr("data-type");
|
89
|
+
self.objectName = self.objectName || jQuery(this).attr("data-object");
|
90
|
+
self.attributeName = self.attributeName || jQuery(this).attr("data-attribute");
|
91
|
+
self.nil = self.nil || jQuery(this).attr("data-nil");
|
92
|
+
self.inner_class = self.inner_class || jQuery(this).attr("data-inner-class");
|
93
|
+
self.html_attrs = self.html_attrs || jQuery(this).attr("data-html-attrs");
|
94
|
+
});
|
95
|
+
|
96
|
+
// Try Rails-id based if parents did not explicitly supply something
|
97
|
+
self.element.parents().each(function(){
|
98
|
+
var res = this.id.match(/^(\w+)_(\d+)$/i);
|
99
|
+
if (res) {
|
100
|
+
self.objectName = self.objectName || res[1];
|
101
|
+
}
|
102
|
+
});
|
103
|
+
|
104
|
+
// Load own attributes (overrides all others)
|
105
|
+
self.url = self.element.attr("data-url") || self.url || document.location.pathname;
|
106
|
+
self.collection = self.element.attr("data-collection") || self.collection;
|
107
|
+
self.formType = self.element.attr("data-type") || self.formtype || "input";
|
108
|
+
self.objectName = self.element.attr("data-object") || self.objectName;
|
109
|
+
self.attributeName = self.element.attr("data-attribute") || self.attributeName;
|
110
|
+
self.activator = self.element.attr("data-activator") || self.element;
|
111
|
+
self.nil = self.element.attr("data-nil") || self.nil || "-";
|
112
|
+
self.inner_class = self.element.attr("data-inner-class") || self.inner_class || null;
|
113
|
+
self.html_attrs = self.element.attr("data-html-attrs") || self.html_attrs;
|
114
|
+
|
115
|
+
if (!self.element.attr("data-sanitize")) {
|
116
|
+
self.sanitize = true;
|
117
|
+
}
|
118
|
+
else {
|
119
|
+
self.sanitize = (self.element.attr("data-sanitize") == "true");
|
120
|
+
}
|
121
|
+
|
122
|
+
if ((self.formType == "select" || self.formType == "checkbox") && self.collection !== null)
|
123
|
+
{
|
124
|
+
self.values = jQuery.parseJSON(self.collection);
|
125
|
+
}
|
126
|
+
},
|
127
|
+
|
128
|
+
bindForm : function() {
|
129
|
+
this.activateForm = BestInPlaceMongoidEditor.forms[this.formType].activateForm;
|
130
|
+
this.getValue = BestInPlaceMongoidEditor.forms[this.formType].getValue;
|
131
|
+
},
|
132
|
+
|
133
|
+
initNil: function() {
|
134
|
+
if (this.element.html() == "")
|
135
|
+
{
|
136
|
+
this.isNil = true
|
137
|
+
this.element.html(this.nil)
|
138
|
+
}
|
139
|
+
},
|
140
|
+
|
141
|
+
getValue : function() {
|
142
|
+
alert("The form was not properly initialized. getValue is unbound");
|
143
|
+
},
|
144
|
+
|
145
|
+
// Trim and Strips HTML from text
|
146
|
+
sanitizeValue : function(s) {
|
147
|
+
if (this.sanitize)
|
148
|
+
{
|
149
|
+
var tmp = document.createElement("DIV");
|
150
|
+
tmp.innerHTML = s;
|
151
|
+
s = tmp.textContent || tmp.innerText;
|
152
|
+
}
|
153
|
+
return jQuery.trim(s);
|
154
|
+
},
|
155
|
+
|
156
|
+
/* Generate the data sent in the POST request */
|
157
|
+
requestData : function() {
|
158
|
+
// To prevent xss attacks, a csrf token must be defined as a meta attribute
|
159
|
+
csrf_token = $('meta[name=csrf-token]').attr('content');
|
160
|
+
csrf_param = $('meta[name=csrf-param]').attr('content');
|
161
|
+
|
162
|
+
var data = "_method=put";
|
163
|
+
data += "&" + this.objectName + '[' + this.attributeName + ']=' + encodeURIComponent(this.getValue());
|
164
|
+
|
165
|
+
if (csrf_param !== undefined && csrf_token !== undefined) {
|
166
|
+
data += "&" + csrf_param + "=" + encodeURIComponent(csrf_token);
|
167
|
+
}
|
168
|
+
return data;
|
169
|
+
},
|
170
|
+
|
171
|
+
ajax : function(options) {
|
172
|
+
options.url = this.url;
|
173
|
+
options.beforeSend = function(xhr){ xhr.setRequestHeader("Accept", "application/json"); };
|
174
|
+
return jQuery.ajax(options);
|
175
|
+
},
|
176
|
+
|
177
|
+
// Handlers ////////////////////////////////////////////////////////////////
|
178
|
+
|
179
|
+
loadSuccessCallback : function(data) {
|
180
|
+
this.element.html(data[this.objectName]);
|
181
|
+
this.element.trigger($.Event("ajax:success"), data);
|
182
|
+
|
183
|
+
// Binding back after being clicked
|
184
|
+
$(this.activator).bind('click', {editor: this}, this.clickHandler);
|
185
|
+
},
|
186
|
+
|
187
|
+
loadErrorCallback : function(request, error) {
|
188
|
+
this.element.html(this.oldValue);
|
189
|
+
|
190
|
+
// Display all error messages from server side validation
|
191
|
+
$.each(jQuery.parseJSON(request.responseText), function(index, value) {
|
192
|
+
if( typeof(value) == "object") {value = index + " " + value.toString(); }
|
193
|
+
var container = $("<span class='flash-error'></span>").html(value);
|
194
|
+
container.purr();
|
195
|
+
});
|
196
|
+
|
197
|
+
// Binding back after being clicked
|
198
|
+
$(this.activator).bind('click', {editor: this}, this.clickHandler);
|
199
|
+
},
|
200
|
+
|
201
|
+
clickHandler : function(event) {
|
202
|
+
event.data.editor.activate();
|
203
|
+
},
|
204
|
+
|
205
|
+
setHtmlAttributes : function() {
|
206
|
+
var formField = this.element.find(this.formType);
|
207
|
+
var attrs = jQuery.parseJSON(this.html_attrs);
|
208
|
+
for(var key in attrs){
|
209
|
+
formField.attr(key, attrs[key]);
|
210
|
+
}
|
211
|
+
}
|
212
|
+
};
|
213
|
+
|
214
|
+
|
215
|
+
BestInPlaceMongoidEditor.forms = {
|
216
|
+
"input" : {
|
217
|
+
activateForm : function() {
|
218
|
+
var output = '<form class="form_in_place" action="javascript:void(0)" style="display:inline;">';
|
219
|
+
output += '<input type="text" name="'+ this.attributeName + '" value="' + this.sanitizeValue(this.oldValue) + '"';
|
220
|
+
if (this.inner_class != null) {
|
221
|
+
output += ' class="' + this.inner_class + '"';
|
222
|
+
}
|
223
|
+
output += '></form>'
|
224
|
+
this.element.html(output);
|
225
|
+
this.setHtmlAttributes();
|
226
|
+
this.element.find('input')[0].select();
|
227
|
+
this.element.find("form").bind('submit', {editor: this}, BestInPlaceMongoidEditor.forms.input.submitHandler);
|
228
|
+
this.element.find("input").bind('blur', {editor: this}, BestInPlaceMongoidEditor.forms.input.inputBlurHandler);
|
229
|
+
this.element.find("input").bind('keyup', {editor: this}, BestInPlaceMongoidEditor.forms.input.keyupHandler);
|
230
|
+
},
|
231
|
+
|
232
|
+
getValue : function() {
|
233
|
+
return this.sanitizeValue(this.element.find("input").val());
|
234
|
+
},
|
235
|
+
|
236
|
+
inputBlurHandler : function(event) {
|
237
|
+
event.data.editor.update();
|
238
|
+
},
|
239
|
+
|
240
|
+
submitHandler : function(event) {
|
241
|
+
event.data.editor.update();
|
242
|
+
},
|
243
|
+
|
244
|
+
keyupHandler : function(event) {
|
245
|
+
if (event.keyCode == 27) {
|
246
|
+
event.data.editor.abort();
|
247
|
+
}
|
248
|
+
}
|
249
|
+
},
|
250
|
+
|
251
|
+
"select" : {
|
252
|
+
activateForm : function() {
|
253
|
+
var output = "<form action='javascript:void(0)' style='display:inline;'><select>";
|
254
|
+
var selected = "";
|
255
|
+
var oldValue = this.oldValue;
|
256
|
+
$.each(this.values, function(index, value) {
|
257
|
+
selected = (value[1] == oldValue ? "selected='selected'" : "");
|
258
|
+
output += "<option value='" + value[0] + "' " + selected + ">" + value[1] + "</option>";
|
259
|
+
});
|
260
|
+
output += "</select></form>";
|
261
|
+
this.element.html(output);
|
262
|
+
this.setHtmlAttributes();
|
263
|
+
this.element.find("select").bind('change', {editor: this}, BestInPlaceMongoidEditor.forms.select.blurHandler);
|
264
|
+
this.element.find("select").bind('blur', {editor: this}, BestInPlaceMongoidEditor.forms.select.blurHandler);
|
265
|
+
this.element.find("select").bind('keyup', {editor: this}, BestInPlaceMongoidEditor.forms.select.keyupHandler);
|
266
|
+
this.element.find("select")[0].focus();
|
267
|
+
},
|
268
|
+
|
269
|
+
getValue : function() {
|
270
|
+
return this.sanitizeValue(this.element.find("select").val());
|
271
|
+
},
|
272
|
+
|
273
|
+
blurHandler : function(event) {
|
274
|
+
event.data.editor.update();
|
275
|
+
},
|
276
|
+
|
277
|
+
keyupHandler : function(event) {
|
278
|
+
if (event.keyCode == 27) event.data.editor.abort();
|
279
|
+
}
|
280
|
+
},
|
281
|
+
|
282
|
+
"checkbox" : {
|
283
|
+
activateForm : function() {
|
284
|
+
var newValue = Boolean(this.oldValue != this.values[1]);
|
285
|
+
var output = newValue ? this.values[1] : this.values[0];
|
286
|
+
this.element.html(output);
|
287
|
+
this.setHtmlAttributes();
|
288
|
+
this.update();
|
289
|
+
},
|
290
|
+
|
291
|
+
getValue : function() {
|
292
|
+
return Boolean(this.element.html() == this.values[1]);
|
293
|
+
}
|
294
|
+
},
|
295
|
+
|
296
|
+
"textarea" : {
|
297
|
+
activateForm : function() {
|
298
|
+
// grab width and height of text
|
299
|
+
width = this.element.css('width');
|
300
|
+
height = this.element.css('height');
|
301
|
+
|
302
|
+
// construct the form
|
303
|
+
var output = '<form action="javascript:void(0)" style="display:inline;"><textarea>';
|
304
|
+
output += this.sanitizeValue(this.oldValue);
|
305
|
+
output += '</textarea></form>';
|
306
|
+
this.element.html(output);
|
307
|
+
this.setHtmlAttributes();
|
308
|
+
|
309
|
+
// set width and height of textarea
|
310
|
+
jQuery(this.element.find("textarea")[0]).css({ 'min-width': width, 'min-height': height });
|
311
|
+
jQuery(this.element.find("textarea")[0]).elastic();
|
312
|
+
|
313
|
+
this.element.find("textarea")[0].focus();
|
314
|
+
this.element.find("textarea").bind('blur', {editor: this}, BestInPlaceMongoidEditor.forms.textarea.blurHandler);
|
315
|
+
this.element.find("textarea").bind('keyup', {editor: this}, BestInPlaceMongoidEditor.forms.textarea.keyupHandler);
|
316
|
+
},
|
317
|
+
|
318
|
+
getValue : function() {
|
319
|
+
return this.sanitizeValue(this.element.find("textarea").val());
|
320
|
+
},
|
321
|
+
|
322
|
+
blurHandler : function(event) {
|
323
|
+
event.data.editor.update();
|
324
|
+
},
|
325
|
+
|
326
|
+
keyupHandler : function(event) {
|
327
|
+
if (event.keyCode == 27) {
|
328
|
+
BestInPlaceMongoidEditor.forms.textarea.abort(event.data.editor);
|
329
|
+
}
|
330
|
+
},
|
331
|
+
|
332
|
+
abort : function(editor) {
|
333
|
+
if (confirm("Are you sure you want to discard your changes?")) {
|
334
|
+
editor.abort();
|
335
|
+
}
|
336
|
+
}
|
337
|
+
}
|
338
|
+
};
|
339
|
+
|
340
|
+
jQuery.fn.best_in_place = function() {
|
341
|
+
this.each(function(){
|
342
|
+
if (!jQuery(this).data('bestInPlaceEditor')) {
|
343
|
+
jQuery(this).data('bestInPlaceEditor', new BestInPlaceMongoidEditor(this));
|
344
|
+
}
|
345
|
+
});
|
346
|
+
return this;
|
347
|
+
};
|
348
|
+
|
349
|
+
|
350
|
+
|
351
|
+
/**
|
352
|
+
* @name Elastic
|
353
|
+
* @descripton Elastic is Jquery plugin that grow and shrink your textareas automaticliy
|
354
|
+
* @version 1.6.5
|
355
|
+
* @requires Jquery 1.2.6+
|
356
|
+
*
|
357
|
+
* @author Jan Jarfalk
|
358
|
+
* @author-email jan.jarfalk@unwrongest.com
|
359
|
+
* @author-website http://www.unwrongest.com
|
360
|
+
*
|
361
|
+
* @licens MIT License - http://www.opensource.org/licenses/mit-license.php
|
362
|
+
*/
|
363
|
+
|
364
|
+
(function(jQuery){
|
365
|
+
jQuery.fn.extend({
|
366
|
+
elastic: function() {
|
367
|
+
// We will create a div clone of the textarea
|
368
|
+
// by copying these attributes from the textarea to the div.
|
369
|
+
var mimics = [
|
370
|
+
'paddingTop',
|
371
|
+
'paddingRight',
|
372
|
+
'paddingBottom',
|
373
|
+
'paddingLeft',
|
374
|
+
'fontSize',
|
375
|
+
'lineHeight',
|
376
|
+
'fontFamily',
|
377
|
+
'width',
|
378
|
+
'fontWeight'];
|
379
|
+
|
380
|
+
return this.each( function() {
|
381
|
+
|
382
|
+
// Elastic only works on textareas
|
383
|
+
if ( this.type != 'textarea' ) {
|
384
|
+
return false;
|
385
|
+
}
|
386
|
+
|
387
|
+
var $textarea = jQuery(this),
|
388
|
+
$twin = jQuery('<div />').css({'position': 'absolute','display':'none','word-wrap':'break-word'}),
|
389
|
+
lineHeight = parseInt($textarea.css('line-height'),10) || parseInt($textarea.css('font-size'),'10'),
|
390
|
+
minheight = parseInt($textarea.css('height'),10) || lineHeight*3,
|
391
|
+
maxheight = parseInt($textarea.css('max-height'),10) || Number.MAX_VALUE,
|
392
|
+
goalheight = 0,
|
393
|
+
i = 0;
|
394
|
+
|
395
|
+
// Opera returns max-height of -1 if not set
|
396
|
+
if (maxheight < 0) { maxheight = Number.MAX_VALUE; }
|
397
|
+
|
398
|
+
// Append the twin to the DOM
|
399
|
+
// We are going to meassure the height of this, not the textarea.
|
400
|
+
$twin.appendTo($textarea.parent());
|
401
|
+
|
402
|
+
// Copy the essential styles (mimics) from the textarea to the twin
|
403
|
+
var i = mimics.length;
|
404
|
+
while(i--){
|
405
|
+
$twin.css(mimics[i].toString(),$textarea.css(mimics[i].toString()));
|
406
|
+
}
|
407
|
+
|
408
|
+
|
409
|
+
// Sets a given height and overflow state on the textarea
|
410
|
+
function setHeightAndOverflow(height, overflow){
|
411
|
+
curratedHeight = Math.floor(parseInt(height,10));
|
412
|
+
if($textarea.height() != curratedHeight){
|
413
|
+
$textarea.css({'height': curratedHeight + 'px','overflow':overflow});
|
414
|
+
|
415
|
+
}
|
416
|
+
}
|
417
|
+
|
418
|
+
|
419
|
+
// This function will update the height of the textarea if necessary
|
420
|
+
function update() {
|
421
|
+
|
422
|
+
// Get curated content from the textarea.
|
423
|
+
var textareaContent = $textarea.val().replace(/&/g,'&').replace(/ /g, ' ').replace(/<|>/g, '>').replace(/\n/g, '<br />');
|
424
|
+
|
425
|
+
// Compare curated content with curated twin.
|
426
|
+
var twinContent = $twin.html().replace(/<br>/ig,'<br />');
|
427
|
+
|
428
|
+
if(textareaContent+' ' != twinContent){
|
429
|
+
|
430
|
+
// Add an extra white space so new rows are added when you are at the end of a row.
|
431
|
+
$twin.html(textareaContent+' ');
|
432
|
+
|
433
|
+
// Change textarea height if twin plus the height of one line differs more than 3 pixel from textarea height
|
434
|
+
if(Math.abs($twin.height() + lineHeight - $textarea.height()) > 3){
|
435
|
+
|
436
|
+
var goalheight = $twin.height()+lineHeight;
|
437
|
+
if(goalheight >= maxheight) {
|
438
|
+
setHeightAndOverflow(maxheight,'auto');
|
439
|
+
} else if(goalheight <= minheight) {
|
440
|
+
setHeightAndOverflow(minheight,'hidden');
|
441
|
+
} else {
|
442
|
+
setHeightAndOverflow(goalheight,'hidden');
|
443
|
+
}
|
444
|
+
|
445
|
+
}
|
446
|
+
|
447
|
+
}
|
448
|
+
|
449
|
+
}
|
450
|
+
|
451
|
+
// Hide scrollbars
|
452
|
+
$textarea.css({'overflow':'hidden'});
|
453
|
+
|
454
|
+
// Update textarea size on keyup, change, cut and paste
|
455
|
+
$textarea.bind('keyup change cut paste', function(){
|
456
|
+
update();
|
457
|
+
});
|
458
|
+
|
459
|
+
// Compact textarea on blur
|
460
|
+
// Lets animate this....
|
461
|
+
$textarea.bind('blur',function(){
|
462
|
+
if($twin.height() < maxheight){
|
463
|
+
if($twin.height() > minheight) {
|
464
|
+
$textarea.height($twin.height());
|
465
|
+
} else {
|
466
|
+
$textarea.height(minheight);
|
467
|
+
}
|
468
|
+
}
|
469
|
+
});
|
470
|
+
|
471
|
+
// And this line is to catch the browser paste event
|
472
|
+
$textarea.live('input paste',function(e){ setTimeout( update, 250); });
|
473
|
+
|
474
|
+
// Run update once when elastic is initialized
|
475
|
+
update();
|
476
|
+
|
477
|
+
});
|
478
|
+
|
479
|
+
}
|
480
|
+
});
|
481
|
+
})(jQuery);
|