├── .babelrc
├── .editorconfig
├── .gitignore
├── .postcssrc.js
├── Dockerfile
├── README.md
├── build
├── build.js
├── check-versions.js
├── logo.png
├── utils.js
├── vue-loader.conf.js
├── webpack.base.conf.js
├── webpack.dev.conf.js
├── webpack.prod.conf.js
└── webpack.test.conf.js
├── config
├── dev.env.js
├── index.js
├── prod.env.js
└── test.env.js
├── index.html
├── package.json
├── src
├── App.vue
├── assets
│ ├── logo.png
│ └── scss
│ │ ├── _styles.scss
│ │ ├── _theming.scss
│ │ ├── _var.scss
│ │ └── main.scss
├── components
│ ├── form_elements
│ │ ├── Elements.vue
│ │ ├── FormElementButton.vue
│ │ ├── FormElementCheckbox.vue
│ │ ├── FormElementDatePicker.vue
│ │ ├── FormElementDatetimePicker.vue
│ │ ├── FormElementLongTextInput.vue
│ │ ├── FormElementNumberInput.vue
│ │ ├── FormElementRadioButton.vue
│ │ ├── FormElementRating.vue
│ │ ├── FormElementSelectList.vue
│ │ ├── FormElementTextEditor.vue
│ │ ├── FormElementTextInput.vue
│ │ ├── FormElementTimePicker.vue
│ │ ├── Properties.vue
│ │ ├── Theming.vue
│ │ └── formbuilder.js
│ └── layouts
│ │ └── NavBar.vue
├── layouts
│ └── Default.vue
├── main.js
├── router
│ └── index.js
├── store
│ └── store.js
└── views
│ ├── Home.vue
│ └── Preview.vue
├── static
└── .gitkeep
├── test
└── unit
│ ├── .eslintrc
│ ├── index.js
│ ├── karma.conf.js
│ └── specs
│ └── HelloWorld.spec.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", {
4 | "modules": false,
5 | "targets": {
6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
7 | }
8 | }],
9 | "stage-2"
10 | ],
11 | "plugins": ["transform-vue-jsx", "transform-runtime"],
12 | "env": {
13 | "test": {
14 | "presets": ["env", "stage-2"],
15 | "plugins": ["transform-vue-jsx", "istanbul"]
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | /dist/
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | /test/unit/coverage/
8 |
9 | # Editor directories and files
10 | .idea
11 | .vscode
12 | *.suo
13 | *.ntvs*
14 | *.njsproj
15 | *.sln
16 |
--------------------------------------------------------------------------------
/.postcssrc.js:
--------------------------------------------------------------------------------
1 | // https://github.com/michael-ciniawsky/postcss-load-config
2 |
3 | module.exports = {
4 | "plugins": {
5 | "postcss-import": {},
6 | "postcss-url": {},
7 | // to edit target browsers: use "browserslist" field in package.json
8 | "autoprefixer": {}
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Use an older Node.js version compatible with your project
2 | FROM node:8
3 |
4 | # Set the working directory
5 | WORKDIR /app
6 |
7 | # Copy package.json and package-lock.json
8 | COPY package*.json ./
9 |
10 | # Install dependencies
11 | RUN npm install
12 |
13 | # Copy the rest of the application code
14 | COPY . .
15 |
16 | # Build the Vue.js application
17 | RUN npm run build
18 |
19 | # Use a simple HTTP server to serve the static files
20 | RUN npm install -g http-server
21 |
22 | # Expose the port your app runs on
23 | EXPOSE 8080
24 |
25 | # Command to serve the built files
26 | CMD [ "http-server", "dist" ]
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Vue Form Builder
2 |
3 | A sample drag & drop form builder project that made with Vue.js and Element UI ([View Live Demo](https://vue-formbuilder.netlify.com/))
4 |
5 | 
6 |
7 |
8 |
9 | ## Build Setup
10 |
11 | ``` bash
12 | # install dependencies
13 | npm install
14 |
15 | # serve with hot reload at localhost:8080
16 | npm run dev
17 |
18 | # build for production with minification
19 | npm run build
20 |
21 | # build for production and view the bundle analyzer report
22 | npm run build --report
23 |
24 | # run unit tests
25 | npm run unit
26 |
27 | # run all tests
28 | npm test
29 | ```
30 |
31 | For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader).
32 |
33 | ## To run Docker
34 | docker build -t vueformbuilder .
35 |
36 | docker run -it -p 8080:8080 --rm --name dockerize-vuejs-app vueformbuilder
--------------------------------------------------------------------------------
/build/build.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | require('./check-versions')()
3 |
4 | process.env.NODE_ENV = 'production'
5 |
6 | const ora = require('ora')
7 | const rm = require('rimraf')
8 | const path = require('path')
9 | const chalk = require('chalk')
10 | const webpack = require('webpack')
11 | const config = require('../config')
12 | const webpackConfig = require('./webpack.prod.conf')
13 |
14 | const spinner = ora('building for production...')
15 | spinner.start()
16 |
17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
18 | if (err) throw err
19 | webpack(webpackConfig, (err, stats) => {
20 | spinner.stop()
21 | if (err) throw err
22 | process.stdout.write(stats.toString({
23 | colors: true,
24 | modules: false,
25 | children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
26 | chunks: false,
27 | chunkModules: false
28 | }) + '\n\n')
29 |
30 | if (stats.hasErrors()) {
31 | console.log(chalk.red(' Build failed with errors.\n'))
32 | process.exit(1)
33 | }
34 |
35 | console.log(chalk.cyan(' Build complete.\n'))
36 | console.log(chalk.yellow(
37 | ' Tip: built files are meant to be served over an HTTP server.\n' +
38 | ' Opening index.html over file:// won\'t work.\n'
39 | ))
40 | })
41 | })
42 |
--------------------------------------------------------------------------------
/build/check-versions.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const chalk = require('chalk')
3 | const semver = require('semver')
4 | const packageConfig = require('../package.json')
5 | const shell = require('shelljs')
6 |
7 | function exec (cmd) {
8 | return require('child_process').execSync(cmd).toString().trim()
9 | }
10 |
11 | const versionRequirements = [
12 | {
13 | name: 'node',
14 | currentVersion: semver.clean(process.version),
15 | versionRequirement: packageConfig.engines.node
16 | }
17 | ]
18 |
19 | if (shell.which('npm')) {
20 | versionRequirements.push({
21 | name: 'npm',
22 | currentVersion: exec('npm --version'),
23 | versionRequirement: packageConfig.engines.npm
24 | })
25 | }
26 |
27 | module.exports = function () {
28 | const warnings = []
29 |
30 | for (let i = 0; i < versionRequirements.length; i++) {
31 | const mod = versionRequirements[i]
32 |
33 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
34 | warnings.push(mod.name + ': ' +
35 | chalk.red(mod.currentVersion) + ' should be ' +
36 | chalk.green(mod.versionRequirement)
37 | )
38 | }
39 | }
40 |
41 | if (warnings.length) {
42 | console.log('')
43 | console.log(chalk.yellow('To use this template, you must update following to modules:'))
44 | console.log()
45 |
46 | for (let i = 0; i < warnings.length; i++) {
47 | const warning = warnings[i]
48 | console.log(' ' + warning)
49 | }
50 |
51 | console.log()
52 | process.exit(1)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/build/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jmeei/vue-formbuilder/500e783fd92c8e3121a24c0496313b96d18b1739/build/logo.png
--------------------------------------------------------------------------------
/build/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const config = require('../config')
4 | const ExtractTextPlugin = require('extract-text-webpack-plugin')
5 | const packageConfig = require('../package.json')
6 |
7 | exports.assetsPath = function (_path) {
8 | const assetsSubDirectory = process.env.NODE_ENV === 'production'
9 | ? config.build.assetsSubDirectory
10 | : config.dev.assetsSubDirectory
11 |
12 | return path.posix.join(assetsSubDirectory, _path)
13 | }
14 |
15 | exports.cssLoaders = function (options) {
16 | options = options || {}
17 |
18 | const cssLoader = {
19 | loader: 'css-loader',
20 | options: {
21 | sourceMap: options.sourceMap
22 | }
23 | }
24 |
25 | const postcssLoader = {
26 | loader: 'postcss-loader',
27 | options: {
28 | sourceMap: options.sourceMap
29 | }
30 | }
31 |
32 | // generate loader string to be used with extract text plugin
33 | function generateLoaders (loader, loaderOptions) {
34 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
35 |
36 | if (loader) {
37 | loaders.push({
38 | loader: loader + '-loader',
39 | options: Object.assign({}, loaderOptions, {
40 | sourceMap: options.sourceMap
41 | })
42 | })
43 | }
44 |
45 | // Extract CSS when that option is specified
46 | // (which is the case during production build)
47 | if (options.extract) {
48 | return ExtractTextPlugin.extract({
49 | use: loaders,
50 | fallback: 'vue-style-loader'
51 | })
52 | } else {
53 | return ['vue-style-loader'].concat(loaders)
54 | }
55 | }
56 |
57 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html
58 | return {
59 | css: generateLoaders(),
60 | postcss: generateLoaders(),
61 | less: generateLoaders('less'),
62 | sass: generateLoaders('sass', { indentedSyntax: true }),
63 | scss: generateLoaders('sass'),
64 | stylus: generateLoaders('stylus'),
65 | styl: generateLoaders('stylus')
66 | }
67 | }
68 |
69 | // Generate loaders for standalone style files (outside of .vue)
70 | exports.styleLoaders = function (options) {
71 | const output = []
72 | const loaders = exports.cssLoaders(options)
73 |
74 | for (const extension in loaders) {
75 | const loader = loaders[extension]
76 | output.push({
77 | test: new RegExp('\\.' + extension + '$'),
78 | use: loader
79 | })
80 | }
81 |
82 | return output
83 | }
84 |
85 | exports.createNotifierCallback = () => {
86 | const notifier = require('node-notifier')
87 |
88 | return (severity, errors) => {
89 | if (severity !== 'error') return
90 |
91 | const error = errors[0]
92 | const filename = error.file && error.file.split('!').pop()
93 |
94 | notifier.notify({
95 | title: packageConfig.name,
96 | message: severity + ': ' + error.name,
97 | subtitle: filename || '',
98 | icon: path.join(__dirname, 'logo.png')
99 | })
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/build/vue-loader.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const utils = require('./utils')
3 | const config = require('../config')
4 | const isProduction = process.env.NODE_ENV === 'production'
5 | const sourceMapEnabled = isProduction
6 | ? config.build.productionSourceMap
7 | : config.dev.cssSourceMap
8 |
9 | module.exports = {
10 | loaders: utils.cssLoaders({
11 | sourceMap: sourceMapEnabled,
12 | extract: isProduction
13 | }),
14 | cssSourceMap: sourceMapEnabled,
15 | cacheBusting: config.dev.cacheBusting,
16 | transformToRequire: {
17 | video: ['src', 'poster'],
18 | source: 'src',
19 | img: 'src',
20 | image: 'xlink:href'
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/build/webpack.base.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const utils = require('./utils')
4 | const config = require('../config')
5 | const vueLoaderConfig = require('./vue-loader.conf')
6 |
7 | function resolve (dir) {
8 | return path.join(__dirname, '..', dir)
9 | }
10 |
11 |
12 |
13 | module.exports = {
14 | context: path.resolve(__dirname, '../'),
15 | entry: {
16 | app: './src/main.js'
17 | },
18 | output: {
19 | path: config.build.assetsRoot,
20 | filename: '[name].js',
21 | publicPath: process.env.NODE_ENV === 'production'
22 | ? config.build.assetsPublicPath
23 | : config.dev.assetsPublicPath
24 | },
25 | resolve: {
26 | extensions: ['*', '.js', '.vue', '.json'],
27 | alias: {
28 | 'vue$': 'vue/dist/vue.esm.js',
29 | '@': resolve('src'),
30 | }
31 | },
32 | module: {
33 | rules: [
34 | {
35 | test: /\.vue$/,
36 | loader: 'vue-loader',
37 | options: vueLoaderConfig
38 | },
39 | {
40 | test: /\.js$/,
41 | loader: 'babel-loader',
42 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
43 | },
44 | {
45 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
46 | loader: 'url-loader',
47 | options: {
48 | limit: 10000,
49 | name: utils.assetsPath('img/[name].[hash:7].[ext]')
50 | }
51 | },
52 | {
53 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
54 | loader: 'url-loader',
55 | options: {
56 | limit: 10000,
57 | name: utils.assetsPath('media/[name].[hash:7].[ext]')
58 | }
59 | },
60 | {
61 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
62 | loader: 'url-loader',
63 | options: {
64 | limit: 10000,
65 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
66 | }
67 | }
68 | ]
69 | },
70 | node: {
71 | // prevent webpack from injecting useless setImmediate polyfill because Vue
72 | // source contains it (although only uses it if it's native).
73 | setImmediate: false,
74 | // prevent webpack from injecting mocks to Node native modules
75 | // that does not make sense for the client
76 | dgram: 'empty',
77 | fs: 'empty',
78 | net: 'empty',
79 | tls: 'empty',
80 | child_process: 'empty'
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/build/webpack.dev.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const utils = require('./utils')
3 | const webpack = require('webpack')
4 | const config = require('../config')
5 | const merge = require('webpack-merge')
6 | const path = require('path')
7 | const baseWebpackConfig = require('./webpack.base.conf')
8 | const CopyWebpackPlugin = require('copy-webpack-plugin')
9 | const HtmlWebpackPlugin = require('html-webpack-plugin')
10 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
11 | const portfinder = require('portfinder')
12 |
13 | const HOST = process.env.HOST
14 | const PORT = process.env.PORT && Number(process.env.PORT)
15 |
16 | const devWebpackConfig = merge(baseWebpackConfig, {
17 | module: {
18 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
19 | },
20 | // cheap-module-eval-source-map is faster for development
21 | devtool: config.dev.devtool,
22 |
23 | // these devServer options should be customized in /config/index.js
24 | devServer: {
25 | clientLogLevel: 'warning',
26 | historyApiFallback: {
27 | rewrites: [
28 | { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
29 | ],
30 | },
31 | hot: true,
32 | contentBase: false, // since we use CopyWebpackPlugin.
33 | compress: true,
34 | host: HOST || config.dev.host,
35 | port: PORT || config.dev.port,
36 | open: config.dev.autoOpenBrowser,
37 | overlay: config.dev.errorOverlay
38 | ? { warnings: false, errors: true }
39 | : false,
40 | publicPath: config.dev.assetsPublicPath,
41 | proxy: config.dev.proxyTable,
42 | quiet: true, // necessary for FriendlyErrorsPlugin
43 | watchOptions: {
44 | poll: config.dev.poll,
45 | }
46 | },
47 | plugins: [
48 | new webpack.DefinePlugin({
49 | 'process.env': require('../config/dev.env')
50 | }),
51 | new webpack.HotModuleReplacementPlugin(),
52 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
53 | new webpack.NoEmitOnErrorsPlugin(),
54 | // https://github.com/ampedandwired/html-webpack-plugin
55 | new HtmlWebpackPlugin({
56 | filename: 'index.html',
57 | template: 'index.html',
58 | inject: true
59 | }),
60 | // copy custom static assets
61 | new CopyWebpackPlugin([
62 | {
63 | from: path.resolve(__dirname, '../static'),
64 | to: config.dev.assetsSubDirectory,
65 | ignore: ['.*']
66 | }
67 | ])
68 | ]
69 | })
70 |
71 | module.exports = new Promise((resolve, reject) => {
72 | portfinder.basePort = process.env.PORT || config.dev.port
73 | portfinder.getPort((err, port) => {
74 | if (err) {
75 | reject(err)
76 | } else {
77 | // publish the new Port, necessary for e2e tests
78 | process.env.PORT = port
79 | // add port to devServer config
80 | devWebpackConfig.devServer.port = port
81 |
82 | // Add FriendlyErrorsPlugin
83 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
84 | compilationSuccessInfo: {
85 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
86 | },
87 | onErrors: config.dev.notifyOnErrors
88 | ? utils.createNotifierCallback()
89 | : undefined
90 | }))
91 |
92 | resolve(devWebpackConfig)
93 | }
94 | })
95 | })
96 |
--------------------------------------------------------------------------------
/build/webpack.prod.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const utils = require('./utils')
4 | const webpack = require('webpack')
5 | const config = require('../config')
6 | const merge = require('webpack-merge')
7 | const baseWebpackConfig = require('./webpack.base.conf')
8 | const CopyWebpackPlugin = require('copy-webpack-plugin')
9 | const HtmlWebpackPlugin = require('html-webpack-plugin')
10 | const ExtractTextPlugin = require('extract-text-webpack-plugin')
11 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
12 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
13 |
14 | const env = process.env.NODE_ENV === 'testing'
15 | ? require('../config/test.env')
16 | : require('../config/prod.env')
17 |
18 | const webpackConfig = merge(baseWebpackConfig, {
19 | module: {
20 | rules: utils.styleLoaders({
21 | sourceMap: config.build.productionSourceMap,
22 | extract: true,
23 | usePostCSS: true
24 | })
25 | },
26 | devtool: config.build.productionSourceMap ? config.build.devtool : false,
27 | output: {
28 | path: config.build.assetsRoot,
29 | filename: utils.assetsPath('js/[name].[chunkhash].js'),
30 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
31 | },
32 | plugins: [
33 | // http://vuejs.github.io/vue-loader/en/workflow/production.html
34 | new webpack.DefinePlugin({
35 | 'process.env': env
36 | }),
37 | new UglifyJsPlugin({
38 | uglifyOptions: {
39 | compress: {
40 | warnings: false
41 | }
42 | },
43 | sourceMap: config.build.productionSourceMap,
44 | parallel: true
45 | }),
46 | // extract css into its own file
47 | new ExtractTextPlugin({
48 | filename: utils.assetsPath('css/[name].[contenthash].css'),
49 | // Setting the following option to `false` will not extract CSS from codesplit chunks.
50 | // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
51 | // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`,
52 | // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
53 | allChunks: true,
54 | }),
55 | // Compress extracted CSS. We are using this plugin so that possible
56 | // duplicated CSS from different components can be deduped.
57 | new OptimizeCSSPlugin({
58 | cssProcessorOptions: config.build.productionSourceMap
59 | ? { safe: true, map: { inline: false } }
60 | : { safe: true }
61 | }),
62 | // generate dist index.html with correct asset hash for caching.
63 | // you can customize output by editing /index.html
64 | // see https://github.com/ampedandwired/html-webpack-plugin
65 | new HtmlWebpackPlugin({
66 | filename: process.env.NODE_ENV === 'testing'
67 | ? 'index.html'
68 | : config.build.index,
69 | template: 'index.html',
70 | inject: true,
71 | minify: {
72 | removeComments: true,
73 | collapseWhitespace: true,
74 | removeAttributeQuotes: true
75 | // more options:
76 | // https://github.com/kangax/html-minifier#options-quick-reference
77 | },
78 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin
79 | chunksSortMode: 'dependency'
80 | }),
81 | // keep module.id stable when vendor modules does not change
82 | new webpack.HashedModuleIdsPlugin(),
83 | // enable scope hoisting
84 | new webpack.optimize.ModuleConcatenationPlugin(),
85 | // split vendor js into its own file
86 | new webpack.optimize.CommonsChunkPlugin({
87 | name: 'vendor',
88 | minChunks (module) {
89 | // any required modules inside node_modules are extracted to vendor
90 | return (
91 | module.resource &&
92 | /\.js$/.test(module.resource) &&
93 | module.resource.indexOf(
94 | path.join(__dirname, '../node_modules')
95 | ) === 0
96 | )
97 | }
98 | }),
99 | // extract webpack runtime and module manifest to its own file in order to
100 | // prevent vendor hash from being updated whenever app bundle is updated
101 | new webpack.optimize.CommonsChunkPlugin({
102 | name: 'manifest',
103 | minChunks: Infinity
104 | }),
105 | // This instance extracts shared chunks from code splitted chunks and bundles them
106 | // in a separate chunk, similar to the vendor chunk
107 | // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
108 | new webpack.optimize.CommonsChunkPlugin({
109 | name: 'app',
110 | async: 'vendor-async',
111 | children: true,
112 | minChunks: 3
113 | }),
114 |
115 | // copy custom static assets
116 | new CopyWebpackPlugin([
117 | {
118 | from: path.resolve(__dirname, '../static'),
119 | to: config.build.assetsSubDirectory,
120 | ignore: ['.*']
121 | }
122 | ])
123 | ]
124 | })
125 |
126 | if (config.build.productionGzip) {
127 | const CompressionWebpackPlugin = require('compression-webpack-plugin')
128 |
129 | webpackConfig.plugins.push(
130 | new CompressionWebpackPlugin({
131 | asset: '[path].gz[query]',
132 | algorithm: 'gzip',
133 | test: new RegExp(
134 | '\\.(' +
135 | config.build.productionGzipExtensions.join('|') +
136 | ')$'
137 | ),
138 | threshold: 10240,
139 | minRatio: 0.8
140 | })
141 | )
142 | }
143 |
144 | if (config.build.bundleAnalyzerReport) {
145 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
146 | webpackConfig.plugins.push(new BundleAnalyzerPlugin())
147 | }
148 |
149 | module.exports = webpackConfig
150 |
--------------------------------------------------------------------------------
/build/webpack.test.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | // This is the webpack config used for unit tests.
3 |
4 | const utils = require('./utils')
5 | const webpack = require('webpack')
6 | const merge = require('webpack-merge')
7 | const baseWebpackConfig = require('./webpack.base.conf')
8 |
9 | const webpackConfig = merge(baseWebpackConfig, {
10 | // use inline sourcemap for karma-sourcemap-loader
11 | module: {
12 | rules: utils.styleLoaders()
13 | },
14 | devtool: '#inline-source-map',
15 | resolveLoader: {
16 | alias: {
17 | // necessary to to make lang="scss" work in test when using vue-loader's ?inject option
18 | // see discussion at https://github.com/vuejs/vue-loader/issues/724
19 | 'scss-loader': 'sass-loader'
20 | }
21 | },
22 | plugins: [
23 | new webpack.DefinePlugin({
24 | 'process.env': require('../config/test.env')
25 | })
26 | ]
27 | })
28 |
29 | // no need for app entry during tests
30 | delete webpackConfig.entry
31 |
32 | module.exports = webpackConfig
33 |
--------------------------------------------------------------------------------
/config/dev.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const merge = require('webpack-merge')
3 | const prodEnv = require('./prod.env')
4 |
5 | module.exports = merge(prodEnv, {
6 | NODE_ENV: '"development"'
7 | })
8 |
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | // Template version: 1.3.1
3 | // see http://vuejs-templates.github.io/webpack for documentation.
4 |
5 | const path = require('path')
6 |
7 | module.exports = {
8 | dev: {
9 |
10 | // Paths
11 | assetsSubDirectory: 'static',
12 | assetsPublicPath: '/',
13 | proxyTable: {},
14 |
15 | // Various Dev Server settings
16 | host: 'localhost', // can be overwritten by process.env.HOST
17 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
18 | autoOpenBrowser: false,
19 | errorOverlay: true,
20 | notifyOnErrors: true,
21 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
22 |
23 |
24 | /**
25 | * Source Maps
26 | */
27 |
28 | // https://webpack.js.org/configuration/devtool/#development
29 | devtool: 'cheap-module-eval-source-map',
30 |
31 | // If you have problems debugging vue-files in devtools,
32 | // set this to false - it *may* help
33 | // https://vue-loader.vuejs.org/en/options.html#cachebusting
34 | cacheBusting: true,
35 |
36 | cssSourceMap: true
37 | },
38 |
39 | build: {
40 | // Template for index.html
41 | index: path.resolve(__dirname, '../dist/index.html'),
42 |
43 | // Paths
44 | assetsRoot: path.resolve(__dirname, '../dist'),
45 | assetsSubDirectory: 'static',
46 | assetsPublicPath: '/',
47 |
48 | /**
49 | * Source Maps
50 | */
51 |
52 | productionSourceMap: true,
53 | // https://webpack.js.org/configuration/devtool/#production
54 | devtool: '#source-map',
55 |
56 | // Gzip off by default as many popular static hosts such as
57 | // Surge or Netlify already gzip all static assets for you.
58 | // Before setting to `true`, make sure to:
59 | // npm install --save-dev compression-webpack-plugin
60 | productionGzip: false,
61 | productionGzipExtensions: ['js', 'css'],
62 |
63 | // Run the build command with an extra argument to
64 | // View the bundle analyzer report after build finishes:
65 | // `npm run build --report`
66 | // Set to `true` or `false` to always turn it on or off
67 | bundleAnalyzerReport: process.env.npm_config_report
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/config/prod.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = {
3 | NODE_ENV: '"production"'
4 | }
5 |
--------------------------------------------------------------------------------
/config/test.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const merge = require('webpack-merge')
3 | const devEnv = require('./dev.env')
4 |
5 | module.exports = merge(devEnv, {
6 | NODE_ENV: '"testing"'
7 | })
8 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | project
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "project",
3 | "version": "1.0.0",
4 | "description": "A Vue.js project",
5 | "author": "Jia Meei ",
6 | "private": true,
7 | "scripts": {
8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
9 | "start": "npm run dev",
10 | "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run",
11 | "test": "npm run unit",
12 | "build": "node build/build.js"
13 | },
14 | "dependencies": {
15 | "element-ui": "^2.4.5",
16 | "medium-editor": "^5.23.3",
17 | "vue": "^2.5.2",
18 | "vue-lodash": "^2.0.0",
19 | "vue-router": "^3.0.1",
20 | "vue-stash": "^2.0.1-beta",
21 | "vue2-animate": "^2.0.0",
22 | "vue2-medium-editor": "^1.1.5",
23 | "vuedraggable": "^2.16.0"
24 | },
25 | "devDependencies": {
26 | "autoprefixer": "^7.1.2",
27 | "babel-core": "^6.22.1",
28 | "babel-helper-vue-jsx-merge-props": "^2.0.3",
29 | "babel-loader": "^7.1.1",
30 | "babel-plugin-istanbul": "^4.1.1",
31 | "babel-plugin-syntax-jsx": "^6.18.0",
32 | "babel-plugin-transform-runtime": "^6.22.0",
33 | "babel-plugin-transform-vue-jsx": "^3.5.0",
34 | "babel-preset-env": "^1.3.2",
35 | "babel-preset-stage-2": "^6.22.0",
36 | "chai": "^4.1.2",
37 | "chalk": "^2.0.1",
38 | "copy-webpack-plugin": "^4.0.1",
39 | "cross-env": "^5.0.1",
40 | "css-loader": "^0.28.11",
41 | "extract-text-webpack-plugin": "^3.0.0",
42 | "file-loader": "^1.1.4",
43 | "friendly-errors-webpack-plugin": "^1.6.1",
44 | "html-webpack-plugin": "^2.30.1",
45 | "inject-loader": "^3.0.0",
46 | "karma": "^1.4.1",
47 | "karma-coverage": "^1.1.1",
48 | "karma-mocha": "^1.3.0",
49 | "karma-phantomjs-launcher": "^1.0.2",
50 | "karma-phantomjs-shim": "^1.4.0",
51 | "karma-sinon-chai": "^1.3.1",
52 | "karma-sourcemap-loader": "^0.3.7",
53 | "karma-spec-reporter": "0.0.31",
54 | "karma-webpack": "^2.0.2",
55 | "mocha": "^3.2.0",
56 | "node-notifier": "^5.1.2",
57 | "node-sass": "^4.9.3",
58 | "optimize-css-assets-webpack-plugin": "^3.2.0",
59 | "ora": "^1.2.0",
60 | "phantomjs-prebuilt": "^2.1.14",
61 | "portfinder": "^1.0.13",
62 | "postcss-import": "^11.0.0",
63 | "postcss-loader": "^2.0.8",
64 | "postcss-url": "^7.2.1",
65 | "rimraf": "^2.6.0",
66 | "sass-loader": "^7.1.0",
67 | "semver": "^5.3.0",
68 | "shelljs": "^0.7.6",
69 | "sinon": "^4.0.0",
70 | "sinon-chai": "^2.8.0",
71 | "style-loader": "^0.22.1",
72 | "uglifyjs-webpack-plugin": "^1.1.1",
73 | "url-loader": "^0.5.8",
74 | "vue-loader": "^13.7.2",
75 | "vue-style-loader": "^3.0.1",
76 | "vue-template-compiler": "^2.5.2",
77 | "webpack": "^3.6.0",
78 | "webpack-bundle-analyzer": "^2.9.0",
79 | "webpack-dev-server": "^2.9.1",
80 | "webpack-merge": "^4.1.0"
81 | },
82 | "engines": {
83 | "node": ">= 6.0.0",
84 | "npm": ">= 3.0.0"
85 | },
86 | "browserslist": [
87 | "> 1%",
88 | "last 2 versions",
89 | "not ie <= 8"
90 | ]
91 | }
92 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
23 |
24 |
25 |
26 |
38 |
39 |
42 |
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jmeei/vue-formbuilder/500e783fd92c8e3121a24c0496313b96d18b1739/src/assets/logo.png
--------------------------------------------------------------------------------
/src/assets/scss/_styles.scss:
--------------------------------------------------------------------------------
1 | // Reset css
2 | html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote{&:before,&:after{content:'';content:none}}q{&:before,&:after{content:'';content:none}}table{border-collapse:collapse;border-spacing:0}
3 |
4 | // Layout
5 | html, body, body > div, .app-wrapper, #app, .el-container {
6 | height: 100%;
7 | }
8 |
9 | body {
10 | font-family: 'Roboto', monospace;
11 | background: #78b395;
12 | }
13 |
14 | .demo-wrapper {
15 | position: relative;
16 | }
17 |
18 | .demo-header {
19 | position: absolute;
20 | top: 20px;
21 | font-size: 90px;
22 | max-width: 600px;
23 | color: #609279;
24 | letter-spacing: -1px;
25 | }
26 |
27 | .demo-footer {
28 | position: absolute;
29 | bottom: 40px;
30 | left: 20px;
31 |
32 | a {
33 | text-decoration: none;
34 | color: inherit;
35 | background: #609279;
36 | padding: 10px 20px;
37 | border-radius: 30px;
38 | }
39 | }
40 |
41 | .app-wrapper {
42 | display: flex;
43 | align-items: center;
44 | justify-content: center;
45 | position: relative;
46 | }
47 |
48 | #app {
49 | width: 1200px;
50 | height: 80vh;
51 | background: white;
52 | border: 1px solid black;
53 | box-shadow: 0 5px 17px 4px rgba(78, 78, 78, 0.2);
54 | }
55 |
56 | .el-main {
57 | padding: 0;
58 | overflow: hidden;
59 | }
60 |
61 | .wrapper--forms {
62 | height: calc(60vh - 110px);
63 | overflow: auto;
64 | }
65 |
66 | .wrapper--snippet {
67 | height: 19vh;
68 | background: black;
69 | overflow: auto;
70 |
71 | pre {
72 | padding: 20px;
73 | color: #ccc;
74 | line-height: 1.3;
75 | }
76 | }
77 |
78 |
79 |
80 | // Empty state
81 | .emptyState {
82 | text-align: center;
83 | color: #d7d7d7;
84 | padding-top: 50px;
85 |
86 | .emptyState__p {
87 | margin-top: 50px;
88 | }
89 |
90 | .emptyState__emoji {
91 | font-size: 80px;
92 | }
93 | }
94 |
95 |
96 | // Forms
97 | .wrapper--forms {
98 | padding: 30px;
99 | position: relative;
100 | }
101 |
102 | .medium-editor-element {
103 | outline: 0;
104 | }
105 |
106 | // Sidebar
107 | .wrapper--sidebar {
108 | padding: 30px;
109 |
110 | .el-tabs {
111 | min-height: 450px;
112 | border-bottom: 0;
113 | }
114 |
115 | .el-tabs__header {
116 | margin-bottom: 0;
117 | }
118 |
119 | .el-tabs__content {
120 | padding: 0px;
121 | }
122 |
123 | .el-tabs__inner {
124 | padding: 20px;
125 | }
126 |
127 | .el-tabs__nav {
128 | float: none;
129 |
130 | .el-tabs__item {
131 | width: 33.33%;
132 | text-align: center;
133 | }
134 | }
135 |
136 | .el-tabs--border-card {
137 | border: 1px solid black;
138 | box-shadow: 8px 8px 0px -2px rgb(49, 49, 49);
139 |
140 | &,
141 | > .el-tabs__header .el-tabs__item.is-active {
142 |
143 | color: #516167;
144 | background: #f9f9f9;
145 | }
146 |
147 | > .el-tabs__header {
148 | background: #d6d9dc;
149 | }
150 | }
151 |
152 |
153 |
154 | .el-form-item__label {
155 | line-height: 1;
156 | }
157 | }
158 |
159 | // Accordian in tabs
160 | .el-tab-pane {
161 | .el-collapse {
162 | border-top: 0;
163 | }
164 |
165 | .el-collapse-item__header {
166 | padding-left: 20px;
167 | background-color: transparent;
168 | font-size: 11px;
169 | text-transform: uppercase;
170 | font-weight: 600;
171 | color: $--color-primary;
172 | letter-spacing: 0.2em;
173 | border-bottom-color: $--color-primary;
174 |
175 | &.is-active { border-bottom-color: transparent; }
176 | }
177 |
178 | .el-collapse-item__content {
179 | padding-bottom: 0;
180 |
181 | .el-form-item {
182 | padding: 10px 20px;
183 | margin-bottom: 0;
184 | border-bottom: 1px solid $--border-color-lighter;
185 |
186 | display: table;
187 | width: 100%;
188 | box-sizing: border-box;
189 |
190 | .el-form-item__label,
191 | .el-form-item__content {
192 | float: none;
193 | display: table-cell;
194 | vertical-align: middle;
195 | }
196 |
197 | .el-form-item__label {
198 | color: lighten($--color-text-regular, 10%);
199 | }
200 |
201 | .el-form-item__content {
202 | line-height: 1;
203 | width: 200px;
204 | text-align: right;
205 | }
206 |
207 | &:last-child {
208 | border-bottom: 0;
209 | }
210 | }
211 | }
212 | }
213 |
214 | // Preview
215 | .preview__wrapper {
216 | max-width: 600px;
217 | margin-left: auto;
218 | margin-right: auto;
219 | }
--------------------------------------------------------------------------------
/src/assets/scss/_theming.scss:
--------------------------------------------------------------------------------
1 | // Mixin for css custom properties
2 | // eg: @include variable(background, blue, --theme-bg)
3 | @mixin variable($property, $fallback, $variable) {
4 | #{$property}: $fallback;
5 | #{$property}: var($variable);
6 | }
7 |
8 | // List all the theming styles
9 | //
10 | .wrapper--forms {
11 | @include variable(font-family, Arial, --theme-global-font-family);
12 | @include variable(color, #777777, --theme-global-font-color);
13 | @include variable(font-size, 16, --theme-global-font-size);
14 |
15 | a {
16 | @include variable(color, #206C92, --theme-global-link-color);
17 | }
18 |
19 | // Typography
20 | h1 { font-size: 2em; }
21 | h2 { font-size: 1.5em; }
22 | h3 { font-size: 1.17em; }
23 | h4 { font-size: 1.12em; }
24 | h5 { font-size: .83em; }
25 |
26 | .form__label {
27 | display: block;
28 |
29 | @include variable(color, #cccccc, --theme-primary-color);
30 | @include variable(font-weight, #cccccc, --theme-label-font-weight);
31 | @include variable(font-size, 16, --theme-label-font-size);
32 | @include variable(margin-bottom, 10, --theme-label-margin-bottom);
33 | }
34 |
35 | .form__helpblock {
36 | display: block;
37 |
38 | @include variable(color, #cccccc, --theme-help-text-color);
39 | @include variable(font-size, 12, --theme-help-text-font-size);
40 | @include variable(margin-top, 10, --theme-help-text-margin-top);
41 | }
42 |
43 | .el-input__inner,
44 | .el-radio__inner,
45 | .el-textarea__inner {
46 | @include variable(border-color, #dcdfe6, --theme-input-border-color);
47 | @include variable(border-radius, 4, --theme-input-border-radius);
48 |
49 | &:hover {
50 | @include variable(border-color, #dcdfe6, --theme-input-hover-border-color);
51 | }
52 |
53 | &:focus {
54 | @include variable(border-color, #dcdfe6, --theme-input-focus-border-color);
55 | box-shadow: 0 0 0 2px var(--theme-input-shadow-color);
56 | }
57 | }
58 |
59 | .el-input-number__decrease {
60 | @include variable(border-top-left-radius, 4, --theme-input-border-radius);
61 | @include variable(border-bottom-left-radius, 4, --theme-input-border-radius);
62 | }
63 |
64 | .el-input-number__increase {
65 | @include variable(border-top-right-radius, 4, --theme-input-border-radius);
66 | @include variable(border-bottom-right-radius, 4, --theme-input-border-radius);
67 | }
68 |
69 | .form__button {
70 | @include variable(background-color, black, --theme-button-background);
71 | @include variable(border-color, black, --theme-button-border-color);
72 | @include variable(color, white, --theme-button-color);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/assets/scss/_var.scss:
--------------------------------------------------------------------------------
1 | /* theme color */
2 | $--color-primary: black;
3 |
4 | /* icon font path, required */
5 | $--font-path: '~element-ui/lib/theme-chalk/fonts';
6 |
7 | /* transition time */
8 | $animationDuration: 0.1s;
9 |
10 | // the :export directive is the magic sauce for webpack
11 | // https://blog.bluematador.com/posts/how-to-share-variables-between-js-and-sass/
12 | // :export {
13 | // whitecolor: $--color-primary;
14 | // }
15 |
--------------------------------------------------------------------------------
/src/assets/scss/main.scss:
--------------------------------------------------------------------------------
1 | // Base - by Element UI
2 | @import "_var";
3 | @import "~element-ui/packages/theme-chalk/src/index";
4 |
5 | // Fonts
6 | @import url('https://fonts.googleapis.com/css?family=Roboto|Space+Mono');
7 |
8 |
9 | // Medium editor
10 | @import "medium-editor/dist/css/medium-editor.css";
11 | @import "medium-editor/dist/css/themes/beagle.css";
12 |
13 | // Styles
14 | @import "_styles";
15 | @import "_theming";
--------------------------------------------------------------------------------
/src/components/form_elements/Elements.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
13 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
95 |
96 |
--------------------------------------------------------------------------------
/src/components/form_elements/FormElementButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ currentField.buttonText }}
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/components/form_elements/FormElementCheckbox.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 | {{ item.optionValue }}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/components/form_elements/FormElementDatePicker.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/form_elements/FormElementDatetimePicker.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/form_elements/FormElementLongTextInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/components/form_elements/FormElementNumberInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/components/form_elements/FormElementRadioButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 | {{ item.optionValue }}
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/components/form_elements/FormElementRating.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/components/form_elements/FormElementSelectList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/components/form_elements/FormElementTextEditor.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/components/form_elements/FormElementTextInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/components/form_elements/FormElementTimePicker.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/components/form_elements/Properties.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
10 | {{activeForm.label}}
11 |
12 |
13 |
14 |
16 |
17 |
19 | {{activeForm.placeholder}}
20 |
21 |
22 |
23 |
25 |
26 | {{activeForm.buttonText}}
27 |
28 |
29 |
30 |
32 |
35 | {{activeForm.fieldText}}
36 |
37 |
38 |
39 |
41 |
42 |
43 |
44 |
46 |
47 |
49 | {{activeForm.helpBlockText}}
50 |
51 |
52 |
53 |
54 |
55 | -
58 |
59 |
60 |
61 | {{item.optionValue}}
62 |
63 |
64 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | Add more
76 |
77 |
78 |
79 |
80 |
81 |
82 |
109 |
110 |
--------------------------------------------------------------------------------
/src/components/form_elements/Theming.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
13 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | Normal
68 | Bold
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
--------------------------------------------------------------------------------
/src/components/form_elements/formbuilder.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 |
3 | import draggable from 'vuedraggable'
4 |
5 | import TextInput from '@/components/form_elements/FormElementTextInput'
6 | import LongTextInput from '@/components/form_elements/FormElementLongTextInput'
7 | import NumberInput from '@/components/form_elements/FormElementNumberInput'
8 | import SelectList from '@/components/form_elements/FormElementSelectList'
9 | import RadioButton from '@/components/form_elements/FormElementRadioButton'
10 | import Checkbox from '@/components/form_elements/FormElementCheckbox'
11 | import TimePicker from '@/components/form_elements/FormElementTimePicker'
12 | import DatePicker from '@/components/form_elements/FormElementDatePicker'
13 | import DatetimePicker from '@/components/form_elements/FormElementDatetimePicker'
14 | import Rating from '@/components/form_elements/FormElementRating'
15 | import Button from '@/components/form_elements/FormElementButton'
16 | import TextEditor from '@/components/form_elements/FormElementTextEditor'
17 |
18 | import Elements from '@/components/form_elements/Elements'
19 | import Properties from '@/components/form_elements/Properties'
20 | import Theming from '@/components/form_elements/Theming'
21 |
22 |
23 | export const FormBuilder = new Vue({
24 | components: { Elements, Properties, Theming, draggable, TextInput, LongTextInput, NumberInput, SelectList, RadioButton, Checkbox, TimePicker, DatePicker, DatetimePicker, Rating, Button, TextEditor },
25 | data() {
26 | return {
27 | fields: [
28 | {
29 | 'name': 'TextInput',
30 | 'text': 'Text',
31 | 'group': 'form', //form group
32 | 'hasOptions': false,
33 | 'isRequired': false,
34 | 'isHelpBlockVisible': false,
35 | 'isPlaceholderVisible': true,
36 | 'isUnique': false
37 | },
38 | {
39 | 'name': 'LongTextInput',
40 | 'text': 'Long Text',
41 | 'group': 'form',
42 | 'hasOptions': false,
43 | 'isRequired': false,
44 | 'isHelpBlockVisible': false,
45 | 'isPlaceholderVisible': true,
46 | 'isUnique': false
47 | },
48 | {
49 | 'name': 'NumberInput',
50 | 'text': 'Number',
51 | 'group': 'form',
52 | 'hasOptions': false,
53 | 'isRequired': false,
54 | 'isHelpBlockVisible': false,
55 | 'isPlaceholderVisible': false,
56 | 'isUnique': false
57 | },
58 | {
59 | 'name': 'SelectList',
60 | 'text': 'Select',
61 | 'group': 'form',
62 | 'hasOptions': true,
63 | 'isRequired': false,
64 | 'isHelpBlockVisible': false,
65 | 'isPlaceholderVisible': false,
66 | 'isUnique': false
67 | },
68 | {
69 | 'name': 'RadioButton',
70 | 'text': 'Radio',
71 | 'group': 'form',
72 | 'hasOptions': true,
73 | 'isRequired': false,
74 | 'isHelpBlockVisible': false,
75 | 'isPlaceholderVisible': false,
76 | 'isUnique': false
77 | },
78 | {
79 | 'name': 'Checkbox',
80 | 'text': 'Checkbox',
81 | 'group': 'form',
82 | 'hasOptions': true,
83 | 'isRequired': false,
84 | 'isHelpBlockVisible': false,
85 | 'isPlaceholderVisible': false,
86 | 'isUnique': false
87 | },
88 | {
89 | 'name': 'TimePicker',
90 | 'text': 'Time Picker',
91 | 'group': 'form',
92 | 'hasOptions': false,
93 | 'isRequired': false,
94 | 'isHelpBlockVisible': false,
95 | 'isPlaceholderVisible': false,
96 | 'isUnique': false
97 | },
98 | {
99 | 'name': 'DatePicker',
100 | 'text': 'Date Picker',
101 | 'group': 'form',
102 | 'hasOptions': false,
103 | 'isRequired': false,
104 | 'isHelpBlockVisible': false,
105 | 'isPlaceholderVisible': false,
106 | 'isUnique': false
107 | },
108 | {
109 | 'name': 'DatetimePicker',
110 | 'text': 'Date-Time Picker',
111 | 'group': 'form',
112 | 'hasOptions': false,
113 | 'isRequired': false,
114 | 'isHelpBlockVisible': false,
115 | 'isPlaceholderVisible': false,
116 | 'isUnique': false
117 | },
118 | {
119 | 'name': 'Rating',
120 | 'text': 'Rating',
121 | 'group': 'form',
122 | 'hasOptions': false,
123 | 'isRequired': false,
124 | 'isHelpBlockVisible': false,
125 | 'isPlaceholderVisible': false,
126 | 'isUnique': false
127 | },
128 | {
129 | 'name': 'Button',
130 | 'text': 'Button',
131 | 'group': 'button',
132 | 'hasOptions': false,
133 | 'isRequired': false,
134 | 'isHelpBlockVisible': false,
135 | 'isPlaceholderVisible': false,
136 | 'isUnique': true
137 | },
138 | {
139 | 'name': 'TextEditor',
140 | 'text': 'Text editor',
141 | 'group': 'static',
142 | 'hasOptions': false,
143 | 'isRequired': false,
144 | 'isHelpBlockVisible': false,
145 | 'isPlaceholderVisible': false,
146 | 'isUnique': false
147 | }
148 | ],
149 |
150 | sortElementOptions: {
151 | group: { name:'formbuilder', pull:false, put:true },
152 | sort: true,
153 | handle: ".form__actionitem--move"
154 | },
155 |
156 | dropElementOptions: {
157 | group: { name:'formbuilder', pull:'clone', put:false },
158 | sort: false,
159 | ghostClass: "sortable__ghost",
160 | filter: ".is-disabled"
161 | }
162 | }
163 | },
164 | methods: {
165 | deleteElement(index){
166 | vm.$store.activeForm = [];
167 | vm.$store.activeTabForFields = "elements";
168 | this.$delete(vm.$store.forms, index);
169 | },
170 |
171 | cloneElement(index, form){
172 | var cloned = _.cloneDeep(form) // clone deep lodash
173 | vm.$store.forms.splice(index, 0, cloned)
174 | },
175 |
176 | editElementProperties(form){
177 | vm.$store.activeForm = form;
178 | vm.$store.activeTabForFields = "properties";
179 | }
180 | }
181 | });
--------------------------------------------------------------------------------
/src/components/layouts/NavBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
40 |
41 |
42 |
54 |
55 |
--------------------------------------------------------------------------------
/src/layouts/Default.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
27 |
28 |
33 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | // The Vue build version to load with the `import` command
2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
3 | import Vue from 'vue'
4 |
5 |
6 | // ================
7 | // Use Element UI
8 | // ----------------
9 | import Element from 'element-ui'
10 | import locale from 'element-ui/lib/locale/lang/en' // Default lang is Chinese
11 | import './assets/scss/_var.scss'
12 | Vue.use(Element, { locale })
13 | import './assets/scss/main.scss'
14 |
15 |
16 | import App from './App'
17 |
18 | // ================
19 | // Transitions
20 | // ----------------
21 | import Animate from 'vue2-animate/dist/vue2-animate.min.css'
22 | Vue.use(Animate)
23 |
24 | // ================
25 | // Lodash
26 | // ----------------
27 | import VueLodash from 'vue-lodash'
28 | Vue.use(VueLodash)
29 |
30 | // ================
31 | // Use Vue Router
32 | // ----------------
33 | import router from './router'
34 |
35 |
36 | // ================
37 | // Use Layouts
38 | // ----------------
39 | import Default from './layouts/Default'
40 | Vue.component('default-layout', Default);
41 |
42 |
43 | // ================
44 | // Vue-stash aka simple vuex alternative
45 | // ----------------
46 | import VueStash from 'vue-stash'
47 | import store from './store/store'
48 | Vue.use(VueStash)
49 |
50 |
51 |
52 | Vue.config.productionTip = false
53 |
54 | var vm = new Vue({
55 | el: '#app',
56 | router,
57 | components: { App },
58 | data: { store },
59 | template: '',
60 | render: h => h(App)
61 | }).$mount('#app')
62 |
63 | global.vm = vm;
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueRouter from 'vue-router'
3 |
4 | Vue.use(VueRouter)
5 |
6 | export default new VueRouter({
7 | mode: 'history',
8 | routes: [
9 | {
10 | path: '/',
11 | name: 'home',
12 | // meta: { layout: "default" },
13 | component: () => import("@/views/Home")
14 | },
15 | {
16 | path: '/preview',
17 | name: 'preview',
18 | component: () => import("@/views/Preview")
19 | }
20 | ]
21 | })
22 |
--------------------------------------------------------------------------------
/src/store/store.js:
--------------------------------------------------------------------------------
1 | export default {
2 | forms: [],
3 | activeForm: [],
4 | activeTabForFields: 'elements',
5 | themingVars: {
6 | globalFontFamily: "Arial",
7 | globalFontColor: "#777777",
8 | globalLinkColor: "#206C92",
9 | globalFontSize: "16",
10 |
11 | labelFontWeight: "500",
12 | labelFontSize: "16",
13 | labelMarginBottom: "10",
14 |
15 | helpTextColor: "#cccccc",
16 | helpTextFontSize: "12",
17 | helpTextMarginTop: "10",
18 |
19 |
20 | inputBorderRadius: "4",
21 | inputBorderColor: "#dcdfe6",
22 | inputHoverBorderColor: "#c0c4cc",
23 | inputFocusBorderColor: "#000000",
24 | inputShadowColor: "#9D9D9D",
25 |
26 | buttonBackground: "#000000",
27 | buttonBorderColor: "#000000",
28 | buttonColor: "#FFFFFF"
29 | }
30 | }
--------------------------------------------------------------------------------
/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
57 |
58 |
61 |
62 |
63 |
80 |
81 |
82 |
83 |
84 |
148 |
149 |
150 |
230 |
--------------------------------------------------------------------------------
/src/views/Preview.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/static/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jmeei/vue-formbuilder/500e783fd92c8e3121a24c0496313b96d18b1739/static/.gitkeep
--------------------------------------------------------------------------------
/test/unit/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "mocha": true
4 | },
5 | "globals": {
6 | "expect": true,
7 | "sinon": true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/test/unit/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | Vue.config.productionTip = false
4 |
5 | // require all test files (files that ends with .spec.js)
6 | const testsContext = require.context('./specs', true, /\.spec$/)
7 | testsContext.keys().forEach(testsContext)
8 |
9 | // require all src files except main.js for coverage.
10 | // you can also change this to match only the subset of files that
11 | // you want coverage for.
12 | const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/)
13 | srcContext.keys().forEach(srcContext)
14 |
--------------------------------------------------------------------------------
/test/unit/karma.conf.js:
--------------------------------------------------------------------------------
1 | // This is a karma config file. For more details see
2 | // http://karma-runner.github.io/0.13/config/configuration-file.html
3 | // we are also using it with karma-webpack
4 | // https://github.com/webpack/karma-webpack
5 |
6 | var webpackConfig = require('../../build/webpack.test.conf')
7 |
8 | module.exports = function karmaConfig (config) {
9 | config.set({
10 | // to run in additional browsers:
11 | // 1. install corresponding karma launcher
12 | // http://karma-runner.github.io/0.13/config/browsers.html
13 | // 2. add it to the `browsers` array below.
14 | browsers: ['PhantomJS'],
15 | frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'],
16 | reporters: ['spec', 'coverage'],
17 | files: ['./index.js'],
18 | preprocessors: {
19 | './index.js': ['webpack', 'sourcemap']
20 | },
21 | webpack: webpackConfig,
22 | webpackMiddleware: {
23 | noInfo: true
24 | },
25 | coverageReporter: {
26 | dir: './coverage',
27 | reporters: [
28 | { type: 'lcov', subdir: '.' },
29 | { type: 'text-summary' }
30 | ]
31 | }
32 | })
33 | }
34 |
--------------------------------------------------------------------------------
/test/unit/specs/HelloWorld.spec.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import HelloWorld from '@/components/HelloWorld'
3 |
4 | describe('HelloWorld.vue', () => {
5 | it('should render correct contents', () => {
6 | const Constructor = Vue.extend(HelloWorld)
7 | const vm = new Constructor().$mount()
8 | expect(vm.$el.querySelector('.hello h1').textContent)
9 | .to.equal('Welcome to Your Vue.js App')
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | module: {
3 | rules: [
4 | // ... other rules omitted
5 |
6 | // this will apply to both plain `.scss` files
7 | // AND `