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,25 @@
1
+ Backbone.Ultimate ||= {}
2
+
3
+ class Backbone.Ultimate.App
4
+ @App: null
5
+
6
+ name: null
7
+
8
+ Models: {}
9
+ Collections: {}
10
+ Routers: {}
11
+ Views: {}
12
+
13
+ scopes: ["Models", "Collections", "Routers", "Views"]
14
+
15
+ constructor: (name = null) ->
16
+ if @constructor.App
17
+ throw new Error("Can't create new Backbone.Ultimate.App because the single instance has already been created");
18
+ else
19
+ @constructor.App = @
20
+ @name = name
21
+ _.extend @[scope], Backbone.Events for scope in @scopes
22
+
23
+
24
+
25
+ _.extend Backbone.Ultimate.App::, Backbone.Events
@@ -0,0 +1,12 @@
1
+ Backbone.Ultimate ||= {}
2
+
3
+ class Backbone.Ultimate.Collection extends Backbone.Collection
4
+
5
+ ready: (callback, context = @) ->
6
+ unless @length
7
+ @readyDeferred ||= $.Deferred()
8
+ @readyDeferred.done =>
9
+ callback.apply context, [@]
10
+ @fetch success: (=> @readyDeferred.resolve()), silent: true
11
+ else
12
+ callback.apply context, [@]
@@ -0,0 +1,12 @@
1
+ Backbone.Ultimate ||= {}
2
+
3
+ class Backbone.Ultimate.Model extends Backbone.Model
4
+
5
+ ready: (callback, context = @) ->
6
+ if _.isEmpty(@attributes)
7
+ @readyDeferred ||= $.Deferred()
8
+ @readyDeferred.done =>
9
+ callback.apply context, [@]
10
+ @fetch success: (=> @readyDeferred.resolve()), silent: true
11
+ else
12
+ callback.apply context, [@]
@@ -0,0 +1,13 @@
1
+ Backbone.Ultimate ||= {}
2
+
3
+ class Backbone.Ultimate.Router extends Backbone.Router
4
+
5
+ namedParam = /:\w+/g
6
+ splatParam = /\*\w+/g
7
+ escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g
8
+
9
+ _routeToRegExp: (route) ->
10
+ route = route.replace(escapeRegExp, '\\$&')
11
+ .replace(namedParam, '([^\/]+)')
12
+ .replace(splatParam, '(.*?)')
13
+ new RegExp("^\/?#{route}\/?$")
@@ -0,0 +1,96 @@
1
+ Backbone.Ultimate ||= {}
2
+
3
+ class Backbone.Ultimate.View extends Backbone.View
4
+
5
+ viewOptions: []
6
+
7
+ loadingState: null
8
+ loadingWidthMethodName: "innerWidth"
9
+ loadingHeightMethodName: "innerHeight"
10
+ loadingStateClass: "loading"
11
+ loadingOverlayClass: "loading-overlay"
12
+
13
+ constructor: ->
14
+ super
15
+
16
+
17
+
18
+ # Overload parent method Backbone.View.setElement() as hook for findNodes().
19
+ setElement: (element, delegate) ->
20
+ super
21
+ @findNodes()
22
+ @
23
+
24
+ findNodes: (jRoot = @$el, nodes = @nodes) ->
25
+ jNodes = {}
26
+ nodes = nodes() if _.isFunction(nodes)
27
+ if _.isObject(nodes)
28
+ for nodeName, selector of nodes
29
+ _isObject = _.isObject(selector)
30
+ if _isObject
31
+ nestedNodes = selector
32
+ selector = _delete(nestedNodes, "selector")
33
+ jNodes[nodeName] = @[nodeName] = jRoot.find(selector)
34
+ if _isObject
35
+ _.extend jNodes, @findNodes(jNodes[nodeName], nestedNodes)
36
+ jNodes
37
+
38
+
39
+
40
+ # Overload and proxy parent method Backbone.View.delegateEvents() as hook for normalizeEvents().
41
+ delegateEvents: (events) ->
42
+ args = []
43
+ Array::push.apply args, arguments if arguments.length > 0
44
+ args[0] = @normalizeEvents(events)
45
+ super args...
46
+
47
+ # Cached regex to split keys for `delegate`, from backbone.js.
48
+ delegateEventSplitter = /^(\S+)\s*(.*)$/
49
+
50
+ normalizeEvents: (events) ->
51
+ events = getValue(@, "events") unless events
52
+ if events
53
+ normalizedEvents = {}
54
+ for key, method of events
55
+ [[], eventName, selector] = key.match(delegateEventSplitter)
56
+ jObj = @[selector]
57
+ if jObj instanceof jQuery and _.isString(jObj.selector)
58
+ key = "#{eventName} #{jObj.selector}"
59
+ normalizedEvents[key] = method
60
+ events = normalizedEvents
61
+ events
62
+
63
+
64
+
65
+ # Overload parent method Backbone.View._configure() as hook for reflectOptions().
66
+ _configure: (options) ->
67
+ super
68
+ @reflectOptions()
69
+
70
+ reflectOptions: (viewOptions = @viewOptions, options = @options) ->
71
+ @[attr] = options[attr] for attr in viewOptions when options[attr]
72
+
73
+
74
+
75
+ # Overloadable getter for jQuery-container that will be blocked.
76
+ getJLoadingContainer: -> @$el
77
+
78
+ loading: (state, text = "", circle = true) ->
79
+ jLoadingContainer = @getJLoadingContainer()
80
+ if jLoadingContainer and jLoadingContainer.length
81
+ jLoadingContainer.removeClass @loadingStateClass
82
+ jLoadingContainer.children(".#{@loadingOverlayClass}").remove()
83
+ if @loadingState = state
84
+ jLoadingContainer.addClass @loadingStateClass
85
+ width = jLoadingContainer[@loadingWidthMethodName]()
86
+ height = jLoadingContainer[@loadingHeightMethodName]()
87
+ text = "<span class=\"text\">#{text}</span>" if text
88
+ circle = "<span class=\"circle\"></span>" if circle
89
+ style = "width: #{width}px; height: #{height}px; line-height: #{height}px;"
90
+ jLoadingContainer.append "<div class=\"#{@loadingOverlayClass}\" style=\"#{style}\">#{circle}#{text}</div>"
91
+
92
+
93
+
94
+ # Improve templating.
95
+ jst: (name, context = @) ->
96
+ JST["backbone/templates/#{name}"] context
@@ -0,0 +1,103 @@
1
+ ###
2
+ * front-end ui-components routines
3
+ *
4
+ * @version 0.5.5.alpha / 2010-2011
5
+ * @author Karpunin Dmitry / Evrone.com
6
+ * @email koderfunk_at_gmail_dot_com
7
+ *
8
+ * TODO register components
9
+ * TODO using underscore.js
10
+ *
11
+ ###
12
+
13
+ # TODO do ($ = jQuery) =>
14
+ ( ($) =>
15
+
16
+ $.regexp ||= {}
17
+
18
+ $.regexp.rorId ||= /(\w+)_(\d+)$/
19
+
20
+ @ror_id = (jObjOrString) ->
21
+ if isJQ jObjOrString
22
+ id = jObjOrString.data 'rorId'
23
+ unless id
24
+ id = jObjOrString.attr 'id'
25
+ if id
26
+ matchResult = id.match $.regexp.rorId
27
+ id = if matchResult then matchResult[2] else ''
28
+ jObjOrString.data 'rorId', id
29
+ else
30
+ id = ''
31
+ else
32
+ matchResult = jObjOrString.match $.regexp.rorId
33
+ id = if matchResult then matchResult[2] else ''
34
+ id
35
+
36
+ $.fn.rorId = ->
37
+ if arguments.length
38
+ id = arguments[0]
39
+ word = 'ror_id'
40
+ thisId = @attr 'id'
41
+ if thisId
42
+ word = matchResult[1] if matchResult = thisId.match $.regexp.rorId
43
+ @data('rorId', id).attr 'id', word + '_' + id
44
+ @
45
+ else
46
+ ror_id @
47
+
48
+ $.fn.getClasses = ->
49
+ return [] unless @length
50
+ classAttr = $.trim @attr 'class'
51
+ if classAttr then classAttr.split(/\s+/) else []
52
+
53
+ # Get hash of html-dom attributes from first matched element or false, if no elements
54
+ $.fn.getAttributes = ->
55
+ if @length
56
+ attrs = {}
57
+ #TRY attrs[attr.nodeName] = attr.nodeValue for attr in this[0].attributes
58
+ $.each this[0].attributes, (index, attr) ->
59
+ attrs[attr.nodeName] = attr.nodeValue
60
+ attrs
61
+ else
62
+ false
63
+
64
+ # [showOrHide[, duration[, callback]]]
65
+ $.fn.slideToggleByState = ->
66
+ if @length
67
+ if arguments.length > 0
68
+ a = args arguments
69
+ if a.shift()
70
+ @slideDown.apply @, a
71
+ else
72
+ @slideUp.apply @, a
73
+ else
74
+ @slideToggle()
75
+ @
76
+
77
+ # TODO replace usages to underscore methods and using distribution
78
+ unless $.isRegExp
79
+ $.isRegExp = (candidate) ->
80
+ deprecate '$.isRegExp', '_.isRegExp'
81
+ typeof candidate is "object" and typeof candidate.test is "function"
82
+
83
+ unless $.isBoolean
84
+ $.isBoolean = (v) ->
85
+ deprecate '$.isBoolean', '_.isBoolean'
86
+ typeof v is "boolean"
87
+
88
+ unless $.isString
89
+ $.isString = (v) ->
90
+ deprecate '$.isString', '_.isString'
91
+ typeof v is "string"
92
+
93
+ unless $.isHTML
94
+ $.regexp.HTML = new RegExp "^\\s*<(\\w+)[\\S\\s]*</(\\w+)>\\s*$"
95
+ $.isHTML = (v) ->
96
+ matches = $.regexp.HTML.exec v
97
+ matches and matches[1] is matches[2]
98
+
99
+ if typeof $.isEmptyString is "undefined"
100
+ $.isEmptyString = (v) ->
101
+ regexpSpace.test v
102
+
103
+ )( jQuery )
@@ -0,0 +1,57 @@
1
+ # TODO bus as class
2
+ # TODO may be, settable maxInstances
3
+ # TODO tree presentative logic in eventName syntax provided by ':', '.', ...
4
+ # require underscore
5
+
6
+ class EventBus
7
+
8
+ events: {}
9
+
10
+ #constructor: ->
11
+
12
+ bind: (eventName, callback) ->
13
+ if @events[eventName]
14
+ @events[eventName].push callback
15
+ else
16
+ @events[eventName] = [callback]
17
+ true
18
+
19
+ # TODO simplify logic
20
+ unbind: (eventName, callback = null) ->
21
+ if eventName
22
+ if eventsHeap = @events[eventName]
23
+ if callback
24
+ i = _.indexOf eventsHeap, callback
25
+ if i >= 0
26
+ eventsHeap.splice i, 1
27
+ true
28
+ else
29
+ false
30
+ else
31
+ delete @events[eventName]
32
+ else
33
+ false
34
+ else
35
+ if callback
36
+ # TODO return boolean
37
+ @unbind eventName, callback for eventName, eventsHeap of @events
38
+ else
39
+ false
40
+
41
+ has: (eventName, callback = null) ->
42
+ if eventsHeap = @events[eventName]
43
+ if callback
44
+ _.include eventsHeap, callback
45
+ else
46
+ true
47
+ else
48
+ false
49
+
50
+ trigger: (eventName, callbackArguments...) ->
51
+ for callback in @events[eventName] or []
52
+ callback.apply(@, callbackArguments)
53
+
54
+
55
+
56
+ # transitional instance
57
+ @bus = new EventBus
@@ -0,0 +1,18 @@
1
+ $ ->
2
+
3
+ jBody = $ 'body'
4
+
5
+ # fixes on sessions/new form, because devise don't return errors on fields
6
+ jBody.on 'ajax:error', 'form.sessions-new', ->
7
+ $(@).find(':input:visible').filter(-> not @value).closestEdge().setErrors()
8
+ false
9
+
10
+ # if devise form success, then open root path
11
+ jBody.on 'ajax:success', 'form.user[data-remote="true"]', (event, data, textStatus, jqXHR) ->
12
+ jForm = $ @
13
+ if jForm.hasClass('users-new')
14
+ getWidget('AccountPanel').showConfirmationInfo(jForm, data)
15
+ else if jForm.hasClass('password-recovery')
16
+ getWidget('AccountPanel').showConfirmationInfo(jForm, data)
17
+ else unless jForm.hasClass('status')
18
+ window.location = jqXHR.getResponseHeader('Location') or '/'
@@ -0,0 +1,154 @@
1
+ //pluralize(9) // "9th"
2
+ //'dog'.pluralize() // "dogs"
3
+ //'dog'.pluralize(4) // "4 dogs"
4
+ //'dog'.pluralize(2, 'dogs too many') // "2 dogs too many"
5
+ //'dogs'.singularize() // "dog"
6
+ //'dogs'.singularize(1) // "1 dog"
7
+
8
+
9
+
10
+ //Dogs suck! CATS FOREVER!
11
+
12
+ Inflector = {
13
+ Inflections: {
14
+ plural: [
15
+ [/(quiz)$/i, "$1zes" ],
16
+ [/^(ox)$/i, "$1en" ],
17
+ [/([m|l])ouse$/i, "$1ice" ],
18
+ [/(matr|vert|ind)ix|ex$/i, "$1ices" ],
19
+ [/(x|ch|ss|sh)$/i, "$1es" ],
20
+ [/([^aeiouy]|qu)y$/i, "$1ies" ],
21
+ [/(hive)$/i, "$1s" ],
22
+ [/(?:([^f])fe|([lr])f)$/i, "$1$2ves"],
23
+ [/sis$/i, "ses" ],
24
+ [/([ti])um$/i, "$1a" ],
25
+ [/(buffal|tomat)o$/i, "$1oes" ],
26
+ [/(bu)s$/i, "$1ses" ],
27
+ [/(alias|status)$/i, "$1es" ],
28
+ [/(octop|vir)us$/i, "$1i" ],
29
+ [/(ax|test)is$/i, "$1es" ],
30
+ [/s$/i, "s" ],
31
+ [/$/, "s" ]
32
+ ],
33
+ singular: [
34
+ [/(quiz)zes$/i, "$1" ],
35
+ [/(matr)ices$/i, "$1ix" ],
36
+ [/(vert|ind)ices$/i, "$1ex" ],
37
+ [/^(ox)en/i, "$1" ],
38
+ [/(alias|status)es$/i, "$1" ],
39
+ [/(octop|vir)i$/i, "$1us" ],
40
+ [/(cris|ax|test)es$/i, "$1is" ],
41
+ [/(shoe)s$/i, "$1" ],
42
+ [/(o)es$/i, "$1" ],
43
+ [/(bus)es$/i, "$1" ],
44
+ [/([m|l])ice$/i, "$1ouse" ],
45
+ [/(x|ch|ss|sh)es$/i, "$1" ],
46
+ [/(m)ovies$/i, "$1ovie" ],
47
+ [/(s)eries$/i, "$1eries"],
48
+ [/([^aeiouy]|qu)ies$/i, "$1y" ],
49
+ [/([lr])ves$/i, "$1f" ],
50
+ [/(tive)s$/i, "$1" ],
51
+ [/(hive)s$/i, "$1" ],
52
+ [/([^f])ves$/i, "$1fe" ],
53
+ [/(^analy)ses$/i, "$1sis" ],
54
+ [/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, "$1$2sis"],
55
+ [/([ti])a$/i, "$1um" ],
56
+ [/(n)ews$/i, "$1ews" ],
57
+ [/s$/i, "" ]
58
+ ],
59
+ irregular: [
60
+ ['move', 'moves' ],
61
+ ['sex', 'sexes' ],
62
+ ['child', 'children'],
63
+ ['man', 'men' ],
64
+ ['person', 'people' ]
65
+ ],
66
+ uncountable: [
67
+ "sheep",
68
+ "fish",
69
+ "series",
70
+ "species",
71
+ "money",
72
+ "rice",
73
+ "information",
74
+ "equipment"
75
+ ]
76
+ },
77
+ ordinalize: function(number) {
78
+ if (11 <= parseInt(number) % 100 && parseInt(number) % 100 <= 13) {
79
+ return number + "th";
80
+ } else {
81
+ switch (parseInt(number) % 10) {
82
+ case 1: return number + "st";
83
+ case 2: return number + "nd";
84
+ case 3: return number + "rd";
85
+ default: return number + "th";
86
+ }
87
+ }
88
+ },
89
+ pluralize: function(word) {
90
+ for (var i = 0; i < Inflector.Inflections.uncountable.length; i++) {
91
+ var uncountable = Inflector.Inflections.uncountable[i];
92
+ if (word.toLowerCase == uncountable) {
93
+ return uncountable;
94
+ }
95
+ }
96
+ for (var i = 0; i < Inflector.Inflections.irregular.length; i++) {
97
+ var singular = Inflector.Inflections.irregular[i][0];
98
+ var plural = Inflector.Inflections.irregular[i][1];
99
+ if ((word.toLowerCase == singular) || (word == plural)) {
100
+ return plural;
101
+ }
102
+ }
103
+ for (var i = 0; i < Inflector.Inflections.plural.length; i++) {
104
+ var regex = Inflector.Inflections.plural[i][0];
105
+ var replace_string = Inflector.Inflections.plural[i][1];
106
+ if (regex.test(word)) {
107
+ return word.replace(regex, replace_string);
108
+ }
109
+ }
110
+ },
111
+ singularize: function(word) {
112
+ for (var i = 0; i < Inflector.Inflections.uncountable.length; i++) {
113
+ var uncountable = Inflector.Inflections.uncountable[i];
114
+ if (word.toLowerCase == uncountable) {
115
+ return uncountable;
116
+ }
117
+ }
118
+ for (var i = 0; i < Inflector.Inflections.irregular.length; i++) {
119
+ var singular = Inflector.Inflections.irregular[i][0];
120
+ var plural = Inflector.Inflections.irregular[i][1];
121
+ if ((word.toLowerCase == singular) || (word == plural)) {
122
+ return plural;
123
+ }
124
+ }
125
+ for (var i = 0; i < Inflector.Inflections.singular.length; i++) {
126
+ var regex = Inflector.Inflections.singular[i][0];
127
+ var replace_string = Inflector.Inflections.singular[i][1];
128
+ if (regex.test(word)) {
129
+ return word.replace(regex, replace_string);
130
+ }
131
+ }
132
+ }
133
+ }
134
+
135
+ function ordinalize(number) {
136
+ return Inflector.ordinalize(number);
137
+ }
138
+
139
+ Object.extend(String.prototype, {
140
+ pluralize: function(count, plural) {
141
+ if (typeof count == 'undefined') {
142
+ return Inflector.pluralize(this);
143
+ } else {
144
+ return count + ' ' + (1 == parseInt(count) ? this : plural || Inflector.pluralize(this));
145
+ }
146
+ },
147
+ singularize: function(count) {
148
+ if (typeof count == 'undefined') {
149
+ return Inflector.singularize(this);
150
+ } else {
151
+ return count + " " + Inflector.singularize(this);
152
+ }
153
+ }
154
+ });
@@ -0,0 +1,2 @@
1
+ @singular = (plural) =>
2
+ plural.replace(/ies$/, 'y').replace(/s$/, '')
@@ -0,0 +1,29 @@
1
+ // pluralize(12, 'коров', 'корова', 'коровы')
2
+ function pluralize(num, zero, one, two) {
3
+ num = Math.abs(num);
4
+ if ( (num >= 5) && (num <= 20) ) return zero;
5
+ num = num % 10;
6
+ if ( num == 1 ) return one;
7
+ if ( (num >= 2) && (num <= 4) ) return two;
8
+ return zero;
9
+ }
10
+
11
+ function limitField(field_selector, limit, info_selector) {
12
+ var jInfo = $(info_selector);
13
+ var jField = $(field_selector);
14
+ var text_buffer = jField.val();
15
+ jField.bind('keyup change', function () {
16
+ var text = jField.val();
17
+ var text_length = text.length;
18
+ if (text_length > limit) {
19
+ jInfo.html('<span style="color: red;">Не может быть длиннее ' + limit + ' символов!</span>');
20
+ jField.val(text_buffer);
21
+ return false;
22
+ } else {
23
+ text_buffer = text;
24
+ var rest = limit - text_length;
25
+ jInfo.html('Остал' + pluralize(rest, 'ось', 'ся', 'ось') + ' ' + rest + ' символ' + pluralize(rest, 'ов', '', 'а') + '.');
26
+ return true;
27
+ }
28
+ } ).change();
29
+ }