biovision-base 0.22.180920.0 → 0.34.190331.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (223) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -21
  3. data/app/assets/images/biovision/base/icons/apply.svg +4 -0
  4. data/app/assets/images/biovision/base/icons/components/contact.svg +26 -0
  5. data/app/assets/images/biovision/base/icons/components/registration.svg +17 -0
  6. data/app/assets/images/biovision/base/icons/destroy.svg +11 -8
  7. data/app/assets/images/biovision/base/icons/settings.svg +41 -0
  8. data/app/assets/images/biovision/base/icons/slider/arrow-left.svg +4 -0
  9. data/app/assets/images/biovision/base/icons/slider/arrow-right.svg +4 -0
  10. data/app/assets/images/biovision/base/placeholders/16x9.svg +10 -3
  11. data/app/assets/images/biovision/base/placeholders/1x1.svg +12 -0
  12. data/app/assets/images/biovision/base/placeholders/3x2.svg +10 -3
  13. data/app/assets/javascripts/biovision/base/biovision.js +780 -461
  14. data/app/assets/javascripts/biovision/base/components/carousel.js +123 -0
  15. data/app/assets/javascripts/biovision/base/polyfills.js +149 -0
  16. data/app/assets/stylesheets/biovision/base/admin.scss +85 -46
  17. data/app/assets/stylesheets/biovision/base/admin/components.scss +141 -0
  18. data/app/assets/stylesheets/biovision/base/biovision.scss +149 -33
  19. data/app/assets/stylesheets/biovision/base/buttons/buttons-common.scss +22 -0
  20. data/app/assets/stylesheets/biovision/base/default.scss +6 -6
  21. data/app/assets/stylesheets/biovision/base/default_admin.scss +25 -8
  22. data/app/assets/stylesheets/biovision/base/themes/simple-layout.scss +0 -3
  23. data/app/controllers/admin/editable_pages_controller.rb +6 -4
  24. data/app/controllers/admin/privileges_controller.rb +2 -3
  25. data/app/controllers/admin/settings_controller.rb +52 -0
  26. data/app/controllers/admin/simple_blocks_controller.rb +28 -0
  27. data/app/controllers/admin/users_controller.rb +1 -1
  28. data/app/controllers/agents_controller.rb +4 -4
  29. data/app/controllers/authentication_controller.rb +28 -6
  30. data/app/controllers/browsers_controller.rb +4 -4
  31. data/app/controllers/concerns/authentication.rb +8 -5
  32. data/app/controllers/concerns/entity_priority.rb +3 -0
  33. data/app/controllers/concerns/lockable_entity.rb +6 -3
  34. data/app/controllers/concerns/removable_image.rb +4 -5
  35. data/app/controllers/concerns/toggleable_entity.rb +4 -5
  36. data/app/controllers/editable_pages_controller.rb +14 -8
  37. data/app/controllers/fallback_controller.rb +7 -2
  38. data/app/controllers/feedback_requests_controller.rb +23 -8
  39. data/app/controllers/metrics_controller.rb +3 -3
  40. data/app/controllers/my/profiles_controller.rb +32 -15
  41. data/app/controllers/privilege_groups_controller.rb +4 -4
  42. data/app/controllers/privileges_controller.rb +7 -26
  43. data/app/controllers/simple_blocks_controller.rb +63 -0
  44. data/app/controllers/stored_values_controller.rb +4 -4
  45. data/app/controllers/tokens_controller.rb +4 -4
  46. data/app/controllers/users_controller.rb +8 -3
  47. data/app/helpers/biovision_users_helper.rb +60 -21
  48. data/app/helpers/editable_pages_helper.rb +22 -0
  49. data/app/helpers/languages_helper.rb +3 -0
  50. data/app/helpers/simple_image_helper.rb +66 -0
  51. data/app/mailers/application_mailer.rb +0 -2
  52. data/app/mailers/code_sender.rb +13 -2
  53. data/app/mailers/feedback_mailer.rb +5 -2
  54. data/app/mailers/user_mailer.rb +6 -1
  55. data/app/models/biovision_component.rb +43 -0
  56. data/app/models/biovision_parameter.rb +34 -0
  57. data/app/models/code.rb +22 -7
  58. data/app/models/code_type.rb +9 -1
  59. data/app/models/concerns/flat_priority.rb +50 -0
  60. data/app/models/concerns/nested_priority.rb +58 -0
  61. data/app/models/concerns/required_unique_slug.rb +5 -2
  62. data/app/models/editable_page.rb +49 -37
  63. data/app/models/foreign_site.rb +5 -3
  64. data/app/models/language.rb +15 -37
  65. data/app/models/metric.rb +2 -4
  66. data/app/models/privilege.rb +23 -45
  67. data/app/models/privilege_group.rb +6 -1
  68. data/app/models/simple_block.rb +66 -0
  69. data/app/models/user.rb +29 -26
  70. data/app/models/user_privilege.rb +1 -1
  71. data/app/services/biovision/components/base_component.rb +115 -0
  72. data/app/services/biovision/components/registration_component.rb +98 -0
  73. data/app/services/code_manager.rb +4 -1
  74. data/app/services/code_manager/confirmation.rb +8 -4
  75. data/app/services/code_manager/invitation.rb +9 -5
  76. data/app/services/code_manager/recovery.rb +9 -6
  77. data/app/services/user_manager.rb +5 -4
  78. data/app/services/user_profile_handler.rb +38 -11
  79. data/app/uploaders/avatar_uploader.rb +5 -1
  80. data/app/uploaders/editable_page_image_uploader.rb +4 -6
  81. data/app/uploaders/media_file_uploader.rb +6 -4
  82. data/app/uploaders/media_snapshot_uploader.rb +6 -2
  83. data/app/uploaders/simple_file_uploader.rb +12 -0
  84. data/app/uploaders/simple_image_uploader.rb +75 -0
  85. data/app/views/about/editable.html.erb +1 -1
  86. data/app/views/admin/agents/_filter.html.erb +1 -1
  87. data/app/views/admin/editable_pages/entity/_in_list.html.erb +16 -0
  88. data/app/views/admin/editable_pages/index.html.erb +8 -1
  89. data/app/views/admin/editable_pages/show.html.erb +21 -17
  90. data/app/views/admin/feedback_requests/entity/_in_list.html.erb +9 -1
  91. data/app/views/admin/index/_biovision_base.html.erb +5 -7
  92. data/app/views/admin/index/_components.html.erb +9 -0
  93. data/app/views/admin/index/dashboard/_editorial.html.erb +2 -2
  94. data/app/views/admin/index/dashboard/_settings.html.erb +2 -2
  95. data/app/views/admin/index/index.html.erb +2 -0
  96. data/app/views/admin/privileges/entity/_groups.html.erb +25 -25
  97. data/app/views/admin/privileges/entity/_in_list.html.erb +30 -9
  98. data/app/views/admin/privileges/index.html.erb +6 -1
  99. data/app/views/admin/privileges/show.html.erb +31 -18
  100. data/app/views/admin/settings/component/_new_parameter.html.erb +62 -0
  101. data/app/views/admin/settings/component/_parameters.html.erb +37 -0
  102. data/app/views/admin/settings/component/_setting.html.erb +18 -0
  103. data/app/views/admin/settings/component/_settings.html.erb +31 -0
  104. data/app/views/admin/settings/index.html.erb +27 -0
  105. data/app/views/admin/settings/show.html.erb +42 -0
  106. data/app/views/admin/simple_blocks/_nav_item.html.erb +6 -0
  107. data/app/views/admin/simple_blocks/entity/_in_list.html.erb +23 -0
  108. data/app/views/admin/simple_blocks/index.html.erb +21 -0
  109. data/app/views/admin/simple_blocks/show.html.erb +51 -0
  110. data/app/views/admin/tokens/_filter.html.erb +1 -1
  111. data/app/views/admin/users/_filter.html.erb +1 -1
  112. data/app/views/admin/users/entity/_privilege.html.erb +12 -21
  113. data/app/views/admin/users/entity/_privilege_tree.html.erb +17 -15
  114. data/app/views/admin/users/privileges.html.erb +9 -1
  115. data/app/views/admin/users/search.jbuilder +3 -3
  116. data/app/views/admin/users/show.html.erb +11 -6
  117. data/app/views/agents/_filter.html.erb +22 -22
  118. data/app/views/agents/_form.html.erb +25 -29
  119. data/app/views/application/forbidden.html.erb +9 -0
  120. data/app/views/application/forbidden.jbuilder +3 -0
  121. data/app/views/authentication/_form.html.erb +13 -13
  122. data/app/views/browsers/_form.html.erb +21 -25
  123. data/app/views/editable_blocks/_entity.html.erb +4 -1
  124. data/app/views/editable_pages/_form.html.erb +63 -62
  125. data/app/views/editable_pages/edit.html.erb +1 -1
  126. data/app/views/editable_pages/entity/_metadata.html.erb +4 -4
  127. data/app/views/editable_pages/new.html.erb +3 -3
  128. data/app/views/fallback/show.html.erb +8 -1
  129. data/app/views/feedback_requests/_form.html.erb +76 -52
  130. data/app/views/feedback_requests/create.js.erb +1 -10
  131. data/app/views/index/index/_editable.html.erb +0 -4
  132. data/app/views/metrics/_form.html.erb +38 -36
  133. data/app/views/my/confirmations/show.html.erb +5 -3
  134. data/app/views/my/profiles/closed.html.erb +7 -0
  135. data/app/views/my/profiles/edit/_form.html.erb +9 -9
  136. data/app/views/my/profiles/new.html.erb +9 -1
  137. data/app/views/my/profiles/new/_form.html.erb +22 -1
  138. data/app/views/my/recoveries/show.html.erb +2 -2
  139. data/app/views/privilege_groups/_form.html.erb +48 -21
  140. data/app/views/privileges/_form.html.erb +68 -36
  141. data/app/views/profiles/_profile.html.erb +3 -3
  142. data/app/views/shared/_pagination.jbuilder +7 -5
  143. data/app/views/shared/admin/_breadcrumbs.html.erb +1 -1
  144. data/app/views/shared/forms/_meta_texts.html.erb +3 -3
  145. data/app/views/shared/forms/_priority.html.erb +12 -0
  146. data/app/views/shared/forms/_state_container.html.erb +1 -0
  147. data/app/views/simple_blocks/_empty.html.erb +0 -0
  148. data/app/views/simple_blocks/_form.html.erb +120 -0
  149. data/app/views/simple_blocks/_simple_block.html.erb +26 -0
  150. data/app/views/simple_blocks/edit.html.erb +17 -0
  151. data/app/views/simple_blocks/new.html.erb +15 -0
  152. data/app/views/stored_values/_form.html.erb +57 -28
  153. data/app/views/tokens/_form.html.erb +31 -26
  154. data/app/views/users/_form.html.erb +1 -1
  155. data/config/initializers/carrierwave.rb +7 -2
  156. data/config/locales/common-ru.yml +5 -0
  157. data/config/locales/components-ru.yml +50 -0
  158. data/config/locales/editable-pages-en.yml +2 -0
  159. data/config/locales/editable-pages-ru.yml +50 -7
  160. data/config/locales/editable-pages-sv.yml +2 -0
  161. data/config/locales/feedback-ru.yml +1 -0
  162. data/config/locales/users-en.yml +0 -1
  163. data/config/locales/users-ru.yml +6 -1
  164. data/config/locales/users-sv.yml +0 -1
  165. data/config/routes.rb +32 -6
  166. data/db/migrate/20181217000000_create_biovision_components.rb +80 -0
  167. data/db/migrate/20181217000010_create_metrics.rb +40 -0
  168. data/db/migrate/20181217000015_create_browsers.rb +38 -0
  169. data/db/migrate/20181217000020_create_languages.rb +30 -0
  170. data/db/migrate/20181217000030_create_users.rb +118 -0
  171. data/db/migrate/20181217000035_create_codes.rb +51 -0
  172. data/db/migrate/20181217000040_create_privileges.rb +142 -0
  173. data/db/migrate/20181217000100_create_media_folders.rb +53 -0
  174. data/db/migrate/20181217000110_create_editable_pages.rb +90 -0
  175. data/db/migrate/20181217000200_create_feedback_requests.rb +27 -0
  176. data/db/migrate/20181217121211_add_uuid_to_users.rb +12 -0
  177. data/db/migrate/20181217121212_update_fields181217.rb +29 -0
  178. data/db/migrate/20190311121212_convert_json_columns.rb +47 -0
  179. data/db/migrate/20190324181818_add_data_to_feedback_requests.rb +14 -0
  180. data/db/migrate/20190326120000_create_simple_blocks.rb +31 -0
  181. data/db/{migrate → obsolete_migrations}/20171223333333_amend_foreign_keys.rb +0 -0
  182. data/db/{migrate → obsolete_migrations}/20180117151515_add_language_to_models.rb +0 -0
  183. data/db/{migrate → obsolete_migrations}/20180321000000_add_profile_data_to_users.rb +4 -4
  184. data/db/{migrate → obsolete_migrations}/20180405000000_add_consent_to_users.rb +0 -0
  185. data/db/{migrate → obsolete_migrations}/20180610222222_add_consent_to_feedback_requests.rb +0 -0
  186. data/db/{migrate → obsolete_migrations}/20180612111111_add_administrative_to_privilege.rb +0 -0
  187. data/db/{migrate → obsolete_migrations}/20180619121212_add_image_alt_text_to_editable_page.rb +0 -0
  188. data/db/{migrate → obsolete_migrations}/20180703111111_add_fields_to_editable_blocks.rb +0 -0
  189. data/db/{migrate → obsolete_migrations}/20180722222222_add_active_to_languages.rb +0 -0
  190. data/db/{migrate → obsolete_migrations}/20180725111111_add_referral_link_to_users.rb +0 -0
  191. data/db/obsolete_migrations/20181012222222_add_deletable_to_privileges.rb +19 -0
  192. data/db/obsolete_migrations/20181012222223_convert_stored_values.rb +13 -0
  193. data/db/obsolete_migrations/20181030080808_update_editable_pages_meta.rb +20 -0
  194. data/lib/biovision/base/base_methods.rb +19 -2
  195. data/lib/biovision/base/version.rb +3 -1
  196. data/lib/tasks/users.rake +19 -1
  197. metadata +75 -40
  198. data/app/views/admin/privileges/_list.html.erb +0 -15
  199. data/db/migrate/20170228000000_create_languages.rb +0 -23
  200. data/db/migrate/20170301000001_create_metrics.rb +0 -23
  201. data/db/migrate/20170301000002_create_metric_values.rb +0 -19
  202. data/db/migrate/20170301000101_create_browsers.rb +0 -22
  203. data/db/migrate/20170301000102_create_agents.rb +0 -22
  204. data/db/migrate/20170302000001_create_users.rb +0 -55
  205. data/db/migrate/20170302000003_create_tokens.rb +0 -23
  206. data/db/migrate/20170302000004_create_code_types.rb +0 -20
  207. data/db/migrate/20170302000005_create_codes.rb +0 -24
  208. data/db/migrate/20170302000101_create_privileges.rb +0 -41
  209. data/db/migrate/20170302000102_create_user_privileges.rb +0 -18
  210. data/db/migrate/20170302000103_create_privilege_groups.rb +0 -23
  211. data/db/migrate/20170302000104_create_privilege_group_privileges.rb +0 -24
  212. data/db/migrate/20170320000000_create_editable_pages.rb +0 -52
  213. data/db/migrate/20170425000001_create_foreign_sites.rb +0 -18
  214. data/db/migrate/20170425000002_create_foreign_users.rb +0 -23
  215. data/db/migrate/20170629120000_create_login_attempts.rb +0 -19
  216. data/db/migrate/20170823000001_create_stored_values.rb +0 -19
  217. data/db/migrate/20171202000000_create_media_folders.rb +0 -28
  218. data/db/migrate/20171202000001_create_media_files.rb +0 -27
  219. data/db/migrate/20171211000000_create_feedback_requests.rb +0 -33
  220. data/db/migrate/20180117160000_create_user_languages.rb +0 -17
  221. data/db/migrate/20180622140000_create_link_blocks.rb +0 -29
  222. data/db/migrate/20180622140001_create_link_block_items.rb +0 -23
  223. data/db/migrate/20180627190000_create_editable_blocks.rb +0 -39
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e26d43272af3cd8b34dd325f8dec9a0728d5eeecd8b5108493c10c4260f97916
4
- data.tar.gz: 694106f3d60380e268c2a93f57439bfcee085ed4e11022acf4001cde0b02f159
3
+ metadata.gz: eaab273faa85e0bc922a32b591fa239f57f78d3391554d304afb1e1a32d60285
4
+ data.tar.gz: f8e5837fdf1922adf3e6acaca5801de5e3f4d483ca8ef6c124a3382e1a1d7330
5
5
  SHA512:
6
- metadata.gz: 067ee5df601cebc75b36340c507f30ee0f6e89a149e1ccc5deb67e716d1d3a8b58ef7ce132a1b6b254005ec118146a45c19b76343a98912de6ee66a0cb7838de
7
- data.tar.gz: 3d0a6eb7e677b759495c67a289d3e46d97807632ecb557fba1f9d548373c6c055b1533f50906f2ba57d96fc7bea8a6f86280e83e413cdb013a569cd2b25daeef
6
+ metadata.gz: 1ee9f0baf217333f237339d555681d334c37cf27d9a822b92e6f1e0bc15fe4b3c654ba73b79bece91b97a80d3226d70cfe33c334323f5b6b18ebc764a0cc079b
7
+ data.tar.gz: 17bb0eef033b0bffe97faa5e4cb4eafefc177a592062298680caa67b286332b1f47cc925928cd9bf78fc48f34a331d48b93e8decfafc7bb4ab3571968bd4ea11
data/README.md CHANGED
@@ -5,7 +5,8 @@ Biovision::Base
5
5
 
6
6
  Используйте на свой страх и риск без каких-либо гарантий работоспособности.
7
7
 
8
- Описание необходимых и рекомендуемых действий находится в `setup.md`
8
+ Описание необходимых и рекомендуемых действий находится
9
+ в [setup.md](https://github.com/Biovision/biovision-base/blob/master/setup.md)
9
10
 
10
11
  Очистка устаревших сессий
11
12
  -------------------------
@@ -30,7 +31,7 @@ Biovision::Base
30
31
  Параметры, которые можно задать через data-атрибуты:
31
32
 
32
33
  * `delay` — задержка перед пролистыванием. По умолчанию — `125` (мс)
33
- * `type` — тип. По умолчанию — `opacity` (пока это единственный вариант)
34
+ * `type` — тип. По умолчанию — `opacity` (есть ещё `slide`).
34
35
 
35
36
  ```html
36
37
  <div class="biovision-slider" data-delay="250" data-type="opacity">
@@ -85,31 +86,14 @@ Biovision::Base
85
86
  который проверяется.
86
87
 
87
88
  4. Рядом с элементом нужно добавить `div` с атрибутом `data-field` и таким же
88
- значением, а также классом `check-result-error hidden`.
89
+ значением, а также классом `check-result-error`.
89
90
 
90
91
  Пример — [app/views/my/profiles/new/_form.html.erb](https://github.com/Biovision/biovision-base/blob/master/app/views/my/profiles/new/_form.html.erb),
91
92
  [app/controllers/users_controller.rb#check](https://github.com/Biovision/biovision-base/blob/master/app/controllers/users_controller.rb#L8),
92
93
  [app/views/users/check.jbuilder](https://github.com/Biovision/biovision-base/blob/master/app/views/users/check.jbuilder)
93
94
 
94
- ## Installation
95
- Add this line to your application's Gemfile:
96
-
97
- ```ruby
98
- gem 'biovision-base'
99
- ```
100
-
101
- And then execute:
102
- ```bash
103
- $ bundle
104
- ```
105
-
106
- Or install it yourself as:
107
- ```bash
108
- $ gem install biovision-base
109
- ```
110
-
111
95
  ## Contributing
112
- Contribution directions go here.
96
+ Fork, update, make pull request.
113
97
 
114
98
  ## License
115
99
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,4 @@
1
+ <svg version="1.1" viewBox="0 0 24 24" width="24px" height="24px" xmlns="http://www.w3.org/2000/svg">
2
+ <title>Apply</title>
3
+ <polyline points="3,12 11,21 21,3" stroke="#61e440" stroke-width="3" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
4
+ </svg>
@@ -0,0 +1,26 @@
1
+ <svg version="1.1" viewBox="0 0 24 24" width="24px" height="24px" xmlns="http://www.w3.org/2000/svg">
2
+ <title>Contact</title>
3
+ <defs>
4
+ <style type="text/css">
5
+ * { stroke-width: 1; }
6
+ .a { stroke: #777; }
7
+ .b { stroke: #555; }
8
+ .c { fill: #3aa; }
9
+ line { stroke-linecap: round; }
10
+ </style>
11
+ </defs>
12
+ <g class="a">
13
+ <rect x="1" y="1" width="15" height="10" fill="#fff"/>
14
+ <polyline points="1,1 9,5 16,1" fill="none"/>
15
+ </g>
16
+ <g class="b">
17
+ <rect x="7" y="6" width="10" height="17" rx="2" fill="#eee"/>
18
+ <line x1="10" y1="8" x2="14" y2="8" />
19
+ <circle cx="12.25" cy="21" r=".5"/>
20
+ <rect x="8" y="9.5" width="8.125" height="10" rx="1" ry="1" fill="#555" stroke="none" />
21
+ </g>
22
+ <g class="c">
23
+ <path d="M20,24 l-3.5,-8 q4,-8 7,0 z"/>
24
+ <circle cx="20.125" cy="15.25" r="1.5" fill="#f7a"/>
25
+ </g>
26
+ </svg>
@@ -0,0 +1,17 @@
1
+ <svg version="1.1" viewBox="0 0 24 24" width="24px" height="24px" xmlns="http://www.w3.org/2000/svg">
2
+ <title>Registration</title>
3
+ <defs>
4
+ <style type="text/css">
5
+ .a { stroke-width: 2; stroke: #3a3; stroke-linecap: round; }
6
+ .b { stroke-width: 1.5; stroke: #333; fill: #eee; }
7
+ </style>
8
+ </defs>
9
+ <g class="a">
10
+ <line x1="5" y1="2" x2="5" y2="8"/>
11
+ <line x1="2" y1="5" x2="8" y2="5"/>
12
+ </g>
13
+ <g class="b">
14
+ <ellipse cx="12.5" cy="10" rx="3.5" ry="4.5"/>
15
+ <path d="M3,22 Q12,11 22,22 z" stroke-linejoin="round"/>
16
+ </g>
17
+ </svg>
@@ -1,9 +1,12 @@
1
- <?xml version="1.0" encoding="utf-8"?>
2
- <svg id="Layer_1" style="enable-background:new 0 0 612 792;" version="1.1" viewBox="0 0 24 24" width="24px" height="24px" xmlns="http://www.w3.org/2000/svg">
3
- <style type="text/css">
4
- .st0{fill:#E44061;}
5
- </style>
6
- <g transform="matrix(0.046985, 0, 0, 0.046985, -2.34925, -6.624886)">
7
- <polygon class="st0" points="382.2,396.4 560.8,217.8 484,141 305.4,319.6 126.8,141 50,217.8 228.6,396.4 50,575 126.8,651.8 305.4,473.2 484,651.8 560.8,575 382.2,396.4 "/>
1
+ <svg version="1.1" viewBox="0 0 24 24" width="24px" height="24px" xmlns="http://www.w3.org/2000/svg">
2
+ <title>Delete</title>
3
+ <defs>
4
+ <style type="text/css">
5
+ line { stroke: #e44061; stroke-width: 3; stroke-linecap: round; }
6
+ </style>
7
+ </defs>
8
+ <g>
9
+ <line x1="3" y1="3" x2="21" y2="21"/>
10
+ <line x1="3" y1="21" x2="21" y2="3"/>
8
11
  </g>
9
- </svg>
12
+ </svg>
@@ -0,0 +1,41 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="160px" height="160px">
2
+ <title>Settings</title>
3
+ <defs>
4
+ <style>
5
+ line { stroke-linecap: round }
6
+ circle { stroke-width: 3 }
7
+ .a { stroke-width: 15; stroke: #aaa }
8
+ .b { stroke-width: 5; stroke: #777 }
9
+ .c { fill: #bbb; stroke: #999 }
10
+ .d { stroke-width: 3; stroke: #444; fill: #777 }
11
+ </style>
12
+ </defs>
13
+ <g>
14
+ <g class="a">
15
+ <line x1="20" y1="10" x2="20" y2="150"/>
16
+ <line x1="60" y1="10" x2="60" y2="150"/>
17
+ <line x1="100" y1="10" x2="100" y2="150"/>
18
+ </g>
19
+ <g class="b">
20
+ <line x1="20" y1="10" x2="20" y2="150"/>
21
+ <line x1="60" y1="10" x2="60" y2="150"/>
22
+ <line x1="100" y1="10" x2="100" y2="150"/>
23
+ </g>
24
+ <g class="c">
25
+ <circle cx="20" cy="55" r="15"/>
26
+ <circle cx="60" cy="115" r="15"/>
27
+ <circle cx="100" cy="30" r="15"/>
28
+ </g>
29
+ </g>
30
+ <g>
31
+ <g>
32
+ <circle cx="140" cy="20" r="12" stroke="#070" fill="#4a4"/>
33
+ <circle cx="140" cy="60" r="12" stroke="#440" fill="#773"/>
34
+ <circle cx="140" cy="100" r="12" stroke="#400" fill="#733"/>
35
+ </g>
36
+ <g class="d">
37
+ <circle cx="140" cy="140" r="12"/>
38
+ <polyline points="130,140 126,126 140,130"/>
39
+ </g>
40
+ </g>
41
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg version="1.1" viewBox="0 0 12 36" width="12px" height="36px" xmlns="http://www.w3.org/2000/svg">
2
+ <title>Left arrow (wide)</title>
3
+ <polyline points="10,2 2,18 10,34" stroke="#777" stroke-width="3" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg version="1.1" viewBox="0 0 12 36" width="12px" height="36px" xmlns="http://www.w3.org/2000/svg">
2
+ <title>Right arrow (wide)</title>
3
+ <polyline points="2,2 10,18 2,34" stroke="#777" stroke-width="3" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
4
+ </svg>
@@ -1,5 +1,12 @@
1
1
  <svg version="1.1" viewBox="0 0 320 180" width="320px" height="180px" xmlns="http://www.w3.org/2000/svg">
2
- <rect x1="1" y1="1" width="100%" height="100%" stroke="#aaa" stroke-width="4" fill="none"/>
3
- <line x1="1" y1="1" x2="319" y2="179" stroke-width="3" stroke="#aaa"/>
4
- <line x1="1" y1="179" x2="319" y2="1" stroke-width="3" stroke="#aaa"/>
2
+ <title>Image placeholder 16×9</title>
3
+ <style>
4
+ * { stroke: #777; fill: none; }
5
+ line { stroke-width: 3; }
6
+ </style>
7
+ <g>
8
+ <rect x="1" y="1" width="317" height="177" stroke-width="4"/>
9
+ <line x1="1" y1="1" x2="319" y2="179"/>
10
+ <line x1="1" y1="179" x2="319" y2="1"/>
11
+ </g>
5
12
  </svg>
@@ -0,0 +1,12 @@
1
+ <svg version="1.1" viewBox="0 0 300 300" width="300px" height="300px" xmlns="http://www.w3.org/2000/svg">
2
+ <title>Image placeholder 1×1</title>
3
+ <style>
4
+ * { stroke: #777; fill: none; }
5
+ line { stroke-width: 3; }
6
+ </style>
7
+ <g>
8
+ <rect x="1" y="1" width="297" height="297" stroke-width="4"/>
9
+ <line x1="1" y1="1" x2="299" y2="299"/>
10
+ <line x1="1" y1="299" x2="299" y2="1"/>
11
+ </g>
12
+ </svg>
@@ -1,5 +1,12 @@
1
1
  <svg version="1.1" viewBox="0 0 300 200" width="300px" height="200px" xmlns="http://www.w3.org/2000/svg">
2
- <rect x1="1" y1="1" width="100%" height="100%" stroke="#777" stroke-width="4" fill="none"/>
3
- <line x1="1" y1="1" x2="299" y2="199" stroke-width="3" stroke="#777"/>
4
- <line x1="1" y1="199" x2="299" y2="1" stroke-width="3" stroke="#777"/>
2
+ <title>Image placeholder 3×2</title>
3
+ <style>
4
+ * { stroke: #777; fill: none; }
5
+ line { stroke-width: 3; }
6
+ </style>
7
+ <g>
8
+ <rect x="1" y="1" width="297" height="197" stroke-width="4"/>
9
+ <line x1="1" y1="1" x2="299" y2="199"/>
10
+ <line x1="1" y1="199" x2="299" y2="1"/>
11
+ </g>
5
12
  </svg>
@@ -1,79 +1,26 @@
1
1
  'use strict';
2
2
 
3
3
  const Biovision = {
4
- locale: document.querySelector('html').getAttribute('lang'),
5
- storage: {
6
- available: function (type) {
7
- try {
8
- const x = '__storage_test__';
9
-
10
- window[type].setItem(x, x);
11
- window[type].removeItem(x);
12
-
13
- return true;
14
- } catch (e) {
15
- return false;
16
- }
17
- },
18
- set: function (type, key, value) {
19
- if (Biovision.storage.available(type)) {
20
- window[type].setItem(key, value);
21
- } else {
22
- console.log('set: Storage ' + type + ' is not available');
23
- }
24
- },
25
- get: function (type, key) {
26
- if (Biovision.storage.available(type)) {
27
- return window[type].getItem(key);
28
- } else {
29
- console.log('get: Storage ' + type + ' is not available');
30
- return null;
31
- }
32
- },
33
- remove: function (type, key) {
34
- if (Biovision.storage.available(type)) {
35
- window[type].removeItem(key);
36
- } else {
37
- console.log('remove: Storage ' + type + ' is not available');
38
- }
39
- },
40
- session: {
41
- set: function (key, value) {
42
- Biovision.storage.set('sessionStorage', key, value);
43
- },
44
- get: function (key) {
45
- return Biovision.storage.get('sessionStorage', key);
46
- },
47
- remove: function (key) {
48
- Biovision.storage.remove('sessionStorage', key);
49
- }
50
- },
51
- local: {
52
- set: function (key, value) {
53
- Biovision.storage.set('localStorage', key, value);
54
- },
55
- get: function (key) {
56
- return Biovision.storage.get('localStorage', key);
57
- },
58
- remove: function (key) {
59
- Biovision.storage.remove('localStorage', key);
60
- }
4
+ locale: '',
5
+ csrfToken: '',
6
+ components: {},
7
+ init: function () {
8
+ this.locale = document.querySelector('html').getAttribute('lang');
9
+ this.csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
10
+
11
+ if (typeof jQuery !== 'undefined') {
12
+ jQuery.ajaxSetup({
13
+ headers: {
14
+ 'X-CSRF-Token': this.csrfToken
15
+ }
16
+ });
61
17
  }
62
- },
63
- preview_file: function (input) {
64
- const target_image = input.getAttribute('data-image');
65
-
66
- if (target_image) {
67
- const target = document.querySelector('#' + target_image + ' img');
68
18
 
69
- if (target && input.files && input.files[0]) {
70
- const reader = new FileReader();
71
-
72
- reader.onload = function (event) {
73
- target.setAttribute('src', event.target.result);
74
- };
75
-
76
- reader.readAsDataURL(input.files[0]);
19
+ for (let component in this.components) {
20
+ if (this.components.hasOwnProperty(component)) {
21
+ if (this.components[component].hasOwnProperty('init')) {
22
+ this.components[component].init();
23
+ }
77
24
  }
78
25
  }
79
26
  },
@@ -85,6 +32,7 @@ const Biovision = {
85
32
  * @param [onFailure]
86
33
  */
87
34
  new_ajax_request: function (method, url, onSuccess, onFailure) {
35
+ console.log("Biovision.new_ajax_request is deprecated; user Biovision.newAjaxRequest instead");
88
36
  return Biovision.newAjaxRequest(method, url, onSuccess, onFailure);
89
37
  },
90
38
  /**
@@ -108,13 +56,11 @@ const Biovision = {
108
56
  (onFailure || Biovision.handleAjaxFailure).call(this);
109
57
  }
110
58
  });
111
- request.addEventListener('error', function () {
112
- console.log('AJAX error:', this);
113
- });
59
+ request.addEventListener("error", Biovision.handleAjaxFailure);
114
60
 
115
61
  request.open(method.toUpperCase(), url);
116
62
  request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
117
- request.setRequestHeader('X-CSRF-Token', Biovision.csrf_token);
63
+ request.setRequestHeader('X-CSRF-Token', Biovision.csrfToken);
118
64
 
119
65
  return request;
120
66
  },
@@ -130,6 +76,7 @@ const Biovision = {
130
76
  * @param response
131
77
  */
132
78
  handle_ajax_failure: function (response) {
79
+ console.log("Biovision.handle_ajax_failure is deprecated; use Biovision.handleAjaxFailure instead");
133
80
  console.log('AJAX failed', this);
134
81
  if (response.hasOwnProperty('responseJSON')) {
135
82
  console.log(response['responseJSON']);
@@ -139,154 +86,456 @@ const Biovision = {
139
86
  },
140
87
  /**
141
88
  * Handle failed AJAX request
89
+ *
90
+ * @type {Function}
142
91
  */
143
92
  handleAjaxFailure: function () {
144
- console.log('AJAX failed', this.responseText);
93
+ console.log('AJAX failed', this);
145
94
  },
146
- transliterate: function (input) {
147
- const char_map = {
148
- 'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd',
149
- 'е': 'e', 'ё': 'yo', 'ж': 'zh', 'з': 'z', 'и': 'i',
150
- 'й': 'j', 'к': 'k', 'л': 'l', 'м': 'm', 'н': 'n',
151
- 'о': 'o', 'п': 'p', 'р': 'r', 'с': 's', 'т': 't',
152
- 'у': 'u', 'ф': 'f', 'х': 'kh', 'ц': 'c', 'ч': 'ch',
153
- 'ш': 'sh', 'щ': 'shh', 'ъ': '', 'ы': 'y', 'ь': '',
154
- 'э': 'e', 'ю': 'yu', 'я': 'ya',
155
- 'å': 'ao', 'ä': 'ae', 'ö': 'oe', 'é': 'e'
156
- };
157
- let string = input.toLowerCase();
95
+ /**
96
+ * Показать список ошибок после обработки формы
97
+ *
98
+ * Используется в контроллерах при отправке форм через remote: true
99
+ *
100
+ * @param {string} model_name название модели
101
+ * @param {Array<string>} list список ошибок
102
+ */
103
+ showListOfErrors: function (model_name, list) {
104
+ const form = document.getElementById(model_name + '-form');
105
+ if (form) {
106
+ let errors = form.querySelector('ol.errors');
107
+ let data = '';
158
108
 
159
- for (let index in char_map) {
160
- if (char_map.hasOwnProperty(index)) {
161
- string = string.replace(new RegExp(index, 'g'), char_map[index]);
109
+ if (!errors) {
110
+ errors = document.createElement('ol');
111
+ errors.classList.add('errors');
162
112
  }
163
- }
164
- string = string.replace(/[^-a-z0-9_.]/g, '-');
165
- string = string.replace(/^[-_.]*([-a-z0-9_.]*[a-z0-9]+)[-_.]*$/, '$1');
166
- string = string.replace(/--+/g, '-');
167
113
 
168
- return string;
114
+ list.forEach(function (message) {
115
+ data += '<li>' + message + '</li>';
116
+ });
117
+
118
+ errors.innerHTML = data;
119
+
120
+ form.prepend(errors);
121
+
122
+ errors.scrollIntoView();
123
+ }
169
124
  },
170
- ajax_delete_button: function (element) {
171
- const messages = {
172
- ru: 'Вы уверены?',
173
- en: 'Are you sure?'
174
- };
175
- const message = messages.hasOwnProperty(Biovision.locale) ? messages[Biovision.locale] : 'Are you sure?';
176
- const url = element.getAttribute('data-url');
177
- const request = Biovision.newAjaxRequest('delete', url, function () {
178
- element.closest('li[data-id]').remove();
125
+ /**
126
+ * Hide and show elements in form with given id
127
+ *
128
+ * Toggles class "hidden" for elements with selectors
129
+ * hideSelector and showSelector for form children
130
+ *
131
+ * @param {String} formId
132
+ * @param {String} hideSelector elements to hide
133
+ * @param {String} showSelector elements to show
134
+ */
135
+ switchFormElements: function (formId, hideSelector, showSelector) {
136
+ const form = document.getElementById(formId);
137
+ if (form) {
138
+ form.querySelectorAll(showSelector).forEach(function (element) {
139
+ element.classList.remove('hidden');
140
+ });
141
+ form.querySelectorAll(hideSelector).forEach(function (element) {
142
+ element.classList.add('hidden');
143
+ });
144
+ } else {
145
+ console.log('Cannot find element with id ' + formId)
146
+ }
147
+ }
148
+ };
149
+
150
+ /**
151
+ * Show/hide "go to top" link when it is present in layout
152
+ *
153
+ * @type {Object}
154
+ */
155
+ Biovision.components.topLinker = {
156
+ /**
157
+ * @type {Boolean}
158
+ */
159
+ initialized: false,
160
+ /**
161
+ * @type {HTMLElement}
162
+ */
163
+ element: undefined,
164
+ /**
165
+ * Initialize component
166
+ */
167
+ init: function () {
168
+ this.element = document.getElementById('go-to-top');
169
+
170
+ if (this.element) {
171
+ const topLinker = this.element;
172
+
173
+ window.addEventListener('scroll', function () {
174
+ if (window.pageYOffset > 500) {
175
+ topLinker.classList.remove('inactive');
176
+ } else {
177
+ topLinker.classList.add('inactive');
178
+ }
179
+ });
180
+
181
+ this.initialized = true;
182
+ }
183
+ }
184
+ };
185
+
186
+ /**
187
+ * Preview images when selecting them in input type="file" fields
188
+ *
189
+ * @type {Object}
190
+ */
191
+ Biovision.components.filePreview = {
192
+ /**
193
+ * @type {Boolean}
194
+ */
195
+ initialized: false,
196
+ /**
197
+ * Initialize component
198
+ */
199
+ init: function () {
200
+ document.addEventListener('change', function (event) {
201
+ const input = event.target;
202
+
203
+ if (input.matches('input[type=file]')) {
204
+ Biovision.components.filePreview.handle(input);
205
+ }
179
206
  });
207
+ this.initialized = true;
208
+ },
209
+ /**
210
+ * Handle change of file input field
211
+ *
212
+ * @param {EventTarget|HTMLInputElement} input
213
+ */
214
+ handle: function (input) {
215
+ const targetImage = input.getAttribute('data-image');
180
216
 
181
- element.addEventListener('click', function () {
182
- element.disabled = true;
217
+ if (targetImage) {
218
+ const target = document.querySelector('#' + targetImage + ' img');
183
219
 
184
- if (confirm(message)) {
185
- request.send();
220
+ if (target && input.files && input.files[0]) {
221
+ const reader = new FileReader();
222
+
223
+ reader.onload = function (event) {
224
+ target.setAttribute('src', event.target["result"]);
225
+ };
226
+
227
+ reader.readAsDataURL(input.files[0]);
186
228
  }
229
+ }
230
+ },
231
+ };
187
232
 
188
- element.disabled = false;
189
- });
233
+ /**
234
+ * Linking entities with checkboxes
235
+ *
236
+ * @type {Object}
237
+ */
238
+ Biovision.components.entityLinker = {
239
+ /**
240
+ * @type {Boolean}
241
+ */
242
+ initialized: false,
243
+ selector: '.entity-links input[type=checkbox]',
244
+ /**
245
+ * List of elements with attached event listener
246
+ *
247
+ * @type {Array<HTMLElement>}
248
+ */
249
+ elements: [],
250
+ /**
251
+ * Initialize component
252
+ */
253
+ init: function () {
254
+ document.querySelectorAll(this.selector).forEach(this.apply);
255
+ this.initialized = true;
190
256
  },
191
- entity_link_changer: function () {
257
+ /**
258
+ * Apply handler to element
259
+ *
260
+ * @param {HTMLInputElement} element
261
+ */
262
+ apply: function (element) {
263
+ const component = Biovision.components.entityLinker;
264
+
265
+ component.elements.push(element);
266
+ element.addEventListener('click', component.handler);
267
+ },
268
+ /**
269
+ * Event handler for clicking on element
270
+ */
271
+ handler: function () {
192
272
  const url = this.getAttribute('data-url');
193
273
 
194
274
  if (url && !this.disabled) {
195
275
  const method = this.checked ? 'put' : 'delete';
276
+ const box = this;
196
277
 
197
278
  this.disabled = true;
198
279
 
199
- Biovision.newAjaxRequest(method, url, () => this.disabled = false).send();
280
+ Biovision.newAjaxRequest(method, url, function () {
281
+ box.disabled = false
282
+ }).send();
200
283
  }
284
+ }
285
+ };
286
+
287
+ /**
288
+ * Instantly check form validity
289
+ *
290
+ * @type {Object}
291
+ */
292
+ Biovision.components.instantCheck = {
293
+ initialized: false,
294
+ selector: 'form[data-check-url]',
295
+ elements: [],
296
+ init: function () {
297
+ document.querySelectorAll(this.selector).forEach(this.apply);
298
+ this.initialized = true;
201
299
  },
202
- instant_check: function (form) {
203
- const url = form.getAttribute('data-check-url');
300
+ /**
301
+ *
302
+ * @param {HTMLFormElement} form
303
+ */
304
+ apply: function (form) {
305
+ const component = Biovision.components.instantCheck;
204
306
 
205
- const perform_check = function () {
206
- const element = this;
307
+ form.querySelectorAll('[data-check]').forEach(function (element) {
308
+ component.elements.push(element);
309
+ element.addEventListener('blur', component.handler);
310
+ });
311
+ },
312
+ handler: function () {
313
+ const element = this;
314
+ const form = element.closest('form');
315
+ const url = form.getAttribute('data-check-url');
207
316
 
208
- const request = Biovision.newAjaxRequest('POST', url, function () {
209
- if (this.responseText) {
210
- const response = JSON.parse(this.responseText);
317
+ const request = Biovision.newAjaxRequest('POST', url, function () {
318
+ if (this.responseText) {
319
+ const response = JSON.parse(this.responseText);
211
320
 
212
- if (response.hasOwnProperty('meta')) {
213
- if (response.meta.valid) {
214
- form.querySelectorAll('[data-field]').forEach(function (field) {
215
- field.innerHTML = '';
216
- field.classList.add('hidden');
217
- });
218
- } else {
219
- const key = element.getAttribute('data-check');
220
- const container = form.querySelector('[data-field="' + key + '"]');
321
+ if (response.hasOwnProperty('meta')) {
322
+ if (response.meta.valid) {
323
+ form.querySelectorAll('[data-field]').forEach(function (field) {
324
+ field.innerHTML = '';
325
+ });
326
+ } else {
327
+ const key = element.getAttribute('data-check');
328
+ const container = form.querySelector('[data-field="' + key + '"]');
221
329
 
222
- if (container) {
223
- const errors = response.meta['errors'];
330
+ if (container) {
331
+ const errors = response.meta['errors'];
224
332
 
225
- if (errors.hasOwnProperty(key)) {
226
- container.innerHTML = errors[key].join('; ');
227
- container.classList.remove('hidden');
228
- } else {
229
- container.classList.add('hidden');
230
- }
333
+ if (errors.hasOwnProperty(key)) {
334
+ container.innerHTML = errors[key].join('; ');
335
+ container.classList.remove('hidden');
336
+ } else {
337
+ container.innerHTML = '';
231
338
  }
232
339
  }
233
340
  }
234
341
  }
235
- });
342
+ }
343
+ });
236
344
 
237
- const data = new FormData();
238
- Array.from((new FormData(form)).entries()).forEach(function (entry) {
239
- const value = entry[1];
345
+ const data = new FormData();
346
+ Array.from((new FormData(form)).entries()).forEach(function (entry) {
347
+ const value = entry[1];
240
348
 
241
- if (value instanceof window.File && value.name === '' && value.size === 0) {
242
- data.append(entry[0], new window.Blob([]), '');
243
- } else {
244
- if (entry[0] !== '_method') {
245
- data.append(entry[0], value);
246
- }
349
+ if (value instanceof window.File && value.name === '' && value.size === 0) {
350
+ data.append(entry[0], new window.Blob([]), '');
351
+ } else {
352
+ if (entry[0] !== '_method') {
353
+ data.append(entry[0], value);
247
354
  }
248
- });
355
+ }
356
+ });
249
357
 
250
- request.send(data);
251
- };
358
+ request.send(data);
359
+ }
360
+ };
252
361
 
253
- if (url.length) {
254
- form.querySelectorAll('[data-check]').forEach(function (element) {
255
- element.addEventListener('blur', perform_check);
256
- });
362
+ Biovision.components.transliterator = {
363
+ initialized: false,
364
+ selector: '[data-transliterate]',
365
+ elements: [],
366
+ init: function () {
367
+ document.querySelectorAll(this.selector).forEach(this.apply);
368
+ this.initialized = true;
369
+ },
370
+ /**
371
+ *
372
+ * @param {HTMLElement} element
373
+ */
374
+ apply: function (element) {
375
+ const component = Biovision.components.transliterator;
376
+
377
+ component.elements.push(element);
378
+ element.addEventListener('blur', component.handler);
379
+ },
380
+ /**
381
+ *
382
+ * @param {Event} event
383
+ */
384
+ handler: function (event) {
385
+ const component = Biovision.components.transliterator;
386
+ const element = event.target;
387
+ const target = document.getElementById(element.getAttribute('data-transliterate'));
388
+
389
+ if (target && target.value === '') {
390
+ target.value = component.transliterate(element.value);
391
+ target.dispatchEvent(new Event('change'));
257
392
  }
258
393
  },
259
394
  /**
260
- * Показать список ошибок после обработки формы
261
395
  *
262
- * Используется в контроллерах при отправке форм через remote: true
396
+ * @param {string} input
397
+ * @returns {string}
398
+ */
399
+ transliterate: function (input) {
400
+ const characterMap = {
401
+ 'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd',
402
+ 'е': 'e', 'ё': 'yo', 'ж': 'zh', 'з': 'z', 'и': 'i',
403
+ 'й': 'j', 'к': 'k', 'л': 'l', 'м': 'm', 'н': 'n',
404
+ 'о': 'o', 'п': 'p', 'р': 'r', 'с': 's', 'т': 't',
405
+ 'у': 'u', 'ф': 'f', 'х': 'kh', 'ц': 'c', 'ч': 'ch',
406
+ 'ш': 'sh', 'щ': 'shh', 'ъ': '', 'ы': 'y', 'ь': '',
407
+ 'э': 'e', 'ю': 'yu', 'я': 'ya',
408
+ 'å': 'ao', 'ä': 'ae', 'ö': 'oe', 'é': 'e'
409
+ };
410
+ let string = input.toLowerCase();
411
+
412
+ for (let index in characterMap) {
413
+ if (characterMap.hasOwnProperty(index)) {
414
+ string = string.replace(new RegExp(index, 'g'), characterMap[index]);
415
+ }
416
+ }
417
+ string = string.replace(/[^-a-z0-9_.]/g, '-');
418
+ string = string.replace(/^[-_.]*([-a-z0-9_.]*[a-z0-9]+)[-_.]*$/, '$1');
419
+ string = string.replace(/--+/g, '-');
420
+
421
+ return string;
422
+ },
423
+ };
424
+
425
+ Biovision.components.formStatus = {
426
+ initialized: false,
427
+ selector: 'form[data-remote]',
428
+ elements: [],
429
+ init: function () {
430
+ document.querySelectorAll(this.selector).forEach(this.apply);
431
+ this.initialized = true;
432
+ },
433
+ /**
263
434
  *
264
- * @param {string} model_name название модели
265
- * @param {Array<string>} list список ошибок
435
+ * @param {HTMLFormElement} element
266
436
  */
267
- showListOfErrors: function (model_name, list) {
268
- const form = document.getElementById(model_name + '-form');
269
- if (form) {
270
- let errors = form.querySelector('ol.errors');
271
- let data = '';
437
+ apply: function (element) {
438
+ const component = Biovision.components.formStatus;
439
+ component.elements.push(element);
272
440
 
273
- if (!errors) {
274
- errors = document.createElement('ol');
275
- errors.classList.add('errors');
441
+ const button = element.querySelector('button[type=submit]');
442
+ const loadingMessage = element.querySelector('.loading_message');
443
+ const stateContainer = element.querySelector('.state_container');
444
+ const progressPercent = element.querySelector('.state_container .percentage');
445
+ const progressBar = element.querySelector('.state_container progress');
446
+
447
+ element.addEventListener('ajax:before', function () {
448
+ button.disabled = true;
449
+
450
+ if (loadingMessage) {
451
+ loadingMessage.classList.remove('hidden');
276
452
  }
453
+ });
277
454
 
278
- list.forEach(function (message) {
279
- data += '<li>' + message + '</li>';
455
+ element.addEventListener('ajax:complete', function () {
456
+ button.disabled = false;
457
+
458
+ if (loadingMessage) {
459
+ loadingMessage.classList.add('hidden');
460
+ }
461
+ if (progressBar) {
462
+ progressBar.value = '0';
463
+ }
464
+ });
465
+
466
+ if (stateContainer) {
467
+ element.addEventListener('ajax:beforeSend', function (event) {
468
+ const request = event.detail[0];
469
+
470
+ request.upload.addEventListener('progress', function (e) {
471
+ const value = e.loaded / e.total;
472
+
473
+ if (progressPercent) {
474
+ progressPercent.innerHTML = (value * 100) + '%';
475
+ }
476
+ if (progressBar) {
477
+ progressBar.value = value;
478
+ }
479
+ });
280
480
  });
481
+ }
482
+ },
483
+ };
281
484
 
282
- errors.innerHTML = data;
485
+ Biovision.components.autoExpand = {
486
+ initialized: false,
487
+ selector: 'textarea.auto-expand',
488
+ elements: [],
489
+ init: function () {
490
+ document.querySelectorAll(this.selector).forEach(this.apply);
491
+ this.initialized = true;
492
+ },
493
+ /**
494
+ *
495
+ * @param {HTMLTextAreaElement} element
496
+ */
497
+ apply: function (element) {
498
+ const component = Biovision.components.autoExpand;
499
+ component.elements.push(element);
283
500
 
284
- form.prepend(errors);
501
+ element.addEventListener('focus', component.handler);
502
+ element.addEventListener('input', component.handler);
503
+ },
504
+ handler: function () {
505
+ if (!this.hasOwnProperty('baseScrollHeight')) {
506
+ let savedValue = this.value;
507
+ this.value = '';
508
+ this.baseScrollHeight = this.scrollHeight;
509
+ this.value = savedValue;
510
+ }
511
+ const styles = getComputedStyle(this);
512
+ const ratio = styles.getPropertyValue('line-height').replace('px', '');
513
+ const minRows = this.getAttribute('data-min-rows') | 0;
514
+ const maxRows = this.hasAttribute('data-max-rows') ? parseInt(this.getAttribute('data-max-rows')) : 25;
515
+ const rows = Math.ceil((this.scrollHeight - this.baseScrollHeight) / ratio);
285
516
 
286
- errors.scrollIntoView();
517
+ this.rows = minRows;
518
+ this.rows = minRows + rows;
519
+ if (this.rows > maxRows) {
520
+ this.rows = maxRows;
287
521
  }
522
+ }
523
+ };
524
+
525
+ Biovision.components.entityImageRemover = {
526
+ initialized: false,
527
+ selector: '.remove-image-button',
528
+ elements: [],
529
+ init: function () {
530
+ document.querySelectorAll(this.selector).forEach(this.apply);
531
+ this.initialized = true;
288
532
  },
289
- removeEntityImage: function () {
533
+ apply: function (element) {
534
+ const component = Biovision.components.entityImageRemover;
535
+ component.elements.push(element);
536
+ element.addEventListener('click', component.handler);
537
+ },
538
+ handler: function () {
290
539
  const button = this;
291
540
  if (!button.disabled) {
292
541
  const message = button.getAttribute('data-text');
@@ -311,104 +560,395 @@ const Biovision = {
311
560
  request.send();
312
561
  }
313
562
  }
563
+ }
564
+ };
565
+
566
+ Biovision.components.ajaxDeleteButton = {
567
+ initialized: false,
568
+ selector: 'button.destroy[data-url]',
569
+ elements: [],
570
+ messages: {
571
+ "ru": "Вы уверены?",
572
+ "en": "Are you sure?"
314
573
  },
315
- autoExpand: function () {
316
- if (!this.hasOwnProperty('baseScrollHeight')) {
317
- let savedValue = this.value;
318
- this.value = '';
319
- this.baseScrollHeight = this.scrollHeight;
320
- this.value = savedValue;
574
+ init: function () {
575
+ document.querySelectorAll(this.selector).forEach(this.apply);
576
+ this.initialized = true;
577
+ },
578
+ apply: function (element) {
579
+ const component = Biovision.components.ajaxDeleteButton;
580
+ component.elements.push(element);
581
+ element.addEventListener('click', component.handler);
582
+ },
583
+ handler: function (event) {
584
+ const messages = Biovision.components.ajaxDeleteButton.messages;
585
+ const element = event.target;
586
+ const message = messages.hasOwnProperty(Biovision.locale) ? messages[Biovision.locale] : 'Are you sure?';
587
+
588
+ element.disabled = true;
589
+
590
+ if (confirm(message)) {
591
+ const url = element.getAttribute('data-url');
592
+ const request = Biovision.newAjaxRequest('delete', url, function () {
593
+ element.closest('li[data-id]').remove();
594
+ });
595
+ request.send();
321
596
  }
322
- const styles = getComputedStyle(this);
323
- const ratio = styles.getPropertyValue('line-height').replace('px', '');
324
- const minRows = this.getAttribute('data-min-rows') | 0;
325
- const maxRows = this.hasAttribute('data-max-rows') ? parseInt(this.getAttribute('data-max-rows')) : 25;
326
- const rows = Math.ceil((this.scrollHeight - this.baseScrollHeight) / ratio);
327
597
 
328
- this.rows = minRows;
329
- this.rows = minRows + rows;
330
- if (this.rows > maxRows) {
331
- this.rows = maxRows;
598
+ element.disabled = false;
599
+ }
600
+ };
601
+
602
+ Biovision.components.storage = {
603
+ initialized: false,
604
+ session: {
605
+ set: function (key, value) {
606
+ Biovision.components.storage.set('sessionStorage', key, value);
607
+ },
608
+ get: function (key) {
609
+ return Biovision.components.storage.get('sessionStorage', key);
610
+ },
611
+ remove: function (key) {
612
+ Biovision.components.storage.remove('sessionStorage', key);
613
+ }
614
+ },
615
+ local: {
616
+ set: function (key, value) {
617
+ Biovision.components.storage.set('localStorage', key, value);
618
+ },
619
+ get: function (key) {
620
+ return Biovision.components.storage.get('localStorage', key);
621
+ },
622
+ remove: function (key) {
623
+ Biovision.components.storage.remove('localStorage', key);
332
624
  }
333
625
  },
334
- remoteFormHandler: function (form) {
335
- const button = form.querySelector('button[type=submit]');
336
- const loadingMessage = form.querySelector('.loading_message');
337
- const stateContainer = form.querySelector('.state_container');
338
- const progressPercent = form.querySelector('.state_container .percentage');
339
- const progressBar = form.querySelector('.state_container progress');
626
+ init: function () {
627
+ Biovision.storage = this;
628
+ this.initialized = true;
629
+ },
630
+ available: function (type) {
631
+ try {
632
+ const x = '__storage_test__';
340
633
 
341
- form.addEventListener('ajax:before', function () {
342
- button.disabled = true;
634
+ window[type].setItem(x, x);
635
+ window[type].removeItem(x);
343
636
 
344
- if (loadingMessage) {
345
- loadingMessage.classList.remove('hidden');
346
- }
637
+ return true;
638
+ } catch (e) {
639
+ return false;
640
+ }
641
+ },
642
+ set: function (type, key, value) {
643
+ if (Biovision.components.storage.available(type)) {
644
+ window[type].setItem(key, value);
645
+ } else {
646
+ console.log('set: Storage ' + type + ' is not available');
647
+ }
648
+ },
649
+ get: function (type, key) {
650
+ if (Biovision.components.storage.available(type)) {
651
+ return window[type].getItem(key);
652
+ } else {
653
+ console.log('get: Storage ' + type + ' is not available');
654
+ return null;
655
+ }
656
+ },
657
+ remove: function (type, key) {
658
+ if (Biovision.components.storage.available(type)) {
659
+ window[type].removeItem(key);
660
+ } else {
661
+ console.log('remove: Storage ' + type + ' is not available');
662
+ }
663
+ }
664
+ };
665
+
666
+ // Кнопка удаления элемента через AJAX
667
+ Biovision.components.destroyButton = {
668
+ initialized: false,
669
+ selector: 'div[data-destroy-url] button.destroy',
670
+ elements: [],
671
+ init: function () {
672
+ document.querySelectorAll(this.selector).forEach(this.apply);
673
+ this.initialized = true;
674
+ },
675
+ apply: function (element) {
676
+ const component = Biovision.components.destroyButton;
677
+ component.elements.push(element);
678
+ element.addEventListener('click', component.handler);
679
+ },
680
+ handler: function () {
681
+ const container = this.closest('div[data-destroy-url]');
682
+ const url = container.getAttribute('data-destroy-url');
683
+ const request = Biovision.newAjaxRequest('DELETE', url, function () {
684
+ container.remove();
347
685
  });
348
686
 
349
- form.addEventListener('ajax:complete', function () {
350
- button.disabled = false;
687
+ this.setAttribute('disabled', 'true');
688
+ request.send();
689
+ }
690
+ };
351
691
 
352
- if (loadingMessage) {
353
- loadingMessage.classList.add('hidden');
354
- }
355
- if (progressBar) {
356
- progressBar.value = '0';
692
+ // Поиск пользователя в админке
693
+ Biovision.components.userSearch = {
694
+ initialized: false,
695
+ selector: '.user-search button',
696
+ elements: [],
697
+ init: function () {
698
+ document.querySelectorAll(this.selector).forEach(this.apply);
699
+ this.initialized = true;
700
+ },
701
+ apply: function (element) {
702
+ const component = Biovision.components.userSearch;
703
+ component.elements.push(element);
704
+ element.addEventListener('click', component.handler);
705
+ },
706
+ handler: function () {
707
+ const container = this.closest('.user-search');
708
+ const input = container.querySelector('input[type=search]');
709
+ const url = container.getAttribute('data-url') + '?q=' + encodeURIComponent(input.value);
710
+
711
+ const request = Biovision.newAjaxRequest('GET', url, function () {
712
+ const response = JSON.parse(this.responseText);
713
+ const results = container.querySelector('.results');
714
+
715
+ if (response.hasOwnProperty('data')) {
716
+ results.innerHTML = response['data']['html'];
717
+
718
+ results.querySelectorAll('li').forEach(function (li) {
719
+ li.addEventListener('click', function (event) {
720
+ const element = event.target;
721
+ const target = document.getElementById(container.getAttribute('data-target'));
722
+
723
+ target.value = element.getAttribute('data-id');
724
+ });
725
+ });
357
726
  }
358
727
  });
359
728
 
360
- if (stateContainer) {
361
- form.addEventListener('ajax:beforeSend', function (event) {
362
- const request = event.detail[0];
729
+ request.send();
730
+ }
731
+ };
363
732
 
364
- request.upload.addEventListener('progress', function (e) {
365
- const value = e.loaded / e.total;
733
+ /**
734
+ * Hide popups when clicking outside
735
+ *
736
+ * @type {Object}
737
+ */
738
+ Biovision.components.hidingPopups = {
739
+ /**
740
+ * @type {Boolean}
741
+ */
742
+ initialized: false,
743
+ /**
744
+ * @type {String}
745
+ */
746
+ selector: '.hiding-popup-container .popup-control',
747
+ /**
748
+ * List of control checkboxes
749
+ *
750
+ * @type {Array<HTMLInputElement>}
751
+ */
752
+ elements: [],
753
+ /**
754
+ * Initialize component
755
+ */
756
+ init: function () {
757
+ const component = this;
758
+ this.elements = [];
759
+ document.querySelectorAll(this.selector).forEach(component.addElement);
760
+ document.addEventListener('click', component.handler);
366
761
 
367
- if (progressPercent) {
368
- progressPercent.innerHTML = (value * 100) + '%';
369
- }
370
- if (progressBar) {
371
- progressBar.value = value;
372
- }
373
- });
762
+ this.initialized = true;
763
+ },
764
+ /**
765
+ * Add container to list
766
+ *
767
+ * @param {HTMLInputElement} element
768
+ */
769
+ addElement: function (element) {
770
+ Biovision.components.hidingPopups.elements.push(element);
771
+ },
772
+ /**
773
+ * Handle click
774
+ *
775
+ * @param {Event} event
776
+ * @type {Function}
777
+ */
778
+ handler: function (event) {
779
+ const component = Biovision.components.hidingPopups;
780
+ const selector = '.hiding-popup-container *';
781
+ const target = event.target;
782
+
783
+ if (target.matches(selector)) {
784
+ const container = target.closest('.hiding-popup-container');
785
+ const checkbox = container.querySelector(component.selector);
786
+ component.elements.forEach(function (element) {
787
+ if (element !== checkbox) {
788
+ element.checked = false;
789
+ }
790
+ });
791
+ } else {
792
+ component.elements.forEach(function (checkbox) {
793
+ checkbox.checked = false;
374
794
  });
375
795
  }
376
796
  }
377
797
  };
378
798
 
379
- document.addEventListener('DOMContentLoaded', function () {
380
- Biovision.csrf_token = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
799
+ Biovision.components.componentEditor = {
800
+ initialized: false,
801
+ /**
802
+ * @type {HTMLElement}
803
+ */
804
+ list: undefined,
805
+ url: undefined,
806
+ elements: [],
807
+ values: {},
808
+ init: function () {
809
+ this.list = document.getElementById('biovision-component-parameters');
810
+ if (this.list) {
811
+ const component = this;
812
+ this.url = this.list.getAttribute('data-url');
813
+ this.list.querySelectorAll('input[type="text"]').forEach(component.apply);
814
+
815
+ this.initialized = true;
816
+ }
817
+ },
818
+ /**
819
+ *
820
+ * @param element
821
+ * @type {Function}
822
+ */
823
+ apply: function (element) {
824
+ const component = Biovision.components.componentEditor;
825
+ const button = element.parentNode.querySelector('button');
381
826
 
382
- const topLinker = document.getElementById('go-to-top');
383
- if (topLinker) {
384
- window.addEventListener('scroll', function () {
385
- if (window.pageYOffset > 500) {
386
- topLinker.classList.remove('inactive');
387
- } else {
388
- topLinker.classList.add('inactive');
827
+ component.values[element.getAttribute('id')] = element.value;
828
+
829
+ component.elements.push(element);
830
+ element.addEventListener('change', component.handleChange);
831
+ element.addEventListener('keyup', component.keyUp);
832
+ button.addEventListener('click', component.handleClick);
833
+ },
834
+ handleChange: function (event) {
835
+ const element = event.target;
836
+ const button = element.parentNode.querySelector('button');
837
+
838
+ button.disabled = false;
839
+ },
840
+ keyUp: function (event) {
841
+ const component = Biovision.components.componentEditor;
842
+ const element = event.target;
843
+ const button = element.parentNode.querySelector('button');
844
+
845
+ if (component.values[element.getAttribute('id')] !== element.value) {
846
+ button.disabled = false;
847
+ }
848
+ },
849
+ /**
850
+ *
851
+ * @param {MouseEvent} event
852
+ * @type {Function}
853
+ */
854
+ handleClick: function (event) {
855
+ const component = Biovision.components.componentEditor;
856
+ /**
857
+ *
858
+ * @type {HTMLButtonElement}
859
+ */
860
+ const button = event.target;
861
+ const li = button.closest('li');
862
+ const input = li.querySelector('input');
863
+ const data = {
864
+ "key": {
865
+ "slug": button.getAttribute('data-slug'),
866
+ "value": input.value
389
867
  }
868
+ };
869
+
870
+ const request = Biovision.jsonAjaxRequest('put', component.url, function () {
871
+ component.values[input.getAttribute('id')] = input.value;
390
872
  });
873
+
874
+ button.disabled = true;
875
+ request.send(JSON.stringify(data));
876
+ },
877
+ add: function (data) {
878
+ if (this.initialized) {
879
+ const li = document.createElement('li');
880
+ const name = document.createElement('div');
881
+ const value = document.createElement('div');
882
+ const input = document.createElement('input');
883
+
884
+ name.classList.add('name');
885
+ name.innerHTML = "<label>" + data["slug"] + "</label>\n<span>(" + data["name"] + ")</span>";
886
+
887
+ value.classList.add('value');
888
+ input.value = data["value"];
889
+ value.appendChild(input);
890
+
891
+ li.appendChild(name);
892
+ li.appendChild(value);
893
+
894
+ if (data["description"]) {
895
+ const desc = document.createElement('div');
896
+ desc.classList.add('description');
897
+ desc.innerHTML = data["description"];
898
+
899
+ li.appendChild(desc);
900
+ }
901
+
902
+ this.list.appendChild(li);
903
+ } else {
904
+ console.log('componentEditor is not initialized');
905
+ }
391
906
  }
907
+ };
392
908
 
393
- // Предварительный просмотр картинки при выборе файла
394
- document.addEventListener('change', function (event) {
395
- const input = event.target;
909
+ Biovision.components.newParameterForm = {
910
+ initialized: false,
911
+ element: undefined,
912
+ init: function () {
913
+ this.element = document.getElementById('new-biovision-parameter-form');
914
+ if (this.element) {
915
+ const component = this;
396
916
 
397
- if (input.matches('input[type=file]')) {
398
- Biovision.preview_file(input);
917
+ this.element.addEventListener('submit', component.handler);
918
+ this.initialized = true;
399
919
  }
400
- });
920
+ },
921
+ handler: function (event) {
922
+ event.preventDefault();
923
+
924
+ const form = event.target;
925
+ const url = form.getAttribute('action');
926
+ const data = {"key": {}};
927
+ form.querySelectorAll('input[data-field]').forEach(function (input) {
928
+ data["key"][input.getAttribute('data-field')] = input.value;
929
+ });
401
930
 
402
- document.addEventListener('click', function (event) {
403
- const element = event.target;
931
+ const request = Biovision.jsonAjaxRequest('put', url, function () {
932
+ Biovision.components.newParameterForm.clear();
933
+ Biovision.components.componentEditor.add(data["key"]);
934
+ });
404
935
 
405
- // Выбор результата поиска пользователей в админке
406
- if (element.matches('.user-search .results li')) {
407
- const container = element.closest('.user-search');
408
- const target = document.getElementById(container.getAttribute('data-target'));
936
+ request.send(JSON.stringify(data));
937
+ },
938
+ clear: function () {
939
+ const component = Biovision.components.newParameterForm;
409
940
 
410
- target.value = element.getAttribute('data-id');
411
- }
941
+ component.element.querySelectorAll('input').forEach(function (input) {
942
+ input.value = '';
943
+ });
944
+ }
945
+ };
946
+
947
+ document.addEventListener('DOMContentLoaded', function () {
948
+ Biovision.init();
949
+
950
+ document.addEventListener('click', function (event) {
951
+ const element = event.target;
412
952
 
413
953
  // Запирание/отпирание сущности (иконка с замком)
414
954
  if (element.matches('li.lock > a img')) {
@@ -546,77 +1086,6 @@ document.addEventListener('DOMContentLoaded', function () {
546
1086
  }
547
1087
  }
548
1088
  });
549
-
550
- // Кнопка поиска пользователя в админке
551
- document.querySelectorAll('.user-search button').forEach(function (element) {
552
- element.addEventListener('click', function () {
553
- const container = this.closest('.user-search');
554
- const input = container.querySelector('input[type=search]');
555
- const url = container.getAttribute('data-url') + '?q=' + encodeURIComponent(input.value);
556
-
557
- const request = Biovision.newAjaxRequest('GET', url, function () {
558
- const response = JSON.parse(this.responseText);
559
- const results = container.querySelector('.results');
560
-
561
- if (response.hasOwnProperty('data')) {
562
- results.innerHTML = response['data']['html'];
563
- }
564
- });
565
-
566
- request.send();
567
- });
568
- });
569
-
570
- document.querySelectorAll('.entity-links input[type=checkbox]').forEach(function (element) {
571
- element.addEventListener('click', Biovision.entity_link_changer);
572
- });
573
-
574
- // Кнопка удаления элемента через AJAX
575
- document.querySelectorAll('div[data-destroy-url] button.destroy').forEach(function (element) {
576
- element.addEventListener('click', function () {
577
- const container = this.closest('div[data-destroy-url]');
578
- const url = container.getAttribute('data-destroy-url');
579
- const request = Biovision.newAjaxRequest('DELETE', url, function () {
580
- container.remove();
581
- });
582
-
583
- this.setAttribute('disabled', 'true');
584
- request.send();
585
- });
586
- });
587
-
588
- document.querySelectorAll('button.destroy[data-url]').forEach(Biovision.ajax_delete_button);
589
-
590
- document.querySelectorAll('[data-transliterate]').forEach(function (element) {
591
- element.addEventListener('blur', function () {
592
- const target = document.getElementById(element.getAttribute('data-transliterate'));
593
-
594
- if (target && target.value === '') {
595
- target.value = Biovision.transliterate(element.value);
596
- target.dispatchEvent(new Event('change'));
597
- }
598
- });
599
- });
600
-
601
- document.querySelectorAll('form[data-remote]').forEach(Biovision.remoteFormHandler);
602
- document.querySelectorAll('form[data-check-url]').forEach(Biovision.instant_check);
603
-
604
- document.querySelectorAll('.remove-image-button').forEach(function (button) {
605
- button.addEventListener('click', Biovision.removeEntityImage);
606
- });
607
-
608
- document.querySelectorAll('textarea.auto-expand').forEach(function (textarea) {
609
- textarea.addEventListener('focus', Biovision.autoExpand);
610
- textarea.addEventListener('input', Biovision.autoExpand);
611
- });
612
-
613
- if (typeof jQuery !== 'undefined') {
614
- jQuery.ajaxSetup({
615
- headers: {
616
- 'X-CSRF-Token': Biovision.csrf_token
617
- }
618
- });
619
- }
620
1089
  });
621
1090
 
622
1091
  function handle_ajax_failure(response) {
@@ -652,153 +1121,3 @@ document.addEventListener('ajax:beforeSend', function (e) {
652
1121
 
653
1122
  e.detail[1].data = newFormData
654
1123
  });
655
-
656
- /*
657
- *************
658
- * Polyfills *
659
- *************
660
- */
661
-
662
- /**
663
- * Element.matches()
664
- *
665
- * IE 9+
666
- *
667
- * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/matches
668
- */
669
- if (!Element.prototype.matches) {
670
- Element.prototype.matches =
671
- Element.prototype.msMatchesSelector ||
672
- Element.prototype.webkitMatchesSelector;
673
- }
674
-
675
- /**
676
- * Element.closest()
677
- *
678
- * IE 9+
679
- *
680
- * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
681
- */
682
- if (!Element.prototype.closest) {
683
- Element.prototype.closest = function (s) {
684
- let el = this;
685
- let ancestor = this;
686
-
687
- if (!document.documentElement.contains(el)) {
688
- return null;
689
- }
690
- do {
691
- if (ancestor.matches(s)) {
692
- return ancestor;
693
- }
694
- ancestor = ancestor.parentElement;
695
- } while (ancestor !== null);
696
-
697
- return null;
698
- };
699
- }
700
-
701
- /**
702
- * ParentNode.prepend()
703
- *
704
- * IE
705
- *
706
- * @see https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/prepend
707
- * @see https://github.com/jserz/js_piece/blob/master/DOM/ParentNode/prepend()/prepend().md
708
- */
709
- (function (arr) {
710
- arr.forEach(function (item) {
711
- if (item.hasOwnProperty('prepend')) {
712
- return;
713
- }
714
- Object.defineProperty(item, 'prepend', {
715
- configurable: true,
716
- enumerable: true,
717
- writable: true,
718
- value: function prepend() {
719
- let argArr = Array.prototype.slice.call(arguments),
720
- docFrag = document.createDocumentFragment();
721
-
722
- argArr.forEach(function (argItem) {
723
- let isNode = argItem instanceof Node;
724
- docFrag.appendChild(isNode ? argItem : document.createTextNode(String(argItem)));
725
- });
726
-
727
- this.insertBefore(docFrag, this.firstChild);
728
- }
729
- });
730
- });
731
- })([Element.prototype, Document.prototype, DocumentFragment.prototype]);
732
-
733
- /**
734
- * ParentNode.append()
735
- *
736
- * IE 9+
737
- *
738
- * @see https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/append
739
- * @see https://github.com/jserz/js_piece/blob/master/DOM/ParentNode/append()/append().md
740
- */
741
- (function (arr) {
742
- arr.forEach(function (item) {
743
- if (item.hasOwnProperty('append')) {
744
- return;
745
- }
746
- Object.defineProperty(item, 'append', {
747
- configurable: true,
748
- enumerable: true,
749
- writable: true,
750
- value: function append() {
751
- let argArr = Array.prototype.slice.call(arguments),
752
- docFrag = document.createDocumentFragment();
753
-
754
- argArr.forEach(function (argItem) {
755
- let isNode = argItem instanceof Node;
756
- docFrag.appendChild(isNode ? argItem : document.createTextNode(String(argItem)));
757
- });
758
-
759
- this.appendChild(docFrag);
760
- }
761
- });
762
- });
763
- })([Element.prototype, Document.prototype, DocumentFragment.prototype]);
764
-
765
- /**
766
- * ChildNode.remove()
767
- *
768
- * IE 9+
769
- *
770
- * @see https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/remove
771
- */
772
- (function (arr) {
773
- arr.forEach(function (item) {
774
- if (item.hasOwnProperty('remove')) {
775
- return;
776
- }
777
- Object.defineProperty(item, 'remove', {
778
- configurable: true,
779
- enumerable: true,
780
- writable: true,
781
- value: function remove() {
782
- if (this.parentNode !== null) {
783
- this.parentNode.removeChild(this);
784
- }
785
- }
786
- });
787
- });
788
- })([Element.prototype, CharacterData.prototype, DocumentType.prototype]);
789
-
790
- /**
791
- * NodeList.forEach()
792
- *
793
- * ES5
794
- *
795
- * @see https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach
796
- */
797
- if (window.NodeList && !NodeList.prototype.forEach) {
798
- NodeList.prototype.forEach = function (callback, thisArg) {
799
- thisArg = thisArg || window;
800
- for (let i = 0; i < this.length; i++) {
801
- callback.call(thisArg, this[i], i, this);
802
- }
803
- };
804
- }