最近公司做的项目要和京东的数据做对接,所以要做个类似京东商品的详情页。页面的数据,是可以从京东接口获取到的,但是地址插件选择的效果需要自己实现。前端的同事在之前的项目中,已经选择了一款地址插件(city-picker.js),但是这款插件最多只支持三级地址,而且最主要的是这插件的地址数据来源,是写死在一个json文件中的,意思就是说,在使用这个插件的时候页面要一次性的把所有的地址数据都加载出来,这在pc端一般倒还可以承受,但是到了,移动端,随便一个手机就会卡死,浏览器直接崩溃。
经过在网上的各种查找,和研究,发现一个博客,http://www.cnblogs.com/huangchanghuan/p/6681510.html
对city-picker这个插件进行了扩展,扩展成了支持四级地址的插件了。这正是我想要的,因为京东给过来的地址数据就是4级的。正好可以使用。然后就拿过来直接用了。
很强大,完美的满足了,我的需求。但是这个大神的博客只是将三级地址改造成了四级地址,没有解决,动态加载数据的问题,就是说用这个四级地址插件的时候,还是要把京东的地址库数据转成json文件一次性加载到页面。这样的话在移动端浏览时还是会把浏览器搞崩。
好了,说了这么多铺垫的废话,就是为了引出,我对这个四级地址插件的改造。
直接代码
1 /*!
2 * CityPicker v@VERSION
3 * https://github.com/tshi0912/citypicker
4 *
5 * Copyright (c) 2015-@YEAR Tao Shi
6 * Released under the MIT license
7 *
8 * Date: @DATE
9 */
10
11
12 ChineseDistricts={
13 "86": {
14 "中国": [
15 {
16 "address": "北京",
17 "code": "1"
18 },
19 {
20 "address": "上海",
21 "code": "2"
22 },
23 {
24 "address": "天津",
25 "code": "3"
26 },
27 {
28 "address": "重庆",
29 "code": "4"
30 },
31 {
32 "address": "河北",
33 "code": "5"
34 },
35 {
36 "address": "山西",
37 "code": "6"
38 },
39 {
40 "address": "河南",
41 "code": "7"
42 },
43 {
44 "address": "辽宁",
45 "code": "8"
46 },
47 {
48 "address": "吉林",
49 "code": "9"
50 },
51 {
52 "address": "黑龙江",
53 "code": "10"
54 },
55 {
56 "address": "内蒙古",
57 "code": "11"
58 },
59 {
60 "address": "江苏",
61 "code": "12"
62 },
63 {
64 "address": "山东",
65 "code": "13"
66 },
67 {
68 "address": "安徽",
69 "code": "14"
70 },
71 {
72 "address": "浙江",
73 "code": "15"
74 },
75 {
76 "address": "福建",
77 "code": "16"
78 },
79 {
80 "address": "湖北",
81 "code": "17"
82 },
83 {
84 "address": "湖南",
85 "code": "18"
86 },
87 {
88 "address": "广东",
89 "code": "19"
90 },
91 {
92 "address": "广西",
93 "code": "20"
94 },
95 {
96 "address": "江西",
97 "code": "21"
98 },
99 {
100 "address": "四川",
101 "code": "22"
102 },
103 {
104 "address": "海南",
105 "code": "23"
106 },
107 {
108 "address": "贵州",
109 "code": "24"
110 },
111 {
112 "address": "云南",
113 "code": "25"
114 },
115 {
116 "address": "西藏",
117 "code": "26"
118 },
119 {
120 "address": "陕西",
121 "code": "27"
122 },
123 {
124 "address": "甘肃",
125 "code": "28"
126 },
127 {
128 "address": "青海",
129 "code": "29"
130 },
131 {
132 "address": "宁夏",
133 "code": "30"
134 },
135 {
136 "address": "新疆",
137 "code": "31"
138 },
139 {
140 "address": "中国台湾",
141 "code": "32"
142 },
143 {
144 "address": "钓鱼岛",
145 "code": "84"
146 },
147 {
148 "address": "港澳",
149 "code": "52993"
150 }
151 ]
152 }
153 };
154
155 (function (factory) {
156 if (typeof define === 'function' && define.amd) {
157 // AMD. Register as anonymous module.
158 define(['jquery', 'ChineseDistricts'], factory);
159 } else if (typeof exports === 'object') {
160 // Node / CommonJS
161 factory(require('jquery'), require('ChineseDistricts'));
162 } else {
163 // Browser globals.
164 factory(jQuery, ChineseDistricts);
165 }
166 })(function ($, ChineseDistricts) {
167
168 'use strict';
169 if (typeof ChineseDistricts === 'undefined') {
170 throw new Error('The file "city-picker.data.js" must be included first!');
171 }
172 var NAMESPACE = 'citypicker';
173 var EVENT_CHANGE = 'change.' + NAMESPACE;
174 var PROVINCE = 'province';
175 var CITY = 'city';
176 var DISTRICT = 'district';
177 var COUNTY = 'county';
178
179 function CityPicker(element, options) {
180 this.$element = $(element);
181 this.$dropdown = null;
182 this.options = $.extend({}, CityPicker.DEFAULTS, $.isPlainObject(options) && options);
183 this.active = false;
184 this.dems = [];
185 this.needBlur = false;
186 this.init();
187 }
188
189 CityPicker.prototype = {
190 constructor: CityPicker,
191
192 init: function () {
193
194 this.defineDems();
195
196 this.render();
197
198 this.bind();
199
200 this.active = true;
201 },
202 //界面显示处理
203 render: function () {
204 var p = this.getPosition(),
205 placeholder = this.$element.attr('placeholder') || this.options.placeholder,
206 textspan = '<span class="city-picker-span" style="' +
207 this.getWidthStyle(p.width) + 'height:' +
208 p.height + 'px;line-height:' + (p.height - 1) + 'px;">' +
209 (placeholder ? '<span class="placeholder">' + placeholder + '</span>' : '') +
210 '<span class="title"></span><div class="arrow"></div>' + '</span>',
211
212 dropdown = '<div class="city-picker-dropdown" style="left:0px;top:100%;' +
213 this.getWidthStyle(p.width, true) + '">' +
214 '<div class="city-select-wrap">' +
215 '<div class="city-select-tab">' +
216 '<a class="active" data-count="province">省份</a>' +
217 (this.includeDem('city') ? '<a data-count="city">城市</a>' : '') +
218 (this.includeDem('district') ? '<a data-count="district">区县</a>' : '') +
219 (this.includeDem('county') ? '<a data-count="county">乡镇</a>' : '') +
220 '</div>' +
221 '<div class="city-select-content">' +
222 '<div class="city-select province" data-count="province"></div>' +
223 (this.includeDem('city') ? '<div class="city-select city" data-count="city"></div>' : '') +
224 (this.includeDem('district') ? '<div class="city-select district" data-count="district"></div>' : '') +
225 (this.includeDem('county') ? '<div class="city-select county" data-count="county"></div>' : '') +
226 '</div></div>';
227
228 this.$element.addClass('city-picker-input');
229 this.$textspan = $(textspan).insertAfter(this.$element);
230 this.$dropdown = $(dropdown).insertAfter(this.$textspan);
231 var $select = this.$dropdown.find('.city-select');
232
233 // setup this.$province, this.$city and/or this.$district object
234 $.each(this.dems, $.proxy(function (i, type) {
235 this['$' + type] = $select.filter('.' + type + '');
236 }, this));
237
238 this.refresh();
239 },
240
241 refresh: function (force) {
242 // clean the data-item for each $select
243 var $select = this.$dropdown.find('.city-select');
244 $select.data('item', null);
245 // parse value from value of the target $element
246 var val = this.$element.val() || '';
247 val = val.split('/');
248 $.each(this.dems, $.proxy(function (i, type) {//遍历dems
249 if (val[i] && i < val.length) {
250 this.options[type] = val[i];//把当前显示值赋值给options
251 } else if (force) {
252 this.options[type] = '';
253 }
254 this.output(type);//输出下拉框显示数据
255 }, this));
256 this.tab(PROVINCE);
257 this.feedText();//界面显示选择的内容
258 this.feedVal();//input标签value赋值
259 },
260 //dems赋值
261 defineDems: function () {
262 var stop = false;
263 $.each([PROVINCE, CITY, DISTRICT,COUNTY], $.proxy(function (i, type) {
264 if (!stop) {
265 this.dems.push(type);
266 }
267 if (type === this.options.level) {
268 stop = true;
269 }
270 }, this));
271 },
272
273 includeDem: function (type) {
274 return $.inArray(type, this.dems) !== -1;
275 },
276
277 getPosition: function () {
278 var p, h, w, s, pw;
279 p = this.$element.position();
280 s = this.getSize(this.$element);
281 h = s.height;
282 w = s.width;
283 if (this.options.responsive) {
284 pw = this.$element.offsetParent().width();
285 if (pw) {
286 w = w / pw;
287 if (w > 0.99) {
288 w = 1;
289 }
290 w = w * 100 + '%';
291 }
292 }
293
294 return {
295 top: p.top || 0,
296 left: p.left || 0,
297 height: h,
298 width: w
299 };
300 },
301
302 getSize: function ($dom) {
303 var $wrap, $clone, sizes;
304 if (!$dom.is(':visible')) {
305 $wrap = $("<div />").appendTo($("body"));
306 $wrap.css({
307 "position": "absolute !important",
308 "visibility": "hidden !important",
309 "display": "block !important"
310 });
311
312 $clone = $dom.clone().appendTo($wrap);
313
314 sizes = {
315 width: $clone.outerWidth(),
316 height: $clone.outerHeight()
317 };
318
319 $wrap.remove();
320 } else {
321 sizes = {
322 width: $dom.outerWidth(),
323 height: $dom.outerHeight()
324 };
325 }
326
327 return sizes;
328 },
329
330 getWidthStyle: function (w, dropdown) {
331 if (this.options.responsive && !$.isNumeric(w)) {
332 return 'width:' + w + ';';
333 } else {
334 return 'width:' + (dropdown ? Math.max(320, w) : w) + 'px;';
335 }
336 },
337 //绑定事件
338 bind: function () {
339 var $this = this;
340 $(document).on('click', (this._mouteclick = function (e) {
341 var $target = $(e.target);
342 var $dropdown, $span, $input;
343 if ($target.is('.city-picker-span')) {
344 $span = $target;
345 } else if ($target.is('.city-picker-span *')) {
346 $span = $target.parents('.city-picker-span');
347 }
348 if ($target.is('.city-picker-input')) {
349 $input = $target;
350 }
351 if ($target.is('.city-picker-dropdown')) {
352 $dropdown = $target;
353 } else if ($target.is('.city-picker-dropdown *')) {
354 $dropdown = $target.parents('.city-picker-dropdown');
355 }
356 if ((!$input && !$span && !$dropdown) ||
357 ($span && $span.get(0) !== $this.$textspan.get(0)) ||
358 ($input && $input.get(0) !== $this.$element.get(0)) ||
359 ($dropdown && $dropdown.get(0) !== $this.$dropdown.get(0))) {
360 $this.close(true);
361 }
362 }));
363 this.$element.on('change', (this._changeElement = $.proxy(function () {
364 this.close(true);
365 this.refresh(true);
366 }, this))).on('focus', (this._focusElement = $.proxy(function () {
367 this.needBlur = true;
368 this.open();
369 }, this))).on('blur', (this._blurElement = $.proxy(function () {
370 if (this.needBlur) {
371 this.needBlur = false;
372 this.close(true);
373 }
374 }, this)));
375 this.$textspan.on('click', function (e) {
376 var $target = $(e.target), type;
377 $this.needBlur = false;
378 if ($target.is('.select-item')) {
379 type = $target.data('count');
380 $this.open(type);
381 } else {
382 if ($this.$dropdown.is(':visible')) {
383 $this.close();
384 } else {
385 $this.open();
386 }
387 }
388 }).on('mousedown', function () {
389 $this.needBlur = false;
390 });
391 this.$dropdown.on('click', '.city-select a', function () {
392 var $select = $(this).parents('.city-select');
393 var $active = $select.find('a.active');
394 var last = $select.next().length === 0;
395 $active.removeClass('active');
396 $(this).addClass('active');
397 if ($active.data('code') !== $(this).data('code')) {
398 $select.data('item', {
399 address: $(this).attr('title'), code: $(this).data('code')
400 });
401 $(this).trigger(EVENT_CHANGE);
402 $this.feedText();
403 $this.feedVal(true);
404 if (last) {
405 $this.close();
406 }
407 }
408 }).on('click', '.city-select-tab a', function () {
409 if (!$(this).hasClass('active')) {
410 var type = $(this).data('count');
411 $this.tab(type);
412 }
413 }).on('mousedown', function () {
414 $this.needBlur = false;
415 });
416 if (this.$province) {
417 this.$province.on(EVENT_CHANGE, (this._changeProvince = $.proxy(function () {
418 if(this.output(CITY)){//判断下一个tab是否有数据,没有则关闭下拉
419 $this.close();
420 return;
421 };
422 this.output(CITY);
423 this.output(DISTRICT);
424 this.output(COUNTY);
425 this.tab(CITY);
426 }, this)));
427 }
428 if (this.$city) {
429 this.$city.on(EVENT_CHANGE, (this._changeCity = $.proxy(function () {
430 if(this.output(DISTRICT)){
431 $this.close();
432 return;
433 };
434 this.output(COUNTY);
435 this.tab(DISTRICT);
436 }, this)));
437 }
438
439 if (this.$district) {
440 this.$district.on(EVENT_CHANGE, (this._changeDistrict = $.proxy(function () {
441 if(this.output(COUNTY)){
442 $this.close();
443 return;
444 };
445 this.tab(COUNTY);
446 }, this)));
447 }
448 },
449 //显示下拉
450 open: function (type) {
451 type = type || PROVINCE;
452 this.$dropdown.show();
453 this.$textspan.addClass('open').addClass('focus');
454 this.tab(type);
455 },
456 //关闭下拉
457 close: function (blur) {
458 this.$dropdown.hide();
459 this.$textspan.removeClass('open');
460 if (blur) {
461 this.$textspan.removeClass('focus');
462 }
463 },
464 //解绑事件
465 unbind: function () {
466
467 $(document).off('click', this._mouteclick);
468
469 this.$element.off('change', this._changeElement);
470 this.$element.off('focus', this._focusElement);
471 this.$element.off('blur', this._blurElement);
472
473 this.$textspan.off('click');
474 this.$textspan.off('mousedown');
475
476 this.$dropdown.off('click');
477 this.$dropdown.off('mousedown');
478
479 if (this.$province) {
480 this.$province.off(EVENT_CHANGE, this._changeProvince);
481 }
482
483 if (this.$city) {
484 this.$city.off(EVENT_CHANGE, this._changeCity);
485 }
486
487 if (this.$district) {
488 this.$district.off(EVENT_CHANGE, this._changeDistrict);
489 }
490 },
491 //获取选择项信息
492 getText: function () {
493 var text = '';
494 this.$dropdown.find('.city-select')
495 .each(function () {
496 var item = $(this).data('item'),
497 type = $(this).data('count');
498 if (item) {
499 text += ($(this).hasClass('province') ? '' : '/') + '<span class="select-item" data-count="' +
500 type + '" data-code="' + item.code + '">' + item.address + '</span>';
501 }
502 });
503 return text;
504 },
505 getPlaceHolder: function () {
506 return this.$element.attr('placeholder') || this.options.placeholder;
507 },
508 //显示placeholder或者选择的区域
509 feedText: function () {
510 var text = this.getText();
511 if (text) {
512 this.$textspan.find('>.placeholder').hide();
513 this.$textspan.find('>.title').html(this.getText()).show();
514 } else {
515 this.$textspan.find('>.placeholder').text(this.getPlaceHolder()).show();
516 this.$textspan.find('>.title').html('').hide();
517 }
518 },
519 getCode: function (count) {
520 var obj = {}, arr = [];
521 this.$textspan.find('.select-item')
522 .each(function () {
523 var code = $(this).data('code');
524 var count = $(this).data('count');
525 obj[count] = code;
526 arr.push(code);
527 });
528 return count ? obj[count] : arr.join('/');
529 },
530 getVal: function () {
531 var text = '';
532 var code='';
533 this.$dropdown.find('.city-select')
534 .each(function () {
535 var item = $(this).data('item');
536 if (item) {
537 text += ($(this).hasClass('province') ? '' : '/') + item.address;
538 code += ($(this).hasClass('province') ? '' : '_') + item.code;
539 }
540 });
541 $("#addrValue").val(code);
542 return text;
543 },
544 //input的value赋值
545 feedVal: function (trigger) {
546 this.$element.val(this.getVal());
547 if(trigger) {
548 this.$element.trigger('cp:updated');
549 }
550 },
551 //输出数据
552 output: function (type) {
553 var $this = this;
554 var options = this.options;
555 //var placeholders = this.placeholders;
556 var $select = this['$' + type];
557 var data = type === PROVINCE ? {} : [];
558 var item;
559 var districts;
560 var code;
561 var matched = null;
562 var value;
563 if (!$select || !$select.length) {
564 return;
565 }
566 item = $select.data('item');
567 value = (item ? item.address : null) || options[type];
568 code = (
569 type === PROVINCE ? 86 :
570 type === CITY ? this.$province && this.$province.find('.active').data('code') :
571 type === DISTRICT ? this.$city && this.$city.find('.active').data('code') :
572 type === COUNTY ? this.$district && this.$district.find('.active').data('code') : code
573 );
574 //districts = $.isNumeric(code) ? ChineseDistricts[code] : null;
575 //判断是否应该去远程加载数据
576 districts = $.isNumeric(code) ? this.remoteLoadData(type,code) : null;
577 if ($.isPlainObject(districts)) {
578 $.each(districts, function (code, address) {
579 var provs;
580 if (type === PROVINCE) {
581 provs = [];
582 for (var i = 0; i < address.length; i++) {
583 if (address[i].address === value) {
584 matched = {
585 code: address[i].code,
586 address: address[i].address
587 };
588 }
589 provs.push({
590 code: address[i].code,
591 address: address[i].address,
592 selected: address[i].address === value
593 });
594 }
595 data[code] = provs;
596 } else {
597 if (address === value) {
598 matched = {
599 code: code,
600 address: address
601 };
602 }
603 data.push({
604 code: code,
605 address: address,
606 selected: address === value
607 });
608 }
609 });
610 }
611
612 $select.html(type === PROVINCE ? this.getProvinceList(data) :
613 this.getList(data, type));
614 $select.data('item', matched);//当前tab添加item(包含选择对象的内容)
615 if(! (type === PROVINCE)){//标识:下一个选项没有数据则关闭
616 if(data.length==0){
617 return true;
618 }
619 }
620 },
621 //遍历省份
622 getProvinceList: function (data) {
623 var list = [],
624 $this = this,
625 simple = this.options.simple;
626
627 $.each(data, function (i, n) {
628 list.push('<dl class="clearfix">');
629 list.push('<dt>' + i + '</dt><dd>');
630 $.each(n, function (j, m) {
631 list.push(
632 '<a' +
633 ' title="' + (m.address || '') + '"' +
634 ' data-code="' + (m.code || '') + '"' +
635 ' class="' +
636 (m.selected ? ' active' : '') +
637 '">' +
638 ( simple ? $this.simplize(m.address, PROVINCE) : m.address) +
639 '</a>');
640 });
641 list.push('</dd></dl>');
642 });
643
644 return list.join('');
645 },
646 //遍历市或区或县
647 getList: function (data, type) {
648 var list = [],
649 $this = this,
650 simple = this.options.simple;
651 list.push('<dl class="clearfix"><dd>');
652
653 $.each(data, function (i, n) {
654 list.push(
655 '<a' +
656 ' title="' + (n.address || '') + '"' +
657 ' data-code="' + (n.code || '') + '"' +
658 ' class="' +
659 (n.selected ? ' active' : '') +
660 '">' +
661 ( simple ? $this.simplize(n.address, type) : n.address) +
662 '</a>');
663 });
664 list.push('</dd></dl>');
665
666 return list.join('');
667 },
668 //简化名字
669 simplize: function (address, type) {
670 address = address || '';
671 if (type === PROVINCE) {
672 return address.replace(/[省,市,自治区,壮族,回族,维吾尔]/g, '');
673 } else if (type === CITY) {
674 return address.replace(/[市,地区,回族,蒙古,苗族,白族,傣族,景颇族,藏族,彝族,壮族,傈僳族,布依族,侗族]/g, '')
675 .replace('哈萨克', '').replace('自治州', '').replace(/自治县/, '');
676 } else if (type === DISTRICT) {
677 return address.length > 2 ? address.replace(/[市,区,县,旗]/g, '') : address;
678 }
679 },
680 //处理tab显示
681 tab: function (type) {
682 var $selects = this.$dropdown.find('.city-select');
683 var $tabs = this.$dropdown.find('.city-select-tab > a');
684 var $select = this['$' + type];
685 var $tab = this.$dropdown.find('.city-select-tab > a[data-count="' + type + '"]');
686 if ($select) {
687 $selects.hide();
688 $select.show();
689 $tabs.removeClass('active');
690 $tab.addClass('active');
691 }
692 },
693
694 reset: function () {
695 this.$element.val(null).trigger('change');
696 },
697
698 destroy: function () {
699 this.unbind();
700 this.$element.removeData(NAMESPACE).removeClass('city-picker-input');
701 this.$textspan.remove();
702 this.$dropdown.remove();
703 },
704 //远程加载数据
705 remoteLoadData: function (cityType,cityId) {
706 var resultData = {};
707 if(PROVINCE===cityType)
708 {
709 return ChineseDistricts[cityId];
710 }
711 $.ajax({
712 url: "/directoryProcurement/jd/cityData/"+cityType+"/"+cityId,
713 type: "GET",
714 dataType: "json",
715 async: false,
716 contentType: "application/json",
717 success: function (result) {
718 if(result.code=="0")
719 {
720 resultData = result.data;
721 }else
722 {
723 console.log(result.desc);
724 }
725 }
726 });
727 return resultData;
728 }
729 };
730
731 CityPicker.DEFAULTS = {
732 simple: false,
733 responsive: false,
734 placeholder: '请选择省/市/区/镇',
735 level: 'county',
736 province: '',
737 city: '',
738 district: '',
739 county:''
740 };
741
742 CityPicker.setDefaults = function (options) {
743 $.extend(CityPicker.DEFAULTS, options);
744 };
745
746 // Save the other citypicker
747 CityPicker.other = $.fn.citypicker;
748
749 // Register as jQuery plugin
750 $.fn.citypicker = function (option) {
751 var args = [].slice.call(arguments, 1);
752
753 return this.each(function () {
754 var $this = $(this);
755 var data = $this.data(NAMESPACE);
756 var options;
757 var fn;
758
759 if (!data) {
760 if (/destroy/.test(option)) {
761 return;
762 }
763
764 options = $.extend({}, $this.data(), $.isPlainObject(option) && option);
765 $this.data(NAMESPACE, (data = new CityPicker(this, options)));
766 }
767
768 if (typeof option === 'string' && $.isFunction(fn = data[option])) {
769 fn.apply(data, args);
770 }
771 });
772 };
773
774 $.fn.citypicker.Constructor = CityPicker;
775 $.fn.citypicker.setDefaults = CityPicker.setDefaults;
776
777 // No conflict
778 $.fn.citypicker.noConflict = function () {
779 $.fn.citypicker = CityPicker.other;
780 return this;
781 };
782
783 $(function () {
784 $('[data-toggle="city-picker"]').citypicker();
785 });
786 });
上面的代码的第一个红色字体处,是为将省和直辖市这种一级地址默认加载过来,其他的三级地址都是靠远程加载过来,
第二处红色字体是为了解决,上面是为了解决一个bug,就当选择了,一级二级三级和四级地址后,如果再重新选择一级地址,后面的三级地址不会清空重选,这样会造成地址归属地不对的问题,这个bug在city-picker原生的代码中是没有的,应该是上面那个博客里,改成四级地址后才出现的bug。
第三处红色字体是为了判断是否有应该去进行远程加载数据,如果只选择了一级地址,就不去远程加载数据,反之则调用远程加载数据方法。
第四处红色字体,是关键了,实现远程加载数据的方法,这个方法是我自己后来加的,写成了同步请求。因为有些地址就没有三级地址,例如北京、丰台、三环到四环之间、这就是一个完整的选择地址了,所以会把一些没有四级地址的信息打印出来,如果有需要的可以修改这个远程加载数据的方法,来实现自己的需求。
最后忘记说了,我是将city-picker.js这个插件的city-picker.data.js和city-picker.js这两个文件合并成了一个文件,因为我只需要默认加载一级地址,一级地址的内容也不多,就写在了一起了,就是第一处红色字体处的js全局对象。
粗略的改造,接受批评。