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,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
+ }