city-picker.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
  1. /*!
  2. * CityPicker v1.1.0
  3. * https://github.com/tshi0912/citypicker
  4. *
  5. * Copyright (c) 2015-2016 Tao Shi
  6. * Released under the MIT license
  7. *
  8. * Date: 2016-09-09T12:11:57.119Z
  9. */
  10. (function (factory) {
  11. if (typeof define === 'function' && define.amd) {
  12. // AMD. Register as anonymous module.
  13. define(['jquery', 'ChineseDistricts'], factory);
  14. } else if (typeof exports === 'object') {
  15. // Node / CommonJS
  16. factory(require('jquery'), require('ChineseDistricts'));
  17. } else {
  18. // Browser globals.
  19. factory(jQuery, ChineseDistricts);
  20. }
  21. })(function ($, ChineseDistricts) {
  22. 'use strict';
  23. if (typeof ChineseDistricts === 'undefined') {
  24. throw new Error('The file "city-picker.data.js" must be included first!');
  25. }
  26. var NAMESPACE = 'citypicker';
  27. var EVENT_CHANGE = 'change.' + NAMESPACE;
  28. var PROVINCE = 'province';
  29. var CITY = 'city';
  30. var DISTRICT = 'district';
  31. function CityPicker(element, options) {
  32. this.$element = $(element);
  33. this.$dropdown = null;
  34. this.options = $.extend({}, CityPicker.DEFAULTS, $.isPlainObject(options) && options);
  35. this.active = false;
  36. this.dems = [];
  37. this.needBlur = false;
  38. this.init();
  39. }
  40. CityPicker.prototype = {
  41. constructor: CityPicker,
  42. init: function () {
  43. this.defineDems();
  44. this.render();
  45. this.bind();
  46. this.active = true;
  47. },
  48. render: function () {
  49. var p = this.getPosition(),
  50. placeholder = this.$element.attr('placeholder') || this.options.placeholder,
  51. textspan = '<span class="city-picker-span" style="' +
  52. this.getWidthStyle(p.width) + 'height:' +
  53. p.height + 'px;line-height:' + (p.height - 1) + 'px;">' +
  54. (placeholder ? '<span class="placeholder">' + placeholder + '</span>' : '') +
  55. '<span class="title"></span><div class="arrow"></div>' + '</span>',
  56. dropdown = '<div class="city-picker-dropdown" style="left:0px;top:100%;' +
  57. this.getWidthStyle(p.width, true) + '">' +
  58. '<div class="city-select-wrap">' +
  59. '<div class="city-select-tab">' +
  60. '<a class="active" data-count="province">省份</a>' +
  61. (this.includeDem('city') ? '<a data-count="city">城市</a>' : '') +
  62. (this.includeDem('district') ? '<a data-count="district">区县</a>' : '') + '</div>' +
  63. '<div class="city-select-content">' +
  64. '<div class="city-select province" data-count="province"></div>' +
  65. (this.includeDem('city') ? '<div class="city-select city" data-count="city"></div>' : '') +
  66. (this.includeDem('district') ? '<div class="city-select district" data-count="district"></div>' : '') +
  67. '</div></div>';
  68. this.$element.addClass('city-picker-input');
  69. this.$textspan = $(textspan).insertAfter(this.$element);
  70. this.$dropdown = $(dropdown).insertAfter(this.$textspan);
  71. var $select = this.$dropdown.find('.city-select');
  72. // setup this.$province, this.$city and/or this.$district object
  73. $.each(this.dems, $.proxy(function (i, type) {
  74. this['$' + type] = $select.filter('.' + type + '');
  75. }, this));
  76. this.refresh();
  77. },
  78. refresh: function (force) {
  79. // clean the data-item for each $select
  80. var $select = this.$dropdown.find('.city-select');
  81. $select.data('item', null);
  82. // parse value from value of the target $element
  83. var val = this.$element.val() || '';
  84. val = val.split('/');
  85. $.each(this.dems, $.proxy(function (i, type) {
  86. if (val[i] && i < val.length) {
  87. this.options[type] = val[i];
  88. } else if (force) {
  89. this.options[type] = '';
  90. }
  91. this.output(type);
  92. }, this));
  93. this.tab(PROVINCE);
  94. this.feedText();
  95. this.feedVal();
  96. },
  97. defineDems: function () {
  98. var stop = false;
  99. $.each([PROVINCE, CITY, DISTRICT], $.proxy(function (i, type) {
  100. if (!stop) {
  101. this.dems.push(type);
  102. }
  103. if (type === this.options.level) {
  104. stop = true;
  105. }
  106. }, this));
  107. },
  108. includeDem: function (type) {
  109. return $.inArray(type, this.dems) !== -1;
  110. },
  111. getPosition: function () {
  112. var p, h, w, s, pw;
  113. p = this.$element.position();
  114. s = this.getSize(this.$element);
  115. h = s.height;
  116. w = s.width;
  117. if (this.options.responsive) {
  118. pw = this.$element.offsetParent().width();
  119. if (pw) {
  120. w = w / pw;
  121. if (w > 0.99) {
  122. w = 1;
  123. }
  124. w = w * 100 + '%';
  125. }
  126. }
  127. return {
  128. top: p.top || 0,
  129. left: p.left || 0,
  130. height: h,
  131. width: w
  132. };
  133. },
  134. getSize: function ($dom) {
  135. var $wrap, $clone, sizes;
  136. if (!$dom.is(':visible')) {
  137. $wrap = $("<div />").appendTo($("body"));
  138. $wrap.css({
  139. "position": "absolute !important",
  140. "visibility": "hidden !important",
  141. "display": "block !important"
  142. });
  143. $clone = $dom.clone().appendTo($wrap);
  144. sizes = {
  145. width: $clone.outerWidth(),
  146. height: $clone.outerHeight()
  147. };
  148. $wrap.remove();
  149. } else {
  150. sizes = {
  151. width: $dom.outerWidth(),
  152. height: $dom.outerHeight()
  153. };
  154. }
  155. return sizes;
  156. },
  157. getWidthStyle: function (w, dropdown) {
  158. // if (this.options.responsive && !$.isNumeric(w)) {
  159. // return 'width:' + w + ';';
  160. // } else {
  161. // return 'width:' + (dropdown ? Math.max(320, w) : w) + 'px;';
  162. // }
  163. return 'width:100%;';
  164. },
  165. bind: function () {
  166. var $this = this;
  167. $(document).on('click', (this._mouteclick = function (e) {
  168. var $target = $(e.target);
  169. var $dropdown, $span, $input;
  170. if ($target.is('.city-picker-span')) {
  171. $span = $target;
  172. } else if ($target.is('.city-picker-span *')) {
  173. $span = $target.parents('.city-picker-span');
  174. }
  175. if ($target.is('.city-picker-input')) {
  176. $input = $target;
  177. }
  178. if ($target.is('.city-picker-dropdown')) {
  179. $dropdown = $target;
  180. } else if ($target.is('.city-picker-dropdown *')) {
  181. $dropdown = $target.parents('.city-picker-dropdown');
  182. }
  183. if ((!$input && !$span && !$dropdown) ||
  184. ($span && $span.get(0) !== $this.$textspan.get(0)) ||
  185. ($input && $input.get(0) !== $this.$element.get(0)) ||
  186. ($dropdown && $dropdown.get(0) !== $this.$dropdown.get(0))) {
  187. $this.close(true);
  188. }
  189. }));
  190. this.$element.on('change', (this._changeElement = $.proxy(function () {
  191. this.close(true);
  192. this.refresh(true);
  193. }, this))).on('focus', (this._focusElement = $.proxy(function () {
  194. this.needBlur = true;
  195. this.open();
  196. }, this))).on('blur', (this._blurElement = $.proxy(function () {
  197. if (this.needBlur) {
  198. this.needBlur = false;
  199. this.close(true);
  200. }
  201. }, this)));
  202. this.$textspan.on('click', function (e) {
  203. var $target = $(e.target), type;
  204. $this.needBlur = false;
  205. if ($target.is('.select-item')) {
  206. type = $target.data('count');
  207. $this.open(type);
  208. } else {
  209. if ($this.$dropdown.is(':visible')) {
  210. $this.close();
  211. } else {
  212. $this.open();
  213. }
  214. }
  215. }).on('mousedown', function () {
  216. $this.needBlur = false;
  217. });
  218. this.$dropdown.on('click', '.city-select a', function () {
  219. var $select = $(this).parents('.city-select');
  220. var $active = $select.find('a.active');
  221. var last = $select.next().length === 0;
  222. $active.removeClass('active');
  223. $(this).addClass('active');
  224. if ($active.data('code') !== $(this).data('code')) {
  225. $select.data('item', {
  226. address: $(this).attr('title'), code: $(this).data('code')
  227. });
  228. $(this).trigger(EVENT_CHANGE);
  229. $this.feedText();
  230. $this.feedVal(true);
  231. if (last) {
  232. $this.close();
  233. }
  234. }
  235. }).on('click', '.city-select-tab a', function () {
  236. if (!$(this).hasClass('active')) {
  237. var type = $(this).data('count');
  238. $this.tab(type);
  239. }
  240. }).on('mousedown', function () {
  241. $this.needBlur = false;
  242. });
  243. if (this.$province) {
  244. this.$province.on(EVENT_CHANGE, (this._changeProvince = $.proxy(function () {
  245. this.output(CITY);
  246. this.output(DISTRICT);
  247. this.tab(CITY);
  248. }, this)));
  249. }
  250. if (this.$city) {
  251. this.$city.on(EVENT_CHANGE, (this._changeCity = $.proxy(function () {
  252. this.output(DISTRICT);
  253. this.tab(DISTRICT);
  254. }, this)));
  255. }
  256. },
  257. open: function (type) {
  258. type = type || PROVINCE;
  259. this.$dropdown.show();
  260. this.$textspan.addClass('open').addClass('focus');
  261. this.tab(type);
  262. },
  263. close: function (blur) {
  264. this.$dropdown.hide();
  265. this.$textspan.removeClass('open');
  266. if (blur) {
  267. this.$textspan.removeClass('focus');
  268. }
  269. },
  270. unbind: function () {
  271. $(document).off('click', this._mouteclick);
  272. this.$element.off('change', this._changeElement);
  273. this.$element.off('focus', this._focusElement);
  274. this.$element.off('blur', this._blurElement);
  275. this.$textspan.off('click');
  276. this.$textspan.off('mousedown');
  277. this.$dropdown.off('click');
  278. this.$dropdown.off('mousedown');
  279. if (this.$province) {
  280. this.$province.off(EVENT_CHANGE, this._changeProvince);
  281. }
  282. if (this.$city) {
  283. this.$city.off(EVENT_CHANGE, this._changeCity);
  284. }
  285. },
  286. getText: function () {
  287. var text = '';
  288. this.$dropdown.find('.city-select')
  289. .each(function () {
  290. var item = $(this).data('item'),
  291. type = $(this).data('count');
  292. if (item) {
  293. text += ($(this).hasClass('province') ? '' : '/') + '<span class="select-item" data-count="' +
  294. type + '" data-code="' + item.code + '">' + item.address + '</span>';
  295. }
  296. });
  297. return text;
  298. },
  299. getPlaceHolder: function () {
  300. return this.$element.attr('placeholder') || this.options.placeholder;
  301. },
  302. feedText: function () {
  303. var text = this.getText();
  304. if (text) {
  305. this.$textspan.find('>.placeholder').hide();
  306. this.$textspan.find('>.title').html(this.getText()).show();
  307. } else {
  308. this.$textspan.find('>.placeholder').text(this.getPlaceHolder()).show();
  309. this.$textspan.find('>.title').html('').hide();
  310. }
  311. },
  312. getCode: function (count) {
  313. var obj = {}, arr = [];
  314. this.$textspan.find('.select-item')
  315. .each(function () {
  316. var code = $(this).data('code');
  317. var count = $(this).data('count');
  318. obj[count] = code;
  319. arr.push(code);
  320. });
  321. return count ? obj[count] : arr.join('/');
  322. },
  323. getVal: function () {
  324. var text = '';
  325. this.$dropdown.find('.city-select')
  326. .each(function () {
  327. var item = $(this).data('item');
  328. if (item) {
  329. text += ($(this).hasClass('province') ? '' : '/') + item.address;
  330. }
  331. });
  332. return text;
  333. },
  334. feedVal: function (trigger) {
  335. this.$element.val(this.getVal());
  336. if(trigger) {
  337. this.$element.trigger('cp:updated');
  338. }
  339. },
  340. output: function (type) {
  341. var options = this.options;
  342. //var placeholders = this.placeholders;
  343. var $select = this['$' + type];
  344. var data = type === PROVINCE ? {} : [];
  345. var item;
  346. var districts;
  347. var code;
  348. var matched = null;
  349. var value;
  350. if (!$select || !$select.length) {
  351. return;
  352. }
  353. item = $select.data('item');
  354. value = (item ? item.address : null) || options[type];
  355. code = (
  356. type === PROVINCE ? 86 :
  357. type === CITY ? this.$province && this.$province.find('.active').data('code') :
  358. type === DISTRICT ? this.$city && this.$city.find('.active').data('code') : code
  359. );
  360. districts = $.isNumeric(code) ? ChineseDistricts[code] : null;
  361. if ($.isPlainObject(districts)) {
  362. $.each(districts, function (code, address) {
  363. var provs;
  364. if (type === PROVINCE) {
  365. provs = [];
  366. for (var i = 0; i < address.length; i++) {
  367. if (address[i].address === value) {
  368. matched = {
  369. code: address[i].code,
  370. address: address[i].address
  371. };
  372. }
  373. provs.push({
  374. code: address[i].code,
  375. address: address[i].address,
  376. selected: address[i].address === value
  377. });
  378. }
  379. data[code] = provs;
  380. } else {
  381. if (address === value) {
  382. matched = {
  383. code: code,
  384. address: address
  385. };
  386. }
  387. data.push({
  388. code: code,
  389. address: address,
  390. selected: address === value
  391. });
  392. }
  393. });
  394. }
  395. $select.html(type === PROVINCE ? this.getProvinceList(data) :
  396. this.getList(data, type));
  397. $select.data('item', matched);
  398. },
  399. getProvinceList: function (data) {
  400. var list = [],
  401. $this = this,
  402. simple = this.options.simple;
  403. $.each(data, function (i, n) {
  404. list.push('<dl class="clearfix">');
  405. list.push('<dt>' + i + '</dt><dd>');
  406. $.each(n, function (j, m) {
  407. list.push(
  408. '<a' +
  409. ' title="' + (m.address || '') + '"' +
  410. ' data-code="' + (m.code || '') + '"' +
  411. ' class="' +
  412. (m.selected ? ' active' : '') +
  413. '">' +
  414. ( simple ? $this.simplize(m.address, PROVINCE) : m.address) +
  415. '</a>');
  416. });
  417. list.push('</dd></dl>');
  418. });
  419. return list.join('');
  420. },
  421. getList: function (data, type) {
  422. var list = [],
  423. $this = this,
  424. simple = this.options.simple;
  425. list.push('<dl class="clearfix"><dd>');
  426. $.each(data, function (i, n) {
  427. list.push(
  428. '<a' +
  429. ' title="' + (n.address || '') + '"' +
  430. ' data-code="' + (n.code || '') + '"' +
  431. ' class="' +
  432. (n.selected ? ' active' : '') +
  433. '">' +
  434. ( simple ? $this.simplize(n.address, type) : n.address) +
  435. '</a>');
  436. });
  437. list.push('</dd></dl>');
  438. return list.join('');
  439. },
  440. simplize: function (address, type) {
  441. address = address || '';
  442. if (type === PROVINCE) {
  443. return address.replace(/[省,市,自治区,壮族,回族,维吾尔]/g, '');
  444. } else if (type === CITY) {
  445. return address.replace(/[市,地区,回族,蒙古,苗族,白族,傣族,景颇族,藏族,彝族,壮族,傈僳族,布依族,侗族]/g, '')
  446. .replace('哈萨克', '').replace('自治州', '').replace(/自治县/, '');
  447. } else if (type === DISTRICT) {
  448. return address.length > 2 ? address.replace(/[市,区,县,旗]/g, '') : address;
  449. }
  450. },
  451. tab: function (type) {
  452. var $selects = this.$dropdown.find('.city-select');
  453. var $tabs = this.$dropdown.find('.city-select-tab > a');
  454. var $select = this['$' + type];
  455. var $tab = this.$dropdown.find('.city-select-tab > a[data-count="' + type + '"]');
  456. if ($select) {
  457. $selects.hide();
  458. $select.show();
  459. $tabs.removeClass('active');
  460. $tab.addClass('active');
  461. }
  462. },
  463. reset: function () {
  464. this.$element.val(null).trigger('change');
  465. },
  466. destroy: function () {
  467. this.unbind();
  468. this.$element.removeData(NAMESPACE).removeClass('city-picker-input');
  469. this.$textspan.remove();
  470. this.$dropdown.remove();
  471. }
  472. };
  473. CityPicker.DEFAULTS = {
  474. simple: false,
  475. responsive: false,
  476. placeholder: '请选择省/市/区',
  477. level: 'district',
  478. province: '',
  479. city: '',
  480. district: ''
  481. };
  482. CityPicker.setDefaults = function (options) {
  483. $.extend(CityPicker.DEFAULTS, options);
  484. };
  485. // Save the other citypicker
  486. CityPicker.other = $.fn.citypicker;
  487. // Register as jQuery plugin
  488. $.fn.citypicker = function (option) {
  489. var args = [].slice.call(arguments, 1);
  490. return this.each(function () {
  491. var $this = $(this);
  492. var data = $this.data(NAMESPACE);
  493. var options;
  494. var fn;
  495. if (!data) {
  496. if (/destroy/.test(option)) {
  497. return;
  498. }
  499. options = $.extend({}, $this.data(), $.isPlainObject(option) && option);
  500. $this.data(NAMESPACE, (data = new CityPicker(this, options)));
  501. }
  502. if (typeof option === 'string' && $.isFunction(fn = data[option])) {
  503. fn.apply(data, args);
  504. }
  505. });
  506. };
  507. $.fn.citypicker.Constructor = CityPicker;
  508. $.fn.citypicker.setDefaults = CityPicker.setDefaults;
  509. // No conflict
  510. $.fn.citypicker.noConflict = function () {
  511. $.fn.citypicker = CityPicker.other;
  512. return this;
  513. };
  514. $(function () {
  515. $('[data-toggle="city-picker"]').citypicker();
  516. });
  517. });