formtastic_masked_input 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Gabriel Sobrinho
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.textile ADDED
@@ -0,0 +1,60 @@
1
+ h1. formtastic_masked_input
2
+
3
+ Sometimes you want to mask your fields like phones, zipcodes or something like that.
4
+
5
+ This plugin will do it for you if you are using formtastic.
6
+
7
+ Note: the javascript file depends on Prototype 1.6.1, just update your prototype.js. Rails 2 uses Prototype version 1.6.0.3
8
+
9
+ h2. Usage
10
+
11
+ <pre>$ ruby script/plugin install git://github.com/sobrinho/formtastic_masked_input.git
12
+ $ ruby script/generate formtastic_masked_input</pre>
13
+
14
+ Include prototype.js and masked_input.js into layout:
15
+
16
+ <pre><%= javascript_include_tag 'prototype', 'masked_input' %></pre>
17
+
18
+ And update your form field:
19
+
20
+ <pre><% semantic_form_for @person do |f| %>
21
+ <% f.inputs do %>
22
+ <%= f.input :name %>
23
+ <%= f.input :phone, :as => :masked, :mask => :phone %>
24
+ <% end %>
25
+
26
+ <%= f.buttons %>
27
+ <% end %></pre>
28
+
29
+ h2. Masks
30
+
31
+ The available masks are made for Brazil:
32
+
33
+ * phone: (99) 9999-9999
34
+ * cpf: 999.999.999-99
35
+ * cnpj: 99.999.999/9999-99
36
+ * date: 99/99/9999
37
+ * cep: 99999-999
38
+ * time: 99:99
39
+
40
+ If you want to add a custom mask, you can edit masked_input.js around line 301 (preferred) or add directly into your layout:
41
+
42
+ <pre><%= javascript_include_tag 'prototype', 'masked_input' %>
43
+ <script type="text/javascript">
44
+ //<![CDATA[
45
+ Object.extend(MaskedInput.masks, {
46
+ 'version': '9.9.9'
47
+ });
48
+ //]]>
49
+ </script></pre>
50
+
51
+ h2. Credits
52
+
53
+ Special thanks to Justin French for formtastic and Bjarte K. Vebjørnsen for MaskedInput port for Prototype.
54
+
55
+ h2. TODO
56
+
57
+ * Make tests for plugin
58
+ * Make tests for MaskedInput
59
+
60
+ Copyright (c) 2010 Gabriel Sobrinho, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ begin
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |gemspec|
8
+ gemspec.name = "formtastic_masked_input"
9
+ gemspec.summary = "MaskedInput for formtastic"
10
+ gemspec.email = "gabriel.sobrinho@gmail.com"
11
+ gemspec.homepage = "http://github.com/sobrinho/formtastic_masked_input"
12
+ gemspec.authors = ["Gabriel Sobrinho"]
13
+ end
14
+
15
+ Jeweler::GemcutterTasks.new
16
+ rescue LoadError
17
+ puts "Jeweler not available. Install it with: gem install jeweler"
18
+ end
19
+
20
+ desc 'Generate documentation for the formtastic_masked_input plugin.'
21
+ Rake::RDocTask.new(:rdoc) do |rdoc|
22
+ rdoc.rdoc_dir = 'rdoc'
23
+ rdoc.title = 'FormtasticMaskedInput'
24
+ rdoc.options << '--line-numbers' << '--inline-source'
25
+ rdoc.rdoc_files.include('README')
26
+ rdoc.rdoc_files.include('lib/**/*.rb')
27
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,9 @@
1
+ Description:
2
+ The formtastic_masked_input generator creates a javascript file called
3
+ masked_input.js to the public path which will be used on formtastic.
4
+
5
+ Example:
6
+ ./script/generate formtastic_masked_input
7
+
8
+ This will create:
9
+ public/javascripts/masked_input.js
@@ -0,0 +1,8 @@
1
+ class FormtasticMaskedInputGenerator < Rails::Generator::Base
2
+ def manifest
3
+ record do |m|
4
+ m.directory "public/javascripts"
5
+ m.file "masked_input.js", "public/javascripts/masked_input.js"
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,312 @@
1
+ /*
2
+ Masked Input plugin for prototype ported from jQuery
3
+ Bjarte K. Vebjørnsen <bjartekv at gmail dot com>
4
+
5
+ Note that the onchange event isn't fired for masked inputs. It won't fire unless event.simulate.js is available.
6
+
7
+ Requires: Prototype >= 1.6.1
8
+ Optional: event.simulate.js from http://github.com/kangax/protolicious to trigger native change event.
9
+
10
+ Tested on windows IE6, IE7, IE8, Opera 9.6, Chrome 3, FireFox 3, Safari 3
11
+
12
+ Masked Input plugin for jQuery
13
+ Copyright (c) 2007-2009 Josh Bush (digitalbush.com)
14
+ Licensed under the MIT license (http://digitalbush.com/projects/masked-input-plugin/#license)
15
+ Version: 1.2.2 (03/09/2009 22:39:06)
16
+ */
17
+
18
+ (function() {
19
+ var pasteEventName = (Prototype.Browser.IE ? 'paste' : 'input'),
20
+ iPhone = (window.orientation != undefined);
21
+
22
+ if(typeof(Prototype) == "undefined")
23
+ throw "MaskedInput requires Prototype to be loaded.";
24
+
25
+ if(Prototype.Version < '1.6.1')
26
+ throw "MaskedInput requires Prototype 1.6.1 or greater.";
27
+
28
+ Element.addMethods({
29
+ caret: function(element, begin, end) {
30
+ if (element.length == 0) return;
31
+ if (typeof begin == 'number') {
32
+ end = (typeof end == 'number') ? end : begin;
33
+ if (element.setSelectionRange) {
34
+ element.focus();
35
+ element.setSelectionRange(begin, end);
36
+ } else if (element.createTextRange) {
37
+ var range = element.createTextRange();
38
+ range.collapse(true);
39
+ range.moveEnd('character', end);
40
+ range.moveStart('character', begin);
41
+ range.select();
42
+ }
43
+ } else {
44
+ if (element.setSelectionRange) {
45
+ begin = element.selectionStart;
46
+ end = element.selectionEnd;
47
+ } else if (document.selection && document.selection.createRange) {
48
+ var range = document.selection.createRange();
49
+ begin = 0 - range.duplicate().moveStart('character', -100000);
50
+ end = begin + range.text.length;
51
+ }
52
+ return { begin: begin, end: end };
53
+ }
54
+ }
55
+ });
56
+
57
+ MaskedInput = Class.create({
58
+ initialize: function(selector, mask, settings) {
59
+ this.elements = $$(selector);
60
+ this.mask(mask, settings);
61
+ },
62
+ unmask: function() {
63
+ this.elements.each(function(el) {
64
+ el.fire("mask:unmask");
65
+ });
66
+ return this;
67
+ },
68
+ mask: function (mask, settings) {
69
+ if (!mask && this.elements.length > 0) {
70
+ var input = $(this.elements[0]);
71
+ var tests = input.retrieve("tests");
72
+ return $A(input.retrieve("buffer")).map(function(c, i) {
73
+ return tests[i] ? c : null;
74
+ }).join('');
75
+ }
76
+ settings = Object.extend({
77
+ placeholder: "_",
78
+ completed: null
79
+ }, settings || {});
80
+
81
+ var defs = MaskedInput.definitions;
82
+ var tests = [];
83
+ var partialPosition = mask.length;
84
+ var firstNonMaskPos = null;
85
+ var len = mask.length;
86
+
87
+ $A(mask.split("")).each(function(c, i) {
88
+ if (c == '?') {
89
+ len--;
90
+ partialPosition = i;
91
+ } else if (defs[c]) {
92
+ tests.push(new RegExp(defs[c]));
93
+ if(firstNonMaskPos==null)
94
+ firstNonMaskPos = tests.length - 1;
95
+ } else {
96
+ tests.push(null);
97
+ }
98
+ });
99
+
100
+ this.elements.each(function(el) {
101
+
102
+ var input = $(el);
103
+
104
+ var buffer = $A(mask.split("")).map( function(c, i) { if (c != '?') return defs[c] ? settings.placeholder : c });
105
+
106
+ var ignore = false; //Variable for ignoring control keys
107
+ var focusText = input.getValue();
108
+
109
+ input.store("buffer", buffer).store("tests", tests);
110
+
111
+ function seekNext(pos) {
112
+ while (++pos < len && !tests[pos]);
113
+ return pos;
114
+ };
115
+
116
+ function shiftL(pos) {
117
+ while (!tests[pos] && --pos >= 0);
118
+ for (var i = pos; i < len; i++) {
119
+ if (tests[i]) {
120
+ buffer[i] = settings.placeholder;
121
+ var j = seekNext(i);
122
+ if (j < len && tests[i].test(buffer[j])) {
123
+ buffer[i] = buffer[j];
124
+ } else
125
+ break;
126
+ }
127
+ }
128
+ writeBuffer();
129
+ input.caret(Math.max(firstNonMaskPos, pos));
130
+ };
131
+
132
+ function shiftR(pos) {
133
+ for (var i = pos, c = settings.placeholder; i < len; i++) {
134
+ if (tests[i]) {
135
+ var j = seekNext(i);
136
+ var t = buffer[i];
137
+ buffer[i] = c;
138
+ if (j < len && tests[j].test(t))
139
+ c = t;
140
+ else
141
+ break;
142
+ }
143
+ }
144
+ };
145
+
146
+ function keydownEvent(e) {
147
+ var pos = input.caret();
148
+ var k = e.keyCode;
149
+ ignore = (k < 16 || (k > 16 && k < 32) || (k > 32 && k < 41));
150
+ //delete selection before proceeding
151
+ if ((pos.begin - pos.end) != 0 && (!ignore || k == 8 || k == 46))
152
+ clearBuffer(pos.begin, pos.end);
153
+
154
+ //backspace, delete, and escape get special treatment
155
+ if (k == 8 || k == 46 || (iPhone && k == 127)) {//backspace/delete
156
+ shiftL(pos.begin + (k == 46 ? 0 : -1));
157
+ e.stop();
158
+ } else if (k == 27) {//escape
159
+ input.setValue(focusText);
160
+ input.caret(0, checkVal());
161
+ e.stop();
162
+ }
163
+ };
164
+
165
+ function keypressEvent(e) {
166
+ if (ignore) {
167
+ ignore = false;
168
+ //Fixes Mac FF bug on backspace
169
+ return (e.keyCode == 8) ? false : null;
170
+ }
171
+ e = e || window.event;
172
+ var k = e.charCode || e.keyCode || e.which;
173
+ var pos = input.caret();
174
+ if (e.ctrlKey || e.altKey || e.metaKey) {//Ignore
175
+ return true;
176
+ } else if ((k >= 32 && k <= 125) || k > 186) {//typeable characters
177
+ var p = seekNext(pos.begin - 1);
178
+ if (p < len) {
179
+ var c = String.fromCharCode(k);
180
+ if (tests[p].test(c)) {
181
+ shiftR(p);
182
+ buffer[p] = c;
183
+ writeBuffer();
184
+ var next = seekNext(p);
185
+ input.caret(next);
186
+ if (settings.completed && next == len)
187
+ settings.completed.call(input);
188
+ }
189
+ }
190
+ }
191
+ e.stop();
192
+ };
193
+
194
+ function blurEvent(e) {
195
+ checkVal();
196
+ if (input.getValue() != focusText) {
197
+ // since the native change event doesn't fire we have to fire it ourselves
198
+ // since Event.fire doesn't support native events we're using Event.simulate if available
199
+ if (window.Event.simulate) {
200
+ input.simulate('change');
201
+ }
202
+ }
203
+ };
204
+
205
+ function focusEvent(e) {
206
+ focusText = input.getValue();
207
+ var pos = checkVal();
208
+ writeBuffer();
209
+
210
+ setTimeout(function() {
211
+ if (pos == mask.length)
212
+ input.caret(0, pos);
213
+ else
214
+ input.caret(pos);
215
+ }, 0);
216
+ };
217
+
218
+ function pasteEvent(e) {
219
+ setTimeout(function() { input.caret(checkVal(true)); }, 0);
220
+ };
221
+
222
+ function clearBuffer(start, end) {
223
+ for (var i = start; i < end && i < len; i++) {
224
+ if (tests[i])
225
+ buffer[i] = settings.placeholder;
226
+ }
227
+ };
228
+
229
+ function writeBuffer() { return input.setValue(buffer.join('')).getValue(); };
230
+
231
+ function checkVal(allow) {
232
+ //try to place characters where they belong
233
+ var test = input.getValue();
234
+ var lastMatch = -1;
235
+ for (var i = 0, pos = 0; i < len; i++) {
236
+ if (tests[i]) {
237
+ buffer[i] = settings.placeholder;
238
+ while (pos++ < test.length) {
239
+ var c = test.charAt(pos - 1);
240
+ if (tests[i].test(c)) {
241
+ buffer[i] = c;
242
+ lastMatch = i;
243
+ break;
244
+ }
245
+ }
246
+ if (pos > test.length)
247
+ break;
248
+ } else if (buffer[i] == test[pos] && i!=partialPosition) {
249
+ pos++;
250
+ lastMatch = i;
251
+ }
252
+ }
253
+ if (!allow && lastMatch + 1 < partialPosition) {
254
+ input.setValue("");
255
+ clearBuffer(0, len);
256
+ } else if (allow || lastMatch + 1 >= partialPosition) {
257
+ writeBuffer();
258
+ if (!allow) input.setValue(input.getValue().substring(0, lastMatch + 1));
259
+ }
260
+ return (partialPosition ? i : firstNonMaskPos);
261
+ };
262
+
263
+ if (!input.readAttribute("readonly"))
264
+ input
265
+ .observe("mask:unmask", function() {
266
+ input
267
+ .store("buffer",undefined)
268
+ .store("tests",undefined)
269
+ .stopObserving("mask:unmask")
270
+ .stopObserving("focus", focusEvent)
271
+ .stopObserving("blur", blurEvent)
272
+ .stopObserving("keydown", keydownEvent)
273
+ .stopObserving("keypress", keypressEvent)
274
+ .stopObserving(pasteEventName, pasteEvent);
275
+ })
276
+ .observe("focus", focusEvent)
277
+ .observe("blur", blurEvent)
278
+ .observe("keydown", keydownEvent)
279
+ .observe("keypress", keypressEvent)
280
+ .observe(pasteEventName, pasteEvent);
281
+
282
+ checkVal(); //Perform initial check for existing values
283
+ });
284
+ return this;
285
+ }
286
+ });
287
+
288
+ Object.extend(MaskedInput,{
289
+ definitions: {
290
+ '9': "[0-9]",
291
+ 'a': "[A-Za-z]",
292
+ '*': "[A-Za-z0-9]"
293
+ },
294
+
295
+ masks: {
296
+ 'phone': '(99) 9999-9999',
297
+ 'cpf' : '999.999.999-99',
298
+ 'cnpj' : '99.999.999/9999-99',
299
+ 'date' : '99/99/9999',
300
+ 'cep' : '99999-999',
301
+ 'time' : '99:99'
302
+ },
303
+
304
+ apply: function () {
305
+ Object.keys(MaskedInput.masks).each(function (name) {
306
+ new MaskedInput('input.mask.' + name, MaskedInput.masks[name]);
307
+ });
308
+ }
309
+ });
310
+
311
+ $(document).observe('dom:loaded', MaskedInput.apply.bind(MaskedInput));
312
+ })();
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'formtastic_masked_input'
@@ -0,0 +1,15 @@
1
+ module FormtasticMaskedInput
2
+ def masked_input(method, options)
3
+ mask = options.delete(:mask)
4
+
5
+ raise ArgumentError, 'you must specify a mask' unless mask
6
+
7
+ options[:input_html] ||= {}
8
+ options[:input_html][:class] ||= ''
9
+ options[:input_html][:class] = "#{options[:input_html][:class]} mask #{mask}".strip
10
+
11
+ string_input(method, options)
12
+ end
13
+ end
14
+
15
+ Formtastic::SemanticFormBuilder.send(:include, FormtasticMaskedInput)
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :active_record_masked_input do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: formtastic_masked_input
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Gabriel Sobrinho
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-02-04 00:00:00 -02:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: gabriel.sobrinho@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.textile
24
+ files:
25
+ - MIT-LICENSE
26
+ - README.textile
27
+ - Rakefile
28
+ - VERSION
29
+ - generators/formtastic_masked_input/USAGE
30
+ - generators/formtastic_masked_input/formtastic_masked_input_generator.rb
31
+ - generators/formtastic_masked_input/templates/masked_input.js
32
+ - init.rb
33
+ - lib/formtastic_masked_input.rb
34
+ - tasks/formtastic_masked_input_tasks.rake
35
+ has_rdoc: true
36
+ homepage: http://github.com/sobrinho/formtastic_masked_input
37
+ licenses: []
38
+
39
+ post_install_message:
40
+ rdoc_options:
41
+ - --charset=UTF-8
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: "0"
49
+ version:
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ requirements: []
57
+
58
+ rubyforge_project:
59
+ rubygems_version: 1.3.5
60
+ signing_key:
61
+ specification_version: 3
62
+ summary: MaskedInput for formtastic
63
+ test_files: []
64
+