formtastic_masked_input 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+