skeuocard-rails 0.1.0 → 1.0.0beta

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.
@@ -1,5 +1,3 @@
1
- // Generated by CoffeeScript 1.3.3
2
-
3
1
  /*
4
2
  "Skeuocard" -- A Skeuomorphic Credit-Card Input Enhancement
5
3
  @description Skeuocard is a skeuomorphic credit card input plugin, supporting
@@ -13,13 +11,14 @@
13
11
 
14
12
 
15
13
  (function() {
16
- var CCIssuers, CCProducts, Skeuocard,
17
- __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
14
+ var $, Skeuocard, visaProduct,
18
15
  __slice = [].slice,
19
- __hasProp = {}.hasOwnProperty,
20
- __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
16
+ __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
17
+
18
+ $ = jQuery;
21
19
 
22
20
  Skeuocard = (function() {
21
+ Skeuocard.currentDate = new Date();
23
22
 
24
23
  function Skeuocard(el, opts) {
25
24
  var optDefaults;
@@ -31,54 +30,69 @@
31
30
  underlyingFields: {}
32
31
  };
33
32
  this._inputViews = {};
33
+ this._inputViewsByFace = {
34
+ front: [],
35
+ back: []
36
+ };
34
37
  this._tabViews = {};
35
- this.product = void 0;
36
- this.productShortname = void 0;
37
- this.issuerShortname = void 0;
38
- this._cardProductNeedsLayout = true;
39
- this.acceptedCardProducts = {};
38
+ this._state = {};
39
+ this.product = null;
40
40
  this.visibleFace = 'front';
41
- this._initialValidationState = {};
42
- this._validationState = {
43
- number: false,
44
- exp: false,
45
- name: false,
46
- cvc: false
47
- };
48
- this._faceFillState = {
49
- front: false,
50
- back: false
51
- };
52
41
  optDefaults = {
53
42
  debug: false,
54
- acceptedCardProducts: [],
43
+ acceptedCardProducts: null,
55
44
  cardNumberPlaceholderChar: 'X',
56
45
  genericPlaceholder: "XXXX XXXX XXXX XXXX",
57
46
  typeInputSelector: '[name="cc_type"]',
58
47
  numberInputSelector: '[name="cc_number"]',
59
- expInputSelector: '[name="cc_exp"]',
48
+ expMonthInputSelector: '[name="cc_exp_month"]',
49
+ expYearInputSelector: '[name="cc_exp_year"]',
60
50
  nameInputSelector: '[name="cc_name"]',
61
51
  cvcInputSelector: '[name="cc_cvc"]',
62
- currentDate: new Date(),
63
52
  initialValues: {},
64
53
  validationState: {},
65
54
  strings: {
66
- hiddenFaceFillPrompt: "Click here to<br /> fill in the other side.",
55
+ hiddenFaceFillPrompt: "<strong>Click here</strong> to <br>fill in the other side.",
67
56
  hiddenFaceErrorWarning: "There's a problem on the other side.",
68
- hiddenFaceSwitchPrompt: "Back to the other side..."
57
+ hiddenFaceSwitchPrompt: "Forget something?<br> Flip the card over."
69
58
  }
70
59
  };
71
60
  this.options = $.extend(optDefaults, opts);
72
61
  this._conformDOM();
73
- this._setAcceptedCardProducts();
74
- this._createInputs();
75
- this._updateProductIfNeeded();
76
- this._flipToInvalidSide();
62
+ this._bindInputEvents();
63
+ this._importImplicitOptions();
64
+ this.render();
77
65
  }
78
66
 
67
+ Skeuocard.prototype._log = function() {
68
+ var msg;
69
+ msg = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
70
+ if ((typeof console !== "undefined" && console !== null ? console.log : void 0) && !!this.options.debug) {
71
+ if (this.options.debug != null) {
72
+ return console.log.apply(console, ["[skeuocard]"].concat(__slice.call(msg)));
73
+ }
74
+ }
75
+ };
76
+
77
+ Skeuocard.prototype.trigger = function() {
78
+ var args, _ref;
79
+ args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
80
+ return (_ref = this.el.container).trigger.apply(_ref, args);
81
+ };
82
+
83
+ Skeuocard.prototype.bind = function() {
84
+ var args, _ref;
85
+ args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
86
+ return (_ref = this.el.container).bind.apply(_ref, args);
87
+ };
88
+
89
+ /*
90
+ Transform the elements within the container, conforming the DOM so that it
91
+ becomes styleable, and that the underlying inputs are hidden.
92
+ */
93
+
94
+
79
95
  Skeuocard.prototype._conformDOM = function() {
80
- var el, fieldName, fieldValue, _ref, _ref1, _ref2,
81
- _this = this;
82
96
  this.el.container.removeClass('no-js');
83
97
  this.el.container.addClass("skeuocard js");
84
98
  this.el.container.find("> :not(input,select,textarea)").remove();
@@ -86,419 +100,367 @@
86
100
  this.el.underlyingFields = {
87
101
  type: this.el.container.find(this.options.typeInputSelector),
88
102
  number: this.el.container.find(this.options.numberInputSelector),
89
- exp: this.el.container.find(this.options.expInputSelector),
103
+ expMonth: this.el.container.find(this.options.expMonthInputSelector),
104
+ expYear: this.el.container.find(this.options.expYearInputSelector),
90
105
  name: this.el.container.find(this.options.nameInputSelector),
91
106
  cvc: this.el.container.find(this.options.cvcInputSelector)
92
107
  };
93
- _ref = this.options.initialValues;
94
- for (fieldName in _ref) {
95
- fieldValue = _ref[fieldName];
96
- this.el.underlyingFields[fieldName].val(fieldValue);
97
- }
98
- _ref1 = this.el.underlyingFields;
99
- for (fieldName in _ref1) {
100
- el = _ref1[fieldName];
101
- this.options.initialValues[fieldName] = el.val();
102
- }
103
- _ref2 = this.el.underlyingFields;
104
- for (fieldName in _ref2) {
105
- el = _ref2[fieldName];
106
- if (this.options.validationState[fieldName] === false || el.hasClass('invalid')) {
107
- this._initialValidationState[fieldName] = false;
108
- if (!el.hasClass('invalid')) {
109
- el.addClass('invalid');
110
- }
111
- }
112
- }
113
- this.el.underlyingFields.number.bind("change", function(e) {
114
- _this._inputViews.number.setValue(_this._getUnderlyingValue('number'));
115
- return _this.render();
116
- });
117
- this.el.underlyingFields.exp.bind("change", function(e) {
118
- _this._inputViews.exp.setValue(_this._getUnderlyingValue('exp'));
119
- return _this.render();
120
- });
121
- this.el.underlyingFields.name.bind("change", function(e) {
122
- _this._inputViews.exp.setValue(_this._getUnderlyingValue('name'));
123
- return _this.render();
124
- });
125
- this.el.underlyingFields.cvc.bind("change", function(e) {
126
- _this._inputViews.exp.setValue(_this._getUnderlyingValue('cvc'));
127
- return _this.render();
128
- });
129
- this.el.surfaceFront = $("<div>").attr({
108
+ this.el.front = $("<div>").attr({
130
109
  "class": "face front"
131
110
  });
132
- this.el.surfaceBack = $("<div>").attr({
111
+ this.el.back = $("<div>").attr({
133
112
  "class": "face back"
134
113
  });
135
114
  this.el.cardBody = $("<div>").attr({
136
115
  "class": "card-body"
137
116
  });
138
- this.el.surfaceFront.appendTo(this.el.cardBody);
139
- this.el.surfaceBack.appendTo(this.el.cardBody);
117
+ this.el.front.appendTo(this.el.cardBody);
118
+ this.el.back.appendTo(this.el.cardBody);
140
119
  this.el.cardBody.appendTo(this.el.container);
141
- this._tabViews.front = new this.FlipTabView('front');
142
- this._tabViews.back = new this.FlipTabView('back');
143
- this.el.surfaceFront.prepend(this._tabViews.front.el);
144
- this.el.surfaceBack.prepend(this._tabViews.back.el);
145
- this._tabViews.front.hide();
146
- this._tabViews.back.hide();
147
- this._tabViews.front.el.click(function() {
148
- return _this.flip();
120
+ this._tabViews.front = new Skeuocard.prototype.FlipTabView(this, 'front', {
121
+ strings: this.options.strings
149
122
  });
150
- this._tabViews.back.el.click(function() {
151
- return _this.flip();
123
+ this._tabViews.back = new Skeuocard.prototype.FlipTabView(this, 'back', {
124
+ strings: this.options.strings
152
125
  });
126
+ this.el.front.prepend(this._tabViews.front.el);
127
+ this.el.back.prepend(this._tabViews.back.el);
128
+ this._tabViews.front.hide();
129
+ this._tabViews.back.hide();
130
+ this._inputViews = {
131
+ number: new this.SegmentedCardNumberInputView(),
132
+ exp: new this.ExpirationInputView({
133
+ currentDate: this.options.currentDate
134
+ }),
135
+ name: new this.TextInputView({
136
+ "class": "cc-name",
137
+ placeholder: "YOUR NAME"
138
+ }),
139
+ cvc: new this.TextInputView({
140
+ "class": "cc-cvc",
141
+ placeholder: "XXX",
142
+ requireMaxLength: true
143
+ })
144
+ };
145
+ this._inputViews.number.el.addClass('cc-number');
146
+ this._inputViews.number.el.appendTo(this.el.front);
147
+ this._inputViews.name.el.appendTo(this.el.front);
148
+ this._inputViews.exp.el.addClass('cc-exp');
149
+ this._inputViews.exp.el.appendTo(this.el.front);
150
+ this._inputViews.cvc.el.appendTo(this.el.back);
153
151
  return this.el.container;
154
152
  };
155
153
 
156
- Skeuocard.prototype._setAcceptedCardProducts = function() {
157
- var matcher, product, _ref,
154
+ /*
155
+ Import implicit initialization options from the DOM. Brings in things like
156
+ the accepted card type, initial validation state, existing values, etc.
157
+ */
158
+
159
+
160
+ Skeuocard.prototype._importImplicitOptions = function() {
161
+ var fieldEl, fieldName, _initialExp, _ref,
158
162
  _this = this;
159
- if (this.options.acceptedCardProducts.length === 0) {
163
+ _ref = this.el.underlyingFields;
164
+ for (fieldName in _ref) {
165
+ fieldEl = _ref[fieldName];
166
+ if (this.options.initialValues[fieldName] == null) {
167
+ this.options.initialValues[fieldName] = fieldEl.val();
168
+ } else {
169
+ this.options.initialValues[fieldName] = this.options.initialValues[fieldName].toString();
170
+ this._setUnderlyingValue(fieldName, this.options.initialValues[fieldName]);
171
+ }
172
+ if (this.options.initialValues[fieldName].length > 0) {
173
+ this._state['initiallyFilled'] = true;
174
+ }
175
+ if (this.options.validationState[fieldName] == null) {
176
+ this.options.validationState[fieldName] = !fieldEl.hasClass('invalid');
177
+ }
178
+ }
179
+ if (this.options.acceptedCardProducts == null) {
180
+ this.options.acceptedCardProducts = [];
160
181
  this.el.underlyingFields.type.find('option').each(function(i, _el) {
161
- var cardProductShortname, el;
182
+ var el, shortname;
162
183
  el = $(_el);
163
- cardProductShortname = el.attr('data-card-product-shortname') || el.attr('value');
164
- return _this.options.acceptedCardProducts.push(cardProductShortname);
184
+ shortname = el.attr('data-sc-type') || el.attr('value');
185
+ return _this.options.acceptedCardProducts.push(shortname);
165
186
  });
166
187
  }
167
- for (matcher in CCProducts) {
168
- product = CCProducts[matcher];
169
- if (_ref = product.companyShortname, __indexOf.call(this.options.acceptedCardProducts, _ref) >= 0) {
170
- this.acceptedCardProducts[matcher] = product;
171
- }
188
+ if (this.options.initialValues.number.length > 0) {
189
+ this.set('number', this.options.initialValues.number);
172
190
  }
173
- return this.acceptedCardProducts;
191
+ if (this.options.initialValues.name.length > 0) {
192
+ this.set('name', this.options.initialValues.name);
193
+ }
194
+ if (this.options.initialValues.cvc.length > 0) {
195
+ this.set('cvc', this.options.initialValues.cvc);
196
+ }
197
+ if (this.options.initialValues.expYear.length > 0 && this.options.initialValues.expMonth.length > 0) {
198
+ _initialExp = new Date(parseInt(this.options.initialValues.expYear), parseInt(this.options.initialValues.expMonth) - 1, 1);
199
+ this.set('exp', _initialExp);
200
+ }
201
+ this._updateValidationForFace('front');
202
+ return this._updateValidationForFace('back');
174
203
  };
175
204
 
176
- Skeuocard.prototype._updateProductIfNeeded = function() {
177
- var matchedIssuerIdentifier, matchedProduct, matchedProductIdentifier, number;
178
- number = this._getUnderlyingValue('number');
179
- matchedProduct = this.getProductForNumber(number);
180
- matchedProductIdentifier = (matchedProduct != null ? matchedProduct.companyShortname : void 0) || '';
181
- matchedIssuerIdentifier = (matchedProduct != null ? matchedProduct.issuerShortname : void 0) || '';
182
- if ((this.productShortname !== matchedProductIdentifier) || (this.issuerShortname !== matchedIssuerIdentifier)) {
183
- this.productShortname = matchedProductIdentifier;
184
- this.issuerShortname = matchedIssuerIdentifier;
185
- this.product = matchedProduct;
186
- this._cardProductNeedsLayout = true;
187
- this.trigger('productWillChange.skeuocard', [this, this.productShortname, matchedProductIdentifier]);
188
- this._log("Triggering render because product changed.");
189
- this.render();
190
- return this.trigger('productDidChange.skeuocard', [this, this.productShortname, matchedProductIdentifier]);
191
- }
205
+ Skeuocard.prototype.set = function(field, newValue) {
206
+ this._inputViews[field].setValue(newValue);
207
+ return this._inputViews[field].trigger('valueChanged', this._inputViews[field]);
192
208
  };
193
209
 
194
- Skeuocard.prototype._createInputs = function() {
195
- var _this = this;
196
- this._inputViews.number = new this.SegmentedCardNumberInputView();
197
- this._inputViews.exp = new this.ExpirationInputView({
198
- currentDate: this.options.currentDate
210
+ /*
211
+ Bind interaction events to their appropriate handlers.
212
+ */
213
+
214
+
215
+ Skeuocard.prototype._bindInputEvents = function() {
216
+ var _expirationChange,
217
+ _this = this;
218
+ this.el.underlyingFields.number.bind("change", function(e) {
219
+ _this._inputViews.number.setValue(_this._getUnderlyingValue('number'));
220
+ return _this.render();
199
221
  });
200
- this._inputViews.name = new this.TextInputView({
201
- "class": "cc-name",
202
- placeholder: "YOUR NAME"
222
+ _expirationChange = function(e) {
223
+ var month, year;
224
+ month = parseInt(_this._getUnderlyingValue('expMonth'));
225
+ year = parseInt(_this._getUnderlyingValue('expYear'));
226
+ _this._inputViews.exp.setValue(new Date(year, month - 1));
227
+ return _this.render();
228
+ };
229
+ this.el.underlyingFields.expMonth.bind("change", _expirationChange);
230
+ this.el.underlyingFields.expYear.bind("change", _expirationChange);
231
+ this.el.underlyingFields.name.bind("change", function(e) {
232
+ _this._inputViews.exp.setValue(_this._getUnderlyingValue('name'));
233
+ return _this.render();
203
234
  });
204
- this._inputViews.cvc = new this.TextInputView({
205
- "class": "cc-cvc",
206
- placeholder: "XXX",
207
- requireMaxLength: true
235
+ this.el.underlyingFields.cvc.bind("change", function(e) {
236
+ _this._inputViews.exp.setValue(_this._getUnderlyingValue('cvc'));
237
+ return _this.render();
208
238
  });
209
- this._inputViews.number.el.addClass('cc-number');
210
- this._inputViews.number.el.appendTo(this.el.surfaceFront);
211
- this._inputViews.name.el.appendTo(this.el.surfaceFront);
212
- this._inputViews.exp.el.addClass('cc-exp');
213
- this._inputViews.exp.el.appendTo(this.el.surfaceFront);
214
- this._inputViews.cvc.el.appendTo(this.el.surfaceBack);
215
- this._inputViews.number.bind("change", function(e, input) {
216
- _this._setUnderlyingValue('number', input.getValue());
217
- _this._updateValidationStateForInputView('number');
218
- return _this._updateProductIfNeeded();
239
+ this._inputViews.number.bind("change valueChanged", function(e, input) {
240
+ var cardNumber, matchedProduct, number, previousProduct, _ref, _ref1;
241
+ cardNumber = input.getValue();
242
+ _this._setUnderlyingValue('number', cardNumber);
243
+ _this._updateValidation('number', cardNumber);
244
+ number = _this._getUnderlyingValue('number');
245
+ matchedProduct = Skeuocard.prototype.CardProduct.firstMatchingNumber(number);
246
+ if (!((_ref = _this.product) != null ? _ref.eql(matchedProduct) : void 0)) {
247
+ _this._log("Product will change:", _this.product, "=>", matchedProduct);
248
+ if (_ref1 = matchedProduct != null ? matchedProduct.attrs.companyShortname : void 0, __indexOf.call(_this.options.acceptedCardProducts, _ref1) >= 0) {
249
+ _this.trigger('productWillChange.skeuocard', [_this, _this.product, matchedProduct]);
250
+ previousProduct = _this.product;
251
+ _this.el.container.removeClass('unaccepted');
252
+ _this._renderProduct(matchedProduct);
253
+ _this.product = matchedProduct;
254
+ } else if (matchedProduct != null) {
255
+ _this.trigger('productWillChange.skeuocard', [_this, _this.product, null]);
256
+ _this.el.container.addClass('unaccepted');
257
+ _this._renderProduct(null);
258
+ _this.product = null;
259
+ } else {
260
+ _this.trigger('productWillChange.skeuocard', [_this, _this.product, null]);
261
+ _this.el.container.removeClass('unaccepted');
262
+ _this._renderProduct(null);
263
+ _this.product = null;
264
+ }
265
+ return _this.trigger('productDidChange.skeuocard', [_this, previousProduct, _this.product]);
266
+ }
219
267
  });
220
- this._inputViews.exp.bind("keyup", function(e, input) {
221
- _this._setUnderlyingValue('exp', input.value);
222
- return _this._updateValidationStateForInputView('exp');
268
+ this._inputViews.exp.bind("keyup valueChanged", function(e, input) {
269
+ var newDate;
270
+ newDate = input.getValue();
271
+ _this._updateValidation('exp', newDate);
272
+ if (newDate != null) {
273
+ _this._setUnderlyingValue('expMonth', newDate.getMonth() + 1);
274
+ return _this._setUnderlyingValue('expYear', newDate.getFullYear());
275
+ }
223
276
  });
224
- this._inputViews.name.bind("keyup", function(e) {
225
- _this._setUnderlyingValue('name', $(e.target).val());
226
- return _this._updateValidationStateForInputView('name');
277
+ this._inputViews.name.bind("keyup valueChanged", function(e, input) {
278
+ var value;
279
+ value = input.getValue();
280
+ _this._setUnderlyingValue('name', value);
281
+ return _this._updateValidation('name', value);
227
282
  });
228
- this._inputViews.cvc.bind("keyup", function(e) {
229
- _this._setUnderlyingValue('cvc', $(e.target).val());
230
- return _this._updateValidationStateForInputView('cvc');
283
+ this._inputViews.cvc.bind("keyup valueChanged", function(e, input) {
284
+ var value;
285
+ value = input.getValue();
286
+ _this._setUnderlyingValue('cvc', value);
287
+ return _this._updateValidation('cvc', value);
288
+ });
289
+ this.el.container.delegate("input", "keyup keydown", this._handleFieldTab.bind(this));
290
+ this._tabViews.front.el.click(function() {
291
+ return _this.flip();
292
+ });
293
+ return this._tabViews.back.el.click(function() {
294
+ return _this.flip();
231
295
  });
232
- this._inputViews.number.setValue(this._getUnderlyingValue('number'));
233
- this._inputViews.exp.setValue(this._getUnderlyingValue('exp'));
234
- this._inputViews.name.el.val(this._getUnderlyingValue('name'));
235
- return this._inputViews.cvc.el.val(this._getUnderlyingValue('cvc'));
236
- };
237
-
238
- Skeuocard.prototype._log = function() {
239
- var msg;
240
- msg = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
241
- if ((typeof console !== "undefined" && console !== null ? console.log : void 0) && !!this.options.debug) {
242
- if (this.options.debug != null) {
243
- return console.log.apply(console, ["[skeuocard]"].concat(__slice.call(msg)));
244
- }
245
- }
246
296
  };
247
297
 
248
- Skeuocard.prototype._flipToInvalidSide = function() {
249
- var fieldName, state, _errorCounts, _oppositeFace, _ref, _ref1;
250
- if (Object.keys(this._initialValidationState).length > 0) {
298
+ Skeuocard.prototype._handleFieldTab = function(e) {
299
+ var backFieldEls, currentFieldEl, frontFieldEls, _currentFace, _oppositeFace;
300
+ if (e.which === 9) {
301
+ currentFieldEl = $(e.currentTarget);
251
302
  _oppositeFace = this.visibleFace === 'front' ? 'back' : 'front';
252
- _errorCounts = {
253
- front: 0,
254
- back: 0
255
- };
256
- _ref = this._initialValidationState;
257
- for (fieldName in _ref) {
258
- state = _ref[fieldName];
259
- _errorCounts[(_ref1 = this.product) != null ? _ref1.layout[fieldName] : void 0]++;
303
+ _currentFace = this.visibleFace === 'front' ? 'front' : 'back';
304
+ backFieldEls = this.el[_oppositeFace].find('input');
305
+ frontFieldEls = this.el[_currentFace].find('input');
306
+ if (this.visibleFace === 'front' && this.el.front.hasClass('filled') && backFieldEls.length > 0 && frontFieldEls.index(currentFieldEl) === frontFieldEls.length - 1 && !e.shiftKey) {
307
+ this.flip();
308
+ backFieldEls.first().focus();
309
+ e.preventDefault();
260
310
  }
261
- if (_errorCounts[this.visibleFace] === 0 && _errorCounts[_oppositeFace] > 0) {
262
- return this.flip();
311
+ if (this.visibleFace === 'back' && e.shiftKey) {
312
+ this.flip();
313
+ backFieldEls.last().focus();
314
+ e.preventDefault();
263
315
  }
264
316
  }
317
+ return true;
265
318
  };
266
319
 
267
- Skeuocard.prototype.render = function() {
268
- var container, el, fieldName, inputEl, sel, surfaceName, _hiddenFaceFilled, _hiddenFaceValid, _oppositeFace, _ref, _visibleFaceFilled, _visibleFaceValid,
269
- _this = this;
270
- this._log("*** start rendering ***");
271
- if (this._cardProductNeedsLayout === true) {
272
- if (this.product !== void 0) {
273
- this._log("[render]", "Activating product", this.product);
274
- this.el.container.removeClass(function(index, css) {
275
- return (css.match(/\b(product|issuer)-\S+/g) || []).join(' ');
276
- });
277
- this.el.container.addClass("product-" + this.product.companyShortname);
278
- if (this.product.issuerShortname != null) {
279
- this.el.container.addClass("issuer-" + this.product.issuerShortname);
280
- }
281
- this._setUnderlyingCardType(this.product.companyShortname);
282
- this._inputViews.number.setGroupings(this.product.cardNumberGrouping);
283
- this._inputViews.exp.show();
284
- this._inputViews.name.show();
285
- this._inputViews.exp.reconfigure({
286
- pattern: this.product.expirationFormat
287
- });
288
- this._inputViews.cvc.show();
289
- this._inputViews.cvc.attr({
290
- maxlength: this.product.cvcLength,
291
- placeholder: new Array(this.product.cvcLength + 1).join(this.options.cardNumberPlaceholderChar)
292
- });
293
- _ref = this.product.layout;
294
- for (fieldName in _ref) {
295
- surfaceName = _ref[fieldName];
296
- sel = surfaceName === 'front' ? 'surfaceFront' : 'surfaceBack';
297
- container = this.el[sel];
298
- inputEl = this._inputViews[fieldName].el;
299
- if (!(container.has(inputEl).length > 0)) {
300
- this._log("Moving", inputEl, "=>", container);
301
- el = this._inputViews[fieldName].el.detach();
302
- $(el).appendTo(this.el[sel]);
303
- }
304
- }
305
- } else {
306
- this._log("[render]", "Becoming generic.");
307
- this._inputViews.exp.clear();
308
- this._inputViews.cvc.clear();
309
- this._inputViews.exp.hide();
310
- this._inputViews.name.hide();
311
- this._inputViews.cvc.hide();
312
- this._inputViews.number.setGroupings([this.options.genericPlaceholder.length]);
313
- /*
314
- @_inputViews.number.reconfigure
315
- groupings: [@options.genericPlaceholder.length],
316
- placeholder: @options.genericPlaceholder
317
- */
318
-
319
- this.el.container.removeClass(function(index, css) {
320
- return (css.match(/\bproduct-\S+/g) || []).join(' ');
321
- });
322
- this.el.container.removeClass(function(index, css) {
323
- return (css.match(/\bissuer-\S+/g) || []).join(' ');
324
- });
325
- }
326
- this._cardProductNeedsLayout = false;
327
- }
328
- this._log("Validation state:", this._validationState);
329
- this.showInitialValidationErrors();
330
- _oppositeFace = this.visibleFace === 'front' ? 'back' : 'front';
331
- _visibleFaceFilled = this._faceFillState[this.visibleFace];
332
- _visibleFaceValid = this.isFaceValid(this.visibleFace);
333
- _hiddenFaceFilled = this._faceFillState[_oppositeFace];
334
- _hiddenFaceValid = this.isFaceValid(_oppositeFace);
335
- if (_visibleFaceFilled && !_visibleFaceValid) {
336
- this._log("Visible face is filled, but invalid; showing validation errors.");
337
- this.showValidationErrors();
338
- } else if (!_visibleFaceFilled) {
339
- this._log("Visible face hasn't been filled; hiding validation errors.");
340
- this.hideValidationErrors();
341
- } else {
342
- this._log("Visible face has been filled, and is valid.");
343
- this.hideValidationErrors();
344
- }
345
- if (this.visibleFace === 'front' && this.fieldsForFace('back').length > 0) {
346
- if (_visibleFaceFilled && _visibleFaceValid && !_hiddenFaceFilled) {
347
- this._tabViews.front.prompt(this.options.strings.hiddenFaceFillPrompt, true);
348
- } else if (_hiddenFaceFilled && !_hiddenFaceValid) {
349
- this._tabViews.front.warn(this.options.strings.hiddenFaceErrorWarning, true);
350
- } else if (_hiddenFaceFilled && _hiddenFaceValid) {
351
- this._tabViews.front.prompt(this.options.strings.hiddenFaceSwitchPrompt, true);
352
- } else {
353
- this._tabViews.front.hide();
320
+ Skeuocard.prototype._updateValidation = function(fieldName, newValue) {
321
+ var fillStateChanged, isFilled, isFixed, isValid, needsFix, validationStateChanged;
322
+ if (this.product == null) {
323
+ return false;
324
+ }
325
+ isFilled = this.product[fieldName].isFilled(newValue);
326
+ needsFix = (this.options.validationState[fieldName] != null) === false;
327
+ isFixed = (this.options.initialValues[fieldName] != null) && newValue !== this.options.initialValues[fieldName];
328
+ isValid = this.product[fieldName].isValid(newValue) && ((needsFix && isFixed) || true);
329
+ fillStateChanged = this._state["" + fieldName + "Filled"] !== isFilled;
330
+ validationStateChanged = this._state["" + fieldName + "Valid"] !== isValid;
331
+ if (fillStateChanged) {
332
+ this.trigger("fieldFillStateWillChange.skeuocard", [this, fieldName, isFilled]);
333
+ this._inputViews[fieldName].el.toggleClass('filled', isFilled);
334
+ this._state["" + fieldName + "Filled"] = isFilled;
335
+ this.trigger("fieldFillStateDidChange.skeuocard", [this, fieldName, isFilled]);
336
+ }
337
+ if (validationStateChanged) {
338
+ this.trigger("fieldValidationStateWillChange.skeuocard", [this, fieldName, isFilled]);
339
+ this._inputViews[fieldName].el.toggleClass('valid', isValid);
340
+ this._inputViews[fieldName].el.toggleClass('invalid', !isValid);
341
+ this._state["" + fieldName + "Valid"] = isValid;
342
+ this.trigger("fieldValidationStateDidChange.skeuocard", [this, fieldName, isFilled]);
343
+ }
344
+ return this._updateValidationForFace(this.visibleFace);
345
+ };
346
+
347
+ Skeuocard.prototype._updateValidationForFace = function(face) {
348
+ var fieldsFilled, fieldsValid, fillStateChanged, isFilled, isValid, iv, validationStateChanged;
349
+ fieldsFilled = ((function() {
350
+ var _i, _len, _ref, _results;
351
+ _ref = this._inputViewsByFace[face];
352
+ _results = [];
353
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
354
+ iv = _ref[_i];
355
+ _results.push(iv.el.hasClass('filled'));
354
356
  }
355
- } else {
356
- if (_hiddenFaceValid) {
357
- this._tabViews.back.prompt(this.options.strings.hiddenFaceSwitchPrompt, true);
358
- } else {
359
- this._tabViews.back.warn(this.options.strings.hiddenFaceErrorWarning, true);
357
+ return _results;
358
+ }).call(this)).every(Boolean);
359
+ fieldsValid = ((function() {
360
+ var _i, _len, _ref, _results;
361
+ _ref = this._inputViewsByFace[face];
362
+ _results = [];
363
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
364
+ iv = _ref[_i];
365
+ _results.push(iv.el.hasClass('valid'));
360
366
  }
367
+ return _results;
368
+ }).call(this)).every(Boolean);
369
+ isFilled = (fieldsFilled && (this.product != null)) || (this._state['initiallyFilled'] || false);
370
+ isValid = fieldsValid && (this.product != null);
371
+ fillStateChanged = this._state["" + face + "Filled"] !== isFilled;
372
+ validationStateChanged = this._state["" + face + "Valid"] !== isValid;
373
+ if (fillStateChanged) {
374
+ this.trigger("faceFillStateWillChange.skeuocard", [this, face, isFilled]);
375
+ this.el[face].toggleClass('filled', isFilled);
376
+ this._state["" + face + "Filled"] = isFilled;
377
+ this.trigger("faceFillStateDidChange.skeuocard", [this, face, isFilled]);
378
+ }
379
+ if (validationStateChanged) {
380
+ this.trigger("faceValidationStateWillChange.skeuocard", [this, face, isValid]);
381
+ this.el[face].toggleClass('valid', isValid);
382
+ this.el[face].toggleClass('invalid', !isValid);
383
+ this._state["" + face + "Valid"] = isValid;
384
+ return this.trigger("faceValidationStateDidChange.skeuocard", [this, face, isValid]);
385
+ }
386
+ };
387
+
388
+ /*
389
+ Assert rendering changes necessary for the current product. Passing a null
390
+ value instead of a product will revert the card to a generic state.
391
+ */
392
+
393
+
394
+ Skeuocard.prototype._renderProduct = function(product) {
395
+ var destFace, fieldName, focused, view, viewEl, _ref, _ref1,
396
+ _this = this;
397
+ this._log("[_renderProduct]", "Rendering product:", product);
398
+ this.el.container.removeClass(function(index, css) {
399
+ return (css.match(/\b(product|issuer)-\S+/g) || []).join(' ');
400
+ });
401
+ if ((product != null ? product.attrs.companyShortname : void 0) != null) {
402
+ this.el.container.addClass("product-" + product.attrs.companyShortname);
361
403
  }
362
- if (!this.isValid()) {
363
- this.el.container.removeClass('valid');
364
- this.el.container.addClass('invalid');
365
- } else {
366
- this.el.container.addClass('valid');
367
- this.el.container.removeClass('invalid');
404
+ if ((product != null ? product.attrs.issuerShortname : void 0) != null) {
405
+ this.el.container.addClass("issuer-" + product.attrs.issuerShortname);
368
406
  }
369
- return this._log("*** rendering complete ***");
370
- };
371
-
372
- Skeuocard.prototype.showInitialValidationErrors = function() {
373
- var fieldName, state, _ref, _results;
374
- _ref = this._initialValidationState;
375
- _results = [];
376
- for (fieldName in _ref) {
377
- state = _ref[fieldName];
378
- if (state === false && this._validationState[fieldName] === false) {
379
- _results.push(this._inputViews[fieldName].addClass('invalid'));
380
- } else {
381
- _results.push(this._inputViews[fieldName].removeClass('invalid'));
407
+ this._setUnderlyingValue('type', (product != null ? product.attrs.companyShortname : void 0) || null);
408
+ this._inputViews.number.setGroupings((product != null ? product.attrs.cardNumberGrouping : void 0) || [this.options.genericPlaceholder.length]);
409
+ if (product != null) {
410
+ this._inputViews.exp.reconfigure({
411
+ pattern: (product != null ? product.attrs.expirationFormat : void 0) || "MM/YY"
412
+ });
413
+ this._inputViews.cvc.attr({
414
+ maxlength: product.attrs.cvcLength,
415
+ placeholder: new Array(product.attrs.cvcLength + 1).join(this.options.cardNumberPlaceholderChar)
416
+ });
417
+ this._inputViewsByFace = {
418
+ front: [],
419
+ back: []
420
+ };
421
+ focused = $('*:focus');
422
+ _ref = product.attrs.layout;
423
+ for (fieldName in _ref) {
424
+ destFace = _ref[fieldName];
425
+ this._log("Moving", fieldName, "to", destFace);
426
+ viewEl = this._inputViews[fieldName].el.detach();
427
+ viewEl.appendTo(this.el[destFace]);
428
+ this._inputViewsByFace[destFace].push(this._inputViews[fieldName]);
429
+ this._inputViews[fieldName].show();
382
430
  }
383
- }
384
- return _results;
385
- };
386
-
387
- Skeuocard.prototype.showValidationErrors = function() {
388
- var fieldName, state, _ref, _results;
389
- _ref = this._validationState;
390
- _results = [];
391
- for (fieldName in _ref) {
392
- state = _ref[fieldName];
393
- if (state === true) {
394
- _results.push(this._inputViews[fieldName].removeClass('invalid'));
395
- } else {
396
- _results.push(this._inputViews[fieldName].addClass('invalid'));
431
+ setTimeout(function() {
432
+ var fieldEl, fieldLength;
433
+ fieldEl = focused.first();
434
+ fieldLength = fieldEl[0].maxLength;
435
+ fieldEl.focus();
436
+ return fieldEl[0].setSelectionRange(fieldLength, fieldLength);
437
+ }, 10);
438
+ } else {
439
+ _ref1 = this._inputViews;
440
+ for (fieldName in _ref1) {
441
+ view = _ref1[fieldName];
442
+ if (fieldName !== 'number') {
443
+ view.hide();
444
+ }
397
445
  }
398
446
  }
399
- return _results;
447
+ return product;
400
448
  };
401
449
 
402
- Skeuocard.prototype.hideValidationErrors = function() {
403
- var fieldName, state, _ref, _results;
404
- _ref = this._validationState;
450
+ Skeuocard.prototype._renderValidation = function() {
451
+ var fieldName, fieldView, _ref, _results;
452
+ _ref = this._inputViews;
405
453
  _results = [];
406
454
  for (fieldName in _ref) {
407
- state = _ref[fieldName];
408
- if ((this._initialValidationState[fieldName] === false && state === true) || (!(this._initialValidationState[fieldName] != null))) {
409
- _results.push(this._inputViews[fieldName].el.removeClass('invalid'));
410
- } else {
411
- _results.push(void 0);
412
- }
455
+ fieldView = _ref[fieldName];
456
+ _results.push(this._updateValidation(fieldName, fieldView.getValue()));
413
457
  }
414
458
  return _results;
415
459
  };
416
460
 
417
- Skeuocard.prototype.setFieldValidationState = function(fieldName, valid) {
418
- if (valid) {
419
- this.el.underlyingFields[fieldName].removeClass('invalid');
420
- } else {
421
- this.el.underlyingFields[fieldName].addClass('invalid');
422
- }
423
- return this._validationState[fieldName] = valid;
424
- };
425
-
426
- Skeuocard.prototype.isFaceFilled = function(faceName) {
427
- var fields, filled, name;
428
- fields = this.fieldsForFace(faceName);
429
- filled = (function() {
430
- var _i, _len, _results;
431
- _results = [];
432
- for (_i = 0, _len = fields.length; _i < _len; _i++) {
433
- name = fields[_i];
434
- if (this._inputViews[name].isFilled()) {
435
- _results.push(name);
436
- }
437
- }
438
- return _results;
439
- }).call(this);
440
- if (fields.length > 0) {
441
- return filled.length === fields.length;
442
- } else {
443
- return false;
444
- }
445
- };
446
-
447
- Skeuocard.prototype.fieldsForFace = function(faceName) {
448
- var face, fn, _ref;
449
- if ((_ref = this.product) != null ? _ref.layout : void 0) {
450
- return (function() {
451
- var _ref1, _results;
452
- _ref1 = this.product.layout;
453
- _results = [];
454
- for (fn in _ref1) {
455
- face = _ref1[fn];
456
- if (face === faceName) {
457
- _results.push(fn);
458
- }
459
- }
460
- return _results;
461
- }).call(this);
462
- }
463
- return [];
464
- };
465
-
466
- Skeuocard.prototype._updateValidationStateForInputView = function(fieldName) {
467
- var field, fieldValid;
468
- field = this._inputViews[fieldName];
469
- fieldValid = field.isValid() && !(this._initialValidationState[fieldName] === false && field.getValue() === this.options.initialValues[fieldName]);
470
- if (fieldValid !== this._validationState[fieldName]) {
471
- this.setFieldValidationState(fieldName, fieldValid);
472
- this._faceFillState.front = this.isFaceFilled('front');
473
- this._faceFillState.back = this.isFaceFilled('back');
474
- this.trigger('validationStateDidChange.skeuocard', [this, this._validationState]);
475
- this._log("Change in validation for " + fieldName + " triggers re-render.");
476
- return this.render();
477
- }
478
- };
479
-
480
- Skeuocard.prototype.isFaceValid = function(faceName) {
481
- var fieldName, valid, _i, _len, _ref;
482
- valid = true;
483
- _ref = this.fieldsForFace(faceName);
484
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
485
- fieldName = _ref[_i];
486
- valid &= this._validationState[fieldName];
487
- }
488
- return !!valid;
489
- };
490
-
491
- Skeuocard.prototype.isValid = function() {
492
- return this._validationState.number && this._validationState.exp && this._validationState.name && this._validationState.cvc;
493
- };
494
-
495
- Skeuocard.prototype._getUnderlyingValue = function(field) {
496
- return this.el.underlyingFields[field].val();
497
- };
498
-
499
- Skeuocard.prototype._setUnderlyingValue = function(field, newValue) {
500
- this.trigger('change.skeuocard', [this]);
501
- return this.el.underlyingFields[field].val(newValue);
461
+ Skeuocard.prototype.render = function() {
462
+ this._renderProduct(this.product);
463
+ return this._renderValidation();
502
464
  };
503
465
 
504
466
  Skeuocard.prototype.flip = function() {
@@ -506,122 +468,123 @@
506
468
  targetFace = this.visibleFace === 'front' ? 'back' : 'front';
507
469
  this.trigger('faceWillBecomeVisible.skeuocard', [this, targetFace]);
508
470
  this.visibleFace = targetFace;
509
- this.render();
510
471
  this.el.cardBody.toggleClass('flip');
511
- surfaceName = this.visibleFace === 'front' ? 'surfaceFront' : 'surfaceBack';
512
- this.el[surfaceName].find('input').first().focus();
472
+ surfaceName = this.visibleFace === 'front' ? 'front' : 'back';
473
+ this.el[surfaceName].find('.cc-field').not('.filled').find('input').first().focus();
513
474
  return this.trigger('faceDidBecomeVisible.skeuocard', [this, targetFace]);
514
475
  };
515
476
 
516
- Skeuocard.prototype.getProductForNumber = function(num) {
517
- var d, issuer, m, matcher, parts, _ref;
518
- _ref = this.acceptedCardProducts;
519
- for (m in _ref) {
520
- d = _ref[m];
521
- parts = m.split('/');
522
- matcher = new RegExp(parts[1], parts[2]);
523
- if (matcher.test(num)) {
524
- issuer = this.getIssuerForNumber(num) || {};
525
- return $.extend({}, d, issuer);
526
- }
477
+ Skeuocard.prototype._setUnderlyingValue = function(field, newValue) {
478
+ var fieldEl, remapAttrKey, _newValue,
479
+ _this = this;
480
+ fieldEl = this.el.underlyingFields[field];
481
+ _newValue = (newValue || "").toString();
482
+ if (fieldEl == null) {
483
+ throw "Set underlying value of unknown field: " + field + ".";
527
484
  }
528
- return void 0;
529
- };
530
-
531
- Skeuocard.prototype.getIssuerForNumber = function(num) {
532
- var d, m, matcher, parts;
533
- for (m in CCIssuers) {
534
- d = CCIssuers[m];
535
- parts = m.split('/');
536
- matcher = new RegExp(parts[1], parts[2]);
537
- if (matcher.test(num)) {
538
- return d;
539
- }
485
+ this.trigger('change.skeuocard', [this]);
486
+ if (!fieldEl.is('select')) {
487
+ return this.el.underlyingFields[field].val(_newValue);
488
+ } else {
489
+ remapAttrKey = "data-sc-" + field.toLowerCase();
490
+ return fieldEl.find('option').each(function(i, _el) {
491
+ var optionEl;
492
+ optionEl = $(_el);
493
+ if (_newValue === (optionEl.attr(remapAttrKey) || optionEl.attr('value'))) {
494
+ return _this.el.underlyingFields[field].val(optionEl.attr('value'));
495
+ }
496
+ });
540
497
  }
541
- return void 0;
542
498
  };
543
499
 
544
- Skeuocard.prototype._setUnderlyingCardType = function(shortname) {
545
- var _this = this;
546
- return this.el.underlyingFields.type.find('option').each(function(i, _el) {
547
- var el;
548
- el = $(_el);
549
- if (shortname === (el.attr('data-card-product-shortname') || el.attr('value'))) {
550
- return el.val(el.attr('value'));
551
- }
552
- });
553
- };
554
-
555
- Skeuocard.prototype.trigger = function() {
556
- var args, _ref;
557
- args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
558
- return (_ref = this.el.container).trigger.apply(_ref, args);
500
+ Skeuocard.prototype._getUnderlyingValue = function(field) {
501
+ var _ref;
502
+ return (_ref = this.el.underlyingFields[field]) != null ? _ref.val() : void 0;
559
503
  };
560
504
 
561
- Skeuocard.prototype.bind = function() {
562
- var args, _ref;
563
- args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
564
- return (_ref = this.el.container).trigger.apply(_ref, args);
505
+ Skeuocard.prototype.isValid = function() {
506
+ return !this.el.front.hasClass('invalid') && !this.el.back.hasClass('invalid');
565
507
  };
566
508
 
567
509
  return Skeuocard;
568
510
 
569
511
  })();
570
512
 
513
+ window.Skeuocard = Skeuocard;
514
+
571
515
  /*
572
516
  Skeuocard::FlipTabView
573
517
  Handles rendering of the "flip button" control and its various warning and
574
518
  prompt states.
575
-
576
- TODO: Rebuild this so that it observes events and contains its own logic.
577
519
  */
578
520
 
579
521
 
580
522
  Skeuocard.prototype.FlipTabView = (function() {
581
-
582
- function FlipTabView(face, opts) {
523
+ function FlipTabView(sc, face, opts) {
524
+ var _this = this;
583
525
  if (opts == null) {
584
526
  opts = {};
585
527
  }
528
+ this.card = sc;
529
+ this.face = face;
586
530
  this.el = $("<div class=\"flip-tab " + face + "\"><p></p></div>");
587
531
  this.options = opts;
532
+ this._state = {};
533
+ this.card.bind('faceFillStateWillChange.skeuocard', this._faceStateChanged.bind(this));
534
+ this.card.bind('faceValidationStateWillChange.skeuocard', this._faceValidationChanged.bind(this));
535
+ this.card.bind('productWillChange.skeuocard', function(e, card, prevProduct, newProduct) {
536
+ if (newProduct == null) {
537
+ return _this.hide();
538
+ }
539
+ });
588
540
  }
589
541
 
590
- FlipTabView.prototype._setText = function(text) {
591
- return this.el.find('p').html(text);
542
+ FlipTabView.prototype._faceStateChanged = function(e, card, face, isFilled) {
543
+ var oppositeFace;
544
+ oppositeFace = face === 'front' ? 'back' : 'front';
545
+ if (isFilled === true && this.card._inputViewsByFace[oppositeFace].length > 0) {
546
+ this.show();
547
+ }
548
+ if (face !== this.face) {
549
+ this._state.opposingFaceFilled = isFilled;
550
+ }
551
+ if (this._state.opposingFaceFilled !== true) {
552
+ return this.warn(this.options.strings.hiddenFaceFillPrompt, true);
553
+ }
592
554
  };
593
555
 
594
- FlipTabView.prototype.warn = function(message, withAnimation) {
595
- if (withAnimation == null) {
596
- withAnimation = false;
556
+ FlipTabView.prototype._faceValidationChanged = function(e, card, face, isValid) {
557
+ if (face !== this.face) {
558
+ this._state.opposingFaceValid = isValid;
597
559
  }
560
+ if (this._state.opposingFaceValid) {
561
+ return this.prompt(this.options.strings.hiddenFaceSwitchPrompt);
562
+ } else {
563
+ if (this._state.opposingFaceFilled) {
564
+ return this.warn(this.options.strings.hiddenFaceErrorWarning);
565
+ } else {
566
+ return this.warn(this.options.strings.hiddenFaceFillPrompt);
567
+ }
568
+ }
569
+ };
570
+
571
+ FlipTabView.prototype._setText = function(text) {
572
+ return this.el.find('p').first().html(text);
573
+ };
574
+
575
+ FlipTabView.prototype.warn = function(message) {
598
576
  this._resetClasses();
599
- this.el.addClass('warn');
600
577
  this._setText(message);
601
- this.show();
602
- if (withAnimation) {
603
- this.el.removeClass('warn-anim');
604
- return this.el.addClass('warn-anim');
605
- }
578
+ return this.el.addClass('warn');
606
579
  };
607
580
 
608
- FlipTabView.prototype.prompt = function(message, withAnimation) {
609
- if (withAnimation == null) {
610
- withAnimation = false;
611
- }
581
+ FlipTabView.prototype.prompt = function(message) {
612
582
  this._resetClasses();
613
- this.el.addClass('prompt');
614
583
  this._setText(message);
615
- this.show();
616
- if (withAnimation) {
617
- this.el.removeClass('valid-anim');
618
- return this.el.addClass('valid-anim');
619
- }
584
+ return this.el.addClass('prompt');
620
585
  };
621
586
 
622
587
  FlipTabView.prototype._resetClasses = function() {
623
- this.el.removeClass('valid-anim');
624
- this.el.removeClass('warn-anim');
625
588
  this.el.removeClass('warn');
626
589
  return this.el.removeClass('prompt');
627
590
  };
@@ -639,95 +602,29 @@
639
602
  })();
640
603
 
641
604
  /*
642
- Skeuocard::TextInputView
605
+ # Skeuocard::SegmentedCardNumberInputView
606
+ # Provides a reconfigurable segmented input view for credit card numbers.
643
607
  */
644
608
 
645
609
 
646
- Skeuocard.prototype.TextInputView = (function() {
647
-
648
- function TextInputView() {}
649
-
650
- TextInputView.prototype.bind = function() {
651
- var args, _ref;
652
- args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
653
- return (_ref = this.el).bind.apply(_ref, args);
654
- };
655
-
656
- TextInputView.prototype.trigger = function() {
657
- var args, _ref;
658
- args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
659
- return (_ref = this.el).trigger.apply(_ref, args);
660
- };
661
-
662
- TextInputView.prototype._getFieldCaretPosition = function(el) {
663
- var input, sel, selLength;
664
- input = el.get(0);
665
- if (input.selectionEnd != null) {
666
- return input.selectionEnd;
667
- } else if (document.selection) {
668
- input.focus();
669
- sel = document.selection.createRange();
670
- selLength = document.selection.createRange().text.length;
671
- sel.moveStart('character', -input.value.length);
672
- return selLength;
673
- }
674
- };
675
-
676
- TextInputView.prototype._setFieldCaretPosition = function(el, pos) {
677
- var input, range;
678
- input = el.get(0);
679
- if (input.createTextRange != null) {
680
- range = input.createTextRange();
681
- range.move("character", pos);
682
- return range.select();
683
- } else if (input.selectionStart != null) {
684
- input.focus();
685
- return input.setSelectionRange(pos, pos);
686
- }
687
- };
688
-
689
- TextInputView.prototype.show = function() {
690
- return this.el.show();
691
- };
692
-
693
- TextInputView.prototype.hide = function() {
694
- return this.el.hide();
695
- };
696
-
697
- TextInputView.prototype.addClass = function() {
698
- var args, _ref;
699
- args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
700
- return (_ref = this.el).addClass.apply(_ref, args);
701
- };
702
-
703
- TextInputView.prototype.removeClass = function() {
704
- var args, _ref;
705
- args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
706
- return (_ref = this.el).removeClass.apply(_ref, args);
707
- };
708
-
709
- TextInputView.prototype._zeroPadNumber = function(num, places) {
710
- var zero;
711
- zero = places - num.toString().length + 1;
712
- return Array(zero).join("0") + num;
713
- };
714
-
715
- return TextInputView;
716
-
717
- })();
718
-
719
610
  Skeuocard.prototype.SegmentedCardNumberInputView = (function() {
720
-
721
611
  SegmentedCardNumberInputView.prototype._digits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
722
612
 
723
- SegmentedCardNumberInputView.prototype._arrowKeys = {
724
- left: 37,
725
- up: 38,
726
- right: 39,
727
- down: 40
613
+ SegmentedCardNumberInputView.prototype._keys = {
614
+ backspace: 8,
615
+ tab: 9,
616
+ enter: 13,
617
+ del: 46,
618
+ arrowLeft: 37,
619
+ arrowUp: 38,
620
+ arrowRight: 39,
621
+ arrowDown: 40,
622
+ arrows: [37, 38, 39, 40],
623
+ command: 16,
624
+ alt: 17
728
625
  };
729
626
 
730
- SegmentedCardNumberInputView.prototype._specialKeys = [8, 9, 16, 17, 18, 19, 20, 27, 33, 34, 35, 36, 37, 38, 39, 40, 45, 46, 91, 93, 144, 145, 224];
627
+ SegmentedCardNumberInputView.prototype._specialKeys = [8, 9, 13, 46, 37, 38, 39, 40, 16, 17];
731
628
 
732
629
  function SegmentedCardNumberInputView(opts) {
733
630
  if (opts == null) {
@@ -747,93 +644,124 @@
747
644
  }
748
645
 
749
646
  SegmentedCardNumberInputView.prototype._buildDOM = function() {
647
+ var _this = this;
750
648
  this.el = $('<fieldset>');
649
+ this.el.addClass('cc-field');
751
650
  this.el.delegate("input", "keypress", this._handleGroupKeyPress.bind(this));
752
651
  this.el.delegate("input", "keydown", this._handleGroupKeyDown.bind(this));
753
652
  this.el.delegate("input", "keyup", this._handleGroupKeyUp.bind(this));
754
653
  this.el.delegate("input", "paste", this._handleGroupPaste.bind(this));
755
- return this.el.delegate("input", "change", this._handleGroupChange.bind(this));
654
+ this.el.delegate("input", "change", this._handleGroupChange.bind(this));
655
+ this.el.delegate("input", "focus", function(e) {
656
+ return _this.el.addClass('focus');
657
+ });
658
+ return this.el.delegate("input", "blur", function(e) {
659
+ return _this.el.removeClass('focus');
660
+ });
756
661
  };
757
662
 
758
663
  SegmentedCardNumberInputView.prototype._handleGroupKeyDown = function(e) {
759
- var currentTarget, inputGroupEl, inputMaxLength, nextInputEl, prevInputEl, selectionEnd;
664
+ var currentTarget, cursorEnd, cursorStart, inputGroupEl, inputMaxLength, nextInputEl, prevInputEl, _ref;
760
665
  if (e.ctrlKey || e.metaKey) {
761
666
  return this._handleModifiedKeyDown(e);
762
667
  }
763
668
  inputGroupEl = $(e.currentTarget);
764
669
  currentTarget = e.currentTarget;
765
- selectionEnd = currentTarget.selectionEnd;
670
+ cursorStart = currentTarget.selectionStart;
671
+ cursorEnd = currentTarget.selectionEnd;
766
672
  inputMaxLength = currentTarget.maxLength;
767
673
  prevInputEl = inputGroupEl.prevAll('input');
768
674
  nextInputEl = inputGroupEl.nextAll('input');
769
- if (e.which === 8 && prevInputEl.length > 0) {
770
- if (selectionEnd === 0) {
771
- this._focusField(prevInputEl.first(), 'end');
772
- }
773
- }
774
- return true;
775
- };
776
-
777
- SegmentedCardNumberInputView.prototype._handleGroupKeyUp = function(e) {
778
- var currentTarget, inputGroupEl, inputMaxLength, nextInputEl, selectionEnd, _ref;
779
- inputGroupEl = $(e.currentTarget);
780
- currentTarget = e.currentTarget;
781
- selectionEnd = currentTarget.selectionEnd;
782
- inputMaxLength = currentTarget.maxLength;
783
- nextInputEl = inputGroupEl.nextAll('input');
784
- if (e.ctrlKey || e.metaKey) {
785
- return false;
786
- }
787
- if ((_ref = e.which) === 37 || _ref === 38 || _ref === 39 || _ref === 40) {
788
- if (this._state.selectingAll) {
789
- this._endSelectAll();
790
- }
791
- }
792
675
  switch (e.which) {
793
- case this._arrowKeys.left:
794
- if (selectionEnd === 0) {
676
+ case this._keys.backspace:
677
+ if (prevInputEl.length > 0 && cursorEnd === 0) {
678
+ this._focusField(prevInputEl.first(), 'end');
679
+ }
680
+ break;
681
+ case this._keys.arrowUp:
682
+ if (cursorEnd === inputMaxLength) {
683
+ this._focusField(inputGroupEl, 'start');
684
+ } else {
795
685
  this._focusField(inputGroupEl.prev(), 'end');
796
686
  }
687
+ e.preventDefault();
797
688
  break;
798
- case this._arrowKeys.right:
799
- if (selectionEnd === inputMaxLength) {
689
+ case this._keys.arrowDown:
690
+ if (cursorEnd === inputMaxLength) {
800
691
  this._focusField(inputGroupEl.next(), 'start');
692
+ } else {
693
+ this._focusField(inputGroupEl, 'end');
801
694
  }
802
- break;
803
- case this._arrowKeys.up:
804
- this._focusField(inputGroupEl.next(), 'start');
805
695
  e.preventDefault();
806
696
  break;
807
- case this._arrowKeys.down:
808
- this._focusField(inputGroupEl.prev(), 'start');
809
- e.preventDefault();
697
+ case this._keys.arrowLeft:
698
+ if (cursorEnd === 0) {
699
+ this._focusField(inputGroupEl.prev(), 'end');
700
+ e.preventDefault();
701
+ }
702
+ break;
703
+ case this._keys.arrowRight:
704
+ if (cursorEnd === inputMaxLength) {
705
+ this._focusField(inputGroupEl.next(), 'start');
706
+ e.preventDefault();
707
+ }
810
708
  break;
811
709
  default:
812
- if (selectionEnd === inputMaxLength) {
813
- if (nextInputEl.length !== 0) {
814
- this._focusField(nextInputEl.first(), 'start');
815
- } else {
816
- e.preventDefault();
817
- }
710
+ if (!(_ref = e.which, __indexOf.call(this._specialKeys, _ref) >= 0) && (cursorStart === inputMaxLength && cursorEnd === inputMaxLength) && nextInputEl.length !== 0) {
711
+ this._focusField(nextInputEl.first(), 'start');
818
712
  }
819
713
  }
820
- this.trigger('change', [this]);
821
714
  return true;
822
715
  };
823
716
 
824
717
  SegmentedCardNumberInputView.prototype._handleGroupKeyPress = function(e) {
825
- var currentTarget, inputGroupEl, inputMaxLength, isDigit, nextInputEl, selectionEnd, _ref, _ref1;
718
+ var inputGroupEl, isDigit, _ref, _ref1;
719
+ inputGroupEl = $(e.currentTarget);
720
+ isDigit = (_ref = String.fromCharCode(e.which), __indexOf.call(this._digits, _ref) >= 0);
721
+ if (e.ctrlKey || e.metaKey) {
722
+ return true;
723
+ }
724
+ if (e.which === 0) {
725
+ return true;
726
+ }
727
+ if ((!e.shiftKey && (_ref1 = e.which, __indexOf.call(this._specialKeys, _ref1) >= 0)) || isDigit) {
728
+ return true;
729
+ }
730
+ e.preventDefault();
731
+ return false;
732
+ };
733
+
734
+ SegmentedCardNumberInputView.prototype._handleGroupKeyUp = function(e) {
735
+ var currentTarget, cursorEnd, cursorStart, inputGroupEl, inputMaxLength, nextInputEl, _ref, _ref1, _ref2;
826
736
  inputGroupEl = $(e.currentTarget);
827
737
  currentTarget = e.currentTarget;
828
- selectionEnd = currentTarget.selectionEnd;
829
738
  inputMaxLength = currentTarget.maxLength;
830
- isDigit = (_ref = String.fromCharCode(e.which), __indexOf.call(this._digits, _ref) >= 0);
739
+ cursorStart = currentTarget.selectionStart;
740
+ cursorEnd = currentTarget.selectionEnd;
831
741
  nextInputEl = inputGroupEl.nextAll('input');
832
- if (e.ctrlKey || e.metaKey || (_ref1 = e.which, __indexOf.call(this._specialKeys, _ref1) >= 0) || isDigit) {
742
+ if (e.ctrlKey || e.metaKey) {
833
743
  return true;
834
- } else {
835
- e.preventDefault();
836
- return false;
744
+ }
745
+ if (this._state.selectingAll && (_ref = e.which, __indexOf.call(this._specialKeys, _ref) >= 0) && e.which !== this._keys.command && e.which !== this._keys.alt) {
746
+ this._endSelectAll();
747
+ }
748
+ if (!(_ref1 = e.which, __indexOf.call(this._specialKeys, _ref1) >= 0) && !(e.shiftKey && e.which === this._keys.tab) && (cursorStart === inputMaxLength && cursorEnd === inputMaxLength) && nextInputEl.length !== 0) {
749
+ this._focusField(nextInputEl.first(), 'start');
750
+ }
751
+ if (!(e.shiftKey && (_ref2 = e.which, __indexOf.call(this._specialKeys, _ref2) >= 0))) {
752
+ this.trigger('change', [this]);
753
+ }
754
+ return true;
755
+ };
756
+
757
+ SegmentedCardNumberInputView.prototype._handleModifiedKeyDown = function(e) {
758
+ var char;
759
+ char = String.fromCharCode(e.which);
760
+ switch (char) {
761
+ case 'a':
762
+ case 'A':
763
+ this._beginSelectAll();
764
+ return e.preventDefault();
837
765
  }
838
766
  };
839
767
 
@@ -850,16 +778,6 @@
850
778
  }, 50);
851
779
  };
852
780
 
853
- SegmentedCardNumberInputView.prototype._handleModifiedKeyDown = function(e) {
854
- var char;
855
- char = String.fromCharCode(e.which);
856
- switch (char) {
857
- case 'A':
858
- this._beginSelectAll();
859
- return e.preventDefault();
860
- }
861
- };
862
-
863
781
  SegmentedCardNumberInputView.prototype._handleGroupChange = function(e) {
864
782
  return e.stopPropagation();
865
783
  };
@@ -870,14 +788,14 @@
870
788
 
871
789
  SegmentedCardNumberInputView.prototype._beginSelectAll = function() {
872
790
  var fieldEl;
873
- if (this._state.selectingAll === false) {
874
- this._state.selectingAll = true;
791
+ if (!this.el.hasClass('selecting-all')) {
875
792
  this._state.lastGrouping = this.options.groupings;
876
- this._state.lastValue = this.getValue();
793
+ this._state.lastLength = this.getValue().length;
877
794
  this.setGroupings(this.optDefaults.groupings);
878
795
  this.el.addClass('selecting-all');
879
796
  fieldEl = this.el.find("input");
880
- return fieldEl[0].setSelectionRange(0, fieldEl.val().length);
797
+ fieldEl[0].setSelectionRange(0, fieldEl.val().length);
798
+ return this._state.selectingAll = true;
881
799
  } else {
882
800
  fieldEl = this.el.find("input");
883
801
  return fieldEl[0].setSelectionRange(0, fieldEl.val().length);
@@ -885,14 +803,12 @@
885
803
  };
886
804
 
887
805
  SegmentedCardNumberInputView.prototype._endSelectAll = function() {
888
- if (this._state.selectingAll) {
889
- if (this._state.lastValue === this.getValue()) {
806
+ if (this.el.hasClass('selecting-all')) {
807
+ this._state.selectingAll = false;
808
+ if (this._state.lastLength === this.getValue().length) {
890
809
  this.setGroupings(this._state.lastGrouping);
891
- } else {
892
- this._focusField(this.el.find('input').last(), 'end');
893
810
  }
894
- this.el.removeClass('selecting-all');
895
- return this._state.selectingAll = false;
811
+ return this.el.removeClass('selecting-all');
896
812
  }
897
813
  };
898
814
 
@@ -949,8 +865,8 @@
949
865
  field = this.el.find('input').last();
950
866
  this._focusField(field, place);
951
867
  } else {
952
- field = void 0;
953
- fieldOffset = void 0;
868
+ field = null;
869
+ fieldOffset = null;
954
870
  _lastStartPos = 0;
955
871
  _ref = this.options.groupings;
956
872
  for (groupIndex = _i = 0, _len = _ref.length; _i < _len; groupIndex = ++_i) {
@@ -1019,35 +935,6 @@
1019
935
  });
1020
936
  };
1021
937
 
1022
- SegmentedCardNumberInputView.prototype.isFilled = function() {
1023
- return this.getValue().length === this.maxLength();
1024
- };
1025
-
1026
- SegmentedCardNumberInputView.prototype.isValid = function() {
1027
- return this.isFilled() && this.isValidLuhn(this.getValue());
1028
- };
1029
-
1030
- SegmentedCardNumberInputView.prototype.isValidLuhn = function(identifier) {
1031
- var alt, i, num, sum, _i, _ref;
1032
- sum = 0;
1033
- alt = false;
1034
- for (i = _i = _ref = identifier.length - 1; _i >= 0; i = _i += -1) {
1035
- num = parseInt(identifier.charAt(i), 10);
1036
- if (isNaN(num)) {
1037
- return false;
1038
- }
1039
- if (alt) {
1040
- num *= 2;
1041
- if (num > 9) {
1042
- num = (num % 10) + 1;
1043
- }
1044
- }
1045
- alt = !alt;
1046
- sum += num;
1047
- }
1048
- return sum % 10 === 0;
1049
- };
1050
-
1051
938
  SegmentedCardNumberInputView.prototype.bind = function() {
1052
939
  var args, _ref;
1053
940
  args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
@@ -1089,37 +976,43 @@
1089
976
  */
1090
977
 
1091
978
 
1092
- Skeuocard.prototype.ExpirationInputView = (function(_super) {
1093
-
1094
- __extends(ExpirationInputView, _super);
1095
-
979
+ Skeuocard.prototype.ExpirationInputView = (function() {
1096
980
  function ExpirationInputView(opts) {
1097
981
  var _this = this;
1098
982
  if (opts == null) {
1099
983
  opts = {};
1100
984
  }
1101
- opts.dateFormatter || (opts.dateFormatter = function(date) {
1102
- return date.getDate() + "-" + (date.getMonth() + 1) + "-" + date.getFullYear();
1103
- });
1104
- opts.dateParser || (opts.dateParser = function(value) {
1105
- var dateParts;
1106
- dateParts = value.split('-');
1107
- return new Date(dateParts[2], dateParts[1] - 1, dateParts[0]);
1108
- });
1109
- opts.currentDate || (opts.currentDate = new Date());
1110
985
  opts.pattern || (opts.pattern = "MM/YY");
1111
986
  this.options = opts;
1112
- this.date = void 0;
1113
- this.value = void 0;
987
+ this.date = null;
1114
988
  this.el = $("<fieldset>");
989
+ this.el.addClass('cc-field');
1115
990
  this.el.delegate("input", "keydown", function(e) {
1116
991
  return _this._onKeyDown(e);
1117
992
  });
1118
993
  this.el.delegate("input", "keyup", function(e) {
1119
994
  return _this._onKeyUp(e);
1120
995
  });
996
+ this.el.delegate("input", "focus", function(e) {
997
+ return _this.el.addClass('focus');
998
+ });
999
+ this.el.delegate("input", "blur", function(e) {
1000
+ return _this.el.removeClass('focus');
1001
+ });
1121
1002
  }
1122
1003
 
1004
+ ExpirationInputView.prototype.bind = function() {
1005
+ var args, _ref;
1006
+ args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
1007
+ return (_ref = this.el).bind.apply(_ref, args);
1008
+ };
1009
+
1010
+ ExpirationInputView.prototype.trigger = function() {
1011
+ var args, _ref;
1012
+ args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
1013
+ return (_ref = this.el).trigger.apply(_ref, args);
1014
+ };
1015
+
1123
1016
  ExpirationInputView.prototype._getFieldCaretPosition = function(el) {
1124
1017
  var input, sel, selLength;
1125
1018
  input = el.get(0);
@@ -1197,6 +1090,12 @@
1197
1090
  }
1198
1091
  };
1199
1092
 
1093
+ ExpirationInputView.prototype._zeroPadNumber = function(num, places) {
1094
+ var zero;
1095
+ zero = places - num.toString().length + 1;
1096
+ return Array(zero).join("0") + num;
1097
+ };
1098
+
1200
1099
  ExpirationInputView.prototype._updateFieldValues = function() {
1201
1100
  var currentDate,
1202
1101
  _this = this;
@@ -1228,24 +1127,13 @@
1228
1127
  });
1229
1128
  };
1230
1129
 
1231
- ExpirationInputView.prototype.setDate = function(newDate) {
1130
+ ExpirationInputView.prototype.setValue = function(newDate) {
1232
1131
  this.date = newDate;
1233
- this.value = this.options.dateFormatter(newDate);
1234
- return this._updateFieldValues();
1235
- };
1236
-
1237
- ExpirationInputView.prototype.setValue = function(newValue) {
1238
- this.value = newValue;
1239
- this.date = this.options.dateParser(newValue);
1240
1132
  return this._updateFieldValues();
1241
1133
  };
1242
1134
 
1243
- ExpirationInputView.prototype.getDate = function() {
1244
- return this.date;
1245
- };
1246
-
1247
1135
  ExpirationInputView.prototype.getValue = function() {
1248
- return this.value;
1136
+ return this.date;
1249
1137
  };
1250
1138
 
1251
1139
  ExpirationInputView.prototype.reconfigure = function(opts) {
@@ -1294,6 +1182,10 @@
1294
1182
  }
1295
1183
  };
1296
1184
 
1185
+ ExpirationInputView.prototype.getRawValue = function(fieldType) {
1186
+ return parseInt(this.el.find(".cc-exp-field-" + fieldType).val());
1187
+ };
1188
+
1297
1189
  ExpirationInputView.prototype._onKeyUp = function(e) {
1298
1190
  var arrowKeys, dateObj, day, groupCaretPos, groupEl, groupMaxLength, groupValLength, month, nextInputEl, pattern, specialKeys, year, _ref, _ref1;
1299
1191
  e.stopPropagation();
@@ -1316,18 +1208,16 @@
1316
1208
  if ((_ref1 = e.which, __indexOf.call(specialKeys, _ref1) < 0) && groupEl.val().length === groupMaxLength && !$.isEmptyObject(nextInputEl) && this._getFieldCaretPosition(groupEl) === groupMaxLength) {
1317
1209
  nextInputEl.focus();
1318
1210
  }
1319
- day = parseInt(this.el.find('.cc-exp-field-d').val()) || 1;
1320
- month = parseInt(this.el.find('.cc-exp-field-m').val());
1321
- year = parseInt(this.el.find('.cc-exp-field-y').val());
1211
+ day = this.getRawValue('d') || 1;
1212
+ month = this.getRawValue('m');
1213
+ year = this.getRawValue('y');
1322
1214
  if (month === 0 || year === 0) {
1323
- this.value = "";
1324
1215
  this.date = null;
1325
1216
  } else {
1326
1217
  if (year < 2000) {
1327
1218
  year += 2000;
1328
1219
  }
1329
1220
  dateObj = new Date(year, month - 1, day);
1330
- this.value = this.options.dateFormatter(dateObj);
1331
1221
  this.date = dateObj;
1332
1222
  }
1333
1223
  this.trigger("keyup", [this]);
@@ -1338,126 +1228,352 @@
1338
1228
  return this.el.find("input");
1339
1229
  };
1340
1230
 
1341
- ExpirationInputView.prototype.isFilled = function() {
1342
- var el, inputEl, _i, _len, _ref;
1343
- _ref = this.groupEls;
1344
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
1345
- inputEl = _ref[_i];
1346
- el = $(inputEl);
1347
- if (el.val().length !== parseInt(el.attr('maxlength'))) {
1348
- return false;
1349
- }
1350
- }
1351
- return true;
1231
+ ExpirationInputView.prototype.show = function() {
1232
+ return this.el.show();
1352
1233
  };
1353
1234
 
1354
- ExpirationInputView.prototype.isValid = function() {
1355
- return this.isFilled() && ((this.date.getFullYear() === this.options.currentDate.getFullYear() && this.date.getMonth() >= this.options.currentDate.getMonth()) || this.date.getFullYear() > this.options.currentDate.getFullYear());
1235
+ ExpirationInputView.prototype.hide = function() {
1236
+ return this.el.hide();
1356
1237
  };
1357
1238
 
1358
1239
  return ExpirationInputView;
1359
1240
 
1360
- })(Skeuocard.prototype.TextInputView);
1241
+ })();
1361
1242
 
1362
- Skeuocard.prototype.TextInputView = (function(_super) {
1243
+ /*
1244
+ Skeuocard::TextInputView
1245
+ */
1363
1246
 
1364
- __extends(TextInputView, _super);
1365
1247
 
1248
+ Skeuocard.prototype.TextInputView = (function() {
1366
1249
  function TextInputView(opts) {
1367
- this.el = $("<input>").attr({
1250
+ var _this = this;
1251
+ this.el = $('<div>');
1252
+ this.inputEl = $("<input>").attr({
1368
1253
  type: 'text',
1369
1254
  placeholder: opts.placeholder,
1370
1255
  "class": opts["class"]
1371
1256
  });
1257
+ this.el.append(this.inputEl);
1258
+ this.el.addClass('cc-field');
1372
1259
  this.options = opts;
1260
+ this.el.delegate("input", "focus", function(e) {
1261
+ return _this.el.addClass('focus');
1262
+ });
1263
+ this.el.delegate("input", "blur", function(e) {
1264
+ return _this.el.removeClass('focus');
1265
+ });
1266
+ this.el.delegate("input", "keyup", function(e) {
1267
+ e.stopPropagation();
1268
+ return _this.trigger('keyup', [_this]);
1269
+ });
1373
1270
  }
1374
1271
 
1375
1272
  TextInputView.prototype.clear = function() {
1376
- return this.el.val("");
1273
+ return this.inputEl.val("");
1377
1274
  };
1378
1275
 
1379
1276
  TextInputView.prototype.attr = function() {
1380
1277
  var args, _ref;
1381
1278
  args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
1382
- return (_ref = this.el).attr.apply(_ref, args);
1279
+ return (_ref = this.inputEl).attr.apply(_ref, args);
1383
1280
  };
1384
1281
 
1385
- TextInputView.prototype.isFilled = function() {
1386
- return this.el.val().length > 0;
1282
+ TextInputView.prototype.setValue = function(newValue) {
1283
+ return this.inputEl.val(newValue);
1387
1284
  };
1388
1285
 
1389
- TextInputView.prototype.isValid = function() {
1390
- if (this.options.requireMaxLength) {
1391
- return this.el.val().length === parseInt(this.el.attr('maxlength'));
1392
- } else {
1393
- return this.isFilled();
1394
- }
1286
+ TextInputView.prototype.getValue = function() {
1287
+ return this.inputEl.val();
1395
1288
  };
1396
1289
 
1397
- TextInputView.prototype.getValue = function() {
1398
- return this.el.val();
1290
+ TextInputView.prototype.bind = function() {
1291
+ var args, _ref;
1292
+ args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
1293
+ return (_ref = this.el).bind.apply(_ref, args);
1399
1294
  };
1400
1295
 
1401
- return TextInputView;
1296
+ TextInputView.prototype.trigger = function() {
1297
+ var args, _ref;
1298
+ args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
1299
+ return (_ref = this.el).trigger.apply(_ref, args);
1300
+ };
1402
1301
 
1403
- })(Skeuocard.prototype.TextInputView);
1302
+ TextInputView.prototype.show = function() {
1303
+ return this.el.show();
1304
+ };
1404
1305
 
1405
- window.Skeuocard = Skeuocard;
1306
+ TextInputView.prototype.hide = function() {
1307
+ return this.el.hide();
1308
+ };
1309
+
1310
+ return TextInputView;
1311
+
1312
+ })();
1406
1313
 
1407
1314
  /*
1408
- # Card Definitions
1315
+ Skeuocard::CardProduct
1409
1316
  */
1410
1317
 
1411
1318
 
1412
- CCProducts = {};
1319
+ Skeuocard.prototype.CardProduct = (function() {
1320
+ CardProduct._registry = [];
1321
+
1322
+ CardProduct.create = function(opts) {
1323
+ return this._registry.push(new Skeuocard.prototype.CardProduct(opts));
1324
+ };
1325
+
1326
+ CardProduct.firstMatchingShortname = function(shortname) {
1327
+ var card, _i, _len, _ref;
1328
+ _ref = this._registry;
1329
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
1330
+ card = _ref[_i];
1331
+ if (card.attrs.companyShortname === shortname) {
1332
+ return card;
1333
+ }
1334
+ }
1335
+ return null;
1336
+ };
1337
+
1338
+ CardProduct.firstMatchingNumber = function(number) {
1339
+ var card, combinedOptions, variation, _i, _len, _ref;
1340
+ _ref = this._registry;
1341
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
1342
+ card = _ref[_i];
1343
+ if (card.pattern.test(number)) {
1344
+ if ((variation = card.firstVariationMatchingNumber(number))) {
1345
+ combinedOptions = $.extend({}, card.attrs, variation);
1346
+ return new Skeuocard.prototype.CardProduct(combinedOptions);
1347
+ }
1348
+ return new Skeuocard.prototype.CardProduct(card.attrs);
1349
+ }
1350
+ }
1351
+ return null;
1352
+ };
1353
+
1354
+ function CardProduct(attrs) {
1355
+ this.attrs = $.extend({}, attrs);
1356
+ this.pattern = this.attrs.pattern;
1357
+ this._variances = [];
1358
+ this.name = {
1359
+ isFilled: this._isCardNameFilled.bind(this),
1360
+ isValid: this._isCardNameValid.bind(this)
1361
+ };
1362
+ this.number = {
1363
+ isFilled: this._isCardNumberFilled.bind(this),
1364
+ isValid: this._isCardNumberValid.bind(this)
1365
+ };
1366
+ this.exp = {
1367
+ isFilled: this._isCardExpirationFilled.bind(this),
1368
+ isValid: this._isCardExpirationValid.bind(this)
1369
+ };
1370
+ this.cvc = {
1371
+ isFilled: this._isCardCVCFilled.bind(this),
1372
+ isValid: this._isCardCVCValid.bind(this)
1373
+ };
1374
+ }
1375
+
1376
+ CardProduct.prototype.createVariation = function(attrs) {
1377
+ return this._variances.push(attrs);
1378
+ };
1379
+
1380
+ CardProduct.prototype.firstVariationMatchingNumber = function(number) {
1381
+ var variance, _i, _len, _ref;
1382
+ _ref = this._variances;
1383
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
1384
+ variance = _ref[_i];
1385
+ if (variance.pattern.test(number)) {
1386
+ return variance;
1387
+ }
1388
+ }
1389
+ return null;
1390
+ };
1391
+
1392
+ CardProduct.prototype.fieldsForLayoutFace = function(faceName) {
1393
+ var face, fieldName, _ref, _results;
1394
+ _ref = this.attrs.layout;
1395
+ _results = [];
1396
+ for (fieldName in _ref) {
1397
+ face = _ref[fieldName];
1398
+ if (face === faceName) {
1399
+ _results.push(fieldName);
1400
+ }
1401
+ }
1402
+ return _results;
1403
+ };
1404
+
1405
+ CardProduct.prototype._id = function() {
1406
+ var ident;
1407
+ ident = this.attrs.companyShortname;
1408
+ if (this.attrs.issuerShortname != null) {
1409
+ ident += this.attrs.issuerShortname;
1410
+ }
1411
+ return ident;
1412
+ };
1413
+
1414
+ CardProduct.prototype.eql = function(otherCardProduct) {
1415
+ return (otherCardProduct != null ? otherCardProduct._id() : void 0) === this._id();
1416
+ };
1417
+
1418
+ CardProduct.prototype._daysInMonth = function(m, y) {
1419
+ switch (m) {
1420
+ case 1:
1421
+ if ((y % 4 === 0 && y % 100) || y % 400 === 0) {
1422
+ return 29;
1423
+ } else {
1424
+ return 28;
1425
+ }
1426
+ case 3:
1427
+ case 5:
1428
+ case 8:
1429
+ case 10:
1430
+ return 30;
1431
+ default:
1432
+ return 31;
1433
+ }
1434
+ };
1435
+
1436
+ CardProduct.prototype._isCardNumberFilled = function(number) {
1437
+ var _ref;
1438
+ if (this.attrs.cardNumberLength != null) {
1439
+ return (_ref = number.length, __indexOf.call(this.attrs.cardNumberLength, _ref) >= 0);
1440
+ }
1441
+ };
1442
+
1443
+ CardProduct.prototype._isCardExpirationFilled = function(exp) {
1444
+ var currentDate, day, month, year;
1445
+ currentDate = Skeuocard.currentDate;
1446
+ if (!((exp != null) && (exp.getMonth != null) && (exp.getFullYear != null))) {
1447
+ return false;
1448
+ }
1449
+ day = exp.getDate();
1450
+ month = exp.getMonth();
1451
+ year = exp.getFullYear();
1452
+ return (day > 0 && day <= this._daysInMonth(month, year)) && (month >= 0 && month <= 11) && (year >= 1900 && year <= currentDate.getFullYear() + 10);
1453
+ };
1454
+
1455
+ CardProduct.prototype._isCardCVCFilled = function(cvc) {
1456
+ return cvc.length === this.attrs.cvcLength;
1457
+ };
1458
+
1459
+ CardProduct.prototype._isCardNameFilled = function(name) {
1460
+ return name.length > 0;
1461
+ };
1462
+
1463
+ CardProduct.prototype._isCardNumberValid = function(number) {
1464
+ return /^\d+$/.test(number) && (this.attrs.validateLuhn === false || this._isValidLuhn(number)) && this._isCardNumberFilled(number);
1465
+ };
1466
+
1467
+ CardProduct.prototype._isCardExpirationValid = function(exp) {
1468
+ var currentDate, day, isDateInFuture, month, year;
1469
+ if (!((exp != null) && (exp.getMonth != null) && (exp.getFullYear != null))) {
1470
+ return false;
1471
+ }
1472
+ currentDate = Skeuocard.currentDate;
1473
+ day = exp.getDate();
1474
+ month = exp.getMonth();
1475
+ year = exp.getFullYear();
1476
+ isDateInFuture = (year === currentDate.getFullYear() && month >= currentDate.getMonth()) || year > currentDate.getFullYear();
1477
+ return isDateInFuture && this._isCardExpirationFilled(exp);
1478
+ };
1479
+
1480
+ CardProduct.prototype._isCardCVCValid = function(cvc) {
1481
+ return this._isCardCVCFilled(cvc);
1482
+ };
1483
+
1484
+ CardProduct.prototype._isCardNameValid = function(name) {
1485
+ return this._isCardNameFilled(name);
1486
+ };
1487
+
1488
+ CardProduct.prototype._isValidLuhn = function(number) {
1489
+ var alt, i, num, sum, _i, _ref;
1490
+ sum = 0;
1491
+ alt = false;
1492
+ for (i = _i = _ref = number.length - 1; _i >= 0; i = _i += -1) {
1493
+ num = parseInt(number.charAt(i), 10);
1494
+ if (isNaN(num)) {
1495
+ return false;
1496
+ }
1497
+ if (alt) {
1498
+ num *= 2;
1499
+ if (num > 9) {
1500
+ num = (num % 10) + 1;
1501
+ }
1502
+ }
1503
+ alt = !alt;
1504
+ sum += num;
1505
+ }
1506
+ return sum % 10 === 0;
1507
+ };
1508
+
1509
+ return CardProduct;
1510
+
1511
+ })();
1512
+
1513
+ /*
1514
+ # Seed CardProducts.
1515
+ */
1413
1516
 
1414
- CCProducts[/^30[0-5][0-9]/] = {
1517
+
1518
+ Skeuocard.prototype.CardProduct.create({
1519
+ pattern: /^(36|38|30[0-5])/,
1415
1520
  companyName: "Diners Club",
1416
1521
  companyShortname: "dinersclubintl",
1417
1522
  cardNumberGrouping: [4, 6, 4],
1523
+ cardNumberLength: [14],
1418
1524
  expirationFormat: "MM/YY",
1419
1525
  cvcLength: 3,
1526
+ validateLuhn: true,
1420
1527
  layout: {
1421
1528
  number: 'front',
1422
1529
  exp: 'front',
1423
1530
  name: 'front',
1424
1531
  cvc: 'back'
1425
1532
  }
1426
- };
1533
+ });
1427
1534
 
1428
- CCProducts[/^3095/] = {
1429
- companyName: "Diners Club International",
1430
- companyShortname: "dinersclubintl",
1431
- cardNumberGrouping: [4, 6, 4],
1432
- expirationFormat: "MM/YY",
1535
+ Skeuocard.prototype.CardProduct.create({
1536
+ pattern: /^35/,
1537
+ companyName: "JCB",
1538
+ companyShortname: "jcb",
1539
+ cardNumberGrouping: [4, 4, 4, 4],
1540
+ cardNumberLength: [16],
1541
+ expirationFormat: "MM/'YY",
1433
1542
  cvcLength: 3,
1543
+ validateLuhn: true,
1434
1544
  layout: {
1435
1545
  number: 'front',
1436
1546
  exp: 'front',
1437
1547
  name: 'front',
1438
1548
  cvc: 'back'
1439
1549
  }
1440
- };
1550
+ });
1441
1551
 
1442
- CCProducts[/^36\d{2}/] = {
1443
- companyName: "Diners Club International",
1444
- companyShortname: "dinersclubintl",
1445
- cardNumberGrouping: [4, 6, 4],
1552
+ Skeuocard.prototype.CardProduct.create({
1553
+ pattern: /^3[47]/,
1554
+ companyName: "American Express",
1555
+ companyShortname: "amex",
1556
+ cardNumberGrouping: [4, 6, 5],
1557
+ cardNumberLength: [15],
1446
1558
  expirationFormat: "MM/YY",
1447
- cvcLength: 3,
1559
+ cvcLength: 4,
1560
+ validateLuhn: true,
1448
1561
  layout: {
1449
1562
  number: 'front',
1450
1563
  exp: 'front',
1451
1564
  name: 'front',
1452
- cvc: 'back'
1565
+ cvc: 'front'
1453
1566
  }
1454
- };
1567
+ });
1455
1568
 
1456
- CCProducts[/^35\d{2}/] = {
1457
- companyName: "JCB",
1458
- companyShortname: "jcb",
1569
+ Skeuocard.prototype.CardProduct.create({
1570
+ pattern: /^(6706|6771|6709)/,
1571
+ companyName: "Laser Card Services Ltd.",
1572
+ companyShortname: "laser",
1459
1573
  cardNumberGrouping: [4, 4, 4, 4],
1574
+ cardNumberLength: [16, 17, 18, 19],
1460
1575
  expirationFormat: "MM/YY",
1576
+ validateLuhn: true,
1461
1577
  cvcLength: 3,
1462
1578
  layout: {
1463
1579
  number: 'front',
@@ -1465,27 +1581,33 @@
1465
1581
  name: 'front',
1466
1582
  cvc: 'back'
1467
1583
  }
1468
- };
1584
+ });
1469
1585
 
1470
- CCProducts[/^3[47]/] = {
1471
- companyName: "American Express",
1472
- companyShortname: "amex",
1473
- cardNumberGrouping: [4, 6, 5],
1586
+ Skeuocard.prototype.CardProduct.create({
1587
+ pattern: /^4/,
1588
+ companyName: "Visa",
1589
+ companyShortname: "visa",
1590
+ cardNumberGrouping: [4, 4, 4, 4],
1591
+ cardNumberLength: [13, 14, 15, 16],
1474
1592
  expirationFormat: "MM/YY",
1475
- cvcLength: 4,
1593
+ validateLuhn: true,
1594
+ cvcLength: 3,
1476
1595
  layout: {
1477
1596
  number: 'front',
1478
1597
  exp: 'front',
1479
1598
  name: 'front',
1480
- cvc: 'front'
1599
+ cvc: 'back'
1481
1600
  }
1482
- };
1483
-
1484
- CCProducts[/^38/] = {
1485
- companyName: "Hipercard",
1486
- companyShortname: "hipercard",
1487
- cardNumberGrouping: [4, 4, 4, 4],
1601
+ });
1602
+
1603
+ Skeuocard.prototype.CardProduct.create({
1604
+ pattern: /^(62|88)/,
1605
+ companyName: "China UnionPay",
1606
+ companyShortname: "unionpay",
1607
+ cardNumberGrouping: [19],
1608
+ cardNumberLength: [16, 17, 18, 19],
1488
1609
  expirationFormat: "MM/YY",
1610
+ validateLuhn: false,
1489
1611
  cvcLength: 3,
1490
1612
  layout: {
1491
1613
  number: 'front',
@@ -1493,13 +1615,16 @@
1493
1615
  name: 'front',
1494
1616
  cvc: 'back'
1495
1617
  }
1496
- };
1618
+ });
1497
1619
 
1498
- CCProducts[/^4[0-9]\d{2}/] = {
1499
- companyName: "Visa",
1500
- companyShortname: "visa",
1620
+ Skeuocard.prototype.CardProduct.create({
1621
+ pattern: /^5[1-5]/,
1622
+ companyName: "Mastercard",
1623
+ companyShortname: "mastercard",
1501
1624
  cardNumberGrouping: [4, 4, 4, 4],
1625
+ cardNumberLength: [16],
1502
1626
  expirationFormat: "MM/YY",
1627
+ validateLuhn: true,
1503
1628
  cvcLength: 3,
1504
1629
  layout: {
1505
1630
  number: 'front',
@@ -1507,13 +1632,16 @@
1507
1632
  name: 'front',
1508
1633
  cvc: 'back'
1509
1634
  }
1510
- };
1635
+ });
1511
1636
 
1512
- CCProducts[/^5[0-8]\d{2}/] = {
1513
- companyName: "Mastercard",
1514
- companyShortname: "mastercard",
1637
+ Skeuocard.prototype.CardProduct.create({
1638
+ pattern: /^(5018|5020|5038|6304|6759|676[1-3])/,
1639
+ companyName: "Maestro (MasterCard)",
1640
+ companyShortname: "maestro",
1515
1641
  cardNumberGrouping: [4, 4, 4, 4],
1642
+ cardNumberLength: [12, 13, 14, 15, 16, 17, 18, 19],
1516
1643
  expirationFormat: "MM/YY",
1644
+ validateLuhn: true,
1517
1645
  cvcLength: 3,
1518
1646
  layout: {
1519
1647
  number: 'front',
@@ -1521,13 +1649,16 @@
1521
1649
  name: 'front',
1522
1650
  cvc: 'back'
1523
1651
  }
1524
- };
1652
+ });
1525
1653
 
1526
- CCProducts[/^6011/] = {
1654
+ Skeuocard.prototype.CardProduct.create({
1655
+ pattern: /^(6011|65|64[4-9]|622)/,
1527
1656
  companyName: "Discover",
1528
1657
  companyShortname: "discover",
1529
1658
  cardNumberGrouping: [4, 4, 4, 4],
1659
+ cardNumberLength: [16],
1530
1660
  expirationFormat: "MM/YY",
1661
+ validateLuhn: true,
1531
1662
  cvcLength: 3,
1532
1663
  layout: {
1533
1664
  number: 'front',
@@ -1535,25 +1666,21 @@
1535
1666
  name: 'front',
1536
1667
  cvc: 'back'
1537
1668
  }
1538
- };
1539
-
1540
- CCIssuers = {};
1541
-
1542
- /*
1543
- Hack fixes the Chase Sapphire card's stupid (nice?) layout non-conformity.
1544
- */
1669
+ });
1545
1670
 
1671
+ visaProduct = Skeuocard.prototype.CardProduct.firstMatchingShortname('visa');
1546
1672
 
1547
- CCIssuers[/^414720/] = {
1673
+ visaProduct.createVariation({
1674
+ pattern: /^414720/,
1548
1675
  issuingAuthority: "Chase",
1549
1676
  issuerName: "Chase Sapphire Card",
1550
1677
  issuerShortname: "chase-sapphire",
1551
1678
  layout: {
1679
+ name: 'front',
1552
1680
  number: 'front',
1553
1681
  exp: 'front',
1554
- name: 'front',
1555
1682
  cvc: 'front'
1556
1683
  }
1557
- };
1684
+ });
1558
1685
 
1559
1686
  }).call(this);