agile_rails 0.0.0.1

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 (259) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +285 -0
  5. data/Rakefile +39 -0
  6. data/agile_rails.gemspec +36 -0
  7. data/app/assets/fonts/ibm-plex-sans-300.woff2 +0 -0
  8. data/app/assets/fonts/ibm-plex-sans-400.woff2 +0 -0
  9. data/app/assets/fonts/ibm-plex-sans-500.woff2 +0 -0
  10. data/app/assets/fonts/ibm-plex-sans-600.woff2 +0 -0
  11. data/app/assets/fonts/ibm-plex-sans-700.woff2 +0 -0
  12. data/app/assets/fonts/ibm-plex-sans-italic.woff2 +0 -0
  13. data/app/assets/images/32px.png +0 -0
  14. data/app/assets/images/throbber.gif +0 -0
  15. data/app/assets/javascripts/agile/agile.js +1489 -0
  16. data/app/assets/javascripts/agile/jquery-migrate.js +702 -0
  17. data/app/assets/javascripts/agile/jquery.bpopup.js +372 -0
  18. data/app/assets/javascripts/agile/jquery.datetimepicker.js +1353 -0
  19. data/app/assets/javascripts/agile/jstree.min.js +6 -0
  20. data/app/assets/javascripts/agile/select-multiple.js +459 -0
  21. data/app/assets/javascripts/agile/some_scripts.js +33 -0
  22. data/app/assets/javascripts/agile.js +22 -0
  23. data/app/assets/javascripts/agile_application.js +22 -0
  24. data/app/assets/javascripts/agile_editor.js +22 -0
  25. data/app/assets/stylesheets/agile/agile.css +1882 -0
  26. data/app/assets/stylesheets/agile/agile_apps.css +149 -0
  27. data/app/assets/stylesheets/agile/jquery.datetimepicker.css +304 -0
  28. data/app/assets/stylesheets/agile/jstree.css +1107 -0
  29. data/app/assets/stylesheets/agile/select-multiple.css +110 -0
  30. data/app/assets/stylesheets/agile/th-bg.png +0 -0
  31. data/app/assets/stylesheets/agile/theme.css +49 -0
  32. data/app/assets/stylesheets/agile.css +21 -0
  33. data/app/assets/stylesheets/agile_application.css +20 -0
  34. data/app/assets/stylesheets/agile_editor.css +19 -0
  35. data/app/controllers/agile_application_controller.rb +735 -0
  36. data/app/controllers/agile_common_controller.rb +345 -0
  37. data/app/controllers/agile_controller.rb +977 -0
  38. data/app/controllers/agile_main_controller.rb +36 -0
  39. data/app/controls/agile_control.rb +120 -0
  40. data/app/controls/agile_report.rb +364 -0
  41. data/app/controls/ar_category_control.rb +39 -0
  42. data/app/controls/ar_help_control.rb +139 -0
  43. data/app/controls/ar_image_control.rb +180 -0
  44. data/app/controls/ar_journal_control.rb +47 -0
  45. data/app/controls/ar_menu_item_control.rb +55 -0
  46. data/app/controls/ar_page_control.rb +64 -0
  47. data/app/controls/ar_poll_result_control.rb +84 -0
  48. data/app/controls/ar_setup_control.rb +62 -0
  49. data/app/controls/belongs_to_control.rb +61 -0
  50. data/app/controls/browse_models_control.rb +98 -0
  51. data/app/controls/settings_form_control.rb +137 -0
  52. data/app/forms/agile_help.yml +112 -0
  53. data/app/forms/agile_menu.yml +140 -0
  54. data/app/forms/agile_report_defaults.yml +39 -0
  55. data/app/forms/all_options.yml +810 -0
  56. data/app/forms/ar_ad.yml +121 -0
  57. data/app/forms/ar_big_table.yml +60 -0
  58. data/app/forms/ar_big_table_value.yml +52 -0
  59. data/app/forms/ar_browse_fields.yml +35 -0
  60. data/app/forms/ar_browse_models.yml +48 -0
  61. data/app/forms/ar_category.yml +73 -0
  62. data/app/forms/ar_category_as_tree.yml +31 -0
  63. data/app/forms/ar_design.yml +75 -0
  64. data/app/forms/ar_filter.yml +52 -0
  65. data/app/forms/ar_folder_permission.yml +56 -0
  66. data/app/forms/ar_folder_rule.yml +48 -0
  67. data/app/forms/ar_gallery.yml +55 -0
  68. data/app/forms/ar_image.yml +126 -0
  69. data/app/forms/ar_image_search.yml +83 -0
  70. data/app/forms/ar_journal.yml +76 -0
  71. data/app/forms/ar_json_ld.yml +56 -0
  72. data/app/forms/ar_key_value.yml +33 -0
  73. data/app/forms/ar_key_value_store.yml +33 -0
  74. data/app/forms/ar_link.yml +60 -0
  75. data/app/forms/ar_menu.yml +67 -0
  76. data/app/forms/ar_menu_item.yml +141 -0
  77. data/app/forms/ar_page.yml +187 -0
  78. data/app/forms/ar_part.yml +91 -0
  79. data/app/forms/ar_permission.yml +52 -0
  80. data/app/forms/ar_permission_rule.yml +40 -0
  81. data/app/forms/ar_piece.yml +106 -0
  82. data/app/forms/ar_policy.yml +64 -0
  83. data/app/forms/ar_policy_rule.yml +42 -0
  84. data/app/forms/ar_policy_rule_nocms.yml +40 -0
  85. data/app/forms/ar_poll.yml +118 -0
  86. data/app/forms/ar_poll_item.yml +78 -0
  87. data/app/forms/ar_poll_result.yml +88 -0
  88. data/app/forms/ar_poll_result_export.yml +35 -0
  89. data/app/forms/ar_removed_url.yml +41 -0
  90. data/app/forms/ar_role.yml +43 -0
  91. data/app/forms/ar_seo.yml +32 -0
  92. data/app/forms/ar_setup.yml +45 -0
  93. data/app/forms/ar_site.yml +149 -0
  94. data/app/forms/ar_steps_template.yml +51 -0
  95. data/app/forms/ar_user.yml +140 -0
  96. data/app/forms/ar_user_role.yml +57 -0
  97. data/app/forms/help/dc_category_as_tree.en +4 -0
  98. data/app/forms/help/dc_category_as_tree.sl +5 -0
  99. data/app/forms/json_ld_schema.yml +168 -0
  100. data/app/helpers/agile_application_helper.rb +1162 -0
  101. data/app/helpers/agile_category_helper.rb +128 -0
  102. data/app/helpers/agile_common_helper.rb +308 -0
  103. data/app/helpers/agile_edit_helper.rb +645 -0
  104. data/app/helpers/agile_helper.rb +509 -0
  105. data/app/helpers/agile_index_helper.rb +677 -0
  106. data/app/helpers/ar_image_helper.rb +128 -0
  107. data/app/models/agile_form_fields/action.rb +61 -0
  108. data/app/models/agile_form_fields/agile_form_field.rb +322 -0
  109. data/app/models/agile_form_fields/belongs_to.rb +112 -0
  110. data/app/models/agile_form_fields/check_box.rb +73 -0
  111. data/app/models/agile_form_fields/comment.rb +62 -0
  112. data/app/models/agile_form_fields/date_picker.rb +104 -0
  113. data/app/models/agile_form_fields/date_select.rb +68 -0
  114. data/app/models/agile_form_fields/datetime_picker.rb +88 -0
  115. data/app/models/agile_form_fields/datetime_select.rb +73 -0
  116. data/app/models/agile_form_fields/file_field.rb +52 -0
  117. data/app/models/agile_form_fields/file_select.rb +69 -0
  118. data/app/models/agile_form_fields/hidden_field.rb +51 -0
  119. data/app/models/agile_form_fields/html_field.rb +69 -0
  120. data/app/models/agile_form_fields/journal_diff.rb +62 -0
  121. data/app/models/agile_form_fields/link_to.rb +69 -0
  122. data/app/models/agile_form_fields/method.rb +66 -0
  123. data/app/models/agile_form_fields/multitext_autocomplete.rb +215 -0
  124. data/app/models/agile_form_fields/number_field.rb +92 -0
  125. data/app/models/agile_form_fields/password_field.rb +63 -0
  126. data/app/models/agile_form_fields/radio_button.rb +95 -0
  127. data/app/models/agile_form_fields/readonly.rb +77 -0
  128. data/app/models/agile_form_fields/select.rb +281 -0
  129. data/app/models/agile_form_fields/submit_tag.rb +58 -0
  130. data/app/models/agile_form_fields/text_area.rb +61 -0
  131. data/app/models/agile_form_fields/text_autocomplete.rb +171 -0
  132. data/app/models/agile_form_fields/text_field.rb +55 -0
  133. data/app/models/agile_form_fields/text_with_select.rb +94 -0
  134. data/app/models/agile_form_fields/tree_select.rb +170 -0
  135. data/app/models/ar_big_table.rb +82 -0
  136. data/app/models/ar_big_table_value.rb +53 -0
  137. data/app/models/ar_category.rb +109 -0
  138. data/app/models/ar_design.rb +116 -0
  139. data/app/models/ar_filter.rb +200 -0
  140. data/app/models/ar_folder_permission.rb +50 -0
  141. data/app/models/ar_folder_rule.rb +47 -0
  142. data/app/models/ar_gallery.rb +53 -0
  143. data/app/models/ar_image.rb +198 -0
  144. data/app/models/ar_internals.rb +60 -0
  145. data/app/models/ar_journal.rb +46 -0
  146. data/app/models/ar_json_ld.rb +131 -0
  147. data/app/models/ar_key_value_store.rb +128 -0
  148. data/app/models/ar_link.rb +48 -0
  149. data/app/models/ar_memory.rb +172 -0
  150. data/app/models/ar_menu.rb +144 -0
  151. data/app/models/ar_menu_item.rb +106 -0
  152. data/app/models/ar_page.rb +74 -0
  153. data/app/models/ar_part.rb +66 -0
  154. data/app/models/ar_permission.rb +180 -0
  155. data/app/models/ar_permission_rule.rb +65 -0
  156. data/app/models/ar_policy.rb +78 -0
  157. data/app/models/ar_policy_rule.rb +65 -0
  158. data/app/models/ar_poll.rb +74 -0
  159. data/app/models/ar_poll_item.rb +47 -0
  160. data/app/models/ar_poll_result.rb +38 -0
  161. data/app/models/ar_removed_url.rb +42 -0
  162. data/app/models/ar_role.rb +84 -0
  163. data/app/models/ar_setup.rb +115 -0
  164. data/app/models/ar_site.rb +68 -0
  165. data/app/models/ar_temp.rb +150 -0
  166. data/app/models/ar_user.rb +72 -0
  167. data/app/models/ar_user_group.rb +38 -0
  168. data/app/models/ar_user_role.rb +54 -0
  169. data/app/models/ar_visit.rb +41 -0
  170. data/app/models/concerns/ar_page_concern.rb +128 -0
  171. data/app/models/concerns/ar_part_concern.rb +48 -0
  172. data/app/models/concerns/ar_piece_concern.rb +48 -0
  173. data/app/models/concerns/ar_policy_rule_concern.rb +87 -0
  174. data/app/models/concerns/ar_seo_concern.rb +66 -0
  175. data/app/models/concerns/ar_site_concern.rb +103 -0
  176. data/app/models/concerns/ar_user_concern.rb +195 -0
  177. data/app/renderers/agile_common_renderer.rb +93 -0
  178. data/app/renderers/agile_renderer.rb +59 -0
  179. data/app/renderers/ar_ad_renderer.rb +219 -0
  180. data/app/renderers/ar_captcha_renderer.rb +113 -0
  181. data/app/renderers/ar_common_renderer.rb +90 -0
  182. data/app/renderers/ar_gallery_renderer.rb +107 -0
  183. data/app/renderers/ar_menu_renderer.rb +195 -0
  184. data/app/renderers/ar_page_renderer.rb +147 -0
  185. data/app/renderers/ar_part_renderer.rb +235 -0
  186. data/app/renderers/ar_piece_renderer.rb +119 -0
  187. data/app/renderers/ar_poll_renderer.rb +272 -0
  188. data/app/views/agile/_edit_stuff.html.erb +57 -0
  189. data/app/views/agile/_form.html.erb +24 -0
  190. data/app/views/agile/_result.html.erb +28 -0
  191. data/app/views/agile/edit.html.erb +13 -0
  192. data/app/views/agile/error.html.erb +2 -0
  193. data/app/views/agile/index.html.erb +14 -0
  194. data/app/views/agile/login.html.erb +19 -0
  195. data/app/views/agile/new.html.erb +12 -0
  196. data/app/views/agile_common/_help.html.erb +18 -0
  197. data/app/views/agile_common/_iframe_edit.html.erb +2 -0
  198. data/app/views/agile_common/paste_clipboard.html.erb +17 -0
  199. data/app/views/layouts/agile.html.erb +17 -0
  200. data/app/views/layouts/content.html.erb +20 -0
  201. data/app/views/models/dump_models.html.erb +47 -0
  202. data/config/initializers/kaminari_patch.rb +56 -0
  203. data/config/locales/agile_de.yml +138 -0
  204. data/config/locales/agile_en.yml +162 -0
  205. data/config/locales/agile_sl.yml +163 -0
  206. data/config/locales/datetimepicker.yml +19 -0
  207. data/config/locales/de.yml +231 -0
  208. data/config/locales/en.yml +13 -0
  209. data/config/locales/kaminari.yml +26 -0
  210. data/config/locales/models_en.yml +1032 -0
  211. data/config/locales/models_sl.yml +1065 -0
  212. data/config/locales/sl.yml +211 -0
  213. data/db/migrate/20240120160001_add_sessions_table.rb +12 -0
  214. data/db/migrate/20240120160002_ar_big_table.rb +17 -0
  215. data/db/migrate/20240120160003_ar_big_table_value.rb +18 -0
  216. data/db/migrate/20240120160004_ar_category.rb +22 -0
  217. data/db/migrate/20240120160005_ar_design.rb +20 -0
  218. data/db/migrate/20240120160006_ar_filter.rb +17 -0
  219. data/db/migrate/20240120160007_ar_gallery.rb +21 -0
  220. data/db/migrate/20240120160008_ar_journal.rb +19 -0
  221. data/db/migrate/20240120160009_ar_key_value_store.rb +11 -0
  222. data/db/migrate/20240120160010_ar_link.rb +19 -0
  223. data/db/migrate/20240120160011_ar_memory.rb +8 -0
  224. data/db/migrate/20240120160012_ar_menu.rb +21 -0
  225. data/db/migrate/20240120160013_ar_menu_item.rb +28 -0
  226. data/db/migrate/20240120160014_ar_page.rb +50 -0
  227. data/db/migrate/20240120160015_ar_part.rb +33 -0
  228. data/db/migrate/20240120160016_ar_permission.rb +16 -0
  229. data/db/migrate/20240120160017_ar_permission_rule.rb +17 -0
  230. data/db/migrate/20240120160018_ar_piece.rb +28 -0
  231. data/db/migrate/20240120160019_ar_policy.rb +21 -0
  232. data/db/migrate/20240120160020_ar_policy_rule.rb +18 -0
  233. data/db/migrate/20240120160021_ar_poll.rb +27 -0
  234. data/db/migrate/20240120160022_ar_poll_item.rb +23 -0
  235. data/db/migrate/20240120160023_ar_poll_result.rb +14 -0
  236. data/db/migrate/20240120160024_ar_removed_url.rb +16 -0
  237. data/db/migrate/20240120160025_ar_role.rb +17 -0
  238. data/db/migrate/20240120160026_ar_site.rb +37 -0
  239. data/db/migrate/20240120160027_ar_temp.rb +11 -0
  240. data/db/migrate/20240120160028_ar_user.rb +42 -0
  241. data/db/migrate/20240120160029_ar_user_group.rb +12 -0
  242. data/db/migrate/20240120160030_ar_user_role.rb +18 -0
  243. data/db/migrate/20240120160031_ar_visit.rb +15 -0
  244. data/db/migrate/20240703016001_ar_setup.rb +16 -0
  245. data/db/migrate/20240703016002_ar_folder_permission.rb +15 -0
  246. data/db/migrate/20240703016003_ar_folder_rule.rb +14 -0
  247. data/db/migrate/20250115000001_ar_image.rb +25 -0
  248. data/lib/agile/configuration.rb +43 -0
  249. data/lib/agile/engine.rb +29 -0
  250. data/lib/agile/version.rb +27 -0
  251. data/lib/agile.rb +282 -0
  252. data/lib/agile_rails.rb +1 -0
  253. data/lib/generators/agile/USAGE +11 -0
  254. data/lib/generators/agile/new_form_generator.rb +369 -0
  255. data/lib/generators/convert_to_ar/convert_to_ar_generator.rb +158 -0
  256. data/lib/tasks/agile_db_clone.rake +132 -0
  257. data/lib/tasks/agile_db_export_to_yaml.rake +37 -0
  258. data/lib/tasks/agile_db_migrate.rake +35 -0
  259. metadata +414 -0
@@ -0,0 +1,1489 @@
1
+ /*
2
+ # Copyright (c) 2024+ Damjan Rems
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ */
23
+
24
+ mouseDown = false;
25
+
26
+ /*******************************************************************
27
+ * Find and extract parameters value from url
28
+ *******************************************************************/
29
+ $.getUrlParam = function(name) {
30
+ var results = new RegExp('[\\?&]' + name + '=([^&#]*)').exec(window.location.href);
31
+ if (results == null) return null;
32
+ return results[1] || 0;
33
+ };
34
+
35
+ /*******************************************************************
36
+ * Dump all attributes to console
37
+ *******************************************************************/
38
+ dumpAttributes = function(obj) {
39
+ console.log('Dumping attributes:');
40
+ $.each(obj.attributes, function() {
41
+ console.log(this.name,this.value);
42
+ });
43
+ };
44
+
45
+ /*******************************************************************
46
+ * Function checks if there are delay loaded embedded elements on
47
+ * selected tab and triggers iframe reload.
48
+ *******************************************************************/
49
+ update_embedded_on_tab_select = function(div_name) {
50
+ let iframes = $(div_name).find("iframe");
51
+ $.each(iframes, function(index, iframe) {
52
+ // delayed load
53
+ let src_delay = iframe.getAttribute('data-src-delay');
54
+ if (src_delay != null && src_delay != '') {
55
+ iframe.setAttribute('data-src-delay', '');
56
+ iframe.setAttribute('src', src_delay);
57
+ }
58
+ // always load on tab select
59
+ let src_always = iframe.getAttribute('data-src-always');
60
+ if (src_always != null) {
61
+ iframe.setAttribute('src', src_always);
62
+ }
63
+ // resize iframe to it's scroll height
64
+ let iframe_height= iframe.contentWindow.document.documentElement.scrollHeight;
65
+ iframe.style.height = iframe_height.toString() + 'px';
66
+ });
67
+ };
68
+
69
+ /*******************************************************************
70
+ * Return false when confirmation is not required
71
+ *******************************************************************/
72
+ confirmation_is_cancelled = function(object) {
73
+ let confirmation = '';
74
+ if ($.type(object) === "string") {
75
+ confirmation = object;
76
+ } else {
77
+ confirmation = object.getAttribute("data-confirm");
78
+ }
79
+ // if confirmation required
80
+ if (confirmation !== null) {
81
+ if (!confirm(confirmation)) {return true;}
82
+ }
83
+ return false;
84
+ };
85
+
86
+ /*******************************************************************
87
+ * Will update select field on the form which select options are depend
88
+ * on other field value. It calls /agile_common/autocomplete and passes
89
+ * method name and depend field value to obtain new values for select field.
90
+ *******************************************************************/
91
+ update_select_depend = function(select_name, depend_name, method) {
92
+ let select_field = $('#' + select_name);
93
+ let depend_value= '';
94
+ let depend_field= null;
95
+ let field_value= null;
96
+
97
+ depend_name.split(",").forEach( function(depend) {
98
+ if ( $('#record_' + depend.trim()).length ) {
99
+ depend_field = $('#record_' + depend.trim());
100
+ } else {
101
+ depend_field = $('#_record_' + depend.trim()); // virtual field
102
+ }
103
+
104
+ // checkbox
105
+ if (depend_field.is(':checkbox')) {
106
+ field_value = depend_field.is(":checked");
107
+ } else {
108
+ field_value = depend_field.val();
109
+ }
110
+
111
+ if (depend_value.length > 0) depend_value = depend_value + ',';
112
+ depend_value = depend_value + field_value;
113
+ });
114
+
115
+ $.ajax({
116
+ url: "/agile/autocomplete",
117
+ type: "POST",
118
+ dataType: "json",
119
+ data: { input: depend_value, search: method},
120
+ success: function(data) {
121
+
122
+ select_field.empty();
123
+ $.each(data, function(index, element) {
124
+ select_field.append( new Option(element['label'], element['id']) );
125
+ });
126
+ // refresh multiple select field
127
+ if (select_field.hasClass('select-multiple')) { select_field.selectMultiple('refresh') }
128
+ }
129
+ });
130
+ };
131
+
132
+ /*******************************************************************
133
+ * Format number input field according to data
134
+ *******************************************************************/
135
+ format_number_field = function(e) {
136
+ let decimals = e.attr("data-decimal") || 2;
137
+ let delimiter = e.attr("data-delimiter") || '.';
138
+ let separator = e.attr("data-separator") || ',';
139
+ let currency = e.attr("data-currency") || '';
140
+ let whole = e.val().split(separator)[0];
141
+ let dec = e.val().split(separator)[1];
142
+ // save value to hidden field which will be used for return
143
+ let field = '#' + e.attr("id").slice(0,-1);
144
+ let value = e.val().replace(delimiter,'.');
145
+
146
+ $(field).val( parseFloat(value).toFixed(decimals) );
147
+
148
+ // decimal part
149
+ if (dec == null) dec = '';
150
+ dec = dec.substring(0, decimals, dec);
151
+ while (dec.length < decimals) dec = dec + '0';
152
+ // whole part
153
+ if (whole == null || whole == '') whole = '0';
154
+ let ar = [];
155
+
156
+ while (whole.length > 0) {
157
+ let pos1 = whole.length - 3
158
+ if (pos1 < 0) pos1 = 0;
159
+ ar.unshift(whole.substr(pos1,3));
160
+ whole = whole.slice(0, -3);
161
+ };
162
+
163
+ if (delimiter !== '') whole = ar.join(delimiter);
164
+ e.val(whole + separator + dec + currency);
165
+ };
166
+
167
+ /*******************************************************************
168
+ * Dynamically loads javascript file from filesystem
169
+ *******************************************************************/
170
+ function load_script( url, callback ) {
171
+ let script = document.createElement( "script" )
172
+ script.type = "text/javascript";
173
+ script.src = url;
174
+ script.onload = function() {
175
+ callback()
176
+ };
177
+ document.head.appendChild(script);
178
+ }
179
+
180
+ /*******************************************************************
181
+ * Activate jquery UI tooltip. This needs jquery.ui >= 1.9
182
+ *******************************************************************/
183
+ /*
184
+ $(function() {
185
+ $( document ).tooltip();
186
+ });
187
+ */
188
+
189
+ /*******************************************************************
190
+ * Process json result from ajax call and update parts of document.
191
+ *
192
+ * Protocol consists of operation and value which are returned as json by
193
+ * called control method. Control method will return a json result usually like this:
194
+ * render json: { operation: value }
195
+ *
196
+ * Operation is further divided into source and selector which are divided by underline char.
197
+ * render json: { #div_status: 'OK!' }
198
+ * will replace html in div="status" with value 'OK!'. Source is '#div' selector is 'status'.
199
+ *
200
+ * Possible combinations are:
201
+ *
202
+ * #div_divname : will replace divname with value
203
+ * #div+_divname : will append value to divname
204
+ * #+div_divname : will prepend value to divname
205
+ * .div_classname : will replace all occurrences of classname with value
206
+ * .+div_classname : will prepend value to all occurrences of classname
207
+ * .div+_classname : will append value to all occurrences of classname
208
+ *
209
+ * Other options:
210
+ *
211
+ * record_name: will replace value of record[name] field on a form with supplied value
212
+ *
213
+ * msg_error: will display error message
214
+ * msg_warning: will display warning message
215
+ * msg_info: will display informational message
216
+ * popup: will display popup message
217
+ * alert: will display standard alert message
218
+ *
219
+ * url: url - will force loading of url supplied in value into same window
220
+ * parenturl: url - will force loading of url supplied in value into parent frame
221
+ * window: url - will force loading of url supplied in value into window on a new tab
222
+ * newwindow: url - will force loading of url supplied in value into new windowed dialog
223
+ * reload: true - will reload current frame
224
+ * reload: parent - will reload parent frame
225
+ *
226
+ * script: Will evaluate provided javascript.
227
+ *
228
+ * Operations can be chained and are executed sequential.
229
+ * render json: {window: "/document.pdf", reload: true }
230
+ * will open /document.pdf in new window and reload current window.
231
+ *
232
+ * render json: {'record_name' => "Damjan", 'record_surname' => 'Rems'}
233
+ * will replace values of two fields on the form.
234
+ *******************************************************************/
235
+
236
+ process_json_result = function(json) {
237
+ let i, w, operation, selector, msg_div, field;
238
+ $.each(json, function(key, value) {
239
+ i = key.search('_');
240
+ if (i > 1) {
241
+ operation = key.substring(0, i);
242
+ selector = key.substring(i+1, 100);
243
+ } else {
244
+ operation = key;
245
+ selector = '';
246
+ }
247
+
248
+ switch (operation) {
249
+
250
+ /**** update fields on form ****/
251
+ case 'record':
252
+ //let name = key.replace('record_','record[') + ']';
253
+ //field = $('[name="' + name + '"]');
254
+ field = $('#' + key);
255
+ // console.log(field);
256
+ // checkbox field
257
+ if (field.is(':checkbox')) {
258
+ field.prop('checked', value);
259
+ // select field
260
+ } else if (field.is('select')) {
261
+ // options for select field
262
+ if (Array.isArray(value)) {
263
+ field.empty();
264
+ $.each(value, function(index, v) {
265
+ field.append( new Option(v[0], v[1]) );
266
+ });
267
+ // select field value
268
+ } else {
269
+ field.val(value).change();
270
+ }
271
+ // radio field
272
+ } else if (field.attr('type') == 'radio') {
273
+ field.val([value])
274
+
275
+ // other input fields
276
+ } else {
277
+ field.val(value);
278
+ }
279
+ break;
280
+
281
+ /**** transfer focus to field ****/
282
+ case 'focus':
283
+ $('#' + value).focus();
284
+ break;
285
+
286
+ /**** display message ****/
287
+ case 'msg':
288
+ let msg_div = 'ar-form-' + selector;
289
+ if ( $('.' + msg_div).length == 0 ) {
290
+ value = '<div class="' + msg_div + '">' + value + '</div>';
291
+ $('.ar-title').after(value);
292
+ } else {
293
+ $('.' + msg_div).html(value);
294
+ $('.' + msg_div).show();
295
+ }
296
+ break;
297
+
298
+ /**** display popup message ****/
299
+ case 'popup':
300
+ if (selector === 'url') {
301
+ $('#popup').bPopup({ loadUrl: value,
302
+ transition: 'slideDown', transitionClose: 'slideDown', speed: 300,
303
+ opacity: 0, position: ['auto', 20],
304
+ closeClass: 'ar-link' });
305
+ }
306
+ else {
307
+ if (selector === '') { selector = 'info' }
308
+ let popup_html = '<div class="popup-' + selector + '">' + value + '<br><button class="ar-link">OK</button></div>';
309
+ $('#popup').html(popup_html);
310
+ $('#popup').bPopup( {
311
+ transition: 'slideDown', transitionClose: 'slideDown', speed: 300,
312
+ opacity: 0, position: ['auto', 150],
313
+ closeClass: 'ar-link' });
314
+ }
315
+ // resize parent iframe if smaller then 500px to ensure popup some space
316
+ let document_height = document.body.scrollHeight;
317
+ if (document_height < 500) {
318
+ let frame = window.frameElement
319
+ if (frame === null) frame = document.body;
320
+ frame.style.height = '500px';
321
+ }
322
+ break;
323
+
324
+ /**** update div ****/
325
+ case '#div+':
326
+ $('#'+selector).append(value);
327
+ break;
328
+ case '#+div':
329
+ $('#'+selector).prepend(value);
330
+ break;
331
+ case '#div':
332
+ $('#'+selector).html(value);
333
+ break;
334
+ case '.div+':
335
+ $('.'+selector).append(value);
336
+ break;
337
+ case '.+div':
338
+ $('.'+selector).prepend(value);
339
+ break;
340
+ case '.div':
341
+ $('.'+selector).html(value);
342
+ break;
343
+
344
+ /**** goto url ****/
345
+ case 'url':
346
+ window.location.href = value;
347
+ break;
348
+ case 'parenturl':
349
+ parent.location.href = value;
350
+ break;
351
+ case 'alert':
352
+ alert(value);
353
+ break;
354
+ case 'window':
355
+ if (value == 'close') { window.close();
356
+ } else if (value == 'reload') {
357
+ window.location.href = window.location.href;
358
+ } else {
359
+ w = window.open(value, selector);
360
+ w.focus();
361
+ }
362
+ break;
363
+ case 'newwindow':
364
+ w = window.open(value, selector,"location=no,scrollbars=yes,resizable=yes");
365
+ w.focus();
366
+ break;
367
+ case 'script':
368
+ eval (value);
369
+ break;
370
+ case 'reload':
371
+ value = value.toString();
372
+ if (value == 'parent') {
373
+ //parent.location.reload();
374
+ parent.location.href = parent.location.href;
375
+
376
+ /*** this would be current window (reload: true) ****/
377
+ } else if (value.length < 5) {
378
+ window.location.href = window.location.href;
379
+ /*** reload iframe ****/
380
+ } else {
381
+ $( '#' + value ).attr('src', $( '#' + value ).attr('src'));
382
+ }
383
+ break;
384
+ default:
385
+ console.log("AgileRails: Invalid ajax result operation: " + operation);
386
+ }
387
+ });
388
+ };
389
+
390
+ /*******************************************************************
391
+ * Will reload window
392
+ *******************************************************************/
393
+ function agile_reload_window() {
394
+ location.reload();
395
+ }
396
+
397
+ /*******************************************************************
398
+ * Will open popup window
399
+ *******************************************************************/
400
+ function popup_window(url, title, parent_win, w, h) {
401
+ let y = parent_win.top.outerHeight / 2 + parent_win.top.screenY - (h / 2);
402
+ let x = parent_win.top.outerWidth / 2 + parent_win.top.screenX - (w / 2);
403
+ let win = parent_win.open(url, 'agile_popup', `toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width=${w}, height=${h}, top=${y}, left=${x}`);
404
+ win.document.title = title;
405
+ return win;
406
+ }
407
+
408
+ /*******************************************************************
409
+ * Will select first editable input field on form. Works only when tab
410
+ * is selected. Still have to find out, what to do on initial display.
411
+ *******************************************************************/
412
+ function select_first_input_field(div_name) {
413
+ $(div_name + " :input:first").each( function() {
414
+ this.focus();
415
+ return true;
416
+ });
417
+ }
418
+
419
+ /*******************************************************************
420
+ * Will scroll to position on the screen. This is replacement for
421
+ * location.hash, which doesn't work in Chrome.
422
+ *
423
+ * Thanks goes to: http://web-design-weekly.com/snippets/scroll-to-position-with-jquery/
424
+ *******************************************************************/
425
+ $.fn.agile_scroll_view = function () {
426
+ return this.each(function () {
427
+ $('html, body').animate({
428
+ scrollTop: $(this).offset().top - 100
429
+ }, 500);
430
+ });
431
+ };
432
+
433
+ /*******************************************************************
434
+ * Updates single field on parent iframe form of embedded form.
435
+ *******************************************************************/
436
+ process_parent_form_updates = function(element) {
437
+ let field = element.getAttribute("data-field");
438
+ let value = element.getAttribute("data-value");
439
+ let selector = '#' + field;
440
+
441
+ // update record
442
+ if (field.match(/record/)) {
443
+ if (window.parent.$(selector).length > 0) {
444
+ if (field.substring(0, 3) === 'td_') { // readonly field
445
+ window.parent.$(selector + ' > div').html(value);
446
+ } else { // input field
447
+ window.parent.$(selector).val(value);
448
+ }
449
+ }
450
+ // any div
451
+ } else {
452
+ if (window.parent.$(selector).length > 0) {
453
+ window.parent.$(selector).html(value);
454
+ }
455
+ }
456
+ };
457
+
458
+ /*****************************************************************
459
+ * Toggle show and hide div
460
+ ******************************************************************/
461
+ agile_div_toggle = function(div) {
462
+ if ($(div).is(":visible")) {
463
+ $(div).slideUp();
464
+ } else {
465
+ $(div).slideDown();
466
+ }
467
+ };
468
+
469
+ /*****************************************************************
470
+ * Process simple ajax call
471
+ ******************************************************************/
472
+ simple_ajax_call = function(url) {
473
+ $.ajax({
474
+ url: url,
475
+ success: function(data) { process_json_result(data); }
476
+ });
477
+ };
478
+
479
+ /*****************************************************************
480
+ * Return value of the input field on a form
481
+ ******************************************************************/
482
+ function agile_field_get_value(field_name) {
483
+ field_name = field_name.replace('record_', '');
484
+ let field = $('[name="record[' + field_name + ']"]');
485
+ return field.val();
486
+ }
487
+
488
+ /*****************************************************************
489
+ * Will process data-fields attribute and add field values as parameters to url
490
+ ******************************************************************/
491
+ function agile_url_add_params(form, url) {
492
+ // check if data-fields attribute present
493
+ let fields = form.getAttribute("data-fields");
494
+ if (fields === null) return url;
495
+ // url might already contain ?
496
+ let form_parms = '?';
497
+ if (url.match(/\?/)) form_parms = '';
498
+
499
+ fields.split(',').forEach( function(field) {
500
+ let value = agile_field_get_value(field);
501
+ if (value) form_parms += '&' + field + '=' + value;
502
+ });
503
+ return url + form_parms;
504
+ }
505
+
506
+ /*******************************************************************
507
+ * Copy div text to clipboard
508
+ *******************************************************************/
509
+ function agile_copy_to_clipboard(div) {
510
+ let copyText = document.getElementById(div).innerText;
511
+ // Copy the text inside the text field
512
+ navigator.clipboard.writeText(copyText);
513
+ }
514
+
515
+ /*
516
+ function handleIframeBlurred(iframe) {
517
+ console.log('iframe blurred');
518
+ jQuery('#agile_form').submit();
519
+ // Additional logic that you need to implement here when blurred
520
+ }
521
+ */
522
+ /*
523
+ function handleIframeBlurred(){
524
+ console.log('iframe blurred');
525
+ // Additional logic that you need to implement here when blurred
526
+ }
527
+
528
+ function handleIframeFocus(){
529
+ console.log('iframe focus');
530
+ // Additional logic that you need to implement here when blurred
531
+ }
532
+
533
+ var active_iframe = null;
534
+ */
535
+
536
+ /*******************************************************************
537
+ * Events start here
538
+ *******************************************************************/
539
+ $(document).ready( function() {
540
+ /* This could be the way to focus on first input field on document open
541
+ if ( $('.ar-form')[0] ) {
542
+ // resize parent iframe to fit selected tab size
543
+ var div_height = $('.ar-form')[0].clientHeight + 130;
544
+ window.frameElement.style.height = div_height.toString() + 'px';
545
+ // select_first_input_field('.ar-form');
546
+ }
547
+ */
548
+ /*
549
+ $('#if_#{@yaml['name']}').addEventListener('blur', handleIframeBlurred);
550
+ $('#if_#{@yaml['name']}').addEventListener('focus', handleIframeFocus);
551
+
552
+ console.log(jQuery(".iframe_embedded").length);
553
+ console.log(window.frameElement);
554
+ */
555
+ /*******************************************************************
556
+ * It will scroll display to ypos if return_to_ypos parameter is present
557
+ *******************************************************************/
558
+ if (window.location.href.match(/return_to_ypos=/))
559
+ {
560
+ window.scrollTo(0, $.getUrlParam('return_to_ypos'));
561
+ }
562
+
563
+ /*******************************************************************
564
+ * The idea is to update fields on parent iframe form, when embedded document
565
+ * is updated in its iframe. Update fields are listed in .ar-form-updates div
566
+ * and are set by flash[:update] Hash object.
567
+ *
568
+ * eg. flash[:update] = {'td_record_radonly' => 'New value for read_only field',
569
+ * 'record_name' => 'New name'}
570
+ *******************************************************************/
571
+ if ( $('.ar-form-updates').length > 0 ) {
572
+ $('.ar-form-updates').children().each( function( index, element ) {
573
+ process_parent_form_updates(element);
574
+ });
575
+ }
576
+
577
+ /*******************************************************************
578
+ * Register ad clicks
579
+ *******************************************************************/
580
+ $('a.link_to_track').click(function() {
581
+ $.post('/agile/ad_click', { id: this.id });
582
+ return true;
583
+ });
584
+
585
+ /*****************************************************************
586
+ * Toggle CMS mode. When clicked on left 30 pixels, window will be scrolled approximately
587
+ * to the position wher toggle was clicked. When clicked from pixel 31 and on it will
588
+ * stay on the top of window.
589
+ ******************************************************************/
590
+ $('.cms-toggle').bind('click', function(e) {
591
+ var url = '/agile/toggle_edit_mode?return_to=' + window.location.href;
592
+ if (e.pageX < 30) url = url + '&return_to_ypos=' + e.pageY ;
593
+ window.location.href = url;
594
+ });
595
+
596
+ /*******************************************************************
597
+ * Popup or close CMS edit menu on icon click
598
+ *******************************************************************/
599
+ $('.ar_popmenu').on('click',function(e) {
600
+ $(e.target).parents('dl:first').find('ul').toggleClass('div-hidden');
601
+ });
602
+
603
+ /*******************************************************************
604
+ * Popup CMS edit menu option clicked
605
+ *******************************************************************/
606
+ $('.ar_popmenu_item').on('click',function(e) {
607
+ let url = e.target.getAttribute("data-url");
608
+ $('#iframe_cms').attr('src', url);
609
+ // $('#iframe_cms').width(1000).height(1000);
610
+ // scroll to top of page and hide menu
611
+ window.scrollTo(0,0);
612
+ $(e.target).parents('dl:first').find('ul').toggleClass('div-hidden');
613
+ });
614
+
615
+ /*******************************************************************
616
+ * Sort action clicked on browser
617
+ *******************************************************************/
618
+ $('.ar-sort-select').change( function(e) {
619
+ let table = e.target.getAttribute("data-table");
620
+ let form = e.target.getAttribute("data-form");
621
+ if (form === null) form = table;
622
+ let sort = e.target.value;
623
+ // e.target.value = null;
624
+ let url = "/agile/run?control=agile.sort&sort=" + sort + "&table=" + table + "&form_name=" + form;
625
+ simple_ajax_call(url);
626
+ });
627
+
628
+ /*******************************************************************
629
+ * Click on field name in result header perform sort action
630
+ *******************************************************************/
631
+ $('.ar-result-header span').on('click',function(e) {
632
+ let url = e.target.getAttribute("data-url");
633
+ simple_ajax_call(url);
634
+ });
635
+
636
+ /*******************************************************************
637
+ * Click on ar-check-all icon. Check or uncheck all checkboxes
638
+ *******************************************************************/
639
+ $('.ar-check-all').on('click',function(e) {
640
+ let checkboxes = $('.ar-check');
641
+ if ($(this).hasClass('mi-check_box')) {
642
+ // check all checkboxes
643
+ checkboxes.each( function() {
644
+ $(this).prop('checked', true);
645
+ $(this).parent().closest('div').addClass('ar-checked');
646
+ });
647
+ $(this).removeClass('mi-check_box').addClass('mi-check_square');
648
+ } else {
649
+ // uncheck all checkboxes
650
+ checkboxes.each( function() {
651
+ $(this).prop('checked', false);
652
+ $(this).parent().closest('div').removeClass('ar-checked');
653
+ });
654
+ $(this).removeClass('mi-check_square').addClass('mi-check_box');
655
+ }
656
+ });
657
+
658
+ /*******************************************************************
659
+ * Click on ar-check icon. Change color of background of element
660
+ *******************************************************************/
661
+ $('.ar-check').on('click',function(e) {
662
+ let parent = $(this).parent().closest('div');
663
+ if ($(this).prop('checked')) {
664
+ parent.addClass('ar-checked');
665
+ } else {
666
+ parent.removeClass('ar-checked');
667
+ }
668
+ });
669
+
670
+ /*******************************************************************
671
+ * Tab clicked on form. Hide old and show selected div.
672
+ *******************************************************************/
673
+ $('.ar-form-li').on('click', function(e) {
674
+ // find li with ar-form-li-selected class. This is our old tab
675
+ let old_tab_id = null;
676
+ $(e.target).parents('ul').find('li').each( function() {
677
+ if ($(this).hasClass('ar-form-li-selected')) {
678
+ // when not already selected toggle ar-form-li-selected class and save old tab
679
+ if ($(this) !== $(e.target)) {
680
+ $(this).toggleClass('ar-form-li-selected');
681
+ $(e.target).toggleClass('ar-form-li-selected');
682
+ old_tab_id = this.getAttribute("data-div");
683
+ }
684
+ return false;
685
+ }
686
+
687
+ }); // show selected data div
688
+ if (old_tab_id !== null) {
689
+ $('#data_' + old_tab_id).toggleClass('div-hidden');
690
+ $('#data_' + e.target.getAttribute("data-div")).toggleClass('div-hidden');
691
+
692
+ // resize parent iframe to fit selected tab size
693
+ let div_height = document.body.scrollHeight;
694
+ let frame = window.frameElement
695
+ if (frame === null) frame = document.body;
696
+ frame.style.height = div_height.toString() + 'px';
697
+
698
+ select_first_input_field('#data_' + e.target.getAttribute("data-div"));
699
+ update_embedded_on_tab_select('#data_' + e.target.getAttribute("data-div"));
700
+ }
701
+ });
702
+
703
+ /*******************************************************************
704
+ ******************************************************************/
705
+ $('.xar-form-accordion').on('click', function(e) {
706
+ $(this).toggleClass('open');
707
+ $('#tab-' + e.target.id).toggleClass('div-hidden');
708
+ if ($('#tab-' + e.target.id).is(':visible')) {
709
+ select_first_input_field('#tab-' + e.target.id);
710
+ }
711
+ });
712
+ /*******************************************************************
713
+ ******************************************************************/
714
+ $('.ar-form-accordion').on('click', function(e) {
715
+ $(this).toggleClass('open');
716
+ let data_div = '#' + e.target.id.replace('acc_', 'data_');
717
+ $(data_div).toggleClass('div-hidden');
718
+ if ($(data_div).is(':visible')) {
719
+ select_first_input_field(data_div);
720
+ update_embedded_on_tab_select(data_div);
721
+ }
722
+ });
723
+
724
+ /*******************************************************************
725
+ * refresh (reload) button clicked
726
+ *******************************************************************/
727
+ $('.ar-link.refresh').on('click', function(e) {
728
+ window.location.href = window.location.href;
729
+ });
730
+
731
+ /*******************************************************************
732
+ * close window
733
+ *******************************************************************/
734
+ $('.ar-link.close').on('click', function(e) {
735
+ window.close();
736
+ });
737
+
738
+ /*******************************************************************
739
+ * History back
740
+ *******************************************************************/
741
+ $('.ar-link.back').on('click', function(e) {
742
+ history.back();
743
+ });
744
+
745
+
746
+ /*******************************************************************
747
+ * Resize iframe_cms to the size of its contents. Make at least 500 px high
748
+ * unless on initial display.
749
+ *******************************************************************/
750
+ $('#iframe_cms').on('load', function() {
751
+ let new_height = this.contentWindow.document.body.offsetHeight + 50;
752
+ if (new_height < 500 && new_height > 60) new_height = 500;
753
+ this.style.height = new_height + 'px';
754
+ // scroll to top
755
+ $('#iframe_cms').agile_scroll_view();
756
+ });
757
+
758
+ /*******************************************************************
759
+ * Same goes for editiframe. Resize it + 30px
760
+ * unless on initial display with no data
761
+ *******************************************************************/
762
+ $('#iframe_edit').on('load', function() {
763
+ if (this.contentWindow.document.body.offsetHeight > 10) {
764
+ this.style.height = (this.contentWindow.document.body.offsetHeight + 30) + 'px';
765
+
766
+ $('#iframe_edit').agile_scroll_view();
767
+ }
768
+ });
769
+
770
+ /*******************************************************************
771
+ * Same goes for iframe_embedded. Resize it + 30px
772
+ *******************************************************************/
773
+ $('.iframe_embedded').on('load', function() {
774
+ let embedded_height = this.contentWindow.document.body.offsetHeight;
775
+ // workaround. It gets tricky when embedded field is on tab
776
+ if (embedded_height == 0) embedded_height = 50;
777
+ this.style.height = (embedded_height + 30) + 'px';
778
+ // resize parent iframe window too
779
+ let parentWindow = this.contentWindow.parent;
780
+ let parent_height = (parentWindow.document.body.offsetHeight + 30) + 'px';
781
+ //parentWindow.frameElement.setAttribute('style', 'height:' + parent_height);
782
+ parentWindow.frameElement.style.height = parent_height;
783
+ /*
784
+ // register blur event
785
+ this.contentWindow.addEventListener('blur', handleIframeBlurred(this));
786
+ this.contentWindow.addEventListener('focusout', handleIframeBlurred(this));
787
+ this.contentWindow.addEventListener('focus', handleIframeFocus(this));
788
+ */
789
+ });
790
+
791
+ /*******************************************************************
792
+ * Process Ajax call on form actions
793
+ *******************************************************************/
794
+ $('.ar-link-ajax').on('click', function(e) {
795
+ // confirmation if required
796
+ if (confirmation_is_cancelled(this)) {return false;}
797
+
798
+ // url must be specified in data-url
799
+ let url = this.getAttribute("data-url");
800
+ if (url.length < 5) return false;
801
+
802
+ // check HTML5 validations
803
+ let validate = this.getAttribute("data-validate");
804
+ if (validate == null || validate == true) {
805
+ if ($("form")[0] && !$("form")[0].checkValidity()) {
806
+ $("form")[0].reportValidity();
807
+ return false;
808
+ }
809
+ }
810
+
811
+ // update html editor fields before data serialization
812
+ let cke_elements = document.querySelectorAll(`div[id^="cke_record"]`);
813
+ cke_elements.forEach(e => {
814
+ let field_id = e.id.replace('cke_', '');
815
+ let text = CKEDITOR.instances[field_id].getData();
816
+ $('#' + field_id).val(text);
817
+ });
818
+
819
+ let data = {};
820
+ let request = this.getAttribute("data-request");
821
+ switch (request) {
822
+ case 'script':
823
+ eval(this.getAttribute("data-script"));
824
+ return false;
825
+
826
+ case 'post':
827
+ data = $('form').serialize();
828
+ break;
829
+
830
+ default:
831
+ request = 'get'; // by default
832
+ }
833
+
834
+ // add checkbox id-s to data if checkboxes present
835
+ let checkboxes = $('.ar-check');
836
+ if (checkboxes.length > 0) {
837
+ let checked = [];
838
+ checkboxes.each( function() {
839
+ if ($(this).prop('checked')) checked.push($(this).attr("id").replace('check-', ''));
840
+ })
841
+ data['checked'] = checked;
842
+ }
843
+
844
+ $('.ar-spinner').show();
845
+ $.ajax({
846
+ url: url,
847
+ type: request,
848
+ dataType: "json",
849
+ data: data,
850
+ success: function(data) {
851
+ process_json_result(data);
852
+ $('.ar-spinner').hide();
853
+ },
854
+ error: function (request, status, error) {
855
+ $('.ar-spinner').css('color','red');
856
+ alert(request.responseText);
857
+ }
858
+ });
859
+ });
860
+
861
+ /*******************************************************************
862
+ * Click on filter off
863
+ *******************************************************************/
864
+ $('.mi-filter_alt_off').on('click', function(e) {
865
+ let url = $(this).parents('.ar-filter').attr("data-url");
866
+ if (url.length > 5) simple_ajax_call(url);
867
+ });
868
+
869
+ /*******************************************************************
870
+ * Process action submit button click.
871
+ *******************************************************************/
872
+ $('.ar-action-submit').on('click', function(e) {
873
+ // confirmation if required
874
+ if (confirmation_is_cancelled(this)) {return false;}
875
+
876
+ // check HTML5 validations
877
+ var form = $("form")[0];
878
+ if (form && !form.checkValidity() ) {
879
+ form.reportValidity();
880
+ return false;
881
+ }
882
+ var url = this.getAttribute("data-url");
883
+ if (url == null) {return false;}
884
+
885
+ form.setAttribute('action', url);
886
+ form.setAttribute('method', "post");
887
+ form.submit();
888
+ });
889
+
890
+ /*******************************************************************
891
+ Will open a new window with URL specified.
892
+ ********************************************************************/
893
+ $('.ar-window-open').on('click', function(e) {
894
+ // confirmation if required
895
+ if (confirmation_is_cancelled(this)) return false;
896
+
897
+ let url = this.getAttribute("data-url");
898
+ let title = this.getAttribute("title");
899
+ let w = this.getAttribute("data-x") || 1000;
900
+ let h = this.getAttribute("data-y") || 800;
901
+
902
+ url = agile_url_add_params(this, url)
903
+ let win = popup_window(url, title, window, w, h);
904
+ win.focus();
905
+ });
906
+
907
+ /*******************************************************************
908
+ Will open a new popup with URL specified.
909
+ ********************************************************************/
910
+ $('.ar-popup-open').on('click', function(e) {
911
+ // confirmation if required
912
+ if (confirmation_is_cancelled(this)) return false;
913
+
914
+ let url = this.getAttribute("data-url");
915
+ let title = this.getAttribute("title");
916
+ let w = this.getAttribute("data-x") || 1000;
917
+ let h = this.getAttribute("data-y") || 800;
918
+
919
+ url = agile_url_add_params(this, url)
920
+ $('#popup').bPopup({ loadUrl: url,
921
+ transition: 'slideDown', transitionClose: 'slideDown', speed: 300,
922
+ opacity: 0, position: ['auto', 20],
923
+ closeClass: 'ar-link'
924
+ });
925
+ });
926
+
927
+ /*******************************************************************
928
+ * Animate button on click
929
+ ******************************************************************
930
+ $('.xar-action-menu li').mousedown( function() {
931
+ $(this).toggleClass('ar-animate-button');
932
+ });
933
+
934
+ ******************************************************************
935
+ * Animate button on click
936
+ ******************************************************************
937
+ $('.xar-action-menu li').mouseup( function() {
938
+ $(this).toggleClass('ar-animate-button');
939
+ });
940
+ */
941
+
942
+ /*******************************************************************
943
+ * App menu option clicked
944
+ *******************************************************************/
945
+ $('.app-menu-item a').on('click', function(e) {
946
+ /* parent of a is li */
947
+ $(e.target).parents('li').each( function() {
948
+ /* for all li's in ul, deselect */
949
+ $(this).parents('ul').find('li').each( function() {
950
+ if ($(this).hasClass('app-menu-item-selected')) {
951
+ $(this).toggleClass('app-menu-item-selected');
952
+ }
953
+ });
954
+ /* select clicked li */
955
+ $(this).toggleClass('app-menu-item-selected');
956
+ });
957
+ });
958
+
959
+ /*******************************************************************
960
+ * Display spinner on link with spinner, submit link
961
+ *******************************************************************/
962
+ $('.ar-link.spin').on('click', function(e) {
963
+ $('.ar-spinner').show();
964
+ });
965
+
966
+ $('.ar-link-submit').on('click', function(e) {
967
+ $('.ar-spinner').show();
968
+ });
969
+
970
+ /*******************************************************************
971
+ * Hide spinner when validation error occured
972
+ *******************************************************************/
973
+ $(':input').on("invalid", function(event) {
974
+ $('.ar-spinner').hide();
975
+ });
976
+
977
+ /*******************************************************************
978
+ * Add button clicked while in edit. Create window dialog for adding new record
979
+ * into required table. This is helper scenario, when user is selecting
980
+ * data from with text_autocomplete and data doesn't exist in belongs_to table.
981
+ *******************************************************************/
982
+ $('.in-edit-add').on('click', function(e) {
983
+ let id = this.getAttribute("data-id");
984
+ let table = this.getAttribute("data-table");
985
+ let url = '/agile/new?window_close=0&table=' + table;
986
+ if (id) {
987
+ url = '/agile/' + id + '/edit?window_close=0&table=' + table;
988
+ }
989
+ let w = popup_window(url, '', window, 1000, 800);
990
+ w.focus();
991
+ });
992
+
993
+ /**********************************************************************
994
+ * When filter_field (field name) is selected on filter subform this routine finds
995
+ * and displays appropriate span with input field.
996
+ **********************************************************************/
997
+ $('#filter_field').on('change', function() {
998
+ if (this.value.length > 0) {
999
+ let name = 'filter_' + this.value;
1000
+ $(this).parents('form').find('span').each( function() {
1001
+
1002
+ if ($(this).attr('id') == name) {
1003
+ if ( $(this).hasClass('div-hidden') ) { $(this).toggleClass('div-hidden'); }
1004
+ } else {
1005
+ if ( !$(this).hasClass('div-hidden') ) { $(this).toggleClass('div-hidden'); }
1006
+ }
1007
+ });
1008
+ }
1009
+ });
1010
+
1011
+ /*******************************************************************
1012
+ * It is not possible to attach any data to submit button except the text
1013
+ * that is written on a button and it is therefore very hard to distinguish
1014
+ * which button was pressed when more than one button is present on a form.
1015
+ *
1016
+ * The purpose of this trigger is to append data hidden in html5 data attributes
1017
+ * to the form. We can now attach any kind of data to submit button and data
1018
+ * will be passed as data[] parameters to controller.
1019
+ *******************************************************************/
1020
+ $('.ar-submit').on('click', function() {
1021
+ $.each(this.attributes, function() {
1022
+ if (this.name.substring(0,5) == 'data-') {
1023
+ $('<input>').attr({
1024
+ type: 'hidden',
1025
+ name: 'data[' + this.name.substring(5) + ']',
1026
+ value: this.value
1027
+ }).appendTo('form');
1028
+ }
1029
+ });
1030
+ });
1031
+
1032
+ /* DOCUMENT INFO */
1033
+ /*******************************************************************
1034
+ * Popup or hide document information
1035
+ *******************************************************************/
1036
+ $('#ar-document-info').on('click',function(e) {
1037
+ popup = $('#ar-document-info-popup');
1038
+ popup.toggleClass('div-hidden');
1039
+ if (!popup.hasClass('div-hidden')) {
1040
+ var o = {
1041
+ left: e.pageX - popup.width() - 10,
1042
+ top: e.pageY - popup.height() - 20
1043
+ };
1044
+ popup.offset(o);
1045
+ };
1046
+ });
1047
+ /*******************************************************************
1048
+ * Just hide document information on click.
1049
+ *******************************************************************/
1050
+ $('#ar-document-info-popup').on('click',function(e) {
1051
+ $('#ar-document-info-popup').toggleClass('div-hidden');
1052
+ });
1053
+
1054
+ /*******************************************************************
1055
+ * Experimental. Force reload of parent page if this div appears.
1056
+ *******************************************************************/
1057
+ $('#div-reload-parent').on('load', function() {
1058
+ // alert('div-reload-parent 1');
1059
+ parent.location.href = parent.location.href;
1060
+ });
1061
+
1062
+ /*******************************************************************
1063
+ * Force reload of parent page if this div appears.
1064
+ *
1065
+ * Just an Idea. Not needed yet.
1066
+ *******************************************************************/
1067
+ $('#div-reload').on('load', function() {
1068
+ alert('div-reload 1');
1069
+ // location.href = location.href;
1070
+ });
1071
+
1072
+ $('#div-reload-parent').on('DOMNodeInserted DOMNodeRemoved', function() {
1073
+ alert('div-reload-parent 2');
1074
+ });
1075
+ $('#div-reload').on('DOMNodeInserted DOMNodeRemoved', function() {
1076
+ alert('div-reload 2');
1077
+ });
1078
+
1079
+ /*******************************************************************
1080
+ * Fire action (by default show document) when doubleclicked on result row
1081
+ *******************************************************************/
1082
+ $('.ar-result tr').on('dblclick', function(e) {
1083
+ let url = String( this.getAttribute("data-dblclick") );
1084
+ // prevent when data-dblclick not set
1085
+ if (url.length > 5) {
1086
+ e.preventDefault();
1087
+ location.href = url;
1088
+ }
1089
+ });
1090
+
1091
+ /*******************************************************************
1092
+ * Fire action (by default show document) when doubleclicked on result row
1093
+ *******************************************************************/
1094
+ $('.ar-result-data').on('dblclick', function(e) {
1095
+ let url = String( this.getAttribute("data-dblclick") );
1096
+ // prevent when data-dblclick not set
1097
+ if (url.length > 5) {
1098
+ e.preventDefault();
1099
+ location.href = url;
1100
+ }
1101
+ });
1102
+
1103
+ /*******************************************************************
1104
+ * Fire action clicked on result row.
1105
+ * TODO: Find out how to prevent event when clicked on action icon.
1106
+ *******************************************************************/
1107
+ $('.ar-result tr').on('click', function(e) {
1108
+ url = String( this.getAttribute("data-click") );
1109
+ // prevent when data-click not set
1110
+ if (url.length > 5) {
1111
+ e.preventDefault();
1112
+ location.href = url;
1113
+ }
1114
+ });
1115
+
1116
+ $('#1menu-filter').on('click', function(e) {
1117
+ let target = e.target;
1118
+ req = target.getAttribute("data-request");
1119
+ $('.menu-filter').toggle(300);
1120
+ });
1121
+
1122
+ /*******************************************************************
1123
+ * Pressing Enter in search field will trigger click event on search icon
1124
+ * and thus force search event.
1125
+ *******************************************************************/
1126
+ $('#_record__filter_field').keydown( function(e) {
1127
+ if (e.which == '13' || e.which == '9') {
1128
+ e.preventDefault();
1129
+ $('.record_filter_field_icon').trigger("click")
1130
+ };
1131
+ });
1132
+
1133
+ /*******************************************************************
1134
+ * Will set internal filter value and fire reload event to enable
1135
+ * filtering documents on index action.
1136
+ *******************************************************************/
1137
+ $('.record_filter_field_icon').on('click', function(e) {
1138
+ let field = $('#_record__filter_field');
1139
+ let url = $(this).parents('span').attr("data-url");
1140
+ let value = null;
1141
+
1142
+ if (field.is(':checkbox')) {
1143
+ value = field.is(':checked'); }
1144
+ else {
1145
+ value = field.val();
1146
+ }
1147
+ url = url + "&filter_value=" + value;
1148
+ simple_ajax_call(url);
1149
+ });
1150
+
1151
+ /*******************************************************************
1152
+ * Click on show filter form
1153
+ *******************************************************************/
1154
+ $('#open_ar_filter').on('click', function(e) {
1155
+ $('#ar_filter').bPopup({
1156
+ transition: 'slideDown',
1157
+ transitionClose: 'slideDown',
1158
+ speed: 300,
1159
+ opacity: 0,
1160
+ position: ['auto', 20],
1161
+ closeClass: 'ar-link' });
1162
+ });
1163
+
1164
+ /*******************************************************************
1165
+ * Click on preview selected image
1166
+ *******************************************************************/
1167
+ $('.ar-image-preview').on('click', function(e) {
1168
+ let img = $(this).children(":first").attr('src');
1169
+ $('#ar-image-preview').bPopup({
1170
+ content: 'image', //'ajax', 'iframe' or 'image'
1171
+ contentContainer: '#ar-image-preview',
1172
+ loadUrl: img,
1173
+ opacity: 0
1174
+ });
1175
+ });
1176
+
1177
+ /*******************************************************************
1178
+ * Set new filter
1179
+ *******************************************************************/
1180
+ $('.ar-filter-set').on('click', function(e) {
1181
+ let url = $(this).attr( 'data-url' );
1182
+ let field = $('select#filter_field1').val();
1183
+ let operation = $('select#filter_oper').val();
1184
+ url = url + '&filter_field=' + field + '&filter_oper=' + operation
1185
+ simple_ajax_call(url);
1186
+ });
1187
+
1188
+ /*******************************************************************
1189
+ * Toggle one CMS menu level
1190
+ *******************************************************************/
1191
+ $('.agile-top-level-menu div').on('click', function(e) {
1192
+ $(e.target).siblings('ul').toggle('fast');
1193
+ $(e.target).toggleClass('expanded');
1194
+ });
1195
+
1196
+ /*******************************************************************
1197
+ * Toggle dataset record menu
1198
+ *
1199
+ * This and additional two event hadlers provide expected behavior of submenus popup and close.
1200
+ *******************************************************************/
1201
+ $('.ar-result-submenu .mi-more_vert').on('click', function(e) {
1202
+ let ul = $(e.target).siblings('ul');
1203
+ // hide last selected menu if not the same
1204
+ if (typeof agile_last_menu_selected !== 'undefined') { agile_last_menu_selected.hide(); }
1205
+ // if menu is past the bottom fix it to bottom
1206
+ let menu_bottom = ul.height() + ul.parent().position().top + 20;
1207
+ if (menu_bottom > $(document).height()) ul.css('bottom', 0);
1208
+ ul.show();
1209
+ agile_last_menu_selected = ul;
1210
+ });
1211
+
1212
+ /*******************************************************************
1213
+ * Result record menu has lost focus. Hide menu.
1214
+ *******************************************************************/
1215
+ $('.ar-result-submenu ul').hover(function(e) {
1216
+ },
1217
+ function(e) {
1218
+ agile_last_menu_selected.hide();
1219
+ agile_last_menu_selected = undefined;
1220
+ });
1221
+
1222
+ /*******************************************************************
1223
+ * dataset record menu is left open if action is canceled. Ex. delete confirm. This will hide menu.
1224
+ *******************************************************************/
1225
+ $('.ar-result-submenu ul li').on('click', function(e) {
1226
+ if (typeof agile_last_menu_selected !== 'undefined') agile_last_menu_selected.hide();
1227
+ });
1228
+
1229
+ /*******************************************************************
1230
+ * Resize result table columns. For now an idea.
1231
+ *******************************************************************/
1232
+ /*
1233
+ $( ".ar-result-header .spacer" )
1234
+ .mouseenter(function() {
1235
+ console.log("enter");
1236
+ })
1237
+ .mouseleave(function() {
1238
+ console.log("leave");
1239
+ });
1240
+ */
1241
+ /*******************************************************************
1242
+ * number_field type entered
1243
+ *******************************************************************/
1244
+ $('.ar-number').on('focus', function(e) {
1245
+ var separator = $(this).attr("data-separator") || ',';
1246
+ var field = '#' + $(this).attr("id").slice(0,-1);
1247
+ var value = $(field).val().replace('.',separator);
1248
+ $(this).val( value );
1249
+ $(this).select();
1250
+ });
1251
+
1252
+ /*******************************************************************
1253
+ * number_field type leaved
1254
+ *******************************************************************/
1255
+ $('.ar-number').on('focusout', function(e) {
1256
+ var decimals = $(this).attr("data-decimal") || 2;
1257
+ var delimiter = $(this).attr("data-delimiter") || '.';
1258
+ var separator = $(this).attr("data-separator") || ',';
1259
+ var currency = $(this).attr("data-currency") || '';
1260
+ var val = this.value;
1261
+ // clear delimiters and replace separator with .
1262
+ val = val.replace(delimiter,'');
1263
+ val = val.replace(separator,'.');
1264
+ val = parseFloat(val).toFixed(decimals);
1265
+ var whole, dec, sign;
1266
+ // [whole,dec] = val.split('.');
1267
+ whole = val.split('.')[0];
1268
+ dec = val.split('.')[1];
1269
+ // remove negative sign and add at the end
1270
+ var sign = whole.substr(0,1);
1271
+ if (sign == '-') {
1272
+ whole = whole.substr(1,20);
1273
+ } else {
1274
+ sign = '';
1275
+ }
1276
+ // save value to field holding return value and trigger change event
1277
+ var field = '#' + $(this).attr("id").slice(0,-1);
1278
+ $(field).val(val);
1279
+ $(field).trigger("change")
1280
+
1281
+ // decimal part
1282
+ if (decimals == 0) separator = '';
1283
+ if (dec == null) dec = '';
1284
+ while (dec.length < decimals) dec = dec + '0';
1285
+ // whole part
1286
+ if (whole == null || whole == '') whole = '0';
1287
+ var ar = [];
1288
+ while (whole.length > 0) {
1289
+ var pos1 = whole.length - 3
1290
+ if (pos1 < 0) pos1 = 0;
1291
+ ar.unshift(whole.substr(pos1,3));
1292
+ whole = whole.slice(0, -3);
1293
+ };
1294
+
1295
+ if (delimiter !== '') whole = ar.join(delimiter);
1296
+ $(this).val(sign + whole + separator + dec + currency);
1297
+ });
1298
+
1299
+ /*******************************************************************
1300
+ * Key pressed in number_field.
1301
+ * - put minus sign in front of input field
1302
+ * - replace dot and comma separators when required. Not all numeric pads are created equal.
1303
+ * - when enter is pressed, save value to field before form is proccessed
1304
+ *******************************************************************/
1305
+ $('.ar-number').on('keydown', function(e) {
1306
+ // Minus sign. Put it on first place
1307
+ if (e.which == 109) {
1308
+ if($(this).val().substr(0,1) == '-') {
1309
+ $(this).val( $(this).val().substr(1,20));
1310
+ } else {
1311
+ $(this).val( '-' + $(this).val());
1312
+ }
1313
+ e.preventDefault();
1314
+ }
1315
+ // replace , with . if . is separator.
1316
+ var separator = $(this).attr("data-separator") || '.';
1317
+ var inp = this;
1318
+ if (e.which == 188) {
1319
+ if (separator == '.') {
1320
+ setTimeout(function() {
1321
+ inp.value = inp.value.replace(/,/g, '.');
1322
+ }, 0);
1323
+ }
1324
+ }
1325
+ // replace . with , if , is separator
1326
+ if (e.which == 190) {
1327
+ if (separator == ',') {
1328
+ setTimeout(function() {
1329
+ inp.value = inp.value.replace(/\./g, ',');
1330
+ }, 0);
1331
+ }
1332
+ }
1333
+
1334
+ // Enter means process form. Save the value before form is processed
1335
+ if (e.which == 13) {
1336
+ var decimals = $(this).attr("data-decimal") || 2;
1337
+ var value = $(this).val().replace(separator,'.');
1338
+ var field = '#' + $(this).attr("id").slice(0,-1);
1339
+
1340
+ $(field).val( parseFloat(value).toFixed(decimals) );
1341
+ }
1342
+ });
1343
+
1344
+ /*******************************************************************
1345
+ * Slovenian keyboard has comma key instead of dot in numeric pad.
1346
+ * This will catch if comma has been pressed and will replace it with dot.
1347
+ *******************************************************************/
1348
+ $('.date-picker').keypress( function(e) {
1349
+ if (e.keyCode !== 44) return;
1350
+ var inp = this;
1351
+ setTimeout(function() {
1352
+ inp.value = inp.value.replace(/,/g, '.');
1353
+ }, 0);
1354
+ });
1355
+
1356
+ /*******************************************************************
1357
+ * Result header sort icon is hoverd. Change background icon to filter.
1358
+ *******************************************************************/
1359
+ $('.ar-result-header .th i').hover( function() {
1360
+ old_sort_icon = '';
1361
+ // save old sort icon and replace it with filter icon
1362
+ $.each( $(this).attr("class").split(/\s+/),
1363
+ function(index, item) { if (item.match('sort')) { old_sort_icon = item}; }
1364
+ );
1365
+ $(this).removeClass(old_sort_icon).addClass('mi-ads_click');
1366
+ // bring back old sort icon
1367
+ }, function() {
1368
+ $(this).removeClass('mi-ads_click').addClass(old_sort_icon);
1369
+ });
1370
+
1371
+ /*******************************************************************
1372
+ * Result header sort icon is clicked. Display filter menu for the field.
1373
+ *******************************************************************/
1374
+ $('.ar-result-header .th i').click( function(e) {
1375
+ e.preventDefault();
1376
+ if ($(this).hasClass('no-filter')) return;
1377
+
1378
+ // additional click will close dialog when visible
1379
+ if ($('.filter-popup').is(':visible')) {
1380
+ $('.filter-popup').hide();
1381
+ return;
1382
+ }
1383
+ // retrieve name of current field and set it in popup
1384
+ let header = $(this).closest('.th');
1385
+ let field_name = header.attr("data-name");
1386
+ $('.filter-popup').attr('data-name', field_name);
1387
+ // change popup position and show
1388
+ $('.filter-popup').css({'top': e.pageY + 5, 'left': e.pageX, 'position': 'absolute'});
1389
+ $('.filter-popup').show();
1390
+ });
1391
+
1392
+ /*******************************************************************
1393
+ * Filter operation is clicked on filter popup. Retrieve data and call
1394
+ * filter on action.
1395
+ *******************************************************************/
1396
+ $('.filter-popup li').click( function(e) {
1397
+ let url = $(this).data('url');
1398
+ let operator = $(this).data('operator');
1399
+ let parent = $(this).closest('.filter-popup');
1400
+ let field_name = parent.data("name");
1401
+
1402
+ url = url + '&filter_field=' + field_name + '&filter_oper=' + operator;
1403
+ simple_ajax_call(url);
1404
+ });
1405
+
1406
+ /*****************************************************************
1407
+ * Toggle div
1408
+ ******************************************************************/
1409
+ $(".ar-handle").click(function(e) {
1410
+ let div= this.getAttribute("data-div");
1411
+ agile_div_toggle(div);
1412
+ e.stopImmediatePropagation(); // required
1413
+ });
1414
+
1415
+ /*******************************************************************
1416
+ * Show-Hide CMS menu on hamburger click
1417
+ *******************************************************************/
1418
+ $('#menu-hamburger').on('click', function(e) {
1419
+ $('.agile-container #cms-menu').toggleClass('visible');
1420
+ });
1421
+
1422
+ /*******************************************************************
1423
+ * Iframe lost focus. Save data
1424
+ *******************************************************************/
1425
+ $('.iframe_embedded').on('focusout', function(e) {
1426
+ console.log('focus out');
1427
+ });
1428
+
1429
+ /*******************************************************************
1430
+ * Iframe lost focus. Save data
1431
+ *******************************************************************/
1432
+ $('.iframe_embedded').on('focus', function(e) {
1433
+ console.log('focus');
1434
+ });
1435
+
1436
+ /*******************************************************************
1437
+ * Iframe lost focus. Save data
1438
+ *******************************************************************/
1439
+ $('.iframe_embedded').on('blur', function(e) {
1440
+ console.log('blur');
1441
+ });
1442
+
1443
+ });
1444
+
1445
+ /*******************************************************************
1446
+ * Catch ctrl+s key pressed and fire save form event. I press ctrl+s
1447
+ * almost every minute. That was a lesson learned years ago when I lost
1448
+ * few hours of work on computer lockup ;-(
1449
+ *******************************************************************/
1450
+ $(document).keydown( function(e) {
1451
+ if ((e.which == '115' || e.which == '83' ) && (e.ctrlKey || e.metaKey))
1452
+ {
1453
+ e.preventDefault();
1454
+ document.forms[0].submit();
1455
+ return false;
1456
+ }
1457
+ return true;
1458
+ });
1459
+
1460
+ /*******************************************************************
1461
+ *******************************************************************
1462
+ document.addEventListener('focus',function(e)
1463
+ {
1464
+ if (e !== active_iframe) {
1465
+ if (active_iframe != null) {
1466
+ console.log('drug iframe');
1467
+ }
1468
+ active_iframe = e.target.ownerDocument;
1469
+ }
1470
+ console.log(active_iframe);
1471
+ console.log(e);
1472
+ console.log(e.target.ownerDocument);
1473
+ },
1474
+ true);
1475
+
1476
+ /*******************************************************************
1477
+
1478
+ *******************************************************************
1479
+ $(document).onmousedown( function(e) {
1480
+ mouseDown = true;
1481
+ console.log("mouse down");
1482
+ });
1483
+
1484
+ $(document).onmouseup( function(e) {
1485
+ mouseDown = false;
1486
+ console.log("mouse up");
1487
+ });
1488
+
1489
+ **/