").append(m.parseHTML(a)).find(d):a)}).complete(c&&function(a,b){g.each(c,e||[a.responseText,b,a])}),this},m.expr.filters.animated=function(a){return m.grep(m.timers,function(b){return a===b.elem}).length};var cd=a.document.documentElement;function dd(a){return m.isWindow(a)?a:9===a.nodeType?a.defaultView||a.parentWindow:!1}m.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=m.css(a,"position"),l=m(a),n={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=m.css(a,"top"),i=m.css(a,"left"),j=("absolute"===k||"fixed"===k)&&m.inArray("auto",[f,i])>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),m.isFunction(b)&&(b=b.call(a,c,h)),null!=b.top&&(n.top=b.top-h.top+g),null!=b.left&&(n.left=b.left-h.left+e),"using"in b?b.using.call(a,n):l.css(n)}},m.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){m.offset.setOffset(this,a,b)});var b,c,d={top:0,left:0},e=this[0],f=e&&e.ownerDocument;if(f)return b=f.documentElement,m.contains(b,e)?(typeof e.getBoundingClientRect!==K&&(d=e.getBoundingClientRect()),c=dd(f),{top:d.top+(c.pageYOffset||b.scrollTop)-(b.clientTop||0),left:d.left+(c.pageXOffset||b.scrollLeft)-(b.clientLeft||0)}):d},position:function(){if(this[0]){var a,b,c={top:0,left:0},d=this[0];return"fixed"===m.css(d,"position")?b=d.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),m.nodeName(a[0],"html")||(c=a.offset()),c.top+=m.css(a[0],"borderTopWidth",!0),c.left+=m.css(a[0],"borderLeftWidth",!0)),{top:b.top-c.top-m.css(d,"marginTop",!0),left:b.left-c.left-m.css(d,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||cd;while(a&&!m.nodeName(a,"html")&&"static"===m.css(a,"position"))a=a.offsetParent;return a||cd})}}),m.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c=/Y/.test(b);m.fn[a]=function(d){return V(this,function(a,d,e){var f=dd(a);return void 0===e?f?b in f?f[b]:f.document.documentElement[d]:a[d]:void(f?f.scrollTo(c?m(f).scrollLeft():e,c?e:m(f).scrollTop()):a[d]=e)},a,d,arguments.length,null)}}),m.each(["top","left"],function(a,b){m.cssHooks[b]=Lb(k.pixelPosition,function(a,c){return c?(c=Jb(a,b),Hb.test(c)?m(a).position()[b]+"px":c):void 0})}),m.each({Height:"height",Width:"width"},function(a,b){m.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){m.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return V(this,function(b,c,d){var e;return m.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?m.css(b,c,g):m.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),m.fn.size=function(){return this.length},m.fn.andSelf=m.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return m});var ed=a.jQuery,fd=a.$;return m.noConflict=function(b){return a.$===m&&(a.$=fd),b&&a.jQuery===m&&(a.jQuery=ed),m},typeof b===K&&(a.jQuery=a.$=m),m});
5 |
--------------------------------------------------------------------------------
/examples/js/one_string.js:
--------------------------------------------------------------------------------
1 | $(function () {
2 | var $address = $('[name="address"]'),
3 | $parent = $('[name="parent"]');
4 |
5 | $address.kladr({
6 | oneString: true,
7 | change: function (obj) {
8 | log(obj);
9 | }
10 | });
11 |
12 | $parent.change(function () {
13 | changeParent($(this).val());
14 | });
15 |
16 | changeParent($('[name="parent"]:checked').val());
17 |
18 | function changeParent(value) {
19 | var parentType = null,
20 | parentId = null;
21 |
22 | switch (value) {
23 | case 'moscow':
24 | parentType = $.kladr.type.region;
25 | parentId = '7700000000000';
26 | break;
27 |
28 | case 'petersburg':
29 | parentType = $.kladr.type.region;
30 | parentId = '7800000000000';
31 | break;
32 | }
33 |
34 | $address.kladr({
35 | parentType: parentType,
36 | parentId: parentId
37 | });
38 | }
39 |
40 | function log(obj) {
41 | var $log, i;
42 |
43 | $('.js-log li').hide();
44 |
45 | for (i in obj) {
46 | $log = $('#' + i);
47 |
48 | if ($log.length) {
49 | $log.find('.value').text(obj[i]);
50 | $log.show();
51 | }
52 | }
53 | }
54 | });
55 |
--------------------------------------------------------------------------------
/examples/js/simple.js:
--------------------------------------------------------------------------------
1 | $(function () {
2 | $('[name="city"]').kladr({
3 | type: $.kladr.type.city
4 | });
5 | });
--------------------------------------------------------------------------------
/examples/js/type_code.js:
--------------------------------------------------------------------------------
1 | $(function () {
2 | var $city = $('[name="city"]'),
3 | $typeCode = $('[name="typecode"]');
4 |
5 | $city.kladr({
6 | type: $.kladr.type.city
7 | });
8 |
9 | $typeCode.change(function () {
10 | changeTypeCode($(this).val());
11 | });
12 |
13 | changeTypeCode($('[name="typecode"]:checked').val());
14 |
15 | function changeTypeCode(value) {
16 | var typeCode = null;
17 |
18 | switch (value) {
19 | case 'city':
20 | typeCode = $.kladr.typeCode.city;
21 | break;
22 |
23 | case 'settlement':
24 | typeCode = $.kladr.typeCode.city + $.kladr.typeCode.settlement;
25 | break;
26 |
27 | case 'all':
28 | typeCode = $.kladr.typeCode.city + $.kladr.typeCode.settlement + $.kladr.typeCode.village;
29 | break;
30 | }
31 |
32 | $city.kladr('typeCode', typeCode);
33 | }
34 | });
--------------------------------------------------------------------------------
/examples/one_string.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Ввод адреса одной строкой
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
Ввод адреса одной строкой
17 |
18 |
19 |
20 |
28 |
29 |
30 |
Выбранный объект
31 |
32 | Код:
33 | Почтовый индекс:
34 | Название:
35 | Полное название:
36 | Подпись:
37 | Подпись коротко:
38 | Тип объекта:
39 | ОКАТО:
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/examples/simple.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Простой пример
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
Простой пример
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/examples/type_code.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Ограничение списка выводимых населённых пунктов
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
26 |
27 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp'),
2 | csso = require('gulp-csso'),
3 | uglify = require('gulp-uglify'),
4 | concat = require('gulp-concat'),
5 | rename = require('gulp-rename');
6 |
7 | gulp.task('default', function () {
8 |
9 | // Javascript
10 | gulp.src([
11 | './kladr/js/core.js',
12 | './kladr/js/kladr.js',
13 | './kladr/js/kladr_zip.js'
14 | ])
15 | .pipe(concat('jquery.kladr.js'))
16 | .pipe(uglify())
17 | .pipe(rename({suffix: '.min'}))
18 | .pipe(gulp.dest('./'));
19 |
20 | // CSS
21 | gulp.src('./kladr/css/style.css')
22 | .pipe(csso())
23 | .pipe(rename('jquery.kladr.min.css'))
24 | .pipe(gulp.dest('./'));
25 | });
--------------------------------------------------------------------------------
/images/spinner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garakh/kladrapi-jsclient/006d91520a0a131cd0c20e332f5f762cb57aebff/images/spinner.png
--------------------------------------------------------------------------------
/jquery.kladr.min.css:
--------------------------------------------------------------------------------
1 | .kladr-error{color:#cb3e27}#kladr_autocomplete ul{position:absolute;display:block;margin:0;padding:0;border:1px solid #c4c4c4;background-color:#fff;z-index:9999;overflow-x:hidden;overflow-y:auto;min-width:200px;max-height:420px;color:#313131}#kladr_autocomplete li{display:list-item;list-style-type:none;margin:0;padding:8px 10px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}#kladr_autocomplete li+li{border-top:1px solid #ededed}#kladr_autocomplete li:hover{background-color:#f2f2f2;cursor:pointer}#kladr_autocomplete li.active{background-color:#e9e9e9}#kladr_autocomplete a{text-decoration:none}#kladr_autocomplete strong{color:#038ebd}#kladr_autocomplete .spinner{position:absolute;display:block;margin:0;padding:0;width:16px;height:16px;background:url("./images/spinner.png") center center no-repeat;z-index:9999}
--------------------------------------------------------------------------------
/jquery.kladr.min.js:
--------------------------------------------------------------------------------
1 | !function(t,e){function n(t){var e={},n={type:"contentType",name:"query",withParents:"withParent"};t.parentType&&t.parentId&&(e[t.parentType+"Id"]=t.parentId);for(var a in t)r(t,a)&&t[a]&&(e[r(n,a)?n[a]:a]=t[a]);return e}function r(t,e){return t.hasOwnProperty(e)}function a(t){var n=e.console;n&&n.error&&n.error(t)}t.kladr={},function(){var n="https:"==e.location.protocol?"https:":"http:";t.kladr.url=n+"//kladr-api.ru/api.php"}(),t.kladr.type={region:"region",district:"district",city:"city",street:"street",building:"building"},t.kladr.typeCode={city:1,settlement:2,village:4},t.kladr.validate=function(e){var n=t.kladr.type;switch(e.type){case n.region:case n.district:case n.city:if(e.parentType&&!e.parentId)return a("parentId undefined"),!1;break;case n.street:if(e.parentType!=n.city&&e.parentType!=n.street)return a('parentType must equal "city" or "street"'),!1;if(!e.parentId)return a("parentId undefined"),!1;break;case n.building:if(!e.zip){if(!~t.inArray(e.parentType,[n.street,n.city]))return a('parentType must equal "street" or "city"'),!1;if(!e.parentId)return a("parentId undefined"),!1}break;default:if(!e.oneString)return a("type incorrect"),!1}return e.oneString&&e.parentType&&!e.parentId?(a("parentId undefined"),!1):e.typeCode&&e.type!=n.city?(a('type must equal "city"'),!1):e.limit<1?(a("limit must greater than 0"),!1):!0},t.kladr.api=function(e,r){if(!r)return void a("Callback undefined");if(!t.kladr.validate(e))return void r([]);var i=setTimeout(function(){r([]),i=null},3e3);t.ajax({url:t.kladr.url+"?callback=?",type:"get",data:n(e),dataType:"jsonp"}).done(function(t){i&&(r(t.result||[]),clearTimeout(i))})},t.kladr.check=function(e,n){return n?(e.withParents=!1,e.limit=1,void t.kladr.api(e,function(t){n(t&&t.length?t[0]:!1)})):void a("Callback undefined")}}(jQuery,window),function(t,e,n,r){function a(r,a){function i(t,e){return t.isGet?s.get(t.str[0]):(s.set(t),void e())}var s=function(){var e="kladr-data",n=r.data(e);return n||(n=t.extend({},u,d),r.data(e,n)),{set:function(t){if(t.obj)for(var a in t.obj)l(t.obj,a)&&l(u,a)&&(n[a]=t.obj[a]);else t.str&&!t.isGet&&l(u,t.str[0])&&(n[t.str[0]]=t.str[1]);r.data(e,n)},get:function(t){return l(u,t)||l(d,t)?n[t]:void 0},_set:function(t,a){n[t]=a,r.data(e,n)},_get:function(t){return l(n,t)?n[t]:void 0}}}();return i(a,function(){function a(a){var i=t(n.getElementById("kladr_autocomplete"));i.length||(i=t('
').appendTo(n.body));var l=x("guid");l?(V=i.find(".autocomplete"+l),A=i.find(".spinner"+l),t(e).off(L),r.off(L),V.off(L)):(l=o(),P("guid",l),r.attr("autocomplete","off"),V=t('
').appendTo(i),A=t('
').appendTo(i),m(),f(),b()),a()}function i(e,n){var r,a,i,o;V.empty();for(var l=0;l
'+i+""),o.data("kladr-object",r),t(" ").append(o).appendTo(V)}function d(){var e,n,r;V.empty(),e="",n=u.noResultText,null!=n&&""!=n&&(r=t(''+n+" "),r.data("kladr-object",{}),t(" ").append(r).appendTo(V))}function f(){var t=r.offset(),e=r.outerWidth(),n=r.outerHeight();if(t&&(f.top!=t.top||f.left!=t.left||f.width!=e||f.height!=n)){f.top=t.top,f.left=t.left,f.width=e,f.height=n,V.css({top:t.top+n+"px",left:t.left});var a=V.outerWidth()-V.width();V.width(e-a);var i=A.width(),o=A.height();A.css({top:t.top+(n-o)/2-1,left:t.left+e-i-2})}}function p(e){if(!(e.which>8&&e.which<46)){if(r.data(z,!1),!w("open_before"))return void v();_(null);var n=r.val();if(!t.trim(n))return B(!1),void v();var a=C(n);if(!w("send_before",a))return void v();T(),w("send"),x("source")(a,function(e){return w("receive",e),r.is(":focus")?t.trim(r.val())&&e.length?(G=!0,i(e,a),f(),I(),V.slideDown(50),void w("open")):(I(),_(null),d(),f(),V.slideDown(50),w("open"),void(G=!1)):(I(),void v())})}}function v(){w("close_before")&&(V.empty().hide(),w("close"))}function y(t){var e=V.find("li.active");switch(t.which){case c.up:e.length?(e.removeClass("active"),e.prev().length&&(e=e.prev())):e=V.find("li").last(),function(){var t=V.scrollTop(),n=V.offset(),r=e.outerHeight(),a=e.offset();a.top-n.top<0&&V.scrollTop(t-r)}(),e.addClass("active"),k();break;case c.down:e.length?(e.removeClass("active"),e.next().length&&(e=e.next())):e=V.find("li").first(),e.length&&!function(){var t=V.scrollTop(),n=V.height(),r=V.offset(),a=e.outerHeight(),i=e.offset();i.top-r.top+a>n&&V.scrollTop(t+a)}(),e.addClass("active"),k();break;case c.enter:v()}}function h(e){var n=t(e);n.is("a")&&(n=n.parents("li")),n.addClass("active"),k(),v()}function k(){if(w("select_before")){var t=V.find(".active a");t.length&&(r.val(t.attr("data-val")).data(z,!0),B(!1),_(t.data("kladr-object")),w("select",x("current")))}}function g(){function e(t,e){B(e),_(t)}if(x("verify")&&w("check_before")){var n=t.trim(r.val());if(!n)return void e(null,!1);if(x("current"))return void B(!1);var a=C(n);if(a.withParents=!1,a.limit=10,!w("send_before",a))return e(null,!1),void w("check",null);T(),w("send"),x("source")(a,function(n){function i(t,n){I(),e(t,n)}if(w("receive"),!t.trim(r.val()))return void i(null,!1);for(var o=a.name.toLowerCase(),l=null,u=null,d=0;d5||e()||setTimeout(a,100)}()}function w(e,n){if(!e)return!0;var a=e.replace(/_([a-z])/gi,function(t,e){return e.toUpperCase()});return r.trigger("kladr_"+e,n),"function"===t.type(x(a))?x(a).call(r.get(0),n)!==!1:!0}function T(){x("spinner")&&x("showSpinner")(A)}function I(){x("spinner")&&x("hideSpinner")(A)}function C(t){var e,n={},r=["token","key","type","typeCode","parentType","parentId","oneString","withParents","limit","strict"];for(e=0;e",u+=a.substr(l,o.length),u+="",u+=a.substr(l+o.length)):u+=""+a+" ",u},valueFormat:function(e,n){var r;return n.oneString?e.parents?(r=[].concat(e.parents),r.push(e),t.kladr.buildAddress(r)):(e.typeShort?e.typeShort+". ":"")+e.name:e.name},showSpinner:function(t){var e=-.2,n=setInterval(function(){return t.is(":visible")?(t.css("background-position","0% "+e+"%"),e+=5.555556,void(e>95&&(e=-.2))):(clearInterval(n),void(n=null))},30);t.show()},hideSpinner:function(t){t.hide()}},d={current:null,controller:null},c={up:38,down:40,enter:13};t.kladr=t.extend(t.kladr,{setDefault:function(t,e){var n=i(t,e);if(n.obj)for(var r in n.obj)l(u,r)&&(u[r]=n.obj[r]);else n.str&&!n.isGet&&l(u,n.str[0])&&(u[n.str[0]]=n.str[1])},getDefault:function(t){return l(u,t)?u[t]:void 0},getInputs:function(e){var r=t(e||n.body),a="[data-kladr-type]";return r.filter(a).add(r.find(a))},setValues:function(e,n){var r,a,i="kladr_change.setvalues",o=t.kladr.type,u={},d=[];if(~t.inArray(t.type(e),["object","array"])){t.each(e,function(t,e){if(e){var n=e.contentType||e.type||t;l(o,n)&&(u[n]=e)}});for(a in o)l(o,a)&&u[a]&&(d[a]=u[a]);r=t.kladr.getInputs(n),function c(){var t,e,n;for(e in d)if(l(d,e)){n=d[e],delete d[e];break}if(e)return t=r.filter('[data-kladr-type="'+e+'"]'),t.length?void t.on(i,function(){t.off(i),c()}).kladr("controller").setValue(n):void c()}()}},getAddress:function(e,n){var r,a=t.kladr.getInputs(e),i=t.kladr.type,o={},u={};a.each(function(){var e,n,r,a=t(this);if(a.attr("data-kladr-id"))if(e=a.kladr("current"),a.attr("data-kladr-one-string")&&e.parents)for(n=[].concat(e.parents),n.push(e),r=0;r=35&&40>=n||n>=96&&105>=n;return t(this).val().length>=6?r:r||n>=48&&57>=n}),this.keyup(function(){function n(t){t?r.addClass("kladr-error"):r.removeClass("kladr-error")}var r=t(this),a=r.val();return a?void t.kladr.api({type:t.kladr.type.building,zip:a,withParents:!0,limit:1},function(r){var a=r.length&&r[0];r=[],a?(n(!1),a.parents&&(r=r.concat(a.parents)),r.push(a),t.kladr.setValues(r,e)):n(!0)}):void n(!1)}),this}}(jQuery);
--------------------------------------------------------------------------------
/kladr/css/style.css:
--------------------------------------------------------------------------------
1 | .kladr-error {
2 | color: #cb3e27;
3 | }
4 |
5 | #kladr_autocomplete ul {
6 | position: absolute;
7 | display: block;
8 | margin: 0;
9 | padding: 0;
10 | border: 1px solid #c4c4c4;
11 | background-color: white;
12 | z-index: 9999;
13 | overflow-x: hidden;
14 | overflow-y: auto;
15 | min-width: 200px;
16 | max-height: 420px;
17 | color: #313131;
18 | }
19 |
20 | #kladr_autocomplete li {
21 | display: list-item;
22 | list-style-type: none;
23 | margin: 0;
24 | padding: 8px 10px;
25 | overflow: hidden;
26 | white-space: nowrap;
27 | text-overflow: ellipsis;
28 | }
29 |
30 | #kladr_autocomplete li + li {
31 | border-top: 1px solid #ededed;
32 | }
33 |
34 | #kladr_autocomplete li:hover {
35 | background-color: #f2f2f2;
36 | cursor: pointer;
37 | }
38 |
39 | #kladr_autocomplete li.active {
40 | background-color: #e9e9e9;
41 | }
42 |
43 | #kladr_autocomplete a {
44 | text-decoration: none;
45 | }
46 |
47 | #kladr_autocomplete strong {
48 | color: #038ebd;
49 | }
50 |
51 | #kladr_autocomplete .spinner {
52 | position: absolute;
53 | display: block;
54 | margin: 0;
55 | padding: 0;
56 | width: 16px;
57 | height: 16px;
58 | background: transparent url("./images/spinner.png") center center no-repeat;
59 | z-index: 9999;
60 | }
--------------------------------------------------------------------------------
/kladr/js/README.md:
--------------------------------------------------------------------------------
1 | jQuery Kladr
2 | ================================================================================
3 |
4 | Исходный код плагина
5 |
6 | * **core.js** - Реализует $.kladr.
7 | * **kladr.js** - Реализует плагин $('input').kladr.
8 | * **kladr_zip.js** - Реализует плагин $('input').kladrZip.
--------------------------------------------------------------------------------
/kladr/js/core.js:
--------------------------------------------------------------------------------
1 | (function ($, window) {
2 | $.kladr = {};
3 |
4 | (function () {
5 | var protocol = window.location.protocol == 'https:' ? 'https:' : 'http:';
6 |
7 | /**
8 | * URL сервиса
9 | *
10 | * @type {string}
11 | */
12 | $.kladr.url = protocol + '//kladr-api.ru/api.php';
13 | })();
14 |
15 | /**
16 | * Перечисление типов объектов
17 | *
18 | * @type {{region: string, district: string, city: string, street: string, building: string}}
19 | */
20 | $.kladr.type = {
21 | region: 'region', // Область
22 | district: 'district', // Район
23 | city: 'city', // Город
24 | street: 'street', // Улица
25 | building: 'building' // Строение
26 | };
27 |
28 | /**
29 | * Перечисление типов населённых пунктов
30 | *
31 | * @type {{city: number, settlement: number, village: number}}
32 | */
33 | $.kladr.typeCode = {
34 | city: 1, // Город
35 | settlement: 2, // Посёлок
36 | village: 4 // Деревня
37 | };
38 |
39 | /**
40 | * Проверяет корректность запроса
41 | *
42 | * @param {{}} query Запрос
43 | * @returns {boolean}
44 | */
45 | $.kladr.validate = function (query) {
46 | var type = $.kladr.type;
47 |
48 | switch (query.type) {
49 | case type.region:
50 | case type.district:
51 | case type.city:
52 | if (query.parentType && !query.parentId) {
53 | error('parentId undefined');
54 | return false;
55 | }
56 | break;
57 | case type.street:
58 |
59 | if (query.parentType != type.city && query.parentType != type.street) {
60 | error('parentType must equal "city" or "street"');
61 | return false;
62 | }
63 | if (!query.parentId) {
64 | error('parentId undefined');
65 | return false;
66 | }
67 | break;
68 | case type.building:
69 | if (!query.zip) {
70 | if (!~$.inArray(query.parentType, [type.street, type.city])) {
71 | error('parentType must equal "street" or "city"');
72 | return false;
73 | }
74 | if (!query.parentId) {
75 | error('parentId undefined');
76 | return false;
77 | }
78 | }
79 | break;
80 | default:
81 | if (!query.oneString) {
82 | error('type incorrect');
83 | return false;
84 | }
85 | break;
86 | }
87 |
88 | if (query.oneString && query.parentType && !query.parentId) {
89 | error('parentId undefined');
90 | return false;
91 | }
92 |
93 | if (query.typeCode && (query.type != type.city)) {
94 | error('type must equal "city"');
95 | return false;
96 | }
97 |
98 | if (query.limit < 1) {
99 | error('limit must greater than 0');
100 | return false;
101 | }
102 |
103 | return true;
104 | };
105 |
106 | /**
107 | * Отправляет запрос к сервису
108 | *
109 | * @param {{}} query Запрос
110 | * @param {Function} callback Функция, которой будет передан массив полученных объектов
111 | */
112 | $.kladr.api = function (query, callback) {
113 | if (!callback) {
114 | error('Callback undefined');
115 | return;
116 | }
117 |
118 | if (!$.kladr.validate(query)) {
119 | callback([]);
120 | return;
121 | }
122 |
123 | var timeout = setTimeout(function () {
124 | callback([]);
125 | timeout = null;
126 | }, 3000);
127 |
128 | $.ajax({
129 | url: $.kladr.url + '?callback=?',
130 | type: 'get',
131 | data: toApiFormat(query),
132 | dataType: 'jsonp'
133 | }).done(function (data) {
134 | if (timeout) {
135 | callback(data.result || []);
136 | clearTimeout(timeout);
137 | }
138 | });
139 | };
140 |
141 | /**
142 | * Проверяет существование объекта, соответствующего запросу
143 | *
144 | * @param {{}} query Запрос
145 | * @param {Function} callback Функция, которой будет передан объект, если он существует, или
146 | * false если не существует.
147 | */
148 | $.kladr.check = function (query, callback) {
149 | if (!callback) {
150 | error('Callback undefined');
151 | return;
152 | }
153 |
154 | query.withParents = false;
155 | query.limit = 1;
156 |
157 | $.kladr.api(query, function (objs) {
158 | objs && objs.length
159 | ? callback(objs[0])
160 | : callback(false);
161 | });
162 | };
163 |
164 | /**
165 | * Преобразует запрос из формата принятого в плагине в формат сервиса
166 | *
167 | * @param {{}} query Запрос в формате плагина
168 | * @returns {{}} Запрос в формате сервиса
169 | */
170 | function toApiFormat(query) {
171 | var params = {},
172 | fields = {
173 | type: 'contentType',
174 | name: 'query',
175 | withParents: 'withParent'
176 | };
177 |
178 | if (query.parentType && query.parentId) {
179 | params[query.parentType + 'Id'] = query.parentId;
180 | }
181 |
182 | for (var key in query) {
183 | if (hasOwn(query, key) && query[key]) {
184 | params[hasOwn(fields, key) ? fields[key] : key] = query[key];
185 | }
186 | }
187 |
188 | return params;
189 | }
190 |
191 | /**
192 | * Проверяет принадлежит ли объекту свойство
193 | *
194 | * @param {{}} obj Объект
195 | * @param {string} property Свойство
196 | * @returns {boolean}
197 | */
198 | function hasOwn(obj, property) {
199 | return obj.hasOwnProperty(property);
200 | }
201 |
202 | /**
203 | * Выводит ошибку на консоль
204 | *
205 | * @param {string} error Текст ошибки
206 | */
207 | function error(error) {
208 | var console = window.console;
209 |
210 | console && console.error && console.error(error);
211 | }
212 | })(jQuery, window);
--------------------------------------------------------------------------------
/kladr/js/kladr.js:
--------------------------------------------------------------------------------
1 | (function ($, window, document, undefined) {
2 |
3 | /**
4 | * Список значений параметров плагина по умолчанию
5 | *
6 | * @type {{token: null, key: null, type: null, typeCode: null, parentType: null, parentId: null, limit: number, oneString: boolean, withParents: boolean, parentInput: null, verify: boolean, spinner: boolean, open: null, close: null, send: null, receive: null, select: null, check: null, change: null, openBefore: null, closeBefore: null, sendBefore: null, selectBefore: null, checkBefore: null, source: Function, labelFormat: Function, valueFormat: Function, showSpinner: Function, hideSpinner: Function}}
7 | */
8 | var defaultOptions = {
9 |
10 | token: null, // Токен для доступа к сервису
11 | key: null, // Ключ для доступа к сервису
12 | type: null, // Тип подставляемых объектов
13 | typeCode: null, // Тип подставляемых населённых пунктов
14 | parentType: null, // Тип родительского объекта
15 | parentId: null, // Идентификатор родительского объекта
16 | limit: 10, // Количество отображаемых в выпадающем списке объектов
17 | oneString: false, // Включить ввод адреса одной строкой
18 | withParents: false, // Получить объекты вместе с родительскими
19 | noResultText: null, // Текст для показа в выпадающем списке в случае отсутствия результатов поиска
20 | checkEmptyRespone: false, // Текст для показа в выпадающем списке в случае отсутствия результатов поиска
21 | strict: null, // Включает строгий режим поиска для городов. Если не указан district, то поиск по домам у которых нет district
22 |
23 | parentInput: null, // Селектор для поиска родительских полей ввода
24 | verify: false, // Проверять введённые данные
25 | spinner: true, // Отображать ajax-крутилку
26 |
27 | open: null, // Открыт выпадающий список объектов
28 | close: null, // Закрыт выпадающий список объектов
29 | send: null, // Отправлен запрос к сервису
30 | receive: null, // Получен ответ от сервиса
31 | select: null, // Выбран объект в выпадающем списке
32 | check: null, // Проверен введённый пользователем объект
33 | change: null, // Изменился объект в поле ввода
34 |
35 | openBefore: null, // Вызывается перед открытием выпадающего списка
36 | closeBefore: null, // Вызывается перед закрытием выпадающего списка
37 | sendBefore: null, // Вызывается перед отправкой запроса сервису
38 | selectBefore: null, // Вызывается перед выбором объекта в списке
39 | checkBefore: null, // Вызывается перед проверкой введённого пользователем объекта
40 |
41 | /**
42 | * Отправляет запрос сервису
43 | *
44 | * @param {{}} query
45 | * @param {Function} callback
46 | */
47 | source: function (query, callback) {
48 | $.kladr.api(query, callback);
49 | },
50 |
51 | /**
52 | * Форматирует подписи для объектов в списке
53 | *
54 | * @param {{}} obj Объект КЛАДР
55 | * @param {{}} query Запрос, по которому получен объект
56 | * @returns {string}
57 | */
58 | labelFormat: function (obj, query) {
59 | var objs;
60 |
61 | if (query.oneString) {
62 | if (obj.parents) {
63 | objs = [].concat(obj.parents);
64 | objs.push(obj);
65 |
66 | return $.kladr.buildAddress(objs);
67 | }
68 |
69 | return (obj.typeShort ? obj.typeShort + '. ' : '') + obj.name;
70 | }
71 |
72 | var label = '',
73 | name,
74 | objName,
75 | queryName,
76 | start;
77 |
78 | if (obj.typeShort) {
79 | label += obj.typeShort + '. ';
80 | }
81 |
82 | name = obj.name;
83 | objName = name.toLowerCase();
84 | queryName = query.name.toLowerCase();
85 | start = objName.indexOf(queryName);
86 | start = ~start ? start : 0;
87 |
88 | if (queryName.length < objName.length) {
89 | label += name.substr(0, start);
90 | label += '';
91 | label += name.substr(start, queryName.length);
92 | label += ' ';
93 | label += name.substr(start + queryName.length);
94 | } else {
95 | label += '' + name + ' ';
96 | }
97 |
98 | return label;
99 | },
100 |
101 | /**
102 | * Форматирует подставляемое в поле ввода значение
103 | *
104 | * @param {{}} obj Объект КЛАДР
105 | * @param {{}} query Запрос, по которому получен объект
106 | * @returns {string}
107 | */
108 | valueFormat: function (obj, query) {
109 | var objs;
110 |
111 | if (query.oneString) {
112 | if (obj.parents) {
113 | objs = [].concat(obj.parents);
114 | objs.push(obj);
115 |
116 | return $.kladr.buildAddress(objs);
117 | }
118 |
119 | return (obj.typeShort ? obj.typeShort + '. ' : '') + obj.name;
120 | }
121 |
122 | return obj.name;
123 | },
124 |
125 | /**
126 | * Выводит ajax-крутилку
127 | *
128 | * @param {{}} $spinner jQuery объект ajax-крутилки
129 | */
130 | showSpinner: function ($spinner) {
131 | var top = -0.2,
132 | spinnerInterval = setInterval(function () {
133 | if (!$spinner.is(':visible')) {
134 | clearInterval(spinnerInterval);
135 | spinnerInterval = null;
136 | return;
137 | }
138 |
139 | $spinner.css('background-position', '0% ' + top + '%');
140 |
141 | top += 5.555556;
142 |
143 | if (top > 95) {
144 | top = -0.2;
145 | }
146 | }, 30);
147 |
148 | $spinner.show();
149 | },
150 |
151 | /**
152 | * Скрывает ajax-крутилку
153 | *
154 | * @param {{}} $spinner jQuery объект ajax-крутилки
155 | */
156 | hideSpinner: function ($spinner) {
157 | $spinner.hide();
158 | }
159 | };
160 |
161 | /**
162 | * Параметры только для чтения
163 | *
164 | * @type {{current: null, controller: null}}
165 | */
166 | var readOnlyParams = {
167 | current: null, // Текущий, выбранный объект КЛАДР
168 | controller: null // Контроллер для управления плагином
169 | };
170 |
171 | /**
172 | * Коды отслеживаемых плагином клавиш
173 | *
174 | * @type {{up: number, down: number, enter: number}}
175 | */
176 | var keys = {
177 | up: 38,
178 | down: 40,
179 | enter: 13
180 | };
181 |
182 | $.kladr = $.extend($.kladr, {
183 |
184 | /**
185 | * Устанавливает значения по умолчанию для параметров плагина
186 | *
187 | * @param {{}|string} param1
188 | * @param {*} param2
189 | */
190 | setDefault: function (param1, param2) {
191 | var params = readParams(param1, param2);
192 |
193 | if (params.obj) {
194 | for (var i in params.obj) {
195 | if (hasOwn(defaultOptions, i)) {
196 | defaultOptions[i] = params.obj[i];
197 | }
198 | }
199 | }
200 | else if (params.str && !params.isGet && hasOwn(defaultOptions, params.str[0])) {
201 | defaultOptions[params.str[0]] = params.str[1];
202 | }
203 | },
204 |
205 | /**
206 | * Возвращает значение по умолчанию для параметра плагина
207 | *
208 | * @param {string} param
209 | * @returns {*}
210 | */
211 | getDefault: function (param) {
212 | if (hasOwn(defaultOptions, param)) {
213 | return defaultOptions[param];
214 | }
215 | },
216 |
217 | /**
218 | * Возращает jQuery коллекцию полей ввода,
219 | * к которым был подключен плагин
220 | *
221 | * @param {*} selector Селектор, DOM элемент или jQuery объект, ограничивающий поиск полей ввода
222 | * @returns {{}} jQuery коллекция полей ввода
223 | */
224 | getInputs: function (selector) {
225 | var $source = $(selector || document.body),
226 | inputSelector = '[data-kladr-type]';
227 |
228 | return $source
229 | .filter(inputSelector)
230 | .add($source.find(inputSelector));
231 | },
232 |
233 | /**
234 | * Устанавливает значения для полей, к которым
235 | * был подключен плагин
236 | *
237 | * @param {{}|[]} values Список значений
238 | * @param {*} selector Селектор, ограничивающий поиск полей ввода
239 | */
240 | setValues: function (values, selector) {
241 | var changeEvent = 'kladr_change.setvalues',
242 | types = $.kladr.type,
243 | filtered = {},
244 | sorted = {},
245 | $inputs, t;
246 |
247 | if (!~$.inArray($.type(values), ['object', 'array'])) {
248 | return;
249 | }
250 |
251 | $.each(values, function (key, value) {
252 | if (!value) {
253 | return;
254 | }
255 |
256 | var type = value.contentType || value.type || key;
257 |
258 | if (hasOwn(types, type)) {
259 | filtered[type] = value;
260 | }
261 | });
262 |
263 | for (t in types) {
264 | if (hasOwn(types, t) && filtered[t]) {
265 | sorted[t] = filtered[t];
266 | }
267 | }
268 |
269 | $inputs = $.kladr.getInputs(selector);
270 |
271 | (function set() {
272 | var $input, type, value;
273 |
274 | for (type in sorted) {
275 | if (hasOwn(sorted, type)) {
276 | value = sorted[type];
277 | delete sorted[type];
278 | break;
279 | }
280 | }
281 |
282 | if (!type) {
283 | return;
284 | }
285 |
286 | $input = $inputs.filter('[data-kladr-type="' + type + '"]');
287 |
288 | if (!$input.length) {
289 | set();
290 | return;
291 | }
292 |
293 | $input
294 | .on(changeEvent, function () {
295 | $input.off(changeEvent);
296 | set();
297 | })
298 | .kladr('controller')
299 | .setValue(value);
300 | })();
301 | },
302 |
303 | /**
304 | * Возвращает собранную на основании полей ввода строку адреса
305 | *
306 | * @param {*} selector Селектор, ограничивающий поиск полей ввода
307 | * @param {Function} build Функция, строящая строку адреса из списка объектов КЛАДР
308 | * @returns {string}
309 | */
310 | getAddress: function (selector, build) {
311 | var $inputs = $.kladr.getInputs(selector),
312 | types = $.kladr.type,
313 | filtered = {},
314 | sorted = {},
315 | t;
316 |
317 | $inputs.each(function () {
318 | var $this = $(this),
319 | obj, objs, i;
320 |
321 | if ($this.attr('data-kladr-id')) {
322 | obj = $this.kladr('current');
323 |
324 | if ($this.attr('data-kladr-one-string') && obj.parents) {
325 | objs = [].concat(obj.parents);
326 | objs.push(obj);
327 |
328 | for (i = 0; i < objs.length; i++) {
329 | filtered[objs[i].contentType] = objs[i];
330 | }
331 | }
332 | else {
333 | filtered[$this.attr('data-kladr-type')] = obj;
334 | }
335 | }
336 | else {
337 | filtered[$this.attr('data-kladr-type')] = $this.val();
338 | }
339 | });
340 |
341 | for (t in types) {
342 | if (hasOwn(types, t) && filtered[t]) {
343 | sorted[t] = filtered[t];
344 | }
345 | }
346 |
347 | return (build || $.kladr.buildAddress)(sorted);
348 | },
349 |
350 | /**
351 | * Строит строку адреса на основании списка объектов КЛАДР
352 | *
353 | * @param {{}|[]} objs Список объектов КЛАДР
354 | * @returns {string}
355 | */
356 | buildAddress: function (objs) {
357 | var lastIds = [],
358 | address = '',
359 | zip = '';
360 |
361 | $.each(objs, function (i, obj) {
362 | var name = '',
363 | type = '',
364 | j;
365 |
366 | if ($.type(obj) === 'object') {
367 | for (j = 0; j < lastIds.length; j++) {
368 | if (lastIds[j] == obj.id) {
369 | return;
370 | }
371 | }
372 |
373 | lastIds.push(obj.id);
374 |
375 | name = obj.name;
376 | type = obj.typeShort + '. ';
377 | zip = obj.zip || zip;
378 | }
379 | else {
380 | name = obj;
381 | }
382 |
383 | if (address) address += ', ';
384 | address += type + name;
385 | });
386 |
387 | address = (zip ? zip + ', ' : '') + address;
388 |
389 | return address;
390 | }
391 | });
392 |
393 | $.fn.kladr = function (param1, param2) {
394 | var params = readParams(param1, param2),
395 | result = null;
396 |
397 | this.each(function () {
398 | var res = kladr($(this), params);
399 |
400 | if (params.isGet) {
401 | result = res;
402 | return false;
403 | }
404 | });
405 |
406 | if (params.isGet) {
407 | return result;
408 | }
409 |
410 | return this;
411 | };
412 |
413 | /**
414 | * Подключает плагин к полю ввода
415 | *
416 | * @param {{}} $input jQuery объект поля ввода
417 | * @param {{}} params Объект параметров
418 | * @returns {*}
419 | */
420 | function kladr($input, params) {
421 |
422 | /**
423 | * Хранилище параметров плагина
424 | *
425 | * @type {{}}
426 | */
427 | var options = (function () {
428 | var dataKey = 'kladr-data',
429 | data = $input.data(dataKey);
430 |
431 | if (!data) {
432 | data = $.extend({}, defaultOptions, readOnlyParams);
433 | $input.data(dataKey, data);
434 | }
435 |
436 | return {
437 |
438 | /**
439 | * Устанавливает значение публичному параметру плагина
440 | *
441 | * @param {{}} params
442 | */
443 | set: function (params) {
444 | if (params.obj) {
445 | for (var i in params.obj) {
446 | if (hasOwn(params.obj, i) && hasOwn(defaultOptions, i)) {
447 | data[i] = params.obj[i];
448 | }
449 | }
450 | }
451 | else if (params.str && !params.isGet && hasOwn(defaultOptions, params.str[0])) {
452 | data[params.str[0]] = params.str[1];
453 | }
454 |
455 | $input.data(dataKey, data);
456 | },
457 |
458 | /**
459 | * Возвращает значение публичного параметра плагина
460 | *
461 | * @param {string} param
462 | * @returns {*}
463 | */
464 | get: function (param) {
465 | if (hasOwn(defaultOptions, param) || hasOwn(readOnlyParams, param)) {
466 | return data[param];
467 | }
468 | },
469 |
470 | /**
471 | * Устанавливает значение параметра плагина
472 | *
473 | * @param {string} param
474 | * @param {*} value
475 | * @private
476 | */
477 | _set: function (param, value) {
478 | data[param] = value;
479 | $input.data(dataKey, data);
480 | },
481 |
482 | /**
483 | * Возвращает значение параметра плагина
484 | *
485 | * @param {string} param
486 | * @returns {*}
487 | * @private
488 | */
489 | _get: function (param) {
490 | if (hasOwn(data, param)) {
491 | return data[param];
492 | }
493 | }
494 | };
495 | })();
496 |
497 | /**
498 | * Инициализирует плагин
499 | *
500 | * @param {{}} params Объект параметров
501 | * @param {Function} callback Функция, выполняемая после инициализации
502 | * @returns {*}
503 | */
504 | function init(params, callback) {
505 | if (params.isGet) {
506 | return options.get(params.str[0]);
507 | }
508 |
509 | options.set(params);
510 | callback();
511 | }
512 |
513 | return init(params, function () {
514 | var $ac = null, // jQuery объект выпадающего списка
515 | $spinner = null, // jQuery объект ajax-крутилки
516 | successSearch = false, // Состояние последнего запроса
517 | eventNamespace = '.kladr', // Пространство имён событий, на которые подписывается плагин
518 | triggerChangeFlag = 'kladrInputChange'; // Флаг, включающий эмуляцию события change для поля ввода
519 |
520 | create(function () {
521 | var isActive = false, // Поле ввода активно
522 | canCheck = true, // Поле ввода можно проверить
523 | lastChangeVal = ''; // Значение поля ввода при последнем событии change
524 |
525 | $input
526 | .attr('data-kladr-type', get('type') || '')
527 | .attr('data-kladr-one-string', get('oneString') || null)
528 | .on('keyup' + eventNamespace, open)
529 | .on('keydown' + eventNamespace, keySelect)
530 | .on('blur' + eventNamespace, function () {
531 | if (!isActive && $input.data(triggerChangeFlag) && (lastChangeVal != $input.val())) {
532 | $input.change();
533 | }
534 | })
535 | .on('blur' + eventNamespace + ' change' + eventNamespace, function (event) {
536 | if (isActive) return;
537 |
538 | if (event.type == 'change') {
539 | lastChangeVal = $input.val();
540 | }
541 |
542 | if (canCheck) {
543 | canCheck = false;
544 | check();
545 | }
546 | if(!successSearch && defaultOptions.checkEmptyRespone) { // Если запрос был неуспешных, т.е. не вернулись данные с сервера, то очищаем input c вводом
547 | $input.val('');
548 | }
549 | close();
550 | return false;
551 | })
552 | .on('focus' + eventNamespace, function () {
553 | canCheck = true;
554 | });
555 |
556 | $ac
557 | .on('touchstart' + eventNamespace + ' mousedown' + eventNamespace, 'li, a', function (event) {
558 | event.preventDefault();
559 |
560 | isActive = true;
561 | mouseSelect(this);
562 | isActive = false;
563 | });
564 |
565 | $(window)
566 | .on('resize' + eventNamespace, position);
567 | });
568 |
569 | /**
570 | * Создаёт DOM элементы плагина
571 | *
572 | * @param {Function} callback
573 | */
574 | function create(callback) {
575 | var $container = $(document.getElementById('kladr_autocomplete'));
576 |
577 | if (!$container.length) {
578 | $container = $('
').appendTo(document.body);
579 | }
580 |
581 | var guid = get('guid');
582 |
583 | if (guid) {
584 | $ac = $container.find('.autocomplete' + guid);
585 | $spinner = $container.find('.spinner' + guid);
586 |
587 | $(window).off(eventNamespace);
588 | $input.off(eventNamespace);
589 | $ac.off(eventNamespace);
590 | }
591 | else {
592 | guid = getGuid();
593 | set('guid', guid);
594 |
595 | $input.attr('autocomplete', 'off');
596 |
597 | $ac = $('')
598 | .appendTo($container);
599 |
600 | $spinner = $('
')
601 | .appendTo($container);
602 |
603 | createController();
604 |
605 | position();
606 | checkAutoFill();
607 | }
608 |
609 | callback();
610 | }
611 |
612 | /**
613 | * Заполняет выпадающий список
614 | *
615 | * @param {[]} objs Массив объектов КЛАДР
616 | * @param {{}} query Объект запроса к сервису
617 | */
618 | function render(objs, query) {
619 | var obj, value, label, $a;
620 |
621 | $ac.empty();
622 |
623 | for (var i = 0; i < objs.length; i++) {
624 | obj = objs[i];
625 | value = get('valueFormat')(obj, query);
626 | label = get('labelFormat')(obj, query);
627 |
628 | $a = $('' + label + ' ');
629 | $a.data('kladr-object', obj);
630 |
631 | $(' ')
632 | .append($a)
633 | .appendTo($ac);
634 | }
635 | }
636 |
637 | /**
638 | * Заполняет выпадающий список сообщением о пустом запросе
639 | *
640 | */
641 | function renderEmpty() {
642 | var obj, value, label, $a;
643 | $ac.empty();
644 | value = '';
645 | label = defaultOptions.noResultText;
646 | if(label == null || label == '')
647 | return;
648 | $a = $('' + label + ' ');
649 | $a.data('kladr-object', {});
650 |
651 | $(' ')
652 | .append($a)
653 | .appendTo($ac);
654 | }
655 |
656 |
657 |
658 | /**
659 | * Позиционирует выпадающий список на странице
660 | */
661 | function position() {
662 | var inputOffset = $input.offset(),
663 | inputWidth = $input.outerWidth(),
664 | inputHeight = $input.outerHeight();
665 |
666 | if (!inputOffset) {
667 | return;
668 | }
669 |
670 | if ((position.top == inputOffset.top)
671 | && (position.left == inputOffset.left)
672 | && (position.width == inputWidth)
673 | && (position.height == inputHeight)) {
674 | return;
675 | }
676 |
677 | position.top = inputOffset.top;
678 | position.left = inputOffset.left;
679 | position.width = inputWidth;
680 | position.height = inputHeight;
681 |
682 | $ac.css({
683 | top: inputOffset.top + inputHeight + 'px',
684 | left: inputOffset.left
685 | });
686 |
687 | var differ = $ac.outerWidth() - $ac.width();
688 | $ac.width(inputWidth - differ);
689 |
690 | var spinnerWidth = $spinner.width(),
691 | spinnerHeight = $spinner.height();
692 |
693 | $spinner.css({
694 | top: inputOffset.top + (inputHeight - spinnerHeight) / 2 - 1,
695 | left: inputOffset.left + inputWidth - spinnerWidth - 2
696 | });
697 | }
698 |
699 | /**
700 | * Открывает выпадающий список
701 | *
702 | * @param {{}} event jQuery объект события
703 | */
704 | function open(event) {
705 | // return on control keys
706 | if ((event.which > 8) && (event.which < 46)) {
707 | return;
708 | }
709 |
710 | $input.data(triggerChangeFlag, false);
711 |
712 | if (!trigger('open_before')) {
713 | close();
714 | return;
715 | }
716 |
717 | setCurrent(null);
718 |
719 | var name = $input.val();
720 |
721 | if (!$.trim(name)) {
722 | error(false);
723 | close();
724 | return;
725 | }
726 |
727 | var query = getQuery(name);
728 |
729 | if (!trigger('send_before', query)) {
730 | close();
731 | return;
732 | }
733 |
734 | showSpinner();
735 | trigger('send');
736 |
737 | get('source')(query, function (objs) {
738 | trigger('receive', objs);
739 |
740 | if (!$input.is(':focus')) {
741 | hideSpinner();
742 | close();
743 | return;
744 | }
745 |
746 | if (!$.trim($input.val()) || !objs.length) {
747 | hideSpinner();
748 | setCurrent(null);
749 | renderEmpty();
750 | position();
751 | $ac.slideDown(50);
752 | trigger('open');
753 | successSearch = false;
754 | return;
755 | }
756 | successSearch = true;
757 | render(objs, query);
758 | position();
759 | hideSpinner();
760 |
761 | $ac.slideDown(50);
762 | trigger('open');
763 | });
764 | }
765 |
766 | /**
767 | * Закрывает выпадающий список
768 | */
769 | function close() {
770 | if (!trigger('close_before')) {
771 | return;
772 | }
773 |
774 | $ac.empty().hide();
775 | trigger('close');
776 | }
777 |
778 | /**
779 | * Осуществляет активацию и выбор элемента выпадающего списка с клавиатуры
780 | *
781 | * @param {{}} event jQuery объект события
782 | */
783 | function keySelect(event) {
784 | var $active = $ac.find('li.active');
785 |
786 | switch (event.which) {
787 | case keys.up:
788 | if ($active.length) {
789 | $active.removeClass('active');
790 | if ($active.prev().length) $active = $active.prev();
791 | } else {
792 | $active = $ac.find('li').last();
793 | }
794 |
795 | (function () {
796 | var acScroll = $ac.scrollTop(),
797 | acOffset = $ac.offset(),
798 | activeHeight = $active.outerHeight(),
799 | activeOffset = $active.offset();
800 |
801 | if ((activeOffset.top - acOffset.top) < 0) {
802 | $ac.scrollTop(acScroll - activeHeight);
803 | }
804 | })();
805 |
806 | $active.addClass('active');
807 | select();
808 | break;
809 |
810 | case keys.down:
811 | if ($active.length) {
812 | $active.removeClass('active');
813 | if ($active.next().length) $active = $active.next();
814 | } else {
815 | $active = $ac.find('li').first();
816 | }
817 |
818 | if($active.length){
819 | (function () {
820 | var acScroll = $ac.scrollTop(),
821 | acHeight = $ac.height(),
822 | acOffset = $ac.offset(),
823 | activeHeight = $active.outerHeight(),
824 | activeOffset = $active.offset();
825 |
826 | if ((activeOffset.top - acOffset.top + activeHeight) > acHeight) {
827 | $ac.scrollTop(acScroll + activeHeight);
828 | }
829 | })();
830 | }
831 |
832 | $active.addClass('active');
833 | select();
834 | break;
835 |
836 | case keys.enter:
837 | close();
838 | break;
839 | }
840 | }
841 |
842 | /**
843 | * Осуществляет активацию и выбор элемента выпадающего списка мышью
844 | *
845 | * @param {{}} element DOM элемент, который необходимо выбрать
846 | */
847 | function mouseSelect(element) {
848 | var $li = $(element);
849 |
850 | if ($li.is('a')) {
851 | $li = $li.parents('li');
852 | }
853 |
854 | $li.addClass('active');
855 |
856 | select();
857 | close();
858 | }
859 |
860 | /**
861 | * Осуществляет выбор активного элемента выпадающего списка
862 | */
863 | function select() {
864 | if (!trigger('select_before')) {
865 | return;
866 | }
867 |
868 | var $a = $ac.find('.active a');
869 | if (!$a.length) {
870 | return;
871 | }
872 |
873 | $input
874 | .val($a.attr('data-val'))
875 | .data(triggerChangeFlag, true);
876 |
877 | error(false);
878 | setCurrent($a.data('kladr-object'));
879 | trigger('select', get('current'));
880 | }
881 |
882 | /**
883 | * Проверяет введённое пользователем значение в поле ввода
884 | */
885 | function check() {
886 | if (!get('verify')) {
887 | return;
888 | }
889 |
890 | if (!trigger('check_before')) {
891 | return;
892 | }
893 |
894 | var name = $.trim($input.val());
895 |
896 | if (!name) {
897 | ret(null, false);
898 | return;
899 | }
900 |
901 | if (get('current')) {
902 | error(false);
903 | return;
904 | }
905 |
906 | var query = getQuery(name);
907 |
908 | query.withParents = false;
909 | query.limit = 10;
910 |
911 | if (!trigger('send_before', query)) {
912 | ret(null, false);
913 | trigger('check', null);
914 | return;
915 | }
916 |
917 | showSpinner();
918 | trigger('send');
919 |
920 | get('source')(query, function (objs) {
921 | trigger('receive');
922 |
923 | if (!$.trim($input.val())) {
924 | ret2(null, false);
925 | return;
926 | }
927 |
928 | var nameLowerCase = query.name.toLowerCase(),
929 | valueLowerCase = null,
930 | obj = null;
931 |
932 | for (var i = 0; i < objs.length; i++) {
933 | valueLowerCase = objs[i].name.toLowerCase();
934 |
935 | if (nameLowerCase == valueLowerCase) {
936 | obj = objs[i];
937 | break;
938 | }
939 | }
940 |
941 | if (obj) {
942 | $input.val(get('valueFormat')(obj, query));
943 | }
944 |
945 | ret2(obj, !obj);
946 | trigger('check', obj);
947 |
948 | function ret2(obj, er) {
949 | hideSpinner();
950 | ret(obj, er);
951 | }
952 | });
953 |
954 | function ret(obj, er) {
955 | error(er);
956 | setCurrent(obj);
957 | }
958 | }
959 |
960 | /**
961 | * Создаёт контроллер плагина
962 | */
963 | function createController() {
964 | var controller = {
965 |
966 | /**
967 | * Устанавливает значение в поле ввода
968 | *
969 | * @param {*} value
970 | * @returns {{}} Контроллер плагина
971 | */
972 | setValue: function (value) {
973 | if ($.type(value) === 'object') {
974 | return controller.setValueByObject(value);
975 | }
976 |
977 | if ($.type(value) === 'number') {
978 | return controller.setValueById(value);
979 | }
980 |
981 | if ($.type(value) === 'string') {
982 | return controller.setValueByName(value);
983 | }
984 |
985 | if (!value) {
986 | return controller.clear();
987 | }
988 |
989 | return controller;
990 | },
991 |
992 | /**
993 | * Устанавливает значение в поле ввода по названию
994 | *
995 | * @param {string} name Название объекта КЛАДР
996 | * @returns {{}} Контроллер плагина
997 | */
998 | setValueByName: function (name) {
999 | name = $.trim(name + '');
1000 |
1001 | if (name) {
1002 | var query = getQuery('');
1003 |
1004 | query.name = fixName(name);
1005 | query.withParents = false;
1006 | query.limit = 10;
1007 |
1008 | if (!trigger('send_before', query)) {
1009 | changeValue(null, query);
1010 | return controller;
1011 | }
1012 |
1013 | lock();
1014 | trigger('send');
1015 |
1016 | get('source')(query, function (objs) {
1017 | trigger('receive');
1018 |
1019 | var nameLowerCase = query.name.toLowerCase(),
1020 | valueLowerCase = null,
1021 | obj = null;
1022 |
1023 | for (var i = 0; i < objs.length; i++) {
1024 | valueLowerCase = objs[i].name.toLowerCase();
1025 |
1026 | if (nameLowerCase == valueLowerCase) {
1027 | obj = objs[i];
1028 | break;
1029 | }
1030 | }
1031 |
1032 | changeValue(obj, query);
1033 | });
1034 | }
1035 |
1036 | return controller;
1037 | },
1038 |
1039 | /**
1040 | * Устанавливает значение в поле ввода по идентификатору
1041 | *
1042 | * @param {number} id Идентификатор объекта КЛАДР
1043 | * @returns {{}} Контроллер плагина
1044 | */
1045 | setValueById: function (id) {
1046 | var query = getQuery('');
1047 |
1048 | query.parentType = query.type;
1049 | query.parentId = id;
1050 | query.limit = 1;
1051 |
1052 | lock();
1053 |
1054 | $.kladr.api(query, function (objs) {
1055 | objs.length
1056 | ? changeValue(objs[0], query)
1057 | : changeValue(null, query);
1058 | });
1059 |
1060 | return controller;
1061 | },
1062 |
1063 | /**
1064 | * Устанавливает объект КЛАДР как значение поля ввода
1065 | *
1066 | * @param {{}} obj Объект КЛАДР
1067 | * @returns {{}} Контроллер плагина
1068 | */
1069 | setValueByObject: function (obj) {
1070 | changeValue(obj, getQuery(''));
1071 | return controller;
1072 | },
1073 |
1074 | /**
1075 | * Очищает поле ввода
1076 | *
1077 | * @returns {{}} Контроллер плагина
1078 | */
1079 | clear: function () {
1080 | changeValue(null, null);
1081 | return controller;
1082 | }
1083 | };
1084 |
1085 | var lockAttr = 'data-kladr-autofill-lock';
1086 |
1087 | /**
1088 | * Блокирует поле ввода для изменения функцией checkAutoFill
1089 | */
1090 | function lock() {
1091 | $input.attr(lockAttr, true);
1092 | }
1093 |
1094 | /**
1095 | * Изменяет значение поля ввода
1096 | *
1097 | * @param {{}} obj Объект КЛАДР
1098 | * @param {{}} query Объект запроса к сервису
1099 | */
1100 | function changeValue(obj, query) {
1101 | obj ? $input.val(get('valueFormat')(obj, query)) : error(true);
1102 | setCurrent(obj);
1103 | $input.removeAttr(lockAttr);
1104 | }
1105 |
1106 | set('controller', controller);
1107 | }
1108 |
1109 | /**
1110 | * Проверяет автоматически установленное при автозаполнении браузером значение
1111 | */
1112 | function checkAutoFill() {
1113 | var count = 0;
1114 |
1115 | (function test() {
1116 | if (++count > 5 || isFilled()) {
1117 | return;
1118 | }
1119 |
1120 | setTimeout(test, 100);
1121 | })();
1122 |
1123 | function isFilled() {
1124 | var name = $input.val();
1125 |
1126 | if (name) {
1127 | var query = getQuery(name),
1128 | queryType = query.type,
1129 | queryParentType = query.parentType,
1130 | type = $.kladr.type,
1131 | parentFilled = true,
1132 | setByName = get('controller').setValueByName,
1133 | lock;
1134 |
1135 | // Костыль для полей ввода улиц
1136 | if (queryType == type.street && queryParentType != type.city) {
1137 | parentFilled = false;
1138 | }
1139 |
1140 | // Костыль для полей ввода строений
1141 | if (queryType == type.building && !~$.inArray(queryParentType, [type.street, type.city])) {
1142 | parentFilled = false;
1143 | }
1144 |
1145 | lock = $input.attr('data-kladr-autofill-lock');
1146 |
1147 | lock && get('current') && parentFilled && setByName(name);
1148 | return !!get('current');
1149 | }
1150 |
1151 | return false;
1152 | }
1153 | }
1154 |
1155 | /**
1156 | * Инициализирует событие плагина
1157 | *
1158 | * @param {string} event Имя события
1159 | * @param {{}} obj Объект передаваемый в событие
1160 | * @returns {boolean} Выполнить действие идущее после события
1161 | */
1162 | function trigger(event, obj) {
1163 | if (!event) {
1164 | return true;
1165 | }
1166 |
1167 | var eventProp = event.replace(/_([a-z])/ig, function (all, letter) {
1168 | return letter.toUpperCase();
1169 | });
1170 |
1171 | $input.trigger('kladr_' + event, obj);
1172 |
1173 | if ($.type(get(eventProp)) === 'function') {
1174 | return get(eventProp).call($input.get(0), obj) !== false;
1175 | }
1176 |
1177 | return true;
1178 | }
1179 |
1180 | /**
1181 | * Отображает ajax-крутилку
1182 | */
1183 | function showSpinner() {
1184 | if (get('spinner')) {
1185 | get('showSpinner')($spinner);
1186 | }
1187 | }
1188 |
1189 | /**
1190 | * Скрывает ajax-крутилку
1191 | */
1192 | function hideSpinner() {
1193 | if (get('spinner')) {
1194 | get('hideSpinner')($spinner);
1195 | }
1196 | }
1197 |
1198 | /**
1199 | * Генерирует запрос к сервису
1200 | *
1201 | * @param {string} name Введённое в поле ввода пользователем значение
1202 | * @returns {{}}
1203 | */
1204 | function getQuery(name) {
1205 | var query = {},
1206 | fields = [
1207 | 'token',
1208 | 'key',
1209 | 'type',
1210 | 'typeCode',
1211 | 'parentType',
1212 | 'parentId',
1213 | 'oneString',
1214 | 'withParents',
1215 | 'limit',
1216 | 'strict'
1217 | ],
1218 | i;
1219 |
1220 | for (i = 0; i < fields.length; i++) {
1221 | query[fields[i]] = get(fields[i]);
1222 | }
1223 |
1224 | query.name = fixName(name);
1225 |
1226 | var parentInput = get('parentInput'),
1227 | parent;
1228 |
1229 | if (parentInput) {
1230 | parent = getParent(parentInput, query.type);
1231 |
1232 | if (parent) {
1233 | query.parentType = parent.type;
1234 | query.parentId = parent.id;
1235 | }
1236 | }
1237 |
1238 | // Костыль для поиска одной строкой
1239 | if (query.oneString) {
1240 | query.withParents = true;
1241 | }
1242 |
1243 | return query;
1244 | }
1245 |
1246 | /**
1247 | * Возвращает тип и идентификатор ближайшего родительского объекта
1248 | * среди заполненных пользователем полей ввода на странице
1249 | *
1250 | * @param {*} selector Селектор для ограничения поиска полей ввода
1251 | * @param {string} type Тип объектов в текущем поле ввода
1252 | * @returns {{}}
1253 | */
1254 | function getParent(selector, type) {
1255 | var $inputs = $.kladr.getInputs(selector),
1256 | types = $.kladr.type,
1257 | parents = {},
1258 | parent = null,
1259 | t;
1260 |
1261 | $inputs.each(function () {
1262 | var $this = $(this),
1263 | id;
1264 |
1265 | if (id = $this.attr('data-kladr-id')) {
1266 | parents[$this.attr('data-kladr-type')] = id;
1267 | }
1268 | });
1269 |
1270 | for (t in types) {
1271 | if (t == type) {
1272 | return parent;
1273 | }
1274 |
1275 | if (hasOwn(types, t) && parents[t]) {
1276 | parent = {
1277 | type: t,
1278 | id: parents[t]
1279 | }
1280 | }
1281 | }
1282 |
1283 | return parent;
1284 | }
1285 |
1286 | /**
1287 | * Отображает ошибку при некорректном вводе
1288 | *
1289 | * @param {string} name Введённое пользователем значение
1290 | * @returns {string}
1291 | */
1292 | function fixName(name) {
1293 | var noCorrect = 'abcdefghijklmnopqrstuvwxyz',
1294 | testName = name.toLowerCase();
1295 |
1296 | for (var i = 0; i < testName.length; i++) {
1297 | if (~noCorrect.indexOf(testName[i])) {
1298 | error(true);
1299 | return name;
1300 | }
1301 | }
1302 |
1303 | error(false);
1304 | return name;
1305 | }
1306 |
1307 | /**
1308 | * Устанавливает текущий объект КЛАДР
1309 | *
1310 | * @param {{}} obj
1311 | */
1312 | function setCurrent(obj) {
1313 | var curr = get('current');
1314 |
1315 | if ((curr && curr.id) === (obj && obj.id)) {
1316 | return;
1317 | }
1318 |
1319 | set('current', obj);
1320 |
1321 | if (obj && obj.id) {
1322 | $input.attr('data-kladr-id', obj.id);
1323 | } else {
1324 | $input.removeAttr('data-kladr-id');
1325 | }
1326 |
1327 | if (get('oneString')) {
1328 | if (obj && obj.contentType) {
1329 | $input.attr('data-kladr-type', obj.contentType);
1330 | }
1331 | }
1332 |
1333 | trigger('change', obj);
1334 | }
1335 |
1336 | /**
1337 | * Управляет выводом ошибки в поле ввода
1338 | *
1339 | * @param {boolean} error Если true, то вывести ошибку. Если false, то снять.
1340 | */
1341 | function error(error) {
1342 | error
1343 | ? $input.addClass('kladr-error')
1344 | : $input.removeClass('kladr-error');
1345 | }
1346 |
1347 | /**
1348 | * Возвращает значение параметра плагина
1349 | *
1350 | * @param {string} param
1351 | * @returns {*}
1352 | */
1353 | function get(param) {
1354 | return options._get(param);
1355 | }
1356 |
1357 | /**
1358 | * Устанавливает значение параметра плагина
1359 | *
1360 | * @param {string} param
1361 | * @param {*} value
1362 | */
1363 | function set(param, value) {
1364 | options._set(param, value);
1365 | }
1366 | });
1367 | }
1368 |
1369 | /**
1370 | * Считывает значение параметров в объект
1371 | * более удобного для использования формата
1372 | *
1373 | * @param {string|{}} param1
1374 | * @param {*} param2
1375 | * @returns {{obj: boolean|{}, str: boolean|{}, isGet: boolean}}
1376 | */
1377 | function readParams(param1, param2) {
1378 | var params = {
1379 | obj: false,
1380 | str: false,
1381 | isGet: false
1382 | };
1383 |
1384 | if ($.type(param1) === 'object') {
1385 | params.obj = param1;
1386 | return params;
1387 | }
1388 |
1389 | if ($.type(param1) === 'string') {
1390 | params.str = [param1, param2];
1391 | params.isGet = (param2 === undefined);
1392 | }
1393 |
1394 | return params;
1395 | }
1396 |
1397 | /**
1398 | * Возвращает глобальный уникальный идентификатор
1399 | *
1400 | * @returns {number}
1401 | */
1402 | function getGuid() {
1403 | return getGuid.guid
1404 | ? ++getGuid.guid
1405 | : getGuid.guid = 1;
1406 | }
1407 |
1408 | /**
1409 | * Проверяет принадлежит ли свойство объекту
1410 | *
1411 | * @param {{}} obj
1412 | * @param {string} property
1413 | * @returns {boolean}
1414 | */
1415 | function hasOwn(obj, property) {
1416 | return obj.hasOwnProperty(property);
1417 | }
1418 | })(jQuery, window, document);
1419 |
--------------------------------------------------------------------------------
/kladr/js/kladr_zip.js:
--------------------------------------------------------------------------------
1 | (function ($) {
2 | $.fn.kladrZip = function (selector) {
3 | this.keydown(function (e) {
4 | var key = e.charCode || e.keyCode || 0,
5 | allow = (
6 | key == 8 ||
7 | key == 9 ||
8 | key == 13 ||
9 | key == 46 ||
10 | key == 110 ||
11 | key == 190 ||
12 | (key >= 35 && key <= 40) ||
13 | (key >= 96 && key <= 105)
14 | );
15 |
16 | if ($(this).val().length >= 6) {
17 | return allow;
18 | }
19 |
20 | return (allow || (key >= 48 && key <= 57));
21 | });
22 |
23 | this.keyup(function () {
24 | var $this = $(this),
25 | zip = $this.val();
26 |
27 | if (!zip) {
28 | error(false);
29 | return;
30 | }
31 |
32 | $.kladr.api({
33 | type: $.kladr.type.building,
34 | zip: zip,
35 | withParents: true,
36 | limit: 1
37 | }, function (objs) {
38 | var obj = objs.length && objs[0];
39 | objs = [];
40 |
41 | if (obj) {
42 | error(false);
43 |
44 | if (obj.parents) {
45 | objs = objs.concat(obj.parents);
46 | }
47 |
48 | objs.push(obj);
49 | $.kladr.setValues(objs, selector);
50 | }
51 | else {
52 | error(true);
53 | }
54 | });
55 |
56 | function error(er) {
57 | er ? $this.addClass('kladr-error') : $this.removeClass('kladr-error');
58 | }
59 | });
60 |
61 | return this;
62 | };
63 | })(jQuery);
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jquery.kladr",
3 | "version": "2.2.2",
4 | "description": "jQuery Kladr",
5 | "main": "jquery.kladr.min.js",
6 | "directories": {
7 | "example": "examples"
8 | },
9 | "scripts": {
10 | "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/garakh/kladrapi-jsclient.git"
15 | },
16 | "author": {
17 | "name": "Alexandr Yakovlev",
18 | "email": "xescoder@gmail.com",
19 | "url": "https://www.linkedin.com/in/xescoder"
20 | },
21 | "keywords": [
22 | "kladr",
23 | "jquery"
24 | ],
25 | "license": "Public Domain",
26 | "bugs": {
27 | "url": "https://github.com/garakh/kladrapi-jsclient/issues"
28 | },
29 | "homepage": "https://github.com/garakh/kladrapi-jsclient",
30 | "devDependencies": {
31 | "gulp": "^3.8.8",
32 | "gulp-csso": "^0.2.9",
33 | "gulp-uglify": "^1.0.1",
34 | "gulp-concat": "^2.4.1",
35 | "gulp-rename": "^1.2.0"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------