rsence 2.1.11 → 2.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 (216) hide show
  1. data/INSTALL.rdoc +5 -3
  2. data/README.rdoc +23 -22
  3. data/VERSION +1 -1
  4. data/conf/default_conf.yaml +65 -29
  5. data/conf/rsence_command_strings.yaml +101 -71
  6. data/js/comm/autosync/autosync.js +3 -3
  7. data/js/comm/jsloader/jsloader.js +16 -3
  8. data/js/comm/queue/queue.js +1 -1
  9. data/js/comm/transporter/transporter.js +106 -83
  10. data/js/comm/values/values.js +8 -2
  11. data/js/controls/button/button.js +9 -4
  12. data/js/controls/button/themes/default/button.css +1 -1
  13. data/js/controls/checkbox/themes/default/checkbox.css +1 -1
  14. data/js/controls/dialogs/sheet/sheet.js +27 -18
  15. data/js/controls/radiobutton/themes/default/radiobutton.css +1 -1
  16. data/js/controls/searchfield/searchfield.coffee +2 -0
  17. data/js/controls/searchfield/themes/default/searchfield.css +96 -0
  18. data/js/controls/searchfield/themes/default/searchfield.html +12 -0
  19. data/js/controls/searchfield/themes/default/searchfield_parts1-ie6.gif +0 -0
  20. data/js/controls/searchfield/themes/default/searchfield_parts1.png +0 -0
  21. data/js/controls/sliders/slider/slider.js +7 -1
  22. data/js/controls/stepper/stepper.js +6 -1
  23. data/js/controls/stringview/stringview.js +50 -39
  24. data/js/controls/tab/tab.js +103 -7
  25. data/js/controls/tab/themes/default/tab.css +2 -0
  26. data/js/controls/textarea/themes/default/textarea.css +4 -0
  27. data/js/controls/textcontrol/textcontrol.js +32 -2
  28. data/js/controls/textcontrol/themes/default/textcontrol.css +17 -2
  29. data/js/controls/uploader/themes/default/uploader.css +3 -3
  30. data/js/controls/uploader/themes/default/uploader.html +1 -1
  31. data/js/controls/uploader/uploader.coffee +110 -0
  32. data/js/controls/window/themes/default/window.css +28 -5
  33. data/js/controls/window/themes/default/window.html +2 -0
  34. data/js/controls/window/window.js +5 -1
  35. data/js/core/class/class.js +22 -4
  36. data/js/core/elem/elem.coffee +816 -0
  37. data/js/core/event/event.js +2 -2
  38. data/js/core/rsence_ns/rsence_ns.coffee +31 -0
  39. data/js/core/rsence_ns/rsence_ns.js +6 -6
  40. data/js/datetime/calendar/calendar.coffee +307 -0
  41. data/js/datetime/calendar/themes/default/calendar.css +90 -9
  42. data/js/datetime/calendar/themes/default/calendar.html +11 -0
  43. data/js/datetime/calendar/themes/default/calendar_bg-ie6.gif +0 -0
  44. data/js/datetime/calendar/themes/default/calendar_bg.png +0 -0
  45. data/js/datetime/calendar/themes/default/calendar_parts1-ie6.gif +0 -0
  46. data/js/datetime/calendar/themes/default/calendar_parts1.png +0 -0
  47. data/js/datetime/calendar/themes/default/calendar_parts2-ie6.gif +0 -0
  48. data/js/datetime/calendar/themes/default/calendar_parts2.png +0 -0
  49. data/js/datetime/datetimepicker/datetimepicker.js +217 -0
  50. data/js/datetime/datetimevalue/datetimevalue.js +22 -5
  51. data/js/datetime/timesheet/themes/default/timesheet.css +51 -22
  52. data/js/datetime/timesheet/themes/default/timesheet.html +4 -2
  53. data/js/datetime/timesheet/timesheet.js +782 -192
  54. data/js/datetime/timesheet_item/themes/default/timesheet_item.css +42 -11
  55. data/js/datetime/timesheet_item/themes/default/timesheet_item.html +4 -2
  56. data/js/datetime/timesheet_item/themes/default/timesheet_item_icons.png +0 -0
  57. data/js/datetime/timesheet_item/timesheet_item.js +158 -254
  58. data/js/datetime/timesheet_item_edit/timesheet_item_edit.js +0 -274
  59. data/js/foundation/application/application.js +52 -9
  60. data/js/foundation/control/eventresponder/eventresponder.js +7 -4
  61. data/js/foundation/control/valueaction/valueaction.js +71 -0
  62. data/js/foundation/eventmanager/eventmanager.js +71 -33
  63. data/js/foundation/geom/rect/rect.js +39 -7
  64. data/js/foundation/json_renderer/json_renderer.js +278 -62
  65. data/js/foundation/locale_settings/locale_settings.js +131 -0
  66. data/js/foundation/system/system.js +40 -13
  67. data/js/foundation/thememanager/thememanager.js +21 -0
  68. data/js/foundation/view/markupview/markupview.js +12 -12
  69. data/js/foundation/view/view.js +221 -27
  70. data/js/graphics/svgcontrol/svgcontrol.js +400 -0
  71. data/js/lists/checkboxlist/checkboxlist.js +18 -7
  72. data/js/lists/listitems/listitems.js +52 -38
  73. data/js/lists/radiobuttonlist/radiobuttonlist.js +23 -7
  74. data/js/menus/menuitem/menuitem.js +5 -0
  75. data/js/menus/menuitem/themes/default/menuitem.css +45 -0
  76. data/js/menus/menuitem/themes/default/menuitem.html +4 -0
  77. data/js/menus/minimenu/minimenu.js +47 -16
  78. data/js/menus/minimenuitem/minimenuitem.js +62 -0
  79. data/js/menus/{minimenu/minimenuitem → minimenuitem}/themes/default/minimenuitem.css +2 -2
  80. data/js/menus/{minimenu/minimenuitem → minimenuitem}/themes/default/minimenuitem.html +0 -0
  81. data/js/menus/{minimenu/minimenuitem → minimenuitem}/themes/default/minimenuitem_checkmark.png +0 -0
  82. data/js/menus/popupmenu/popupmenu.js +14 -0
  83. data/js/menus/popupmenu/themes/default/popupmenu.css +65 -0
  84. data/js/menus/popupmenu/themes/default/popupmenu.html +7 -0
  85. data/js/menus/popupmenu/themes/default/popupmenu.png +0 -0
  86. data/js/no_pkg/no_pkg.js +2 -0
  87. data/js/util/reloadapp/reloadapp.js +1 -1
  88. data/js/views/scrollview/scrollview.js +6 -0
  89. data/lib/rsence.rb +136 -3
  90. data/lib/rsence/argv.rb +218 -0
  91. data/lib/rsence/argv/argv_util.rb +58 -0
  92. data/lib/rsence/argv/env_check.rb +58 -0
  93. data/lib/rsence/argv/help_argv.rb +15 -0
  94. data/lib/rsence/argv/initenv_argv.rb +218 -0
  95. data/lib/rsence/argv/save_argv.rb +92 -0
  96. data/lib/rsence/argv/startup_argv.rb +118 -0
  97. data/lib/rsence/argv/status_argv.rb +132 -0
  98. data/lib/rsence/argv/test_port.rb +32 -0
  99. data/lib/{daemon → rsence}/daemon.rb +67 -23
  100. data/lib/{conf/default.rb → rsence/default_config.rb} +18 -10
  101. data/lib/{plugins → rsence}/dependencies.rb +0 -0
  102. data/lib/{util → rsence}/gzstring.rb +0 -0
  103. data/lib/rsence/http.rb +3 -0
  104. data/lib/{http → rsence/http}/broker.rb +106 -19
  105. data/lib/{http → rsence/http}/rackup.rb +0 -0
  106. data/lib/{http → rsence/http}/request.rb +0 -4
  107. data/lib/{http → rsence/http}/response.rb +0 -1
  108. data/lib/{session → rsence}/msg.rb +17 -1
  109. data/lib/{plugins → rsence}/pluginmanager.rb +29 -12
  110. data/lib/{plugins → rsence}/plugins.rb +7 -7
  111. data/lib/{plugins → rsence/plugins}/gui_plugin.rb +8 -3
  112. data/lib/{plugins → rsence/plugins}/guiparser.rb +9 -6
  113. data/lib/{plugins → rsence/plugins}/plugin.rb +23 -4
  114. data/lib/{plugins → rsence/plugins}/plugin_base.rb +11 -1
  115. data/lib/{plugins → rsence/plugins}/plugin_plugins.rb +2 -2
  116. data/lib/{plugins → rsence/plugins}/plugin_sqlite_db.rb +0 -0
  117. data/lib/{plugins → rsence/plugins}/servlet.rb +0 -0
  118. data/lib/{session → rsence}/sessionmanager.rb +101 -39
  119. data/lib/{session → rsence}/sessionstorage.rb +30 -16
  120. data/lib/{daemon → rsence}/sigcomm.rb +0 -0
  121. data/lib/{transporter → rsence}/transporter.rb +13 -11
  122. data/lib/{values/hvalue.rb → rsence/value.rb} +6 -1
  123. data/lib/{values → rsence}/valuemanager.rb +1 -1
  124. data/plugins/client_pkg/client_pkg.rb +14 -4
  125. data/plugins/client_pkg/info.yaml +2 -2
  126. data/plugins/client_pkg/lib/client_pkg_build.rb +145 -45
  127. data/plugins/client_pkg/lib/client_pkg_cache.rb +1 -1
  128. data/plugins/client_pkg/lib/client_pkg_serve.rb +1 -1
  129. data/plugins/main/main.rb +43 -3
  130. data/plugins/main/tmpl/index.html +2 -10
  131. data/plugins/main/values.yaml +3 -1
  132. data/plugins/ticket/lib/common.rb +6 -3
  133. data/plugins/ticket/ticket.rb +11 -3
  134. metadata +144 -174
  135. data/js/comm/autosync/js.inc +0 -0
  136. data/js/comm/js.inc +0 -0
  137. data/js/comm/jsloader/js.inc +0 -0
  138. data/js/comm/queue/js.inc +0 -0
  139. data/js/comm/session/js.inc +0 -0
  140. data/js/comm/sessionwatcher/js.inc +0 -0
  141. data/js/comm/transporter/js.inc +0 -0
  142. data/js/comm/urlresponder/js.inc +0 -0
  143. data/js/comm/values/js.inc +0 -0
  144. data/js/controls/button/js.inc +0 -0
  145. data/js/controls/checkbox/js.inc +0 -0
  146. data/js/controls/dialogs/alert_sheet/js.inc +0 -0
  147. data/js/controls/dialogs/confirm_sheet/js.inc +0 -0
  148. data/js/controls/dialogs/sheet/js.inc +0 -0
  149. data/js/controls/imageview/js.inc +0 -0
  150. data/js/controls/passwordcontrol/js.inc +0 -0
  151. data/js/controls/progress/progressbar/js.inc +0 -0
  152. data/js/controls/progress/progressindicator/js.inc +0 -0
  153. data/js/controls/radiobutton/js.inc +0 -0
  154. data/js/controls/sliders/slider/js.inc +0 -0
  155. data/js/controls/sliders/vslider/js.inc +0 -0
  156. data/js/controls/stepper/js.inc +0 -0
  157. data/js/controls/stringview/js.inc +0 -0
  158. data/js/controls/tab/js.inc +0 -0
  159. data/js/controls/textarea/js.inc +0 -0
  160. data/js/controls/textcontrol/js.inc +0 -0
  161. data/js/controls/uploader/js.inc +0 -0
  162. data/js/controls/uploader/uploader.js +0 -154
  163. data/js/controls/validatorview/js.inc +0 -0
  164. data/js/controls/window/js.inc +0 -0
  165. data/js/core/class/js.inc +0 -0
  166. data/js/core/elem/elem.js +0 -1325
  167. data/js/core/elem/js.inc +0 -0
  168. data/js/core/event/js.inc +0 -0
  169. data/js/core/iefix/js.inc +0 -0
  170. data/js/core/rsence_ns/js.inc +0 -0
  171. data/js/datetime/calendar/calendar.js +0 -198
  172. data/js/datetime/calendar/js.inc +0 -0
  173. data/js/datetime/datetimevalue/js.inc +0 -0
  174. data/js/datetime/timesheet/js.inc +0 -0
  175. data/js/datetime/timesheet/old_timesheet.js +0 -292
  176. data/js/datetime/timesheet/themes/default/old_timesheet.css +0 -30
  177. data/js/datetime/timesheet/themes/default/old_timesheet.html +0 -2
  178. data/js/datetime/timesheet_item/js.inc +0 -0
  179. data/js/datetime/timesheet_item/old_timesheet_item.js +0 -308
  180. data/js/datetime/timesheet_item/themes/default/old_timesheet_item.css +0 -42
  181. data/js/datetime/timesheet_item/themes/default/old_timesheet_item.html +0 -8
  182. data/js/datetime/timesheet_item_edit/js.inc +0 -0
  183. data/js/datetime/timesheet_item_edit/old_timesheet_item_edit.js +0 -274
  184. data/js/foundation/application/js.inc +0 -0
  185. data/js/foundation/control/controldefaults/js.inc +0 -0
  186. data/js/foundation/control/dummyvalue/js.inc +0 -0
  187. data/js/foundation/control/dyncontrol/js.inc +0 -0
  188. data/js/foundation/control/eventresponder/js.inc +0 -0
  189. data/js/foundation/control/js.inc +0 -0
  190. data/js/foundation/control/valuematrix/js.inc +0 -0
  191. data/js/foundation/control/valueresponder/js.inc +0 -0
  192. data/js/foundation/eventmanager/js.inc +0 -0
  193. data/js/foundation/geom/point/js.inc +0 -0
  194. data/js/foundation/geom/rect/js.inc +0 -0
  195. data/js/foundation/json_renderer/js.inc +0 -0
  196. data/js/foundation/system/js.inc +0 -0
  197. data/js/foundation/thememanager/js.inc +0 -0
  198. data/js/foundation/value/js.inc +0 -0
  199. data/js/foundation/view/js.inc +0 -0
  200. data/js/foundation/view/markupview/js.inc +0 -0
  201. data/js/foundation/view/morphanimation/js.inc +0 -0
  202. data/js/foundation/view/viewdefaults/js.inc +0 -0
  203. data/js/lists/checkboxlist/js.inc +0 -0
  204. data/js/lists/listitems/js.inc +0 -0
  205. data/js/lists/propertylist/js.inc +0 -0
  206. data/js/lists/propertylist/propertylisteditor/js.inc +0 -0
  207. data/js/lists/radiobuttonlist/js.inc +0 -0
  208. data/js/menus/minimenu/js.inc +0 -0
  209. data/js/menus/minimenu/minimenuitem/js.inc +0 -0
  210. data/js/menus/minimenu/minimenuitem/minimenuitem.js +0 -33
  211. data/js/util/reloadapp/js.inc +0 -0
  212. data/js/util/sha/js.inc +0 -0
  213. data/js/views/centerview/js.inc +0 -0
  214. data/js/views/inlineview/js.inc +0 -0
  215. data/js/views/scrollview/js.inc +0 -0
  216. data/lib/conf/argv.rb +0 -880
@@ -1,3 +1,14 @@
1
+ <div class="calendar_bg">
2
+ <div class="calendar_nw"></div>
3
+ <div class="calendar_n"></div>
4
+ <div class="calendar_ne"></div>
5
+ <div class="calendar_w"></div>
6
+ <div class="calendar_c"></div>
7
+ <div class="calendar_e"></div>
8
+ <div class="calendar_sw"></div>
9
+ <div class="calendar_s"></div>
10
+ <div class="calendar_se"></div>
11
+ </div>
1
12
  <div class="calendar_control" id="control#{_ID}">
2
13
  <div class="calendar_head">
3
14
  <div class="calendar_head_prev_month" onclick="HSystem.views[#{this.viewId}].prevMonth();"></div>
@@ -0,0 +1,217 @@
1
+ /* RSence
2
+ * Copyright 2010 Riassence Inc.
3
+ * http://riassence.com/
4
+ *
5
+ * You should have received a copy of the GNU General Public License along
6
+ * with this software package. If not, contact licensing@riassence.com
7
+ */
8
+
9
+ var
10
+ HDateTimePicker = HControl.extend({
11
+ setLocked: function(_lockedState){
12
+ var
13
+ _enabledState = !_lockedState;
14
+ this.yyyy.setEnabled(_enabledState);
15
+ this.mm.setEnabled(_enabledState);
16
+ this.dd.setEnabled(_enabledState);
17
+ this.h.setEnabled(_enabledState);
18
+ this.m.setEnabled(_enabledState);
19
+ },
20
+ refreshValue: function(_readMode){
21
+ if(!this.drawn){
22
+ return;
23
+ }
24
+ if(!this.m){
25
+ return;
26
+ }
27
+ this.setLocked( HVM.values[ this.options.lockedId ].value );
28
+ var
29
+ date = new Date( this.value*1000 ),
30
+ yyyy = date.getUTCFullYear(),
31
+ mm = date.getUTCMonth()+1,
32
+ dd = date.getUTCDate(),
33
+ h = date.getUTCHours(),
34
+ m = date.getUTCMinutes();
35
+ if(_readMode){
36
+ var
37
+ nuDate = new Date( this.value*1000 ),
38
+ doSet = false;
39
+ if(this.yyyyValue.value !== yyyy){
40
+ nuDate.setUTCFullYear( this.yyyyValue.value ); doSet = true;
41
+ }
42
+ if(this.mmValue.value !== mm){
43
+ nuDate.setUTCMonth( this.mmValue.value-1 ); doSet = true;
44
+ }
45
+ if(this.ddValue.value !== dd){
46
+ nuDate.setUTCDate( this.ddValue.value ); doSet = true;
47
+ }
48
+ if(this.hValue.value !== h){
49
+ nuDate.setUTCHours( this.hValue.value ); doSet = true;
50
+ }
51
+ if(this.mValue.value !== m){
52
+ nuDate.setUTCMinutes( this.mValue.value ); doSet = true;
53
+ }
54
+ if(doSet){
55
+ this.setValue( nuDate.getTime()/1000 );
56
+ this.setMonthMax( nuDate );
57
+ }
58
+ }
59
+ else{
60
+ this.yyyyValue.set( yyyy );
61
+ this.mmValue.set( mm );
62
+ this.ddValue.set( dd );
63
+ this.hValue.set( h );
64
+ this.mValue.set( m );
65
+ }
66
+ },
67
+ setMonthMax: function( nuDate ){
68
+ nuDate.setUTCSeconds( 0 );
69
+ nuDate.setUTCMinutes( 0 );
70
+ nuDate.setUTCHours( 0 );
71
+ nuDate.setUTCDate( 1 );
72
+ var
73
+ mm = nuDate.getUTCMonth();
74
+ if(mm === 11){
75
+ nuDate.setUTCMonth( 0 );
76
+ nuDate.setUTCFullYear( nuDate.getUTCFullYear()+1 );
77
+ }
78
+ else {
79
+ nuDate.setUTCMonth( mm+1 );
80
+ }
81
+ var
82
+ ms = nuDate.getTime() - 1000,
83
+ maxDaysDate = new Date(ms),
84
+ maxDays = maxDaysDate.getUTCDate();
85
+ // console.log('maxDaysDate:',maxDaysDate.getUTCFullYear(),'-',maxDaysDate.getUTCMonth(),'-',maxDaysDate.getUTCDate());
86
+ // console.log('maxDays:',maxDays);
87
+ if(maxDays !== this.dd.numField.options.maxValue){
88
+ // console.log('reset maxValue..');
89
+ this.dd.numField.options.maxValue = maxDays;
90
+ (this.dd.numField.options.maxValue < this.dd.numField.value) && this.ddValue.set( maxDays );
91
+ this.dd.stepper.options.maxValue = maxDays;
92
+ }
93
+ },
94
+ die: function(){
95
+ this.yyyyValue.die();
96
+ this.yyyy.die();
97
+ this.mmValue.die();
98
+ this.mm.die();
99
+ this.ddValue.die();
100
+ this.dd.die();
101
+ this.hValue.die();
102
+ this.h.die();
103
+ this.mValue.die();
104
+ this.m.die();
105
+ this.base();
106
+ },
107
+ drawSubviews: function(){
108
+ ELEM.setStyle( this.elemId, 'overflow', 'visible' );
109
+ var
110
+ _NumStepperField = HView.extend({
111
+ setEnabled: function(_state){
112
+ this.numField.setEnabled(_state);
113
+ this.stepper.setEnabled(_state);
114
+ if(_state){
115
+ this.stepper.show();
116
+ }
117
+ else{
118
+ this.stepper.hide();
119
+ }
120
+ },
121
+ drawSubviews: function(){
122
+ ELEM.setStyle( this.elemId, 'overflow', 'visible' );
123
+ this.numField = HNumericTextControl.extend({
124
+ refreshValue: function(){
125
+ this.base();
126
+ this.parent.parent.refreshValue(true);
127
+ },
128
+ textBlur: function(){
129
+ this.setValue(
130
+ this.validateText(
131
+ this.getTextFieldValue()
132
+ )
133
+ );
134
+ }
135
+ }).nu(
136
+ [ 0, 0, this.rect.width-10, 21 ],
137
+ this, {
138
+ events: {
139
+ textEnter: false
140
+ },
141
+ minValue: this.options.minValue,
142
+ maxValue: this.options.maxValue,
143
+ valueObj: this.options.valueObj
144
+ }
145
+ );
146
+ this.stepper = HStepper.nu(
147
+ [ this.rect.width-15, 0, 15, 21 ],
148
+ this, {
149
+ minValue: this.options.minValue,
150
+ maxValue: this.options.maxValue,
151
+ valueObj: this.options.valueObj
152
+ }
153
+ );
154
+ }
155
+ }),
156
+ _numStepperRect = HRect.nu( 0, 0, 50, 21 );
157
+ this.yyyyValue = HValue.nu( false, 2010 );
158
+ this.yyyy = _NumStepperField.nu(
159
+ HRect.nu( _numStepperRect ),
160
+ this, {
161
+ minValue: 2010,
162
+ maxValue: 2020,
163
+ valueObj: this.yyyyValue
164
+ }
165
+ );
166
+ _numStepperRect.setWidth( 35 );
167
+ _numStepperRect.offsetBy( 55, 0 );
168
+ this.mmValue = HValue.nu( false, 12 );
169
+ this.mm = _NumStepperField.nu(
170
+ HRect.nu( _numStepperRect ),
171
+ this, {
172
+ minValue: 1,
173
+ maxValue: 12,
174
+ valueObj: this.mmValue
175
+ }
176
+ );
177
+ _numStepperRect.offsetBy( 40, 0 );
178
+ this.ddValue = HValue.nu( false, 24 );
179
+ this.dd = _NumStepperField.nu(
180
+ HRect.nu( _numStepperRect ),
181
+ this, {
182
+ minValue: 1,
183
+ maxValue: 31,
184
+ valueObj: this.ddValue
185
+ }
186
+ );
187
+ _numStepperRect.offsetBy( 50, 0 );
188
+ this.hValue = HValue.nu( false, 22 );
189
+ this.h = _NumStepperField.nu(
190
+ HRect.nu( _numStepperRect ),
191
+ this, {
192
+ minValue: 0,
193
+ maxValue: 23,
194
+ valueObj: this.hValue
195
+ }
196
+ );
197
+ _numStepperRect.offsetBy( 40, 0 );
198
+ this.mValue = HValue.nu( false, 45 );
199
+ this.m = _NumStepperField.nu(
200
+ HRect.nu( _numStepperRect ),
201
+ this, {
202
+ minValue: 0,
203
+ maxValue: 59,
204
+ valueObj: this.mValue
205
+ }
206
+ );
207
+ _numStepperRect.offsetBy( 37, 2 );
208
+ _numStepperRect.setWidth( 60 );
209
+ HStringView.nu(
210
+ HRect.nu( _numStepperRect ),
211
+ this, {
212
+ valueObj: HVM.values[this.options.tzValueId]
213
+ }
214
+ );
215
+ this.refreshValue();
216
+ }
217
+ });
@@ -16,10 +16,6 @@ HDateTime = HClass.extend({
16
16
  msDay: 86400000,
17
17
  msHour: 3600000,
18
18
  msMinute: 60000,
19
- months_localized: [
20
- 'January', 'February', 'March', 'April', 'May', 'June', 'July',
21
- 'August', 'September', 'October', 'November', 'December'
22
- ],
23
19
 
24
20
  /** = Description
25
21
  * Returns month name of the given date.
@@ -33,7 +29,7 @@ HDateTime = HClass.extend({
33
29
  **/
34
30
  monthName: function(_date){
35
31
  _date = (_date instanceof Date)?_date:this.date();
36
- return this.months_localized[_date.getUTCMonth()];
32
+ return HLocale.dateTime.strings.monthsLong[_date.getUTCMonth()];
37
33
  },
38
34
 
39
35
  /** = Description
@@ -77,6 +73,13 @@ HDateTime = HClass.extend({
77
73
  return _date.getUTCDate();
78
74
  },
79
75
 
76
+ setMday: function( _mday ){
77
+ var
78
+ _date = this.date();
79
+ _date.setUTCDate( _mday );
80
+ return Math.round( _date.getTime()/1000 );
81
+ },
82
+
80
83
  /** = Description
81
84
  * Returns month number for the date given as input.
82
85
  * Note that months are numbered from 0 to 11.
@@ -93,6 +96,13 @@ HDateTime = HClass.extend({
93
96
  return _date.getUTCMonth();
94
97
  },
95
98
 
99
+ setMonth: function( _month ){
100
+ var
101
+ _date = this.date();
102
+ _date.setUTCMonth( _month );
103
+ return Math.round( _date.getTime()/1000 );
104
+ },
105
+
96
106
  /** = Description
97
107
  * Returns year for given date.
98
108
  *
@@ -108,6 +118,13 @@ HDateTime = HClass.extend({
108
118
  return _date.getUTCFullYear();
109
119
  },
110
120
 
121
+ setYear: function( _year ){
122
+ var
123
+ _date = this.date();
124
+ _date.setUTCFullYear( _year );
125
+ return Math.round( _date.getTime()/1000 );
126
+ },
127
+
111
128
  /** = Description
112
129
  * Returns the timezone offset in milliseconds.
113
130
  *
@@ -1,30 +1,59 @@
1
- .timesheet_hours_col {
1
+ .timesheet_label,
2
+ .timesheet_items,
3
+ .timesheet_timeline,
4
+ .timesheet_timeline_hour,
5
+ .timesheet_timeline_line,
6
+ .timesheet_timeline_notch {
2
7
  position: absolute;
3
- top: 0px; left: 0px; width: 32px;
4
- font-family: Arial, sans-serif;
8
+ }
9
+
10
+ .timesheet_label,
11
+ .timesheet_items,
12
+ .timesheet_timeline {
13
+ font-family: Helvetica, Arial, sans-serif;
5
14
  font-size: 11px;
6
- color: #666;
15
+ color: #000;
7
16
  vertical-align: middle;
8
- text-align: right;
17
+ text-align: left;
9
18
  }
10
- .timesheet_hours_row {
11
- position: absolute;
12
- left: 0px; width: 32px;
19
+
20
+ .timesheet_label {
21
+ left: 33px; top: 0px; right: 0px; height: 16px;
22
+ font-size: 15px;
13
23
  }
14
- .timesheet_lines_col {
15
- position: absolute;
16
- top: 0px; left: 36px; right: 0px;
17
- border-left: 1px solid #aaa;
18
- border-bottom: 1px solid #aaa;
19
- border-right: 1px solid #aaa;
24
+
25
+ .timesheet_timeline {
26
+ left: 0px; top: 0px; right: 0px; bottom: 0px;
27
+ cursor: move;
20
28
  }
21
- .timesheet_lines_row0,
22
- .timesheet_lines_row1 {
23
- position: absolute;
24
- left: 0px; right: 0px; height: 1px;
25
- line-height: 1px; font-size: 0px;
26
- background-color: #ccc;
29
+
30
+ .timesheet_timeline_hour {
31
+ left: 0px; width: 26px; text-align: right; height: 14px;
32
+ }
33
+
34
+ .timesheet_timeline_line {
35
+ left: 31px; right: 3px; height: 1px; background-color: #bbb;
36
+ }
37
+ .timesheet_timeline_notch {
38
+ left: 31px; right: 3px; height: 1px; background-color: #ddd;
39
+ }
40
+
41
+ .timesheet_items {
42
+ left: 29px; top: 19px; right: 1px; bottom: 11px;
43
+ border: 1px solid #ccc;
44
+ border-radius: 3px;
45
+ }
46
+
47
+ .nohours .timesheet_timeline_notch,
48
+ .nohours .timesheet_timeline_line {
49
+ left: 2px;
50
+ }
51
+ .nohours .timesheet_timeline_hour {
52
+ display: none;
53
+ }
54
+ .nohours .timesheet_label {
55
+ left: 4px;
27
56
  }
28
- .timesheet_lines_row0 {
29
- background-color: #aaa;
57
+ .nohours .timesheet_items {
58
+ left: 0px;
30
59
  }
@@ -1,2 +1,4 @@
1
- <div class="timesheet_hours_col" id="label#{_ID}"></div>
2
- <div class="timesheet_lines_col" id="state#{_ID}"></div>
1
+ <div id="label#{_ID}" class="timesheet_label"></div>
2
+ <div id="timeline#{_ID}" class="timesheet_timeline"></div>
3
+ <div id="value#{_ID}" class="timesheet_items"></div>
4
+ ${this.themeSettings(30, 20, 0, 10, -6);}
@@ -6,149 +6,442 @@
6
6
  * with this software package. If not, contact licensing@riassence.com
7
7
  */
8
8
 
9
- /*** = Description
10
- ** HTimesheet is a simple timesheet control.
11
- ***/
9
+ HLocale.components.HTimeSheet = {
10
+ strings: {
11
+ newItemLabel: 'New item'
12
+ }
13
+ };
14
+
15
+
12
16
  var//RSence.DateTime
13
17
  HTimeSheet = HControl.extend({
14
18
 
15
- componentName: 'timesheet',
19
+ debug: false,
16
20
 
17
- /* Default amount of pixels per hour. Override with options when necessary. */
18
- pxPerHour: 24,
19
-
20
- /* Default amount of pixels from left. Override with options when necessary. */
21
- itemOffsetLeft: 36,
22
-
23
- /* Default amount of pixels from right. Override with options when necessary. */
24
- itemOffsetRight: 0,
21
+ localeStrings: HLocale.components.HTimeSheet.strings,
22
+ componentName: 'timesheet',
23
+ markupElemNames: [
24
+ 'label', 'value', 'timeline'
25
+ ],
25
26
 
26
27
  defaultEvents: {
27
- draggable: true
28
+ draggable: true,
29
+ click: true,
30
+ doubleClick: true,
31
+ resize: true
28
32
  },
29
33
 
30
34
  controlDefaults: HControlDefaults.extend({
31
- pxPerHour: null,
32
- itemOffsetLeft: null,
33
- itemOffsetRight: null,
34
- itemMinHeight: 24,
35
- newItemLabel: 'New Item',
36
- constructor: function(_ctrl){
37
- if(this.pxPerHour !== null){
38
- _ctrl.pxPerHour = this.pxPerHour;
35
+ timeStart: 0, // 1970-01-01 00:00:00
36
+ timeEnd: 86399, // 1970-01-01 23:59:59
37
+ tzOffset: 0, // timezone offset in seconds; eg: 7200 => UTC+2
38
+ itemMinHeight: 16,
39
+ hideHours: false,
40
+ autoLabel: false, // automatically set the label to the date
41
+ autoLabelFn: 'formatDate',
42
+ autoLabelFnOptions: { longWeekDay: true },
43
+ notchesPerHour: 4, // by default 1/4 of an hour precision (15 minutes)
44
+ itemOffsetLeft: 64, // Theme settings; don't enter in options
45
+ itemOffsetRight: 0, // Theme settings; don't enter in options
46
+ itemOffsetTop: 20, // Theme settings; don't enter in options
47
+ itemOffsetBottom: 0, // Theme settings; don't enter in options
48
+ itemDisplayTime: true,
49
+ allowClickCreate: false,
50
+ allowDoubleClickCreate: true,
51
+ minDragSize: 5, // minimum amount of pixels dragged required for accepting a drag
52
+ hourOffsetTop: -4, // Theme settings; don't enter in options
53
+ constructor: function( _ctrl ){
54
+ if( this.defaultLabel === undefined ){
55
+ this.defaultLabel = _ctrl.localeStrings.newItemLabel;
39
56
  }
40
- if(this.itemOffsetLeft !== null){
41
- _ctrl.itemOffsetLeft = this.itemOffsetLeft;
57
+ }
58
+ }),
59
+
60
+ themeSettings: function( _itemOffsetLeft, _itemOffsetTop, _itemOffsetRight, _itemOffsetBottom, _hourOffsetTop ){
61
+ if( this.options.hideHours ){
62
+ ELEM.addClassName( this.elemId, 'nohours' );
63
+ this.options.itemOffsetLeft = 0;
64
+ }
65
+ else if( _itemOffsetLeft !== undefined ) {
66
+ this.options.itemOffsetLeft = _itemOffsetLeft;
67
+ }
68
+ if( _itemOffsetTop !== undefined ) {
69
+ this.options.itemOffsetTop = _itemOffsetTop;
70
+ }
71
+ if( _itemOffsetRight !== undefined ) {
72
+ this.options.itemOffsetRight = _itemOffsetRight;
73
+ }
74
+ if( _itemOffsetBottom !== undefined ) {
75
+ this.options.itemOffsetBottom = _itemOffsetBottom;
76
+ }
77
+ if( _hourOffsetTop !== undefined ) {
78
+ this.options.hourOffsetTop = _hourOffsetTop;
79
+ }
80
+ },
81
+
82
+ autoLabel: function(){
83
+ var
84
+ _locale = HLocale.dateTime,
85
+ _label = _locale[this.options.autoLabelFn]( this.options.timeStart, this.options.autoLabelFnOptions );
86
+ if( this.label !== _label ){
87
+ this.label = _label;
88
+ this.refreshLabel();
89
+ }
90
+ },
91
+
92
+ clearHours: function(){
93
+ for( var i = 0; i < this.hourItems.length; i++ ){
94
+ ELEM.del( this.hourItems[i] );
95
+ }
96
+ },
97
+
98
+ drawHours: function(){
99
+
100
+ var
101
+ _parentElemId = this.markupElemIds.timeline,
102
+ _dateStart = new Date( this.options.timeStart * 1000 ),
103
+ _dateEnd = new Date( this.options.timeEnd * 1000 ),
104
+ _hourStart = _dateStart.getUTCHours(),
105
+ _hourEnd = _dateEnd.getUTCHours(),
106
+ _hours = (_hourEnd - _hourStart) + 1,
107
+ _rectHeight = ELEM.getSize( _parentElemId )[1],
108
+ _topOffset = this.options.itemOffsetTop,
109
+ _height = _rectHeight - _topOffset - this.options.itemOffsetBottom,
110
+ _pxPerHour = _height / _hours,
111
+ _notchesPerHour = this.options.notchesPerHour,
112
+ _pxPerLine = _pxPerHour / _notchesPerHour,
113
+ _hour = _hourStart,
114
+ _bottomPos = _rectHeight-_height-_topOffset-2,
115
+ _hourItem,
116
+ _lineItem,
117
+ _hourTop,
118
+ _lineTop,
119
+ i,
120
+ _pxPerNotch = _pxPerHour / _notchesPerHour,
121
+ _notchItem,
122
+ _notchTop;
123
+
124
+ ELEM.setStyle( _parentElemId, 'visibility', 'hidden', true );
125
+
126
+ ELEM.setStyle( this.markupElemIds.value, 'bottom', _bottomPos+'px' );
127
+
128
+ if( this['hourItems'] !== undefined ){
129
+ this.clearHours();
130
+ }
131
+ this.itemOptions = {
132
+ notchHeight: _pxPerNotch,
133
+ notches: (_hours * _notchesPerHour),
134
+ offsetTop: _topOffset,
135
+ offsetBottom: _bottomPos,
136
+ height: _height
137
+ };
138
+ this.hourItems = [];
139
+ for( ; _hour < (_hourEnd+1); _hour++ ){
140
+ _hourItem = ELEM.make( this.markupElemIds.timeline, 'div' );
141
+ _lineTop = Math.round(_topOffset + (_hour*_pxPerHour));
142
+ if( _hour !== _hourStart ){
143
+ _hourTop = _lineTop + this.options.hourOffsetTop;
144
+ ELEM.setStyle( _hourItem, 'top', _hourTop+'px' );
145
+ ELEM.addClassName( _hourItem, 'timesheet_timeline_hour' );
146
+ ELEM.setHTML( _hourItem, _hour+':00' );
147
+ this.hourItems.push( _hourItem );
148
+ _lineItem = ELEM.make( _parentElemId, 'div' );
149
+ ELEM.addClassName( _lineItem, 'timesheet_timeline_line' );
150
+ ELEM.setStyle( _lineItem, 'top', _lineTop + 'px' );
151
+ this.hourItems.push( _lineItem );
42
152
  }
43
- if(this.itemOffsetRight !== null){
44
- _ctrl.itemOffsetRight = this.itemOffsetRight;
153
+ for( i=1; i < _notchesPerHour; i++ ){
154
+ _notchItem = ELEM.make( _parentElemId, 'div' );
155
+ ELEM.addClassName( _notchItem, 'timesheet_timeline_notch' );
156
+ _notchTop = Math.round(_lineTop + (_pxPerNotch*i));
157
+ ELEM.setStyle( _notchItem, 'top', _notchTop+'px' );
158
+ this.hourItems.push( _notchItem );
45
159
  }
46
160
  }
47
- }),
161
+
162
+ ELEM.setStyle( this.markupElemIds.timeline, 'visibility', 'inherit' );
163
+
164
+ },
48
165
 
49
- /** = Description
50
- * Redraws the timesheet.
51
- *
52
- **/
166
+ // extra hook for refreshing; updates label and hours before doing common things
53
167
  refresh: function(){
54
- if(this.drawn){
55
- var _areaHeight = this.rect.height;
56
- this.pxPerHour = (_areaHeight-(_areaHeight%48)) / 24;
57
- if(this.options['hideLabel']){
58
- this.setStyleOfPart( 'label', 'display', 'none' );
59
- this.setStyleOfPart( 'state', 'left', '0px' );
60
- this.itemOffsetLeft = 0;
168
+ if( this.drawn ){
169
+ if( this.options.autoLabel ){
170
+ this.autoLabel();
61
171
  }
62
- else{
63
- this.setStyleOfPart( 'label', 'height', (this.pxPerHour*24)+'px' );
64
- }
65
- this.setStyleOfPart( 'state', 'height', (this.pxPerHour*24)+'px' );
172
+ this.drawHours();
66
173
  }
67
174
  this.base();
68
175
  },
69
176
 
70
- /** = Description
71
- * Refreshes the hour labels.
72
- *
73
- **/
74
- refreshLabel: function(){
75
- var hour = 1,
76
- hours = [],
77
- rowHeight = this.pxPerHour;
78
- lineHeight = Math.round(this.pxPerHour/2);
79
- for(; hour < 24; hour++){
80
- hours.push('<div style="line-height:'+rowHeight+'px;height:'+rowHeight+'px;top:'+Math.round((hour*rowHeight)-lineHeight)+'px" class="timesheet_hours_row">'+hour+':00</div>');
177
+ // set the timezone offset (in seconds)
178
+ setTzOffset: function( _tzOffset ){
179
+ this.options.tzOffset = _tzOffset;
180
+ this.refresh();
181
+ },
182
+
183
+ // set the start timestamp of the timesheet
184
+ setTimeStart: function( _timeStart ){
185
+ this.options.timeStart = _timeStart;
186
+ this.refresh();
187
+ },
188
+
189
+ // set the end timestamp of the timesheet
190
+ setTimeEnd: function( _timeEnd ){
191
+ this.options.timeEnd = _timeEnd;
192
+ this.refresh();
193
+ },
194
+
195
+ // sets the range of timestams of the timesheet
196
+ setTimeRange: function( _timeRange ){
197
+ if( (_timeRange instanceof Array) && (_timeRange.length === 2) ){
198
+ this.setTimeStart( _timeRange[0] );
199
+ this.setTimeEnd( _timeRange[1] );
200
+ }
201
+ else if(
202
+ (_timeRange instanceof Object) &&
203
+ (_timeRange['timeStart'] !== undefined) &&
204
+ (_timeRange['timeEnd'] !== undefined)
205
+ ){
206
+ this.setTimeStart( _timeRange.timeStart );
207
+ this.setTimeEnd( _timeRange.timeEnd );
81
208
  }
82
- this.markupElemIds && this.markupElemIds.label && ELEM.setHTML(this.markupElemIds.label,hours.join(''));
83
- this.refreshState();
84
209
  },
85
210
 
86
- /** = Description
87
- * Refreshes the lines which mark hours and half-hours.
88
- *
89
- **/
90
- refreshState: function(){
91
- var line = 0,
92
- lines = [],
93
- lineHeight = Math.round(this.pxPerHour/2);
94
- for(; line < 48; line++){
95
- lines.push('<div style="top:'+(line*lineHeight)+'px" class="timesheet_lines_row'+(line%2)+'"></div>');
211
+ // sets the timestamp of the timesheet
212
+ setDate: function( _date ){
213
+ var
214
+ _range = (this.options.timeEnd - this.options.timeStart),
215
+ _newTimeRange = [
216
+ _date,
217
+ _date + _range
218
+ ];
219
+ this.setTimeRange( _newTimeRange );
220
+ this.refresh();
221
+ },
222
+
223
+ // draw decorations
224
+ drawSubviews: function(){
225
+ this.drawHours();
226
+ var
227
+ _options = this.options,
228
+ _minDuration = Math.round(3600/_options.notchesPerHour),
229
+ _dummyValue = {
230
+ // id: 'new',
231
+ label: '',
232
+ start: 0,
233
+ duration: _minDuration,
234
+ // locked: false,
235
+ color: '#cc0000'
236
+ };
237
+ this.dragPreviewRect = this.rectFromValue({start:_options.timeStart,duration:_minDuration});
238
+ this.minDuration = _minDuration;
239
+ this.dragPreview = HTimeSheetItem.nu(
240
+ this.dragPreviewRect,
241
+ this, {
242
+ value: _dummyValue,
243
+ visible: false,
244
+ displayTime: this.options.itemDisplayTime
245
+ }
246
+ );
247
+ this.dragPreview.setStyleOfPart('state','color','#fff');
248
+ },
249
+
250
+ // event listener for clicks, simulates double clicks in case of not double click aware browser
251
+ click: function( x, y, b ){
252
+ var
253
+ prevClickTime = false,
254
+ notCreated = !this.clickCreated && !this.doubleClickCreated && !this.dragCreated;
255
+ // this.doubleClickSimCreated = false;
256
+ if( !this.startDragTime && this.prevClickTime ){
257
+ prevClickTime = this.prevClickTime;
258
+ }
259
+ else if (this.startDragTime){
260
+ prevClickTime = this.startDragTime;
261
+ }
262
+ if( notCreated && this.options.allowClickCreate ){
263
+ // console.log('click create');
264
+ this.clickCreate( x,y );
265
+ this.clickCreated = true;
266
+ this.doubleClickCreated = false;
267
+ this.prevClickTime = false;
268
+ }
269
+ else if( notCreated && this.options.allowDoubleClickCreate ){
270
+ var
271
+ currTime = new Date().getTime(),
272
+ timeDiff = prevClickTime?(currTime - prevClickTime):-1;
273
+ if( timeDiff > 150 && timeDiff < 500 && !this.doubleClickCreated ){
274
+ // console.log('click double create');
275
+ this.clickCreate( x, y );
276
+ this.clickCreated = false;
277
+ this.doubleClickCreated = true;
278
+ this.doubleClickSimCreated = true;
279
+ }
280
+ else {
281
+ this.doubleClickCreated = false;
282
+ }
283
+ this.prevClickTime = currTime;
284
+ }
285
+ else {
286
+ this.clickCreated = false;
287
+ this.doubleClickCreated = false;
288
+ this.prevClickTime = false;
96
289
  }
97
- this.markupElemIds && this.markupElemIds.label && ELEM.setHTML(this.markupElemIds.state,lines.join(''));
98
290
  },
99
- dragItem: false,
100
291
 
101
- /** = Description
102
- * Creates an item into timesheet with default label 'New Item'.
103
- *
104
- * = Parameters
105
- * +origY+:: Y coordinate of the new item.
106
- *
107
- **/
108
- createItem: function(origY){
109
- var _lineHeight = Math.round(this.pxPerHour/2);
110
- origY = Math.floor( origY / _lineHeight )*_lineHeight;
111
- var maxY = _lineHeight*48,
112
- lineHeight = Math.round(this.pxPerHour/2);
113
- if(origY>maxY){
114
- origY = maxY;
292
+ // creates an item on click
293
+ clickCreate: function(x,y){
294
+ var
295
+ _startTime = this.pxToTime( y-this.pageY() ),
296
+ _endTime = _startTime + this.minDuration;
297
+ this.refreshDragPreview( _startTime, _endTime );
298
+ this.dragPreview.bringToFront();
299
+ this.dragPreview.show();
300
+ if( this.activateEditor( this.dragPreview ) ){
301
+ this.editor.createItem( HVM.clone( this.dragPreview.value ) );
302
+ }
303
+ else {
304
+ this.dragPreview.hide();
115
305
  }
116
- this.dragItem = this.createTimeSheetItem( { label: this.options.newItemLabel }, origY, lineHeight, 'create' );
117
306
  },
118
307
 
119
- /** = Description
120
- * Dragging is used to mark items on the timesheet.
121
- *
122
- * = Parameters
123
- * +x+:: x coordinate of the origin of drag
124
- * +y+:: y coordinate of the origin of drag
125
- *
126
- **/
127
- startDrag: function(x,y){
128
- this._startDragTime = new Date().getTime();
129
- this._startDragCoords = [x,y];
308
+ // event listener for double clicks
309
+ doubleClick: function(x,y){
310
+ this.prevClickTime = false;
311
+ this.doubleClickCreated = false;
312
+ var notCreated = !this.clickCreated && !this.doubleClickCreated && !this.doubleClickSimCreated && !this.dragCreated;
313
+ if( !this.options.allowDoubleClickCreate && this.options.allowClickCreate && notCreated ){
314
+ this.click(x,y);
315
+ }
316
+ else if ( this.options.allowDoubleClickCreate && !this.options.allowClickCreate && notCreated ){
317
+ // console.log('double click create');
318
+ this.clickCreate( x, y );
319
+ this.clickCreated = false;
320
+ this.doubleClickCreated = true;
321
+ }
322
+ else {
323
+ // console.log('no double click create');
324
+ this.clickCreated = false;
325
+ }
326
+ this.doubleClickSimCreated = false;
327
+ },
328
+
329
+ // update the preview area
330
+ refreshDragPreview: function( _startTime, _endTime ){
331
+ this.dragPreviewRect.setTop( this.timeToPx( _startTime, true ) );
332
+ this.dragPreviewRect.setBottom( this.timeToPx( _endTime, true ) );
333
+ if( this.dragPreviewRect.height < this.options.itemMinHeight ){
334
+ this.dragPreviewRect.setHeight( this.options.itemMinHeight );
335
+ }
336
+ this.dragPreview.drawRect();
337
+ this.dragPreview.value.start = _startTime;
338
+ this.dragPreview.value.duration = _endTime - _startTime;
339
+ this.dragPreview.refreshValue();
340
+ },
341
+
342
+ // drag & drop event listeners, used for dragging new timesheet items
343
+ startDrag: function( x, y, b ){
344
+ // console.log('st');
345
+ this._startDragY = y;
346
+ this.startDragTime = this.pxToTime( y-this.pageY() );
347
+ this.refreshDragPreview( this.startDragTime, this.startDragTime + this.minDuration );
348
+ this.dragPreview.bringToFront();
349
+ this.dragPreview.show();
130
350
  return true;
131
351
  },
132
352
 
133
- drag: function(x,y){
134
- if(((new Date().getTime()) - this._startDragTime) < 200){
135
- // only create when 200 ms has elapsed to enable clicking
136
- return true;
353
+ drag: function( x, y, b ){
354
+ // console.log('dr');
355
+ var
356
+ _dragTime = this.pxToTime( y-this.pageY() ),
357
+ _startTime,
358
+ _endTime;
359
+ if( _dragTime < this.startDragTime ){
360
+ _startTime = _dragTime;
361
+ _endTime = this.startDragTime;
137
362
  }
138
- if(this._startDragCoords[0]!==x && this._startDragCoords[1]!==y){
139
- this.createItem(this._startDragCoords[1]-this.pageY());
140
- EVENT.startDragging( this.dragItem );
141
- return true;
363
+ else {
364
+ _endTime = _dragTime;
365
+ _startTime = this.startDragTime;
142
366
  }
367
+ this.refreshDragPreview( _startTime, _endTime );
368
+ return true;
143
369
  },
144
370
 
145
- click: function(x,y){
146
- // deactivate all items
147
- EVENT.changeActiveControl(this);
148
- return true;
371
+ endDrag: function( x, y, b ){
372
+ // console.log('ed');
373
+ var
374
+ _dragTime = this.pxToTime( y-this.pageY() ),
375
+ _minDistanceSatisfied = Math.abs( this._startDragY - y ) >= this.options.minDragSize;
376
+ // if( this.options.allowClickCreate ){
377
+ // _minDistanceSatisfied = true;
378
+ // }
379
+ this.dragPreview.hide();
380
+ if( _dragTime !== this.startDragTime ){
381
+ if( _minDistanceSatisfied ){
382
+ if( this.activateEditor( this.dragPreview ) ){
383
+ // console.log('drag create');
384
+ this.dragCreated = true;
385
+ this.editor.createItem( HVM.clone( this.dragPreview.value ) );
386
+ return true;
387
+ }
388
+ }
389
+ this.dragCreated = false;
390
+ }
391
+ else {
392
+ this.dragCreated = false;
393
+ this.clickCreated = false;
394
+ this.startDragTime = false;
395
+ this.click( x, y, b );
396
+ return true;
397
+ }
398
+ return false;
149
399
  },
150
400
 
151
- listItemViews: false,
401
+ // a resize triggers refresh, of which the important part is refreshValue, which triggers redraw of the time sheet items
402
+ resize: function(){
403
+ this.base();
404
+ this.refresh();
405
+ },
406
+
407
+ // snaps the time to grid
408
+ snapTime: function( _timeSecs ){
409
+ var
410
+ _options = this.options,
411
+ _pxDate = new Date( Math.round(_timeSecs) * 1000 ),
412
+ _snapSecs = Math.round( 3600 / _options.notchesPerHour ),
413
+ _halfSnapSecs = _snapSecs * 0.5,
414
+ _hourSecs = (_pxDate.getUTCMinutes()*60) + _pxDate.getUTCSeconds(),
415
+ _remSecs = _hourSecs % _snapSecs;
416
+ if( _remSecs > _halfSnapSecs ){
417
+ _timeSecs += _snapSecs-_remSecs;
418
+ }
419
+ else {
420
+ _timeSecs -= _remSecs;
421
+ }
422
+ return _timeSecs;
423
+ },
424
+
425
+ // snaps the pixel to grid
426
+ snapPx: function( _px ){
427
+ var
428
+ _timeSecs = this.pxToTime( _px );
429
+ _timeSecs = this.snapTime( _timeSecs );
430
+ return this.timeToPx( _timeSecs );
431
+ },
432
+
433
+ // activates the editor; _item is the timesheet item to edit
434
+ activateEditor: function( _item ){
435
+ if( this['editor'] ){
436
+ var _editor = this.editor;
437
+ _editor.setTimeSheetItem( _item );
438
+ _item.bringToFront();
439
+ _editor.bringToFront();
440
+ _editor.show();
441
+ return true;
442
+ }
443
+ return false;
444
+ },
152
445
 
153
446
  /** = Description
154
447
  * Sets the editor given as parameter as the editor of instance.
@@ -160,133 +453,430 @@ HTimeSheet = HControl.extend({
160
453
  setEditor: function( _editor ){
161
454
  this.editor = _editor;
162
455
  },
163
-
164
- /** = Description
165
- * Returns HRect the size of given parameters and suitable for timesheet.
166
- *
167
- * = Parameters
168
- * +_origY+:: Y coordinate.
169
- * +_lineHeight+:: The height of item on time sheet.
170
- *
171
- **/
172
- createItemRect: function(_origY, _lineHeight){
173
- var _left = this.itemOffsetLeft+1,
174
- _top = _origY+1,
175
- _right = this.rect.width - this.itemOffsetRight - 1,
176
- _bottom = _origY + _lineHeight - 2;
177
- if( (_top - _bottom) < this.options.itemMinHeight ){
178
- _bottom = _top + this.options.itemMinHeight;
179
- }
180
- return HRect.nu( _left, _top, _right, _bottom );
181
- },
182
456
 
183
457
  /** = Description
184
458
  * Destructor; destroys the editor first and commences inherited die.
185
459
  *
186
460
  **/
187
461
  die: function(){
188
- this.editor.die();
462
+ this.editor && this.editor.die();
463
+ this.editor = null;
189
464
  this.base();
190
465
  },
191
466
 
192
- /** = Description
193
- * Returns a new time sheet item control. By default returns an instance
194
- * of HTimeSheetItem. Extend to use custom time sheet controls customized
195
- * for application specific purposes.
196
- *
197
- * = Parameters
198
- * *_value*:: A value object for the item.
199
- * *_top*:: Top position, 0 by default.
200
- * *_height*:: Height, same as half of +#pxPerHour+ by default.
201
- * *_drogMode*:: Dragging mode. 'normal' or 'create'. Is 'normal' by default.
202
- *
203
- **/
204
- createTimeSheetItem: function( _value, _top, _height, _dragMode ) {
205
- if(_dragMode===undefined){
206
- _dragMode = 'normal';
467
+ // converts pixels to time
468
+ pxToTime: function( _px, _noSnap ){
469
+ var
470
+ _options = this.options,
471
+ _timeStart = _options.timeStart,
472
+ _timeEnd = _options.timeEnd,
473
+ _timeRange = _timeEnd - _timeStart,
474
+ _itemOptions = this.itemOptions,
475
+ _top = _itemOptions.offsetTop+1,
476
+ _height = _itemOptions.height,
477
+ _pxPerSec = _height / _timeRange,
478
+ _timeSecs;
479
+ _px -= _top;
480
+ _timeSecs = _timeStart + ( _px / _pxPerSec );
481
+ if( !_noSnap ){
482
+ _timeSecs = this.snapTime( _timeSecs );
483
+ }
484
+ if( _timeSecs > _options.timeEnd ){
485
+ _timeSecs = _options.timeEnd;
486
+ }
487
+ else if ( _timeSecs < _options.timeStart ) {
488
+ _timeSecs = _options.timeStart;
489
+ }
490
+ return Math.round( _timeSecs );
491
+ },
492
+
493
+ // converts time to pixels
494
+ timeToPx: function( _time, _snap ){
495
+
496
+ if( _snap ){
497
+ _time = this.snapTime( _time );
498
+ }
499
+
500
+ var
501
+ _options = this.options,
502
+ _timeStart = _options.timeStart,
503
+ _timeEnd = _options.timeEnd;
504
+
505
+ if( _time < _timeStart ){
506
+ _time = _timeStart;
507
+ }
508
+ if( _time > _timeEnd ){
509
+ _time = _timeEnd;
510
+ }
511
+
512
+ var
513
+ _timeRange = _timeEnd - _timeStart,
514
+ _itemOptions = this.itemOptions,
515
+ _top = _itemOptions.offsetTop,
516
+ _height = _itemOptions.height,
517
+ _pxPerSec = _height / _timeRange,
518
+ _timeSecs = _time - _timeStart,
519
+ _px = _top + ( _timeSecs * _pxPerSec );
520
+ return Math.round( _px );
521
+ },
522
+
523
+ // converts time to pixels for the rect
524
+ rectFromValue: function( _value ){
525
+ var
526
+ _topPx = this.timeToPx( _value.start ),
527
+ _bottomPx = this.timeToPx( _value.start + _value.duration ),
528
+ _leftPx = this.options.itemOffsetLeft,
529
+ _rightPx = this.rect.width - this.options.itemOffsetRight - 2,
530
+ _rect;
531
+ if( _topPx === 'underflow' ){
532
+ _topPx = _itemOptions.offsetTop;
533
+ }
534
+ else if( _topPx === 'overflow' ){
535
+ return false;
536
+ }
537
+ if( _bottomPx === 'underflow' ){
538
+ return false;
207
539
  }
208
- if(_top===undefined){
209
- _top = 0;
540
+ else if( _bottomPx === 'overflow' ){
541
+ _bottomPx = _itemOptions.offsetTop + _itemOptions.height;
210
542
  }
211
- if(_height===undefined){
212
- _height = Math.round(this.pxPerHour/2);
543
+ _rect = HRect.nu( _leftPx, _topPx, _rightPx, _bottomPx );
544
+ if( _rect.height < this.options.itemMinHeight ){
545
+ _rect.setHeight( this.options.itemMinHeight );
213
546
  }
547
+ return _rect;
548
+ },
549
+
550
+ // creates a single time sheet item component
551
+ createTimeSheetItem: function( _value ){
214
552
  var
215
- _label = _value['label'],
553
+ _rect = this.rectFromValue( _value ),
554
+ _item;
555
+ if( _rect === false ){
556
+ return false;
557
+ }
216
558
  _item = HTimeSheetItem.nu(
217
- this.createItemRect( _top, _height ),
559
+ _rect,
218
560
  this, {
219
- label: _label,
220
561
  value: _value,
562
+ displayTime: this.options.itemDisplayTime,
221
563
  events: {
222
- draggable: true
564
+ draggable: true,
565
+ // click: true,
566
+ doubleClick: true
223
567
  }
224
568
  }
225
569
  );
226
- _item.dragMode = _dragMode;
227
570
  return _item;
228
571
  },
229
-
230
- /** = Description
231
- * Redraws and refreshes the values on timesheet.
232
- *
233
- **/
234
- refreshValue: function(){
572
+
573
+ // calls createTimeSheetItem with each value of the timesheet value array
574
+ drawTimeSheetItems: function(){
575
+
235
576
  var
236
577
  _data = this.value,
237
- i;
238
- if(this.listItemViews === false){
239
- this.listItemViews = [];
578
+ i = 0,
579
+ _value,
580
+ _item,
581
+ _items = this.timeSheetItems;
582
+
583
+ if((_data instanceof Array) && (_data.length > 0)){
584
+ for( ; i < _data.length; i++){
585
+ _value = _data[i];
586
+ _item = this.createTimeSheetItem( _value );
587
+ if(_item){
588
+ _items.push( _item );
589
+ }
590
+ }
591
+ }
592
+ },
593
+
594
+
595
+ /** =Description
596
+ * Create a new timeSheetItems if it hasn't been done already,
597
+ * otherwise destroy the items of the old one before proceeding.
598
+ **/
599
+ _initTimeSheetItems: function(){
600
+ if(this.timeSheetItems === undefined){
601
+ this.timeSheetItems = [];
240
602
  }
241
- else if(this.listItemViews.length > 0){
242
- for( i=0; i<this.listItemViews.length; i++){
243
- this.listItemViews[i].die();
603
+ else if(this.timeSheetItems.length > 0){
604
+ for( var i=0; i<this.timeSheetItems.length; i++){
605
+ this.timeSheetItems[i].die();
244
606
  }
245
- this.listItemViews = [];
607
+ this.timeSheetItems = [];
246
608
  }
247
- if(_data instanceof Array && _data.length > 0){
248
- var
249
- _value,
250
- _item;
251
- for( i=0; i<_data.length; i++){
252
- _value = _data[i];
253
- _item = this.createTimeSheetItem( _value );
254
- this.listItemViews.push( _item );
609
+ },
610
+
611
+ // checks if i overlaps j
612
+ // todo: refactor, it's a bit messy
613
+ _doesOverlap: function( i, j, _overlaps, _rectSelf, _items ){
614
+ if( _rectSelf === undefined ) {
615
+ _rectSelf = this.timeSheetItems[i].rect;
616
+ }
617
+ if( _items === undefined ){
618
+ _items = this.timeSheetItems;
619
+ }
620
+ var
621
+ _isntSame = (i !== j),
622
+ _isntListedSelf = (_overlaps.indexOf(i)===-1),
623
+ _isntListedOther = (_overlaps.indexOf(j)===-1),
624
+ _rectOther = _items[j].rect;
625
+ if( !_isntSame ){ return false; }
626
+ if( !_isntListedSelf ){ return false; }
627
+ if( _isntListedOther ){
628
+ if( _rectOther.intersects( _rectSelf, 1, 1 ) || _rectSelf.intersects( _rectOther, 1, 1 ) ){
629
+ return true;
255
630
  }
256
631
  }
632
+ return false;
633
+ },
634
+
635
+
636
+ // finds the index in the array which contains most sequential items
637
+ _findLargestSequence: function( _arr ){
257
638
  var
639
+ i = 1,
640
+ _index = 0,
641
+ _length = 1,
642
+ _maxLength = 1,
643
+ _bestIndex = 0;
644
+ for( ; i < _arr.length; i++ ){
645
+ // grow:
646
+ if( ( _arr[i] - _arr[i-1] === 1 ) && ( _index === i-_length ) ){
647
+ _length += 1;
648
+ }
649
+ // reset:
650
+ else {
651
+ _index = i;
652
+ _length = 1;
653
+ }
654
+ if( _length > _maxLength ){
655
+ _bestIndex = _index;
656
+ _maxLength = _length;
657
+ }
658
+ }
659
+ return [ _bestIndex, _maxLength ];
660
+ },
661
+
662
+ // find the amount of overlapping time sheet items
663
+ _findOverlapCount: function(){
664
+ var
665
+ _overlapCount,
666
+ _items = this._sortedTimeSheetItems(),
258
667
  _overlaps = [],
668
+ _maxOverlapCount = 0,
669
+ i = 0,
259
670
  j;
260
- for(i=0;i<this.listItemViews.length;i++){
261
- for(j=0;j<this.listItemViews.length;j++){
262
- if((i !== j) && (_overlaps.indexOf(i)===-1) && (_overlaps.indexOf(j)===-1)){
263
- if(this.listItemViews[i].rect.intersects(this.listItemViews[j].rect)){
264
- _overlaps.push(i);
265
- }
671
+ for( ; i < _items.length; i++ ){
672
+ _overlapCount = 0;
673
+ for( j = 0; j < _items.length; j++ ){
674
+ if( this._doesOverlap( i, j, _overlaps, _items[i].rect, _items ) ){
675
+ _overlapCount++;
676
+ _overlaps.push( j );
677
+ }
678
+ }
679
+ if( _overlapCount !== 0 ){
680
+ _overlaps.push( i );
681
+ if( _overlapCount > _maxOverlapCount ){
682
+ _maxOverlapCount = _overlapCount;
266
683
  }
267
684
  }
268
685
  }
686
+ return _maxOverlapCount;
687
+ },
688
+
689
+ // returns a sorted copy of the timeSheetItems array
690
+ _sortedTimeSheetItems: function( _sortFn ){
691
+ if( _sortFn === undefined ){
692
+ _sortFn = function(a,b){
693
+ return ( b.rect.height - a.rect.height);
694
+ };
695
+ }
269
696
  var
270
- _overlapCount = _overlaps.length+1,
271
- _overlapLefts = {},
272
- _itemWidth = ( this.rect.width - this.itemOffsetRight - this.itemOffsetLeft ),
273
- _width = Math.floor( _itemWidth / _overlapCount),
274
- _left = this.itemOffsetLeft;
275
- for(j=0;j<_overlapCount;j++){
276
- _overlapLefts[_overlaps[j]] = _left + (j*_width) + _width;
277
- }
278
- for(i=0;i<this.listItemViews.length;i++){
279
- if(_overlaps.indexOf(i)===-1){
280
- this.listItemViews[i].rect.setLeft(_left);
697
+ i = 0,
698
+ _arr = [],
699
+ _items = this.timeSheetItems;
700
+ for( ; i < _items.length; i++ ){
701
+ _arr.push( _items[i] );
702
+ }
703
+ _arr = _arr.sort(_sortFn);
704
+ return _arr;
705
+ },
706
+
707
+
708
+ // Optimizes the left and right position of each timesheet item to fit
709
+ _updateTimelineRects: function(){
710
+ var
711
+ // loop indexes:
712
+ i, j, k, l,
713
+ // amount of items ovelapping (max, actual number might be smaller after optimization)
714
+ _options = this.options,
715
+ _rect = this.rect,
716
+ _overlapCount = this._findOverlapCount(),
717
+ _availWidth = ( _rect.width - _options.itemOffsetRight - _options.itemOffsetLeft ),
718
+ _left = _options.itemOffsetLeft,
719
+ _width = Math.floor( _availWidth / (_overlapCount+1) ),
720
+ // get a list of timesheet items sorted by height (larger to smaller order)
721
+ _items = this._sortedTimeSheetItems(),
722
+ _itemCount = _items.length,
723
+ _itemRect,
724
+ _testRect,
725
+ _leftPos,
726
+ _rightPos,
727
+ _maxCol = 0,
728
+ _origCol,
729
+ _origColById = [],
730
+ _overlapCols,
731
+ _vacantCols,
732
+ _optimalColAndLength,
733
+ _col,
734
+ _colWidth,
735
+ _overlaps;
736
+
737
+ // No overlapping; nothing to do
738
+ if(_overlapCount === 0 ){
739
+ return false;
740
+ }
741
+
742
+ // move all items initially to one column right of the max overlaps
743
+ _leftPos = _left+(_width*(_overlapCount+1));
744
+ for( i = 0; i < _itemCount; i++ ){
745
+ _itemRect = _items[i].rect;
746
+ _itemRect.setLeft( _leftPos );
747
+ _itemRect.setRight( _leftPos+_width );
748
+ }
749
+
750
+ // optimize gaps by traversing each combination
751
+ // and finding the first column with no gaps
752
+ // the top-level loops three times in the following modes:
753
+ // 0: place items into the first vacant column and find the actual max columns
754
+ // 1: stretch columns to final column width
755
+ // 2: stretch columns to fit multiple columns, if space is vacant
756
+ for( l = 0; l < 3; l++ ){
757
+ for( i = 0; i < _itemCount; i++){
758
+ _itemRect = _items[i].rect;
759
+ // in mode 1, just the column widths are changed
760
+ if( l === 1 ){
761
+ _leftPos = _left + (_origColById[i]*_width);
762
+ _itemRect.setLeft( _leftPos );
763
+ _itemRect.setRight( _leftPos + _width );
764
+ continue;
765
+ }
766
+ _testRect = HRect.nu( _itemRect );
767
+
768
+ _overlapCols = [];
769
+ _vacantCols = [];
770
+
771
+ // test each column position (modes 0 and 2)
772
+ for( k = 0; k < _overlapCount+1; k++ ){
773
+ _leftPos = _left + (k*_width);
774
+ _testRect.setLeft( _leftPos );
775
+ _testRect.setRight( _leftPos + _width );
776
+ _overlaps = [];
777
+ for( j = 0; j < _itemCount; j++){
778
+ if( this._doesOverlap( i, j, _overlaps, _testRect, _items ) ){
779
+ if( _overlapCols.indexOf( k ) === -1 ){
780
+ _overlapCols.push( k );
781
+ }
782
+ }
783
+ }
784
+ if( _vacantCols.indexOf( k ) === -1 && _overlapCols.indexOf( k ) === -1 ){
785
+ _vacantCols.push( k );
786
+ }
787
+ }
788
+
789
+ // on the first run (mode 0) place items into the first column:
790
+ if( l === 0 ){
791
+ _origCol = _vacantCols[0];
792
+ _origColById.push( _origCol );
793
+ _leftPos = _left+(_origCol*_width);
794
+ _rightPos = _leftPos + _width;
795
+ if( _maxCol < _origCol ){
796
+ _maxCol = _origCol;
797
+ }
798
+ }
799
+ else {
800
+ // on mode 2: stretch to fill multiple column widths,
801
+ // because no item moving is done anymore at this stage, so we know what's free and what's not
802
+ if( _vacantCols.length > 0 ){
803
+ _optimalColAndLength = this._findLargestSequence( _vacantCols );
804
+ _col = _vacantCols[ _optimalColAndLength[0] ];
805
+ _colWidth = _optimalColAndLength[1];
806
+ }
807
+ else {
808
+ _origCol = _origColById[i];
809
+ _col = _origCol;
810
+ _colWidth = 1;
811
+ }
812
+ _leftPos = _left+(_col*_width);
813
+ _rightPos = _leftPos+(_colWidth*_width);
814
+ }
815
+ _itemRect.setLeft( _leftPos );
816
+ _itemRect.setRight( _rightPos );
281
817
  }
282
- else {
283
- this.listItemViews[i].rect.setLeft(_overlapLefts[i]);
818
+ // afther the first run (mode 0) we know the actual amount of columns, so adjust column width accordingly
819
+ if( l === 0 ){
820
+ _overlapCount = _maxCol;
821
+ _width = Math.floor( _availWidth / (_maxCol+1) );
284
822
  }
285
- this.listItemViews[i].rect.setWidth(_width);
286
823
  }
287
- for(i=0;i<this.listItemViews.length;i++){
288
- this.listItemViews[i].drawRect();
824
+ return true;
825
+ },
826
+
827
+ // draws the timeline (sub-routine of refreshValue)
828
+ drawTimeline: function(){
829
+ this._initTimeSheetItems();
830
+ this.drawTimeSheetItems();
831
+ this._updateTimelineRects();
832
+ // use the dimensions of the views
833
+ for( var i = 0; i < this.timeSheetItems.length; i++){
834
+ this.timeSheetItems[i].drawRect();
835
+ }
836
+ },
837
+
838
+ _sha: SHA.nu(8),
839
+
840
+ /*
841
+
842
+ Each item looks like this, any extra attributes are allowed,
843
+ but not used and not guaranteed to be preserved:
844
+
845
+ {
846
+ id: 'abcdef1234567890', // identifier, used in server to map id's
847
+ label: 'Event title', // label of event title
848
+ start: 1299248619, // epoch timestamp of event start
849
+ duration: 3600, // duration of event in seconds
850
+ locked: true, // when false, prevents editing the item
851
+ icons: [ 1, 3, 6 ], // icon id's to display
852
+ color: '#ffffff' // defaults to '#ffffff' if undefined
853
+ }
854
+
855
+
856
+ */
857
+ /** = Description
858
+ * Redraws and refreshes the values on timesheet.
859
+ *
860
+ **/
861
+ refreshValue: function(){
862
+
863
+ if(!this.itemOptions){
864
+ return;
865
+ }
866
+
867
+ // optimization that ensures the rect and previous value are different before redrawing
868
+ var
869
+ _valueStr = COMM.Values.encode( this.value ),
870
+ _rectStr = this.rect.toString(),
871
+ _timeRangeStr = this.options.timeStart+':'+this.options.timeEnd,
872
+ _shaSum = this._sha.strSHA1( _valueStr+_rectStr+_timeRangeStr );
873
+ if( this._prevSum !== _shaSum ){
874
+ // the preview timesheet item is hidden when new data arrives (including what it created)
875
+ this.dragPreview.hide();
876
+ this._prevSum = _shaSum;
877
+ this.drawTimeline();
289
878
  }
290
879
  }
880
+
291
881
  });
292
882