c80_map_floors 0.1.0.9 → 0.1.0.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/lib/awesomplete.js +450 -0
  3. data/app/assets/javascripts/map_objects/area.js +12 -8
  4. data/app/assets/javascripts/map_objects/building.js +23 -6
  5. data/app/assets/javascripts/src/main.js +12 -2
  6. data/app/assets/javascripts/src/state_controller.js +50 -8
  7. data/app/assets/javascripts/svg_elements/admin_building_label.js +6 -6
  8. data/app/assets/javascripts/svg_elements/building_label.js +88 -60
  9. data/app/assets/javascripts/ui/tabs/tabs.js +55 -2
  10. data/app/assets/javascripts/view/building_info/building_info.js +85 -8
  11. data/app/assets/javascripts/view/building_info/mobj_info_parser.js +316 -12
  12. data/app/assets/javascripts/view/search_gui.js +295 -0
  13. data/app/assets/stylesheets/c80_map_floors.scss +3 -1
  14. data/app/assets/stylesheets/lib/awesomplete.scss +104 -0
  15. data/app/assets/stylesheets/lib_custom/awesomplete.scss +8 -0
  16. data/app/assets/stylesheets/map.scss +61 -16
  17. data/app/assets/stylesheets/ui/search_gui.scss +51 -0
  18. data/app/assets/stylesheets/ui/tabs_js.scss +25 -0
  19. data/app/assets/stylesheets/view/buttons/back_to_map_button.scss +1 -1
  20. data/app/assets/stylesheets/view/elems/building_info.scss +1 -1
  21. data/app/assets/stylesheets/view/elems/free_areas_label.scss +6 -1
  22. data/app/controllers/c80_map_floors/ajax_controller.rb +82 -0
  23. data/app/helpers/c80_map_floors/search_gui_helper.rb +19 -0
  24. data/app/models/c80_map_floors/area.rb +1 -1
  25. data/app/models/c80_map_floors/area_representator.rb +13 -22
  26. data/app/models/c80_map_floors/building_representator.rb +8 -1
  27. data/app/models/c80_map_floors/floor.rb +1 -1
  28. data/app/models/c80_map_floors/map_building.rb +2 -2
  29. data/app/models/c80_map_floors/map_json.rb +2 -1
  30. data/app/views/c80_map_floors/_map_row_index.html.erb +5 -0
  31. data/app/views/c80_map_floors/shared/map_row/_search_gui.html.erb +11 -0
  32. data/config/routes.rb +2 -0
  33. data/lib/c80_map_floors/version.rb +1 -1
  34. metadata +9 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 31966802b697ea9309a9b2f6200a0a5bdda7faee
4
- data.tar.gz: 72e5e8432d971bd72cbd178eca37e7dd93c3b04f
3
+ metadata.gz: 2b17f8e15a4a08414b6675fbe965a5cbef72d10b
4
+ data.tar.gz: f21b31c759a5e8661e6a186315f2e9c2a3cdcc55
5
5
  SHA512:
6
- metadata.gz: bf3b68b79cee003d5566fc9d5269fe322db7db74f274ffd8518ad72fc9feaa0c1eda21bc505d9091f84d56ffdd4421fb3af209f5465cc05e9af57f1fb99bb8c1
7
- data.tar.gz: 48137d966c943d594adf406a92c6a67b0bf275745490c036c1c7cc0f5ba1e8e200d67fcd0726cd6c9cf80eb3a000323047b932535bfa45046e2adb0d879419cd
6
+ metadata.gz: cf959e253e274628ac7b7610263b15960247437ec8d556f7b6c2981ef947ce3687997b1dee8fb8a467bf85a4e11f622c14e7a10903de2697b4069116de418cbe
7
+ data.tar.gz: 931f343f204d4393d3c67b62c3136cfd9d577e0a47a02f610da205613f3d6d65f7856741b9c13e8c0fbc610e26f86dc545ca44cde0b8aa04fa7445901018e5b7
@@ -0,0 +1,450 @@
1
+ /**
2
+ * Simple, lightweight, usable local autocomplete library for modern browsers
3
+ * Because there weren’t enough autocomplete scripts in the world? Because I’m completely insane and have NIH syndrome? Probably both. :P
4
+ * @author Lea Verou http://leaverou.github.io/awesomplete
5
+ * MIT license
6
+ */
7
+
8
+ (function () {
9
+
10
+ var _ = function (input, o) {
11
+ var me = this;
12
+
13
+ // Setup
14
+
15
+ this.isOpened = false;
16
+
17
+ this.input = $(input);
18
+ this.input.setAttribute("autocomplete", "off");
19
+ this.input.setAttribute("aria-autocomplete", "list");
20
+
21
+ o = o || {};
22
+
23
+ configure(this, {
24
+ minChars: 2,
25
+ maxItems: 10,
26
+ autoFirst: false,
27
+ data: _.DATA,
28
+ filter: _.FILTER_CONTAINS,
29
+ sort: _.SORT_BYLENGTH,
30
+ item: _.ITEM,
31
+ replace: _.REPLACE
32
+ }, o);
33
+
34
+ this.index = -1;
35
+
36
+ // Create necessary elements
37
+
38
+ this.container = $.create("div", {
39
+ className: "awesomplete",
40
+ around: input
41
+ });
42
+
43
+ this.ul = $.create("ul", {
44
+ hidden: "hidden",
45
+ inside: this.container
46
+ });
47
+
48
+ this.status = $.create("span", {
49
+ className: "visually-hidden",
50
+ role: "status",
51
+ "aria-live": "assertive",
52
+ "aria-relevant": "additions",
53
+ inside: this.container
54
+ });
55
+
56
+ // Bind events
57
+
58
+ $.bind(this.input, {
59
+ "input": this.evaluate.bind(this),
60
+ "blur": this.close.bind(this, { reason: "blur" }),
61
+ "keydown": function(evt) {
62
+ var c = evt.keyCode;
63
+
64
+ // If the dropdown `ul` is in view, then act on keydown for the following keys:
65
+ // Enter / Esc / Up / Down
66
+ if(me.opened) {
67
+ if (c === 13 && me.selected) { // Enter
68
+ evt.preventDefault();
69
+ me.select();
70
+ }
71
+ else if (c === 27) { // Esc
72
+ me.close({ reason: "esc" });
73
+ }
74
+ else if (c === 38 || c === 40) { // Down/Up arrow
75
+ evt.preventDefault();
76
+ me[c === 38? "previous" : "next"]();
77
+ }
78
+ }
79
+ }
80
+ });
81
+
82
+ $.bind(this.input.form, {"submit": this.close.bind(this, { reason: "submit" })});
83
+
84
+ $.bind(this.ul, {"mousedown": function(evt) {
85
+ var li = evt.target;
86
+
87
+ if (li !== this) {
88
+
89
+ while (li && !/li/i.test(li.nodeName)) {
90
+ li = li.parentNode;
91
+ }
92
+
93
+ if (li && evt.button === 0) { // Only select on left click
94
+ evt.preventDefault();
95
+ me.select(li, evt.target);
96
+ }
97
+ }
98
+ }});
99
+
100
+ if (this.input.hasAttribute("list")) {
101
+ this.list = "#" + this.input.getAttribute("list");
102
+ this.input.removeAttribute("list");
103
+ }
104
+ else {
105
+ this.list = this.input.getAttribute("data-list") || o.list || [];
106
+ }
107
+
108
+ _.all.push(this);
109
+ };
110
+
111
+ _.prototype = {
112
+ set list(list) {
113
+ if (Array.isArray(list)) {
114
+ this._list = list;
115
+ }
116
+ else if (typeof list === "string" && list.indexOf(",") > -1) {
117
+ this._list = list.split(/\s*,\s*/);
118
+ }
119
+ else { // Element or CSS selector
120
+ list = $(list);
121
+
122
+ if (list && list.children) {
123
+ var items = [];
124
+ slice.apply(list.children).forEach(function (el) {
125
+ if (!el.disabled) {
126
+ var text = el.textContent.trim();
127
+ var value = el.value || text;
128
+ var label = el.label || text;
129
+ if (value !== "") {
130
+ items.push({ label: label, value: value });
131
+ }
132
+ }
133
+ });
134
+ this._list = items;
135
+ }
136
+ }
137
+
138
+ if (document.activeElement === this.input) {
139
+ this.evaluate();
140
+ }
141
+ },
142
+
143
+ get selected() {
144
+ return this.index > -1;
145
+ },
146
+
147
+ get opened() {
148
+ return this.isOpened;
149
+ },
150
+
151
+ close: function (o) {
152
+ if (!this.opened) {
153
+ return;
154
+ }
155
+
156
+ this.ul.setAttribute("hidden", "");
157
+ this.isOpened = false;
158
+ this.index = -1;
159
+
160
+ $.fire(this.input, "awesomplete-close", o || {});
161
+ },
162
+
163
+ open: function () {
164
+ this.ul.removeAttribute("hidden");
165
+ this.isOpened = true;
166
+
167
+ if (this.autoFirst && this.index === -1) {
168
+ this.goto(0);
169
+ }
170
+
171
+ $.fire(this.input, "awesomplete-open");
172
+ },
173
+
174
+ next: function () {
175
+ var count = this.ul.children.length;
176
+ this.goto(this.index < count - 1 ? this.index + 1 : (count ? 0 : -1) );
177
+ },
178
+
179
+ previous: function () {
180
+ var count = this.ul.children.length;
181
+ var pos = this.index - 1;
182
+
183
+ this.goto(this.selected && pos !== -1 ? pos : count - 1);
184
+ },
185
+
186
+ // Should not be used, highlights specific item without any checks!
187
+ goto: function (i) {
188
+ var lis = this.ul.children;
189
+
190
+ if (this.selected) {
191
+ lis[this.index].setAttribute("aria-selected", "false");
192
+ }
193
+
194
+ this.index = i;
195
+
196
+ if (i > -1 && lis.length > 0) {
197
+ lis[i].setAttribute("aria-selected", "true");
198
+ this.status.textContent = lis[i].textContent;
199
+
200
+ // scroll to highlighted element in case parent's height is fixed
201
+ this.ul.scrollTop = lis[i].offsetTop - this.ul.clientHeight + lis[i].clientHeight;
202
+
203
+ $.fire(this.input, "awesomplete-highlight", {
204
+ text: this.suggestions[this.index]
205
+ });
206
+ }
207
+ },
208
+
209
+ select: function (selected, origin) {
210
+ if (selected) {
211
+ this.index = $.siblingIndex(selected);
212
+ } else {
213
+ selected = this.ul.children[this.index];
214
+ }
215
+
216
+ if (selected) {
217
+ var suggestion = this.suggestions[this.index];
218
+
219
+ var allowed = $.fire(this.input, "awesomplete-select", {
220
+ text: suggestion,
221
+ origin: origin || selected
222
+ });
223
+
224
+ if (allowed) {
225
+ this.replace(suggestion);
226
+ this.close({ reason: "select" });
227
+ $.fire(this.input, "awesomplete-selectcomplete", {
228
+ text: suggestion
229
+ });
230
+ }
231
+ }
232
+ },
233
+
234
+ evaluate: function() {
235
+ var me = this;
236
+ var value = this.input.value;
237
+
238
+ if (value.length >= this.minChars && this._list.length > 0) {
239
+ this.index = -1;
240
+ // Populate list with options that match
241
+ this.ul.innerHTML = "";
242
+
243
+ this.suggestions = this._list
244
+ .map(function(item) {
245
+ return new Suggestion(me.data(item, value));
246
+ })
247
+ .filter(function(item) {
248
+ return me.filter(item, value);
249
+ })
250
+ .sort(this.sort)
251
+ .slice(0, this.maxItems);
252
+
253
+ this.suggestions.forEach(function(text) {
254
+ me.ul.appendChild(me.item(text, value));
255
+ });
256
+
257
+ if (this.ul.children.length === 0) {
258
+ this.close({ reason: "nomatches" });
259
+ } else {
260
+ this.open();
261
+ }
262
+ }
263
+ else {
264
+ this.close({ reason: "nomatches" });
265
+ }
266
+ }
267
+ };
268
+
269
+ // Static methods/properties
270
+
271
+ _.all = [];
272
+
273
+ _.FILTER_CONTAINS = function (text, input) {
274
+ return RegExp($.regExpEscape(input.trim()), "i").test(text);
275
+ };
276
+
277
+ _.FILTER_STARTSWITH = function (text, input) {
278
+ return RegExp("^" + $.regExpEscape(input.trim()), "i").test(text);
279
+ };
280
+
281
+ _.SORT_BYLENGTH = function (a, b) {
282
+ if (a.length !== b.length) {
283
+ return a.length - b.length;
284
+ }
285
+
286
+ return a < b? -1 : 1;
287
+ };
288
+
289
+ _.ITEM = function (text, input) {
290
+ var html = input.trim() === '' ? text : text.replace(RegExp($.regExpEscape(input.trim()), "gi"), "<mark>$&</mark>");
291
+ return $.create("li", {
292
+ innerHTML: html,
293
+ "aria-selected": "false"
294
+ });
295
+ };
296
+
297
+ _.REPLACE = function (text) {
298
+ this.input.value = text.value;
299
+ };
300
+
301
+ _.DATA = function (item/*, input*/) { return item; };
302
+
303
+ // Private functions
304
+
305
+ function Suggestion(data) {
306
+ var o = Array.isArray(data)
307
+ ? { label: data[0], value: data[1] }
308
+ : typeof data === "object" && "label" in data && "value" in data ? data : { label: data, value: data };
309
+
310
+ this.label = o.label || o.value;
311
+ this.value = o.value;
312
+ }
313
+ Object.defineProperty(Suggestion.prototype = Object.create(String.prototype), "length", {
314
+ get: function() { return this.label.length; }
315
+ });
316
+ Suggestion.prototype.toString = Suggestion.prototype.valueOf = function () {
317
+ return "" + this.label;
318
+ };
319
+
320
+ function configure(instance, properties, o) {
321
+ for (var i in properties) {
322
+ var initial = properties[i],
323
+ attrValue = instance.input.getAttribute("data-" + i.toLowerCase());
324
+
325
+ if (typeof initial === "number") {
326
+ instance[i] = parseInt(attrValue);
327
+ }
328
+ else if (initial === false) { // Boolean options must be false by default anyway
329
+ instance[i] = attrValue !== null;
330
+ }
331
+ else if (initial instanceof Function) {
332
+ instance[i] = null;
333
+ }
334
+ else {
335
+ instance[i] = attrValue;
336
+ }
337
+
338
+ if (!instance[i] && instance[i] !== 0) {
339
+ instance[i] = (i in o)? o[i] : initial;
340
+ }
341
+ }
342
+ }
343
+
344
+ // Helpers
345
+
346
+ var slice = Array.prototype.slice;
347
+
348
+ function $(expr, con) {
349
+ return typeof expr === "string"? (con || document).querySelector(expr) : expr || null;
350
+ }
351
+
352
+ function $$(expr, con) {
353
+ return slice.call((con || document).querySelectorAll(expr));
354
+ }
355
+
356
+ $.create = function(tag, o) {
357
+ var element = document.createElement(tag);
358
+
359
+ for (var i in o) {
360
+ var val = o[i];
361
+
362
+ if (i === "inside") {
363
+ $(val).appendChild(element);
364
+ }
365
+ else if (i === "around") {
366
+ var ref = $(val);
367
+ ref.parentNode.insertBefore(element, ref);
368
+ element.appendChild(ref);
369
+ }
370
+ else if (i in element) {
371
+ element[i] = val;
372
+ }
373
+ else {
374
+ element.setAttribute(i, val);
375
+ }
376
+ }
377
+
378
+ return element;
379
+ };
380
+
381
+ $.bind = function(element, o) {
382
+ if (element) {
383
+ for (var event in o) {
384
+ var callback = o[event];
385
+
386
+ event.split(/\s+/).forEach(function (event) {
387
+ element.addEventListener(event, callback);
388
+ });
389
+ }
390
+ }
391
+ };
392
+
393
+ $.fire = function(target, type, properties) {
394
+ var evt = document.createEvent("HTMLEvents");
395
+
396
+ evt.initEvent(type, true, true );
397
+
398
+ for (var j in properties) {
399
+ evt[j] = properties[j];
400
+ }
401
+
402
+ return target.dispatchEvent(evt);
403
+ };
404
+
405
+ $.regExpEscape = function (s) {
406
+ return s.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&");
407
+ };
408
+
409
+ $.siblingIndex = function (el) {
410
+ /* eslint-disable no-cond-assign */
411
+ for (var i = 0; el = el.previousElementSibling; i++);
412
+ return i;
413
+ };
414
+
415
+ // Initialization
416
+
417
+ function init() {
418
+ $$("input.awesomplete").forEach(function (input) {
419
+ new _(input);
420
+ });
421
+ }
422
+
423
+ // Are we in a browser? Check for Document constructor
424
+ if (typeof Document !== "undefined") {
425
+ // DOM already loaded?
426
+ if (document.readyState !== "loading") {
427
+ init();
428
+ }
429
+ else {
430
+ // Wait for it
431
+ document.addEventListener("DOMContentLoaded", init);
432
+ }
433
+ }
434
+
435
+ _.$ = $;
436
+ _.$$ = $$;
437
+
438
+ // Make sure to export Awesomplete on self when in a browser
439
+ if (typeof self !== "undefined") {
440
+ self.Awesomplete = _;
441
+ }
442
+
443
+ // Expose Awesomplete as a CJS module
444
+ if (typeof module === "object" && module.exports) {
445
+ module.exports = _;
446
+ }
447
+
448
+ return _;
449
+
450
+ }());
@@ -110,17 +110,19 @@ function Area() {
110
110
  _this._polygon_overlay.hover(_this._mouse_in, _this._mouse_out);
111
111
  _this._calcBBox();
112
112
 
113
- // TODO:: старый код не работает: вместо area_hash должно быть data (и так далее)
113
+ //<editor-fold desc="// если у полигона имеется Площадь - добавим css класс (для разукрашивания таких полигонов)">
114
114
  var k = 'unassigned';
115
- if (options.area_hash != undefined) {
116
- if (typeof options.area_hash.id !== 'undefined') {
117
- k = 'free';
118
- if (!options.area_hash.is_free) {
119
- k = 'busy';
115
+ if (options['data'] != undefined) {
116
+ if (typeof options['data']['id'] !== 'undefined') {
117
+ console.log('<Area.init> [breakpoint]');
118
+ k = 'busy';
119
+ if (options['data']['is_free']) {
120
+ k = 'free';
120
121
  }
121
122
  }
122
123
  }
123
124
  _this._polygon.parent().attr("class", k);
125
+ //</editor-fold>
124
126
 
125
127
  };
126
128
 
@@ -239,12 +241,14 @@ function Area() {
239
241
  _this._mouse_in = function () {
240
242
  //console.log('<Area._mouse_in>');
241
243
  //console.log(_this._polygon);
242
- _this._polygon.attr('class', 'hover');
244
+ //_this._polygon.attr('class', 'hover');
245
+ $(_this._polygon).addClass('hover');
243
246
  };
244
247
 
245
248
  _this._mouse_out = function () {
246
249
  //console.log('<Area._mouse_out>');
247
- _this._polygon.attr('class', '');
250
+ //_this._polygon.attr('class', '');
251
+ $(_this._polygon).removeClass('hover');
248
252
  };
249
253
 
250
254
  _this._calc_polygon_attr = function () {
@@ -27,8 +27,9 @@ function Building() {
27
27
  // если вошли в какой-то этаж - эта переменная будет хранить ссылку на объект с данными полигона Этажа из locations.json
28
28
  var _json_current_floor = null;
29
29
 
30
- _this._label = null;
31
- _this._admin_label = null;
30
+ _this.id = null;
31
+ _this._label = null; // подпись над зданием - сколько магазинов (удовлетворяющих поиску) находятся в Здании
32
+ _this._admin_label = null; // админский лейбл - айдишник и название Здания (привязанного к полигону), видимый в режиме редактирования
32
33
 
33
34
  var _zoomToMe = function () {
34
35
 
@@ -163,7 +164,7 @@ function Building() {
163
164
 
164
165
  /**
165
166
  *
166
- * @param options - Это C80MapFloors::MapBuilding.my_as_json
167
+ * @param options - Это C80MapFloors::MapBuilding.my_as_json5
167
168
  * @param link_to_map
168
169
  */
169
170
  _this.init = function (options, link_to_map) {
@@ -211,9 +212,6 @@ function Building() {
211
212
 
212
213
  _this._calcBBox();
213
214
 
214
- // TODO:: подпись над зданием - сколько свободных площадей
215
- _this._label = new BuildingLabel(options, _map);
216
-
217
215
  // подпись над полигоном показываем только админам
218
216
  // UPD: не прокатит, т.к. здания инициализируются ДО того, как приходит ajax/map_edit_buttons, где IS_ADMIN=true
219
217
  //if (IS_ADMIN) {
@@ -407,4 +405,23 @@ function Building() {
407
405
  _this._admin_label = null;
408
406
  }
409
407
  };
408
+
409
+ // показать/скрыть зеленые кружки с цифрами
410
+ this.greenCircleShow = function (count) { // count - кол-во (удовлетворяющих поиску) магазинов в здании
411
+ console.log('<Building.greenCircleShow> Покажем зеленую метку над зданием.');
412
+ if (_this._label == null) {
413
+ _this._label = new BuildingLabel({
414
+ x: _options['coords'][0],
415
+ y: _options['coords'][1],
416
+ count: count
417
+ }, _map);
418
+ }
419
+ };
420
+ this.greenCircleHide = function () {
421
+ console.log('<Building.greenCircleShow> Скроем зеленую метку над зданием.');
422
+ if (_this._label != null) {
423
+ _this._label.destroy();
424
+ _this._label = null;
425
+ }
426
+ };
410
427
  }
@@ -89,6 +89,7 @@ var InitMap = function (params) {
89
89
  self.last_clicked_g = null; // начали просматривать area\building (запустили сессию), и здесь храним ссылку на последний кликнутый полигон из svg_overlay в течение сессии
90
90
  //self.o.dnd_enable = null; // если да, то можно карту dnd мышкой
91
91
  self.building_info_klass = null; // класс, занимающися отображением данных об этаже\здании\площади
92
+ self.search_gui_klass = null; // класс, занимающийся обслуживанием поисковых запросов пользователя карты
92
93
 
93
94
  // во время анимации каждый шаг рассчитывается мгновенный scale
94
95
  self.scale_during_animation = null;
@@ -291,6 +292,9 @@ var InitMap = function (params) {
291
292
  }
292
293
  });
293
294
 
295
+ // инициализируем класс, обслуживающий поиск
296
+ self.search_gui_klass = new SearchGUI(self);
297
+
294
298
  // начнём слушать окно браузера
295
299
  $(window).resize(function () {
296
300
 
@@ -848,6 +852,12 @@ var InitMap = function (params) {
848
852
  //ip = Polygon.createFromSaved(iobj);
849
853
  //utils.id('svg').appendChild(ip.g);
850
854
  }
855
+
856
+ // Только после того, как нарисуем всех детей на карте, подсветим результаты поиска
857
+ if (self.search_gui_klass != null) {
858
+ self.search_gui_klass.handleSearchResults();
859
+ }
860
+
851
861
  };
852
862
 
853
863
  /**
@@ -1359,7 +1369,7 @@ var InitMap = function (params) {
1359
1369
  }
1360
1370
  };
1361
1371
 
1362
- // показать инфо о просматриваемой площади
1372
+ // TODO:: показать инфо о просматриваемой площади (это код, который остался без изменений от c80_map)
1363
1373
  self.showAreaInfo = function (area_json, parent_floor_json) {
1364
1374
  //console.log(area_hash);
1365
1375
 
@@ -1377,7 +1387,7 @@ var InitMap = function (params) {
1377
1387
  // "price": "от 155 руб/кв.м в месяц"
1378
1388
  // }
1379
1389
 
1380
- // так было в c80_map
1390
+ // так стало в c80_map_floors
1381
1391
  //"rent_building_hash": {
1382
1392
  // "id": 2,
1383
1393
  // "title": "Здание 2",