ultimate-base 0.2.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 (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 )