iugu-ux 0.9.9 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. data/lib/iugu-ux/version.rb +1 -1
  2. data/sandbox/log/development.log +432 -0
  3. data/sandbox/tmp/cache/assets/BD0/110/sprockets%2F52568313d04166c1237865292979c86d +0 -0
  4. data/sandbox/tmp/cache/assets/C48/D00/sprockets%2F4a51b39094a347e3e325005c7847551c +0 -0
  5. data/sandbox/tmp/cache/assets/C49/3B0/sprockets%2F405c7112191eed23e8b860254281e763 +0 -0
  6. data/sandbox/tmp/cache/assets/C4A/8F0/sprockets%2F736644cea24629d653454c90503a0c45 +0 -0
  7. data/sandbox/tmp/cache/assets/C4F/3A0/sprockets%2F41918e8d7b402652a5649e4b7134d620 +0 -0
  8. data/sandbox/tmp/cache/assets/C5A/0F0/sprockets%2F66317254e81572f5a89184618e3c3b5b +0 -0
  9. data/sandbox/tmp/cache/assets/C7A/F30/sprockets%2F70249181577b261a3ea8851b530f1e7c +0 -0
  10. data/sandbox/tmp/cache/assets/C86/500/sprockets%2F6990a45a256263b7579492ac7a6a626a +0 -0
  11. data/sandbox/tmp/cache/assets/C87/F60/sprockets%2F83b4a89e682705a1d0c1e1327608f968 +0 -0
  12. data/sandbox/tmp/cache/assets/C88/820/sprockets%2Fb50c6382fa02d221d1b29a40402d1641 +0 -0
  13. data/sandbox/tmp/cache/assets/C97/790/sprockets%2Ffa5602401f5500215f5c4555190dcbd2 +0 -0
  14. data/sandbox/tmp/cache/assets/CC3/930/sprockets%2F7e2c9302e5050f66e888725be83f862d +0 -0
  15. data/sandbox/tmp/cache/assets/CC7/EB0/sprockets%2F079d3b368c9e843b78c90079f26d628b +0 -0
  16. data/sandbox/tmp/cache/assets/CC8/510/sprockets%2F868a1670f8822e6b7847dd22e747a66f +0 -0
  17. data/sandbox/tmp/cache/assets/CCA/850/sprockets%2F1d05d32f046ec7313b0d3619a232d73f +0 -0
  18. data/sandbox/tmp/cache/assets/CDB/610/sprockets%2Fb5b20836971a5786ca125712b8e0bf5e +0 -0
  19. data/sandbox/tmp/cache/assets/CDB/E30/sprockets%2F918458a88fec2cd01991c003b2318ab7 +0 -0
  20. data/sandbox/tmp/cache/assets/CEC/760/sprockets%2F2e588fd16989acc60b813afe18471562 +0 -0
  21. data/sandbox/tmp/cache/assets/D08/ED0/sprockets%2Fcb1aee736d3b287a872298b7741b012b +0 -0
  22. data/sandbox/tmp/cache/assets/D1A/B70/sprockets%2F47ae92dd9d034b32098b19bf8f2475a9 +0 -0
  23. data/sandbox/tmp/cache/assets/D34/3E0/sprockets%2F46a73c17c9a561115944df004dbbdd7b +0 -0
  24. data/sandbox/tmp/cache/assets/D3B/BC0/sprockets%2F19510a763bd1521ff5fc70ffe0d837d3 +0 -0
  25. data/sandbox/tmp/cache/assets/D4A/1A0/sprockets%2Fb746360a8b3ce79c57e74a9c15309fdd +0 -0
  26. data/sandbox/tmp/cache/assets/D4A/EB0/sprockets%2F601dc7267b0c9e83e7c9bd2bf94e3564 +0 -0
  27. data/sandbox/tmp/cache/assets/D4F/810/sprockets%2Fa10070d6497ba2b4ea552d4a1bb444cb +0 -0
  28. data/sandbox/tmp/cache/assets/D53/E90/sprockets%2Fb333afe57100a0a8c1bc6c37405e56bb +0 -0
  29. data/sandbox/tmp/cache/assets/D55/310/sprockets%2F2b9be79ca64ec7d294d4825e21696ce9 +0 -0
  30. data/sandbox/tmp/cache/assets/D5C/200/sprockets%2Fcd323e6f7545cee0b3ba1e84d7520b00 +0 -0
  31. data/sandbox/tmp/cache/assets/D66/7C0/sprockets%2F5afbe1fb48c4015a6ffc9085c0519b05 +0 -0
  32. data/sandbox/tmp/cache/assets/D6D/AA0/sprockets%2F1cb4a75912d38d0bdde6ee6770644d0f +0 -0
  33. data/sandbox/tmp/cache/assets/D6E/AE0/sprockets%2Fe193ea7b03f186ad2a9515fc78fa8c21 +0 -0
  34. data/sandbox/tmp/cache/assets/D74/0A0/sprockets%2Fc1392b79af6b88250b634479cbf2bcfc +0 -0
  35. data/sandbox/tmp/cache/assets/D79/5A0/sprockets%2Feb399a6f39a8dcd0bf011768e7153d8d +0 -0
  36. data/sandbox/tmp/cache/assets/D7A/D40/sprockets%2F4d2bc08f2787a3870ff93e0e0f7bc67e +0 -0
  37. data/sandbox/tmp/cache/assets/D7D/A60/sprockets%2Fad3ac10002e5ebc21d911fe229b6ec40 +0 -0
  38. data/sandbox/tmp/cache/assets/D82/480/sprockets%2F8945f20bf64a2469e9edbfb085e4b8c8 +0 -0
  39. data/sandbox/tmp/cache/assets/D92/120/sprockets%2Fbbadd54a208919f24fb50bc9e3d3d107 +0 -0
  40. data/sandbox/tmp/cache/assets/D96/940/sprockets%2F6a2c3af22b173eb975f382b4cdcd8c41 +0 -0
  41. data/sandbox/tmp/cache/assets/D96/E90/sprockets%2F0d5f451deca7e6811663f30afc3a4fd4 +0 -0
  42. data/sandbox/tmp/cache/assets/D97/180/sprockets%2F933323d1c368cfeda7a0cacdbd295933 +0 -0
  43. data/sandbox/tmp/cache/assets/DAD/190/sprockets%2Fbee62a5d7a8cd35a33763f1cd879de59 +0 -0
  44. data/sandbox/tmp/cache/assets/DB8/5B0/sprockets%2F35ed47d3f028fe65e7d4f5f4f8f95e1b +0 -0
  45. data/sandbox/tmp/cache/assets/DC5/340/sprockets%2Fd0dab495f0bbced215dd46e8ca316575 +0 -0
  46. data/sandbox/tmp/cache/assets/DCA/FD0/sprockets%2F0b59a77adfc61bb863e708ba262ce6bd +0 -0
  47. data/sandbox/tmp/cache/assets/DCE/E70/sprockets%2Facff0eeec48ccf1c25a1b968242b7827 +0 -0
  48. data/sandbox/tmp/cache/assets/DD4/040/sprockets%2Fe4c0c08358fdcc373c5f2da767cd08ff +0 -0
  49. data/sandbox/tmp/cache/assets/DD7/9B0/sprockets%2F9f2c0826bd7ae7b745ae8da6db48c26e +0 -0
  50. data/sandbox/tmp/cache/assets/DE0/650/sprockets%2F0f87cd6ed404cdfc99c86bb974d0ca59 +0 -0
  51. data/sandbox/tmp/cache/assets/DE5/390/sprockets%2F24a1cda6893cebd53997dc859def67cd +0 -0
  52. data/sandbox/tmp/cache/assets/DFC/5B0/sprockets%2Fcece55fa8f17c917fcc78cf6113a01bb +0 -0
  53. data/sandbox/tmp/cache/assets/E00/1D0/sprockets%2F571f97ca7b6dfdfefafd03ae735127a1 +0 -0
  54. data/sandbox/tmp/cache/assets/E05/2E0/sprockets%2F92ecbfa92baf6e394ce6c39d731b7cd2 +0 -0
  55. data/sandbox/tmp/cache/assets/E36/F20/sprockets%2Fd3bd4977eec9eca9c55b9a20b81b9cdb +0 -0
  56. data/sandbox/tmp/cache/assets/E37/A20/sprockets%2Fec3ccca6dfe2fc4e8793b0d83398e5ba +0 -0
  57. data/sandbox/tmp/cache/assets/F1C/0B0/sprockets%2Ffb1cb6e9be4bf2cd5aaecfbb1de7d77f +0 -0
  58. data/sandbox/tmp/cache/sass/8e0849d701bca2f5a178ed1215813b1316068a4d/components.sassc +0 -0
  59. data/sandbox/tmp/cache/sass/8e0849d701bca2f5a178ed1215813b1316068a4d/typography.sassc +0 -0
  60. data/sandbox/tmp/cache/sass/8e0849d701bca2f5a178ed1215813b1316068a4d/variables.sassc +0 -0
  61. data/sandbox/tmp/cache/sass/b49d75942b505963eb360d4c3b43ec26f9ded67e/checkbox.sassc +0 -0
  62. data/sandbox/tmp/cache/sass/b49d75942b505963eb360d4c3b43ec26f9ded67e/combobox.sassc +0 -0
  63. data/sandbox/tmp/cache/sass/b49d75942b505963eb360d4c3b43ec26f9ded67e/container.sassc +0 -0
  64. data/sandbox/tmp/cache/sass/b49d75942b505963eb360d4c3b43ec26f9ded67e/group.sassc +0 -0
  65. data/sandbox/tmp/cache/sass/b49d75942b505963eb360d4c3b43ec26f9ded67e/input.sassc +0 -0
  66. data/sandbox/tmp/cache/sass/b49d75942b505963eb360d4c3b43ec26f9ded67e/radio.sassc +0 -0
  67. data/sandbox/tmp/cache/sass/b49d75942b505963eb360d4c3b43ec26f9ded67e/responsive_box.sassc +0 -0
  68. data/sandbox/tmp/cache/sass/b49d75942b505963eb360d4c3b43ec26f9ded67e/view.sassc +0 -0
  69. data/vendor/assets/javascripts/iugu-ux/components/presenters/iugu-ui-table.jst.eco +1 -1
  70. data/vendor/assets/javascripts/iugu-ux/components/usecode/iugu-ui-button.js.coffee +15 -3
  71. data/vendor/assets/javascripts/iugu-ux/components/usecode/iugu-ui-combobox.js.coffee +1 -0
  72. data/vendor/assets/javascripts/iugu-ux/components/usecode/iugu-ui-table.js.coffee +1 -0
  73. data/vendor/assets/javascripts/iugu-ux/components/usecode/iugu-ui-view.js.coffee +1 -1
  74. data/vendor/assets/javascripts/vendor.js +2 -3
  75. data/vendor/assets/javascripts/vendor/backbone.validation.js +1 -1
  76. data/vendor/assets/javascripts/vendor/numeral.js +2104 -0
  77. data/vendor/assets/javascripts/vendor/numeral.languages.js +96 -0
  78. data/vendor/assets/javascripts/web-app/helpers.coffee +1 -1
  79. data/vendor/assets/stylesheets/iugu-ux/components/combobox.sass +7 -0
  80. data/vendor/assets/stylesheets/iugu-ux/components/input.sass +11 -0
  81. data/vendor/assets/stylesheets/iugu-ux/components/notice.sass +6 -0
  82. metadata +72 -33
  83. data/vendor/assets/javascripts/vendor/mobiscroll.core-2.3.1.js +0 -966
  84. data/vendor/assets/javascripts/vendor/mobiscroll.datetime-2.3.js +0 -656
  85. data/vendor/assets/javascripts/vendor/mobiscroll.select-2.3.1.js +0 -257
@@ -1,5 +1,5 @@
1
1
  <div class="dataset">
2
- <table class="table table-bordered">
2
+ <table class="table table-bordered <%= @identifier %>">
3
3
  <thead>
4
4
  <tr>
5
5
  <% for field in _.pairs(@fields): %>
@@ -46,17 +46,19 @@ class IuguUI.Button
46
46
  else if input_selector
47
47
  @input_element = $(input_selector)
48
48
 
49
- if @getInput() and @el.data("value") == @getInput().val()
49
+ if @getInput() and @el.data("value") and @el.data("value").toString() == @getInput().val().toString()
50
50
  @el.addClass("selected") unless @el.hasClass("selected")
51
51
 
52
- debug @getInput()
53
-
54
52
  if @getInput() and @getInput().is(":checkbox") and @getInput().is(":checked")
55
53
  @el.addClass("selected")
56
54
 
55
+
57
56
  if @getInput() and @getInput().is(":radio") and @getInput().is(":checked")
58
57
  @el.addClass("selected")
59
58
 
59
+ if @getInput() and @getInput().is(":radio") or @getInput().is(":checkbox")
60
+ @getInput().bind "change", @inputChangedCallback
61
+
60
62
  @el.bind "click", @click
61
63
 
62
64
  if @options.hideInput and @input_element
@@ -74,6 +76,16 @@ class IuguUI.Button
74
76
  return false unless elms and elms.size() > 1
75
77
  true
76
78
 
79
+ inputChangedCallback: ->
80
+ if @hasLinkedElements()
81
+ @linkedElements().removeClass("selected")
82
+
83
+ if @getInput() and @getInput().is(":checkbox") and @getInput().is(":checked")
84
+ @el.addClass("selected")
85
+
86
+ if @getInput() and @getInput().is(":radio") and @getInput().is(":checked")
87
+ @el.addClass("selected")
88
+
77
89
  configureInputElementValue: ( value ) ->
78
90
  return unless @getInput()
79
91
 
@@ -42,6 +42,7 @@ class IuguUI.Combobox
42
42
  @root_element = $('<div>',
43
43
  "data-display-selection": true
44
44
  "data-mark-selected": true
45
+ "class": @el.attr("class")
45
46
  )
46
47
 
47
48
  ul_element = $('<ul>')
@@ -13,6 +13,7 @@ class IuguUI.Table extends IuguUI.Dataset
13
13
  sortableBy: @options.sortableBy
14
14
  fields: @options.fields
15
15
  sortBy: @sortBy
16
+ identifier: @options.identifier
16
17
 
17
18
  initialize: ->
18
19
  super
@@ -87,7 +87,7 @@ class IuguUI.View extends IuguUI.Base
87
87
  control.parents('.iugu-ui-form-wrapper').addClass "input-with-errors"
88
88
 
89
89
  if list.length == 0
90
- group.prepend '<div class="notice notice-red"><ul class="error-list"></ul></div>'
90
+ group.prepend "<div class='notice notice-red'><h4 class='notice-heading'>#{_t 'phrases.error_title'}</h4><ul class='error-list'></ul></div>"
91
91
  list = group.find ".error-list"
92
92
 
93
93
  new_attr = attr.replace '.', '-'
@@ -13,9 +13,6 @@
13
13
  //= require ./vendor/jquery.iframe-transport.js
14
14
  //= require ./vendor/jquery.base64.js
15
15
  //= require ./vendor/jquery.fileupload.js
16
- //= require ./vendor/mobiscroll.core-2.3.1.js
17
- //= require ./vendor/mobiscroll.datetime-2.3.js
18
- //= require ./vendor/mobiscroll.select-2.3.1.js
19
16
  //= require ./vendor/underscore.js
20
17
  //= require ./vendor/backbone.js
21
18
  //= require ./vendor/backbone.associations-min.js
@@ -26,6 +23,8 @@
26
23
  //= require ./vendor/backbone.forms.js
27
24
  //= require ./vendor/backbone.forms.list.js
28
25
  //= require ./vendor/backbone.forms.default.js
26
+ //= require ./vendor/numeral.js
27
+ //= require ./vendor/numeral.languages.js
29
28
  //= require ./vendor/rivets.js
30
29
  //= require ./vendor/async.js
31
30
  //= require ./vendor/tasks.js
@@ -502,7 +502,7 @@ Backbone.Validation = (function(_){
502
502
  internationalized: function(attrName, model) {
503
503
  var right_attr = attrName.split('.');
504
504
  right_attr = right_attr[right_attr.length-1];
505
- return _t(model.constructor.name.toLowerCase() + '_fields.' + right_attr);
505
+ return _t(model.identifier + '_fields.' + right_attr);
506
506
  }
507
507
  };
508
508
 
@@ -0,0 +1,2104 @@
1
+ // numeral.js
2
+ // version : 1.4.7
3
+ // author : Adam Draper
4
+ // license : MIT
5
+ // http://adamwdraper.github.com/Numeral-js/
6
+
7
+ (function () {
8
+
9
+ /************************************
10
+ Constants
11
+ ************************************/
12
+
13
+ var numeral,
14
+ VERSION = '1.4.7',
15
+ // internal storage for language config files
16
+ languages = {},
17
+ currentLanguage = 'en',
18
+ zeroFormat = null,
19
+ // check for nodeJS
20
+ hasModule = (typeof module !== 'undefined' && module.exports);
21
+
22
+
23
+ /************************************
24
+ Constructors
25
+ ************************************/
26
+
27
+
28
+ // Numeral prototype object
29
+ function Numeral (number) {
30
+ this._n = number;
31
+ }
32
+
33
+ /**
34
+ * Implementation of toFixed() that treats floats more like decimals
35
+ *
36
+ * Fixes binary rounding issues (eg. (0.615).toFixed(2) === '0.61') that present
37
+ * problems for accounting- and finance-related software.
38
+ */
39
+ function toFixed (value, precision, optionals) {
40
+ var power = Math.pow(10, precision),
41
+ output;
42
+
43
+ // Multiply up by precision, round accurately, then divide and use native toFixed():
44
+ output = (Math.round(value * power) / power).toFixed(precision);
45
+
46
+ if (optionals) {
47
+ var optionalsRegExp = new RegExp('0{1,' + optionals + '}$');
48
+ output = output.replace(optionalsRegExp, '');
49
+ }
50
+
51
+ return output;
52
+ }
53
+
54
+ /************************************
55
+ Formatting
56
+ ************************************/
57
+
58
+ // determine what type of formatting we need to do
59
+ function formatNumeral (n, format) {
60
+ var output;
61
+
62
+ // figure out what kind of format we are dealing with
63
+ if (format.indexOf('$') > -1) { // currency!!!!!
64
+ output = formatCurrency(n, format);
65
+ } else if (format.indexOf('%') > -1) { // percentage
66
+ output = formatPercentage(n, format);
67
+ } else if (format.indexOf(':') > -1) { // time
68
+ output = formatTime(n, format);
69
+ } else { // plain ol' numbers or bytes
70
+ output = formatNumber(n, format);
71
+ }
72
+
73
+ // return string
74
+ return output;
75
+ }
76
+
77
+ // revert to number
78
+ function unformatNumeral (n, string) {
79
+ if (string.indexOf(':') > -1) {
80
+ n._n = unformatTime(string);
81
+ } else {
82
+ if (string === zeroFormat) {
83
+ n._n = 0;
84
+ } else {
85
+ var stringOriginal = string;
86
+ if (languages[currentLanguage].delimiters.decimal !== '.') {
87
+ string = string.replace(/\./g,'').replace(languages[currentLanguage].delimiters.decimal, '.');
88
+ }
89
+
90
+ // see if abbreviations are there so that we can multiply to the correct number
91
+ var thousandRegExp = new RegExp(languages[currentLanguage].abbreviations.thousand + '(?:\\)|(\\' + languages[currentLanguage].currency.symbol + ')?(?:\\))?)?$'),
92
+ millionRegExp = new RegExp(languages[currentLanguage].abbreviations.million + '(?:\\)|(\\' + languages[currentLanguage].currency.symbol + ')?(?:\\))?)?$'),
93
+ billionRegExp = new RegExp(languages[currentLanguage].abbreviations.billion + '(?:\\)|(\\' + languages[currentLanguage].currency.symbol + ')?(?:\\))?)?$'),
94
+ trillionRegExp = new RegExp(languages[currentLanguage].abbreviations.trillion + '(?:\\)|(\\' + languages[currentLanguage].currency.symbol + ')?(?:\\))?)?$');
95
+
96
+ // see if bytes are there so that we can multiply to the correct number
97
+ var prefixes = ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
98
+ bytesMultiplier = false;
99
+
100
+ for (var power = 0; power <= prefixes.length; power++) {
101
+ bytesMultiplier = (string.indexOf(prefixes[power]) > -1) ? Math.pow(1024, power + 1) : false;
102
+
103
+ if (bytesMultiplier) {
104
+ break;
105
+ }
106
+ }
107
+
108
+ // do some math to create our number
109
+ n._n = ((bytesMultiplier) ? bytesMultiplier : 1) * ((stringOriginal.match(thousandRegExp)) ? Math.pow(10, 3) : 1) * ((stringOriginal.match(millionRegExp)) ? Math.pow(10, 6) : 1) * ((stringOriginal.match(billionRegExp)) ? Math.pow(10, 9) : 1) * ((stringOriginal.match(trillionRegExp)) ? Math.pow(10, 12) : 1) * ((string.indexOf('%') > -1) ? 0.01 : 1) * Number(((string.indexOf('(') > -1) ? '-' : '') + string.replace(/[^0-9\.-]+/g, ''));
110
+
111
+ // round if we are talking about bytes
112
+ n._n = (bytesMultiplier) ? Math.ceil(n._n) : n._n;
113
+ }
114
+ }
115
+ return n._n;
116
+ }
117
+
118
+ function formatCurrency (n, format) {
119
+ var prependSymbol = (format.indexOf('$') <= 1) ? true : false;
120
+
121
+ // remove $ for the moment
122
+ var space = '';
123
+
124
+ // check for space before or after currency
125
+ if (format.indexOf(' $') > -1) {
126
+ space = ' ';
127
+ format = format.replace(' $', '');
128
+ } else if (format.indexOf('$ ') > -1) {
129
+ space = ' ';
130
+ format = format.replace('$ ', '');
131
+ } else {
132
+ format = format.replace('$', '');
133
+ }
134
+
135
+ // format the number
136
+ var output = formatNumeral(n, format);
137
+
138
+ // position the symbol
139
+ if (prependSymbol) {
140
+ if (output.indexOf('(') > -1 || output.indexOf('-') > -1) {
141
+ output = output.split('');
142
+ output.splice(1, 0, languages[currentLanguage].currency.symbol + space);
143
+ output = output.join('');
144
+ } else {
145
+ output = languages[currentLanguage].currency.symbol + space + output;
146
+ }
147
+ } else {
148
+ if (output.indexOf(')') > -1) {
149
+ output = output.split('');
150
+ output.splice(-1, 0, space + languages[currentLanguage].currency.symbol);
151
+ output = output.join('');
152
+ } else {
153
+ output = output + space + languages[currentLanguage].currency.symbol;
154
+ }
155
+ }
156
+
157
+ return output;
158
+ }
159
+
160
+ function formatPercentage (n, format) {
161
+ var space = '';
162
+ // check for space before %
163
+ if (format.indexOf(' %') > -1) {
164
+ space = ' ';
165
+ format = format.replace(' %', '');
166
+ } else {
167
+ format = format.replace('%', '');
168
+ }
169
+
170
+ n._n = n._n * 100;
171
+ var output = formatNumeral(n, format);
172
+ if (output.indexOf(')') > -1 ) {
173
+ output = output.split('');
174
+ output.splice(-1, 0, space + '%');
175
+ output = output.join('');
176
+ } else {
177
+ output = output + space + '%';
178
+ }
179
+ return output;
180
+ }
181
+
182
+ function formatTime (n, format) {
183
+ var hours = Math.floor(n._n/60/60),
184
+ minutes = Math.floor((n._n - (hours * 60 * 60))/60),
185
+ seconds = Math.round(n._n - (hours * 60 * 60) - (minutes * 60));
186
+ return hours + ':' + ((minutes < 10) ? '0' + minutes : minutes) + ':' + ((seconds < 10) ? '0' + seconds : seconds);
187
+ }
188
+
189
+ function unformatTime (string) {
190
+ var timeArray = string.split(':'),
191
+ seconds = 0;
192
+ // turn hours and minutes into seconds and add them all up
193
+ if (timeArray.length === 3) {
194
+ // hours
195
+ seconds = seconds + (Number(timeArray[0]) * 60 * 60);
196
+ // minutes
197
+ seconds = seconds + (Number(timeArray[1]) * 60);
198
+ // seconds
199
+ seconds = seconds + Number(timeArray[2]);
200
+ } else if (timeArray.lenght === 2) {
201
+ // minutes
202
+ seconds = seconds + (Number(timeArray[0]) * 60);
203
+ // seconds
204
+ seconds = seconds + Number(timeArray[1]);
205
+ }
206
+ return Number(seconds);
207
+ }
208
+
209
+ function formatNumber (n, format) {
210
+ var negP = false,
211
+ optDec = false,
212
+ abbr = '',
213
+ bytes = '',
214
+ ord = '',
215
+ abs = Math.abs(n._n);
216
+
217
+ // check if number is zero and a custom zero format has been set
218
+ if (n._n === 0 && zeroFormat !== null) {
219
+ return zeroFormat;
220
+ } else {
221
+ // see if we should use parentheses for negative number
222
+ if (format.indexOf('(') > -1) {
223
+ negP = true;
224
+ format = format.slice(1, -1);
225
+ }
226
+
227
+ // see if abbreviation is wanted
228
+ if (format.indexOf('a') > -1) {
229
+ // check for space before abbreviation
230
+ if (format.indexOf(' a') > -1) {
231
+ abbr = ' ';
232
+ format = format.replace(' a', '');
233
+ } else {
234
+ format = format.replace('a', '');
235
+ }
236
+
237
+ if (abs >= Math.pow(10, 12)) {
238
+ // trillion
239
+ abbr = abbr + languages[currentLanguage].abbreviations.trillion;
240
+ n._n = n._n / Math.pow(10, 12);
241
+ } else if (abs < Math.pow(10, 12) && abs >= Math.pow(10, 9)) {
242
+ // billion
243
+ abbr = abbr + languages[currentLanguage].abbreviations.billion;
244
+ n._n = n._n / Math.pow(10, 9);
245
+ } else if (abs < Math.pow(10, 9) && abs >= Math.pow(10, 6)) {
246
+ // million
247
+ abbr = abbr + languages[currentLanguage].abbreviations.million;
248
+ n._n = n._n / Math.pow(10, 6);
249
+ } else if (abs < Math.pow(10, 6) && abs >= Math.pow(10, 3)) {
250
+ // thousand
251
+ abbr = abbr + languages[currentLanguage].abbreviations.thousand;
252
+ n._n = n._n / Math.pow(10, 3);
253
+ }
254
+ }
255
+
256
+ // see if we are formatting bytes
257
+ if (format.indexOf('b') > -1) {
258
+ // check for space before
259
+ if (format.indexOf(' b') > -1) {
260
+ bytes = ' ';
261
+ format = format.replace(' b', '');
262
+ } else {
263
+ format = format.replace('b', '');
264
+ }
265
+
266
+ var prefixes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
267
+ min,
268
+ max;
269
+
270
+ for (var power = 0; power <= prefixes.length; power++) {
271
+ min = Math.pow(1024, power);
272
+ max = Math.pow(1024, power+1);
273
+
274
+ if (n._n >= min && n._n < max) {
275
+ bytes = bytes + prefixes[power];
276
+ if (min > 0) {
277
+ n._n = n._n / min;
278
+ }
279
+ break;
280
+ }
281
+ }
282
+ }
283
+
284
+ // see if ordinal is wanted
285
+ if (format.indexOf('o') > -1) {
286
+ // check for space before
287
+ if (format.indexOf(' o') > -1) {
288
+ ord = ' ';
289
+ format = format.replace(' o', '');
290
+ } else {
291
+ format = format.replace('o', '');
292
+ }
293
+
294
+ ord = ord + languages[currentLanguage].ordinal(n._n);
295
+ }
296
+
297
+ if (format.indexOf('[.]') > -1) {
298
+ optDec = true;
299
+ format = format.replace('[.]', '.');
300
+ }
301
+
302
+ var w = n._n.toString().split('.')[0],
303
+ precision = format.split('.')[1],
304
+ thousands = format.indexOf(','),
305
+ d = '',
306
+ neg = false;
307
+
308
+ if (precision) {
309
+ if (precision.indexOf('[') > -1) {
310
+ precision = precision.replace(']', '');
311
+ precision = precision.split('[');
312
+ d = toFixed(n._n, (precision[0].length + precision[1].length), precision[1].length);
313
+ } else {
314
+ d = toFixed(n._n, precision.length);
315
+ }
316
+
317
+ w = d.split('.')[0];
318
+
319
+ if (d.split('.')[1].length) {
320
+ d = languages[currentLanguage].delimiters.decimal + d.split('.')[1];
321
+ } else {
322
+ d = '';
323
+ }
324
+
325
+ if (optDec && Number(d) === 0) {
326
+ d = '';
327
+ }
328
+ } else {
329
+ w = toFixed(n._n, null);
330
+ }
331
+
332
+ // format number
333
+ if (w.indexOf('-') > -1) {
334
+ w = w.slice(1);
335
+ neg = true;
336
+ }
337
+
338
+ if (thousands > -1) {
339
+ w = w.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1' + languages[currentLanguage].delimiters.thousands);
340
+ }
341
+
342
+ if (format.indexOf('.') === 0) {
343
+ w = '';
344
+ }
345
+
346
+ return ((negP && neg) ? '(' : '') + ((!negP && neg) ? '-' : '') + w + d + ((ord) ? ord : '') + ((abbr) ? abbr : '') + ((bytes) ? bytes : '') + ((negP && neg) ? ')' : '');
347
+ }
348
+ }
349
+
350
+ /************************************
351
+ Top Level Functions
352
+ ************************************/
353
+
354
+ numeral = function (input) {
355
+ if (numeral.isNumeral(input)) {
356
+ input = input.value();
357
+ } else if (!Number(input)) {
358
+ input = 0;
359
+ }
360
+
361
+ return new Numeral(Number(input));
362
+ };
363
+
364
+ // version number
365
+ numeral.version = VERSION;
366
+
367
+ // compare numeral object
368
+ numeral.isNumeral = function (obj) {
369
+ return obj instanceof Numeral;
370
+ };
371
+
372
+ // This function will load languages and then set the global language. If
373
+ // no arguments are passed in, it will simply return the current global
374
+ // language key.
375
+ numeral.language = function (key, values) {
376
+ if (!key) {
377
+ return currentLanguage;
378
+ }
379
+
380
+ if (key && !values) {
381
+ currentLanguage = key;
382
+ }
383
+
384
+ if (values || !languages[key]) {
385
+ loadLanguage(key, values);
386
+ }
387
+
388
+ return numeral;
389
+ };
390
+
391
+ numeral.language('en', {
392
+ delimiters: {
393
+ thousands: ',',
394
+ decimal: '.'
395
+ },
396
+ abbreviations: {
397
+ thousand: 'k',
398
+ million: 'm',
399
+ billion: 'b',
400
+ trillion: 't'
401
+ },
402
+ ordinal: function (number) {
403
+ var b = number % 10;
404
+ return (~~ (number % 100 / 10) === 1) ? 'th' :
405
+ (b === 1) ? 'st' :
406
+ (b === 2) ? 'nd' :
407
+ (b === 3) ? 'rd' : 'th';
408
+ },
409
+ currency: {
410
+ symbol: '$'
411
+ }
412
+ });
413
+
414
+ numeral.zeroFormat = function (format) {
415
+ if (typeof(format) === 'string') {
416
+ zeroFormat = format;
417
+ } else {
418
+ zeroFormat = null;
419
+ }
420
+ };
421
+
422
+ /************************************
423
+ Helpers
424
+ ************************************/
425
+
426
+ function loadLanguage(key, values) {
427
+ languages[key] = values;
428
+ }
429
+
430
+
431
+ /************************************
432
+ Numeral Prototype
433
+ ************************************/
434
+
435
+
436
+ numeral.fn = Numeral.prototype = {
437
+
438
+ clone : function () {
439
+ return numeral(this);
440
+ },
441
+
442
+ format : function (inputString) {
443
+ return formatNumeral(this, inputString ? inputString : numeral.defaultFormat);
444
+ },
445
+
446
+ unformat : function (inputString) {
447
+ return unformatNumeral(this, inputString ? inputString : numeral.defaultFormat);
448
+ },
449
+
450
+ value : function () {
451
+ return this._n;
452
+ },
453
+
454
+ valueOf : function () {
455
+ return this._n;
456
+ },
457
+
458
+ set : function (value) {
459
+ this._n = Number(value);
460
+ return this;
461
+ },
462
+
463
+ add : function (value) {
464
+ this._n = this._n + Number(value);
465
+ return this;
466
+ },
467
+
468
+ subtract : function (value) {
469
+ this._n = this._n - Number(value);
470
+ return this;
471
+ },
472
+
473
+ multiply : function (value) {
474
+ this._n = this._n * Number(value);
475
+ return this;
476
+ },
477
+
478
+ divide : function (value) {
479
+ this._n = this._n / Number(value);
480
+ return this;
481
+ },
482
+
483
+ difference : function (value) {
484
+ var difference = this._n - Number(value);
485
+
486
+ if (difference < 0) {
487
+ difference = -difference;
488
+ }
489
+
490
+ return difference;
491
+ }
492
+
493
+ };
494
+
495
+ /************************************
496
+ Exposing Numeral
497
+ ************************************/
498
+
499
+ // CommonJS module is defined
500
+ if (hasModule) {
501
+ module.exports = numeral;
502
+ }
503
+
504
+ /*global ender:false */
505
+ if (typeof ender === 'undefined') {
506
+ // here, `this` means `window` in the browser, or `global` on the server
507
+ // add `numeral` as a global object via a string identifier,
508
+ // for Closure Compiler 'advanced' mode
509
+ this['numeral'] = numeral;
510
+ }
511
+
512
+ /*global define:false */
513
+ if (typeof define === 'function' && define.amd) {
514
+ define([], function () {
515
+ return numeral;
516
+ });
517
+ }
518
+ }).call(this);
519
+
520
+ /*!
521
+ * jQuery contextMenu - Plugin for simple contextMenu handling
522
+ *
523
+ * Version: 1.5.25
524
+ *
525
+ * Authors: Rodney Rehm, Addy Osmani (patches for FF)
526
+ * Web: http://medialize.github.com/jQuery-contextMenu/
527
+ *
528
+ * Licensed under
529
+ * MIT License http://www.opensource.org/licenses/mit-license
530
+ * GPL v3 http://opensource.org/licenses/GPL-3.0
531
+ *
532
+ */
533
+
534
+ (function($, undefined){
535
+
536
+ // TODO: -
537
+ // ARIA stuff: menuitem, menuitemcheckbox und menuitemradio
538
+ // create <menu> structure if $.support[htmlCommand || htmlMenuitem] and !opt.disableNative
539
+
540
+ // determine html5 compatibility
541
+ $.support.htmlMenuitem = ('HTMLMenuItemElement' in window);
542
+ $.support.htmlCommand = ('HTMLCommandElement' in window);
543
+ $.support.eventSelectstart = ("onselectstart" in document.documentElement);
544
+ /* // should the need arise, test for css user-select
545
+ $.support.cssUserSelect = (function(){
546
+ var t = false,
547
+ e = document.createElement('div');
548
+
549
+ $.each('Moz|Webkit|Khtml|O|ms|Icab|'.split('|'), function(i, prefix) {
550
+ var propCC = prefix + (prefix ? 'U' : 'u') + 'serSelect',
551
+ prop = (prefix ? ('-' + prefix.toLowerCase() + '-') : '') + 'user-select';
552
+
553
+ e.style.cssText = prop + ': text;';
554
+ if (e.style[propCC] == 'text') {
555
+ t = true;
556
+ return false;
557
+ }
558
+
559
+ return true;
560
+ });
561
+
562
+ return t;
563
+ })();
564
+ */
565
+
566
+ var // currently active contextMenu trigger
567
+ $currentTrigger = null,
568
+ // is contextMenu initialized with at least one menu?
569
+ initialized = false,
570
+ // window handle
571
+ $win = $(window),
572
+ // number of registered menus
573
+ counter = 0,
574
+ // mapping selector to namespace
575
+ namespaces = {},
576
+ // mapping namespace to options
577
+ menus = {},
578
+ // custom command type handlers
579
+ types = {},
580
+ // default values
581
+ defaults = {
582
+ // selector of contextMenu trigger
583
+ selector: null,
584
+ // where to append the menu to
585
+ appendTo: null,
586
+ // method to trigger context menu ["right", "left", "hover"]
587
+ trigger: "right",
588
+ // hide menu when mouse leaves trigger / menu elements
589
+ autoHide: false,
590
+ // ms to wait before showing a hover-triggered context menu
591
+ delay: 200,
592
+ // determine position to show menu at
593
+ determinePosition: function($menu) {
594
+ // position to the lower middle of the trigger element
595
+ if ($.ui && $.ui.position) {
596
+ // .position() is provided as a jQuery UI utility
597
+ // (...and it won't work on hidden elements)
598
+ $menu.css('display', 'block').position({
599
+ my: "center top",
600
+ at: "center bottom",
601
+ of: this,
602
+ offset: "0 5",
603
+ collision: "fit"
604
+ }).css('display', 'none');
605
+ } else {
606
+ // determine contextMenu position
607
+ var offset = this.offset();
608
+ offset.top += this.outerHeight();
609
+ offset.left += this.outerWidth() / 2 - $menu.outerWidth() / 2;
610
+ $menu.css(offset);
611
+ }
612
+ },
613
+ // position menu
614
+ position: function(opt, x, y) {
615
+ var $this = this,
616
+ offset;
617
+ // determine contextMenu position
618
+ if (!x && !y) {
619
+ opt.determinePosition.call(this, opt.$menu);
620
+ return;
621
+ } else if (x === "maintain" && y === "maintain") {
622
+ // x and y must not be changed (after re-show on command click)
623
+ offset = opt.$menu.position();
624
+ } else {
625
+ // x and y are given (by mouse event)
626
+ var triggerIsFixed = opt.$trigger.parents().andSelf()
627
+ .filter(function() {
628
+ return $(this).css('position') == "fixed";
629
+ }).length;
630
+
631
+ if (triggerIsFixed) {
632
+ y -= $win.scrollTop();
633
+ x -= $win.scrollLeft();
634
+ }
635
+ offset = {top: y, left: x};
636
+ }
637
+
638
+ // correct offset if viewport demands it
639
+ var bottom = $win.scrollTop() + $win.height(),
640
+ right = $win.scrollLeft() + $win.width(),
641
+ height = opt.$menu.height(),
642
+ width = opt.$menu.width();
643
+
644
+ if (offset.top + height > bottom) {
645
+ offset.top -= height;
646
+ }
647
+
648
+ if (offset.left + width > right) {
649
+ offset.left -= width;
650
+ }
651
+
652
+ opt.$menu.css(offset);
653
+ },
654
+ // position the sub-menu
655
+ positionSubmenu: function($menu) {
656
+ if ($.ui && $.ui.position) {
657
+ // .position() is provided as a jQuery UI utility
658
+ // (...and it won't work on hidden elements)
659
+ $menu.css('display', 'block').position({
660
+ my: "left top",
661
+ at: "right top",
662
+ of: this,
663
+ collision: "fit"
664
+ }).css('display', '');
665
+ } else {
666
+ // determine contextMenu position
667
+ var offset = {
668
+ top: 0,
669
+ left: this.outerWidth()
670
+ };
671
+ $menu.css(offset);
672
+ }
673
+ },
674
+ // offset to add to zIndex
675
+ zIndex: 1,
676
+ // show hide animation settings
677
+ animation: {
678
+ duration: 50,
679
+ show: 'slideDown',
680
+ hide: 'slideUp'
681
+ },
682
+ // events
683
+ events: {
684
+ show: $.noop,
685
+ hide: $.noop
686
+ },
687
+ // default callback
688
+ callback: null,
689
+ // list of contextMenu items
690
+ items: {}
691
+ },
692
+ // mouse position for hover activation
693
+ hoveract = {
694
+ timer: null,
695
+ pageX: null,
696
+ pageY: null
697
+ },
698
+ // determine zIndex
699
+ zindex = function($t) {
700
+ var zin = 0,
701
+ $tt = $t;
702
+
703
+ while (true) {
704
+ zin = Math.max(zin, parseInt($tt.css('z-index'), 10) || 0);
705
+ $tt = $tt.parent();
706
+ if (!$tt || !$tt.length || "html body".indexOf($tt.prop('nodeName').toLowerCase()) > -1 ) {
707
+ break;
708
+ }
709
+ }
710
+
711
+ return zin;
712
+ },
713
+ // event handlers
714
+ handle = {
715
+ // abort anything
716
+ abortevent: function(e){
717
+ e.preventDefault();
718
+ e.stopImmediatePropagation();
719
+ },
720
+
721
+ // contextmenu show dispatcher
722
+ contextmenu: function(e) {
723
+ var $this = $(this);
724
+
725
+ // disable actual context-menu
726
+ e.preventDefault();
727
+ e.stopImmediatePropagation();
728
+
729
+ // abort native-triggered events unless we're triggering on right click
730
+ if (e.data.trigger != 'right' && e.originalEvent) {
731
+ return;
732
+ }
733
+
734
+ if (!$this.hasClass('context-menu-disabled')) {
735
+ // theoretically need to fire a show event at <menu>
736
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#context-menus
737
+ // var evt = jQuery.Event("show", { data: data, pageX: e.pageX, pageY: e.pageY, relatedTarget: this });
738
+ // e.data.$menu.trigger(evt);
739
+
740
+ $currentTrigger = $this;
741
+ if (e.data.build) {
742
+ var built = e.data.build($currentTrigger, e);
743
+ // abort if build() returned false
744
+ if (built === false) {
745
+ return;
746
+ }
747
+
748
+ // dynamically build menu on invocation
749
+ e.data = $.extend(true, {}, defaults, e.data, built || {});
750
+
751
+ // abort if there are no items to display
752
+ if (!e.data.items || $.isEmptyObject(e.data.items)) {
753
+ // Note: jQuery captures and ignores errors from event handlers
754
+ if (window.console) {
755
+ (console.error || console.log)("No items specified to show in contextMenu");
756
+ }
757
+
758
+ throw new Error('No Items sepcified');
759
+ }
760
+
761
+ // backreference for custom command type creation
762
+ e.data.$trigger = $currentTrigger;
763
+
764
+ op.create(e.data);
765
+ }
766
+ // show menu
767
+ op.show.call($this, e.data, e.pageX, e.pageY);
768
+ }
769
+ },
770
+ // contextMenu left-click trigger
771
+ click: function(e) {
772
+ e.preventDefault();
773
+ e.stopImmediatePropagation();
774
+ $(this).trigger($.Event("contextmenu", { data: e.data, pageX: e.pageX, pageY: e.pageY }));
775
+ },
776
+ // contextMenu right-click trigger
777
+ mousedown: function(e) {
778
+ // register mouse down
779
+ var $this = $(this);
780
+
781
+ // hide any previous menus
782
+ if ($currentTrigger && $currentTrigger.length && !$currentTrigger.is($this)) {
783
+ $currentTrigger.data('contextMenu').$menu.trigger('contextmenu:hide');
784
+ }
785
+
786
+ // activate on right click
787
+ if (e.button == 2) {
788
+ $currentTrigger = $this.data('contextMenuActive', true);
789
+ }
790
+ },
791
+ // contextMenu right-click trigger
792
+ mouseup: function(e) {
793
+ // show menu
794
+ var $this = $(this);
795
+ if ($this.data('contextMenuActive') && $currentTrigger && $currentTrigger.length && $currentTrigger.is($this) && !$this.hasClass('context-menu-disabled')) {
796
+ e.preventDefault();
797
+ e.stopImmediatePropagation();
798
+ $currentTrigger = $this;
799
+ $this.trigger($.Event("contextmenu", { data: e.data, pageX: e.pageX, pageY: e.pageY }));
800
+ }
801
+
802
+ $this.removeData('contextMenuActive');
803
+ },
804
+ // contextMenu hover trigger
805
+ mouseenter: function(e) {
806
+ var $this = $(this),
807
+ $related = $(e.relatedTarget),
808
+ $document = $(document);
809
+
810
+ // abort if we're coming from a menu
811
+ if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) {
812
+ return;
813
+ }
814
+
815
+ // abort if a menu is shown
816
+ if ($currentTrigger && $currentTrigger.length) {
817
+ return;
818
+ }
819
+
820
+ hoveract.pageX = e.pageX;
821
+ hoveract.pageY = e.pageY;
822
+ hoveract.data = e.data;
823
+ $document.on('mousemove.contextMenuShow', handle.mousemove);
824
+ hoveract.timer = setTimeout(function() {
825
+ hoveract.timer = null;
826
+ $document.off('mousemove.contextMenuShow');
827
+ $currentTrigger = $this;
828
+ $this.trigger($.Event("contextmenu", { data: hoveract.data, pageX: hoveract.pageX, pageY: hoveract.pageY }));
829
+ }, e.data.delay );
830
+ },
831
+ // contextMenu hover trigger
832
+ mousemove: function(e) {
833
+ hoveract.pageX = e.pageX;
834
+ hoveract.pageY = e.pageY;
835
+ },
836
+ // contextMenu hover trigger
837
+ mouseleave: function(e) {
838
+ // abort if we're leaving for a menu
839
+ var $related = $(e.relatedTarget);
840
+ if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) {
841
+ return;
842
+ }
843
+
844
+ try {
845
+ clearTimeout(hoveract.timer);
846
+ } catch(e) {}
847
+
848
+ hoveract.timer = null;
849
+ },
850
+
851
+ // click on layer to hide contextMenu
852
+ layerClick: function(e) {
853
+ var $this = $(this),
854
+ root = $this.data('contextMenuRoot'),
855
+ mouseup = false,
856
+ button = e.button,
857
+ x = e.pageX,
858
+ y = e.pageY,
859
+ target,
860
+ offset,
861
+ selectors;
862
+
863
+ e.preventDefault();
864
+ e.stopImmediatePropagation();
865
+
866
+ // This hack looks about as ugly as it is
867
+ // Firefox 12 (at least) fires the contextmenu event directly "after" mousedown
868
+ // for some reason `root.$layer.hide(); document.elementFromPoint()` causes this
869
+ // contextmenu event to be triggered on the uncovered element instead of on the
870
+ // layer (where every other sane browser, including Firefox nightly at the time)
871
+ // triggers the event. This workaround might be obsolete by September 2012.
872
+ $this.on('mouseup', function() {
873
+ mouseup = true;
874
+ });
875
+ setTimeout(function() {
876
+ var $window, hideshow;
877
+ // test if we need to reposition the menu
878
+ if ((root.trigger == 'left' && button == 0) || (root.trigger == 'right' && button == 2)) {
879
+ if (document.elementFromPoint) {
880
+ root.$layer.hide();
881
+ target = document.elementFromPoint(x - $win.scrollLeft(), y - $win.scrollTop());
882
+ root.$layer.show();
883
+
884
+ selectors = [];
885
+ for (var s in namespaces) {
886
+ selectors.push(s);
887
+ }
888
+
889
+ target = $(target).closest(selectors.join(', '));
890
+
891
+ if (target.length) {
892
+ if (target.is(root.$trigger[0])) {
893
+ root.position.call(root.$trigger, root, x, y);
894
+ return;
895
+ }
896
+ }
897
+ } else {
898
+ offset = root.$trigger.offset();
899
+ $window = $(window);
900
+ // while this looks kinda awful, it's the best way to avoid
901
+ // unnecessarily calculating any positions
902
+ offset.top += $window.scrollTop();
903
+ if (offset.top <= e.pageY) {
904
+ offset.left += $window.scrollLeft();
905
+ if (offset.left <= e.pageX) {
906
+ offset.bottom = offset.top + root.$trigger.outerHeight();
907
+ if (offset.bottom >= e.pageY) {
908
+ offset.right = offset.left + root.$trigger.outerWidth();
909
+ if (offset.right >= e.pageX) {
910
+ // reposition
911
+ root.position.call(root.$trigger, root, x, y);
912
+ return;
913
+ }
914
+ }
915
+ }
916
+ }
917
+ }
918
+ }
919
+
920
+ hideshow = function(e) {
921
+ if (e) {
922
+ e.preventDefault();
923
+ e.stopImmediatePropagation();
924
+ }
925
+
926
+ root.$menu.trigger('contextmenu:hide');
927
+ if (target && target.length) {
928
+ setTimeout(function() {
929
+ target.contextMenu({x: x, y: y});
930
+ }, 50);
931
+ }
932
+ };
933
+
934
+ if (mouseup) {
935
+ // mouseup has already happened
936
+ hideshow();
937
+ } else {
938
+ // remove only after mouseup has completed
939
+ $this.on('mouseup', hideshow);
940
+ }
941
+ }, 50);
942
+ },
943
+ // key handled :hover
944
+ keyStop: function(e, opt) {
945
+ if (!opt.isInput) {
946
+ e.preventDefault();
947
+ }
948
+
949
+ e.stopPropagation();
950
+ },
951
+ key: function(e) {
952
+ var opt = $currentTrigger.data('contextMenu') || {},
953
+ $children = opt.$menu.children(),
954
+ $round;
955
+
956
+ switch (e.keyCode) {
957
+ case 9:
958
+ case 38: // up
959
+ handle.keyStop(e, opt);
960
+ // if keyCode is [38 (up)] or [9 (tab) with shift]
961
+ if (opt.isInput) {
962
+ if (e.keyCode == 9 && e.shiftKey) {
963
+ e.preventDefault();
964
+ opt.$selected && opt.$selected.find('input, textarea, select').blur();
965
+ opt.$menu.trigger('prevcommand');
966
+ return;
967
+ } else if (e.keyCode == 38 && opt.$selected.find('input, textarea, select').prop('type') == 'checkbox') {
968
+ // checkboxes don't capture this key
969
+ e.preventDefault();
970
+ return;
971
+ }
972
+ } else if (e.keyCode != 9 || e.shiftKey) {
973
+ opt.$menu.trigger('prevcommand');
974
+ return;
975
+ }
976
+ // omitting break;
977
+
978
+ // case 9: // tab - reached through omitted break;
979
+ case 40: // down
980
+ handle.keyStop(e, opt);
981
+ if (opt.isInput) {
982
+ if (e.keyCode == 9) {
983
+ e.preventDefault();
984
+ opt.$selected && opt.$selected.find('input, textarea, select').blur();
985
+ opt.$menu.trigger('nextcommand');
986
+ return;
987
+ } else if (e.keyCode == 40 && opt.$selected.find('input, textarea, select').prop('type') == 'checkbox') {
988
+ // checkboxes don't capture this key
989
+ e.preventDefault();
990
+ return;
991
+ }
992
+ } else {
993
+ opt.$menu.trigger('nextcommand');
994
+ return;
995
+ }
996
+ break;
997
+
998
+ case 37: // left
999
+ handle.keyStop(e, opt);
1000
+ if (opt.isInput || !opt.$selected || !opt.$selected.length) {
1001
+ break;
1002
+ }
1003
+
1004
+ if (!opt.$selected.parent().hasClass('context-menu-root')) {
1005
+ var $parent = opt.$selected.parent().parent();
1006
+ opt.$selected.trigger('contextmenu:blur');
1007
+ opt.$selected = $parent;
1008
+ return;
1009
+ }
1010
+ break;
1011
+
1012
+ case 39: // right
1013
+ handle.keyStop(e, opt);
1014
+ if (opt.isInput || !opt.$selected || !opt.$selected.length) {
1015
+ break;
1016
+ }
1017
+
1018
+ var itemdata = opt.$selected.data('contextMenu') || {};
1019
+ if (itemdata.$menu && opt.$selected.hasClass('context-menu-submenu')) {
1020
+ opt.$selected = null;
1021
+ itemdata.$selected = null;
1022
+ itemdata.$menu.trigger('nextcommand');
1023
+ return;
1024
+ }
1025
+ break;
1026
+
1027
+ case 35: // end
1028
+ case 36: // home
1029
+ if (opt.$selected && opt.$selected.find('input, textarea, select').length) {
1030
+ return;
1031
+ } else {
1032
+ (opt.$selected && opt.$selected.parent() || opt.$menu)
1033
+ .children(':not(.disabled, .not-selectable)')[e.keyCode == 36 ? 'first' : 'last']()
1034
+ .trigger('contextmenu:focus');
1035
+ e.preventDefault();
1036
+ return;
1037
+ }
1038
+ break;
1039
+
1040
+ case 13: // enter
1041
+ handle.keyStop(e, opt);
1042
+ if (opt.isInput) {
1043
+ if (opt.$selected && !opt.$selected.is('textarea, select')) {
1044
+ e.preventDefault();
1045
+ return;
1046
+ }
1047
+ break;
1048
+ }
1049
+ opt.$selected && opt.$selected.trigger('mouseup');
1050
+ return;
1051
+
1052
+ case 32: // space
1053
+ case 33: // page up
1054
+ case 34: // page down
1055
+ // prevent browser from scrolling down while menu is visible
1056
+ handle.keyStop(e, opt);
1057
+ return;
1058
+
1059
+ case 27: // esc
1060
+ handle.keyStop(e, opt);
1061
+ opt.$menu.trigger('contextmenu:hide');
1062
+ return;
1063
+
1064
+ default: // 0-9, a-z
1065
+ var k = (String.fromCharCode(e.keyCode)).toUpperCase();
1066
+ if (opt.accesskeys[k]) {
1067
+ // according to the specs accesskeys must be invoked immediately
1068
+ opt.accesskeys[k].$node.trigger(opt.accesskeys[k].$menu
1069
+ ? 'contextmenu:focus'
1070
+ : 'mouseup'
1071
+ );
1072
+ return;
1073
+ }
1074
+ break;
1075
+ }
1076
+ // pass event to selected item,
1077
+ // stop propagation to avoid endless recursion
1078
+ e.stopPropagation();
1079
+ opt.$selected && opt.$selected.trigger(e);
1080
+ },
1081
+
1082
+ // select previous possible command in menu
1083
+ prevItem: function(e) {
1084
+ e.stopPropagation();
1085
+ var opt = $(this).data('contextMenu') || {};
1086
+
1087
+ // obtain currently selected menu
1088
+ if (opt.$selected) {
1089
+ var $s = opt.$selected;
1090
+ opt = opt.$selected.parent().data('contextMenu') || {};
1091
+ opt.$selected = $s;
1092
+ }
1093
+
1094
+ var $children = opt.$menu.children(),
1095
+ $prev = !opt.$selected || !opt.$selected.prev().length ? $children.last() : opt.$selected.prev(),
1096
+ $round = $prev;
1097
+
1098
+ // skip disabled
1099
+ while ($prev.hasClass('disabled') || $prev.hasClass('not-selectable')) {
1100
+ if ($prev.prev().length) {
1101
+ $prev = $prev.prev();
1102
+ } else {
1103
+ $prev = $children.last();
1104
+ }
1105
+ if ($prev.is($round)) {
1106
+ // break endless loop
1107
+ return;
1108
+ }
1109
+ }
1110
+
1111
+ // leave current
1112
+ if (opt.$selected) {
1113
+ handle.itemMouseleave.call(opt.$selected.get(0), e);
1114
+ }
1115
+
1116
+ // activate next
1117
+ handle.itemMouseenter.call($prev.get(0), e);
1118
+
1119
+ // focus input
1120
+ var $input = $prev.find('input, textarea, select');
1121
+ if ($input.length) {
1122
+ $input.focus();
1123
+ }
1124
+ },
1125
+ // select next possible command in menu
1126
+ nextItem: function(e) {
1127
+ e.stopPropagation();
1128
+ var opt = $(this).data('contextMenu') || {};
1129
+
1130
+ // obtain currently selected menu
1131
+ if (opt.$selected) {
1132
+ var $s = opt.$selected;
1133
+ opt = opt.$selected.parent().data('contextMenu') || {};
1134
+ opt.$selected = $s;
1135
+ }
1136
+
1137
+ var $children = opt.$menu.children(),
1138
+ $next = !opt.$selected || !opt.$selected.next().length ? $children.first() : opt.$selected.next(),
1139
+ $round = $next;
1140
+
1141
+ // skip disabled
1142
+ while ($next.hasClass('disabled') || $next.hasClass('not-selectable')) {
1143
+ if ($next.next().length) {
1144
+ $next = $next.next();
1145
+ } else {
1146
+ $next = $children.first();
1147
+ }
1148
+ if ($next.is($round)) {
1149
+ // break endless loop
1150
+ return;
1151
+ }
1152
+ }
1153
+
1154
+ // leave current
1155
+ if (opt.$selected) {
1156
+ handle.itemMouseleave.call(opt.$selected.get(0), e);
1157
+ }
1158
+
1159
+ // activate next
1160
+ handle.itemMouseenter.call($next.get(0), e);
1161
+
1162
+ // focus input
1163
+ var $input = $next.find('input, textarea, select');
1164
+ if ($input.length) {
1165
+ $input.focus();
1166
+ }
1167
+ },
1168
+
1169
+ // flag that we're inside an input so the key handler can act accordingly
1170
+ focusInput: function(e) {
1171
+ var $this = $(this).closest('.context-menu-item'),
1172
+ data = $this.data(),
1173
+ opt = data.contextMenu,
1174
+ root = data.contextMenuRoot;
1175
+
1176
+ root.$selected = opt.$selected = $this;
1177
+ root.isInput = opt.isInput = true;
1178
+ },
1179
+ // flag that we're inside an input so the key handler can act accordingly
1180
+ blurInput: function(e) {
1181
+ var $this = $(this).closest('.context-menu-item'),
1182
+ data = $this.data(),
1183
+ opt = data.contextMenu,
1184
+ root = data.contextMenuRoot;
1185
+
1186
+ root.isInput = opt.isInput = false;
1187
+ },
1188
+
1189
+ // :hover on menu
1190
+ menuMouseenter: function(e) {
1191
+ var root = $(this).data().contextMenuRoot;
1192
+ root.hovering = true;
1193
+ },
1194
+ // :hover on menu
1195
+ menuMouseleave: function(e) {
1196
+ var root = $(this).data().contextMenuRoot;
1197
+ if (root.$layer && root.$layer.is(e.relatedTarget)) {
1198
+ root.hovering = false;
1199
+ }
1200
+ },
1201
+
1202
+ // :hover done manually so key handling is possible
1203
+ itemMouseenter: function(e) {
1204
+ var $this = $(this),
1205
+ data = $this.data(),
1206
+ opt = data.contextMenu,
1207
+ root = data.contextMenuRoot;
1208
+
1209
+ root.hovering = true;
1210
+
1211
+ // abort if we're re-entering
1212
+ if (e && root.$layer && root.$layer.is(e.relatedTarget)) {
1213
+ e.preventDefault();
1214
+ e.stopImmediatePropagation();
1215
+ }
1216
+
1217
+ // make sure only one item is selected
1218
+ (opt.$menu ? opt : root).$menu
1219
+ .children('.hover').trigger('contextmenu:blur');
1220
+
1221
+ if ($this.hasClass('disabled') || $this.hasClass('not-selectable')) {
1222
+ opt.$selected = null;
1223
+ return;
1224
+ }
1225
+
1226
+ $this.trigger('contextmenu:focus');
1227
+ },
1228
+ // :hover done manually so key handling is possible
1229
+ itemMouseleave: function(e) {
1230
+ var $this = $(this),
1231
+ data = $this.data(),
1232
+ opt = data.contextMenu,
1233
+ root = data.contextMenuRoot;
1234
+
1235
+ if (root !== opt && root.$layer && root.$layer.is(e.relatedTarget)) {
1236
+ root.$selected && root.$selected.trigger('contextmenu:blur');
1237
+ e.preventDefault();
1238
+ e.stopImmediatePropagation();
1239
+ root.$selected = opt.$selected = opt.$node;
1240
+ return;
1241
+ }
1242
+
1243
+ $this.trigger('contextmenu:blur');
1244
+ },
1245
+ // contextMenu item click
1246
+ itemClick: function(e) {
1247
+ var $this = $(this),
1248
+ data = $this.data(),
1249
+ opt = data.contextMenu,
1250
+ root = data.contextMenuRoot,
1251
+ key = data.contextMenuKey,
1252
+ callback;
1253
+
1254
+ // abort if the key is unknown or disabled or is a menu
1255
+ if (!opt.items[key] || $this.hasClass('disabled') || $this.hasClass('context-menu-submenu')) {
1256
+ return;
1257
+ }
1258
+
1259
+ e.preventDefault();
1260
+ e.stopImmediatePropagation();
1261
+
1262
+ if ($.isFunction(root.callbacks[key])) {
1263
+ // item-specific callback
1264
+ callback = root.callbacks[key];
1265
+ } else if ($.isFunction(root.callback)) {
1266
+ // default callback
1267
+ callback = root.callback;
1268
+ } else {
1269
+ // no callback, no action
1270
+ return;
1271
+ }
1272
+
1273
+ // hide menu if callback doesn't stop that
1274
+ if (callback.call(root.$trigger, key, root) !== false) {
1275
+ root.$menu.trigger('contextmenu:hide');
1276
+ } else if (root.$menu.parent().length) {
1277
+ op.update.call(root.$trigger, root);
1278
+ }
1279
+ },
1280
+ // ignore click events on input elements
1281
+ inputClick: function(e) {
1282
+ e.stopImmediatePropagation();
1283
+ },
1284
+
1285
+ // hide <menu>
1286
+ hideMenu: function(e, data) {
1287
+ var root = $(this).data('contextMenuRoot');
1288
+ op.hide.call(root.$trigger, root, data && data.force);
1289
+ },
1290
+ // focus <command>
1291
+ focusItem: function(e) {
1292
+ e.stopPropagation();
1293
+ var $this = $(this),
1294
+ data = $this.data(),
1295
+ opt = data.contextMenu,
1296
+ root = data.contextMenuRoot;
1297
+
1298
+ $this.addClass('hover')
1299
+ .siblings('.hover').trigger('contextmenu:blur');
1300
+
1301
+ // remember selected
1302
+ opt.$selected = root.$selected = $this;
1303
+
1304
+ // position sub-menu - do after show so dumb $.ui.position can keep up
1305
+ if (opt.$node) {
1306
+ root.positionSubmenu.call(opt.$node, opt.$menu);
1307
+ }
1308
+ },
1309
+ // blur <command>
1310
+ blurItem: function(e) {
1311
+ e.stopPropagation();
1312
+ var $this = $(this),
1313
+ data = $this.data(),
1314
+ opt = data.contextMenu,
1315
+ root = data.contextMenuRoot;
1316
+
1317
+ $this.removeClass('hover');
1318
+ opt.$selected = null;
1319
+ }
1320
+ },
1321
+ // operations
1322
+ op = {
1323
+ show: function(opt, x, y) {
1324
+ var $this = $(this),
1325
+ offset,
1326
+ css = {};
1327
+
1328
+ // hide any open menus
1329
+ $('#context-menu-layer').trigger('mousedown');
1330
+
1331
+ // backreference for callbacks
1332
+ opt.$trigger = $this;
1333
+
1334
+ // show event
1335
+ if (opt.events.show.call($this, opt) === false) {
1336
+ $currentTrigger = null;
1337
+ return;
1338
+ }
1339
+
1340
+ // create or update context menu
1341
+ op.update.call($this, opt);
1342
+
1343
+ // position menu
1344
+ opt.position.call($this, opt, x, y);
1345
+
1346
+ // make sure we're in front
1347
+ if (opt.zIndex) {
1348
+ css.zIndex = zindex($this) + opt.zIndex;
1349
+ }
1350
+
1351
+ // add layer
1352
+ op.layer.call(opt.$menu, opt, css.zIndex);
1353
+
1354
+ // adjust sub-menu zIndexes
1355
+ opt.$menu.find('ul').css('zIndex', css.zIndex + 1);
1356
+
1357
+ // position and show context menu
1358
+ opt.$menu.css( css )[opt.animation.show](opt.animation.duration);
1359
+ // make options available
1360
+ $this.data('contextMenu', opt);
1361
+ // register key handler
1362
+ $(document).off('keydown.contextMenu').on('keydown.contextMenu', handle.key);
1363
+ // register autoHide handler
1364
+ if (opt.autoHide) {
1365
+ // trigger element coordinates
1366
+ var pos = $this.position();
1367
+ pos.right = pos.left + $this.outerWidth();
1368
+ pos.bottom = pos.top + this.outerHeight();
1369
+ // mouse position handler
1370
+ $(document).on('mousemove.contextMenuAutoHide', function(e) {
1371
+ if (opt.$layer && !opt.hovering && (!(e.pageX >= pos.left && e.pageX <= pos.right) || !(e.pageY >= pos.top && e.pageY <= pos.bottom))) {
1372
+ // if mouse in menu...
1373
+ opt.$menu.trigger('contextmenu:hide');
1374
+ }
1375
+ });
1376
+ }
1377
+ },
1378
+ hide: function(opt, force) {
1379
+ var $this = $(this);
1380
+ if (!opt) {
1381
+ opt = $this.data('contextMenu') || {};
1382
+ }
1383
+
1384
+ // hide event
1385
+ if (!force && opt.events && opt.events.hide.call($this, opt) === false) {
1386
+ return;
1387
+ }
1388
+
1389
+ if (opt.$layer) {
1390
+ // keep layer for a bit so the contextmenu event can be aborted properly by opera
1391
+ setTimeout((function($layer){ return function(){
1392
+ $layer.remove();
1393
+ };
1394
+ })(opt.$layer), 10);
1395
+
1396
+ try {
1397
+ delete opt.$layer;
1398
+ } catch(e) {
1399
+ opt.$layer = null;
1400
+ }
1401
+ }
1402
+
1403
+ // remove handle
1404
+ $currentTrigger = null;
1405
+ // remove selected
1406
+ opt.$menu.find('.hover').trigger('contextmenu:blur');
1407
+ opt.$selected = null;
1408
+ // unregister key and mouse handlers
1409
+ //$(document).off('.contextMenuAutoHide keydown.contextMenu'); // http://bugs.jquery.com/ticket/10705
1410
+ $(document).off('.contextMenuAutoHide').off('keydown.contextMenu');
1411
+ // hide menu
1412
+ opt.$menu && opt.$menu[opt.animation.hide](opt.animation.duration, function (){
1413
+ // tear down dynamically built menu after animation is completed.
1414
+ if (opt.build) {
1415
+ opt.$menu.remove();
1416
+ $.each(opt, function(key, value) {
1417
+ switch (key) {
1418
+ case 'ns':
1419
+ case 'selector':
1420
+ case 'build':
1421
+ case 'trigger':
1422
+ return true;
1423
+
1424
+ default:
1425
+ opt[key] = undefined;
1426
+ try {
1427
+ delete opt[key];
1428
+ } catch (e) {}
1429
+ return true;
1430
+ }
1431
+ });
1432
+ }
1433
+ });
1434
+ },
1435
+ create: function(opt, root) {
1436
+ if (root === undefined) {
1437
+ root = opt;
1438
+ }
1439
+ // create contextMenu
1440
+ opt.$menu = $('<ul class="context-menu-list ' + (opt.className || "") + '"></ul>').data({
1441
+ 'contextMenu': opt,
1442
+ 'contextMenuRoot': root
1443
+ });
1444
+
1445
+ $.each(['callbacks', 'commands', 'inputs'], function(i,k){
1446
+ opt[k] = {};
1447
+ if (!root[k]) {
1448
+ root[k] = {};
1449
+ }
1450
+ });
1451
+
1452
+ root.accesskeys || (root.accesskeys = {});
1453
+
1454
+ // create contextMenu items
1455
+ $.each(opt.items, function(key, item){
1456
+ var $t = $('<li class="context-menu-item ' + (item.className || "") +'"></li>'),
1457
+ $label = null,
1458
+ $input = null;
1459
+
1460
+ item.$node = $t.data({
1461
+ 'contextMenu': opt,
1462
+ 'contextMenuRoot': root,
1463
+ 'contextMenuKey': key
1464
+ });
1465
+
1466
+ // register accesskey
1467
+ // NOTE: the accesskey attribute should be applicable to any element, but Safari5 and Chrome13 still can't do that
1468
+ if (item.accesskey) {
1469
+ var aks = splitAccesskey(item.accesskey);
1470
+ for (var i=0, ak; ak = aks[i]; i++) {
1471
+ if (!root.accesskeys[ak]) {
1472
+ root.accesskeys[ak] = item;
1473
+ item._name = item.name.replace(new RegExp('(' + ak + ')', 'i'), '<span class="context-menu-accesskey">$1</span>');
1474
+ break;
1475
+ }
1476
+ }
1477
+ }
1478
+
1479
+ if (typeof item == "string") {
1480
+ $t.addClass('context-menu-separator not-selectable');
1481
+ } else if (item.type && types[item.type]) {
1482
+ // run custom type handler
1483
+ types[item.type].call($t, item, opt, root);
1484
+ // register commands
1485
+ $.each([opt, root], function(i,k){
1486
+ k.commands[key] = item;
1487
+ if ($.isFunction(item.callback)) {
1488
+ k.callbacks[key] = item.callback;
1489
+ }
1490
+ });
1491
+ } else {
1492
+ // add label for input
1493
+ if (item.type == 'html') {
1494
+ $t.addClass('context-menu-html not-selectable');
1495
+ } else if (item.type) {
1496
+ $label = $('<label></label>').appendTo($t);
1497
+ $('<span></span>').html(item._name || item.name).appendTo($label);
1498
+ $t.addClass('context-menu-input');
1499
+ opt.hasTypes = true;
1500
+ $.each([opt, root], function(i,k){
1501
+ k.commands[key] = item;
1502
+ k.inputs[key] = item;
1503
+ });
1504
+ } else if (item.items) {
1505
+ item.type = 'sub';
1506
+ }
1507
+
1508
+ switch (item.type) {
1509
+ case 'text':
1510
+ $input = $('<input type="text" value="1" name="context-menu-input-'+ key +'" value="">')
1511
+ .val(item.value || "").appendTo($label);
1512
+ break;
1513
+
1514
+ case 'textarea':
1515
+ $input = $('<textarea name="context-menu-input-'+ key +'"></textarea>')
1516
+ .val(item.value || "").appendTo($label);
1517
+
1518
+ if (item.height) {
1519
+ $input.height(item.height);
1520
+ }
1521
+ break;
1522
+
1523
+ case 'checkbox':
1524
+ $input = $('<input type="checkbox" value="1" name="context-menu-input-'+ key +'" value="">')
1525
+ .val(item.value || "").prop("checked", !!item.selected).prependTo($label);
1526
+ break;
1527
+
1528
+ case 'radio':
1529
+ $input = $('<input type="radio" value="1" name="context-menu-input-'+ item.radio +'" value="">')
1530
+ .val(item.value || "").prop("checked", !!item.selected).prependTo($label);
1531
+ break;
1532
+
1533
+ case 'select':
1534
+ $input = $('<select name="context-menu-input-'+ key +'">').appendTo($label);
1535
+ if (item.options) {
1536
+ $.each(item.options, function(value, text) {
1537
+ $('<option></option>').val(value).text(text).appendTo($input);
1538
+ });
1539
+ $input.val(item.selected);
1540
+ }
1541
+ break;
1542
+
1543
+ case 'sub':
1544
+ $('<span></span>').html(item._name || item.name).appendTo($t);
1545
+ item.appendTo = item.$node;
1546
+ op.create(item, root);
1547
+ $t.data('contextMenu', item).addClass('context-menu-submenu');
1548
+ item.callback = null;
1549
+ break;
1550
+
1551
+ case 'html':
1552
+ $(item.html).appendTo($t);
1553
+ break;
1554
+
1555
+ default:
1556
+ $.each([opt, root], function(i,k){
1557
+ k.commands[key] = item;
1558
+ if ($.isFunction(item.callback)) {
1559
+ k.callbacks[key] = item.callback;
1560
+ }
1561
+ });
1562
+
1563
+ $('<span></span>').html(item._name || item.name || "").appendTo($t);
1564
+ break;
1565
+ }
1566
+
1567
+ // disable key listener in <input>
1568
+ if (item.type && item.type != 'sub' && item.type != 'html') {
1569
+ $input
1570
+ .on('focus', handle.focusInput)
1571
+ .on('blur', handle.blurInput);
1572
+
1573
+ if (item.events) {
1574
+ $input.on(item.events, opt);
1575
+ }
1576
+ }
1577
+
1578
+ // add icons
1579
+ if (item.icon) {
1580
+ $t.addClass("icon icon-" + item.icon);
1581
+ }
1582
+ }
1583
+
1584
+ // cache contained elements
1585
+ item.$input = $input;
1586
+ item.$label = $label;
1587
+
1588
+ // attach item to menu
1589
+ $t.appendTo(opt.$menu);
1590
+
1591
+ // Disable text selection
1592
+ if (!opt.hasTypes && $.support.eventSelectstart) {
1593
+ // browsers support user-select: none,
1594
+ // IE has a special event for text-selection
1595
+ // browsers supporting neither will not be preventing text-selection
1596
+ $t.on('selectstart.disableTextSelect', handle.abortevent);
1597
+ }
1598
+ });
1599
+ // attach contextMenu to <body> (to bypass any possible overflow:hidden issues on parents of the trigger element)
1600
+ if (!opt.$node) {
1601
+ opt.$menu.css('display', 'none').addClass('context-menu-root');
1602
+ }
1603
+ opt.$menu.appendTo(opt.appendTo || document.body);
1604
+ },
1605
+ update: function(opt, root) {
1606
+ var $this = this;
1607
+ if (root === undefined) {
1608
+ root = opt;
1609
+ // determine widths of submenus, as CSS won't grow them automatically
1610
+ // position:absolute > position:absolute; min-width:100; max-width:200; results in width: 100;
1611
+ // kinda sucks hard...
1612
+ opt.$menu.find('ul').andSelf().css({position: 'static', display: 'block'}).each(function(){
1613
+ var $this = $(this);
1614
+ $this.width($this.css('position', 'absolute').width())
1615
+ .css('position', 'static');
1616
+ }).css({position: '', display: ''});
1617
+ }
1618
+ // re-check disabled for each item
1619
+ opt.$menu.children().each(function(){
1620
+ var $item = $(this),
1621
+ key = $item.data('contextMenuKey'),
1622
+ item = opt.items[key],
1623
+ disabled = ($.isFunction(item.disabled) && item.disabled.call($this, key, root)) || item.disabled === true;
1624
+
1625
+ // dis- / enable item
1626
+ $item[disabled ? 'addClass' : 'removeClass']('disabled');
1627
+
1628
+ if (item.type) {
1629
+ // dis- / enable input elements
1630
+ $item.find('input, select, textarea').prop('disabled', disabled);
1631
+
1632
+ // update input states
1633
+ switch (item.type) {
1634
+ case 'text':
1635
+ case 'textarea':
1636
+ item.$input.val(item.value || "");
1637
+ break;
1638
+
1639
+ case 'checkbox':
1640
+ case 'radio':
1641
+ item.$input.val(item.value || "").prop('checked', !!item.selected);
1642
+ break;
1643
+
1644
+ case 'select':
1645
+ item.$input.val(item.selected || "");
1646
+ break;
1647
+ }
1648
+ }
1649
+
1650
+ if (item.$menu) {
1651
+ // update sub-menu
1652
+ op.update.call($this, item, root);
1653
+ }
1654
+ });
1655
+ },
1656
+ layer: function(opt, zIndex) {
1657
+ // add transparent layer for click area
1658
+ // filter and background for Internet Explorer, Issue #23
1659
+ var $layer = opt.$layer = $('<div id="context-menu-layer" style="position:fixed; z-index:' + zIndex + '; top:0; left:0; opacity: 0; filter: alpha(opacity=0); background-color: #000;"></div>')
1660
+ .css({height: $win.height(), width: $win.width(), display: 'block'})
1661
+ .data('contextMenuRoot', opt)
1662
+ .insertBefore(this)
1663
+ .on('contextmenu', handle.abortevent)
1664
+ .on('mousedown', handle.layerClick);
1665
+
1666
+ // IE6 doesn't know position:fixed;
1667
+ if (!$.support.fixedPosition) {
1668
+ $layer.css({
1669
+ 'position' : 'absolute',
1670
+ 'height' : $(document).height()
1671
+ });
1672
+ }
1673
+
1674
+ return $layer;
1675
+ }
1676
+ };
1677
+
1678
+ // split accesskey according to http://www.whatwg.org/specs/web-apps/current-work/multipage/editing.html#assigned-access-key
1679
+ function splitAccesskey(val) {
1680
+ var t = val.split(/\s+/),
1681
+ keys = [];
1682
+
1683
+ for (var i=0, k; k = t[i]; i++) {
1684
+ k = k[0].toUpperCase(); // first character only
1685
+ // theoretically non-accessible characters should be ignored, but different systems, different keyboard layouts, ... screw it.
1686
+ // a map to look up already used access keys would be nice
1687
+ keys.push(k);
1688
+ }
1689
+
1690
+ return keys;
1691
+ }
1692
+
1693
+ // handle contextMenu triggers
1694
+ $.fn.contextMenu = function(operation) {
1695
+ if (operation === undefined) {
1696
+ this.first().trigger('contextmenu');
1697
+ } else if (operation.x && operation.y) {
1698
+ this.first().trigger($.Event("contextmenu", {pageX: operation.x, pageY: operation.y}));
1699
+ } else if (operation === "hide") {
1700
+ var $menu = this.data('contextMenu').$menu;
1701
+ $menu && $menu.trigger('contextmenu:hide');
1702
+ } else if (operation) {
1703
+ this.removeClass('context-menu-disabled');
1704
+ } else if (!operation) {
1705
+ this.addClass('context-menu-disabled');
1706
+ }
1707
+
1708
+ return this;
1709
+ };
1710
+
1711
+ // manage contextMenu instances
1712
+ $.contextMenu = function(operation, options) {
1713
+ if (typeof operation != 'string') {
1714
+ options = operation;
1715
+ operation = 'create';
1716
+ }
1717
+
1718
+ if (typeof options == 'string') {
1719
+ options = {selector: options};
1720
+ } else if (options === undefined) {
1721
+ options = {};
1722
+ }
1723
+
1724
+ // merge with default options
1725
+ var o = $.extend(true, {}, defaults, options || {}),
1726
+ $document = $(document);
1727
+
1728
+ switch (operation) {
1729
+ case 'create':
1730
+ // no selector no joy
1731
+ if (!o.selector) {
1732
+ throw new Error('No selector specified');
1733
+ }
1734
+ // make sure internal classes are not bound to
1735
+ if (o.selector.match(/.context-menu-(list|item|input)($|\s)/)) {
1736
+ throw new Error('Cannot bind to selector "' + o.selector + '" as it contains a reserved className');
1737
+ }
1738
+ if (!o.build && (!o.items || $.isEmptyObject(o.items))) {
1739
+ throw new Error('No Items sepcified');
1740
+ }
1741
+ counter ++;
1742
+ o.ns = '.contextMenu' + counter;
1743
+ namespaces[o.selector] = o.ns;
1744
+ menus[o.ns] = o;
1745
+
1746
+ // default to right click
1747
+ if (!o.trigger) {
1748
+ o.trigger = 'right';
1749
+ }
1750
+
1751
+ if (!initialized) {
1752
+ // make sure item click is registered first
1753
+ $document
1754
+ .on({
1755
+ 'contextmenu:hide.contextMenu': handle.hideMenu,
1756
+ 'prevcommand.contextMenu': handle.prevItem,
1757
+ 'nextcommand.contextMenu': handle.nextItem,
1758
+ 'contextmenu.contextMenu': handle.abortevent,
1759
+ 'mouseenter.contextMenu': handle.menuMouseenter,
1760
+ 'mouseleave.contextMenu': handle.menuMouseleave
1761
+ }, '.context-menu-list')
1762
+ .on('mouseup.contextMenu', '.context-menu-input', handle.inputClick)
1763
+ .on({
1764
+ 'mouseup.contextMenu': handle.itemClick,
1765
+ 'contextmenu:focus.contextMenu': handle.focusItem,
1766
+ 'contextmenu:blur.contextMenu': handle.blurItem,
1767
+ 'contextmenu.contextMenu': handle.abortevent,
1768
+ 'mouseenter.contextMenu': handle.itemMouseenter,
1769
+ 'mouseleave.contextMenu': handle.itemMouseleave
1770
+ }, '.context-menu-item');
1771
+
1772
+ initialized = true;
1773
+ }
1774
+
1775
+ // engage native contextmenu event
1776
+ $document
1777
+ .on('contextmenu' + o.ns, o.selector, o, handle.contextmenu);
1778
+
1779
+ switch (o.trigger) {
1780
+ case 'hover':
1781
+ $document
1782
+ .on('mouseenter' + o.ns, o.selector, o, handle.mouseenter)
1783
+ .on('mouseleave' + o.ns, o.selector, o, handle.mouseleave);
1784
+ break;
1785
+
1786
+ case 'left':
1787
+ $document.on('click' + o.ns, o.selector, o, handle.click);
1788
+ break;
1789
+ /*
1790
+ default:
1791
+ // http://www.quirksmode.org/dom/events/contextmenu.html
1792
+ $document
1793
+ .on('mousedown' + o.ns, o.selector, o, handle.mousedown)
1794
+ .on('mouseup' + o.ns, o.selector, o, handle.mouseup);
1795
+ break;
1796
+ */
1797
+ }
1798
+
1799
+ // create menu
1800
+ if (!o.build) {
1801
+ op.create(o);
1802
+ }
1803
+ break;
1804
+
1805
+ case 'destroy':
1806
+ if (!o.selector) {
1807
+ $document.off('.contextMenu .contextMenuAutoHide');
1808
+ $.each(namespaces, function(key, value) {
1809
+ $document.off(value);
1810
+ });
1811
+
1812
+ namespaces = {};
1813
+ menus = {};
1814
+ counter = 0;
1815
+ initialized = false;
1816
+
1817
+ $('#context-menu-layer, .context-menu-list').remove();
1818
+ } else if (namespaces[o.selector]) {
1819
+ var $visibleMenu = $('.context-menu-list').filter(':visible');
1820
+ if ($visibleMenu.length && $visibleMenu.data().contextMenuRoot.$trigger.is(o.selector)) {
1821
+ $visibleMenu.trigger('contextmenu:hide', {force: true});
1822
+ }
1823
+
1824
+ try {
1825
+ if (menus[namespaces[o.selector]].$menu) {
1826
+ menus[namespaces[o.selector]].$menu.remove();
1827
+ }
1828
+
1829
+ delete menus[namespaces[o.selector]];
1830
+ } catch(e) {
1831
+ menus[namespaces[o.selector]] = null;
1832
+ }
1833
+
1834
+ $document.off(namespaces[o.selector]);
1835
+ }
1836
+ break;
1837
+
1838
+ case 'html5':
1839
+ // if <command> or <menuitem> are not handled by the browser,
1840
+ // or options was a bool true,
1841
+ // initialize $.contextMenu for them
1842
+ if ((!$.support.htmlCommand && !$.support.htmlMenuitem) || (typeof options == "boolean" && options)) {
1843
+ $('menu[type="context"]').each(function() {
1844
+ if (this.id) {
1845
+ $.contextMenu({
1846
+ selector: '[contextmenu=' + this.id +']',
1847
+ items: $.contextMenu.fromMenu(this)
1848
+ });
1849
+ }
1850
+ }).css('display', 'none');
1851
+ }
1852
+ break;
1853
+
1854
+ default:
1855
+ throw new Error('Unknown operation "' + operation + '"');
1856
+ }
1857
+
1858
+ return this;
1859
+ };
1860
+
1861
+ // import values into <input> commands
1862
+ $.contextMenu.setInputValues = function(opt, data) {
1863
+ if (data === undefined) {
1864
+ data = {};
1865
+ }
1866
+
1867
+ $.each(opt.inputs, function(key, item) {
1868
+ switch (item.type) {
1869
+ case 'text':
1870
+ case 'textarea':
1871
+ item.value = data[key] || "";
1872
+ break;
1873
+
1874
+ case 'checkbox':
1875
+ item.selected = data[key] ? true : false;
1876
+ break;
1877
+
1878
+ case 'radio':
1879
+ item.selected = (data[item.radio] || "") == item.value ? true : false;
1880
+ break;
1881
+
1882
+ case 'select':
1883
+ item.selected = data[key] || "";
1884
+ break;
1885
+ }
1886
+ });
1887
+ };
1888
+
1889
+ // export values from <input> commands
1890
+ $.contextMenu.getInputValues = function(opt, data) {
1891
+ if (data === undefined) {
1892
+ data = {};
1893
+ }
1894
+
1895
+ $.each(opt.inputs, function(key, item) {
1896
+ switch (item.type) {
1897
+ case 'text':
1898
+ case 'textarea':
1899
+ case 'select':
1900
+ data[key] = item.$input.val();
1901
+ break;
1902
+
1903
+ case 'checkbox':
1904
+ data[key] = item.$input.prop('checked');
1905
+ break;
1906
+
1907
+ case 'radio':
1908
+ if (item.$input.prop('checked')) {
1909
+ data[item.radio] = item.value;
1910
+ }
1911
+ break;
1912
+ }
1913
+ });
1914
+
1915
+ return data;
1916
+ };
1917
+
1918
+ // find <label for="xyz">
1919
+ function inputLabel(node) {
1920
+ return (node.id && $('label[for="'+ node.id +'"]').val()) || node.name;
1921
+ }
1922
+
1923
+ // convert <menu> to items object
1924
+ function menuChildren(items, $children, counter) {
1925
+ if (!counter) {
1926
+ counter = 0;
1927
+ }
1928
+
1929
+ $children.each(function() {
1930
+ var $node = $(this),
1931
+ node = this,
1932
+ nodeName = this.nodeName.toLowerCase(),
1933
+ label,
1934
+ item;
1935
+
1936
+ // extract <label><input>
1937
+ if (nodeName == 'label' && $node.find('input, textarea, select').length) {
1938
+ label = $node.text();
1939
+ $node = $node.children().first();
1940
+ node = $node.get(0);
1941
+ nodeName = node.nodeName.toLowerCase();
1942
+ }
1943
+
1944
+ /*
1945
+ * <menu> accepts flow-content as children. that means <embed>, <canvas> and such are valid menu items.
1946
+ * Not being the sadistic kind, $.contextMenu only accepts:
1947
+ * <command>, <menuitem>, <hr>, <span>, <p> <input [text, radio, checkbox]>, <textarea>, <select> and of course <menu>.
1948
+ * Everything else will be imported as an html node, which is not interfaced with contextMenu.
1949
+ */
1950
+
1951
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#concept-command
1952
+ switch (nodeName) {
1953
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#the-menu-element
1954
+ case 'menu':
1955
+ item = {name: $node.attr('label'), items: {}};
1956
+ counter = menuChildren(item.items, $node.children(), counter);
1957
+ break;
1958
+
1959
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-a-element-to-define-a-command
1960
+ case 'a':
1961
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-button-element-to-define-a-command
1962
+ case 'button':
1963
+ item = {
1964
+ name: $node.text(),
1965
+ disabled: !!$node.attr('disabled'),
1966
+ callback: (function(){ return function(){ $node.click(); }; })()
1967
+ };
1968
+ break;
1969
+
1970
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-command-element-to-define-a-command
1971
+
1972
+ case 'menuitem':
1973
+ case 'command':
1974
+ switch ($node.attr('type')) {
1975
+ case undefined:
1976
+ case 'command':
1977
+ case 'menuitem':
1978
+ item = {
1979
+ name: $node.attr('label'),
1980
+ disabled: !!$node.attr('disabled'),
1981
+ callback: (function(){ return function(){ $node.click(); }; })()
1982
+ };
1983
+ break;
1984
+
1985
+ case 'checkbox':
1986
+ item = {
1987
+ type: 'checkbox',
1988
+ disabled: !!$node.attr('disabled'),
1989
+ name: $node.attr('label'),
1990
+ selected: !!$node.attr('checked')
1991
+ };
1992
+ break;
1993
+
1994
+ case 'radio':
1995
+ item = {
1996
+ type: 'radio',
1997
+ disabled: !!$node.attr('disabled'),
1998
+ name: $node.attr('label'),
1999
+ radio: $node.attr('radiogroup'),
2000
+ value: $node.attr('id'),
2001
+ selected: !!$node.attr('checked')
2002
+ };
2003
+ break;
2004
+
2005
+ default:
2006
+ item = undefined;
2007
+ }
2008
+ break;
2009
+
2010
+ case 'hr':
2011
+ item = '-------';
2012
+ break;
2013
+
2014
+ case 'input':
2015
+ switch ($node.attr('type')) {
2016
+ case 'text':
2017
+ item = {
2018
+ type: 'text',
2019
+ name: label || inputLabel(node),
2020
+ disabled: !!$node.attr('disabled'),
2021
+ value: $node.val()
2022
+ };
2023
+ break;
2024
+
2025
+ case 'checkbox':
2026
+ item = {
2027
+ type: 'checkbox',
2028
+ name: label || inputLabel(node),
2029
+ disabled: !!$node.attr('disabled'),
2030
+ selected: !!$node.attr('checked')
2031
+ };
2032
+ break;
2033
+
2034
+ case 'radio':
2035
+ item = {
2036
+ type: 'radio',
2037
+ name: label || inputLabel(node),
2038
+ disabled: !!$node.attr('disabled'),
2039
+ radio: !!$node.attr('name'),
2040
+ value: $node.val(),
2041
+ selected: !!$node.attr('checked')
2042
+ };
2043
+ break;
2044
+
2045
+ default:
2046
+ item = undefined;
2047
+ break;
2048
+ }
2049
+ break;
2050
+
2051
+ case 'select':
2052
+ item = {
2053
+ type: 'select',
2054
+ name: label || inputLabel(node),
2055
+ disabled: !!$node.attr('disabled'),
2056
+ selected: $node.val(),
2057
+ options: {}
2058
+ };
2059
+ $node.children().each(function(){
2060
+ item.options[this.value] = $(this).text();
2061
+ });
2062
+ break;
2063
+
2064
+ case 'textarea':
2065
+ item = {
2066
+ type: 'textarea',
2067
+ name: label || inputLabel(node),
2068
+ disabled: !!$node.attr('disabled'),
2069
+ value: $node.val()
2070
+ };
2071
+ break;
2072
+
2073
+ case 'label':
2074
+ break;
2075
+
2076
+ default:
2077
+ item = {type: 'html', html: $node.clone(true)};
2078
+ break;
2079
+ }
2080
+
2081
+ if (item) {
2082
+ counter++;
2083
+ items['key' + counter] = item;
2084
+ }
2085
+ });
2086
+
2087
+ return counter;
2088
+ }
2089
+
2090
+ // convert html5 menu
2091
+ $.contextMenu.fromMenu = function(element) {
2092
+ var $this = $(element),
2093
+ items = {};
2094
+
2095
+ menuChildren(items, $this.children());
2096
+
2097
+ return items;
2098
+ };
2099
+
2100
+ // make defaults accessible
2101
+ $.contextMenu.defaults = defaults;
2102
+ $.contextMenu.types = types;
2103
+
2104
+ })(jQuery);