pg_eventstore 0.10.2 → 1.0.0.rc1

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/README.md +26 -0
  4. data/db/migrations/1_create_events.sql +12 -11
  5. data/db/migrations/2_create_subscriptions.sql +6 -2
  6. data/db/migrations/3_create_subscription_commands.sql +9 -5
  7. data/db/migrations/4_create_subscriptions_set_commands.sql +1 -1
  8. data/db/migrations/5_partitions.sql +1 -0
  9. data/docs/how_it_works.md +14 -1
  10. data/lib/pg_eventstore/commands/append.rb +1 -1
  11. data/lib/pg_eventstore/commands/event_modifiers/prepare_link_event.rb +30 -8
  12. data/lib/pg_eventstore/commands/event_modifiers/prepare_regular_event.rb +8 -10
  13. data/lib/pg_eventstore/commands/link_to.rb +14 -7
  14. data/lib/pg_eventstore/errors.rb +10 -12
  15. data/lib/pg_eventstore/event.rb +4 -0
  16. data/lib/pg_eventstore/queries/event_queries.rb +27 -6
  17. data/lib/pg_eventstore/queries/links_resolver.rb +28 -6
  18. data/lib/pg_eventstore/queries/partition_queries.rb +8 -0
  19. data/lib/pg_eventstore/queries/subscription_command_queries.rb +27 -7
  20. data/lib/pg_eventstore/queries/subscription_queries.rb +58 -28
  21. data/lib/pg_eventstore/queries/subscriptions_set_command_queries.rb +13 -1
  22. data/lib/pg_eventstore/queries/subscriptions_set_queries.rb +18 -4
  23. data/lib/pg_eventstore/query_builders/events_filtering_query.rb +4 -4
  24. data/lib/pg_eventstore/subscriptions/command_handlers/subscription_feeder_commands.rb +10 -2
  25. data/lib/pg_eventstore/subscriptions/command_handlers/subscription_runners_commands.rb +9 -7
  26. data/lib/pg_eventstore/subscriptions/commands_handler.rb +3 -2
  27. data/lib/pg_eventstore/subscriptions/subscription.rb +28 -12
  28. data/lib/pg_eventstore/subscriptions/subscription_feeder.rb +19 -15
  29. data/lib/pg_eventstore/subscriptions/subscription_runner.rb +1 -1
  30. data/lib/pg_eventstore/subscriptions/subscription_runners_feeder.rb +1 -1
  31. data/lib/pg_eventstore/subscriptions/subscriptions_set.rb +22 -1
  32. data/lib/pg_eventstore/version.rb +1 -1
  33. data/lib/pg_eventstore/web/application.rb +180 -0
  34. data/lib/pg_eventstore/web/paginator/base_collection.rb +56 -0
  35. data/lib/pg_eventstore/web/paginator/event_types_collection.rb +50 -0
  36. data/lib/pg_eventstore/web/paginator/events_collection.rb +105 -0
  37. data/lib/pg_eventstore/web/paginator/helpers.rb +119 -0
  38. data/lib/pg_eventstore/web/paginator/stream_contexts_collection.rb +48 -0
  39. data/lib/pg_eventstore/web/paginator/stream_ids_collection.rb +50 -0
  40. data/lib/pg_eventstore/web/paginator/stream_names_collection.rb +51 -0
  41. data/lib/pg_eventstore/web/public/fonts/vendor/FontAwesome.otf +0 -0
  42. data/lib/pg_eventstore/web/public/fonts/vendor/fontawesome-webfont.eot +0 -0
  43. data/lib/pg_eventstore/web/public/fonts/vendor/fontawesome-webfont.svg +685 -0
  44. data/lib/pg_eventstore/web/public/fonts/vendor/fontawesome-webfont.ttf +0 -0
  45. data/lib/pg_eventstore/web/public/fonts/vendor/fontawesome-webfont.woff +0 -0
  46. data/lib/pg_eventstore/web/public/fonts/vendor/fontawesome-webfont.woff2 +0 -0
  47. data/lib/pg_eventstore/web/public/images/favicon.ico +0 -0
  48. data/lib/pg_eventstore/web/public/javascripts/gentelella.js +334 -0
  49. data/lib/pg_eventstore/web/public/javascripts/pg_eventstore.js +162 -0
  50. data/lib/pg_eventstore/web/public/javascripts/vendor/bootstrap.bundle.min.js +7 -0
  51. data/lib/pg_eventstore/web/public/javascripts/vendor/bootstrap.bundle.min.js.map +1 -0
  52. data/lib/pg_eventstore/web/public/javascripts/vendor/jquery.autocomplete.min.js +8 -0
  53. data/lib/pg_eventstore/web/public/javascripts/vendor/jquery.min.js +4 -0
  54. data/lib/pg_eventstore/web/public/javascripts/vendor/jquery.min.js.map +1 -0
  55. data/lib/pg_eventstore/web/public/javascripts/vendor/select2.full.min.js +2 -0
  56. data/lib/pg_eventstore/web/public/stylesheets/pg_eventstore.css +5 -0
  57. data/lib/pg_eventstore/web/public/stylesheets/vendor/bootstrap.min.css +7 -0
  58. data/lib/pg_eventstore/web/public/stylesheets/vendor/bootstrap.min.css.map +1 -0
  59. data/lib/pg_eventstore/web/public/stylesheets/vendor/font-awesome.min.css +4 -0
  60. data/lib/pg_eventstore/web/public/stylesheets/vendor/font-awesome.min.css.map +7 -0
  61. data/lib/pg_eventstore/web/public/stylesheets/vendor/gentelella.min.css +13 -0
  62. data/lib/pg_eventstore/web/public/stylesheets/vendor/select2-bootstrap4.min.css +3 -0
  63. data/lib/pg_eventstore/web/public/stylesheets/vendor/select2.min.css +2 -0
  64. data/lib/pg_eventstore/web/subscriptions/helpers.rb +76 -0
  65. data/lib/pg_eventstore/web/subscriptions/set_collection.rb +34 -0
  66. data/lib/pg_eventstore/web/subscriptions/subscriptions.rb +33 -0
  67. data/lib/pg_eventstore/web/subscriptions/subscriptions_set.rb +33 -0
  68. data/lib/pg_eventstore/web/subscriptions/subscriptions_to_set_association.rb +32 -0
  69. data/lib/pg_eventstore/web/views/home/dashboard.erb +147 -0
  70. data/lib/pg_eventstore/web/views/home/partials/event_filter.erb +15 -0
  71. data/lib/pg_eventstore/web/views/home/partials/events.erb +22 -0
  72. data/lib/pg_eventstore/web/views/home/partials/pagination_links.erb +3 -0
  73. data/lib/pg_eventstore/web/views/home/partials/stream_filter.erb +31 -0
  74. data/lib/pg_eventstore/web/views/layouts/application.erb +116 -0
  75. data/lib/pg_eventstore/web/views/subscriptions/index.erb +220 -0
  76. data/lib/pg_eventstore/web.rb +22 -0
  77. data/lib/pg_eventstore.rb +5 -0
  78. data/pg_eventstore.gemspec +2 -1
  79. metadata +60 -2
File without changes
@@ -0,0 +1,334 @@
1
+ /**
2
+ * Resize function without multiple trigger
3
+ *
4
+ * Usage:
5
+ * $(window).smartresize(function(){
6
+ * // code here
7
+ * });
8
+ */
9
+ (function($,sr){
10
+ // debouncing function from John Hann
11
+ // http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/
12
+ var debounce = function (func, threshold, execAsap) {
13
+ var timeout;
14
+
15
+ return function debounced () {
16
+ var obj = this, args = arguments;
17
+ function delayed () {
18
+ if (!execAsap)
19
+ func.apply(obj, args);
20
+ timeout = null;
21
+ }
22
+
23
+ if (timeout)
24
+ clearTimeout(timeout);
25
+ else if (execAsap)
26
+ func.apply(obj, args);
27
+
28
+ timeout = setTimeout(delayed, threshold || 100);
29
+ };
30
+ };
31
+
32
+ // smartresize
33
+ jQuery.fn[sr] = function(fn){ return fn ? this.bind('resize', debounce(fn)) : this.trigger(sr); };
34
+
35
+ })(jQuery,'smartresize');
36
+ /**
37
+ * To change this license header, choose License Headers in Project Properties.
38
+ * To change this template file, choose Tools | Templates
39
+ * and open the template in the editor.
40
+ */
41
+
42
+ var CURRENT_URL = window.location.href.split('#')[0].split('?')[0],
43
+ $BODY = $('body'),
44
+ $MENU_TOGGLE = $('#menu_toggle'),
45
+ $SIDEBAR_MENU = $('#sidebar-menu'),
46
+ $SIDEBAR_FOOTER = $('.sidebar-footer'),
47
+ $LEFT_COL = $('.left_col'),
48
+ $RIGHT_COL = $('.right_col'),
49
+ $NAV_MENU = $('.nav_menu'),
50
+ $FOOTER = $('footer');
51
+
52
+ // Sidebar
53
+ function init_sidebar() {
54
+ // TODO: This is some kind of easy fix, maybe we can improve this
55
+ var setContentHeight = function () {
56
+ // reset height
57
+ $RIGHT_COL.css('min-height', $(window).height());
58
+
59
+ var bodyHeight = $BODY.outerHeight(),
60
+ footerHeight = $BODY.hasClass('footer_fixed') ? -10 : $FOOTER.height(),
61
+ leftColHeight = $LEFT_COL.eq(1).height() + $SIDEBAR_FOOTER.height(),
62
+ contentHeight = bodyHeight < leftColHeight ? leftColHeight : bodyHeight;
63
+
64
+ // normalize content
65
+ contentHeight -= $NAV_MENU.height() + footerHeight;
66
+
67
+ $RIGHT_COL.css('min-height', contentHeight);
68
+ };
69
+
70
+ var openUpMenu = function () {
71
+ $SIDEBAR_MENU.find('li').removeClass('active active-sm');
72
+ $SIDEBAR_MENU.find('li ul').slideUp();
73
+ }
74
+
75
+ $SIDEBAR_MENU.find('a').on('click', function (ev) {
76
+ var $li = $(this).parent();
77
+
78
+ if ($li.is('.active')) {
79
+ $li.removeClass('active active-sm');
80
+ $('ul:first', $li).slideUp(function () {
81
+ setContentHeight();
82
+ });
83
+ } else {
84
+ // prevent closing menu if we are on child menu
85
+ if (!$li.parent().is('.child_menu')) {
86
+ openUpMenu();
87
+ } else {
88
+ if ($BODY.is('nav-sm')) {
89
+ if (!$li.parent().is('child_menu')) {
90
+ openUpMenu();
91
+ }
92
+ }
93
+ }
94
+
95
+ $li.addClass('active');
96
+
97
+ $('ul:first', $li).slideDown(function () {
98
+ setContentHeight();
99
+ });
100
+ }
101
+ });
102
+
103
+ // toggle small or large menu
104
+ $MENU_TOGGLE.on('click', function () {
105
+ if ($BODY.hasClass('nav-md')) {
106
+ $SIDEBAR_MENU.find('li.active ul').hide();
107
+ $SIDEBAR_MENU.find('li.active').addClass('active-sm').removeClass('active');
108
+ } else {
109
+ $SIDEBAR_MENU.find('li.active-sm ul').show();
110
+ $SIDEBAR_MENU.find('li.active-sm').addClass('active').removeClass('active-sm');
111
+ }
112
+
113
+ $BODY.toggleClass('nav-md nav-sm');
114
+
115
+ setContentHeight();
116
+
117
+ $('.dataTable').each(function () { $(this).dataTable().fnDraw(); });
118
+ });
119
+
120
+ // check active menu
121
+ $SIDEBAR_MENU.find('a[href="' + CURRENT_URL + '"]').parent('li').addClass('current-page');
122
+
123
+ $SIDEBAR_MENU.find('a').filter(function () {
124
+ return this.href == CURRENT_URL;
125
+ }).parent('li').addClass('current-page').parents('ul').slideDown(function () {
126
+ setContentHeight();
127
+ }).parent().addClass('active');
128
+
129
+ // recompute content when resizing
130
+ $(window).smartresize(function () {
131
+ setContentHeight();
132
+ });
133
+
134
+ setContentHeight();
135
+
136
+ // fixed sidebar
137
+ if ($.fn.mCustomScrollbar) {
138
+ $('.menu_fixed').mCustomScrollbar({
139
+ autoHideScrollbar: true,
140
+ theme: 'minimal',
141
+ mouseWheel: { preventDefault: true }
142
+ });
143
+ }
144
+ }
145
+ // /Sidebar
146
+
147
+ // Panel toolbox
148
+ $(document).ready(function () {
149
+ $('.collapse-link').on('click', function () {
150
+ var $BOX_PANEL = $(this).closest('.x_panel'),
151
+ $ICON = $(this).find('i'),
152
+ $BOX_CONTENT = $BOX_PANEL.find('.x_content');
153
+
154
+ // fix for some div with hardcoded fix class
155
+ if ($BOX_PANEL.attr('style')) {
156
+ $BOX_CONTENT.slideToggle(200, function () {
157
+ $BOX_PANEL.removeAttr('style');
158
+ });
159
+ } else {
160
+ $BOX_CONTENT.slideToggle(200);
161
+ $BOX_PANEL.css('height', 'auto');
162
+ }
163
+
164
+ $ICON.toggleClass('fa-chevron-up fa-chevron-down');
165
+ });
166
+
167
+ $('.close-link').click(function () {
168
+ var $BOX_PANEL = $(this).closest('.x_panel');
169
+
170
+ $BOX_PANEL.remove();
171
+ });
172
+ });
173
+ // /Panel toolbox
174
+
175
+ // Tooltip
176
+ $(document).ready(function () {
177
+ $('[data-toggle="tooltip"]').tooltip({
178
+ container: 'body'
179
+ });
180
+ });
181
+ // /Tooltip
182
+
183
+ // Progressbar
184
+ $(document).ready(function () {
185
+ if ($(".progress .progress-bar")[0]) {
186
+ $('.progress .progress-bar').progressbar();
187
+ }
188
+ });
189
+ // /Progressbar
190
+
191
+ // Switchery
192
+ $(document).ready(function () {
193
+ if ($(".js-switch")[0]) {
194
+ var elems = Array.prototype.slice.call(document.querySelectorAll('.js-switch'));
195
+ elems.forEach(function (html) {
196
+ var switchery = new Switchery(html, {
197
+ color: '#26B99A'
198
+ });
199
+ });
200
+ }
201
+ });
202
+ // /Switchery
203
+
204
+ // iCheck
205
+ $(document).ready(function () {
206
+ if ($("input.flat")[0]) {
207
+ $(document).ready(function () {
208
+ $('input.flat').iCheck({
209
+ checkboxClass: 'icheckbox_flat-green',
210
+ radioClass: 'iradio_flat-green'
211
+ });
212
+ });
213
+ }
214
+ });
215
+ // /iCheck
216
+
217
+ // Table
218
+ $('table input').on('ifChecked', function () {
219
+ checkState = '';
220
+ $(this).parent().parent().parent().addClass('selected');
221
+ countChecked();
222
+ });
223
+ $('table input').on('ifUnchecked', function () {
224
+ checkState = '';
225
+ $(this).parent().parent().parent().removeClass('selected');
226
+ countChecked();
227
+ });
228
+
229
+ var checkState = '';
230
+
231
+ $('.bulk_action input').on('ifChecked', function () {
232
+ checkState = '';
233
+ $(this).parent().parent().parent().addClass('selected');
234
+ countChecked();
235
+ });
236
+ $('.bulk_action input').on('ifUnchecked', function () {
237
+ checkState = '';
238
+ $(this).parent().parent().parent().removeClass('selected');
239
+ countChecked();
240
+ });
241
+ $('.bulk_action input#check-all').on('ifChecked', function () {
242
+ checkState = 'all';
243
+ countChecked();
244
+ });
245
+ $('.bulk_action input#check-all').on('ifUnchecked', function () {
246
+ checkState = 'none';
247
+ countChecked();
248
+ });
249
+
250
+ function countChecked() {
251
+ if (checkState === 'all') {
252
+ $(".bulk_action input[name='table_records']").iCheck('check');
253
+ }
254
+ if (checkState === 'none') {
255
+ $(".bulk_action input[name='table_records']").iCheck('uncheck');
256
+ }
257
+
258
+ var checkCount = $(".bulk_action input[name='table_records']:checked").length;
259
+
260
+ if (checkCount) {
261
+ $('.column-title').hide();
262
+ $('.bulk-actions').show();
263
+ $('.action-cnt').html(checkCount + ' Records Selected');
264
+ } else {
265
+ $('.column-title').show();
266
+ $('.bulk-actions').hide();
267
+ }
268
+ }
269
+
270
+ // Accordion
271
+ $(document).ready(function () {
272
+ $(".expand").on("click", function () {
273
+ $(this).next().slideToggle(200);
274
+ let $expand = $(this).find(">:first-child");
275
+
276
+ if ($expand.text() == "+") {
277
+ $expand.text("-");
278
+ } else {
279
+ $expand.text("+");
280
+ }
281
+ });
282
+ });
283
+
284
+
285
+ //hover and retain popover when on popover content
286
+ var originalLeave = $.fn.popover.Constructor.prototype.leave;
287
+ $.fn.popover.Constructor.prototype.leave = function (obj) {
288
+ var self = obj instanceof this.constructor ?
289
+ obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type);
290
+ var container, timeout;
291
+
292
+ originalLeave.call(this, obj);
293
+
294
+ if (obj.currentTarget) {
295
+ container = $(obj.currentTarget).siblings('.popover');
296
+ timeout = self.timeout;
297
+ container.one('mouseenter', function () {
298
+ //We entered the actual popover – call off the dogs
299
+ clearTimeout(timeout);
300
+ //Let's monitor popover content instead
301
+ container.one('mouseleave', function () {
302
+ $.fn.popover.Constructor.prototype.leave.call(self, self);
303
+ });
304
+ });
305
+ }
306
+ };
307
+
308
+ $('body').popover({
309
+ selector: '[data-popover]',
310
+ trigger: 'click hover',
311
+ delay: {
312
+ show: 50,
313
+ hide: 400
314
+ }
315
+ });
316
+
317
+
318
+ function gd(year, month, day) {
319
+ return new Date(year, month - 1, day).getTime();
320
+ }
321
+
322
+ /* AUTOSIZE */
323
+
324
+ function init_autosize() {
325
+ if (typeof $.fn.autosize !== 'undefined') {
326
+ autosize($('.resizable_textarea'));
327
+ }
328
+ };
329
+
330
+
331
+ $(document).ready(function () {
332
+ init_sidebar();
333
+ init_autosize();
334
+ });
@@ -0,0 +1,162 @@
1
+ $(function(){
2
+ "use strict";
3
+
4
+ let initStreamFilterAutocomplete = function($filter) {
5
+ let $contextSelect = $filter.find('select[name*="context"]');
6
+ let $streamNameSelect = $filter.find('select[name*="stream_name"]');
7
+ let $streamIdSelect = $filter.find('select[name*="stream_id"]');
8
+ $contextSelect.select2({
9
+ ajax: {
10
+ url: $contextSelect.data('url'),
11
+ processResults: function(data, params){
12
+ params.starting_id = data.pagination.starting_id;
13
+ data.results.forEach(function(contextAttrs){
14
+ contextAttrs.id = contextAttrs.context;
15
+ contextAttrs.text = contextAttrs.context;
16
+ });
17
+ return data;
18
+ },
19
+ },
20
+ allowClear: true
21
+ });
22
+ $streamNameSelect.select2({
23
+ ajax: {
24
+ url: $streamNameSelect.data('url'),
25
+ data: function (params) {
26
+ params.context = $contextSelect.val();
27
+
28
+ return params;
29
+ },
30
+ processResults: function(data, params){
31
+ params.starting_id = data.pagination.starting_id;
32
+ data.results.forEach(function(streamNameAttrs){
33
+ streamNameAttrs.id = streamNameAttrs.stream_name;
34
+ streamNameAttrs.text = streamNameAttrs.stream_name;
35
+ });
36
+ return data;
37
+ },
38
+ },
39
+ allowClear: true
40
+ });
41
+ $streamIdSelect.select2({
42
+ ajax: {
43
+ url: $streamIdSelect.data('url'),
44
+ data: function (params) {
45
+ params.context = $contextSelect.val();
46
+ params.stream_name = $streamNameSelect.val();
47
+
48
+ return params;
49
+ },
50
+ processResults: function(data, params){
51
+ params.starting_id = data.pagination.starting_id;
52
+ data.results.forEach(function(streamNameAttrs){
53
+ streamNameAttrs.id = streamNameAttrs.stream_id;
54
+ streamNameAttrs.text = streamNameAttrs.stream_id;
55
+ });
56
+ return data;
57
+ },
58
+ },
59
+ allowClear: true
60
+ });
61
+ }
62
+
63
+ let initEventTypeFilterAutocomplete = function($filter) {
64
+ let $eventTypeSelect = $filter.find('select');
65
+ $eventTypeSelect.select2({
66
+ ajax: {
67
+ url: $eventTypeSelect.data('url'),
68
+ processResults: function(data, params){
69
+ params.starting_id = data.pagination.starting_id;
70
+ data.results.forEach(function(contextAttrs){
71
+ contextAttrs.id = contextAttrs.event_type;
72
+ contextAttrs.text = contextAttrs.event_type;
73
+ });
74
+ return data;
75
+ },
76
+ },
77
+ allowClear: true
78
+ });
79
+ }
80
+
81
+ // Per page drop down
82
+ $('#per_page_select').change(function(e){
83
+ window.location.href = $(this).find('option:selected').data('url');
84
+ });
85
+
86
+ let $filtersForm = $('#filters-form');
87
+ // Stream filter template
88
+ let streamFilterTmpl = $('#stream-filter-tmpl').text();
89
+ // Event type filter template
90
+ let eventFilterTmpl = $('#event-type-filter-tmpl').text();
91
+
92
+ // Remove filter button
93
+ $filtersForm.on('click', '.remove-filter', function(){
94
+ $(this).parents('.form-row').remove();
95
+ });
96
+ // Add stream filter button
97
+ $filtersForm.on('click', '.add-stream-filter', function(){
98
+ let filtersNum = $filtersForm.find('.stream-filters').children().length + '';
99
+ $filtersForm.find('.stream-filters').append(streamFilterTmpl.replace(/%NUM%/g, filtersNum));
100
+ initStreamFilterAutocomplete($filtersForm.find('.stream-filters').children().last());
101
+ });
102
+ // Add event type filter button
103
+ $filtersForm.on('click', '.add-event-filter', function(){
104
+ $filtersForm.find('.event-filters').append(eventFilterTmpl);
105
+ initEventTypeFilterAutocomplete($filtersForm.find('.event-filters').children().last());
106
+ });
107
+ // Init select2 for stream filters which were initially rendered
108
+ $filtersForm.find('.stream-filters').children().each(function(){
109
+ initStreamFilterAutocomplete($(this));
110
+ });
111
+ // Init select2 for event type filters which were initially rendered
112
+ $filtersForm.find('.event-filters').children().each(function(){
113
+ initEventTypeFilterAutocomplete($(this));
114
+ });
115
+
116
+ let autoRefreshInterval;
117
+ $('#auto-refresh').change(function(){
118
+ if($(this).is(':checked')) {
119
+ autoRefreshInterval = setInterval(function(){
120
+ $.getJSON(window.location.href).success(function(response, textStatus, xhr){
121
+ console.log(textStatus);
122
+ if(textStatus === 'success') {
123
+ $('#events-table').find('tbody').html(response.events);
124
+ $('#total-count').html(response.total_count);
125
+ $('#pagination').html(response.pagination);
126
+ }
127
+ });
128
+ }, 2000);
129
+ } else {
130
+ clearInterval(autoRefreshInterval);
131
+ }
132
+ });
133
+
134
+ // Toggle event's JSON data/metadata details
135
+ $('#events-table').on('click', '.toggle-event-data', function(){
136
+ $(this).parents('tr').next().toggleClass('d-none');
137
+ });
138
+
139
+ // Handle a[data-method]. It works very similar to rails ujs
140
+ $('body').on('click', 'a[data-method]', function(e){
141
+ e.preventDefault();
142
+
143
+ let href = $(this).attr('href');
144
+ let method = $(this).data('method');
145
+ let $form = $(`<form method="${method}" action="${href}"></form>`);
146
+ let hashInput = `<input name="hash" value="${window.location.hash}" type="hidden" />`;
147
+
148
+ $form.hide().append(hashInput).appendTo('body');
149
+ $form.submit();
150
+ });
151
+
152
+ // When user navigates through SubscriptionsSet-s tabs - also change a hash of the url. Its value is send when
153
+ // clicking on a[data-method] links
154
+ $('.set-tab').click(function(e){
155
+ window.location.hash = $(this).attr('href');
156
+ })
157
+ // Open correct SubscriptionsSet tab on page load based on location's hash value. So, e.g., if you navigated to
158
+ // #set-1 SubscriptionsSet, then, on page reload - the correct tab will be opened
159
+ if(window.location.hash !== "") {
160
+ $(`.set-tab[href="${window.location.hash}"]`).get(0).click();
161
+ }
162
+ });