weaver 0.8.13 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/data/weaver/css/plugins/codemirror/codemirror.css +97 -96
- data/data/weaver/css/style-dark.css +8628 -0
- data/data/weaver/css/style.css +37 -0
- data/data/weaver/js/plugins/codemirror/codemirror.js +8817 -6763
- data/data/weaver/js/plugins/skeuocard/Gruntfile.coffee +74 -0
- data/data/weaver/js/plugins/skeuocard/LICENSE +21 -0
- data/data/weaver/js/plugins/skeuocard/README.md +393 -0
- data/data/weaver/js/plugins/skeuocard/bower.json +40 -0
- data/data/weaver/js/plugins/skeuocard/fonts/ocra-webfont.eot +0 -0
- data/data/weaver/js/plugins/skeuocard/fonts/ocra-webfont.svg +138 -0
- data/data/weaver/js/plugins/skeuocard/fonts/ocra-webfont.ttf +0 -0
- data/data/weaver/js/plugins/skeuocard/fonts/ocra-webfont.woff +0 -0
- data/data/weaver/js/plugins/skeuocard/images/card-flip-arrow.png +0 -0
- data/data/weaver/js/plugins/skeuocard/images/card-front-background.png +0 -0
- data/data/weaver/js/plugins/skeuocard/images/card-invalid-indicator.png +0 -0
- data/data/weaver/js/plugins/skeuocard/images/card-valid-anim.gif +0 -0
- data/data/weaver/js/plugins/skeuocard/images/card-valid-indicator.png +0 -0
- data/data/weaver/js/plugins/skeuocard/images/error-pointer.png +0 -0
- data/data/weaver/js/plugins/skeuocard/images/issuers/visa-chase-sapphire.png +0 -0
- data/data/weaver/js/plugins/skeuocard/images/issuers/visa-simple-front.png +0 -0
- data/data/weaver/js/plugins/skeuocard/images/products/amex-front.png +0 -0
- data/data/weaver/js/plugins/skeuocard/images/products/dinersclubintl-front.png +0 -0
- data/data/weaver/js/plugins/skeuocard/images/products/discover-front.png +0 -0
- data/data/weaver/js/plugins/skeuocard/images/products/generic-back.png +0 -0
- data/data/weaver/js/plugins/skeuocard/images/products/generic-front.png +0 -0
- data/data/weaver/js/plugins/skeuocard/images/products/jcb-front.png +0 -0
- data/data/weaver/js/plugins/skeuocard/images/products/maestro-front.png +0 -0
- data/data/weaver/js/plugins/skeuocard/images/products/mastercard-front.png +0 -0
- data/data/weaver/js/plugins/skeuocard/images/products/unionpay-front.png +0 -0
- data/data/weaver/js/plugins/skeuocard/images/products/visa-back.png +0 -0
- data/data/weaver/js/plugins/skeuocard/images/products/visa-front.png +0 -0
- data/data/weaver/js/plugins/skeuocard/images/src/card-front-background.fw.png +0 -0
- data/data/weaver/js/plugins/skeuocard/images/src/error-pointer.png +0 -0
- data/data/weaver/js/plugins/skeuocard/images/src/product-amex-front.fw.png +0 -0
- data/data/weaver/js/plugins/skeuocard/images/src/product-dinersclub-front.fw.png +0 -0
- data/data/weaver/js/plugins/skeuocard/images/src/product-discover-front.fw.png +0 -0
- data/data/weaver/js/plugins/skeuocard/images/src/product-generic-front.fw.png +0 -0
- data/data/weaver/js/plugins/skeuocard/images/src/product-jcb-front.fw.png +0 -0
- data/data/weaver/js/plugins/skeuocard/images/src/product-maestro-front.fw.png +0 -0
- data/data/weaver/js/plugins/skeuocard/images/src/product-mastercard-front.fw.png +0 -0
- data/data/weaver/js/plugins/skeuocard/images/src/product-unionpay-front.fw.png +0 -0
- data/data/weaver/js/plugins/skeuocard/images/src/product-visa-front.fw.png +0 -0
- data/data/weaver/js/plugins/skeuocard/index.html +124 -0
- data/data/weaver/js/plugins/skeuocard/javascripts/skeuocard.js +1748 -0
- data/data/weaver/js/plugins/skeuocard/javascripts/skeuocard.min.js +2 -0
- data/data/weaver/js/plugins/skeuocard/javascripts/src/CardProduct.coffee +284 -0
- data/data/weaver/js/plugins/skeuocard/javascripts/src/ExpirationInputView.coffee +206 -0
- data/data/weaver/js/plugins/skeuocard/javascripts/src/FlipTabView.coffee +67 -0
- data/data/weaver/js/plugins/skeuocard/javascripts/src/SegmentedCardNumberInputView.coffee +284 -0
- data/data/weaver/js/plugins/skeuocard/javascripts/src/Skeuocard.coffee +439 -0
- data/data/weaver/js/plugins/skeuocard/javascripts/src/TextInputView.coffee +42 -0
- data/data/weaver/js/plugins/skeuocard/javascripts/vendor/cssua.min.js +7 -0
- data/data/weaver/js/plugins/skeuocard/javascripts/vendor/demo.fix.js +17 -0
- data/data/weaver/js/plugins/skeuocard/javascripts/vendor/jquery-2.0.3.min.js +5 -0
- data/data/weaver/js/plugins/skeuocard/package-lock.json +760 -0
- data/data/weaver/js/plugins/skeuocard/package.json +19 -0
- data/data/weaver/js/plugins/skeuocard/screenshot.png +0 -0
- data/data/weaver/js/plugins/skeuocard/styles/demo.css +2 -0
- data/data/weaver/js/plugins/skeuocard/styles/skeuocard.css +2 -0
- data/data/weaver/js/plugins/skeuocard/styles/skeuocard.reset.css +2 -0
- data/data/weaver/js/plugins/skeuocard/styles/src/_browser_hacks.scss +52 -0
- data/data/weaver/js/plugins/skeuocard/styles/src/_cards.scss +516 -0
- data/data/weaver/js/plugins/skeuocard/styles/src/_util.scss +15 -0
- data/data/weaver/js/plugins/skeuocard/styles/src/demo.scss +265 -0
- data/data/weaver/js/plugins/skeuocard/styles/src/skeuocard.reset.scss +60 -0
- data/data/weaver/js/plugins/skeuocard/styles/src/skeuocard.scss +190 -0
- data/lib/weaver/element_types/accordion.rb +2 -0
- data/lib/weaver/page_types/page.rb +5 -0
- data/lib/weaver/page_types/structured_page.rb +1 -1
- data/lib/weaver/version.rb +1 -1
- metadata +69 -6
@@ -0,0 +1,439 @@
|
|
1
|
+
###
|
2
|
+
"Skeuocard" -- A Skeuomorphic Credit-Card Input Enhancement
|
3
|
+
@description Skeuocard is a skeuomorphic credit card input plugin, supporting
|
4
|
+
progressive enhancement. It renders a credit-card input which
|
5
|
+
behaves similarly to a physical credit card.
|
6
|
+
@author Ken Keiter <ken@kenkeiter.com>
|
7
|
+
@updated 2013-07-25
|
8
|
+
@website http://kenkeiter.com/
|
9
|
+
@exports [window.Skeuocard]
|
10
|
+
###
|
11
|
+
|
12
|
+
$ = jQuery
|
13
|
+
|
14
|
+
class Skeuocard
|
15
|
+
|
16
|
+
@currentDate: new Date()
|
17
|
+
|
18
|
+
constructor: (el, opts = {})->
|
19
|
+
@el = container: $(el), underlyingFields: {}
|
20
|
+
@_inputViews = {}
|
21
|
+
@_inputViewsByFace = {front: [], back: []}
|
22
|
+
@_tabViews = {}
|
23
|
+
@_state = {}
|
24
|
+
@product = null
|
25
|
+
@visibleFace = 'front'
|
26
|
+
|
27
|
+
# configure default opts
|
28
|
+
optDefaults =
|
29
|
+
debug: false
|
30
|
+
dontFocus: false
|
31
|
+
acceptedCardProducts: null
|
32
|
+
cardNumberPlaceholderChar: 'X'
|
33
|
+
genericPlaceholder: "XXXX XXXX XXXX XXXX"
|
34
|
+
typeInputSelector: '[name="cc_type"]'
|
35
|
+
numberInputSelector: '[name="cc_number"]'
|
36
|
+
expMonthInputSelector: '[name="cc_exp_month"]'
|
37
|
+
expYearInputSelector: '[name="cc_exp_year"]'
|
38
|
+
nameInputSelector: '[name="cc_name"]'
|
39
|
+
cvcInputSelector: '[name="cc_cvc"]'
|
40
|
+
initialValues: {}
|
41
|
+
validationState: {}
|
42
|
+
strings:
|
43
|
+
hiddenFaceFillPrompt: "<strong>Click here</strong> to <br>fill in the other side."
|
44
|
+
hiddenFaceErrorWarning: "There's a problem on the other side."
|
45
|
+
hiddenFaceSwitchPrompt: "Forget something?<br> Flip the card over."
|
46
|
+
@options = $.extend(optDefaults, opts)
|
47
|
+
|
48
|
+
# initialize the card
|
49
|
+
@_conformDOM() # conform the DOM, add our elements
|
50
|
+
@_bindInputEvents() # bind input and interaction events
|
51
|
+
@_importImplicitOptions() # import init options from DOM element attrs
|
52
|
+
@render() # perform initial render
|
53
|
+
|
54
|
+
# Debugging helper; if debug is set to true at instantiation, messages will
|
55
|
+
# be printed to the console.
|
56
|
+
_log: (msg...)->
|
57
|
+
if console?.log and !!@options.debug
|
58
|
+
console.log("[skeuocard]", msg...) if @options.debug?
|
59
|
+
|
60
|
+
# Trigger an event on a Skeuocard instance (jQuery's #trigger signature).
|
61
|
+
trigger: (args...)->
|
62
|
+
@el.container.trigger(args...)
|
63
|
+
|
64
|
+
# Bind an event handler on a Skeuocard instance (jQuery's #trigger signature).
|
65
|
+
bind: (args...)->
|
66
|
+
@el.container.bind(args...)
|
67
|
+
|
68
|
+
###
|
69
|
+
Transform the elements within the container, conforming the DOM so that it
|
70
|
+
becomes styleable, and that the underlying inputs are hidden.
|
71
|
+
###
|
72
|
+
_conformDOM: ->
|
73
|
+
@el.container.removeClass('no-js')
|
74
|
+
@el.container.addClass("skeuocard js")
|
75
|
+
|
76
|
+
# Attach underlying form fields.
|
77
|
+
@el.underlyingFields =
|
78
|
+
type: @el.container.find(@options.typeInputSelector)
|
79
|
+
number: @el.container.find(@options.numberInputSelector)
|
80
|
+
expMonth: @el.container.find(@options.expMonthInputSelector)
|
81
|
+
expYear: @el.container.find(@options.expYearInputSelector)
|
82
|
+
name: @el.container.find(@options.nameInputSelector)
|
83
|
+
cvc: @el.container.find(@options.cvcInputSelector)
|
84
|
+
# remove anything that's not an underlying form field
|
85
|
+
$(elem).detach() for own name, elem of @el.underlyingFields
|
86
|
+
@el.container.find("> :not(input,select,textarea)").remove()
|
87
|
+
$(elem).appendTo(@el.container) for own name, elem of @el.underlyingFields
|
88
|
+
@el.container.find("> input,select,textarea").hide()
|
89
|
+
# construct the necessary card elements
|
90
|
+
@el.front = $("<div>").attr(class: "face front")
|
91
|
+
@el.back = $("<div>").attr(class: "face back")
|
92
|
+
@el.cardBody = $("<div>").attr(class: "card-body")
|
93
|
+
# add elements to the DOM
|
94
|
+
@el.front.appendTo(@el.cardBody)
|
95
|
+
@el.back.appendTo(@el.cardBody)
|
96
|
+
@el.cardBody.appendTo(@el.container)
|
97
|
+
# create the validation indicator (flip tab), and attach them.
|
98
|
+
@_tabViews.front = new Skeuocard::FlipTabView(@, 'front', strings: @options.strings)
|
99
|
+
@_tabViews.back = new Skeuocard::FlipTabView(@, 'back', strings: @options.strings)
|
100
|
+
@el.front.prepend(@_tabViews.front.el)
|
101
|
+
@el.back.prepend(@_tabViews.back.el)
|
102
|
+
@_tabViews.front.hide()
|
103
|
+
@_tabViews.back.hide()
|
104
|
+
# Create new input views, attach them to the appropriate surfaces
|
105
|
+
@_inputViews =
|
106
|
+
number: new @SegmentedCardNumberInputView()
|
107
|
+
exp: new @ExpirationInputView(currentDate: @options.currentDate)
|
108
|
+
name: new @TextInputView(class: "cc-name", placeholder: "YOUR NAME")
|
109
|
+
cvc: new @TextInputView(class: "cc-cvc", placeholder: "XXX", requireMaxLength: true)
|
110
|
+
# style and attach the number view to the DOM
|
111
|
+
@_inputViews.number.el.addClass('cc-number')
|
112
|
+
@_inputViews.number.el.appendTo(@el.front)
|
113
|
+
# attach name input
|
114
|
+
@_inputViews.name.el.appendTo(@el.front)
|
115
|
+
# style and attach the exp view to the DOM
|
116
|
+
@_inputViews.exp.el.addClass('cc-exp')
|
117
|
+
@_inputViews.exp.el.appendTo(@el.front)
|
118
|
+
# attach cvc field to the DOM
|
119
|
+
@_inputViews.cvc.el.appendTo(@el.back)
|
120
|
+
|
121
|
+
return @el.container
|
122
|
+
|
123
|
+
###
|
124
|
+
Import implicit initialization options from the DOM. Brings in things like
|
125
|
+
the accepted card type, initial validation state, existing values, etc.
|
126
|
+
###
|
127
|
+
_importImplicitOptions: ->
|
128
|
+
|
129
|
+
for fieldName, fieldEl of @el.underlyingFields
|
130
|
+
# import initial values, with constructor options taking precedence
|
131
|
+
unless @options.initialValues[fieldName]?
|
132
|
+
@options.initialValues[fieldName] = fieldEl.val()
|
133
|
+
else # update underlying field value so that it is canonical.
|
134
|
+
@options.initialValues[fieldName] = @options.initialValues[fieldName].toString()
|
135
|
+
@_setUnderlyingValue(fieldName, @options.initialValues[fieldName])
|
136
|
+
# set a flag if any fields were initially filled
|
137
|
+
if @options.initialValues[fieldName]?.length > 0
|
138
|
+
@_state['initiallyFilled'] = true
|
139
|
+
# import initial validation state
|
140
|
+
unless @options.validationState[fieldName]?
|
141
|
+
@options.validationState[fieldName] = not fieldEl.hasClass('invalid')
|
142
|
+
|
143
|
+
# If no explicit acceptedCardProducts were specified, determine accepted
|
144
|
+
# card products using the underlying type select field.
|
145
|
+
unless @options.acceptedCardProducts?
|
146
|
+
@options.acceptedCardProducts = []
|
147
|
+
@el.underlyingFields.type.find('option').each (i, _el)=>
|
148
|
+
el = $(_el)
|
149
|
+
shortname = el.attr('data-sc-type') || el.attr('value')
|
150
|
+
@options.acceptedCardProducts.push shortname
|
151
|
+
|
152
|
+
# setup default values; when render is called, these will be picked up
|
153
|
+
if @options.initialValues.number?.length > 0
|
154
|
+
@set 'number', @options.initialValues.number
|
155
|
+
|
156
|
+
if @options.initialValues.name?.length > 0
|
157
|
+
@set 'name', @options.initialValues.name
|
158
|
+
|
159
|
+
if @options.initialValues.cvc?.length > 0
|
160
|
+
@set 'cvc', @options.initialValues.cvc
|
161
|
+
|
162
|
+
if @options.initialValues.expYear?.length > 0 and
|
163
|
+
@options.initialValues.expMonth?.length > 0
|
164
|
+
_initialExp = new Date parseInt(@options.initialValues.expYear),
|
165
|
+
parseInt(@options.initialValues.expMonth) - 1, 1
|
166
|
+
@set 'exp', _initialExp
|
167
|
+
|
168
|
+
@_updateValidationForFace('front')
|
169
|
+
@_updateValidationForFace('back')
|
170
|
+
|
171
|
+
set: (field, newValue)->
|
172
|
+
@_inputViews[field].setValue(newValue)
|
173
|
+
@_inputViews[field].trigger('valueChanged', @_inputViews[field])
|
174
|
+
|
175
|
+
###
|
176
|
+
Bind interaction events to their appropriate handlers.
|
177
|
+
###
|
178
|
+
_bindInputEvents: ->
|
179
|
+
# bind change handlers to render
|
180
|
+
@el.underlyingFields.number.bind "change", (e)=>
|
181
|
+
@_inputViews.number.setValue @_getUnderlyingValue('number')
|
182
|
+
@render()
|
183
|
+
|
184
|
+
_expirationChange = (e)=>
|
185
|
+
month = parseInt @_getUnderlyingValue('expMonth')
|
186
|
+
year = parseInt @_getUnderlyingValue('expYear')
|
187
|
+
@_inputViews.exp.setValue new Date(year, month - 1)
|
188
|
+
@render()
|
189
|
+
|
190
|
+
@el.underlyingFields.expMonth.bind "change", _expirationChange
|
191
|
+
@el.underlyingFields.expYear.bind "change", _expirationChange
|
192
|
+
|
193
|
+
@el.underlyingFields.name.bind "change", (e)=>
|
194
|
+
@_inputViews.exp.setValue @_getUnderlyingValue('name')
|
195
|
+
@render()
|
196
|
+
|
197
|
+
@el.underlyingFields.cvc.bind "change", (e)=>
|
198
|
+
@_inputViews.exp.setValue @_getUnderlyingValue('cvc')
|
199
|
+
@render()
|
200
|
+
|
201
|
+
# bind change events to their underlying form elements
|
202
|
+
@_inputViews.number.bind "change valueChanged", (e, input)=>
|
203
|
+
cardNumber = input.getValue()
|
204
|
+
@_setUnderlyingValue 'number', cardNumber
|
205
|
+
@_updateValidation 'number', cardNumber
|
206
|
+
# update the product if needed.
|
207
|
+
number = @_getUnderlyingValue('number')
|
208
|
+
matchedProduct = Skeuocard::CardProduct.firstMatchingNumber(number)
|
209
|
+
# check if the product is accepted
|
210
|
+
if not @product?.eql(matchedProduct)
|
211
|
+
@_log("Product will change:", @product, "=>", matchedProduct)
|
212
|
+
if matchedProduct?.attrs.companyShortname in @options.acceptedCardProducts
|
213
|
+
@trigger 'productWillChange.skeuocard', [@, @product, matchedProduct]
|
214
|
+
previousProduct = @product
|
215
|
+
@el.container.removeClass('unaccepted')
|
216
|
+
@_renderProduct(matchedProduct)
|
217
|
+
@product = matchedProduct
|
218
|
+
else if matchedProduct?
|
219
|
+
@trigger 'productWillChange.skeuocard', [@, @product, null]
|
220
|
+
@el.container.addClass('unaccepted')
|
221
|
+
@_renderProduct(null)
|
222
|
+
@product = null
|
223
|
+
else
|
224
|
+
@trigger 'productWillChange.skeuocard', [@, @product, null]
|
225
|
+
@el.container.removeClass('unaccepted')
|
226
|
+
@_renderProduct(null)
|
227
|
+
@product = null
|
228
|
+
@trigger 'productDidChange.skeuocard', [@, previousProduct, @product]
|
229
|
+
|
230
|
+
@_inputViews.exp.bind "keyup valueChanged", (e, input)=>
|
231
|
+
newDate = input.getValue()
|
232
|
+
@_updateValidation('exp', newDate)
|
233
|
+
if newDate?
|
234
|
+
@_setUnderlyingValue('expMonth', newDate.getMonth() + 1)
|
235
|
+
@_setUnderlyingValue('expYear', newDate.getFullYear())
|
236
|
+
|
237
|
+
@_inputViews.name.bind "keyup valueChanged", (e, input)=>
|
238
|
+
value = input.getValue()
|
239
|
+
@_setUnderlyingValue('name', value)
|
240
|
+
@_updateValidation('name', value)
|
241
|
+
|
242
|
+
@_inputViews.cvc.bind "keyup valueChanged", (e, input)=>
|
243
|
+
value = input.getValue()
|
244
|
+
@_setUnderlyingValue('cvc', value)
|
245
|
+
@_updateValidation('cvc', value)
|
246
|
+
|
247
|
+
@el.container.delegate "input", "keyup keydown", @_handleFieldTab.bind(@)
|
248
|
+
|
249
|
+
@_tabViews.front.el.click => @flip()
|
250
|
+
@_tabViews.back.el.click => @flip()
|
251
|
+
|
252
|
+
_handleFieldTab: (e)->
|
253
|
+
if e.which is 9 # tab
|
254
|
+
currentFieldEl = $(e.currentTarget)
|
255
|
+
_oppositeFace = if @visibleFace is 'front' then 'back' else 'front'
|
256
|
+
_currentFace = if @visibleFace is 'front' then 'front' else 'back'
|
257
|
+
backFieldEls = @el[_oppositeFace].find('input')
|
258
|
+
frontFieldEls = @el[_currentFace].find('input')
|
259
|
+
|
260
|
+
if @visibleFace is 'front' and
|
261
|
+
@el.front.hasClass('filled') and
|
262
|
+
backFieldEls.length > 0 and
|
263
|
+
frontFieldEls.index(currentFieldEl) is frontFieldEls.length-1 and
|
264
|
+
not e.shiftKey
|
265
|
+
@flip()
|
266
|
+
backFieldEls.first().focus()
|
267
|
+
e.preventDefault()
|
268
|
+
if @visibleFace is 'back' and e.shiftKey
|
269
|
+
@flip()
|
270
|
+
backFieldEls.last().focus() # other side, now...
|
271
|
+
e.preventDefault()
|
272
|
+
return true
|
273
|
+
|
274
|
+
_updateValidation: (fieldName, newValue)->
|
275
|
+
return false unless @product?
|
276
|
+
|
277
|
+
# Check against the current product to determine if the field is filled
|
278
|
+
isFilled = @product[fieldName].isFilled(newValue)
|
279
|
+
# If an initial value was supplied and marked as invalid, ensure that it
|
280
|
+
# has been changed to a new value.
|
281
|
+
needsFix = @options.validationState[fieldName]? is false
|
282
|
+
isFixed = @options.initialValues[fieldName]? and
|
283
|
+
newValue isnt @options.initialValues[fieldName]
|
284
|
+
# Check validity of value, asserting fixes have occurred if necessary.
|
285
|
+
isValid = @product[fieldName].isValid(newValue) and ((needsFix and isFixed) or true)
|
286
|
+
|
287
|
+
# Determine if state changed
|
288
|
+
fillStateChanged = @_state["#{fieldName}Filled"] isnt isFilled
|
289
|
+
validationStateChanged = @_state["#{fieldName}Valid"] isnt isValid
|
290
|
+
|
291
|
+
# If the fill state has changed, trigger events, and make styling changes.
|
292
|
+
if fillStateChanged
|
293
|
+
@trigger "fieldFillStateWillChange.skeuocard", [@, fieldName, isFilled]
|
294
|
+
@_inputViews[fieldName].el.toggleClass 'filled', isFilled
|
295
|
+
@_state["#{fieldName}Filled"] = isFilled
|
296
|
+
@trigger "fieldFillStateDidChange.skeuocard", [@, fieldName, isFilled]
|
297
|
+
|
298
|
+
# If the valid state has changed, trigger events, and make styling changes.
|
299
|
+
if validationStateChanged
|
300
|
+
@trigger "fieldValidationStateWillChange.skeuocard", [@, fieldName, isValid]
|
301
|
+
@_inputViews[fieldName].el.toggleClass 'valid', isValid
|
302
|
+
@_inputViews[fieldName].el.toggleClass 'invalid', not isValid
|
303
|
+
@_state["#{fieldName}Valid"] = isValid
|
304
|
+
@trigger "fieldValidationStateDidChange.skeuocard", [@, fieldName, isValid]
|
305
|
+
|
306
|
+
@_updateValidationForFace('front')
|
307
|
+
@_updateValidationForFace('back')
|
308
|
+
|
309
|
+
_updateValidationForFace: (face)->
|
310
|
+
fieldsFilled = (iv.el.hasClass('filled') for iv in @_inputViewsByFace[face]).every(Boolean)
|
311
|
+
fieldsValid = (iv.el.hasClass('valid') for iv in @_inputViewsByFace[face]).every(Boolean)
|
312
|
+
|
313
|
+
isFilled = (fieldsFilled and @product?) or (@_state['initiallyFilled'] or false)
|
314
|
+
isValid = fieldsValid and @product?
|
315
|
+
|
316
|
+
fillStateChanged = @_state["#{face}Filled"] isnt isFilled
|
317
|
+
validationStateChanged = @_state["#{face}Valid"] isnt isValid
|
318
|
+
|
319
|
+
if fillStateChanged
|
320
|
+
@trigger "faceFillStateWillChange.skeuocard", [@, face, isFilled]
|
321
|
+
@el[face].toggleClass 'filled', isFilled
|
322
|
+
@_state["#{face}Filled"] = isFilled
|
323
|
+
@trigger "faceFillStateDidChange.skeuocard", [@, face, isFilled]
|
324
|
+
|
325
|
+
if validationStateChanged
|
326
|
+
@trigger "faceValidationStateWillChange.skeuocard", [@, face, isValid]
|
327
|
+
@el[face].toggleClass 'valid', isValid
|
328
|
+
@el[face].toggleClass 'invalid', not isValid
|
329
|
+
@_state["#{face}Valid"] = isValid
|
330
|
+
@trigger "faceValidationStateDidChange.skeuocard", [@, face, isValid]
|
331
|
+
|
332
|
+
###
|
333
|
+
Assert rendering changes necessary for the current product. Passing a null
|
334
|
+
value instead of a product will revert the card to a generic state.
|
335
|
+
###
|
336
|
+
_renderProduct: (product)->
|
337
|
+
@_log("[_renderProduct]", "Rendering product:", product)
|
338
|
+
|
339
|
+
# remove existing product and issuer classes (destyling product)
|
340
|
+
@el.container.removeClass (index, css)=>
|
341
|
+
(css.match(/\b(product|issuer)-\S+/g) || []).join(' ')
|
342
|
+
# add classes necessary to identify new product
|
343
|
+
if product?.attrs.companyShortname?
|
344
|
+
@el.container.addClass("product-#{product.attrs.companyShortname}")
|
345
|
+
if product?.attrs.issuerShortname?
|
346
|
+
@el.container.addClass("issuer-#{product.attrs.issuerShortname}")
|
347
|
+
# update the underlying card type field
|
348
|
+
@_setUnderlyingValue('type', product?.attrs.companyShortname || null)
|
349
|
+
# reconfigure the number input groupings
|
350
|
+
@_inputViews.number.setGroupings(product?.attrs.cardNumberGrouping ||
|
351
|
+
[@options.genericPlaceholder.length], @options.dontFocus)
|
352
|
+
delete @options.dontFocus
|
353
|
+
if product?
|
354
|
+
# reconfigure the expiration input groupings
|
355
|
+
@_inputViews.exp.reconfigure
|
356
|
+
pattern: product?.attrs.expirationFormat || "MM/YY"
|
357
|
+
# reconfigure the CVC
|
358
|
+
@_inputViews.cvc.attr
|
359
|
+
maxlength: product.attrs.cvcLength
|
360
|
+
placeholder: new Array(product.attrs.cvcLength + 1).join(@options.cardNumberPlaceholderChar)
|
361
|
+
|
362
|
+
# set visibility and re-layout fields
|
363
|
+
@_inputViewsByFace = {front: [], back: []}
|
364
|
+
focused = $('input:focus') # allow restoration of focus upon re-attachment
|
365
|
+
for fieldName, destFace of product.attrs.layout
|
366
|
+
@_log("Moving", fieldName, "to", destFace)
|
367
|
+
viewEl = @_inputViews[fieldName].el.detach()
|
368
|
+
viewEl.appendTo(@el[destFace])
|
369
|
+
@_inputViewsByFace[destFace].push @_inputViews[fieldName]
|
370
|
+
@_inputViews[fieldName].show()
|
371
|
+
# Restore focus. Use setTimeout to resolve IE10 issue.
|
372
|
+
setTimeout =>
|
373
|
+
fieldEl = focused.first()
|
374
|
+
if fieldEl.length
|
375
|
+
fieldLength = fieldEl[0].maxLength
|
376
|
+
fieldEl.focus()
|
377
|
+
fieldEl[0].setSelectionRange(fieldLength, fieldLength)
|
378
|
+
, 10
|
379
|
+
else
|
380
|
+
for fieldName, view of @_inputViews
|
381
|
+
view.hide() if fieldName isnt 'number'
|
382
|
+
|
383
|
+
return product
|
384
|
+
|
385
|
+
_renderValidation: ->
|
386
|
+
# update the validation state of all fields
|
387
|
+
for fieldName, fieldView of @_inputViews
|
388
|
+
@_updateValidation(fieldName, fieldView.getValue())
|
389
|
+
|
390
|
+
# Update the card's visual representation to reflect internal state.
|
391
|
+
render: ->
|
392
|
+
@_renderProduct(@product)
|
393
|
+
@_renderValidation()
|
394
|
+
# @_flipToInvalidSide()
|
395
|
+
|
396
|
+
# Flip the card over.
|
397
|
+
flip: ->
|
398
|
+
targetFace = if @visibleFace is 'front' then 'back' else 'front'
|
399
|
+
@trigger('faceWillBecomeVisible.skeuocard', [@, targetFace])
|
400
|
+
@visibleFace = targetFace
|
401
|
+
@el.cardBody.toggleClass('flip')
|
402
|
+
surfaceName = if @visibleFace is 'front' then 'front' else 'back'
|
403
|
+
@el[surfaceName].find('.cc-field').not('.filled').find('input').first().focus()
|
404
|
+
@trigger('faceDidBecomeVisible.skeuocard', [@, targetFace])
|
405
|
+
|
406
|
+
# Set a value in the underlying form.
|
407
|
+
_setUnderlyingValue: (field, newValue)->
|
408
|
+
fieldEl = @el.underlyingFields[field]
|
409
|
+
_newValue = (newValue || "").toString()
|
410
|
+
throw "Set underlying value of unknown field: #{field}." unless fieldEl?
|
411
|
+
|
412
|
+
@trigger('change.skeuocard', [@])
|
413
|
+
unless fieldEl.is('select')
|
414
|
+
@el.underlyingFields[field].val(_newValue)
|
415
|
+
else
|
416
|
+
remapAttrKey = "data-sc-" + field.toLowerCase()
|
417
|
+
fieldEl.find('option').each (i, _el)=>
|
418
|
+
optionEl = $(_el)
|
419
|
+
if _newValue is (optionEl.attr(remapAttrKey) || optionEl.attr('value'))
|
420
|
+
@el.underlyingFields[field].val optionEl.attr('value')
|
421
|
+
|
422
|
+
# Get a value from the underlying form.
|
423
|
+
_getUnderlyingValue: (field)->
|
424
|
+
@el.underlyingFields[field]?.val()
|
425
|
+
|
426
|
+
isValid: ->
|
427
|
+
if @product
|
428
|
+
if @product.faces is 'both'
|
429
|
+
not @el.front.hasClass('invalid') and not @el.back.hasClass('invalid')
|
430
|
+
else if @product.faces is 'front'
|
431
|
+
not @el.front.hasClass('invalid')
|
432
|
+
else
|
433
|
+
not @el.back.hasClass('invalid')
|
434
|
+
else
|
435
|
+
false
|
436
|
+
|
437
|
+
|
438
|
+
# Export the object.
|
439
|
+
window.Skeuocard = Skeuocard
|
@@ -0,0 +1,42 @@
|
|
1
|
+
###
|
2
|
+
Skeuocard::TextInputView
|
3
|
+
###
|
4
|
+
class Skeuocard::TextInputView
|
5
|
+
constructor: (opts)->
|
6
|
+
@el = $('<div>')
|
7
|
+
@inputEl = $("<input>").attr
|
8
|
+
type: 'text'
|
9
|
+
placeholder: opts.placeholder
|
10
|
+
class: opts.class
|
11
|
+
@el.append @inputEl
|
12
|
+
@el.addClass 'cc-field'
|
13
|
+
@options = opts
|
14
|
+
@el.delegate "input", "focus", (e)=> @el.addClass('focus')
|
15
|
+
@el.delegate "input", "blur", (e)=> @el.removeClass('focus')
|
16
|
+
@el.delegate "input", "keyup", (e)=>
|
17
|
+
e.stopPropagation()
|
18
|
+
@trigger('keyup', [@])
|
19
|
+
|
20
|
+
clear: ->
|
21
|
+
@inputEl.val("")
|
22
|
+
|
23
|
+
attr: (args...)->
|
24
|
+
@inputEl.attr(args...)
|
25
|
+
|
26
|
+
setValue: (newValue)->
|
27
|
+
@inputEl.val(newValue)
|
28
|
+
|
29
|
+
getValue: ->
|
30
|
+
@inputEl.val()
|
31
|
+
|
32
|
+
bind: (args...)->
|
33
|
+
@el.bind(args...)
|
34
|
+
|
35
|
+
trigger: (args...)->
|
36
|
+
@el.trigger(args...)
|
37
|
+
|
38
|
+
show: ->
|
39
|
+
@el.show()
|
40
|
+
|
41
|
+
hide: ->
|
42
|
+
@el.hide()
|
@@ -0,0 +1,7 @@
|
|
1
|
+
var cssua=function(k,n){var p=/\s*([\-\w ]+)[\s\/\:]([\d_]+\b(?:[\-\._\/]\w+)*)/,q=/([\w\-\.]+[\s\/][v]?[\d_]+\b(?:[\-\._\/]\w+)*)/g,r=/\b(?:(blackberry\w*|bb10)|(rim tablet os))(?:\/(\d+\.\d+(?:\.\w+)*))?/,s=/\bsilk-accelerated=true\b/,t=/\bfluidapp\b/,u=/(\bwindows\b|\bmacintosh\b|\blinux\b|\bunix\b)/,v=/(\bandroid\b|\bipad\b|\bipod\b|\bwindows phone\b|\bwpdesktop\b|\bxblwp7\b|\bzunewp7\b|\bwindows ce\b|\bblackberry\w*|\bbb10\b|\brim tablet os\b|\bmeego|\bwebos\b|\bpalm|\bsymbian|\bj2me\b|\bdocomo\b|\bpda\b|\bchtml\b|\bmidp\b|\bcldc\b|\w*?mobile\w*?|\w*?phone\w*?)/,
|
2
|
+
w=/(\bxbox\b|\bplaystation\b|\bnintendo\s+\w+)/,d={parse:function(c){var a={};c=(""+c).toLowerCase();if(!c)return a;for(var b,g,e=c.split(/[()]/),f=0,d=e.length;f<d;f++)if(f%2){var l=e[f].split(";");b=0;for(g=l.length;b<g;b++)if(p.exec(l[b])){var h=RegExp.$1.split(" ").join("_"),k=RegExp.$2;if(!a[h]||parseFloat(a[h])<parseFloat(k))a[h]=k}}else if(l=e[f].match(q))for(b=0,g=l.length;b<g;b++)h=l[b].split(/[\/\s]+/),h.length&&"mozilla"!==h[0]&&(a[h[0].split(" ").join("_")]=h.slice(1).join("-"));v.exec(c)?
|
3
|
+
(a.mobile=RegExp.$1,r.exec(c)&&(delete a[a.mobile],a.blackberry=a.version||RegExp.$3||RegExp.$2||RegExp.$1,RegExp.$1?a.mobile="blackberry":"0.0.1"===a.version&&(a.blackberry="7.1.0.0"))):u.exec(c)?a.desktop=RegExp.$1:w.exec(c)&&(a.game=RegExp.$1,b=a.game.split(" ").join("_"),a.version&&!a[b]&&(a[b]=a.version));a.intel_mac_os_x?(a.mac_os_x=a.intel_mac_os_x.split("_").join("."),delete a.intel_mac_os_x):a.cpu_iphone_os?(a.ios=a.cpu_iphone_os.split("_").join("."),delete a.cpu_iphone_os):a.cpu_os?(a.ios=
|
4
|
+
a.cpu_os.split("_").join("."),delete a.cpu_os):"iphone"!==a.mobile||a.ios||(a.ios="1");a.opera&&a.version?(a.opera=a.version,delete a.blackberry):s.exec(c)?a.silk_accelerated=!0:t.exec(c)&&(a.fluidapp=a.version);if(a.applewebkit)a.webkit=a.applewebkit,delete a.applewebkit,a.opr&&(a.opera=a.opr,delete a.opr,delete a.chrome),a.safari&&(a.chrome||a.crios||a.opera||a.silk||a.fluidapp||a.phantomjs||a.mobile&&!a.ios?delete a.safari:a.safari=a.version&&!a.rim_tablet_os?a.version:{419:"2.0.4",417:"2.0.3",
|
5
|
+
416:"2.0.2",412:"2.0",312:"1.3",125:"1.2",85:"1.0"}[parseInt(a.safari,10)]||a.safari);else if(a.msie||a.trident)if(a.opera||(a.ie=a.msie||a.rv),delete a.msie,a.windows_phone_os)a.windows_phone=a.windows_phone_os,delete a.windows_phone_os;else{if("wpdesktop"===a.mobile||"xblwp7"===a.mobile||"zunewp7"===a.mobile)a.mobile="windows desktop",a.windows_phone=9>+a.ie?"7.0":10>+a.ie?"7.5":"8.0",delete a.windows_nt}else if(a.gecko||a.firefox)a.gecko=a.rv;a.rv&&delete a.rv;a.version&&delete a.version;return a},
|
6
|
+
format:function(c){var a="",b;for(b in c)if(b&&c.hasOwnProperty(b)){var g=b,e=c[b],g=g.split(".").join("-"),f=" ua-"+g;if("string"===typeof e){for(var e=e.split(" ").join("_").split(".").join("-"),d=e.indexOf("-");0<d;)f+=" ua-"+g+"-"+e.substring(0,d),d=e.indexOf("-",d+1);f+=" ua-"+g+"-"+e}a+=f}return a},encode:function(c){var a="",b;for(b in c)b&&c.hasOwnProperty(b)&&(a&&(a+="\x26"),a+=encodeURIComponent(b)+"\x3d"+encodeURIComponent(c[b]));return a}};d.userAgent=d.ua=d.parse(n);var m=d.format(d.ua)+
|
7
|
+
" js";k.className=k.className?k.className.replace(/\bno-js\b/g,"")+m:m.substr(1);return d}(document.documentElement,navigator.userAgent);
|
@@ -0,0 +1,17 @@
|
|
1
|
+
var metas = document.getElementsByTagName('meta');
|
2
|
+
var i;
|
3
|
+
if (navigator.userAgent.match(/iPhone/i)) {
|
4
|
+
for (i=0; i<metas.length; i++) {
|
5
|
+
if (metas[i].name == "viewport") {
|
6
|
+
metas[i].content = "width=device-width, minimum-scale=1.0, maximum-scale=1.0";
|
7
|
+
}
|
8
|
+
}
|
9
|
+
document.addEventListener("gesturestart", gestureStart, false);
|
10
|
+
}
|
11
|
+
function gestureStart() {
|
12
|
+
for (i=0; i<metas.length; i++) {
|
13
|
+
if (metas[i].name == "viewport") {
|
14
|
+
metas[i].content = "width=device-width, minimum-scale=0.25, maximum-scale=1.6";
|
15
|
+
}
|
16
|
+
}
|
17
|
+
}
|