spina-openinghours 0.0.5
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.
- 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);
|