ultimate-base 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/.gitignore +5 -0
  2. data/.rvmrc +2 -0
  3. data/.rvmrc.example +2 -0
  4. data/Gemfile +4 -0
  5. data/Gemfile.lock +14 -0
  6. data/LICENSE +5 -0
  7. data/README.md +1 -0
  8. data/Rakefile +1 -0
  9. data/app/assets/javascripts/backbone/backbone.js +1452 -0
  10. data/app/assets/javascripts/backbone/ultimate/app.js.coffee +25 -0
  11. data/app/assets/javascripts/backbone/ultimate/collection.js.coffee +12 -0
  12. data/app/assets/javascripts/backbone/ultimate/model.js.coffee +12 -0
  13. data/app/assets/javascripts/backbone/ultimate/router.js.coffee +13 -0
  14. data/app/assets/javascripts/backbone/ultimate/view.js.coffee +96 -0
  15. data/app/assets/javascripts/ultimate/base.js.coffee +103 -0
  16. data/app/assets/javascripts/ultimate/bus.js.coffee +57 -0
  17. data/app/assets/javascripts/ultimate/devise.js.coffee +18 -0
  18. data/app/assets/javascripts/ultimate/experimental/_inflections/dzone.inflections.js +154 -0
  19. data/app/assets/javascripts/ultimate/experimental/_inflections/inflections.js.coffee +2 -0
  20. data/app/assets/javascripts/ultimate/experimental/_inflections/plur.js +29 -0
  21. data/app/assets/javascripts/ultimate/experimental/_inflections/underscore.inflection.js +175 -0
  22. data/app/assets/javascripts/ultimate/experimental/fuzzy-json-generator.js.coffee +48 -0
  23. data/app/assets/javascripts/ultimate/helpers/array.js.coffee +63 -0
  24. data/app/assets/javascripts/ultimate/helpers/asset_tag.js.coffee +63 -0
  25. data/app/assets/javascripts/ultimate/helpers/decor.js.coffee +14 -0
  26. data/app/assets/javascripts/ultimate/helpers/forms.js.coffee +0 -0
  27. data/app/assets/javascripts/ultimate/helpers/tags.js.coffee +70 -0
  28. data/app/assets/javascripts/ultimate/helpers.js.coffee +149 -0
  29. data/app/assets/javascripts/ultimate/improves/datepicker.js.coffee +34 -0
  30. data/app/assets/javascripts/ultimate/improves/form-errors.js.coffee +146 -0
  31. data/app/assets/javascripts/ultimate/improves/form.js.coffee +155 -0
  32. data/app/assets/javascripts/ultimate/improves/magic-radios.js.coffee +41 -0
  33. data/app/assets/javascripts/ultimate/improves/tablesorter.js +59 -0
  34. data/app/assets/javascripts/ultimate/improves/typed-field.js.coffee +98 -0
  35. data/app/assets/javascripts/ultimate/underscore/underscore.js +1059 -0
  36. data/app/assets/javascripts/ultimate/underscore/underscore.string.js +480 -0
  37. data/app/assets/javascripts/ultimate/widgets/dock.js.coffee +70 -0
  38. data/app/assets/javascripts/ultimate/widgets/gear.js.coffee +84 -0
  39. data/app/assets/javascripts/ultimate/widgets/jquery-ext.js.coffee +104 -0
  40. data/app/assets/javascripts/ultimate/widgets/jquery.adapter.js.coffee +62 -0
  41. data/app/assets/javascripts/ultimate/widgets/widget.js.coffee +115 -0
  42. data/app/assets/stylesheets/polyfills/PIE.htc +96 -0
  43. data/app/assets/stylesheets/polyfills/boxsizing.htc +300 -0
  44. data/app/assets/stylesheets/ultimate/mixins/_routines.css.scss +95 -0
  45. data/app/assets/stylesheets/ultimate/mixins/_vendors.css.scss +34 -0
  46. data/app/assets/stylesheets/ultimate/mixins/css3/_text-shadow.scss +37 -0
  47. data/app/assets/stylesheets/ultimate/mixins/css3.css.scss +328 -0
  48. data/app/assets/stylesheets/ultimate/mixins/decor.css.scss +86 -0
  49. data/app/assets/stylesheets/ultimate/mixins/fonts.css.scss +100 -0
  50. data/app/assets/stylesheets/ultimate/mixins/microstructures.css.scss +188 -0
  51. data/app/assets/stylesheets/ultimate/structures/slider.css.scss +53 -0
  52. data/lib/ultimate-base/engine.rb +6 -0
  53. data/lib/ultimate-base/extensions/directive_processor.rb +64 -0
  54. data/lib/ultimate-base/extensions/sass_script_functions.rb +39 -0
  55. data/lib/ultimate-base/version.rb +5 -0
  56. data/lib/ultimate-base.rb +10 -0
  57. data/ultimate-base.gemspec +25 -0
  58. metadata +102 -0
@@ -0,0 +1,146 @@
1
+ # TODO this is part of custom forms
2
+ # TODO registrable
3
+ # TODO may be, replace "clear immune" technique with something more effective, ex. additional class
4
+
5
+ ( ( $ ) ->
6
+
7
+ $.errorableFields = {}
8
+ $.errorableFieldsSelector = ''
9
+
10
+ $.registerErrorableField = (selector, events, errorCleaner) ->
11
+ $.errorableFields[selector.split(/\s+/)[0]] = arguments
12
+ $.errorableFieldsSelector = _.keys($.errorableFields).join(', ')
13
+ true
14
+
15
+ $.fieldWithErrorsWrapperClass = 'field_with_errors'
16
+ $.fieldWithClearImmuneWrapperClass = 'immune'
17
+ $.labelForFieldWithErrorClass = 'with-error'
18
+
19
+ # [ selector: String = '.g-select, .g-text-field, .g-text-area' ]
20
+ # [ [, ]errors: Array = [] ]
21
+ # [ [, ]clearImmune: Boolean = false ]
22
+ $.fn.setFormErrors = ->
23
+ # dafault parameters
24
+ selector = $.errorableFieldsSelector # '.g-select, .g-text-field, .g-text-area'
25
+ errors = []
26
+ clearImmune = false
27
+ # parsing arguments
28
+ # TODO liquid arguments parser ( by arguments info, ex: {selector: String, errors: Array, clearImmune: Boolean} )
29
+ a = args arguments
30
+ if a.length
31
+ first = a.shift()
32
+ if _.isString first
33
+ selector = first
34
+ if a.length
35
+ first = a.shift()
36
+ if _.isString first
37
+ first = [first]
38
+ if _.isArray first
39
+ errors = _.uniq first
40
+ if a.length
41
+ first = a.shift()
42
+ if _.isBoolean first
43
+ clearImmune = first
44
+ # ui process
45
+ @clearFormErrors selector, clearImmune
46
+ wrapperSelector = ".#{$.fieldWithErrorsWrapperClass}"
47
+ if clearImmune
48
+ wrapperSelector += ".#{$.fieldWithClearImmuneWrapperClass}"
49
+ jErrorWrappers = @find(selector).wrap(selectorToHtml wrapperSelector).parent()
50
+ jErrorWrappers.append (for err in errors then content_tag('div', err, class: 'error')).join("\n")
51
+ jErrorWrappers.closestLabel().addClass($.labelForFieldWithErrorClass)
52
+
53
+ $.fn.setErrors = ->
54
+ deprecate('jQ.setErrors(...)', 'jQ.setFormErrors(...)')
55
+ @setFormErrors.apply(@, arguments)
56
+
57
+ # [selector]
58
+ # [clearImmune]
59
+ $.fn.clearFormErrors = ->
60
+ selector = $.errorableFieldsSelector # '.g-select, .g-text-field, .g-text-area'
61
+ clearImmune = false
62
+ # parsing arguments
63
+ # TODO liquid arguments parser ( by arguments info, ex: {selector: String, clearImmune: Boolean} )
64
+ a = args arguments
65
+ if a.length
66
+ first = a.shift()
67
+ if _.isString first
68
+ selector = first
69
+ if a.length
70
+ first = a.shift()
71
+ if _.isBoolean(first)
72
+ clearImmune = first
73
+ wrapperSelector = ".#{$.fieldWithErrorsWrapperClass}"
74
+ if clearImmune
75
+ wrapperSelector += ".#{$.fieldWithClearImmuneWrapperClass}"
76
+ else
77
+ wrapperSelector += ":not(.#{$.fieldWithClearImmuneWrapperClass})"
78
+ fullSelector = "#{wrapperSelector}:has(#{selector})"
79
+ jErrorWrappers = @find fullSelector
80
+ jErrorWrappers = @closest fullSelector unless jErrorWrappers.length
81
+ if jErrorWrappers.length
82
+ jErrorWrappers.closestLabel().removeClass($.labelForFieldWithErrorClass)
83
+ jErrorWrappers.children('.error').remove()
84
+ jErrorWrappers.children(selector).unwrap()
85
+ @
86
+
87
+ $.fn.clearFromErrors = ->
88
+ deprecate('jQ.clearFromErrors(...)', 'jQ.clearFormErrors(...)')
89
+ @clearFormErrors.apply(@, arguments)
90
+
91
+ $.ujsAjaxError = (event, jqXHR) ->
92
+ jForm = $(@).clearFormErrors()
93
+ try
94
+ responseJSON = $.parseJSON jqXHR.responseText
95
+ errors = responseJSON['errors'] or responseJSON
96
+ if errors
97
+ jFields = jForm.find '[name*="["]:input:visible' # TODO research without :visible, because has custom elements
98
+ if jFields.length
99
+ for fieldErrors, fieldName of errors
100
+ jField = jFields.filter "[name$=\"[#{fieldName}]\"]"
101
+ if jField.length
102
+ jField.closestEdge().setErrors fieldErrors
103
+ catch e
104
+ # nop
105
+ true
106
+
107
+
108
+ $.fn.customErrorsHandler = (suspend = true, eventName = 'ajax:error', selector = 'form[data-remote="true"]') ->
109
+ if @length
110
+ if @is 'form'
111
+ jForms = @filter selector
112
+ jForms.off eventName, $.ujsAjaxError
113
+ jForms.on eventName, $.ujsAjaxError if suspend
114
+ else
115
+ @off eventName, selector, $.ujsAjaxError
116
+ @on eventName, selector, $.ujsAjaxError if suspend
117
+ @
118
+
119
+
120
+
121
+
122
+ $.fn.errorCleaners = (suspend = true) ->
123
+ if @length
124
+ jDocks = if @is ':input' then @closestEdge() else @
125
+ for fieldWrapperSelector, [selector, events, errorCleaner] of $.errorableFields
126
+ fullSelector = ".#{$.fieldWithErrorsWrapperClass} > #{selector}"
127
+ jDocks.off events, fullSelector
128
+ jDocks.on events, fullSelector, errorCleaner if suspend
129
+ @
130
+
131
+
132
+
133
+ $.registerErrorableField '.g-text-field input:visible', 'change keyup', ->
134
+ # text-fields error cleaner
135
+ jTextField = $ @
136
+ unless jTextField.val() is '' # TODO may be use isEmptyString()
137
+ jTextField.clearFormErrors('.g-text-field').clearFormErrors('.g-text-field', true)
138
+ jTextField.change().focus() # unwraping break change, and there changing continue
139
+
140
+ $.registerErrorableField '.g-select select', 'change', ->
141
+ # selects error cleaner
142
+ jSelect = $ @
143
+ unless jSelect.val() is '' # TODO may be use isEmptyString()
144
+ jSelect.clearFormErrors('.g-select').clearFormErrors('.g-select', true)
145
+
146
+ )( jQuery )
@@ -0,0 +1,155 @@
1
+ ( ( $ ) ->
2
+
3
+ # TODO try optimize, excepting Edge and search in siblings for next cycle
4
+ $.fn.nearestFind = (selector, extremeEdgeSelector = 'form, body') ->
5
+ jEdge = @
6
+ jResult = jEdge.find selector
7
+ until jResult.length or jEdge.is extremeEdgeSelector
8
+ jEdge = jEdge.parent()
9
+ jResult = jEdge.find selector
10
+ jResult
11
+
12
+
13
+
14
+ $.fn.closestEdge = (edgeSelector = '.input-line, .field, .field-line, tr, li, fieldset, .g-box, form') ->
15
+ # TODO remove and extract edgeSelector, else undeprecate
16
+ deprecate 'closestEdge', '$().closest(\'your selector\')'
17
+ @closest edgeSelector
18
+
19
+
20
+
21
+ # @param strongById: Boolean = false
22
+ $.fn.closestLabel = ( strongById = false, passCache = false ) ->
23
+ (if @is(':input') then @ else @find(':input')).filter(':not(input[type="hidden"])').map ->
24
+ jInput = $ @
25
+ jLabel = jInput.data 'closestLabel' unless passCache
26
+ unless jLabel
27
+ if jLabel is false
28
+ return null
29
+ else
30
+ jLabel = {length: 0}
31
+ jEdge = jInput.closestEdge() # TODO may be need params
32
+ inputId = jInput.attr 'id'
33
+ if inputId
34
+ # try search label by id linkage
35
+ labelSelectorByFor = "label[for=\"#{inputId}\"]"
36
+ # at first search in the edge area
37
+ jLabel = jEdge.find labelSelectorByFor
38
+ unless jLabel.length
39
+ # try search in the closest form
40
+ jLabel = jInput.closest('form').find labelSelectorByFor
41
+ # trust only unique label on the form
42
+ jLabel = {length: 0} if jLabel.length > 1
43
+ # try get first labet in the edge area
44
+ jLabel = jEdge.find('label:first') unless jLabel.length
45
+ # taken!
46
+ if jLabel.length
47
+ # oh, this label already linked
48
+ if jLabel.data 'closestInput'
49
+ jLabel = {length: 0}
50
+ else
51
+ jLabel.data 'closestInput', jInput
52
+ jInput.data 'closestLabel', jLabel
53
+ if jLabel.length
54
+ if strongById and jLabel.attr('for') isnt jInput.attr('id')
55
+ null
56
+ else
57
+ jLabel[0]
58
+ else
59
+ null
60
+
61
+
62
+
63
+ $.linkedLabelsClick = ->
64
+ jLabel = $ @
65
+ jInput = jLabel.data 'closestInput'
66
+
67
+ unless jInput
68
+ attrFor = jLabel.attr 'for'
69
+ jInput = jLabel.nearestFind ":input##{attrFor}"
70
+ jInput = jLabel.nearestFind ':input' unless jInput.length is 1
71
+ jLabel.data 'closestInput', jInput if jInput.length
72
+
73
+ if jInput and jInput.length is 1
74
+ if jInput.is 'input:checkbox, input:radio'
75
+ jInput.click()
76
+ else if jInput.is 'select'
77
+ # TODO try: jInput.click() в любом случае свести к универсальносит
78
+ jInput.closest('.g-select').click()
79
+ else
80
+ jInput.focus()
81
+ false
82
+
83
+ # Cacheble bulletproof labels
84
+ # example: $('body').linkedLabels();
85
+ $.fn.linkedLabels = (suspend = true, eventName = 'click.linked-labels') ->
86
+ if @is 'label'
87
+ jLabels = @filter 'label'
88
+ jLabels.off eventName
89
+ jLabels.on eventName, $.linkedLabelsClick if suspend
90
+ else
91
+ @off eventName, 'label'
92
+ @on eventName, 'label', $.linkedLabelsClick if suspend
93
+ @
94
+
95
+
96
+
97
+ # [ additionAttr: String = 'data-nested-addition' ]
98
+ $.fn.setNestedAdditions = (additionAttr = 'data-nested-addition') ->
99
+ @find(":input[#{additionAttr}]").each ->
100
+ jInput = $ @
101
+ inputId = jInput.attr 'id'
102
+ if inputId
103
+ newId = "#{inputId}_#{jInput.attr additionAttr}"
104
+ jInput.attr 'id', newId
105
+ jLabel = jInput.closestLabel(true)
106
+ jLabel.attr 'for', newId if jLabel.length
107
+ jInput.removeAttr additionAttr
108
+ @
109
+
110
+
111
+
112
+ # setNestedIndex(params = {nestedField: index || [index, old_index]})
113
+ # setNestedIndex(nestedField, index, [old_index])
114
+ $.fn.setNestedIndex = ->
115
+ params = {}
116
+ if arguments.length is 1
117
+ params = arguments[0]
118
+ else
119
+ params[arguments[0]] = (if arguments.length > 2 then [arguments[1], arguments[2]] else arguments[1])
120
+ replacers =
121
+ name: []
122
+ id: []
123
+ for nestedField, index of params
124
+ if $.isArray index
125
+ [index, oldIndex] = index
126
+ else
127
+ oldIndex = '\\d*'
128
+ replacers['id'].push [ (new RegExp "_#{nestedField}_#{oldIndex}"), "_#{nestedField}_#{index}" ]
129
+ replacers['name'].push [ (new RegExp "\\[#{nestedField}\\]\\[#{oldIndex}\\]"), "[#{nestedField}][#{index}]" ]
130
+ ( if @is(':input') then @filter('[name]') else @find(':input[name]') ).map ->
131
+ jInput = $ @
132
+ for replacerAttr, replacerPairs of replacers
133
+ # if extract attribute
134
+ if attr = jInput.attr replacerAttr
135
+ # dup attr
136
+ newAttr = attr
137
+ for pair in replacerPairs
138
+ newAttr = newAttr.replace pair[0], pair[1]
139
+ if newAttr isnt attr
140
+ jInput.closestLabel(true).attr('for', newAttr) if replacerAttr is 'id'
141
+ jInput.attr replacerAttr, newAttr
142
+ @
143
+
144
+
145
+
146
+ $.fillTabIndexesCounter = 0
147
+
148
+ # TODO this may be .is 'input'
149
+ $.fn.fillTabIndexes = (onlyVisible = false) ->
150
+ if @length
151
+ @find("input:not([readonly])#{if onlyVisible then ':visible' else ''}").each ->
152
+ $(@).attr 'tabindex', ++$.fillTabIndexesCounter
153
+ @
154
+
155
+ )( jQuery )
@@ -0,0 +1,41 @@
1
+ #
2
+ # Magic Radios
3
+ #
4
+ # @example
5
+ # = f.radio_button :main_addr_same_as_jur, true, data: {disable: ".main-addr"}
6
+ # = f.radio_button :main_addr_same_as_jur, false, data: {enable: ".main-addr"}
7
+ # = f.text_field :main_addr, :disabled => @profile.main_addr_same_as_jur, :class => "main-addr"
8
+ # javascript:
9
+ # $('.some form').magicRadios();
10
+
11
+ ( ( $ ) ->
12
+
13
+ $.fn.magicRadios = (suspend = true, closestDock = 'fieldset, form, body', eventName = 'change.magic-radios') ->
14
+ if @length
15
+ jDocks = if @is 'input:radio' then @closest closestDock else @
16
+ jDocks.off eventName
17
+ if suspend
18
+ jDocks.on eventName, 'input:radio', (event) ->
19
+ jDock = $ event.delegateTarget
20
+ rules = [
21
+ ['enable', 'disabled', false, true]
22
+ ['check', 'checked', true]
23
+ ['uncheck', 'checked', false]
24
+ ['disable', 'disabled', true]
25
+ ['writable', 'readonly', false, true]
26
+ ['readonly', 'readonly', true]
27
+ ]
28
+ for rule in rules
29
+ [dataParam, propName, propValue, focusFirst] = rule
30
+ linkedFields = $(@).data dataParam
31
+ if linkedFields
32
+ jFounded = jDock.find linkedFields
33
+ jFields = jFounded.filter ':input'
34
+ jFields = jFounded.find ':input' unless jFields.length
35
+ if jFields.length
36
+ jFields.prop propName, propValue
37
+ jFields.first().focus() if focusFirst
38
+ true
39
+ @
40
+
41
+ )( jQuery )
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Декорирование таблиц с данными + добавление сортировки + доп. обработка таблиц с checkable строками
3
+ *
4
+ * @param {jQuery} jRoot корневые jQuery объекты, в которых будет осуществляться поиск table.tablesorter:not(.decorated)
5
+ * @returns {Number} count >= 0 количество продекорированных таблиц
6
+ */
7
+ function decor_tables(jRoot) {
8
+ var jTables = jRoot.find('table.tablesorter:not(.decorated)'),
9
+ jTablesHeaders = jTables.find('thead th:not([colspan])'),
10
+ ts_initData = {};
11
+ if ( arguments.length > 1 ) ts_initData.sortList = arguments[1];
12
+ jTablesHeaders.prepend('<div class="bullet-wrapper"><div class="bullet"></div></div>');
13
+ jTables.find('thead')
14
+ .find('tr:first th:first').addClass('first-child')
15
+ .prepend('<div class="corner-wrapper"><div class="corner left"></div></div>')
16
+ .end().find('tr th:last-child').addClass('last-child').first()
17
+ .prepend('<div class="corner-wrapper"><div class="corner right"></div></div>');
18
+ jTables.find('td:last-child').addClass('last-child');
19
+ jTables.each( function () {
20
+ var jTable = $(this),
21
+ jTFoot = jTable.children('tfoot');
22
+ if ( !jTFoot.length ) jTFoot = $('<tfoot></tfoot>').appendTo(jTable);
23
+ var jRow = $('<tr></tr>').appendTo(jTFoot);
24
+ $('<td colspan="' + jTable.find('thead th').length + '" class="border-bottom"></td>').appendTo(jRow)
25
+ .append('<div class="corner-wrapper"><div class="corner left"></div><div class="corner right"></div></div>');
26
+ } )
27
+ .addClass('decorated')
28
+ .tablesorter(ts_initData);
29
+ jTables.filter('.checkable').tablesorter({headers: { 0: { sorter: false}}})
30
+ .find('thead th.first-child').removeClass('header')
31
+ .find('.bullet-wrapper').remove();
32
+ return jTables.length;
33
+ }
34
+
35
+ $('table.checkable thead th:first input:checkbox').live('change', function () {
36
+ var jThis = $(this),
37
+ jTable = jThis.closest('table.checkable'),
38
+ jCheckboxes = jTable.find('tbody input:checkbox');
39
+ jCheckboxes.prop('checked', jThis.prop('checked'));
40
+ refresh_states(jTable);
41
+ } );
42
+
43
+ $('table.checkable tbody td:first-child input:checkbox').live('change', function () {
44
+ var jTable = $(this).closest('table.checkable');
45
+ jTable.find('input:checkbox#select_all').prop('checked', jTable.find('tbody input:checkbox:not(:checked)').length === 0);
46
+ refresh_states(jTable);
47
+ } );
48
+
49
+
50
+
51
+ $( function () {
52
+
53
+ if ( $.tablesorter ) {
54
+ $.tablesorter.defaults.widgets = ['zebra'];
55
+ }
56
+
57
+ //decor_tables($('body'));
58
+
59
+ } );
@@ -0,0 +1,98 @@
1
+ ( ( $ ) ->
2
+
3
+ # ========= max & min =========
4
+ $('input:text[data-gte], input:text[data-lte]').live 'change focusout blur', ->
5
+ jField = $ @
6
+ realValue = jField.val()
7
+ if realValue isnt ''
8
+ min = parseFloat jField.data('gte')
9
+ max = parseFloat jField.data('lte')
10
+ oldValue = parseFloat realValue
11
+ v = oldValue
12
+ v = 0 if isNaN v
13
+ v = min if not isNaN(min) and v < min
14
+ v = max if not isNaN(max) and v > max
15
+ jField.val(v).change() unless v is oldValue
16
+
17
+ $.fn.setFieldBounds = (min, max) ->
18
+ @filter('input:text')
19
+ .attr
20
+ 'data-gte': min
21
+ 'data-lte': max
22
+ .data
23
+ 'gte': min
24
+ 'lte': max
25
+
26
+ # ========= regexpMask =========
27
+
28
+ $.regexp = {} unless $.regexp
29
+ $.regexp.typedField =
30
+ 'integer' : /^$|^\d+$/
31
+ 'float' : /^$|^\d+(\.\d*)?$/
32
+ 'currency' : /^$|^\d+(\.\d{0,2})?$/
33
+ 'text' : /^.*$/
34
+
35
+ $.fn.getRegExpMask = ->
36
+ mask = @data 'regexpMask'
37
+ if _.isString(mask)
38
+ try
39
+ mask = new RegExp mask
40
+ @data 'regexpMask', mask
41
+ catch e
42
+ # nop
43
+ else
44
+ dataType = @data 'dataType'
45
+ mask = $.regexp.typedField[dataType] if dataType
46
+ unless mask
47
+ @val 'unknown dataType'
48
+ mask = $.regexp.typedField['text']
49
+ mask
50
+
51
+ $.fn.dropRegExpMask = ->
52
+ @
53
+ .removeAttr('data-regexp-mask')
54
+ .removeData('regexpMask')
55
+ .removeData('value')
56
+
57
+ $.fn.setRegExpMask = (mask) ->
58
+ stringMask = 'true'
59
+ if _.isString mask
60
+ stringMask = mask
61
+ try
62
+ mask = new RegExp mask
63
+ catch e
64
+ # nop
65
+ if _.isRegExp mask
66
+ @
67
+ .dropRegExpMask()
68
+ .attr('data-regexp-mask', stringMask)
69
+ .data('regexpMask', mask)
70
+ .change()
71
+ @
72
+
73
+ $.fn.observeTypedFields = (selector = 'input:text[data-data-type], input:text[data-regexp-mask]') ->
74
+ return @ unless @length
75
+
76
+ @on 'keypress', selector, (event) ->
77
+ if event.metaKey
78
+ $(@).trigger 'paste', arguments
79
+ return true
80
+ return true unless event.charCode
81
+ jThis = $ @
82
+ oldValue = jThis.val()
83
+ newValue = oldValue.substring(0, @selectionStart) + String.fromCharCode(event.charCode) + oldValue.substring(@selectionEnd)
84
+ return false unless jThis.getRegExpMask().test(newValue)
85
+ jThis.data 'value', newValue
86
+
87
+ @on 'change', selector, ->
88
+ jThis = $ @
89
+ if jThis.getRegExpMask().test(jThis.val())
90
+ jThis.data 'value', jThis.val()
91
+ else
92
+ jThis.val jThis.data('value')
93
+
94
+ @on 'paste drop', selector, ->
95
+ jThis = $ @
96
+ setTimeout ( -> jThis.trigger 'change', arguments ), 100
97
+
98
+ )( jQuery )