spina-openinghours 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +3 -0
- data/Rakefile +34 -0
- data/app/assets/javascripts/spina/openinghours/jquery.formatter.js +814 -0
- data/app/assets/javascripts/spina/openinghours/openinghours.js.coffee +8 -0
- data/app/controllers/spina/admin/openinghours_controller.rb +43 -0
- data/app/decorators/controllers/spina/pages_controller_decorator.rb +12 -0
- data/app/models/spina/openinghour.rb +21 -0
- data/app/views/spina/admin/openinghours/_form.html.haml +20 -0
- data/app/views/spina/admin/openinghours/edit.html.haml +1 -0
- data/app/views/spina/admin/openinghours/index.html.haml +15 -0
- data/config/routes.rb +5 -0
- data/db/migrate/20140402111207_create_openinghours.rb +10 -0
- data/lib/openinghours.rb +4 -0
- data/lib/spina/openinghours.rb +6 -0
- data/lib/spina/openinghours/configuration.rb +14 -0
- data/lib/spina/openinghours/engine.rb +21 -0
- data/lib/spina/openinghours/version.rb +5 -0
- data/lib/tasks/openinghours_tasks.rake +4 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +23 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +29 -0
- data/test/dummy/config/environments/production.rb +80 -0
- data/test/dummy/config/environments/test.rb +36 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +12 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +56 -0
- data/test/dummy/public/404.html +58 -0
- data/test/dummy/public/422.html +58 -0
- data/test/dummy/public/500.html +57 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/integration/navigation_test.rb +10 -0
- data/test/openinghours_test.rb +7 -0
- data/test/test_helper.rb +15 -0
- metadata +158 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 03858ac5c62b6492a3223688ddf3a60e37dfc348
|
4
|
+
data.tar.gz: 67bb028eac90d3ce98ff0404d308a01bb44f5d1a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2ed441bca59e6e6009a11a80d0623b46f2328970076f00f0590767b3f5d656736c37b036c0e734a12146cbbc4851ca3b6896be2915ab4cfe2cc6b4d92a9e25f9
|
7
|
+
data.tar.gz: 629437dd8058996e53a728e06205427f712f70bc4c43ab684c53d27d93e59f7a65b00d2b43277cd4e89807324900111af93bca056936bd9f68543a6655efdb89
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2014 YOURNAME
|
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.rdoc
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'Reviews'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
Bundler::GemHelper.install_tasks
|
23
|
+
|
24
|
+
require 'rake/testtask'
|
25
|
+
|
26
|
+
Rake::TestTask.new(:test) do |t|
|
27
|
+
t.libs << 'lib'
|
28
|
+
t.libs << 'test'
|
29
|
+
t.pattern = 'test/**/*_test.rb'
|
30
|
+
t.verbose = false
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
task default: :test
|
@@ -0,0 +1,814 @@
|
|
1
|
+
/*!
|
2
|
+
* v0.0.9
|
3
|
+
* Copyright (c) 2013 First Opinion
|
4
|
+
* formatter.js is open sourced under the MIT license.
|
5
|
+
*
|
6
|
+
* thanks to digitalBush/jquery.maskedinput for some of the trickier
|
7
|
+
* keycode handling
|
8
|
+
*/
|
9
|
+
|
10
|
+
;(function ($, window, document, undefined) {
|
11
|
+
|
12
|
+
// Defaults
|
13
|
+
var defaults = {
|
14
|
+
persistent: false,
|
15
|
+
repeat: false,
|
16
|
+
placeholder: ' '
|
17
|
+
};
|
18
|
+
|
19
|
+
// Regexs for input validation
|
20
|
+
var inptRegs = {
|
21
|
+
'9': /[0-9]/,
|
22
|
+
'a': /[A-Za-z]/,
|
23
|
+
'*': /[A-Za-z0-9]/
|
24
|
+
};
|
25
|
+
|
26
|
+
//
|
27
|
+
// Class Constructor - Called with new Formatter(el, opts)
|
28
|
+
// Responsible for setting up required instance variables, and
|
29
|
+
// attaching the event listener to the element.
|
30
|
+
//
|
31
|
+
function Formatter(el, opts) {
|
32
|
+
// Cache this
|
33
|
+
var self = this;
|
34
|
+
|
35
|
+
// Make sure we have an element. Make accesible to instance
|
36
|
+
self.el = el;
|
37
|
+
if (!self.el) {
|
38
|
+
throw new TypeError('Must provide an existing element');
|
39
|
+
}
|
40
|
+
|
41
|
+
// Merge opts with defaults
|
42
|
+
self.opts = utils.extend({}, defaults, opts);
|
43
|
+
|
44
|
+
// 1 pattern is special case
|
45
|
+
if (typeof self.opts.pattern !== 'undefined') {
|
46
|
+
self.opts.patterns = self._specFromSinglePattern(self.opts.pattern);
|
47
|
+
delete self.opts.pattern;
|
48
|
+
}
|
49
|
+
|
50
|
+
// Make sure we have valid opts
|
51
|
+
if (typeof self.opts.patterns === 'undefined') {
|
52
|
+
throw new TypeError('Must provide a pattern or array of patterns');
|
53
|
+
}
|
54
|
+
|
55
|
+
self.patternMatcher = patternMatcher(self.opts.patterns);
|
56
|
+
|
57
|
+
// Upate pattern with initial value
|
58
|
+
self._updatePattern();
|
59
|
+
|
60
|
+
// Init values
|
61
|
+
self.hldrs = {};
|
62
|
+
self.focus = 0;
|
63
|
+
|
64
|
+
// Add Listeners
|
65
|
+
utils.addListener(self.el, 'keydown', function (evt) {
|
66
|
+
self._keyDown(evt);
|
67
|
+
});
|
68
|
+
utils.addListener(self.el, 'keypress', function (evt) {
|
69
|
+
self._keyPress(evt);
|
70
|
+
});
|
71
|
+
utils.addListener(self.el, 'paste', function (evt) {
|
72
|
+
self._paste(evt);
|
73
|
+
});
|
74
|
+
|
75
|
+
// Persistence
|
76
|
+
if (self.opts.persistent) {
|
77
|
+
// Format on start
|
78
|
+
self._processKey('', false);
|
79
|
+
self.el.blur();
|
80
|
+
|
81
|
+
// Add Listeners
|
82
|
+
utils.addListener(self.el, 'focus', function (evt) {
|
83
|
+
self._focus(evt);
|
84
|
+
});
|
85
|
+
utils.addListener(self.el, 'click', function (evt) {
|
86
|
+
self._focus(evt);
|
87
|
+
});
|
88
|
+
utils.addListener(self.el, 'touchstart', function (evt) {
|
89
|
+
self._focus(evt);
|
90
|
+
});
|
91
|
+
}
|
92
|
+
}
|
93
|
+
|
94
|
+
//
|
95
|
+
// @public
|
96
|
+
// Add new char
|
97
|
+
//
|
98
|
+
Formatter.addInptType = function (chr, reg) {
|
99
|
+
inptRegs[chr] = reg;
|
100
|
+
};
|
101
|
+
|
102
|
+
//
|
103
|
+
// @public
|
104
|
+
// Apply the given pattern to the current input without moving caret.
|
105
|
+
//
|
106
|
+
Formatter.prototype.resetPattern = function (str) {
|
107
|
+
// Update opts to hold new pattern
|
108
|
+
this.opts.patterns = str ? this._specFromSinglePattern(str) : this.opts.patterns;
|
109
|
+
|
110
|
+
// Get current state
|
111
|
+
this.sel = inptSel.get(this.el);
|
112
|
+
this.val = this.el.value;
|
113
|
+
|
114
|
+
// Init values
|
115
|
+
this.delta = 0;
|
116
|
+
|
117
|
+
// Remove all formatted chars from val
|
118
|
+
this._removeChars();
|
119
|
+
|
120
|
+
this.patternMatcher = patternMatcher(this.opts.patterns);
|
121
|
+
|
122
|
+
// Update pattern
|
123
|
+
var newPattern = this.patternMatcher.getPattern(this.val);
|
124
|
+
this.mLength = newPattern.mLength;
|
125
|
+
this.chars = newPattern.chars;
|
126
|
+
this.inpts = newPattern.inpts;
|
127
|
+
|
128
|
+
// Format on start
|
129
|
+
this._processKey('', false, true);
|
130
|
+
};
|
131
|
+
|
132
|
+
//
|
133
|
+
// @private
|
134
|
+
// Determine correct format pattern based on input val
|
135
|
+
//
|
136
|
+
Formatter.prototype._updatePattern = function () {
|
137
|
+
// Determine appropriate pattern
|
138
|
+
var newPattern = this.patternMatcher.getPattern(this.val);
|
139
|
+
|
140
|
+
// Only update the pattern if there is an appropriate pattern for the value.
|
141
|
+
// Otherwise, leave the current pattern (and likely delete the latest character.)
|
142
|
+
if (newPattern) {
|
143
|
+
// Get info about the given pattern
|
144
|
+
this.mLength = newPattern.mLength;
|
145
|
+
this.chars = newPattern.chars;
|
146
|
+
this.inpts = newPattern.inpts;
|
147
|
+
}
|
148
|
+
};
|
149
|
+
|
150
|
+
//
|
151
|
+
// @private
|
152
|
+
// Handler called on all keyDown strokes. All keys trigger
|
153
|
+
// this handler. Only process delete keys.
|
154
|
+
//
|
155
|
+
Formatter.prototype._keyDown = function (evt) {
|
156
|
+
// The first thing we need is the character code
|
157
|
+
var k = evt.which || evt.keyCode;
|
158
|
+
|
159
|
+
// If delete key
|
160
|
+
if (k && utils.isDelKey(k)) {
|
161
|
+
// Process the keyCode and prevent default
|
162
|
+
this._processKey(null, k);
|
163
|
+
return utils.preventDefault(evt);
|
164
|
+
}
|
165
|
+
};
|
166
|
+
|
167
|
+
//
|
168
|
+
// @private
|
169
|
+
// Handler called on all keyPress strokes. Only processes
|
170
|
+
// character keys (as long as no modifier key is in use).
|
171
|
+
//
|
172
|
+
Formatter.prototype._keyPress = function (evt) {
|
173
|
+
// The first thing we need is the character code
|
174
|
+
var k, isSpecial;
|
175
|
+
// Mozilla will trigger on special keys and assign the the value 0
|
176
|
+
// We want to use that 0 rather than the keyCode it assigns.
|
177
|
+
if (evt.which) {
|
178
|
+
k = evt.which;
|
179
|
+
} else {
|
180
|
+
k = evt.keyCode;
|
181
|
+
isSpecial = utils.isSpecialKey(k);
|
182
|
+
}
|
183
|
+
// Process the keyCode and prevent default
|
184
|
+
if (!utils.isDelKey(k) && !isSpecial && !utils.isModifier(evt)) {
|
185
|
+
this._processKey(String.fromCharCode(k), false);
|
186
|
+
return utils.preventDefault(evt);
|
187
|
+
}
|
188
|
+
};
|
189
|
+
|
190
|
+
//
|
191
|
+
// @private
|
192
|
+
// Handler called on paste event.
|
193
|
+
//
|
194
|
+
Formatter.prototype._paste = function (evt) {
|
195
|
+
// Process the clipboard paste and prevent default
|
196
|
+
this._processKey(utils.getClip(evt), false);
|
197
|
+
return utils.preventDefault(evt);
|
198
|
+
};
|
199
|
+
|
200
|
+
//
|
201
|
+
// @private
|
202
|
+
// Handle called on focus event.
|
203
|
+
//
|
204
|
+
Formatter.prototype._focus = function () {
|
205
|
+
// Wrapped in timeout so that we can grab input selection
|
206
|
+
var self = this;
|
207
|
+
setTimeout(function () {
|
208
|
+
// Grab selection
|
209
|
+
var selection = inptSel.get(self.el);
|
210
|
+
// Char check
|
211
|
+
var isAfterStart = selection.end > self.focus,
|
212
|
+
isFirstChar = selection.end === 0;
|
213
|
+
// If clicked in front of start, refocus to start
|
214
|
+
if (isAfterStart || isFirstChar) {
|
215
|
+
inptSel.set(self.el, self.focus);
|
216
|
+
}
|
217
|
+
}, 0);
|
218
|
+
};
|
219
|
+
|
220
|
+
//
|
221
|
+
// @private
|
222
|
+
// Using the provided key information, alter el value.
|
223
|
+
//
|
224
|
+
Formatter.prototype._processKey = function (chars, delKey,ingoreCaret) {
|
225
|
+
// Get current state
|
226
|
+
this.sel = inptSel.get(this.el);
|
227
|
+
this.val = this.el.value;
|
228
|
+
|
229
|
+
// Init values
|
230
|
+
this.delta = 0;
|
231
|
+
|
232
|
+
// If chars were highlighted, we need to remove them
|
233
|
+
if (this.sel.begin !== this.sel.end) {
|
234
|
+
this.delta = (-1) * Math.abs(this.sel.begin - this.sel.end);
|
235
|
+
this.val = utils.removeChars(this.val, this.sel.begin, this.sel.end);
|
236
|
+
}
|
237
|
+
|
238
|
+
// Delete key (moves opposite direction)
|
239
|
+
else if (delKey && delKey == 46) {
|
240
|
+
this._delete();
|
241
|
+
|
242
|
+
// or Backspace and not at start
|
243
|
+
} else if (delKey && this.sel.begin - 1 >= 0) {
|
244
|
+
|
245
|
+
// Always have a delta of at least -1 for the character being deleted.
|
246
|
+
this.delta -= 1;
|
247
|
+
|
248
|
+
// Count number of additional format chars to be deleted. (A group of multiple format chars should be deleted like one value char.)
|
249
|
+
while (this.chars[this.focus-1]) {
|
250
|
+
this.delta--;
|
251
|
+
this.focus--;
|
252
|
+
}
|
253
|
+
|
254
|
+
this.val = utils.removeChars(this.val, this.sel.end + this.delta, this.sel.end);
|
255
|
+
|
256
|
+
// or Backspace and at start - exit
|
257
|
+
} else if (delKey) {
|
258
|
+
return true;
|
259
|
+
}
|
260
|
+
|
261
|
+
// If the key is not a del key, it should convert to a str
|
262
|
+
if (!delKey) {
|
263
|
+
// Add char at position and increment delta
|
264
|
+
this.val = utils.addChars(this.val, chars, this.sel.begin);
|
265
|
+
this.delta += chars.length;
|
266
|
+
}
|
267
|
+
|
268
|
+
// Format el.value (also handles updating caret position)
|
269
|
+
this._formatValue(ingoreCaret);
|
270
|
+
};
|
271
|
+
|
272
|
+
//
|
273
|
+
// @private
|
274
|
+
// Deletes the character in front of it
|
275
|
+
//
|
276
|
+
Formatter.prototype._delete = function () {
|
277
|
+
// Adjust focus to make sure its not on a formatted char
|
278
|
+
while (this.chars[this.sel.begin]) {
|
279
|
+
this._nextPos();
|
280
|
+
}
|
281
|
+
|
282
|
+
// As long as we are not at the end
|
283
|
+
if (this.sel.begin < this.val.length) {
|
284
|
+
// We will simulate a delete by moving the caret to the next char
|
285
|
+
// and then deleting
|
286
|
+
this._nextPos();
|
287
|
+
this.val = utils.removeChars(this.val, this.sel.end -1, this.sel.end);
|
288
|
+
this.delta = -1;
|
289
|
+
}
|
290
|
+
};
|
291
|
+
|
292
|
+
//
|
293
|
+
// @private
|
294
|
+
// Quick helper method to move the caret to the next pos
|
295
|
+
//
|
296
|
+
Formatter.prototype._nextPos = function () {
|
297
|
+
this.sel.end ++;
|
298
|
+
this.sel.begin ++;
|
299
|
+
};
|
300
|
+
|
301
|
+
//
|
302
|
+
// @private
|
303
|
+
// Alter element value to display characters matching the provided
|
304
|
+
// instance pattern. Also responsible for updating
|
305
|
+
//
|
306
|
+
Formatter.prototype._formatValue = function (ignoreCaret) {
|
307
|
+
// Set caret pos
|
308
|
+
this.newPos = this.sel.end + this.delta;
|
309
|
+
|
310
|
+
// Remove all formatted chars from val
|
311
|
+
this._removeChars();
|
312
|
+
|
313
|
+
// Switch to first matching pattern based on val
|
314
|
+
this._updatePattern();
|
315
|
+
|
316
|
+
// Validate inputs
|
317
|
+
this._validateInpts();
|
318
|
+
|
319
|
+
// Add formatted characters
|
320
|
+
this._addChars();
|
321
|
+
|
322
|
+
// Set value and adhere to maxLength
|
323
|
+
this.el.value = this.val.substr(0, this.mLength);
|
324
|
+
|
325
|
+
// Set new caret position
|
326
|
+
if ((typeof ignoreCaret) === 'undefined' || ignoreCaret === false) {
|
327
|
+
inptSel.set(this.el, this.newPos);
|
328
|
+
}
|
329
|
+
};
|
330
|
+
|
331
|
+
//
|
332
|
+
// @private
|
333
|
+
// Remove all formatted before and after a specified pos
|
334
|
+
//
|
335
|
+
Formatter.prototype._removeChars = function () {
|
336
|
+
// Delta shouldn't include placeholders
|
337
|
+
if (this.sel.end > this.focus) {
|
338
|
+
this.delta += this.sel.end - this.focus;
|
339
|
+
}
|
340
|
+
|
341
|
+
// Account for shifts during removal
|
342
|
+
var shift = 0;
|
343
|
+
|
344
|
+
// Loop through all possible char positions
|
345
|
+
for (var i = 0; i <= this.mLength; i++) {
|
346
|
+
// Get transformed position
|
347
|
+
var curChar = this.chars[i],
|
348
|
+
curHldr = this.hldrs[i],
|
349
|
+
pos = i + shift,
|
350
|
+
val;
|
351
|
+
|
352
|
+
// If after selection we need to account for delta
|
353
|
+
pos = (i >= this.sel.begin) ? pos + this.delta : pos;
|
354
|
+
val = this.val.charAt(pos);
|
355
|
+
// Remove char and account for shift
|
356
|
+
if (curChar && curChar == val || curHldr && curHldr == val) {
|
357
|
+
this.val = utils.removeChars(this.val, pos, pos + 1);
|
358
|
+
shift--;
|
359
|
+
}
|
360
|
+
}
|
361
|
+
|
362
|
+
// All hldrs should be removed now
|
363
|
+
this.hldrs = {};
|
364
|
+
|
365
|
+
// Set focus to last character
|
366
|
+
this.focus = this.val.length;
|
367
|
+
};
|
368
|
+
|
369
|
+
//
|
370
|
+
// @private
|
371
|
+
// Make sure all inpts are valid, else remove and update delta
|
372
|
+
//
|
373
|
+
Formatter.prototype._validateInpts = function () {
|
374
|
+
// Loop over each char and validate
|
375
|
+
for (var i = 0; i < this.val.length; i++) {
|
376
|
+
// Get char inpt type
|
377
|
+
var inptType = this.inpts[i];
|
378
|
+
|
379
|
+
// Checks
|
380
|
+
var isBadType = !inptRegs[inptType],
|
381
|
+
isInvalid = !isBadType && !inptRegs[inptType].test(this.val.charAt(i)),
|
382
|
+
inBounds = this.inpts[i];
|
383
|
+
|
384
|
+
// Remove if incorrect and inbounds
|
385
|
+
if ((isBadType || isInvalid) && inBounds) {
|
386
|
+
this.val = utils.removeChars(this.val, i, i + 1);
|
387
|
+
this.focusStart--;
|
388
|
+
this.newPos--;
|
389
|
+
this.delta--;
|
390
|
+
i--;
|
391
|
+
}
|
392
|
+
}
|
393
|
+
};
|
394
|
+
|
395
|
+
//
|
396
|
+
// @private
|
397
|
+
// Loop over val and add formatted chars as necessary
|
398
|
+
//
|
399
|
+
Formatter.prototype._addChars = function () {
|
400
|
+
if (this.opts.persistent) {
|
401
|
+
// Loop over all possible characters
|
402
|
+
for (var i = 0; i <= this.mLength; i++) {
|
403
|
+
if (!this.val.charAt(i)) {
|
404
|
+
// Add placeholder at pos
|
405
|
+
this.val = utils.addChars(this.val, this.opts.placeholder, i);
|
406
|
+
this.hldrs[i] = this.opts.placeholder;
|
407
|
+
}
|
408
|
+
this._addChar(i);
|
409
|
+
}
|
410
|
+
|
411
|
+
// Adjust focus to make sure its not on a formatted char
|
412
|
+
while (this.chars[this.focus]) {
|
413
|
+
this.focus++;
|
414
|
+
}
|
415
|
+
} else {
|
416
|
+
// Avoid caching val.length and this.focus, as they may change in _addChar.
|
417
|
+
for (var j = 0; j <= this.val.length; j++) {
|
418
|
+
// When moving backwards, i.e. delting characters, don't add format characters past focus point.
|
419
|
+
if ( (this.delta <= 0 && j === this.focus && this.chars[j] === undefined) || (this.focus === 0) ) { return true; }
|
420
|
+
|
421
|
+
// Place character in current position of the formatted string.
|
422
|
+
this._addChar(j);
|
423
|
+
}
|
424
|
+
}
|
425
|
+
};
|
426
|
+
|
427
|
+
//
|
428
|
+
// @private
|
429
|
+
// Add formattted char at position
|
430
|
+
//
|
431
|
+
Formatter.prototype._addChar = function (i) {
|
432
|
+
// If char exists at position
|
433
|
+
var chr = this.chars[i];
|
434
|
+
if (!chr) { return true; }
|
435
|
+
|
436
|
+
// If chars are added in between the old pos and new pos
|
437
|
+
// we need to increment pos and delta
|
438
|
+
if (utils.isBetween(i, [this.sel.begin -1, this.newPos +1])) {
|
439
|
+
this.newPos ++;
|
440
|
+
this.delta ++;
|
441
|
+
}
|
442
|
+
|
443
|
+
// If character added before focus, incr
|
444
|
+
if (i <= this.focus) {
|
445
|
+
this.focus++;
|
446
|
+
}
|
447
|
+
|
448
|
+
// Updateholder
|
449
|
+
if (this.hldrs[i]) {
|
450
|
+
delete this.hldrs[i];
|
451
|
+
this.hldrs[i + 1] = this.opts.placeholder;
|
452
|
+
}
|
453
|
+
|
454
|
+
// Update value
|
455
|
+
this.val = utils.addChars(this.val, chr, i);
|
456
|
+
};
|
457
|
+
|
458
|
+
//
|
459
|
+
// @private
|
460
|
+
// Create a patternSpec for passing into patternMatcher that
|
461
|
+
// has exactly one catch all pattern.
|
462
|
+
//
|
463
|
+
Formatter.prototype._specFromSinglePattern = function (patternStr) {
|
464
|
+
return [{ '*': patternStr }];
|
465
|
+
};
|
466
|
+
|
467
|
+
|
468
|
+
// Define module
|
469
|
+
var pattern = {};
|
470
|
+
|
471
|
+
// Match information
|
472
|
+
var DELIM_SIZE = 4;
|
473
|
+
|
474
|
+
// Our regex used to parse
|
475
|
+
var regexp = new RegExp('{{([^}]+)}}', 'g');
|
476
|
+
|
477
|
+
//
|
478
|
+
// Helper method to parse pattern str
|
479
|
+
//
|
480
|
+
var getMatches = function (pattern) {
|
481
|
+
// Populate array of matches
|
482
|
+
var matches = [],
|
483
|
+
match;
|
484
|
+
while(match = regexp.exec(pattern)) {
|
485
|
+
matches.push(match);
|
486
|
+
}
|
487
|
+
|
488
|
+
return matches;
|
489
|
+
};
|
490
|
+
|
491
|
+
//
|
492
|
+
// Create an object holding all formatted characters
|
493
|
+
// with corresponding positions
|
494
|
+
//
|
495
|
+
pattern.parse = function (pattern) {
|
496
|
+
// Our obj to populate
|
497
|
+
var info = { inpts: {}, chars: {} };
|
498
|
+
|
499
|
+
// Pattern information
|
500
|
+
var matches = getMatches(pattern),
|
501
|
+
pLength = pattern.length;
|
502
|
+
|
503
|
+
// Counters
|
504
|
+
var mCount = 0,
|
505
|
+
iCount = 0,
|
506
|
+
i = 0;
|
507
|
+
|
508
|
+
// Add inpts, move to end of match, and process
|
509
|
+
var processMatch = function (val) {
|
510
|
+
var valLength = val.length;
|
511
|
+
for (var j = 0; j < valLength; j++) {
|
512
|
+
info.inpts[iCount] = val.charAt(j);
|
513
|
+
iCount++;
|
514
|
+
}
|
515
|
+
mCount ++;
|
516
|
+
i += (val.length + DELIM_SIZE - 1);
|
517
|
+
};
|
518
|
+
|
519
|
+
// Process match or add chars
|
520
|
+
for (i; i < pLength; i++) {
|
521
|
+
if (mCount < matches.length && i == matches[mCount].index) {
|
522
|
+
processMatch(matches[mCount][1]);
|
523
|
+
} else {
|
524
|
+
info.chars[i - (mCount * DELIM_SIZE)] = pattern.charAt(i);
|
525
|
+
}
|
526
|
+
}
|
527
|
+
|
528
|
+
// Set mLength and return
|
529
|
+
info.mLength = i - (mCount * DELIM_SIZE);
|
530
|
+
return info;
|
531
|
+
};
|
532
|
+
|
533
|
+
//
|
534
|
+
// Parse a matcher string into a RegExp. Accepts valid regular
|
535
|
+
// expressions and the catchall '*'.
|
536
|
+
// @private
|
537
|
+
//
|
538
|
+
var parseMatcher = function (matcher) {
|
539
|
+
if (matcher === '*') {
|
540
|
+
return /.*/;
|
541
|
+
}
|
542
|
+
return new RegExp(matcher);
|
543
|
+
};
|
544
|
+
|
545
|
+
//
|
546
|
+
// Parse a pattern spec and return a function that returns a pattern
|
547
|
+
// based on user input. The first matching pattern will be chosen.
|
548
|
+
// Pattern spec format:
|
549
|
+
// Array [
|
550
|
+
// Object: { Matcher(RegExp String) : Pattern(Pattern String) },
|
551
|
+
// ...
|
552
|
+
// ]
|
553
|
+
function patternMatcher (patternSpec) {
|
554
|
+
var matchers = [],
|
555
|
+
patterns = [];
|
556
|
+
|
557
|
+
// Iterate over each pattern in order.
|
558
|
+
utils.forEach(patternSpec, function (patternMatcher) {
|
559
|
+
// Process single property object to obtain pattern and matcher.
|
560
|
+
utils.forEach(patternMatcher, function (patternStr, matcherStr) {
|
561
|
+
var parsedPattern = pattern.parse(patternStr),
|
562
|
+
regExpMatcher = parseMatcher(matcherStr);
|
563
|
+
|
564
|
+
matchers.push(regExpMatcher);
|
565
|
+
patterns.push(parsedPattern);
|
566
|
+
|
567
|
+
// Stop after one iteration.
|
568
|
+
return false;
|
569
|
+
});
|
570
|
+
});
|
571
|
+
|
572
|
+
var getPattern = function (input) {
|
573
|
+
var matchedIndex;
|
574
|
+
utils.forEach(matchers, function (matcher, index) {
|
575
|
+
if (matcher.test(input)) {
|
576
|
+
matchedIndex = index;
|
577
|
+
return false;
|
578
|
+
}
|
579
|
+
});
|
580
|
+
|
581
|
+
return matchedIndex === undefined ? null : patterns[matchedIndex];
|
582
|
+
};
|
583
|
+
|
584
|
+
return {
|
585
|
+
getPattern: getPattern,
|
586
|
+
patterns: patterns,
|
587
|
+
matchers: matchers
|
588
|
+
};
|
589
|
+
}
|
590
|
+
|
591
|
+
// Define module
|
592
|
+
var inptSel = {};
|
593
|
+
|
594
|
+
//
|
595
|
+
// Get begin and end positions of selected input. Return 0's
|
596
|
+
// if there is no selectiion data
|
597
|
+
//
|
598
|
+
inptSel.get = function (el) {
|
599
|
+
// If normal browser return with result
|
600
|
+
if (typeof el.selectionStart == "number") {
|
601
|
+
return {
|
602
|
+
begin: el.selectionStart,
|
603
|
+
end: el.selectionEnd
|
604
|
+
};
|
605
|
+
}
|
606
|
+
|
607
|
+
// Uh-Oh. We must be IE. Fun with TextRange!!
|
608
|
+
var range = document.selection.createRange();
|
609
|
+
// Determine if there is a selection
|
610
|
+
if (range && range.parentElement() == el) {
|
611
|
+
var inputRange = el.createTextRange(),
|
612
|
+
endRange = el.createTextRange(),
|
613
|
+
length = el.value.length;
|
614
|
+
|
615
|
+
// Create a working TextRange for the input selection
|
616
|
+
inputRange.moveToBookmark(range.getBookmark());
|
617
|
+
|
618
|
+
// Move endRange begin pos to end pos (hence endRange)
|
619
|
+
endRange.collapse(false);
|
620
|
+
|
621
|
+
// If we are at the very end of the input, begin and end
|
622
|
+
// must both be the length of the el.value
|
623
|
+
if (inputRange.compareEndPoints("StartToEnd", endRange) > -1) {
|
624
|
+
return { begin: length, end: length };
|
625
|
+
}
|
626
|
+
|
627
|
+
// Note: moveStart usually returns the units moved, which
|
628
|
+
// one may think is -length, however, it will stop when it
|
629
|
+
// gets to the begin of the range, thus giving us the
|
630
|
+
// negative value of the pos.
|
631
|
+
return {
|
632
|
+
begin: -inputRange.moveStart("character", -length),
|
633
|
+
end: -inputRange.moveEnd("character", -length)
|
634
|
+
};
|
635
|
+
}
|
636
|
+
|
637
|
+
//Return 0's on no selection data
|
638
|
+
return { begin: 0, end: 0 };
|
639
|
+
};
|
640
|
+
|
641
|
+
//
|
642
|
+
// Set the caret position at a specified location
|
643
|
+
//
|
644
|
+
inptSel.set = function (el, pos) {
|
645
|
+
// If normal browser
|
646
|
+
if (el.setSelectionRange) {
|
647
|
+
el.focus();
|
648
|
+
el.setSelectionRange(pos,pos);
|
649
|
+
|
650
|
+
// IE = TextRange fun
|
651
|
+
} else if (el.createTextRange) {
|
652
|
+
var range = el.createTextRange();
|
653
|
+
range.collapse(true);
|
654
|
+
range.moveEnd('character', pos);
|
655
|
+
range.moveStart('character', pos);
|
656
|
+
range.select();
|
657
|
+
}
|
658
|
+
};
|
659
|
+
// Define module
|
660
|
+
var utils = {};
|
661
|
+
|
662
|
+
// Useragent info for keycode handling
|
663
|
+
var uAgent = (typeof navigator !== 'undefined') ? navigator.userAgent : null,
|
664
|
+
iPhone = /iphone/i.test(uAgent);
|
665
|
+
|
666
|
+
//
|
667
|
+
// Shallow copy properties from n objects to destObj
|
668
|
+
//
|
669
|
+
utils.extend = function (destObj) {
|
670
|
+
for (var i = 1; i < arguments.length; i++) {
|
671
|
+
for (var key in arguments[i]) {
|
672
|
+
destObj[key] = arguments[i][key];
|
673
|
+
}
|
674
|
+
}
|
675
|
+
return destObj;
|
676
|
+
};
|
677
|
+
|
678
|
+
//
|
679
|
+
// Add a given character to a string at a defined pos
|
680
|
+
//
|
681
|
+
utils.addChars = function (str, chars, pos) {
|
682
|
+
return str.substr(0, pos) + chars + str.substr(pos, str.length);
|
683
|
+
};
|
684
|
+
|
685
|
+
//
|
686
|
+
// Remove a span of characters
|
687
|
+
//
|
688
|
+
utils.removeChars = function (str, start, end) {
|
689
|
+
return str.substr(0, start) + str.substr(end, str.length);
|
690
|
+
};
|
691
|
+
|
692
|
+
//
|
693
|
+
// Return true/false is num false between bounds
|
694
|
+
//
|
695
|
+
utils.isBetween = function (num, bounds) {
|
696
|
+
bounds.sort(function(a,b) { return a-b; });
|
697
|
+
return (num > bounds[0] && num < bounds[1]);
|
698
|
+
};
|
699
|
+
|
700
|
+
//
|
701
|
+
// Helper method for cross browser event listeners
|
702
|
+
//
|
703
|
+
utils.addListener = function (el, evt, handler) {
|
704
|
+
return (typeof el.addEventListener != "undefined")
|
705
|
+
? el.addEventListener(evt, handler, false)
|
706
|
+
: el.attachEvent('on' + evt, handler);
|
707
|
+
};
|
708
|
+
|
709
|
+
//
|
710
|
+
// Helper method for cross browser implementation of preventDefault
|
711
|
+
//
|
712
|
+
utils.preventDefault = function (evt) {
|
713
|
+
return (evt.preventDefault) ? evt.preventDefault() : (evt.returnValue = false);
|
714
|
+
};
|
715
|
+
|
716
|
+
//
|
717
|
+
// Helper method for cross browser implementation for grabbing
|
718
|
+
// clipboard data
|
719
|
+
//
|
720
|
+
utils.getClip = function (evt) {
|
721
|
+
if (evt.clipboardData) { return evt.clipboardData.getData('Text'); }
|
722
|
+
if (window.clipboardData) { return window.clipboardData.getData('Text'); }
|
723
|
+
};
|
724
|
+
|
725
|
+
//
|
726
|
+
// Returns true/false if k is a del key
|
727
|
+
//
|
728
|
+
utils.isDelKey = function (k) {
|
729
|
+
return k === 8 || k === 46 || (iPhone && k === 127);
|
730
|
+
};
|
731
|
+
|
732
|
+
//
|
733
|
+
// Returns true/false if k is an arrow key
|
734
|
+
//
|
735
|
+
utils.isSpecialKey = function (k) {
|
736
|
+
var codes = {
|
737
|
+
'9' : 'tab',
|
738
|
+
'13': 'enter',
|
739
|
+
'35': 'end',
|
740
|
+
'36': 'home',
|
741
|
+
'37': 'leftarrow',
|
742
|
+
'38': 'uparrow',
|
743
|
+
'39': 'rightarrow',
|
744
|
+
'40': 'downarrow',
|
745
|
+
'116': 'F5'
|
746
|
+
};
|
747
|
+
// If del or special key
|
748
|
+
return codes[k];
|
749
|
+
};
|
750
|
+
|
751
|
+
//
|
752
|
+
// Returns true/false if modifier key is held down
|
753
|
+
//
|
754
|
+
utils.isModifier = function (evt) {
|
755
|
+
return evt.ctrlKey || evt.altKey || evt.metaKey;
|
756
|
+
};
|
757
|
+
|
758
|
+
//
|
759
|
+
// Iterates over each property of object or array.
|
760
|
+
//
|
761
|
+
utils.forEach = function (collection, callback, thisArg) {
|
762
|
+
if (collection.hasOwnProperty("length")) {
|
763
|
+
for (var index = 0, len = collection.length; index < len; index++) {
|
764
|
+
if (callback.call(thisArg, collection[index], index, collection) === false) {
|
765
|
+
break;
|
766
|
+
}
|
767
|
+
}
|
768
|
+
} else {
|
769
|
+
for (var key in collection) {
|
770
|
+
if (collection.hasOwnProperty(key)) {
|
771
|
+
if (callback.call(thisArg, collection[key], key, collection) === false) {
|
772
|
+
break;
|
773
|
+
}
|
774
|
+
}
|
775
|
+
}
|
776
|
+
}
|
777
|
+
};
|
778
|
+
|
779
|
+
// A really lightweight plugin wrapper around the constructor,
|
780
|
+
// preventing against multiple instantiations
|
781
|
+
var pluginName = 'formatter';
|
782
|
+
|
783
|
+
$.fn[pluginName] = function (options) {
|
784
|
+
|
785
|
+
// Initiate plugin if options passed
|
786
|
+
if (typeof options == 'object') {
|
787
|
+
this.each(function () {
|
788
|
+
if (!$.data(this, 'plugin_' + pluginName)) {
|
789
|
+
$.data(this, 'plugin_' + pluginName,
|
790
|
+
new Formatter(this, options));
|
791
|
+
}
|
792
|
+
});
|
793
|
+
}
|
794
|
+
|
795
|
+
// Add resetPattern method to plugin
|
796
|
+
this.resetPattern = function (str) {
|
797
|
+
this.each(function () {
|
798
|
+
var formatted = $.data(this, 'plugin_' + pluginName);
|
799
|
+
// resetPattern for instance
|
800
|
+
if (formatted) { formatted.resetPattern(str); }
|
801
|
+
});
|
802
|
+
// Chainable please
|
803
|
+
return this;
|
804
|
+
};
|
805
|
+
|
806
|
+
// Chainable please
|
807
|
+
return this;
|
808
|
+
};
|
809
|
+
|
810
|
+
$.fn[pluginName].addInptType = function (chr, regexp) {
|
811
|
+
Formatter.addInptType(chr, regexp);
|
812
|
+
};
|
813
|
+
|
814
|
+
})( jQuery, window, document);
|