Сервис Яндекс Карты предоставляет один "чудесный" инструмент Конструктор Яндекс Карт. Хотя уже пару лет существует более удобный "аналог" конструктора, пользователей продукта от Яндекс не убавляется. Ко мне обратился один программист и попросил добавить некий функционал к карте, созданной на этом конструкторе. На первый взгляд ничего сложного, карта создается на Мапс АПИ Яндекс и оно без проблем могло бы быть использовано. Однако у программистов Яндекса свое мнение на этот счет - весь API они прячут в контейнер функции и никаких интерфейсов не предоставляют. Чтобы как-то манипулировать картой, создавать на ней объекты и прочее, необходимо иметь доступ к экземпляру ymaps.Map
, созданному в недрах конструктора. Чтобы было понятнее приведу пример
Вот, что дает конструктор от Яндекс
<script type="text/javascript" charset="utf-8" src="https://api-maps.yandex.ru/services/constructor/1.0/js/?sid=&width=600&height=450"></script>
А вот этот код уже в развертке, пропущенный через jsbeautifier.org
(function(A, o, f, s, B, c) { if (!A.response) { return } var m = window.location.protocol === "file:" ? "http:" : "", a = m + c + "2.0/", j = { MAP: "yandex#map", SATELLITE: "yandex#satellite", HYBRID: "yandex#hybrid", PMAP: "yandex#publicMap" }, r = A.response.map, t = ["package.map", "package.controls", "package.geoObjects"], u = o[0] ? o[0] + "px" : "100%", p = o[1] ? o[1] + "px" : "100%", y = String(Number(new Date)) + String(Math.round(Math.random() * 1000000)), l = "ymaps" + y, b = "fid" + y, C = r.lang || "ru-RU", d = a + "?lang=" + C + "&coordorder=longlat&load=" + t.join(",") + "&wizard=constructor&onload=" + b + "&ns=" + B, e = document.getElementsByTagName("script"), z, h = f.match(/\/\/(.+)$/), q = h && h[1], n, k; if (q) { for (var x = e.length - 1; x > -1; x--) { z = e[x]; if (z.src.indexOf(q) !== -1 && !z.ctorInited) { z.ctorInited = true; break } } if (z) { w(function() { if (s) { k = document.getElementById(s) } n = g(); if (k) { k.appendChild(n) } else { z.parentNode.insertBefore(n, z) } v(n); if (z.parentNode) { z.parentNode.removeChild(z) } }) } } function g() { var i = document.createElement("ymaps"); i.setAttribute("id", l); i.style.display = "block"; i.style.width = u; i.style.height = p; return i } function v(G) { var F = window[B], E = new F.Map(G, { center: r.center, zoom: r.zoom, type: j[r.type] }, { autoFitToViewport: "always", geoObjectStrokeOpacity: 1, geoObjectFillOpacity: 1, geoObjectStrokeColor: "ff0000e6", geoObjectStrokeWidth: 5, geoObjectFillColor: "ff000099", geoObjectIconContentLayout: F.templateLayoutFactory.createClass("$[properties.number]"), geoObjectBalloonContentBodyLayout: F.templateLayoutFactory.createClass("$[properties.name]") }), M = r.geoObjects, Q = r.styles, P = ["yandex#map", "yandex#satellite", "yandex#hybrid"]; if (r.lang === "ru-RU" || r.lang === "uk-UA") { P.push("yandex#publicMap") } for (var H in Q) { if (Q.hasOwnProperty(H)) { F.option.presetStorage.add("ctor#" + H, Q[H]) } } var N = "zoomControl"; var L = "smallZoomControl"; var K = E.container.getSize()[1] < 270 ? L : N; E.controls.add(K).add("mapTools").add(new F.control.TypeSelector(P)); E.events.add("sizechange", function(R) { var i = R.get("newSize")[1]; if (K === L && i >= 270) { E.controls.remove(K).add(K = N) } if (K === N && i < 270) { E.controls.remove(K).add(K = L) } }); for (var J = 0, O = M.length; J < O; J++) { var I = M[J], D = I.style; E.geoObjects.add(new F.GeoObject({ geometry: I.geometry, properties: { name: I.name, number: I.number } }, { preset: D.indexOf("#") === 0 ? "ctor" + D : D })) } } function w(F) { var E = window[B]; if (E) { E.load(t, F) } else { window[b] = function() { setTimeout(F, 0); window[b] = null }; var D = document.getElementsByTagName("head")[0], i = document.createElement("script"); i.charset = "utf-8"; i.src = d; D.insertBefore(i, D.firstChild) } } }({ "response": { "map": { "type": "MAP", "boundedBy": [ [55.880159999999997, 54.693719999999999], [56.08616, 54.783110000000001] ], "zoom": 12, "size": [600, 450], "center": [55.983159999999998, 54.738439999999997], "lang": "ru-RU", "uid": 26102747, "sid": "jo_8MdujtygRn5TUUluLw2BnNI2qEljp", "created": 1428467161, "updated": 1428467161, "access": 65535, "favourite": 0, "name": "Без названия", "description": "", "geoObjects": [] } } }, ["600", "450"], "https://api-maps.yandex.ru/services/constructor/1.0/js/?sid=&width=600&height=450", "", "ymaps_ctor", "//api-maps.yandex.ru/"));
Все завернуто в анонимную функцию. Экземпляр ymaps.Map создается на строке 64. Как видите он запоминается в локальную переменную, которую увидеть извне невозможно. Так как же получить доступ к этой переменной?
Далее пойдет код, который можно смело показывать в рубрике: Как не следует программировать на JavaScript
План такой: переопределить функцию, которая вызывается в момент когда АПИ карт загружено. Потом в его вызове переопределить конструктор ymaps.Map
, и в нем уже каждый созданный экземпляр сохранять в глобальной области видимости.
Как же это сделать?
На помощь нам придет вся сила JavaScript, а именно такая его фишка, что любую функцию или метод, можно переопределить. Что нам это дает? Еще раз взгляните на код выше. Функция callback, которая вызывается для создания карты, по готовности API, получает название на строке 17. Оно формируется случайно
y = String(Number(new Date)) + String(Math.round(Math.random() * 1000000)), b = "fid" + y,
По идее переопределить функцию, не зная названия, мы не можем. Может тогда капнем глубже и переопределим Date
и Math.random
?!
<script> var Date1 = Date, round1 = Math.random; Date = function () { return new Number(1111111); } Math.random = function () { return 111111; } </script> <script src="http://api-maps.yandex.ru/services/constructor/1.0/js/?sid=&width=auto&height=345&width=617" type="text/javascript"></script><script> Date = Date1; Math.random = round1; </script>
Финт ушами и никакого мошенничества. Теперь мы можем использовать функцию window['fid1111111111111000000']
. Ее тоже нужно переопределить
Date = Date1; Math.random = round1; var oldmap = window['fid1111111111111000000']; var map; window['fid1111111111111000000'] = function () { var MapCreattor = window.ymaps_ctor.Map; window.ymaps_ctor.Map = function (a,b,c) { map = new MapCreattor(a,b,c); return map; } oldmap(); }
API Яши доступно через поле window.ymaps_ctor
. Это вы поймете, анализируя первый листинг. Это все! На 6-ой строке мы переопределяем конструктор. Меняем его на свой, а уже в нем вызываем старый конструктор, сохраняя ссылку на карту в свою переменную. Profit!!!
Теперь с этим объектом можно делать все что угодно: добавлять метки, полигоны и любой другой элемент.
Яндекс тролит программистов делая вот такую документацию API Конструктора карт в которой нет ни слова о том, что вообще можно сделать используя конструктор. Есть в этом доля смысла - если вы хотите большего, то почему бы не использовать само АПИ.
В этом они правы, но ведь конструктор удобен тем, что позволяет визуально создавать сколько угодно меток и полигонов, задавать им всплывающие окна и т.п. Т.е. всю работу с контентом карты можно было бы делать через него. А вот программировать, добавлять свои элементы управления, макеты и поведения меток, можно было бы делать отдельно. Через некий интерфейс.
Код, приведенный выше ужасен. И не стоит прибегать к нему в серьезных проектах. Лучше сразу воспользоваться нормальным конструктором карт, который выдаст вам код на API. и уже вы сможете делать с ним все что хотите. А если у вас сайт на Joomla, то можно вообще не программировать, а воспользоваться компонентом Яндекс Карты - тут Вам доступен и визуальный редактор ака Конструктор, и все плюшки API Yandex Maps
Не бойтесь сложных решений, JavaScript гибок и этот пост в очередной раз доказывает, что в нем нет закрытых тем. Возможно все!
Всего доброго!
Комментарии