├── .travis.yml ├── _example ├── js │ ├── index.js │ └── Address.js ├── example.png ├── demo │ ├── css │ │ └── demo.css │ ├── .babelrc │ ├── index.html │ ├── webpack.config.js │ ├── scripts │ │ ├── start.js │ │ └── build.js │ ├── package.json │ └── app.js ├── .babelrc ├── app.js ├── index.html ├── webpack.config.js ├── package.json ├── scripts │ └── build.js └── README.md ├── demo.png ├── .coveralls.yml ├── es ├── index.js ├── config_test │ └── mockStore.js ├── data │ ├── RawDataSort.js │ └── RawData.js ├── zipcode │ ├── District.js │ ├── County.js │ ├── ZipCode.js │ └── ZipCodeTW.js └── _test_ │ ├── District.test.js │ ├── County.test.js │ ├── ZipCode.test.js │ └── ZipCodeTW.test.js ├── .gitignore ├── .babelrc ├── index.html ├── webpack.config.js ├── LICENSE ├── .eslintrc.js ├── scripts ├── start.js └── build.js ├── app.js ├── package.json └── README.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | -------------------------------------------------------------------------------- /_example/js/index.js: -------------------------------------------------------------------------------- 1 | import Address from "./Address"; 2 | export {Address}; -------------------------------------------------------------------------------- /demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chris-Tsai/zipcode-tw-react/HEAD/demo.png -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-pro 2 | repo_token: MAKslNrlkcOQVdeH0UTe6JoDBPpHJEewL 3 | -------------------------------------------------------------------------------- /_example/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chris-Tsai/zipcode-tw-react/HEAD/_example/example.png -------------------------------------------------------------------------------- /_example/demo/css/demo.css: -------------------------------------------------------------------------------- 1 | .pull-right { 2 | float: right !important; 3 | vertical-align: bottom !important; 4 | } -------------------------------------------------------------------------------- /es/index.js: -------------------------------------------------------------------------------- 1 | import ZipCodeTW from "./zipcode/ZipCodeTW"; 2 | export {ZipCodeTW}; 3 | import ZipCode from "./zipcode/ZipCode"; 4 | export {ZipCode}; -------------------------------------------------------------------------------- /es/config_test/mockStore.js: -------------------------------------------------------------------------------- 1 | import configureMockStore from 'redux-mock-store'; 2 | 3 | const mockStore = (state) => { 4 | const mockStore = configureMockStore(); 5 | const store = mockStore(state); 6 | return store; 7 | }; 8 | 9 | export default mockStore; 10 | 11 | -------------------------------------------------------------------------------- /es/data/RawDataSort.js: -------------------------------------------------------------------------------- 1 | export default { 2 | "基隆市": 1, 3 | "台北市": 2, 4 | "新北市": 3, 5 | "桃園市": 4, 6 | "新竹市": 5, 7 | "新竹縣": 6, 8 | "苗栗縣": 7, 9 | "台中市": 8, 10 | "彰化縣": 9, 11 | "南投縣": 10, 12 | "雲林縣": 11, 13 | "嘉義市": 12, 14 | "嘉義縣": 13, 15 | "台南市": 14, 16 | "高雄市": 15, 17 | "屏東縣": 16, 18 | "台東縣": 17, 19 | "花蓮縣": 18, 20 | "宜蘭縣": 19, 21 | "澎湖縣": 20, 22 | "金門縣": 21, 23 | "連江縣": 22 24 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # production 7 | build 8 | dist 9 | lib 10 | 11 | # misc 12 | .DS_Store 13 | npm-debug.log 14 | js/bundle.js 15 | build_watch 16 | 17 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 18 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 19 | 20 | .idea 21 | *.iml -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "@babel/plugin-proposal-class-properties", 4 | "@babel/plugin-syntax-dynamic-import", 5 | "@babel/plugin-syntax-import-meta", 6 | "@babel/plugin-proposal-json-strings" 7 | ], 8 | "presets": [ 9 | [ 10 | "@babel/preset-env", 11 | { 12 | "targets": { 13 | "browsers": [ 14 | "chrome >= 52", 15 | "ie >= 10" 16 | ] 17 | } 18 | } 19 | ], 20 | "@babel/preset-react" 21 | ] 22 | } -------------------------------------------------------------------------------- /_example/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "@babel/plugin-proposal-class-properties", 4 | "@babel/plugin-syntax-dynamic-import", 5 | "@babel/plugin-syntax-import-meta", 6 | "@babel/plugin-proposal-json-strings" 7 | ], 8 | "presets": [ 9 | [ 10 | "@babel/preset-env", 11 | { 12 | "targets": { 13 | "browsers": [ 14 | "chrome >= 52", 15 | "ie >= 10" 16 | ] 17 | } 18 | } 19 | ], 20 | "@babel/preset-react" 21 | ] 22 | } -------------------------------------------------------------------------------- /_example/demo/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "@babel/plugin-proposal-class-properties", 4 | "@babel/plugin-syntax-dynamic-import", 5 | "@babel/plugin-syntax-import-meta", 6 | "@babel/plugin-proposal-json-strings" 7 | ], 8 | "presets": [ 9 | [ 10 | "@babel/preset-env", 11 | { 12 | "targets": { 13 | "browsers": [ 14 | "chrome >= 52", 15 | "ie >= 10" 16 | ] 17 | } 18 | } 19 | ], 20 | "@babel/preset-react" 21 | ] 22 | } -------------------------------------------------------------------------------- /_example/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Address from "./js/Address"; 4 | 5 | class ZipCodeTWTest extends React.Component { 6 | 7 | constructor() { 8 | super(); 9 | } 10 | 11 | render() { 12 | return ( 13 |
14 |

Example

15 |
16 |
17 | ); 18 | } 19 | } 20 | 21 | window.app = {}; 22 | 23 | app.create = (dom) => { 24 | ReactDOM.render( 25 | , 26 | dom 27 | ) 28 | }; -------------------------------------------------------------------------------- /_example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ZipcodeTW Example 7 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /_example/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = function (app = "app.js") { 4 | return { 5 | entry: { 6 | app: ['@babel/polyfill', path.resolve(__dirname, app)] 7 | }, 8 | output: { 9 | path: path.resolve(__dirname, 'build'), 10 | filename: 'zipcode-tw-react-example.js', 11 | chunkFilename: '[name].js' 12 | }, 13 | module: { 14 | loaders: [{ 15 | test: /\.(js|jsx)$/, 16 | exclude: [ 17 | path.resolve(__dirname, 'node_modules') 18 | ], 19 | loaders: ['babel-loader'] 20 | }] 21 | }, 22 | plugins: [], 23 | node: {} 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ZipCodeTW Demo 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /_example/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ZipCodeTW Demo 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = function (app = "app.js") { 4 | return { 5 | entry: { 6 | app: ['@babel/polyfill', path.resolve(__dirname, app)] 7 | }, 8 | output: { 9 | path: path.resolve(__dirname, 'build'), 10 | filename: 'zipcode-tw-react.js', 11 | chunkFilename: '[name].js' 12 | }, 13 | module: { 14 | loaders: [{ 15 | test: /\.(js|jsx)$/, 16 | exclude: [ 17 | path.resolve(__dirname, 'node_modules') 18 | ], 19 | loaders: ['babel-loader'] 20 | }, { 21 | test: /\.css$/, 22 | loader: "style-loader!css-loader" 23 | }] 24 | }, 25 | plugins: [], 26 | node: {} 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /_example/demo/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = function (app = "app.js") { 4 | return { 5 | entry: { 6 | app: ['@babel/polyfill', path.resolve(__dirname, app)] 7 | }, 8 | output: { 9 | path: path.resolve(__dirname, 'build'), 10 | filename: 'zipcode-tw-react.js', 11 | chunkFilename: '[name].js' 12 | }, 13 | module: { 14 | loaders: [{ 15 | test: /\.(js|jsx)$/, 16 | exclude: [ 17 | path.resolve(__dirname, 'node_modules') 18 | ], 19 | loaders: ['babel-loader'] 20 | }, { 21 | test: /\.css$/, 22 | loader: "style-loader!css-loader" 23 | }] 24 | }, 25 | plugins: [], 26 | node: {} 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Chris-Tsai 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:react/recommended", 10 | ], 11 | "parser": "babel-eslint", 12 | "parserOptions": { 13 | "ecmaFeatures": { 14 | "experimentalObjectRestSpread": true, 15 | "jsx": true 16 | }, 17 | "ecmaVersion": 2018, 18 | "sourceType": "module" 19 | }, 20 | "plugins": [ 21 | "react", 22 | ], 23 | "rules": { 24 | // "indent": [ 25 | // "error", 26 | // 2 27 | // ], 28 | "linebreak-style": [ 29 | "error", 30 | "windows" 31 | ], 32 | // "quotes": [ 33 | // "error", 34 | // "single" 35 | // ], 36 | // "semi": [ 37 | // "error", 38 | // "always" 39 | // ] 40 | "no-unused-vars": ["error", { 41 | "ignoreRestSiblings": true 42 | }], 43 | "no-console": "warn", 44 | "react/prop-types": [0, { 45 | ignore: ['history', 'dispatch'], 46 | }], 47 | // "react/no-deprecated": "warn", 48 | }, 49 | "settings": { 50 | "react": { 51 | "pragma": "React", // Pragma to use, default to "React" 52 | "version": "16.2", // React version, default to the latest React stable release 53 | }, 54 | }, 55 | }; -------------------------------------------------------------------------------- /scripts/start.js: -------------------------------------------------------------------------------- 1 | const webpack = require("webpack"); 2 | const WebpackDevServer = require("webpack-dev-server"); 3 | const config = require("../webpack.config"); 4 | const fs = require("fs"); 5 | const path = require("path"); 6 | 7 | const port = process.env.PORT || 3001; 8 | const host = process.env.HOST || 'localhost'; 9 | 10 | const cfg = config(); 11 | 12 | fs.unlink(path.resolve(cfg.output.path, cfg.output.filename), function() {}); 13 | 14 | cfg.entry.app.unshift( 15 | "webpack-dev-server/client?http://"+host+":"+port+"/", 16 | "webpack/hot/dev-server" 17 | ); 18 | 19 | cfg.plugins.unshift( 20 | new webpack.HotModuleReplacementPlugin() 21 | ); 22 | 23 | // console.log(JSON.stringify(cfg)); 24 | 25 | const compiler = webpack(cfg); 26 | 27 | // webpack-dev-server options: https://webpack.github.io/docs/webpack-dev-server.html#api 28 | const server = new WebpackDevServer(compiler, { 29 | hot: true, 30 | before: function(app) { 31 | // Here you can access the Express app object and add your own custom middleware to it. 32 | // For example, to define custom handlers for some paths: 33 | // app.get('/some/path', function(req, res) { 34 | // res.json({ custom: 'response' }); 35 | // }); 36 | }, 37 | quiet: true, 38 | publicPath: '/build/', 39 | stats: { colors: true } 40 | }); 41 | server.listen(port, host, function(err) { 42 | if (err) { 43 | throw new Error(err); 44 | } 45 | console.log('Listening on http://' + host + ':' + port); 46 | }); -------------------------------------------------------------------------------- /_example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zipcode-tw-react-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "lib/index.js", 6 | "jsnext:main": "es/index.js", 7 | "dependencies": { 8 | "prop-types": "^15.5.8", 9 | "react": "^16.6.0", 10 | "react-bootstrap": "^0.32.4", 11 | "react-dom": "^16.6.0", 12 | "sweetalert2": "^7.28.2", 13 | "zipcode-tw-react": "^1.1.3" 14 | }, 15 | "devDependencies": { 16 | "@babel/cli": "^7.0.0-beta.36", 17 | "babel-loader": "^8.0.2", 18 | "@babel/core": "^7.0.0-rc.1", 19 | "@babel/plugin-proposal-class-properties": "^7.0.0-rc.1", 20 | "@babel/plugin-proposal-json-strings": "^7.0.0-rc.1", 21 | "@babel/plugin-proposal-object-rest-spread": "^7.0.0-rc.1", 22 | "@babel/plugin-syntax-dynamic-import": "^7.0.0-rc.1", 23 | "@babel/plugin-syntax-import-meta": "^7.0.0-rc.1", 24 | "@babel/polyfill": "^7.0.0-rc.1", 25 | "@babel/preset-env": "^7.0.0-rc.1", 26 | "@babel/preset-flow": "^7.0.0-rc.1", 27 | "@babel/preset-react": "^7.0.0-rc.1", 28 | "babel-core": "^7.0.0-bridge.0", 29 | "fs-extra": "^5.0.0", 30 | "webpack": "^3.10.0", 31 | "webpack-body-parser": "^1.11.110", 32 | "webpack-encoding-plugin": "^0.2.0" 33 | }, 34 | "scripts": { 35 | "test": "echo \"Error: no test specified\" && exit 1", 36 | "build": "node ./scripts/build.js" 37 | }, 38 | "author": "Chris.Tsai", 39 | "license": "MIT", 40 | "homepage": "https://github.com/Chris-Tsai/zipcode-tw-react#readme" 41 | } 42 | -------------------------------------------------------------------------------- /_example/demo/scripts/start.js: -------------------------------------------------------------------------------- 1 | const webpack = require("webpack"); 2 | const WebpackDevServer = require("webpack-dev-server"); 3 | const config = require("../webpack.config"); 4 | const fs = require("fs"); 5 | const path = require("path"); 6 | 7 | const port = process.env.PORT || 3001; 8 | const host = process.env.HOST || 'localhost'; 9 | 10 | const cfg = config(); 11 | 12 | fs.unlink(path.resolve(cfg.output.path, cfg.output.filename), function () { 13 | }); 14 | 15 | cfg.entry.app.unshift( 16 | "webpack-dev-server/client?http://" + host + ":" + port + "/", 17 | "webpack/hot/dev-server" 18 | ); 19 | 20 | cfg.plugins.unshift( 21 | new webpack.HotModuleReplacementPlugin() 22 | ); 23 | 24 | // console.log(JSON.stringify(cfg)); 25 | 26 | const compiler = webpack(cfg); 27 | 28 | // webpack-dev-server options: https://webpack.github.io/docs/webpack-dev-server.html#api 29 | const server = new WebpackDevServer(compiler, { 30 | hot: true, 31 | before: function (app) { 32 | // Here you can access the Express app object and add your own custom middleware to it. 33 | // For example, to define custom handlers for some paths: 34 | // app.get('/some/path', function(req, res) { 35 | // res.json({ custom: 'response' }); 36 | // }); 37 | }, 38 | quiet: true, 39 | publicPath: '/build/', 40 | stats: {colors: true} 41 | }); 42 | server.listen(port, host, function (err) { 43 | if (err) { 44 | throw new Error(err); 45 | } 46 | console.log('Listening on http://' + host + ':' + port); 47 | }); -------------------------------------------------------------------------------- /_example/demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zipcode-tw-react-demo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "lib/index.js", 6 | "jsnext:main": "es/index.js", 7 | "dependencies": { 8 | "prop-types": "^15.5.8", 9 | "react": "^16.6.0", 10 | "react-bootstrap": "^0.32.4", 11 | "react-dom": "^16.6.0", 12 | "sweetalert2": "^7.28.2", 13 | "zipcode-tw-react": "^1.1.3" 14 | }, 15 | "devDependencies": { 16 | "@babel/cli": "^7.0.0-beta.36", 17 | "babel-loader": "^8.0.2", 18 | "@babel/core": "^7.0.0-rc.1", 19 | "@babel/plugin-proposal-class-properties": "^7.0.0-rc.1", 20 | "@babel/plugin-proposal-json-strings": "^7.0.0-rc.1", 21 | "@babel/plugin-proposal-object-rest-spread": "^7.0.0-rc.1", 22 | "@babel/plugin-syntax-dynamic-import": "^7.0.0-rc.1", 23 | "@babel/plugin-syntax-import-meta": "^7.0.0-rc.1", 24 | "@babel/polyfill": "^7.0.0-rc.1", 25 | "@babel/preset-env": "^7.0.0-rc.1", 26 | "@babel/preset-flow": "^7.0.0-rc.1", 27 | "@babel/preset-react": "^7.0.0-rc.1", 28 | "babel-core": "^7.0.0-bridge.0", 29 | "fs-extra": "^5.0.0", 30 | "webpack": "^3.10.0", 31 | "webpack-body-parser": "^1.11.110", 32 | "webpack-encoding-plugin": "^0.2.0" 33 | }, 34 | "scripts": { 35 | "test": "echo \"Error: no test specified\" && exit 1", 36 | "build": "node ./scripts/build.js", 37 | "start": "node ./scripts/start.js" 38 | }, 39 | "author": "Chris.Tsai", 40 | "license": "MIT", 41 | "homepage": "https://github.com/Chris-Tsai/zipcode-tw-react#readme" 42 | } 43 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | const webpack = require("webpack"); 2 | const config = require("../webpack.config"); 3 | const path = require('path'); 4 | const fs = require('fs-extra'); 5 | 6 | console.log('Creating an optimized build...'); 7 | 8 | const cfg = config(); 9 | cfg.output.path = path.resolve(__dirname, '../dist'); 10 | 11 | // delete build folder 12 | const deleteFolderRecursive = function(path) { 13 | if( fs.existsSync(path) ) { 14 | fs.readdirSync(path).forEach(function(file,index){ 15 | const curPath = path + "/" + file; 16 | if(fs.lstatSync(curPath).isDirectory()) { // recurse 17 | deleteFolderRecursive(curPath); 18 | } else { // delete file 19 | fs.unlinkSync(curPath); 20 | } 21 | }); 22 | // fs.rmdirSync(path); 23 | } 24 | }; 25 | 26 | deleteFolderRecursive(cfg.output.path); 27 | console.log('Successfully deleted dist folder'); 28 | 29 | webpack(cfg).run(function (err, stats) { 30 | if (err) { 31 | throw new Error(err); 32 | } 33 | const jsonStats = stats.toJson(); 34 | if (jsonStats.errors.length > 0) { 35 | throw new Error(jsonStats.errors); 36 | } 37 | console.log('Successfully compiled: ' + path.resolve(cfg.output.path, cfg.output.filename)); 38 | }); 39 | 40 | // begin compile uglify file 41 | const minCfg = config(); 42 | minCfg.output.path = path.resolve(__dirname, '../dist'); 43 | minCfg.output.filename = 'zipcode-tw-react.min.js'; 44 | 45 | minCfg.plugins.unshift( 46 | new webpack.optimize.UglifyJsPlugin(), 47 | new webpack.optimize.OccurrenceOrderPlugin(), 48 | new webpack.DefinePlugin({ 49 | "process.env": { 50 | NODE_ENV: JSON.stringify("production") 51 | } 52 | }) 53 | ); 54 | 55 | webpack(minCfg).run(function (err, stats) { 56 | if (err) { 57 | throw new Error(err); 58 | } 59 | const jsonStats = stats.toJson(); 60 | if (jsonStats.errors.length > 0) { 61 | throw new Error(jsonStats.errors); 62 | } 63 | console.log('Successfully compiled uglify: ' + path.resolve(minCfg.output.path, minCfg.output.filename)); 64 | }); -------------------------------------------------------------------------------- /_example/scripts/build.js: -------------------------------------------------------------------------------- 1 | const webpack = require("webpack"); 2 | const config = require("../webpack.config"); 3 | const path = require('path'); 4 | const fs = require('fs-extra'); 5 | 6 | console.log('Creating an optimized build...'); 7 | 8 | const cfg = config(); 9 | cfg.output.path = path.resolve(__dirname, '../dist'); 10 | 11 | // delete build folder 12 | const deleteFolderRecursive = function(path) { 13 | if( fs.existsSync(path) ) { 14 | fs.readdirSync(path).forEach(function(file,index){ 15 | const curPath = path + "/" + file; 16 | if(fs.lstatSync(curPath).isDirectory()) { // recurse 17 | deleteFolderRecursive(curPath); 18 | } else { // delete file 19 | fs.unlinkSync(curPath); 20 | } 21 | }); 22 | // fs.rmdirSync(path); 23 | } 24 | }; 25 | 26 | deleteFolderRecursive(cfg.output.path); 27 | console.log('Successfully deleted dist folder'); 28 | 29 | webpack(cfg).run(function (err, stats) { 30 | if (err) { 31 | throw new Error(err); 32 | } 33 | const jsonStats = stats.toJson(); 34 | if (jsonStats.errors.length > 0) { 35 | throw new Error(jsonStats.errors); 36 | } 37 | console.log('Successfully compiled: ' + path.resolve(cfg.output.path, cfg.output.filename)); 38 | }); 39 | 40 | // begin compile uglify file 41 | const minCfg = config(); 42 | minCfg.output.path = path.resolve(__dirname, '../dist'); 43 | minCfg.output.filename = 'zipcode-tw-react-example.min.js'; 44 | 45 | minCfg.plugins.unshift( 46 | new webpack.optimize.UglifyJsPlugin(), 47 | new webpack.optimize.OccurrenceOrderPlugin(), 48 | new webpack.DefinePlugin({ 49 | "process.env": { 50 | NODE_ENV: JSON.stringify("production") 51 | } 52 | }) 53 | ); 54 | 55 | webpack(minCfg).run(function (err, stats) { 56 | if (err) { 57 | throw new Error(err); 58 | } 59 | const jsonStats = stats.toJson(); 60 | if (jsonStats.errors.length > 0) { 61 | throw new Error(jsonStats.errors); 62 | } 63 | console.log('Successfully compiled uglify: ' + path.resolve(minCfg.output.path, minCfg.output.filename)); 64 | }); -------------------------------------------------------------------------------- /_example/demo/scripts/build.js: -------------------------------------------------------------------------------- 1 | const webpack = require("webpack"); 2 | const config = require("../webpack.config"); 3 | const path = require('path'); 4 | const fs = require('fs-extra'); 5 | 6 | console.log('Creating an optimized build...'); 7 | 8 | const cfg = config(); 9 | cfg.output.path = path.resolve(__dirname, '../dist'); 10 | 11 | // delete build folder 12 | const deleteFolderRecursive = function (path) { 13 | if (fs.existsSync(path)) { 14 | fs.readdirSync(path).forEach(function (file, index) { 15 | const curPath = path + "/" + file; 16 | if (fs.lstatSync(curPath).isDirectory()) { // recurse 17 | deleteFolderRecursive(curPath); 18 | } else { // delete file 19 | fs.unlinkSync(curPath); 20 | } 21 | }); 22 | // fs.rmdirSync(path); 23 | } 24 | }; 25 | 26 | deleteFolderRecursive(cfg.output.path); 27 | console.log('Successfully deleted dist folder'); 28 | 29 | webpack(cfg).run(function (err, stats) { 30 | if (err) { 31 | throw new Error(err); 32 | } 33 | const jsonStats = stats.toJson(); 34 | if (jsonStats.errors.length > 0) { 35 | throw new Error(jsonStats.errors); 36 | } 37 | console.log('Successfully compiled: ' + path.resolve(cfg.output.path, 38 | cfg.output.filename)); 39 | }); 40 | 41 | // begin compile uglify file 42 | const minCfg = config(); 43 | minCfg.output.path = path.resolve(__dirname, '../dist'); 44 | minCfg.output.filename = 'zipcode-tw-react.min.js'; 45 | 46 | minCfg.plugins.unshift( 47 | new webpack.optimize.UglifyJsPlugin(), 48 | new webpack.optimize.OccurrenceOrderPlugin(), 49 | new webpack.DefinePlugin({ 50 | "process.env": { 51 | NODE_ENV: JSON.stringify("production") 52 | } 53 | }) 54 | ); 55 | 56 | webpack(minCfg).run(function (err, stats) { 57 | if (err) { 58 | throw new Error(err); 59 | } 60 | const jsonStats = stats.toJson(); 61 | if (jsonStats.errors.length > 0) { 62 | throw new Error(jsonStats.errors); 63 | } 64 | console.log( 65 | 'Successfully compiled uglify: ' + path.resolve(minCfg.output.path, 66 | minCfg.output.filename)); 67 | }); -------------------------------------------------------------------------------- /es/zipcode/District.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | /** 5 | * 行政區 6 | * 7 | * @author Chris Tsai 8 | */ 9 | export default class District extends React.Component { 10 | 11 | constructor(props) { 12 | super(props); 13 | } 14 | 15 | handleChange = (e) =>{ 16 | let value = e.target.value; 17 | let {onChange} = this.props; 18 | if(typeof (onChange) == 'function'){ 19 | onChange(value); 20 | } 21 | } 22 | 23 | render() { 24 | const {dataOptions, fieldName, districtClass, districtStyle, value, displayType, 25 | } = this.props; 26 | 27 | const districts = !!dataOptions && dataOptions.map((op) => 28 | ); 29 | return ( 30 | <> 31 | {!!displayType && displayType === 'text' ? 32 | 40 | : 41 | <> 42 | {value} 47 | 48 | 49 | } 50 | 51 | ); 52 | } 53 | } 54 | 55 | District.propTypes = { 56 | 57 | /** 58 | * 顯示樣式 59 | */ 60 | displayType: PropTypes.oneOf(['text', 'display']), 61 | /** 62 | * 欄位名稱 63 | */ 64 | fieldName: PropTypes.string.isRequired, 65 | 66 | /** 67 | * 欄位值 68 | */ 69 | value: PropTypes.string, 70 | 71 | /** 72 | * onChange callback function 73 | */ 74 | onChange: PropTypes.func, 75 | 76 | /** 77 | * dataOptions 78 | */ 79 | dataOptions: PropTypes.arrayOf(PropTypes.string), 80 | 81 | /** 82 | * class 83 | */ 84 | districtClass: PropTypes.oneOfType([PropTypes.string, PropTypes.array, PropTypes.object]), 85 | 86 | /** 87 | * style 88 | */ 89 | districtStyle: PropTypes.oneOfType([PropTypes.string, PropTypes.array, PropTypes.object]), 90 | }; -------------------------------------------------------------------------------- /es/zipcode/County.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | /** 5 | * 縣市 6 | * 7 | * @author Chris Tsai 8 | */ 9 | export default class County extends React.Component { 10 | 11 | constructor(props) { 12 | super(props); 13 | } 14 | 15 | handleChange = (e) =>{ 16 | let value = e.target.value; 17 | let {onChange} = this.props; 18 | if(typeof (onChange) == 'function'){ 19 | onChange(value); 20 | } 21 | } 22 | 23 | render() { 24 | const { 25 | dataOptions, fieldName, countyClass, countyStyle, value, displayType 26 | } = this.props; 27 | 28 | const counties = !!dataOptions && dataOptions.map((op) => 29 | ); 30 | return ( 31 | <> 32 | {!!displayType && displayType === 'text' ? 33 | 41 | : 42 | <> 43 | {value} 48 | 49 | 50 | } 51 | 52 | ); 53 | } 54 | } 55 | 56 | County.propTypes = { 57 | /** 58 | * 顯示樣式 59 | */ 60 | displayType: PropTypes.oneOf(['text', 'display']), 61 | /** 62 | * 欄位名稱 63 | */ 64 | fieldName: PropTypes.string.isRequired, 65 | 66 | /** 67 | * 欄位值 68 | */ 69 | value: PropTypes.string, 70 | 71 | /** 72 | * onChange callback function 73 | */ 74 | onChange: PropTypes.func, 75 | 76 | /** 77 | * dataOptions 78 | */ 79 | dataOptions: PropTypes.arrayOf(PropTypes.string), 80 | 81 | /** 82 | * class 83 | */ 84 | countyClass: PropTypes.oneOfType( 85 | [PropTypes.string, PropTypes.array, PropTypes.object]), 86 | 87 | /** 88 | * style 89 | */ 90 | countyStyle: PropTypes.oneOfType( 91 | [PropTypes.string, PropTypes.array, PropTypes.object]), 92 | }; -------------------------------------------------------------------------------- /es/zipcode/ZipCode.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | /** 5 | * 郵遞區號 6 | * 7 | * @author Chris Tsai 8 | */ 9 | export default class ZipCode extends React.Component { 10 | 11 | constructor(props) { 12 | super(props); 13 | } 14 | 15 | handleChange = (e) =>{ 16 | let value = e.target.value; 17 | let {onChange} = this.props; 18 | if((value == '' || /^\d+$/.test(value)) && value.length <= 3 19 | && typeof (onChange) == 'function'){ 20 | onChange(value); 21 | } 22 | } 23 | 24 | handleBlur = (e) =>{ 25 | let value = e.target.value; 26 | let {onBlur} = this.props; 27 | if(typeof (onBlur) == 'function'){ 28 | onBlur(value); 29 | } 30 | } 31 | 32 | render() { 33 | const {fieldName, zipClass, zipStyle, value, displayType, placeholder 34 | } = this.props; 35 | 36 | const nowStyle = typeof(zipStyle) == 'undefined' ? {width:'40px'} : zipStyle; 37 | return ( 38 | <> 39 | {!!displayType && displayType === 'text' ? 40 | 50 | : 51 | <> 52 | {value} 57 | 58 | 59 | } 60 | 61 | ); 62 | } 63 | } 64 | 65 | ZipCode.propTypes = { 66 | /** 67 | * 顯示樣式 68 | */ 69 | displayType: PropTypes.oneOf(['text', 'display']), 70 | /** 71 | * 欄位名稱 72 | */ 73 | fieldName: PropTypes.string.isRequired, 74 | 75 | /** 76 | * 欄位描述 77 | */ 78 | placeholder: PropTypes.string, 79 | 80 | /** 81 | * 欄位值 82 | */ 83 | value: PropTypes.string, 84 | 85 | /** 86 | * onChange callback function 87 | */ 88 | onChange: PropTypes.func, 89 | 90 | /** 91 | * onBlur callback function 92 | */ 93 | onBlur: PropTypes.func, 94 | 95 | /** 96 | * class 97 | */ 98 | zipClass: PropTypes.oneOfType([PropTypes.string, PropTypes.array, PropTypes.object]), 99 | 100 | /** 101 | * style 102 | */ 103 | zipStyle: PropTypes.oneOfType([PropTypes.string, PropTypes.array, PropTypes.object]), 104 | }; -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import ZipCodeTW from "./es/zipcode/ZipCodeTW"; 4 | 5 | class ZipCodeTWTest extends React.Component { 6 | 7 | constructor() { 8 | super(); 9 | this.state = { 10 | county: '', 11 | district: '', 12 | zipCode: '', 13 | } 14 | } 15 | 16 | // 變更地址資訊 17 | handleZipCodeChange = (e) =>{ 18 | const {countyFieldName, countyValue, districtFieldName, districtValue, zipFieldName, zipValue} = e; 19 | this.setState({ 20 | [zipFieldName]: zipValue, 21 | [countyFieldName]: countyValue, 22 | [districtFieldName]: districtValue, 23 | }); 24 | } 25 | 26 | render() { 27 | let countySort = { 28 | "基隆市": 2, 29 | "台北市": 1, 30 | "新北市": 3, 31 | "桃園市": 4, 32 | "新竹市": 5, 33 | "新竹縣": 6, 34 | "苗栗縣": 7, 35 | "台中市": 8, 36 | "彰化縣": 9, 37 | "南投縣": 10, 38 | "雲林縣": 11, 39 | "嘉義市": 12, 40 | "嘉義縣": 13, 41 | "台南市": 14, 42 | "高雄市": 15, 43 | "屏東縣": 16, 44 | "台東縣": 17, 45 | "花蓮縣": 18, 46 | "宜蘭縣": 19, 47 | "澎湖縣": 20, 48 | "金門縣": 21, 49 | "連江縣": 22 50 | }; 51 | return ( 52 |
53 |

Example

54 |
59 | 81 |
82 |
83 | ); 84 | } 85 | } 86 | 87 | window.app = {}; 88 | 89 | app.create = (dom) => { 90 | ReactDOM.render( 91 | , 92 | dom 93 | ) 94 | }; -------------------------------------------------------------------------------- /es/_test_/District.test.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import District from "../zipcode/District"; 3 | import Enzyme, {mount} from 'enzyme'; 4 | import Adapter from 'enzyme-adapter-react-16'; 5 | import mockStore from "../config_test/mockStore"; 6 | 7 | Enzyme.configure({adapter: new Adapter()}); 8 | 9 | function setup(fieldName, value, className, displayType, mockFn) { 10 | const store = mockStore({ 11 | district: { 12 | fieldName: fieldName, 13 | value: value, 14 | districtClass: className, 15 | displayType: displayType, 16 | onChange: mockFn, 17 | dataOptions: ['北區','東區','西區','南區'] 18 | } 19 | }); 20 | const state = store.getState(); 21 | const props = Object.assign({}, state.district); 22 | const wrapper = mount(); 23 | 24 | return { 25 | props, 26 | wrapper 27 | } 28 | } 29 | 30 | describe('District test', () => { 31 | it('test displayType= text', () => { 32 | const onChangeMock = jest.fn(); 33 | const fieldName = 'district'; 34 | const value = '東區'; 35 | const changeValue = '西區'; 36 | const displayType = 'text'; 37 | const className = 'form-control'; 38 | const {wrapper} = setup(fieldName, value, className, displayType, onChangeMock); 39 | expect(wrapper.find('select').props().value).toEqual(value); 40 | expect(wrapper.find('select').props().name).toEqual(fieldName); 41 | expect(wrapper.find('select').hasClass('form-control')).toBe(true); 42 | expect(wrapper.find('span').exists()).toBe(false); 43 | wrapper.find('select').simulate('change', {target: {value: changeValue} }); 44 | expect(onChangeMock).toBeCalledWith(changeValue); 45 | }); 46 | 47 | it('test displayType= text with no function', () => { 48 | const onChangeMock = undefined; 49 | const fieldName = 'district'; 50 | const value = '東區'; 51 | const changeValue = '西區'; 52 | const displayType = 'text'; 53 | const className = 'form-control'; 54 | const {wrapper} = setup(fieldName, value, className, displayType, onChangeMock); 55 | expect(wrapper.find('select').props().value).toEqual(value); 56 | expect(wrapper.find('select').props().name).toEqual(fieldName); 57 | expect(wrapper.find('select').hasClass('form-control')).toBe(true); 58 | expect(wrapper.find('span').exists()).toBe(false); 59 | wrapper.find('select').simulate('change', {target: {value: changeValue} }); 60 | }); 61 | 62 | it('test displayType= display', () => { 63 | const fieldName = 'district'; 64 | const value = '東區'; 65 | const displayType = 'display'; 66 | const className = 'form-control'; 67 | const {wrapper} = setup(fieldName, value, className, displayType); 68 | expect(wrapper.find('select').exists()).toBe(false); 69 | expect(wrapper.find('span').exists()).toBe(true); 70 | }); 71 | }); -------------------------------------------------------------------------------- /es/_test_/County.test.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import County from "../zipcode/County"; 3 | import Enzyme, {shallow, mount} from 'enzyme'; 4 | import Adapter from 'enzyme-adapter-react-16'; 5 | import mockStore from "../config_test/mockStore"; 6 | 7 | Enzyme.configure({adapter: new Adapter()}); 8 | 9 | function setup(fieldName, value, className, displayType, mockFn) { 10 | const store = mockStore({ 11 | county: { 12 | fieldName: fieldName, 13 | value: value, 14 | countyClass: className, 15 | displayType: displayType, 16 | onChange: mockFn, 17 | dataOptions: ['台北市','新北市','桃園市','台中市'] 18 | } 19 | }); 20 | const state = store.getState(); 21 | const props = Object.assign({}, state.county); 22 | const wrapper = mount(); 23 | 24 | return { 25 | props, 26 | wrapper 27 | } 28 | } 29 | 30 | describe('County test', () => { 31 | it('test displayType= text', () => { 32 | const onChangeMock = jest.fn(); 33 | const countyFieldName = 'zipName'; 34 | const countyValue = '台北市'; 35 | const changeCountyValue = '新北市'; 36 | const displayType = 'text'; 37 | const countyClass = 'form-control'; 38 | const {wrapper} = setup(countyFieldName, countyValue, countyClass, displayType, onChangeMock); 39 | expect(wrapper.find('select').props().value).toEqual(countyValue); 40 | expect(wrapper.find('select').props().name).toEqual(countyFieldName); 41 | expect(wrapper.find('select').hasClass('form-control')).toBe(true); 42 | expect(wrapper.find('span').exists()).toBe(false); 43 | wrapper.find('select').simulate('change', {target: {value: changeCountyValue} }); 44 | expect(onChangeMock).toBeCalledWith(changeCountyValue); 45 | }); 46 | 47 | it('test displayType= text with no function', () => { 48 | const onChangeMock = undefined; 49 | const countyFieldName = 'zipName'; 50 | const countyValue = '台北市'; 51 | const changeCountyValue = '新北市'; 52 | const displayType = 'text'; 53 | const countyClass = 'form-control'; 54 | const {wrapper} = setup(countyFieldName, countyValue, countyClass, displayType, onChangeMock); 55 | expect(wrapper.find('select').props().value).toEqual(countyValue); 56 | expect(wrapper.find('select').props().name).toEqual(countyFieldName); 57 | expect(wrapper.find('select').hasClass('form-control')).toBe(true); 58 | expect(wrapper.find('span').exists()).toBe(false); 59 | wrapper.find('select').simulate('change', {target: {value: changeCountyValue} }); 60 | }); 61 | 62 | it('test displayType= display', () => { 63 | const countyFieldName = 'zipName'; 64 | const countyValue = '台北市'; 65 | const displayType = 'display'; 66 | const countyClass = 'form-control'; 67 | const {wrapper} = setup(countyFieldName, countyValue, countyClass, displayType); 68 | expect(wrapper.find('select').exists()).toBe(false); 69 | expect(wrapper.find('span').exists()).toBe(true); 70 | }); 71 | }); -------------------------------------------------------------------------------- /_example/README.md: -------------------------------------------------------------------------------- 1 | # zipcode-tw-react-example 2 | 3 | ## Installation 4 | 5 | In zipcode-tw-react 6 | ```bash 7 | $ cd _example 8 | $ npm install 9 | $ npm run build 10 | ``` 11 | 12 | Open `_example/index.html` and you will see 13 | 14 | ![pic](example.png) 15 | 16 | --- 17 | ## Useage 18 | 19 | 程式參考: [Address.js](https://github.com/Chris-Tsai/zipcode-tw-react/blob/master/_example/js/Address.js "Address.js") 20 | 21 | #### ZipCodeTW with address 22 | ```javascript 23 |
24 | 34 | 35 | 41 |
42 | ``` 43 | 44 | #### Use zipCodePositionLast: false 45 | ```javascript 46 |
47 | 58 |
59 | ``` 60 | 61 | 62 | #### No use address 63 | ```javascript 64 |
65 | 70 |
71 | ``` 72 | 73 | #### Use address 74 | ```javascript 75 |
76 | 82 |
83 | ``` 84 | #### Use fullAddress 85 | ```javascript 86 |
87 | 93 |
94 | ``` 95 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zipcode-tw-react", 3 | "version": "1.2.0", 4 | "description": "", 5 | "main": "lib/index.js", 6 | "jsnext:main": "es/index.js", 7 | "dependencies": { 8 | "prop-types": "^15.5.8", 9 | "react": "^16.6.0", 10 | "react-dom": "^16.6.0" 11 | }, 12 | "devDependencies": { 13 | "@babel/cli": "^7.0.0-beta.36", 14 | "babel-loader": "^8.0.2", 15 | "@babel/core": "^7.0.0-rc.1", 16 | "@babel/plugin-proposal-class-properties": "^7.0.0-rc.1", 17 | "@babel/plugin-proposal-json-strings": "^7.0.0-rc.1", 18 | "@babel/plugin-proposal-object-rest-spread": "^7.0.0-rc.1", 19 | "@babel/plugin-syntax-dynamic-import": "^7.0.0-rc.1", 20 | "@babel/plugin-syntax-import-meta": "^7.0.0-rc.1", 21 | "@babel/polyfill": "^7.0.0-rc.1", 22 | "@babel/preset-env": "^7.0.0-rc.1", 23 | "@babel/preset-flow": "^7.0.0-rc.1", 24 | "@babel/preset-react": "^7.0.0-rc.1", 25 | "babel-core": "^7.0.0-bridge.0", 26 | "fs-extra": "^5.0.0", 27 | "webpack": "^3.10.0", 28 | "webpack-body-parser": "^1.11.110", 29 | "webpack-dev-server": "^2.10.0", 30 | "webpack-encoding-plugin": "^0.2.0", 31 | "eslint": "^5.8.0", 32 | "eslint-plugin-react": "^7.11.1", 33 | "babel-eslint": "^10.0.1", 34 | "jest": "^23.6.0", 35 | "babel-jest": "^23.6.0", 36 | "react-test-renderer": "^16.6.0", 37 | "css-loader": "^0.28.11", 38 | "style-loader": "^0.19.1", 39 | "check-node-version": "^3.2.0", 40 | "enzyme": "^3.7.0", 41 | "enzyme-adapter-react-16": "^1.6.0", 42 | "redux-mock-store": "^1.5.3", 43 | "redux": "^3.6.0", 44 | "coveralls": "^3.0.2" 45 | }, 46 | "scripts": { 47 | "lint": "check-node-version --package && eslint ./es/zipcode/*.js", 48 | "test": "check-node-version --package && jest --coverage --coverageReporters=text-lcov | coveralls", 49 | "build": "check-node-version --package && node ./scripts/build.js", 50 | "start": "check-node-version --package && node ./scripts/start.js", 51 | "compile": "./node_modules/.bin/babel es -d lib --ignore es/_test_/**,es/config_test/**", 52 | "release": "./node_modules/.bin/babel es -d lib --ignore es/_test_/**,es/config_test/** && npm publish --access=public" 53 | }, 54 | "jest": { 55 | "collectCoverageFrom": [ 56 | "es/**/*.js", 57 | "!**/__tests__/**", 58 | "!es/config_test/**", 59 | "!es/data/**", 60 | "!es/index.js" 61 | ], 62 | "moduleNameMapper": { 63 | "\\.(css|less)$": "identity-obj-proxy" 64 | } 65 | }, 66 | "engines": { 67 | "node": ">=6.2.0", 68 | "npm": ">=5.4.2" 69 | }, 70 | "repository": { 71 | "type": "git", 72 | "url": "git+https://github.com/Chris-Tsai/zipcode-tw-react.git" 73 | }, 74 | "keywords": [ 75 | "react", 76 | "zipcode", 77 | "taiwan", 78 | "tw", 79 | "郵遞區號", 80 | "台灣" 81 | ], 82 | "author": "Chris.Tsai", 83 | "license": "MIT", 84 | "bugs": { 85 | "url": "https://github.com/Chris-Tsai/zipcode-tw-react/issues" 86 | }, 87 | "homepage": "https://github.com/Chris-Tsai/zipcode-tw-react#readme" 88 | } 89 | -------------------------------------------------------------------------------- /es/_test_/ZipCode.test.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import ZipCode from "../zipcode/ZipCode"; 3 | import Enzyme, {mount} from 'enzyme'; 4 | import Adapter from 'enzyme-adapter-react-16'; 5 | import mockStore from "../config_test/mockStore"; 6 | 7 | Enzyme.configure({adapter: new Adapter()}); 8 | 9 | function setup(fieldName, value, className, displayType, mockFn, mockBlur) { 10 | const store = mockStore({ 11 | zipCode: { 12 | fieldName: fieldName, 13 | value: value, 14 | zipClass: className, 15 | displayType: displayType, 16 | onChange: mockFn, 17 | onBlur: mockBlur 18 | } 19 | }); 20 | const state = store.getState(); 21 | const props = Object.assign({}, state.zipCode); 22 | const wrapper = mount(); 23 | 24 | return { 25 | props, 26 | wrapper 27 | } 28 | } 29 | 30 | describe('ZipCode test', () => { 31 | it('test displayType= text', () => { 32 | const onChangeMock = jest.fn(); 33 | const onBlurMock = jest.fn(); 34 | const fieldName = 'zipCode'; 35 | const value = '100'; 36 | const changeValue = '111'; 37 | const displayType = 'text'; 38 | const className = 'form-control'; 39 | const {wrapper} = setup(fieldName, value, className, displayType, onChangeMock, onBlurMock); 40 | expect(wrapper.find('input').props().value).toEqual(value); 41 | expect(wrapper.find('input').props().name).toEqual(fieldName); 42 | expect(wrapper.find('input').hasClass('form-control')).toBe(true); 43 | expect(wrapper.find('span').exists()).toBe(false); 44 | wrapper.find('input').simulate('change', {target: {value: changeValue} }); 45 | expect(onChangeMock).toBeCalledWith(changeValue); 46 | 47 | wrapper.find('input').simulate('blur', {target: {value: changeValue} }); 48 | expect(onBlurMock).toBeCalledWith(changeValue); 49 | }); 50 | 51 | it('test displayType= text with no function', () => { 52 | const onChangeMock = undefined; 53 | const onBlurMock = undefined; 54 | const fieldName = 'zipCode'; 55 | const value = '100'; 56 | const changeValue = '111'; 57 | const displayType = 'text'; 58 | const className = 'form-control'; 59 | const {wrapper} = setup(fieldName, value, className, displayType, onChangeMock, onBlurMock); 60 | expect(wrapper.find('input').props().value).toEqual(value); 61 | expect(wrapper.find('input').props().name).toEqual(fieldName); 62 | expect(wrapper.find('input').hasClass('form-control')).toBe(true); 63 | expect(wrapper.find('span').exists()).toBe(false); 64 | wrapper.find('input').simulate('change', {target: {value: changeValue} }); 65 | 66 | wrapper.find('input').simulate('blur', {target: {value: changeValue} }); 67 | }); 68 | 69 | it('test displayType= display', () => { 70 | const fieldName = 'zipCode'; 71 | const value = '100'; 72 | const displayType = 'display'; 73 | const className = 'form-control'; 74 | const {wrapper} = setup(fieldName, value, className, displayType); 75 | expect(wrapper.find('input[type=\'text\']').exists()).toBe(false); 76 | expect(wrapper.find('span').exists()).toBe(true); 77 | }); 78 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zipcode-tw-react 2 | 提供台灣縣市、行政區下拉選單以及郵遞區號輸入欄位組合的React Component 3 | 藉由RawData快速進行郵遞區號切換,並提供地址合併顯示。 4 | 5 | [![travis-ci Status](https://travis-ci.org/Chris-Tsai/zipcode-tw-react.svg?branch=master)](https://travis-ci.org/Chris-Tsai/zipcode-tw-react.svg?branch=master) 6 | [![Coverage Status](https://coveralls.io/repos/github/Chris-Tsai/zipcode-tw-react/badge.svg?branch=master&service=github)](https://coveralls.io/github/Chris-Tsai/zipcode-tw-react?branch=master) 7 | [![License](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/Chris-Tsai/zipcode-tw-react/blob/master/LICENSE) 8 | 9 | 10 | ## Feature 11 | - 挑選台灣縣市、行政區下拉選單,快速帶出郵遞區號。 12 | - 提供可自定義台灣縣市下拉選單排序(countySort)。 13 | - 輸入郵遞區號,快速帶出台灣縣市、行政區。 14 | - 提供完整地址(fullAddress)或路段地址(address)欄位傳入,合併顯示郵遞區號及地址。 15 | - 可自定義下拉選單、輸入欄位、地址顯示欄位的CSS, Style,達到畫面的一致性。 16 | 17 | 18 | ## Demo 19 | Try it online: https://chris-tsai.github.io/ 20 | 21 | ![pic](demo.png) 22 | 23 | ## Installation 24 | 25 | ```bash 26 | npm install zipcode-tw-react --save 27 | ``` 28 | or use package.json 29 | 30 | ```bash 31 | "dependencies": { 32 | ... 33 | + "zipcode-tw-react": "^1.2.0", 34 | }, 35 | ``` 36 | 37 | ## Usage 38 | 39 | ```javascript 40 | import {ZipCodeTW} from "zipcode-tw-react"; 41 | 42 | 45 | ``` 46 | Example : [zipcode-tw-react-example](https://github.com/Chris-Tsai/zipcode-tw-react/tree/master/_example) 47 | 48 | ## Props 49 | 50 | ###### Field 51 | 52 | Name | Type | Default | Description 53 | :--- | :--- | :--- | :--- 54 | displayType| one of: 'text', 'display' | 'text' | displayType= display
1. 以span顯示且包含readOnly & disabled屬性
2. 提供fullAddress、address參數合併顯示郵遞區號及地址 55 | countySort| object | {"基隆市": 1, "台北市":2, "新北市":3,
"桃園市":4, "新竹市":5, "新竹縣":6,
"苗栗縣":7, "台中市":8, "彰化縣":9,
"南投縣":10,"雲林縣":11, "嘉義市":12,
"嘉義縣":13, "台南市":14, "高雄市":15,
"屏東縣":16, "台東縣":17, "花蓮縣":18,
"宜蘭縣":19, "澎湖縣":20,"金門縣":21,
"連江縣":22}| 56 | zipCodePositionLast| bool | true| Decide zipCode input text position,
when displayType= display, position is fixed 57 | countyFieldName | string |'county' | 58 | countyValue | string | | 59 | districtFieldName | string |'district' | 60 | districtValue | string | | 61 | zipCodeFieldName | string |'zipCode' | 62 | zipCodeValue | string | | 63 | countyClass | string |'form-control' | 64 | countyStyle | object | {} | 65 | districtClass | string |'form-control' | 66 | districtStyle | object | displayType= 'text'
預設為 {marginLeft:'5px', minWidth:'107px', paddingRight:'0px'} | 67 | zipClass | string | 'form-control'| 68 | zipStyle | object | displayType= 'text'
預設為 {marginLeft:'5px', width: '50px'}| 69 | zipCodePlaceholder | string | | 70 | fullAddress | string | | 完整地址(優化顯示) 71 | address | string | | 路段地址資訊(優化顯示) 72 | addressClass | string | 'form-control'| 73 | addressStyle | object | {} | 74 | 75 | ###### Method 76 | 77 | Name | Return | Description 78 | :--- | :--- | :--- 79 | handleChangeCounty | { countyFieldName, countyValue,
districtFieldName, districtValue,
zipFieldName, zipValue } 80 | handleChangeDistrict | { countyFieldName, countyValue,
districtFieldName, districtValue,
zipFieldName, zipValue } 81 | handleChangeZipCode | { zipFieldName, zipValue } 82 | handleBlurZipCode | { countyFieldName, countyValue,
districtFieldName, districtValue,
zipFieldName, zipValue } 83 | handleZipCodeNotExists | { countyFieldName, countyValue,
districtFieldName, districtValue,
zipFieldName, zipValue, origZipCode } 84 | 85 | 89 | -------------------------------------------------------------------------------- /_example/js/Address.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import swal from "sweetalert2"; 3 | import {ZipCodeTW} from "zipcode-tw-react" 4 | 5 | class Address extends React.Component { 6 | 7 | constructor(props) { 8 | super(props); 9 | 10 | this.state = { 11 | displayType: 'text', 12 | county: '台北市', 13 | district: '中山區', 14 | zipCode: '104', 15 | address: '敬業三路20號' 16 | } 17 | } 18 | 19 | handleChange = (e) =>{ 20 | this.setState({[e.target.name]: e.target.value}); 21 | } 22 | 23 | // 變更地址資訊 24 | handleZipCodeChange = (e) =>{ 25 | const {countyFieldName, countyValue, districtFieldName, districtValue, zipFieldName, zipValue} = e; 26 | this.setState({ 27 | [zipFieldName]: zipValue, 28 | [countyFieldName]: countyValue, 29 | [districtFieldName]: districtValue, 30 | }); 31 | } 32 | 33 | // 處理郵遞區號不存在 34 | handleZipCodeNotExists = (e) =>{ 35 | const {countyFieldName, countyValue, districtFieldName, districtValue, zipFieldName, zipValue, origZipValue} = e; 36 | this.setState({ 37 | [zipFieldName]: zipValue, 38 | [countyFieldName]: countyValue, 39 | [districtFieldName]: districtValue 40 | }); 41 | 42 | swal('郵遞區號不存在: ' + origZipValue, '', 'error'); 43 | } 44 | 45 | render() { 46 | let addressShow = this.state.displayType === 'display' ? 'none' : 'inline'; 47 | let fullAddress = this.state.county+this.state.district+this.state.address; 48 | return ( 49 | <> 50 |
51 | 52 |
53 | 63 | 64 | 70 |
71 |
72 |
73 | 74 |
75 | 86 |
87 |
88 |
89 |
90 |

DisplayType: 'display'

91 | 92 |
93 | 98 |
99 |
100 |
101 |
102 | 103 |
104 | 110 |
111 |
112 | 113 |
114 |
115 | 116 |
117 | 123 |
124 |
125 | 126 | ); 127 | } 128 | } 129 | 130 | export default Address; -------------------------------------------------------------------------------- /es/data/RawData.js: -------------------------------------------------------------------------------- 1 | export default { 2 | '': {'':''}, 3 | 基隆市: { 4 | 仁愛區: '200', 5 | 信義區: '201', 6 | 中正區: '202', 7 | 中山區: '203', 8 | 安樂區: '204', 9 | 暖暖區: '205', 10 | 七堵區: '206', 11 | }, 12 | 台北市: { 13 | 中正區: '100', 14 | 大同區: '103', 15 | 中山區: '104', 16 | 松山區: '105', 17 | 大安區: '106', 18 | 萬華區: '108', 19 | 信義區: '110', 20 | 士林區: '111', 21 | 北投區: '112', 22 | 內湖區: '114', 23 | 南港區: '115', 24 | 文山區: '116', 25 | }, 26 | 新北市: { 27 | 萬里區: '207', 28 | 金山區: '208', 29 | 板橋區: '220', 30 | 汐止區: '221', 31 | 深坑區: '222', 32 | 石碇區: '223', 33 | 瑞芳區: '224', 34 | 平溪區: '226', 35 | 雙溪區: '227', 36 | 貢寮區: '228', 37 | 新店區: '231', 38 | 坪林區: '232', 39 | 烏來區: '233', 40 | 永和區: '234', 41 | 中和區: '235', 42 | 土城區: '236', 43 | 三峽區: '237', 44 | 樹林區: '238', 45 | 鶯歌區: '239', 46 | 三重區: '241', 47 | 新莊區: '242', 48 | 泰山區: '243', 49 | 林口區: '244', 50 | 蘆洲區: '247', 51 | 五股區: '248', 52 | 八里區: '249', 53 | 淡水區: '251', 54 | 三芝區: '252', 55 | 石門區: '253', 56 | }, 57 | 宜蘭縣: { 58 | 宜蘭市: '260', 59 | 頭城鎮: '261', 60 | 礁溪鄉: '262', 61 | 壯圍鄉: '263', 62 | 員山鄉: '264', 63 | 羅東鎮: '265', 64 | 三星鄉: '266', 65 | 大同鄉: '267', 66 | 五結鄉: '268', 67 | 冬山鄉: '269', 68 | 蘇澳鎮: '270', 69 | 南澳鄉: '272', 70 | 釣魚台列嶼: '290', 71 | }, 72 | 新竹市: { 73 | 東區: '300', 74 | 北區: '300', 75 | 香山區: '300', 76 | }, 77 | 新竹縣: { 78 | 竹北市: '302', 79 | 湖口鄉: '303', 80 | 新豐鄉: '304', 81 | 新埔鎮: '305', 82 | 關西鎮: '306', 83 | 芎林鄉: '307', 84 | 寶山鄉: '308', 85 | 竹東鎮: '310', 86 | 五峰鄉: '311', 87 | 橫山鄉: '312', 88 | 尖石鄉: '313', 89 | 北埔鄉: '314', 90 | 峨嵋鄉: '315', 91 | }, 92 | 桃園市: { 93 | 中壢區: '320', 94 | 平鎮區: '324', 95 | 龍潭區: '325', 96 | 楊梅區: '326', 97 | 新屋區: '327', 98 | 觀音區: '328', 99 | 桃園區: '330', 100 | 龜山區: '333', 101 | 八德區: '334', 102 | 大溪區: '335', 103 | 復興區: '336', 104 | 大園區: '337', 105 | 蘆竹區: '338', 106 | }, 107 | 苗栗縣: { 108 | 竹南鎮: '350', 109 | 頭份市: '351', 110 | 三灣鄉: '352', 111 | 南庄鄉: '353', 112 | 獅潭鄉: '354', 113 | 後龍鎮: '356', 114 | 通霄鎮: '357', 115 | 苑裡鎮: '358', 116 | 苗栗市: '360', 117 | 造橋鄉: '361', 118 | 頭屋鄉: '362', 119 | 公館鄉: '363', 120 | 大湖鄉: '364', 121 | 泰安鄉: '365', 122 | 銅鑼鄉: '366', 123 | 三義鄉: '367', 124 | 西湖鄉: '368', 125 | 卓蘭鎮: '369', 126 | }, 127 | 台中市: { 128 | 中區: '400', 129 | 東區: '401', 130 | 南區: '402', 131 | 西區: '403', 132 | 北區: '404', 133 | 北屯區: '406', 134 | 西屯區: '407', 135 | 南屯區: '408', 136 | 太平區: '411', 137 | 大里區: '412', 138 | 霧峰區: '413', 139 | 烏日區: '414', 140 | 豐原區: '420', 141 | 后里區: '421', 142 | 石岡區: '422', 143 | 東勢區: '423', 144 | 和平區: '424', 145 | 新社區: '426', 146 | 潭子區: '427', 147 | 大雅區: '428', 148 | 神岡區: '429', 149 | 大肚區: '432', 150 | 沙鹿區: '433', 151 | 龍井區: '434', 152 | 梧棲區: '435', 153 | 清水區: '436', 154 | 大甲區: '437', 155 | 外埔區: '438', 156 | 大安區: '439', 157 | }, 158 | 彰化縣: { 159 | 彰化市: '500', 160 | 芬園鄉: '502', 161 | 花壇鄉: '503', 162 | 秀水鄉: '504', 163 | 鹿港鎮: '505', 164 | 福興鄉: '506', 165 | 線西鄉: '507', 166 | 和美鎮: '508', 167 | 伸港鄉: '509', 168 | 員林市: '510', 169 | 社頭鄉: '511', 170 | 永靖鄉: '512', 171 | 埔心鄉: '513', 172 | 溪湖鎮: '514', 173 | 大村鄉: '515', 174 | 埔鹽鄉: '516', 175 | 田中鎮: '520', 176 | 北斗鎮: '521', 177 | 田尾鄉: '522', 178 | 埤頭鄉: '523', 179 | 溪州鄉: '524', 180 | 竹塘鄉: '525', 181 | 二林鎮: '526', 182 | 大城鄉: '527', 183 | 芳苑鄉: '528', 184 | 二水鄉: '530', 185 | }, 186 | 南投縣: { 187 | 南投市: '540', 188 | 中寮鄉: '541', 189 | 草屯鎮: '542', 190 | 國姓鄉: '544', 191 | 埔里鎮: '545', 192 | 仁愛鄉: '546', 193 | 名間鄉: '551', 194 | 集集鎮: '552', 195 | 水里鄉: '553', 196 | 魚池鄉: '555', 197 | 信義鄉: '556', 198 | 竹山鎮: '557', 199 | 鹿谷鄉: '558', 200 | }, 201 | 嘉義市: { 202 | 東區: '600', 203 | 西區: '600', 204 | }, 205 | 嘉義縣: { 206 | 番路鄉: '602', 207 | 梅山鄉: '603', 208 | 竹崎鄉: '604', 209 | 阿里山: '605', 210 | 中埔鄉: '606', 211 | 大埔鄉: '607', 212 | 水上鄉: '608', 213 | 鹿草鄉: '611', 214 | 太保市: '612', 215 | 朴子市: '613', 216 | 東石鄉: '614', 217 | 六腳鄉: '615', 218 | 新港鄉: '616', 219 | 民雄鄉: '621', 220 | 大林鎮: '622', 221 | 溪口鄉: '623', 222 | 義竹鄉: '624', 223 | 布袋鎮: '625', 224 | }, 225 | 雲林縣: { 226 | 斗南鎮: '630', 227 | 大埤鄉: '631', 228 | 虎尾鎮: '632', 229 | 土庫鎮: '633', 230 | 褒忠鄉: '634', 231 | 東勢鄉: '635', 232 | 臺西鄉: '636', 233 | 崙背鄉: '637', 234 | 麥寮鄉: '638', 235 | 斗六市: '640', 236 | 林內鄉: '643', 237 | 古坑鄉: '646', 238 | 莿桐鄉: '647', 239 | 西螺鎮: '648', 240 | 二崙鄉: '649', 241 | 北港鎮: '651', 242 | 水林鄉: '652', 243 | 口湖鄉: '653', 244 | 四湖鄉: '654', 245 | 元長鄉: '655', 246 | }, 247 | 台南市: { 248 | 中西區: '700', 249 | 東區: '701', 250 | 南區: '702', 251 | 北區: '704', 252 | 安平區: '708', 253 | 安南區: '709', 254 | 永康區: '710', 255 | 歸仁區: '711', 256 | 新化區: '712', 257 | 左鎮區: '713', 258 | 玉井區: '714', 259 | 楠西區: '715', 260 | 南化區: '716', 261 | 仁德區: '717', 262 | 關廟區: '718', 263 | 龍崎區: '719', 264 | 官田區: '720', 265 | 麻豆區: '721', 266 | 佳里區: '722', 267 | 西港區: '723', 268 | 七股區: '724', 269 | 將軍區: '725', 270 | 學甲區: '726', 271 | 北門區: '727', 272 | 新營區: '730', 273 | 後壁區: '731', 274 | 白河區: '732', 275 | 東山區: '733', 276 | 六甲區: '734', 277 | 下營區: '735', 278 | 柳營區: '736', 279 | 鹽水區: '737', 280 | 善化區: '741', 281 | 大內區: '742', 282 | 山上區: '743', 283 | 新市區: '744', 284 | 安定區: '745', 285 | }, 286 | 高雄市: { 287 | 新興區: '800', 288 | 前金區: '801', 289 | 苓雅區: '802', 290 | 鹽埕區: '803', 291 | 鼓山區: '804', 292 | 旗津區: '805', 293 | 前鎮區: '806', 294 | 三民區: '807', 295 | 楠梓區: '811', 296 | 小港區: '812', 297 | 左營區: '813', 298 | 仁武區: '814', 299 | 大社區: '815', 300 | 東沙群島: '817', 301 | 南沙群島: '819', 302 | 岡山區: '820', 303 | 路竹區: '821', 304 | 阿蓮區: '822', 305 | 田寮區: '823', 306 | 燕巢區: '824', 307 | 橋頭區: '825', 308 | 梓官區: '826', 309 | 彌陀區: '827', 310 | 永安區: '828', 311 | 湖內區: '829', 312 | 鳳山區: '830', 313 | 大寮區: '831', 314 | 林園區: '832', 315 | 鳥松區: '833', 316 | 大樹區: '840', 317 | 旗山區: '842', 318 | 美濃區: '843', 319 | 六龜區: '844', 320 | 內門區: '845', 321 | 杉林區: '846', 322 | 甲仙區: '847', 323 | 桃源區: '848', 324 | 那瑪夏區: '849', 325 | 茂林區: '851', 326 | 茄萣區: '852', 327 | }, 328 | 屏東縣: { 329 | 屏東市: '900', 330 | 三地門鄉: '901', 331 | 霧臺鄉: '902', 332 | 瑪家鄉: '903', 333 | 九如鄉: '904', 334 | 里港鄉: '905', 335 | 高樹鄉: '906', 336 | 鹽埔鄉: '907', 337 | 長治鄉: '908', 338 | 麟洛鄉: '909', 339 | 竹田鄉: '911', 340 | 內埔鄉: '912', 341 | 萬丹鄉: '913', 342 | 潮州鎮: '920', 343 | 泰武鄉: '921', 344 | 來義鄉: '922', 345 | 萬巒鄉: '923', 346 | 崁頂鄉: '924', 347 | 新埤鄉: '925', 348 | 南州鄉: '926', 349 | 林邊鄉: '927', 350 | 東港鎮: '928', 351 | 琉球鄉: '929', 352 | 佳冬鄉: '931', 353 | 新園鄉: '932', 354 | 枋寮鄉: '940', 355 | 枋山鄉: '941', 356 | 春日鄉: '942', 357 | 獅子鄉: '943', 358 | 車城鄉: '944', 359 | 牡丹鄉: '945', 360 | 恆春鎮: '946', 361 | 滿州鄉: '947', 362 | }, 363 | 台東縣: { 364 | 臺東市: '950', 365 | 綠島鄉: '951', 366 | 蘭嶼鄉: '952', 367 | 延平鄉: '953', 368 | 卑南鄉: '954', 369 | 鹿野鄉: '955', 370 | 關山鎮: '956', 371 | 海端鄉: '957', 372 | 池上鄉: '958', 373 | 東河鄉: '959', 374 | 成功鎮: '961', 375 | 長濱鄉: '962', 376 | 太麻里鄉: '963', 377 | 金峰鄉: '964', 378 | 大武鄉: '965', 379 | 達仁鄉: '966', 380 | }, 381 | 花蓮縣: { 382 | 花蓮市: '970', 383 | 新城鄉: '971', 384 | 秀林鄉: '972', 385 | 吉安鄉: '973', 386 | 壽豐鄉: '974', 387 | 鳳林鎮: '975', 388 | 光復鄉: '976', 389 | 豐濱鄉: '977', 390 | 瑞穗鄉: '978', 391 | 萬榮鄉: '979', 392 | 玉里鎮: '981', 393 | 卓溪鄉: '982', 394 | 富里鄉: '983', 395 | }, 396 | 金門縣: { 397 | 金沙鎮: '890', 398 | 金湖鎮: '891', 399 | 金寧鄉: '892', 400 | 金城鎮: '893', 401 | 烈嶼鄉: '894', 402 | 烏坵鄉: '896', 403 | }, 404 | 連江縣: { 405 | 南竿鄉: '209', 406 | 北竿鄉: '210', 407 | 莒光鄉: '211', 408 | 東引鄉: '212', 409 | }, 410 | 澎湖縣: { 411 | 馬公市: '880', 412 | 西嶼鄉: '881', 413 | 望安鄉: '882', 414 | 七美鄉: '883', 415 | 白沙鄉: '884', 416 | 湖西鄉: '885', 417 | }, 418 | }; -------------------------------------------------------------------------------- /es/zipcode/ZipCodeTW.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import PropTypes from "prop-types"; 3 | import District from "./District"; 4 | import County from "./County"; 5 | import ZipCode from "./ZipCode"; 6 | import RawData from '../data/RawData'; 7 | import RawDataSort from '../data/RawDataSort'; 8 | 9 | /** 10 | * 組合元件 11 | * 12 | * @author Chris Tsai 13 | */ 14 | export default class ZipCodeTW extends React.Component { 15 | 16 | constructor(props) { 17 | super(props); 18 | this.state = { 19 | countyFieldName: 'county', 20 | districtFieldName: 'district', 21 | zipCodeFieldName: 'zipCode', 22 | county: 'county', 23 | counties: [], 24 | district: 'district', 25 | districts: [], 26 | zipCode: 'zipCode', 27 | zipCodePlaceholder: '', 28 | }; 29 | } 30 | 31 | componentDidUpdate(prevProps) { 32 | const { 33 | countyValue, 34 | districtValue, 35 | zipCodeValue 36 | } = this.props; 37 | if(prevProps.countyValue !== countyValue 38 | || prevProps.districtValue !== districtValue 39 | || prevProps.zipCodeValue !== zipCodeValue) { 40 | this.initData(); 41 | } 42 | } 43 | 44 | componentDidMount() { 45 | this.initData(); 46 | } 47 | 48 | initData = () =>{ 49 | const counties = Object.keys(RawData); 50 | const { 51 | countyValue, 52 | districtValue, 53 | zipCodeValue, 54 | countyFieldName, 55 | districtFieldName, 56 | zipCodeFieldName, 57 | countySort, 58 | } = this.props; 59 | if(typeof countySort != 'undefined'){ 60 | counties.sort(function (a, b) { 61 | return countySort[a] - countySort[b]; 62 | }); 63 | }else{ 64 | counties.sort(function (a, b) { 65 | return RawDataSort[a] - RawDataSort[b]; 66 | }); 67 | } 68 | 69 | const county = (countyValue === '') ? counties[0] : countyValue; 70 | let district; 71 | let zipCode = typeof(zipCodeValue) == 'undefined' ? '' : zipCodeValue; 72 | let countyRaw = RawData[county]; 73 | const districts = typeof(countyRaw) == 'undefined' ? [] : Object.keys(countyRaw).map((d) => d, []); 74 | 75 | if(typeof(districts) != 'undefined' && districts.length > 0){ 76 | if (districtValue === '') { 77 | district = districts[0]; 78 | } else if (districts.indexOf(districtValue) > -1) { 79 | district = districtValue; 80 | } else { 81 | district = districts[0]; 82 | } 83 | zipCode = RawData[county][district]; 84 | } 85 | 86 | const nowCountyFieldName = typeof (countyFieldName) != 'undefined' && countyFieldName !== '' ? countyFieldName: 'county'; 87 | const nowDistrictFieldNam = typeof (districtFieldName) != 'undefined' && districtFieldName !== '' ? districtFieldName: 'district'; 88 | const nowZipCodeFieldName = typeof (zipCodeFieldName) != 'undefined' && zipCodeFieldName !== '' ? zipCodeFieldName: 'zipCode'; 89 | 90 | this.setState({ 91 | county, district, zipCode, 92 | counties, districts, 93 | countyFieldName: nowCountyFieldName, 94 | districtFieldName: nowDistrictFieldNam, 95 | zipCodeFieldName: nowZipCodeFieldName 96 | }); 97 | } 98 | 99 | handleChangeCounty = (county) => { 100 | const districts = Object.keys(RawData[county]).map((d) => d, []); 101 | let district = districts[0]; 102 | let zipCode = RawData[county][districts[0]]; 103 | let {handleChangeCounty} = this.props; 104 | let {countyFieldName, districtFieldName, zipCodeFieldName} = this.state; 105 | this.setState({ 106 | county: county, 107 | districts: districts, 108 | district: district, 109 | zipCode: zipCode, 110 | }, () => { 111 | if(typeof (handleChangeCounty) == 'function'){ 112 | handleChangeCounty({ 113 | 'countyFieldName':countyFieldName, 'countyValue': county, 114 | 'districtFieldName':districtFieldName, 'districtValue': district, 115 | 'zipFieldName':zipCodeFieldName,'zipValue':zipCode 116 | }); 117 | } 118 | }); 119 | } 120 | 121 | handleChangeDistrict = (district) =>{ 122 | let zipCode = RawData[this.state.county][district]; 123 | let {handleChangeDistrict} = this.props; 124 | let {countyFieldName, districtFieldName, zipCodeFieldName} = this.state; 125 | this.setState({ 126 | district: district, 127 | zipCode: zipCode, 128 | }, () => { 129 | if(typeof (handleChangeDistrict) == 'function'){ 130 | handleChangeDistrict({ 131 | 'countyFieldName': countyFieldName, 'countyValue': this.state.county, 132 | 'districtFieldName':districtFieldName, 'districtValue': district, 133 | 'zipFieldName':zipCodeFieldName,'zipValue':zipCode 134 | }); 135 | } 136 | }); 137 | } 138 | 139 | handleChangeZipCode = (zipCode) =>{ 140 | let {handleChangeZipCode} = this.props; 141 | let {zipCodeFieldName} = this.state; 142 | this.setState({ 143 | zipCode: zipCode, 144 | }, () =>{ 145 | if(typeof (handleChangeZipCode) == 'function'){ 146 | handleChangeZipCode({ 147 | 'zipFieldName':zipCodeFieldName,'zipValue':zipCode 148 | }); 149 | } 150 | }); 151 | } 152 | 153 | handleBlurZipCode = (zipCode) =>{ 154 | const { countyN, districtN } = this.findCountyAndDistrictByZipCode(zipCode); 155 | let {handleZipCodeNotExists, handleBlurZipCode} = this.props; 156 | let {countyFieldName, districtFieldName, zipCodeFieldName} = this.state; 157 | if(typeof(countyN) != 'undefined' && typeof(districtN) != 'undefined'){ 158 | const districts = Object.keys(RawData[countyN]).map((d) => d, []); 159 | this.setState({ 160 | county: countyN, district: districtN, districts: districts 161 | }, () =>{ 162 | if(typeof (handleBlurZipCode) == 'function'){ 163 | handleBlurZipCode({ 164 | 'countyFieldName':countyFieldName, 'countyValue': countyN, 165 | 'districtFieldName':districtFieldName, 'districtValue': districtN, 166 | 'zipFieldName':zipCodeFieldName,'zipValue':zipCode 167 | }); 168 | } 169 | }); 170 | }else{ 171 | this.setState({ 172 | county: '', district: '', districts: [], zipCode: '' 173 | }, () =>{ 174 | if(typeof (handleZipCodeNotExists) == 'function'){ 175 | handleZipCodeNotExists({ 176 | 'countyFieldName':countyFieldName, 'countyValue': '', 177 | 'districtFieldName':districtFieldName, 'districtValue': '', 178 | 'zipFieldName':zipCodeFieldName,'zipValue':'', 'origZipValue': zipCode 179 | }); 180 | } 181 | }); 182 | } 183 | } 184 | 185 | findCountyAndDistrictByZipCode = (zipCode) =>{ 186 | let rtn = {} 187 | Object.keys(RawData).forEach((countyN) => { 188 | Object.keys(RawData[countyN]).forEach((districtN) => { 189 | if (RawData[countyN][districtN] === zipCode.toString()) { 190 | rtn = { 191 | countyN, 192 | districtN, 193 | }; 194 | } 195 | }); 196 | }); 197 | return rtn; 198 | } 199 | 200 | render() { 201 | const {zipStyle, countyStyle, districtStyle, zipClass, countyClass, districtClass, displayType, zipCodePositionLast, countySort} = this.props; 202 | const {fullAddress, address, addressClass, addressStyle} = this.props; 203 | const displayTypeFlag = (displayType === 'display') ? true : false; 204 | const nowZipCodePositionLast = typeof (zipCodePositionLast) != 'undefined' ? zipCodePositionLast : true; 205 | const nowCountyStyle = typeof (countyStyle) != 'undefined' ? countyStyle: nowZipCodePositionLast ? {} : {marginLeft:'5px'}; 206 | const nowDistrictStyle = 207 | typeof (districtStyle) != 'undefined' ? districtStyle: displayTypeFlag ? {} : {marginLeft:'5px', minWidth:'107px', paddingRight:'0px'}; 208 | const nowZipStyle = 209 | typeof (zipStyle) != 'undefined' ? zipStyle: (!displayTypeFlag && !nowZipCodePositionLast) ? {width: '50px'} : {marginLeft:'5px', width: '50px'}; 210 | const nowCountyClass = 211 | typeof (countyClass) != 'undefined' ? countyClass: 'form-control'; 212 | const nowDistrictClass = 213 | typeof (districtClass) != 'undefined' ? districtClass: 'form-control'; 214 | const nowZipClass = 215 | typeof (zipClass) != 'undefined' ? zipClass: 'form-control'; 216 | const nowAddressClass = 217 | typeof (addressClass) != 'undefined' ? addressClass: 'form-control'; 218 | 219 | return ( 220 | <> 221 | {displayTypeFlag || (!displayTypeFlag && !nowZipCodePositionLast) ? 222 | : '' 231 | } 232 | 233 | {typeof (fullAddress) != 'undefined' && fullAddress !== '' && displayTypeFlag ? 234 | {fullAddress} : <> 238 | 246 | 254 | {!displayTypeFlag && nowZipCodePositionLast ? 255 | : '' 264 | } 265 | {typeof (address) != 'undefined' && address !== '' && displayTypeFlag ? 266 | {address} : '' 269 | } 270 | 271 | } 272 | 273 | ); 274 | } 275 | } 276 | 277 | ZipCodeTW.propTypes = { 278 | displayType: PropTypes.oneOf(['text', 'display']).isRequired, 279 | fullAddress: PropTypes.string, 280 | zipCodePositionLast: PropTypes.bool, 281 | address: PropTypes.string, 282 | countyFieldName: PropTypes.string, 283 | countyValue: PropTypes.string, 284 | districtFieldName: PropTypes.string, 285 | districtValue: PropTypes.string, 286 | zipCodeFieldName: PropTypes.string, 287 | zipCodeValue: PropTypes.string, 288 | zipCodePlaceholder: PropTypes.string, 289 | handleChangeCounty: PropTypes.func, 290 | handleChangeDistrict: PropTypes.func, 291 | handleChangeZipCode: PropTypes.func, 292 | handleBlurZipCode: PropTypes.func, 293 | handleZipCodeNotExists: PropTypes.func, 294 | countyClass: PropTypes.oneOfType([PropTypes.string, PropTypes.array, PropTypes.object]), 295 | countyStyle: PropTypes.oneOfType([PropTypes.string, PropTypes.array, PropTypes.object]), 296 | districtClass: PropTypes.oneOfType([PropTypes.string, PropTypes.array, PropTypes.object]), 297 | districtStyle: PropTypes.oneOfType([PropTypes.string, PropTypes.array, PropTypes.object]), 298 | zipClass: PropTypes.oneOfType([PropTypes.string, PropTypes.array, PropTypes.object]), 299 | zipStyle: PropTypes.oneOfType([PropTypes.string, PropTypes.array, PropTypes.object]), 300 | addressClass: PropTypes.oneOfType([PropTypes.string, PropTypes.array, PropTypes.object]), 301 | addressStyle: PropTypes.oneOfType([PropTypes.string, PropTypes.array, PropTypes.object]), 302 | countySort: PropTypes.object, 303 | }; -------------------------------------------------------------------------------- /es/_test_/ZipCodeTW.test.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import ZipCodeTW from "../zipcode/ZipCodeTW"; 3 | import Enzyme, {mount} from 'enzyme'; 4 | import Adapter from 'enzyme-adapter-react-16'; 5 | import mockStore from "../config_test/mockStore"; 6 | 7 | Enzyme.configure({adapter: new Adapter()}); 8 | 9 | function setup(countyFieldName, countyValue, districtFieldName, districtValue, zipFieldName, zipValue, zipCodePositionLast, 10 | displayType, mockFn, mockFnD, mockFnZ, notExistsZ, mockBlur, useClass, 11 | useStyle, address, fullAddress) { 12 | const store = mockStore({ 13 | zipCodeTW: { 14 | countyFieldName: countyFieldName, 15 | countyValue: countyValue, 16 | districtFieldName: districtFieldName, 17 | districtValue: districtValue, 18 | zipCodeFieldName: zipFieldName, 19 | zipCodeValue: zipValue, 20 | zipCodePositionLast: zipCodePositionLast, 21 | displayType: displayType, 22 | handleChangeCounty: mockFn, 23 | handleChangeDistrict: mockFnD, 24 | handleChangeZipCode: mockFnZ, 25 | handleZipCodeNotExists: notExistsZ, 26 | handleBlurZipCode: mockBlur, 27 | countyClass: useClass, 28 | districtClass: useClass, 29 | zipClass: useClass, 30 | countyStyle: useStyle, 31 | districtStyle: useStyle, 32 | zipStyle: useStyle, 33 | addressClass: useClass, 34 | addressStyle: useStyle, 35 | address: address, 36 | fullAddress: fullAddress 37 | 38 | } 39 | }); 40 | const state = store.getState(); 41 | const props = Object.assign({}, state.zipCodeTW); 42 | const wrapper = mount(); 43 | 44 | return { 45 | props, 46 | wrapper 47 | } 48 | } 49 | 50 | describe('ZipCodeTW test', () => { 51 | it('test displayType= text', () => { 52 | const onChangeMock = jest.fn(); 53 | const onChangeMockD = jest.fn(); 54 | const onChangeMockZ = jest.fn(); 55 | const notExistsZ = jest.fn(); 56 | const onBlurMock = jest.fn(); 57 | const countyFieldName = 'zipName'; 58 | const countyValue = '台北市'; 59 | const districtFieldName = 'district'; 60 | const districtValue = '中正區'; 61 | const zipFieldName = 'zipCode'; 62 | const zipValue = '100'; 63 | const changeValue = '新北市'; 64 | const changeDistrictValue = '萬里區'; 65 | const changeZipValue = '207'; 66 | const displayType = 'text'; 67 | const zipCodePositionLast = false; 68 | const {wrapper} = setup(countyFieldName, countyValue, districtFieldName, districtValue, zipFieldName, zipValue, 69 | zipCodePositionLast, displayType, onChangeMock, onChangeMockD, onChangeMockZ, notExistsZ, onBlurMock); 70 | expect(wrapper.find('select[name=\''+countyFieldName+'\']').props().value).toEqual(countyValue); 71 | expect(wrapper.find('select[name=\''+countyFieldName+'\']').props().name).toEqual(countyFieldName); 72 | expect(wrapper.find('select[name=\''+countyFieldName+'\']').hasClass('form-control')).toBe(true); 73 | 74 | expect(wrapper.find('select[name=\''+districtFieldName+'\']').props().value).toEqual(districtValue); 75 | expect(wrapper.find('select[name=\''+districtFieldName+'\']').props().name).toEqual(districtFieldName); 76 | expect(wrapper.find('select[name=\''+districtFieldName+'\']').hasClass('form-control')).toBe(true); 77 | 78 | expect(wrapper.find('input[name=\''+zipFieldName+'\']').props().value).toEqual(zipValue); 79 | expect(wrapper.find('input[name=\''+zipFieldName+'\']').props().name).toEqual(zipFieldName); 80 | expect(wrapper.find('input[name=\''+zipFieldName+'\']').hasClass('form-control')).toBe(true); 81 | wrapper.find('select[name=\''+countyFieldName+'\']').simulate('change', {target: {value: changeValue} }); 82 | expect(onChangeMock).toBeCalledWith({"countyFieldName": "zipName", "countyValue": "新北市", "districtFieldName": "district", "districtValue": "萬里區", "zipFieldName": "zipCode", "zipValue": "207" 83 | }); 84 | 85 | wrapper.find('select[name=\''+districtFieldName+'\']').simulate('change', {target: {value: changeDistrictValue} }); 86 | expect(onChangeMockD).toBeCalledWith({"countyFieldName": "zipName", "countyValue": "新北市", "districtFieldName": "district", "districtValue": "萬里區", "zipFieldName": "zipCode", "zipValue": "207" 87 | }); 88 | 89 | wrapper.find('input[name=\''+zipFieldName+'\']').simulate('change', {target: {value: changeZipValue} }); 90 | expect(onChangeMockZ).toBeCalledWith({"zipFieldName": "zipCode", "zipValue": "207" 91 | }); 92 | 93 | wrapper.find('input[name=\''+zipFieldName+'\']').simulate('blur', {target: {value: changeZipValue} }); 94 | expect(onBlurMock).toBeCalledWith({"countyFieldName": "zipName", "countyValue": "新北市", "districtFieldName": "district", "districtValue": "萬里區", "zipFieldName": "zipCode", "zipValue": "207" 95 | }); 96 | 97 | wrapper.find('input[name=\''+zipFieldName+'\']').simulate('blur', {target: {value: '999'} }); 98 | expect(notExistsZ).toBeCalledWith({"countyFieldName": "zipName", "countyValue": "", "districtFieldName": "district", "districtValue": "", "zipFieldName": "zipCode", "zipValue": "", "origZipValue":"999" 99 | }); 100 | }); 101 | 102 | it('test displayType= text (undefined)', () => { 103 | const onChangeMock = jest.fn(); 104 | const onChangeMockD = jest.fn(); 105 | const onChangeMockZ = jest.fn(); 106 | const notExistsZ = jest.fn(); 107 | const onBlurMock = jest.fn(); 108 | const countyFieldName = ''; 109 | const countyValue = ''; 110 | const districtFieldName = ''; 111 | const districtValue = undefined; 112 | const zipFieldName = ''; 113 | const zipValue = undefined; 114 | const changeValue = '新北市'; 115 | const changeDistrictValue = '萬里區'; 116 | const changeZipValue = '207'; 117 | const displayType = 'text'; 118 | const useClass = 'form-control'; 119 | const useStyle = {width:'100px'}; 120 | const zipCodePositionLast = false; 121 | const {wrapper} = setup(countyFieldName, countyValue, districtFieldName, districtValue, zipFieldName, zipValue, 122 | zipCodePositionLast, displayType, onChangeMock, onChangeMockD, onChangeMockZ, notExistsZ, onBlurMock, useClass, useStyle); 123 | expect(wrapper.find('select[name=\'county\']').props().value).toEqual(countyValue); 124 | expect(wrapper.find('select[name=\'county\']').hasClass('form-control')).toBe(true); 125 | 126 | expect(wrapper.find('select[name=\'district\']').props().value).toEqual(districtValue); 127 | expect(wrapper.find('select[name=\'district\']').hasClass('form-control')).toBe(true); 128 | 129 | expect(wrapper.find('input[name=\'zipCode\']').props().value).toEqual(zipValue); 130 | expect(wrapper.find('input[name=\'zipCode\']').hasClass('form-control')).toBe(true); 131 | 132 | wrapper.find('select[name=\'county\']').simulate('change', {target: {value: changeValue} }); 133 | expect(onChangeMock).toBeCalledWith({"countyFieldName": "county", "countyValue": "新北市", "districtFieldName": "district", "districtValue": "萬里區", "zipFieldName": "zipCode", "zipValue": "207" 134 | }); 135 | 136 | wrapper.find('select[name=\'district\']').simulate('change', {target: {value: changeDistrictValue} }); 137 | expect(onChangeMockD).toBeCalledWith({"countyFieldName": "county", "countyValue": "新北市", "districtFieldName": "district", "districtValue": "萬里區", "zipFieldName": "zipCode", "zipValue": "207" 138 | }); 139 | 140 | wrapper.find('input[name=\'zipCode\']').simulate('change', {target: {value: changeZipValue} }); 141 | expect(onChangeMockZ).toBeCalledWith({"zipFieldName": "zipCode", "zipValue": "207" 142 | }); 143 | 144 | wrapper.find('input[name=\'zipCode\']').simulate('blur', {target: {value: changeZipValue} }); 145 | expect(onBlurMock).toBeCalledWith({"countyFieldName": "county", "countyValue": "新北市", "districtFieldName": "district", "districtValue": "萬里區", "zipFieldName": "zipCode", "zipValue": "207" 146 | }); 147 | 148 | wrapper.find('input[name=\'zipCode\']').simulate('blur', {target: {value: '999'} }); 149 | expect(notExistsZ).toBeCalledWith({"countyFieldName": "county", "countyValue": "", "districtFieldName": "district", "districtValue": "", "zipFieldName": "zipCode", "zipValue": "", "origZipValue":"999" 150 | }); 151 | 152 | }); 153 | 154 | it('test displayType= text not equal', () => { 155 | const onChangeMock = jest.fn(); 156 | const onChangeMockD = jest.fn(); 157 | const onChangeMockZ = jest.fn(); 158 | const notExistsZ = jest.fn(); 159 | const onBlurMock = jest.fn(); 160 | const countyFieldName = ''; 161 | const countyValue = '台北市'; 162 | const districtFieldName = ''; 163 | const districtValue = ''; 164 | const zipFieldName = ''; 165 | const zipValue = undefined; 166 | const displayType = 'text'; 167 | const useClass = 'form-control'; 168 | const useStyle = {width:'100px'}; 169 | const zipCodePositionLast = false; 170 | const {wrapper} = setup(countyFieldName, countyValue, districtFieldName, districtValue, zipFieldName, zipValue, 171 | zipCodePositionLast, displayType, onChangeMock, onChangeMockD, onChangeMockZ, notExistsZ, onBlurMock, useClass, useStyle); 172 | expect(wrapper.find('select[name=\'county\']').props().value).toEqual(countyValue); 173 | expect(wrapper.find('select[name=\'county\']').hasClass('form-control')).toBe(true); 174 | 175 | expect(wrapper.find('select[name=\'district\']').props().value).toEqual(districtValue); 176 | expect(wrapper.find('select[name=\'district\']').hasClass('form-control')).toBe(true); 177 | 178 | expect(wrapper.find('input[name=\'zipCode\']').props().value).toEqual(zipValue); 179 | expect(wrapper.find('input[name=\'zipCode\']').hasClass('form-control')).toBe(true); 180 | 181 | }); 182 | 183 | it('test props', () => { 184 | const onChangeMock = undefined; 185 | const onChangeMockD = undefined; 186 | const onChangeMockZ = undefined; 187 | const notExistsZ = undefined; 188 | const onBlurMock = undefined; 189 | const countyFieldName = ''; 190 | const countyValue = '台北市'; 191 | const districtFieldName = ''; 192 | const districtValue = ''; 193 | const zipFieldName = ''; 194 | const zipValue = undefined; 195 | const changeValue = '新北市'; 196 | const changeDistrictValue = '萬里區'; 197 | const changeZipValue = '207'; 198 | const displayType = 'text'; 199 | const useClass = 'form-control'; 200 | const useStyle = {width:'100px'}; 201 | const zipCodePositionLast = false; 202 | const {wrapper} = setup(countyFieldName, countyValue, districtFieldName, districtValue, zipFieldName, zipValue, 203 | zipCodePositionLast, displayType, onChangeMock, onChangeMockD, onChangeMockZ, notExistsZ, onBlurMock, useClass, useStyle); 204 | const prevProp = Object.assign(wrapper.props(), {zipCodeValue: '999'}); 205 | wrapper.instance().componentDidUpdate(prevProp); 206 | 207 | wrapper.find('select[name=\'county\']').simulate('change', {target: {value: changeValue} }); 208 | 209 | wrapper.find('select[name=\'district\']').simulate('change', {target: {value: changeDistrictValue} }); 210 | 211 | wrapper.find('input[name=\'zipCode\']').simulate('change', {target: {value: changeZipValue} }); 212 | 213 | wrapper.find('input[name=\'zipCode\']').simulate('blur', {target: {value: changeZipValue} }); 214 | 215 | wrapper.find('input[name=\'zipCode\']').simulate('blur', {target: {value: '999'} }); 216 | }); 217 | 218 | it('test displayType= text not equal', () => { 219 | const onChangeMock = jest.fn(); 220 | const onChangeMockD = jest.fn(); 221 | const onChangeMockZ = jest.fn(); 222 | const notExistsZ = jest.fn(); 223 | const onBlurMock = jest.fn(); 224 | const countyFieldName = ''; 225 | const countyValue = '台北縣'; 226 | const districtFieldName = ''; 227 | const districtValue = ''; 228 | const zipFieldName = ''; 229 | const zipValue = undefined; 230 | const displayType = 'text'; 231 | const useClass = 'form-control'; 232 | const useStyle = {width:'100px'}; 233 | const zipCodePositionLast = false; 234 | const {wrapper} = setup(countyFieldName, countyValue, districtFieldName, districtValue, zipFieldName, zipValue, 235 | zipCodePositionLast, displayType, onChangeMock, onChangeMockD, onChangeMockZ, notExistsZ, onBlurMock, useClass, useStyle); 236 | expect(wrapper.find('select[name=\'county\']').props().value).toEqual(countyValue); 237 | expect(wrapper.find('select[name=\'county\']').hasClass('form-control')).toBe(true); 238 | 239 | expect(wrapper.find('select[name=\'district\']').props().value).toEqual(districtValue); 240 | expect(wrapper.find('select[name=\'district\']').hasClass('form-control')).toBe(true); 241 | 242 | expect(wrapper.find('input[name=\'zipCode\']').props().value).toEqual(zipValue); 243 | expect(wrapper.find('input[name=\'zipCode\']').hasClass('form-control')).toBe(true); 244 | 245 | }); 246 | 247 | it('test displayType= display', () => { 248 | const countyFieldName = 'zipName'; 249 | const countyValue = '台北市'; 250 | const districtFieldName = 'district'; 251 | const districtValue = '中正區'; 252 | const zipFieldName = 'zipCode'; 253 | const zipValue = '100'; 254 | const displayType = 'display'; 255 | const zipCodePositionLast = undefined; 256 | const {wrapper} = setup(countyFieldName, countyValue, districtFieldName, districtValue, zipFieldName, zipValue, zipCodePositionLast, displayType); 257 | expect(wrapper.find('select[name=\''+countyFieldName+'\']').exists()).toBe(false); 258 | }); 259 | 260 | it('test displayType= display with address', () => { 261 | const countyFieldName = 'zipName'; 262 | const countyValue = '台北市'; 263 | const districtFieldName = 'district'; 264 | const districtValue = '中正區'; 265 | const zipFieldName = 'zipCode'; 266 | const zipValue = '100'; 267 | const displayType = 'display'; 268 | const zipCodePositionLast = undefined; 269 | const {wrapper} = setup(countyFieldName, countyValue, districtFieldName, 270 | districtValue, zipFieldName, zipValue, 271 | zipCodePositionLast, displayType, undefined, undefined, undefined, 272 | undefined, undefined, undefined, undefined, '中正路1號'); 273 | expect( 274 | wrapper.find('select[name=\'' + countyFieldName + '\']').exists()).toBe( 275 | false); 276 | }); 277 | 278 | it('test displayType= display with fullAddress', () => { 279 | const countyFieldName = 'zipName'; 280 | const countyValue = '台北市'; 281 | const districtFieldName = 'district'; 282 | const districtValue = '中正區'; 283 | const zipFieldName = 'zipCode'; 284 | const zipValue = '100'; 285 | const displayType = 'display'; 286 | const zipCodePositionLast = undefined; 287 | const {wrapper} = setup(countyFieldName, countyValue, districtFieldName, 288 | districtValue, zipFieldName, zipValue, 289 | zipCodePositionLast, displayType, undefined, undefined, undefined, 290 | undefined, undefined, undefined, undefined, undefined, '台北市中正區中正路1號'); 291 | expect( 292 | wrapper.find('select[name=\'' + countyFieldName + '\']').exists()).toBe( 293 | false); 294 | }); 295 | }); -------------------------------------------------------------------------------- /_example/demo/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import {Panel, Table} from "react-bootstrap"; 4 | import {ZipCodeTW} from "zipcode-tw-react" 5 | import swal from 'sweetalert2'; 6 | import './css/demo.css'; 7 | 8 | class ZipCodeTWTest extends React.Component { 9 | 10 | constructor() { 11 | super(); 12 | this.state = { 13 | displayType: 'text', 14 | countyName1: 'countyValue1', 15 | countyValue1: '台北市', 16 | districtName1: 'districtValue1', 17 | districtValue1: '中山區', 18 | zipCodeName1: 'zipCodeValue1', 19 | zipCodeValue1: '104', 20 | address: '敬業三路20號', 21 | countyClass: 'form-control', 22 | countyStyle: undefined, 23 | districtClass: 'form-control', 24 | districtStyle: undefined, 25 | zipCodeClass: 'form-control', 26 | zipCodeStyle: undefined, 27 | countyStyleStr: '', 28 | districtStyleStr: 'marginLeft:\'5px\', minWidth:\'107px\', paddingRight:\'0px\'', 29 | zipCodeStyleStr: 'marginLeft:\'5px\', width: \'50px\'', 30 | handleCountyChange: {}, 31 | handleDistrictChange: {}, 32 | handleZipCodeChange: {}, 33 | handleZipCodeBlur: {}, 34 | handleZipCodeNotExists: {}, 35 | addressClass: 'form-control', 36 | addressStyle: undefined, 37 | addressStyleStr: '', 38 | }; 39 | } 40 | 41 | handleChange = (e) => { 42 | this.setState({[e.target.name]: e.target.value}); 43 | } 44 | 45 | handleChangeObj = (e) => { 46 | let name = e.target.name; 47 | try { 48 | this.setState({ 49 | [name.substring(0, name.indexOf('Str'))]: eval( 50 | '({' + e.target.value + '})'), [e.target.name]: e.target.value 51 | }); 52 | } catch (ex) { 53 | this.setState({[e.target.name]: e.target.value}); 54 | } 55 | } 56 | 57 | handleClick = (e) => { 58 | let show = this.state.show; 59 | let displayType = this.state.displayType; 60 | this.setState({ 61 | show: !show, 62 | displayType: displayType === 'display' ? 'text' : 'display' 63 | }); 64 | } 65 | 66 | handleCountyChange = (e) => { 67 | const {countyFieldName, countyValue, districtFieldName, districtValue, zipFieldName, zipValue} = e; 68 | this.setState({ 69 | [zipFieldName]: zipValue, 70 | [countyFieldName]: countyValue, 71 | [districtFieldName]: districtValue, 72 | handleCountyChange: e, 73 | handleDistrictChange: {}, 74 | handleZipCodeChange: {}, 75 | handleZipCodeBlur: {}, 76 | handleZipCodeNotExists: {} 77 | }); 78 | } 79 | 80 | handleDistrictChange = (e) => { 81 | const {districtFieldName, districtValue, zipFieldName, zipValue} = e; 82 | this.setState({ 83 | [zipFieldName]: zipValue, 84 | [districtFieldName]: districtValue, 85 | handleDistrictChange: e, 86 | handleCountyChange: {}, 87 | handleZipCodeChange: {}, 88 | handleZipCodeBlur: {}, 89 | handleZipCodeNotExists: {} 90 | }); 91 | } 92 | 93 | handleZipCodeChange = (e) => { 94 | this.setState({ 95 | [e.zipFieldName]: e.zipValue, 96 | handleZipCodeChange: e, 97 | handleDistrictChange: {}, 98 | handleCountyChange: {}, 99 | handleZipCodeBlur: {}, 100 | handleZipCodeNotExists: {} 101 | }); 102 | } 103 | 104 | handleZipCodeBlur = (e) => { 105 | const {countyFieldName, countyValue, districtFieldName, districtValue, zipFieldName, zipValue} = e; 106 | this.setState({ 107 | [zipFieldName]: zipValue, 108 | [countyFieldName]: countyValue, 109 | [districtFieldName]: districtValue, 110 | handleZipCodeBlur: e, 111 | handleDistrictChange: {}, 112 | handleCountyChange: {}, 113 | handleZipCodeChange: {}, 114 | handleZipCodeNotExists: {} 115 | }); 116 | } 117 | 118 | handleZipCodeNotExists = (e) => { 119 | const {countyFieldName, countyValue, districtFieldName, districtValue, zipFieldName, zipValue, origZipValue} = e; 120 | this.setState({ 121 | [zipFieldName]: zipValue, 122 | [countyFieldName]: countyValue, 123 | [districtFieldName]: districtValue, 124 | handleZipCodeNotExists: e, 125 | handleZipCodeBlur: {}, 126 | handleDistrictChange: {}, 127 | handleCountyChange: {}, 128 | handleZipCodeChange: {} 129 | }); 130 | 131 | swal('郵遞區號不存在: ' + origZipValue, '', 'error'); 132 | } 133 | 134 | render() { 135 | let countyRtn = JSON.stringify(this.state.handleCountyChange); 136 | let districtRtn = JSON.stringify(this.state.handleDistrictChange); 137 | let zipCodeRtn = JSON.stringify(this.state.handleZipCodeChange); 138 | let zipBlurRtn = JSON.stringify(this.state.handleZipCodeBlur); 139 | let zipNotExistsRtn = JSON.stringify(this.state.handleZipCodeNotExists); 140 | let fullAddress = this.state.countyValue1 + this.state.districtValue1 141 | + this.state.address; 142 | let addressShow = this.state.displayType === 'display' ? 'none' : 'inline'; 143 | return ( 144 |
145 |
146 |
Live Demo 149 |
152 | Github 153 | Npmjs 155 |
156 |
157 |
158 | 159 | ZipCodeTW with Bootstrap CSS 160 | 161 |

DisplayType: 'text'

162 |
163 | 182 | 183 | 193 |
194 | 195 |
196 | 197 |
198 | 218 |
219 |
220 |
221 |

DisplayType: 'display'

222 | 223 |
224 | 232 |
233 |
234 |
235 | 236 |
237 | 248 |
249 |
250 | 251 |
252 | 253 |
254 | 265 |
266 |
267 | 268 |

269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 285 | 286 | 291 | 292 | 293 | 294 | 299 | 300 | 305 | 306 | 307 | 308 | 312 | 313 | 318 | 319 | 320 | 321 | 325 | 326 | 331 | 332 | 333 |
PropValuePropValue
countyClass 284 | countyStyle 290 |
districtClass 298 | districtStyle 304 |
zipCodeClass 311 | zipCodeStyle 317 |
addressClass 324 | addressStyle 330 |
334 | 335 |

336 | 337 |
338 |
{countyRtn}
339 |
340 | 341 |
342 |
{districtRtn}
343 |
344 | 345 |
346 |
{zipCodeRtn}
347 |
348 | 349 |
350 |
{zipBlurRtn}
351 |
352 | 353 |
354 |
{zipNotExistsRtn}
355 |
356 |
357 |
358 |
359 | ); 360 | } 361 | } 362 | 363 | window.app = {}; 364 | 365 | app.create = (dom) => { 366 | ReactDOM.render( 367 | , 368 | dom 369 | ) 370 | }; --------------------------------------------------------------------------------