weaver 0.8.14 → 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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/data/weaver/css/plugins/codemirror/codemirror.css +97 -96
  3. data/data/weaver/css/style-dark.css +8628 -0
  4. data/data/weaver/css/style.css +37 -0
  5. data/data/weaver/js/plugins/codemirror/codemirror.js +8817 -6763
  6. data/data/weaver/js/plugins/skeuocard/Gruntfile.coffee +74 -0
  7. data/data/weaver/js/plugins/skeuocard/LICENSE +21 -0
  8. data/data/weaver/js/plugins/skeuocard/README.md +393 -0
  9. data/data/weaver/js/plugins/skeuocard/bower.json +40 -0
  10. data/data/weaver/js/plugins/skeuocard/fonts/ocra-webfont.eot +0 -0
  11. data/data/weaver/js/plugins/skeuocard/fonts/ocra-webfont.svg +138 -0
  12. data/data/weaver/js/plugins/skeuocard/fonts/ocra-webfont.ttf +0 -0
  13. data/data/weaver/js/plugins/skeuocard/fonts/ocra-webfont.woff +0 -0
  14. data/data/weaver/js/plugins/skeuocard/images/card-flip-arrow.png +0 -0
  15. data/data/weaver/js/plugins/skeuocard/images/card-front-background.png +0 -0
  16. data/data/weaver/js/plugins/skeuocard/images/card-invalid-indicator.png +0 -0
  17. data/data/weaver/js/plugins/skeuocard/images/card-valid-anim.gif +0 -0
  18. data/data/weaver/js/plugins/skeuocard/images/card-valid-indicator.png +0 -0
  19. data/data/weaver/js/plugins/skeuocard/images/error-pointer.png +0 -0
  20. data/data/weaver/js/plugins/skeuocard/images/issuers/visa-chase-sapphire.png +0 -0
  21. data/data/weaver/js/plugins/skeuocard/images/issuers/visa-simple-front.png +0 -0
  22. data/data/weaver/js/plugins/skeuocard/images/products/amex-front.png +0 -0
  23. data/data/weaver/js/plugins/skeuocard/images/products/dinersclubintl-front.png +0 -0
  24. data/data/weaver/js/plugins/skeuocard/images/products/discover-front.png +0 -0
  25. data/data/weaver/js/plugins/skeuocard/images/products/generic-back.png +0 -0
  26. data/data/weaver/js/plugins/skeuocard/images/products/generic-front.png +0 -0
  27. data/data/weaver/js/plugins/skeuocard/images/products/jcb-front.png +0 -0
  28. data/data/weaver/js/plugins/skeuocard/images/products/maestro-front.png +0 -0
  29. data/data/weaver/js/plugins/skeuocard/images/products/mastercard-front.png +0 -0
  30. data/data/weaver/js/plugins/skeuocard/images/products/unionpay-front.png +0 -0
  31. data/data/weaver/js/plugins/skeuocard/images/products/visa-back.png +0 -0
  32. data/data/weaver/js/plugins/skeuocard/images/products/visa-front.png +0 -0
  33. data/data/weaver/js/plugins/skeuocard/images/src/card-front-background.fw.png +0 -0
  34. data/data/weaver/js/plugins/skeuocard/images/src/error-pointer.png +0 -0
  35. data/data/weaver/js/plugins/skeuocard/images/src/product-amex-front.fw.png +0 -0
  36. data/data/weaver/js/plugins/skeuocard/images/src/product-dinersclub-front.fw.png +0 -0
  37. data/data/weaver/js/plugins/skeuocard/images/src/product-discover-front.fw.png +0 -0
  38. data/data/weaver/js/plugins/skeuocard/images/src/product-generic-front.fw.png +0 -0
  39. data/data/weaver/js/plugins/skeuocard/images/src/product-jcb-front.fw.png +0 -0
  40. data/data/weaver/js/plugins/skeuocard/images/src/product-maestro-front.fw.png +0 -0
  41. data/data/weaver/js/plugins/skeuocard/images/src/product-mastercard-front.fw.png +0 -0
  42. data/data/weaver/js/plugins/skeuocard/images/src/product-unionpay-front.fw.png +0 -0
  43. data/data/weaver/js/plugins/skeuocard/images/src/product-visa-front.fw.png +0 -0
  44. data/data/weaver/js/plugins/skeuocard/index.html +124 -0
  45. data/data/weaver/js/plugins/skeuocard/javascripts/skeuocard.js +1748 -0
  46. data/data/weaver/js/plugins/skeuocard/javascripts/skeuocard.min.js +2 -0
  47. data/data/weaver/js/plugins/skeuocard/javascripts/src/CardProduct.coffee +284 -0
  48. data/data/weaver/js/plugins/skeuocard/javascripts/src/ExpirationInputView.coffee +206 -0
  49. data/data/weaver/js/plugins/skeuocard/javascripts/src/FlipTabView.coffee +67 -0
  50. data/data/weaver/js/plugins/skeuocard/javascripts/src/SegmentedCardNumberInputView.coffee +284 -0
  51. data/data/weaver/js/plugins/skeuocard/javascripts/src/Skeuocard.coffee +439 -0
  52. data/data/weaver/js/plugins/skeuocard/javascripts/src/TextInputView.coffee +42 -0
  53. data/data/weaver/js/plugins/skeuocard/javascripts/vendor/cssua.min.js +7 -0
  54. data/data/weaver/js/plugins/skeuocard/javascripts/vendor/demo.fix.js +17 -0
  55. data/data/weaver/js/plugins/skeuocard/javascripts/vendor/jquery-2.0.3.min.js +5 -0
  56. data/data/weaver/js/plugins/skeuocard/package-lock.json +760 -0
  57. data/data/weaver/js/plugins/skeuocard/package.json +19 -0
  58. data/data/weaver/js/plugins/skeuocard/screenshot.png +0 -0
  59. data/data/weaver/js/plugins/skeuocard/styles/demo.css +2 -0
  60. data/data/weaver/js/plugins/skeuocard/styles/skeuocard.css +2 -0
  61. data/data/weaver/js/plugins/skeuocard/styles/skeuocard.reset.css +2 -0
  62. data/data/weaver/js/plugins/skeuocard/styles/src/_browser_hacks.scss +52 -0
  63. data/data/weaver/js/plugins/skeuocard/styles/src/_cards.scss +516 -0
  64. data/data/weaver/js/plugins/skeuocard/styles/src/_util.scss +15 -0
  65. data/data/weaver/js/plugins/skeuocard/styles/src/demo.scss +265 -0
  66. data/data/weaver/js/plugins/skeuocard/styles/src/skeuocard.reset.scss +60 -0
  67. data/data/weaver/js/plugins/skeuocard/styles/src/skeuocard.scss +190 -0
  68. data/lib/weaver/page_types/page.rb +5 -0
  69. data/lib/weaver/page_types/structured_page.rb +1 -1
  70. data/lib/weaver/version.rb +1 -1
  71. 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
+ }