原生Javascript做一个地址多级下拉筛选框

1,626 阅读6分钟

大家在浏览网页时,有没有遇到过下面这样的多级筛选框

1.gif

有没有想过,这里的逻辑判断是如何完成的?接下来,楼主就带领大家利用原生JS语法来完成这样一个多级筛选框,有路过的小伙伴记得留个赞哟!!!!

第一步,咱们先创建一个基础的HTML页面。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>地址多级下拉筛选框</title>
  </head>
  <body>
  </body>
</html>

第二步,咱们给HTML页面添加三个表单下拉框,放入body里。

<form action="">
<select name="province">
  <option value="" >--省--</option>
</select>
<select name="city">
  <option value="" >--市--</option>
</select>
<select name="county">
  <option value="" >--区/县--</option>
</select>
  <button type="reset">重置</button>
</form>

第三步,给页面加点样式,让这个下拉框居中显示。

<style>
  body {
    padding-top: 50px;
    text-align: center;
  }
</style>

此时一个酷炫的三级筛选框就出现啦!!!

image.png

当然,这是一个没有灵魂,无法和用户交互的筛选框,当我们点击时,并不会有任何东西出现。

image.png

接下来,我就给他注入灵魂,让它能跟随我们的选择来显示不同的内容。

第四步,创建一个script标签,并放入我们需要的数据,当然,因为全国城市太多,楼主这里就只选择了两个省和他们的前两个市及前两个区县,有兴趣的小伙伴可以对照地图将数据补全哈!

<script>
  const obj = {
    "河北省": {
      "石家庄市": {
        "长安区": {},
        "桥西区": {},
        "新华区": {},
        "井陉矿区": {},
        "裕华区": {},
        "藁城区": {},
        "鹿泉区": {},
        "栾城区": {},
        "井陉县": {},
        "正定县": {},
        "行唐县": {},
        "灵寿县": {},
        "高邑县": {},
        "深泽县": {},
        "赞皇县": {},
        "无极县": {},
        "平山县": {},
        "元氏县": {},
        "赵县": {},
        "辛集市": {},
        "晋州市": {},
        "新乐市": {},
      },
      "唐山市": {
        "路南区": {},
        "路北区": {},
        "古冶区": {},
        "开平区": {},
        "丰南区": {},
        "丰润区": {},
        "曹妃甸区": {},
        "滦县": {},
        "滦南县": {},
        "乐亭县": {},
        "迁西县": {},
        "玉田县": {},
        "遵化市": {},
        "迁安市": {},
      },
      "秦皇岛市": {},
      "邯郸市": {},
      "邢台市": {},
      "保定市": {},
      "张家口市": {},
      "承德市": {},
      "沧州市": {},
      "廊坊市": {},
      "衡水市": {},
    },
    "山西省": {
      "太原市": {
        "小店区": {},
        "迎泽区": {},
        "杏花岭区": {},
        "尖草坪区": {},
        "万柏林区": {},
        "晋源区": {},
        "清徐县": {},
        "阳曲县": {},
        "娄烦县": {},
        "古交市": {},
      },
      "大同市": {
        "新荣区": {},
        "平城区": {},
        "云冈区": {},
        "云州区": {},
        "阳高县": {},
        "天镇县": {},
        "广灵县": {},
        "灵丘县": {},
        "浑源县": {},
        "左云县": {},
      },
      "阳泉市": {},
      "长治市": {},
      "晋城市": {},
      "朔州市": {},
      "晋中市": {},
      "运城市": {},
      "忻州市": {},
      "临汾市": {},
      "吕梁市": {},
    },
    "北京市": {},
    "天津市": {},
    "内蒙古自治区": {},
    "辽宁省": {},
    "吉林省": {},
    "黑龙江省": {},
    "上海市": {},
    "江苏省": {},
    "浙江省": {},
    "安徽省": {},
    "福建省": {},
    "江西省": {},
    "山东省": {},
    "河南省": {},
    "湖北省": {},
    "湖南省": {},
    "广东省": {},
    "广西壮族自治区": {},
    "海南省": {},
    "重庆市": {},
    "四川省": {},
    "贵州省": {},
    "云南省": {},
    "西藏自治区": {},
    "陕西省": {},
    "甘肃省": {},
    "青海省": {},
    "宁夏回族自治区": {},
    "新疆维吾尔自治区": {},
    "台湾省": {},
    "香港特别行政区": {},
    "澳门特别行政区": {},
  }
</script>

第五步,咱们正式开始Javascript的逻辑部分,首先,在页面已进入,就应该加载出每个省的数据,供用户进行选择。那么就需要我们对上面对象进行遍历,取出obj对象的每一个属性名。

因为对象遍历的方法比较少,所以这里推荐使用Object.key()这个方法取出对象的所有属性值,这个方法会返回给我们一个数组,我们就可以直接对这个数组进行遍历。当然,如果说你非要直接遍历对象的话,也不是不可以,使用 for (Key in obj)就能够遍历对象,这里楼主采用第一种方法。每遍历一个数据我们就生成一个下拉框option。

渲染数据也有两种方式,第一种是dom树驱动,就是每遍历一次,就创建并往dom树中追加一个标签。第二种是数据驱动,创建一个空数组,每循环一次,就创建一个标签存入数组里,等遍历结束后,再一次性追加入dom树中,第二中方法因为对dom树操作次数少,可以提高页面加载速度及性能,所以这里选择第二种。 map()方法会自动创建并返回一个数组来存放我们遍历产生的新数组,所以这里使用map()方法来遍历数组,就不需要我们在去创建空数组了,只用我们定义一个常量来接收这个数组再把它加载进dom树里即可。

当然,我们不能直接把数组加载进,还需要join一下,将数组变成字符串才行。PS这里采用+=赋值是为了避免覆盖默认的下拉框,如果直接采用=赋值,那么页面一打开就会自动加载第一个省,用户体验会很差。当然,后面加载市、县的时候就可以直接赋值,当用户选择省后,市自动变为第一个数据,提醒用户可以选择市了。

综上所述:

// 1加载省数据
const province = Object.keys(obj).map((item) => `<option  value="${item}">${item}</option>`)
document.querySelector(`[name='province']`).innerHTML += province.join(``)

此时,页面一加载,我们便可以点击省这个下拉框来选择了。

image.png

第六步,我们就需要根据用户选择的省来加载对应的市了。这里,需要用到事件监听,监听用户改变省下拉框的事件,当change这个事件触发时,我们就立即获取用户选择的省,并立即遍历加载对应的市,综上:

// 2加载市,监听省change事件
document.querySelector(`[name='province']`).addEventListener('change', function () {
  // 2-1获取当前选择的省,这里的this指向函数的调用者[name='province']这个标签
  const province = this.value
const city = Object.keys(obj[province]).map((item) =>`<option  value="${item}">${item}</option>`)
    document.querySelector(`[name='city']`).innerHTML = city.join(``)
})

同理,加载区县也是在监听到市区发生变化,就立即获取数据渲染

// 3加载县
document.querySelector(`[name='city']`).addEventListener('change', function () {
  // 3-1获取当前选择的市
  const city = this.value
  3-2获取当前选择的省
  const province = document.querySelector(`[name='province']`).value
    // 3-2加载县
  const county = Object.keys(obj[province][city]).map((item) =>`<option  value="${item}">${item}</option>`)
  document.querySelector(`[name='county']`).innerHTML = county.join(``)
})

那么,写到这里,其实一个简单的小联动效果其实已经可以出来了

1.gif

但是!但是!但是!这也仅仅完成了基本的需求,因为但凡你多操作几步,你就会发现一堆bug在等着你,比如:

1.gif

当然,咱们不能干那种自己拉屎别人擦屁股的事对吧?会出现这种情况是因为我们漏了几个逻辑判断。当用户选择完后面市、区县的筛选框再反过来点击省时,将省改为默认的空状态时,我们要强制将后面的市、区也重置成默认状态,如何完成?我们只需在每次事件一触发,立即清空后面市、县的数据即可,我们将加载市、县的事件监听里加两行代码:

// 2加载市,监听省change事件
document.querySelector(`[name='province']`).addEventListener('change', function () {
  // 2-1获取当前选择的省
  const province = this.value
  
  
  //以下新增
  // 2-2任何时刻,当用户点击省时,立即强制市、县为空
  document.querySelector(`[name='city']`).innerHTML = `<option  value="">--市--</option>`
  document.querySelector(`[name='county']`).innerHTML = `<option  value="">--区/县--</option>`
  //以上新增
  
  
  const city = Object.keys(obj[province]).map((item) =>
      `<option  value="${item}">${item}</option>`)
  document.querySelector(`[name='city']`).innerHTML = city.join(``)
})
// 3加载县
document.querySelector(`[name='city']`).addEventListener('change', function () {
  // 3-1获取当前选择的市
  const city = this.value


  //以下新增
  // 3-2任何时刻,当用户点击市时,立即强制县为空
  document.querySelector(`[name='county']`).innerHTML = `<option  value="">--区/县--</option>`
  //以上新增
  
  
  const province = document.querySelector(`[name='province']`).value
  // 3-3加载县
  const county = Object.keys(obj[province][city]).map((item) =>
      `<option  value="${item}">${item}</option>`)
  document.querySelector(`[name='county']`).innerHTML = county.join(``)
})

以上,即可解决点击省,后面的市、区不重置的问题,如图

1.gif

最后,还有最后一个bug,就是我们一直忽略的那个button重置按钮,这个表单按钮自带重置属性,但是无法满足我们的需求,需要我们再给它添加一点事件,如图:

1.gif

市和县重置后显示有文字是因为我们在渲染时使用了=赋值覆盖了数据,将默认数据覆盖掉了,所以,默认数据显示第一个,当然就算将=号换成+=,也一样会有bug,如:

1.gif 正常我们重置表单后,在未点击省的时候,市和县应该是没有选项才对,但是这里却有显示选项,这是因为数据在我们最开始依次点击时,已经加载了进去,而表单的重置按钮,只会把选项切换到默认选项(就是带有selected属性的标签,如果都没带这个属性,那就显示第一个),而不是删除下拉选项列表,但是我们这里需要删除下拉列表,所以,我们只能手动重置一下了:

document.querySelector(`button`).addEventListener('click',function(){
  document.querySelector(`[name='city']`).innerHTML = `<option  value="">--市--</option>`
  document.querySelector(`[name='county']`).innerHTML = `<option  value="">--区/县--</option>`
  document.querySelector(`[name='county']`).innerHTML = `<option  value="">--区/县--</option>`
})

效果如下,这个简单的小demo就完成了!!!

1.gif

下面附上完整代码:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>省、市、区联动</title>
    <style>
      body {
        padding-top: 50px;
        text-align: center;
      }
    </style>
  </head>
  <body>
    <form action="">
      <select name="province">
        <option value="" >--省--</option>
      </select>
      <select name="city">
        <option value="">--市--</option>
      </select>
      <select name="county">
        <option value="">--区/县--</option>
      </select>
      <button type="reset">重置</button>
    </form>
    <script>
      const obj = {
        "河北省": {
          "石家庄市": {
            "长安区": {},
            "桥西区": {},
            "新华区": {},
            "井陉矿区": {},
            "裕华区": {},
            "藁城区": {},
            "鹿泉区": {},
            "栾城区": {},
            "井陉县": {},
            "正定县": {},
            "行唐县": {},
            "灵寿县": {},
            "高邑县": {},
            "深泽县": {},
            "赞皇县": {},
            "无极县": {},
            "平山县": {},
            "元氏县": {},
            "赵县": {},
            "辛集市": {},
            "晋州市": {},
            "新乐市": {},
          },
          "唐山市": {
            "路南区": {},
            "路北区": {},
            "古冶区": {},
            "开平区": {},
            "丰南区": {},
            "丰润区": {},
            "曹妃甸区": {},
            "滦县": {},
            "滦南县": {},
            "乐亭县": {},
            "迁西县": {},
            "玉田县": {},
            "遵化市": {},
            "迁安市": {},
          },
          "秦皇岛市": {},
          "邯郸市": {},
          "邢台市": {},
          "保定市": {},
          "张家口市": {},
          "承德市": {},
          "沧州市": {},
          "廊坊市": {},
          "衡水市": {},
        },
        "山西省": {
          "太原市": {
            "小店区": {},
            "迎泽区": {},
            "杏花岭区": {},
            "尖草坪区": {},
            "万柏林区": {},
            "晋源区": {},
            "清徐县": {},
            "阳曲县": {},
            "娄烦县": {},
            "古交市": {},
          },
          "大同市": {
            "新荣区": {},
            "平城区": {},
            "云冈区": {},
            "云州区": {},
            "阳高县": {},
            "天镇县": {},
            "广灵县": {},
            "灵丘县": {},
            "浑源县": {},
            "左云县": {},
          },
          "阳泉市": {},
          "长治市": {},
          "晋城市": {},
          "朔州市": {},
          "晋中市": {},
          "运城市": {},
          "忻州市": {},
          "临汾市": {},
          "吕梁市": {},
        },
        "北京市": {},
        "天津市": {},
        "内蒙古自治区": {},
        "辽宁省": {},
        "吉林省": {},
        "黑龙江省": {},
        "上海市": {},
        "江苏省": {},
        "浙江省": {},
        "安徽省": {},
        "福建省": {},
        "江西省": {},
        "山东省": {},
        "河南省": {},
        "湖北省": {},
        "湖南省": {},
        "广东省": {},
        "广西壮族自治区": {},
        "海南省": {},
        "重庆市": {},
        "四川省": {},
        "贵州省": {},
        "云南省": {},
        "西藏自治区": {},
        "陕西省": {},
        "甘肃省": {},
        "青海省": {},
        "宁夏回族自治区": {},
        "新疆维吾尔自治区": {},
        "台湾省": {},
        "香港特别行政区": {},
        "澳门特别行政区": {},
      }
      
      console.log(obj)
      for (const objKey in obj) {
        
      }
      
      
      // 1加载省数据
      const province = Object.keys(obj).map((item) => `<option  value="${item}">${item}</option>`)
      document.querySelector(`[name='province']`).innerHTML += province.join(``)
      
      
      // 2加载市,监听省change事件
      document.querySelector(`[name='province']`).addEventListener('change', function () {
        // 2-1获取当前选择的省
        const province = this.value
        
        
        //新增
        // 2-2任何时刻,当用户点击省时,强制市、县为空
        document.querySelector(`[name='city']`).innerHTML = `<option  value="">--市--</option>`
        document.querySelector(`[name='county']`).innerHTML = `<option  value="">--区/县--</option>`
        //新增
        
        
        const city = Object.keys(obj[province]).map((item) =>
            `<option  value="${item}">${item}</option>`)
        document.querySelector(`[name='city']`).innerHTML = city.join(``)
      })
      
      
      // 3加载县
      document.querySelector(`[name='city']`).addEventListener('change', function () {
        // 3-1获取当前选择的市
        const city = this.value
  
  
        //新增
        // 3-2任何时刻,当用户点击市时,强制县为空
        document.querySelector(`[name='county']`).innerHTML = `<option  value="">--区/县--</option>`
        //新增
        
        
        const province = document.querySelector(`[name='province']`).value
        // 3-3加载县
        const county = Object.keys(obj[province][city]).map((item) =>
            `<option  value="${item}">${item}</option>`)
        document.querySelector(`[name='county']`).innerHTML = county.join(``)
      })
      
      document.querySelector(`button`).addEventListener('click',function(){
        document.querySelector(`[name='city']`).innerHTML = `<option  value="">--市--</option>`
        document.querySelector(`[name='county']`).innerHTML = `<option  value="">--区/县--</option>`
        document.querySelector(`[name='county']`).innerHTML = `<option  value="">--区/县--</option>`
      })
    
    
    </script>
  </body>
</html>