├── .babelrc
├── .editorconfig
├── .eslintrc
├── .gitignore
├── .stylelintrc
├── README.md
├── bin
├── dev-server.js
├── lib
│ ├── open-browser.js
│ └── open-chrome.applescript
└── spa-server.js
├── dist
└── _redirects
├── package-lock.json
├── package.json
├── src
├── components
│ ├── App.vue
│ └── FormDate.vue
└── main.js
├── static
└── index.html
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "modules": false
7 | }
8 | ]
9 | ],
10 | "plugins": [
11 | "@babel/plugin-syntax-dynamic-import"
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | charset = utf-8
7 | end_of_line = lf
8 | indent_size = 2
9 | indent_style = space
10 | insert_final_newline = true
11 | trim_trailing_whitespace = true
12 |
13 | [*.md]
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "env": {
4 | "browser": true,
5 | "node": true
6 | },
7 | "parserOptions": {
8 | "parser": "babel-eslint",
9 | "sourceType": "module"
10 | },
11 | "extends": [
12 | "@avalanche/eslint-config",
13 | "plugin:vue/recommended"
14 | ],
15 | "rules": {
16 | "vue/require-default-prop": 0
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Numerous always-ignore extensions
2 | *.diff
3 | *.err
4 | *.log
5 | *.orig
6 | *.rej
7 | *.swo
8 | *.swp
9 | *.tgz
10 | *.vi
11 | *.zip
12 | *~
13 |
14 | # OS or Editor folders
15 | ._*
16 | .cache
17 | .DS_Store
18 | .idea
19 | .project
20 | .settings
21 | .tmproj
22 | *.esproj
23 | *.sublime-project
24 | *.sublime-workspace
25 | nbproject
26 | Thumbs.db
27 |
28 | # Folders to ignore
29 | dist/*
30 | !dist/_redirects
31 | node_modules
32 |
--------------------------------------------------------------------------------
/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@avalanche/stylelint-config",
3 | "processors": [
4 | "@mapbox/stylelint-processor-arbitrary-tags"
5 | ],
6 | "rules": {
7 | "max-nesting-depth": 4,
8 | "no-empty-source": null
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Building a Date Input Component with Vue.js
2 |
3 | [](https://www.patreon.com/maoberlehner)
4 | [](https://paypal.me/maoberlehner)
5 |
6 | This is an example project for the following article: [Building a Date Input Component with Vue.js](https://markus.oberlehner.net/blog/building-a-date-input-component-with-vue/)
7 |
8 | ## Build Setup
9 |
10 | ``` bash
11 | # Install dependencies.
12 | npm install
13 |
14 | # Serve with hot reload.
15 | npm start
16 |
17 | # Build for production with minification.
18 | npm run build
19 |
20 | # Serve production build.
21 | npm run serve:production
22 | ```
23 |
24 | ## About
25 |
26 | ### Author
27 |
28 | Markus Oberlehner
29 | Website: https://markus.oberlehner.net
30 | Twitter: https://twitter.com/MaOberlehner
31 | PayPal.me: https://paypal.me/maoberlehner
32 | Patreon: https://www.patreon.com/maoberlehner
33 |
34 | ### License
35 |
36 | MIT
37 |
--------------------------------------------------------------------------------
/bin/dev-server.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | const serve = require(`webpack-serve`);
3 |
4 | const openBrowser = require(`./lib/open-browser`);
5 | const config = require(`../webpack.config`);
6 |
7 | serve({ config, clipboard: false }).then((server) => {
8 | server.on(`listening`, () => {
9 | openBrowser(`http://${server.options.host}:${server.options.port}`);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/bin/lib/open-browser.js:
--------------------------------------------------------------------------------
1 | const { execSync } = require(`child_process`);
2 | const opn = require(`opn`);
3 |
4 | module.exports = function openBrowser(url) {
5 | // If we're on OS X, we can try opening
6 | // Chrome with AppleScript. This lets us reuse an
7 | // existing tab when possible instead of creating a new one.
8 | // See: https://github.com/vuejs/vue-cli/blob/dev/packages/%40vue/cli-shared-utils/lib/openBrowser.js
9 | const browser = process.env.BROWSER;
10 | const shouldTryOpenChromeWithAppleScript =
11 | process.platform === `darwin` &&
12 | (typeof browser !== `string` || browser === `google chrome`);
13 |
14 | if (shouldTryOpenChromeWithAppleScript) {
15 | try {
16 | execSync(`ps cax | grep "Google Chrome"`);
17 | execSync(`osascript open-chrome.applescript "${encodeURI(url)}"`, {
18 | cwd: __dirname,
19 | stdio: `ignore`,
20 | });
21 |
22 | return true;
23 | } catch (error) {
24 | // Ignore errors.
25 | }
26 | }
27 |
28 | try {
29 | opn(url).catch(() => {}); // Prevent `unhandledRejection` error.
30 |
31 | return true;
32 | } catch (error) {
33 | return false;
34 | }
35 | };
36 |
--------------------------------------------------------------------------------
/bin/lib/open-chrome.applescript:
--------------------------------------------------------------------------------
1 | (*
2 | Copyright (c) 2015-present, Facebook, Inc.
3 |
4 | This source code is licensed under the MIT license found in the
5 | LICENSE file at
6 | https://github.com/facebookincubator/create-react-app/blob/master/LICENSE
7 | *)
8 |
9 | property targetTab: null
10 | property targetTabIndex: -1
11 | property targetWindow: null
12 |
13 | on run argv
14 | set theURL to item 1 of argv
15 |
16 | tell application "Chrome"
17 |
18 | if (count every window) = 0 then
19 | make new window
20 | end if
21 |
22 | -- 1: Looking for tab running debugger
23 | -- then, Reload debugging tab if found
24 | -- then return
25 | set found to my lookupTabWithUrl(theURL)
26 | if found then
27 | set targetWindow's active tab index to targetTabIndex
28 | tell targetTab to reload
29 | tell targetWindow to activate
30 | set index of targetWindow to 1
31 | return
32 | end if
33 |
34 | -- 2: Looking for Empty tab
35 | -- In case debugging tab was not found
36 | -- We try to find an empty tab instead
37 | set found to my lookupTabWithUrl("chrome://newtab/")
38 | if found then
39 | set targetWindow's active tab index to targetTabIndex
40 | set URL of targetTab to theURL
41 | tell targetWindow to activate
42 | return
43 | end if
44 |
45 | -- 3: Create new tab
46 | -- both debugging and empty tab were not found
47 | -- make a new tab with url
48 | tell window 1
49 | activate
50 | make new tab with properties {URL:theURL}
51 | end tell
52 | end tell
53 | end run
54 |
55 | -- Function:
56 | -- Lookup tab with given url
57 | -- if found, store tab, index, and window in properties
58 | -- (properties were declared on top of file)
59 | on lookupTabWithUrl(lookupUrl)
60 | tell application "Chrome"
61 | -- Find a tab with the given url
62 | set found to false
63 | set theTabIndex to -1
64 | repeat with theWindow in every window
65 | set theTabIndex to 0
66 | repeat with theTab in every tab of theWindow
67 | set theTabIndex to theTabIndex + 1
68 | if (theTab's URL as string) contains lookupUrl then
69 | -- assign tab, tab index, and window to properties
70 | set targetTab to theTab
71 | set targetTabIndex to theTabIndex
72 | set targetWindow to theWindow
73 | set found to true
74 | exit repeat
75 | end if
76 | end repeat
77 |
78 | if found then
79 | exit repeat
80 | end if
81 | end repeat
82 | end tell
83 | return found
84 | end lookupTabWithUrl
85 |
--------------------------------------------------------------------------------
/bin/spa-server.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | const compression = require(`compression`);
3 | const express = require(`express`);
4 | const path = require(`path`);
5 |
6 | const openBrowser = require(`./lib/open-browser`);
7 |
8 | const app = express();
9 | const publicPath = path.join(process.cwd(), `dist`);
10 | const port = 5000;
11 |
12 | app.use(compression());
13 | app.use(`/`, express.static(publicPath, { index: false }));
14 | app.get(`/*`, (request, response) => {
15 | response.sendFile(`${publicPath}/index.html`);
16 | });
17 |
18 | app.listen(port);
19 |
20 | // eslint-disable-next-line no-console
21 | console.log(`Server started!`);
22 | // eslint-disable-next-line no-console
23 | console.log(`http://localhost:${port}`);
24 |
25 | openBrowser(`http://localhost:${port}`);
26 |
--------------------------------------------------------------------------------
/dist/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
2 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "building-a-date-input-component-with-vue",
3 | "version": "0.1.0",
4 | "author": "Markus Oberlehner",
5 | "homepage": "https://github.com/maoberlehner/building-a-date-input-component-with-vue",
6 | "license": "MIT",
7 | "private": true,
8 | "scripts": {
9 | "start": "npm run serve:dev",
10 | "build": "cross-env NODE_ENV=production webpack",
11 | "serve:dev": "cross-env NODE_ENV=development node bin/dev-server.js",
12 | "serve:production": "cross-env NODE_ENV=production node bin/spa-server.js",
13 | "lint:scripts": "eslint --ext .js,.vue --ignore-path .gitignore .",
14 | "lint:styles": "stylelint --syntax scss 'src/**/*.+(scss|vue)'",
15 | "lint": "npm run lint:scripts && npm run lint:styles"
16 | },
17 | "dependencies": {
18 | "normalize.css": "^8.0.0",
19 | "reset-css": "^3.0.0",
20 | "vue": "^2.5.16"
21 | },
22 | "devDependencies": {
23 | "@avalanche/eslint-config": "^2.0.0",
24 | "@avalanche/stylelint-config": "^0.1.2",
25 | "@babel/core": "^7.0.0-beta.44",
26 | "@babel/plugin-syntax-dynamic-import": "^7.0.0-beta.44",
27 | "@babel/preset-env": "^7.0.0-beta.44",
28 | "@mapbox/stylelint-processor-arbitrary-tags": "^0.2.0",
29 | "babel-eslint": "^8.2.3",
30 | "babel-loader": "^8.0.0-beta.2",
31 | "compression": "^1.7.2",
32 | "cross-env": "^5.1.4",
33 | "css-loader": "^0.28.11",
34 | "eslint": "^4.19.1",
35 | "eslint-plugin-import": "^2.11.0",
36 | "eslint-plugin-vue": "^4.4.0",
37 | "express": "^4.16.3",
38 | "html-webpack-plugin": "^3.2.0",
39 | "mini-css-extract-plugin": "^0.4.0",
40 | "node-sass": "^4.8.3",
41 | "node-sass-magic-importer": "^5.1.2",
42 | "opn": "^5.3.0",
43 | "optimize-css-assets-webpack-plugin": "^4.0.0",
44 | "sass-loader": "^7.0.1",
45 | "stylelint": "^9.2.0",
46 | "uglifyjs-webpack-plugin": "^1.2.4",
47 | "vue-loader": "^15.0.0-beta.7",
48 | "vue-template-compiler": "^2.5.16",
49 | "webpack": "^4.5.0",
50 | "webpack-cli": "^2.0.14",
51 | "webpack-serve": "^0.3.1"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/components/App.vue:
--------------------------------------------------------------------------------
1 |
2 |