├── .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 | ![alt text](https://d15k2d11r6t6rl.cloudfront.net/public/users/Integrators/0db9f180-d222-4b2b-9371-cf9393bf4764/c69e8df5-3118-4e31-8e2b-c875e264bf55/form-builder-vue.png "Vue Form Builder Screenshot") 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 | 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 | 22 | 23 | 95 | 96 | -------------------------------------------------------------------------------- /src/components/form_elements/FormElementButton.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /src/components/form_elements/FormElementCheckbox.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/components/form_elements/FormElementDatePicker.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/components/form_elements/FormElementDatetimePicker.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/components/form_elements/FormElementLongTextInput.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/components/form_elements/FormElementNumberInput.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/form_elements/FormElementRadioButton.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/components/form_elements/FormElementRating.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/components/form_elements/FormElementSelectList.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/components/form_elements/FormElementTextEditor.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | -------------------------------------------------------------------------------- /src/components/form_elements/FormElementTextInput.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | -------------------------------------------------------------------------------- /src/components/form_elements/FormElementTimePicker.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/components/form_elements/Properties.vue: -------------------------------------------------------------------------------- 1 | 81 | 82 | 109 | 110 | -------------------------------------------------------------------------------- /src/components/form_elements/Theming.vue: -------------------------------------------------------------------------------- 1 | 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 | 41 | 42 | 54 | 55 | -------------------------------------------------------------------------------- /src/layouts/Default.vue: -------------------------------------------------------------------------------- 1 | 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 | 83 | 84 | 148 | 149 | 150 | 230 | -------------------------------------------------------------------------------- /src/views/Preview.vue: -------------------------------------------------------------------------------- 1 | 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 `