├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── coverage.yml ├── .gitignore ├── LICENSE ├── README.md ├── app-extension ├── .npmignore ├── README.md ├── package.json └── src │ ├── boot │ └── register.js │ ├── index.js │ ├── prompts.js │ └── templates │ └── .gitkeep ├── docs └── logo.PNG ├── jsconfig.json ├── netlify.toml ├── test ├── babel.config.js ├── codecov.yml ├── jest.config.js ├── jest │ ├── .eslintrc.js │ ├── .gitignore │ ├── __tests__ │ │ ├── Crud.spec.js │ │ ├── columns.js │ │ └── response.json │ ├── index.js │ └── jest.setup.js └── package.json └── ui ├── .dockerignore ├── .npmignore ├── Dockerfile ├── build ├── config.js ├── entry │ ├── index.common.js │ ├── index.esm.js │ └── index.umd.js ├── index.js ├── script.app-ext.js ├── script.clean.js ├── script.javascript.js ├── script.open-umd.js └── utils.js ├── dev ├── .babelrc ├── .editorconfig ├── .gitignore ├── .postcssrc.js ├── README.md ├── babel.config.js ├── package.json ├── public │ ├── favicon.ico │ └── icons │ │ ├── favicon-128x128.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ └── favicon-96x96.png ├── quasar.conf.js ├── quasar.extensions.json ├── quasar.testing.json └── src │ ├── App.vue │ ├── assets │ ├── .gitkeep │ ├── columns.md │ └── getting_started.md │ ├── boot │ └── register.js │ ├── components │ ├── .gitkeep │ ├── QTabExampleFive.vue │ ├── QTabExampleFour.vue │ ├── QTabExampleOne.vue │ ├── QTabExampleSix.vue │ ├── QTabExampleThree.vue │ └── QTabExampleTwo.vue │ ├── css │ ├── app.sass │ └── quasar.variables.sass │ ├── index.template.html │ ├── layouts │ ├── Drawer.vue │ └── Layout.vue │ ├── pages │ ├── PageApi.vue │ ├── PageIndex.vue │ └── PageMoreExamples.vue │ └── router │ ├── index.js │ ├── pages.js │ └── routes.js ├── docker-compose.yaml ├── package.json ├── src ├── components │ ├── Crud.md │ ├── Crud.vue │ └── helper.js └── index.js └── umd-test.html /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: Running Code Coverage 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/setup-node@v1 10 | with: 11 | node-version: 12.x 12 | - uses: actions/checkout@v2 13 | - run: cd ui && npm install 14 | - run: cd test && npm install && npm run test:unit:coverage 15 | - run: bash <(curl -s https://codecov.io/bash) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .thumbs.db 3 | node_modules 4 | dist 5 | yarn.lock 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Editor directories and files 11 | .idea 12 | .vscode 13 | *.suo 14 | *.ntvs* 15 | *.njsproj 16 | *.sln 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) odranoelo_O@hotmail.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![Quasar Crud](https://github.com/odranoelBR/vue-quasar-crud/blob/main/docs/logo.PNG) 3 | > Quasar Crud Component based on QTable using Axios API to fetch remote data. 4 | 5 |
6 | 7 | 8 | [![codecov](https://codecov.io/gh/odranoelBR/vue-quasar-crud/branch/main/graph/badge.svg?token=C1R7WA5KCZ)](https://codecov.io/gh/odranoelBR/vue-quasar-crud) 9 | ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/odranoelbr/vue-quasar-crud?color=9CB922) 10 | [![Netlify Status](https://api.netlify.com/api/v1/badges/f11464cd-75f0-4052-a329-e8b82c144bc6/deploy-status)](https://app.netlify.com/sites/vue-quasar-crud/deploys) 11 | 12 | ## Links 13 | * [Vuejs Page](https://vuejs.org/) 14 | * [Quasar Framework Page](http://quasar-framework.org/) 15 | 16 | ## Install 17 | ``` 18 | quasar ext add crud 19 | ``` 20 | 21 | ## Uninstall 22 | ``` 23 | quasar ext remove crud 24 | ``` 25 | 26 | ## Demo 27 | https://vue-quasar-crud.netlify.app/ 28 | 29 | ## Infos 30 | * Using Quasar default quasar extesion template 31 | * You must supply axios dependency 32 | 33 | ## External Dependecies 34 | * [Axios.js](https://github.com/mzabriskie/axios) as HTTP client 35 | 36 | -------------------------------------------------------------------------------- /app-extension/.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .thumbs.db 3 | yarn.lock 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | .vscode 11 | *.suo 12 | *.ntvs* 13 | *.njsproj 14 | *.sln 15 | .editorconfig 16 | .eslintignore 17 | .eslintrc.js 18 | -------------------------------------------------------------------------------- /app-extension/README.md: -------------------------------------------------------------------------------- 1 | # Quasar App Extension crud 2 | 3 | > Add a short description of your App Extension. What does it do? How is it beneficial? Why would someone want to use it? 4 | 5 | [![npm](https://img.shields.io/npm/v/quasar-app-extension-crud.svg?label=quasar-app-extension-crud)](https://www.npmjs.com/package/quasar-app-extension-crud) 6 | [![npm](https://img.shields.io/npm/dt/quasar-app-extension-crud.svg)](https://www.npmjs.com/package/quasar-app-extension-crud) 7 | 8 | # Install 9 | ```bash 10 | quasar ext add crud 11 | ``` 12 | Quasar CLI will retrieve it from NPM and install the extension. 13 | 14 | ## Prompts 15 | 16 | > If your app extension uses prompts, explain them here, otherwise remove this section and remove prompts.js file. 17 | 18 | # Uninstall 19 | ```bash 20 | quasar ext remove crud 21 | ``` 22 | 23 | # Info 24 | > Add longer information here that will help the user of your app extension. 25 | 26 | # Other Info 27 | > Add other information that's not as important to know 28 | 29 | # Donate 30 | If you appreciate the work that went into this App Extension, please consider [donating to Quasar](https://donate.quasar.dev). 31 | 32 | # License 33 | MIT (c) odranoelo_O@hotmail.com 34 | -------------------------------------------------------------------------------- /app-extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quasar-app-extension-crud", 3 | "version": "1.0.2", 4 | "description": "A Quasar App Extension", 5 | "author": "odranoelo_O@hotmail.com", 6 | "license": "MIT", 7 | "main": "src/index.js", 8 | "repository": { 9 | "type": "git", 10 | "url": "" 11 | }, 12 | "bugs": "", 13 | "homepage": "", 14 | "dependencies": { 15 | "quasar-ui-crud": "^1.0.1" 16 | }, 17 | "engines": { 18 | "node": ">= 8.9.0", 19 | "npm": ">= 5.6.0", 20 | "yarn": ">= 1.6.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app-extension/src/boot/register.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Plugin from 'quasar-ui-crud/src/index.js' 3 | 4 | Vue.use(Plugin) -------------------------------------------------------------------------------- /app-extension/src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Quasar App Extension index/runner script 3 | * (runs on each dev/build) 4 | * 5 | * Docs: https://quasar.dev/app-extensions/development-guide/index-api 6 | * API: https://github.com/quasarframework/quasar/blob/master/app/lib/app-extension/IndexAPI.js 7 | */ 8 | 9 | function extendConf (conf) { 10 | // register our boot file 11 | conf.boot.push('~quasar-app-extension-crud/src/boot/register.js') 12 | 13 | conf.framework.plugins.push('Dialog') 14 | conf.framework.plugins.push('Notify') 15 | 16 | // make sure app extension files & ui package gets transpiled 17 | conf.build.transpileDependencies.push(/quasar-app-extension-crud[\\/]src/) 18 | } 19 | 20 | module.exports = function (api) { 21 | // Quasar compatibility check; you may need 22 | // hard dependencies, as in a minimum version of the "quasar" 23 | // package or a minimum version of "@quasar/app" CLI 24 | api.compatibleWith('quasar', '^1.1.1') 25 | api.compatibleWith('@quasar/app', '^1.1.0 || ^2.0.0') 26 | 27 | // Uncomment the line below if you provide a JSON API for your component 28 | // api.registerDescribeApi('MyComponent', '~quasar-ui-crud/src/components/MyComponent.json') 29 | 30 | // We extend /quasar.conf.js 31 | api.extendQuasarConf(extendConf) 32 | } 33 | -------------------------------------------------------------------------------- /app-extension/src/prompts.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Quasar App Extension prompts script 3 | * 4 | * Docs: https://quasar.dev/app-extensions/development-guide/prompts-api 5 | * 6 | * Inquirer prompts 7 | * (answers are available as "api.prompts" in the other scripts) 8 | * https://www.npmjs.com/package/inquirer#question 9 | * 10 | * Example: 11 | 12 | return [ 13 | { 14 | name: 'name', 15 | type: 'string', 16 | required: true, 17 | message: 'Quasar CLI Extension name (without prefix)', 18 | }, 19 | { 20 | name: 'preset', 21 | type: 'checkbox', 22 | message: 'Check the features needed for your project:', 23 | choices: [ 24 | { 25 | name: 'Install script', 26 | value: 'install' 27 | }, 28 | { 29 | name: 'Prompts script', 30 | value: 'prompts' 31 | }, 32 | { 33 | name: 'Uninstall script', 34 | value: 'uninstall' 35 | } 36 | ] 37 | } 38 | ] 39 | 40 | */ 41 | 42 | module.exports = function () { 43 | return [] 44 | } 45 | -------------------------------------------------------------------------------- /app-extension/src/templates/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odranoelBR/vue-quasar-crud/33fe100784681eceace644065793d76bc96a9601/app-extension/src/templates/.gitkeep -------------------------------------------------------------------------------- /docs/logo.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odranoelBR/vue-quasar-crud/33fe100784681eceace644065793d76bc96a9601/docs/logo.PNG -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "compilerOptions": { 4 | // This must be specified if "paths" is set 5 | "baseUrl": ".", 6 | // Relative to "baseUrl" 7 | "paths": { 8 | "ui/*": ["src/*"] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | base = "ui" 3 | publish = "dev/dist/spa" 4 | command = "yarn && cd dev && yarn && yarn build" -------------------------------------------------------------------------------- /test/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | test: { 4 | plugins: [ 5 | '@babel/plugin-transform-runtime' 6 | ], 7 | presets: [ 8 | "@babel/preset-env" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /test/codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | token: 08b24873-78e9-49a8-80ba-073fc8052983 3 | max_report_age: off 4 | disable_default_path_fixes: false 5 | fixes: 6 | - "::jest/" 7 | -------------------------------------------------------------------------------- /test/jest.config.js: -------------------------------------------------------------------------------- 1 | const esModules = ['quasar/lang', 'lodash-es'].join('|'); 2 | module.exports = { 3 | globals: { 4 | __DEV__: true 5 | }, 6 | // verbose: true, 7 | // watch: true, 8 | collectCoverage: false, 9 | coverageReporters: ["json", "lcov", "text", "clover"], 10 | coverageDirectory: 'test/jest/coverage', 11 | collectCoverageFrom: [ 12 | '/ui/src/components/**/*.(js|vue)' 13 | ], 14 | rootDir: "../", 15 | roots: [ 16 | '/test', 17 | '/ui' 18 | ], 19 | // Needed in JS codebases too because of feature flags 20 | coveragePathIgnorePatterns: [ 21 | 'test/node_modules/', 22 | 'ui/node_modules/', 23 | '.d.ts$' 24 | ], 25 | coverageThreshold: { 26 | global: { 27 | // branches: 50, 28 | // functions: 50, 29 | // lines: 50, 30 | // statements: 50 31 | }, 32 | }, 33 | testMatch: [ 34 | '/test/jest/__tests__/**/*.(spec|test).js', 35 | ], 36 | moduleFileExtensions: ['vue', 'js', 'jsx', 'json'], 37 | moduleNameMapper: { 38 | '^vue$': '/test/node_modules/vue/dist/vue.common.js', 39 | '^quasar$': 'quasar/dist/quasar.common.js', 40 | '^@components/(.*)$': '/ui/src/components/$1' 41 | }, 42 | transform: { 43 | "^.+\\.js$": "/test/node_modules/babel-jest", 44 | "^.+\\.vue$": "/test/node_modules/vue-jest", 45 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 46 | '/test/node_modules/jest-transform-stub', 47 | }, 48 | transformIgnorePatterns: [ 49 | `node_modules/(?!(${esModules}))` 50 | ], 51 | snapshotSerializers: ['/test/node_modules/jest-serializer-vue'], 52 | }; 53 | -------------------------------------------------------------------------------- /test/jest/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | // Removes 'no-undef' lint errors for Jest global functions (`describe`, `it`, etc), 4 | // add Jest-specific lint rules and Jest plugin 5 | // See https://github.com/jest-community/eslint-plugin-jest#recommended 6 | 'plugin:jest/recommended', 7 | // Uncomment following line to apply style rules 8 | // 'plugin:jest/style', 9 | ], 10 | }; 11 | -------------------------------------------------------------------------------- /test/jest/.gitignore: -------------------------------------------------------------------------------- 1 | coverage/* -------------------------------------------------------------------------------- /test/jest/__tests__/Crud.spec.js: -------------------------------------------------------------------------------- 1 | import { mountQuasar } from '../index' 2 | import axios from 'axios' 3 | import Crud from '@components/Crud.vue' 4 | import columns from './columns.js' 5 | import response from './response.json' 6 | 7 | jest.mock('axios'); 8 | 9 | beforeAll(() => { 10 | jest.spyOn(console, 'warn').mockImplementation(() => { }); 11 | jest.spyOn(console, 'error').mockImplementation(() => { }); 12 | }); 13 | 14 | const defaultPropsData = () => ({ 15 | http: axios, 16 | api: '', 17 | columns: JSON.parse(JSON.stringify(columns)), 18 | listIndex: value => value, 19 | rowKey: '' 20 | }) 21 | 22 | let returnData = [ 23 | { id: 1, first_name: 'Brominator', email: 'bro@gmail.com' }, 24 | { id: 2, first_name: 'Foo f', email: 'foo@gmail.com' } 25 | ] 26 | 27 | test('all columns enabled to create', () => { 28 | axios.get.mockResolvedValueOnce([]); 29 | 30 | const wrapper = mountQuasar(Crud, { 31 | propsData: defaultPropsData() 32 | }) 33 | 34 | expect(wrapper.vm.columnsToRender).toHaveLength(2) 35 | }) 36 | 37 | test('at least one row was selected', () => { 38 | axios.get.mockResolvedValueOnce(returnData); 39 | 40 | const wrapper = mountQuasar(Crud, { 41 | propsData: defaultPropsData() 42 | }) 43 | wrapper.setData({ selected: [returnData[0]] }) 44 | 45 | expect(wrapper.vm.someSelected).toBeTruthy() 46 | }) 47 | 48 | test('using customSelected slot', () => { 49 | axios.get.mockResolvedValueOnce([]); 50 | 51 | const wrapper = mountQuasar(Crud, { 52 | propsData: defaultPropsData(), 53 | slots: { 54 | customSelected: '
' 55 | } 56 | }) 57 | 58 | expect(wrapper.vm.hasCustomSelectedSlot).toBeTruthy() 59 | }) 60 | 61 | test('get fields with validation', () => { 62 | axios.get.mockResolvedValueOnce([]); 63 | 64 | const props = defaultPropsData() 65 | 66 | const wrapper = mountQuasar(Crud, { 67 | propsData: props 68 | }) 69 | 70 | expect(wrapper.vm.fieldsWithValidation).toHaveLength(1) 71 | expect(wrapper.vm.fieldsWithValidation).toStrictEqual([props.columns[0]]) 72 | }) 73 | 74 | test('custom message delete with function after user select', () => { 75 | axios.get.mockResolvedValueOnce(returnData); 76 | 77 | const props = defaultPropsData() 78 | props.msgDelete = row => `Deleted ${row.first_name} ?` 79 | 80 | const wrapper = mountQuasar(Crud, { 81 | propsData: props 82 | }) 83 | 84 | wrapper.setData({ selected: [returnData[0]] }) 85 | 86 | expect(wrapper.vm.messageDeleteForShow).toBe('Deleted Brominator ?') 87 | }) 88 | 89 | test('custom title delete with function after user select', () => { 90 | 91 | axios.get.mockResolvedValueOnce(returnData); 92 | 93 | const props = defaultPropsData() 94 | props.titleDelete = row => `Do you want delete ${row.first_name} ?` 95 | 96 | const wrapper = mountQuasar(Crud, { 97 | propsData: props 98 | }) 99 | 100 | wrapper.setData({ selected: [returnData[1]] }) 101 | 102 | expect(wrapper.vm.titleDeleteForShow).toBe('Do you want delete Foo f ?') 103 | }) 104 | 105 | 106 | test('get fields with validation', () => { 107 | 108 | axios.get.mockResolvedValueOnce(); 109 | 110 | const props = defaultPropsData() 111 | 112 | const wrapper = mountQuasar(Crud, { 113 | propsData: props 114 | }) 115 | 116 | expect(wrapper.vm.fieldsWithValidation).toHaveLength(1) 117 | expect(wrapper.vm.fieldsWithValidation).toStrictEqual([props.columns[0]]) 118 | }) 119 | 120 | 121 | test('mount component without make requests', () => { 122 | 123 | const props = defaultPropsData() 124 | props.getOnStart = false 125 | 126 | const wrapper = mountQuasar(Crud, { 127 | propsData: props 128 | }) 129 | 130 | const spyOnGet = jest.spyOn(wrapper.vm, 'get') 131 | 132 | expect(spyOnGet).not.toBeCalled() 133 | }) 134 | 135 | test('make a get request ONLY when param change', async () => { 136 | 137 | const props = defaultPropsData() 138 | 139 | props.getOnStart = false 140 | props.getOnParamChange = true 141 | 142 | const wrapper = mountQuasar(Crud, { 143 | propsData: props 144 | }) 145 | 146 | const spyOnGet = jest.spyOn(wrapper.vm, 'get') 147 | 148 | expect(spyOnGet).not.toBeCalled() 149 | 150 | await wrapper.setProps({ params: 'id=5' }) 151 | expect(spyOnGet).toBeCalled() 152 | }) 153 | 154 | test('only email column are visible to create', () => { 155 | 156 | const props = defaultPropsData() 157 | 158 | props.columns[0].showCreate = false 159 | axios.get.mockResolvedValue([]); 160 | 161 | const wrapper = mountQuasar(Crud, { 162 | propsData: props 163 | }) 164 | 165 | expect(wrapper.vm.columnsToRender).toHaveLength(1) 166 | }) 167 | 168 | test('api url mounting default mouting', () => { 169 | 170 | const props = defaultPropsData() 171 | 172 | props.api = 'people' 173 | 174 | const wrapper = mountQuasar(Crud, { 175 | propsData: props 176 | }) 177 | 178 | expect(wrapper.vm.apiUri).toBe('people?page=1&per_page=3&sort=,asc') 179 | }) 180 | 181 | test('api url mounting no server side pagination', () => { 182 | 183 | const props = defaultPropsData() 184 | 185 | props.api = 'people' 186 | props.paginationServerSide = false 187 | 188 | const wrapper = mountQuasar(Crud, { 189 | propsData: props 190 | }) 191 | 192 | expect(wrapper.vm.apiUri).toBe('people') 193 | }) 194 | 195 | test('object to save mounting empty', () => { 196 | 197 | axios.get.mockResolvedValue([]); 198 | 199 | const props = defaultPropsData() 200 | 201 | props.columns[0].value = '' 202 | props.columns[1].value = '' 203 | 204 | const wrapper = mountQuasar(Crud, { 205 | propsData: props 206 | }) 207 | 208 | expect(wrapper.vm.objectToSave).toStrictEqual({}) 209 | }) 210 | 211 | test('object to save mounting simple values', () => { 212 | 213 | const props = defaultPropsData() 214 | 215 | props.columns[0].value = 'Brother Lee' 216 | props.columns[1].value = 'brother@mail.com' 217 | 218 | const wrapper = mountQuasar(Crud, { 219 | propsData: props 220 | }) 221 | 222 | expect(wrapper.vm.objectToSave).toStrictEqual({ "email": "brother@mail.com", "first_name": "Brother Lee" }) 223 | }) 224 | 225 | 226 | test('object to save mounting complex values', () => { 227 | 228 | const props = defaultPropsData() 229 | 230 | props.columns[0].value = {} 231 | props.columns[1].value = [] 232 | 233 | const wrapper = mountQuasar(Crud, { 234 | propsData: props 235 | }) 236 | 237 | expect(wrapper.vm.objectToSave).toStrictEqual({ "email": [], "first_name": {} }) 238 | }) 239 | 240 | test('object to save mounting with formating values', () => { 241 | 242 | const props = defaultPropsData() 243 | 244 | props.columns[0].value = { value: 'BROTHER', label: 'Brother' } 245 | props.columns[0].formatForPost = (value) => value.value 246 | 247 | props.columns[1].value = 'brother' 248 | props.columns[1].formatForPost = (value) => `${value}@mail.com` 249 | 250 | const wrapper = mountQuasar(Crud, { 251 | propsData: props 252 | }) 253 | 254 | expect(wrapper.vm.objectToSave).toStrictEqual({ "email": 'brother@mail.com', "first_name": 'BROTHER' }) 255 | }) 256 | 257 | test('column prop valitador with string value on format', () => { 258 | 259 | const props = defaultPropsData() 260 | 261 | props.columns[0].value = { value: 'BROTHER', label: 'Brother' } 262 | props.columns[0].formatForPost = '' 263 | 264 | props.columns[1].value = 'brother' 265 | props.columns[1].formatForPost = {} 266 | 267 | const validator = Crud.props.columns.validator 268 | 269 | expect(validator(props.columns)).toBeFalsy() 270 | expect(console.warn).toHaveBeenCalled(); 271 | }) 272 | 273 | test('open modal without data', () => { 274 | 275 | axios.get.mockResolvedValue([]); 276 | 277 | const props = defaultPropsData() 278 | const wrapper = mountQuasar(Crud, { 279 | propsData: props 280 | }) 281 | const spyOnResetColumnValuesMethod = jest.spyOn(wrapper.vm, 'resetColumnValues') 282 | 283 | expect(wrapper.vm.modalOpened).toBeFalsy() 284 | wrapper.vm.toggleModal() 285 | 286 | expect(wrapper.vm.modalOpened).toBeTruthy() 287 | expect(spyOnResetColumnValuesMethod).toHaveBeenCalled() 288 | }) 289 | test('open modal with data after select a row ', () => { 290 | 291 | axios.get.mockResolvedValue(returnData); 292 | 293 | const props = defaultPropsData() 294 | const wrapper = mountQuasar(Crud, { 295 | propsData: props 296 | }) 297 | 298 | wrapper.setData({ selected: [returnData[0]] })// select a row 299 | 300 | const spyOnPopulateColumnsWithSelectedRow = jest.spyOn(wrapper.vm, 'populateColumnsWithSelectedRow') 301 | 302 | expect(wrapper.vm.modalOpened).toBeFalsy() 303 | wrapper.vm.toggleModalWithData() 304 | 305 | expect(wrapper.vm.modalOpened).toBeTruthy() 306 | expect(spyOnPopulateColumnsWithSelectedRow).toHaveBeenCalled() 307 | }) 308 | 309 | test('get $emit successOnGet event', async () => { 310 | 311 | const props = defaultPropsData() 312 | const wrapper = mountQuasar(Crud, { 313 | propsData: props 314 | }) 315 | 316 | wrapper.vm.get() 317 | await wrapper.vm.$nextTick() 318 | 319 | expect(wrapper.emitted().successOnGet).toBeTruthy() 320 | 321 | }) 322 | 323 | test('o succefull get set the total size from api', async () => { 324 | axios.get.mockResolvedValue({ data: response }); 325 | 326 | const props = defaultPropsData() 327 | 328 | props.paginationTotalIndex = 'total' 329 | 330 | const wrapper = mountQuasar(Crud, { 331 | propsData: props 332 | }) 333 | 334 | expect(wrapper.vm.pagination.rowsNumber).toBe(1) 335 | 336 | wrapper.vm.get() 337 | await wrapper.vm.$nextTick() 338 | 339 | expect(wrapper.vm.pagination.rowsNumber).toBe(12) 340 | }) 341 | 342 | test('get $emit errorOnGet event', async () => { 343 | 344 | axios.get.mockRejectedValue('error msg') 345 | 346 | const props = defaultPropsData() 347 | 348 | const wrapper = mountQuasar(Crud, { 349 | propsData: props 350 | }) 351 | 352 | await wrapper.vm.$nextTick() 353 | await wrapper.vm.$nextTick() 354 | 355 | expect(wrapper.emitted().errorOnGet).toBeTruthy() 356 | }) 357 | 358 | test('delete $emit successOnDelete event', async () => { 359 | 360 | axios.delete.mockResolvedValue(); 361 | 362 | const props = defaultPropsData() 363 | const wrapper = mountQuasar(Crud, { 364 | propsData: props 365 | }) 366 | 367 | wrapper.setData({ selected: [returnData[0]] }) 368 | 369 | wrapper.vm.delete() 370 | await wrapper.vm.$nextTick() 371 | 372 | expect(wrapper.emitted().successOnDelete).toBeTruthy() 373 | 374 | }) 375 | 376 | test('delete $emit errorOnDelete event', async () => { 377 | 378 | axios.delete.mockRejectedValue('error msg') 379 | 380 | const props = defaultPropsData() 381 | 382 | const wrapper = mountQuasar(Crud, { 383 | propsData: props 384 | }) 385 | 386 | wrapper.setData({ selected: [returnData[0]] }) 387 | wrapper.vm.delete() 388 | 389 | await wrapper.vm.$nextTick() 390 | await wrapper.vm.$nextTick() 391 | 392 | expect(wrapper.emitted().errorOnDelete).toBeTruthy() 393 | }) 394 | 395 | test('put $emit successOnPut event', async () => { 396 | 397 | axios.put.mockResolvedValue(); 398 | 399 | const props = defaultPropsData() 400 | 401 | const wrapper = mountQuasar(Crud, { 402 | propsData: props 403 | }) 404 | wrapper.setData({ selected: [returnData[0]] }) 405 | wrapper.vm.save() 406 | 407 | await wrapper.vm.$nextTick() 408 | 409 | expect(wrapper.emitted().successOnPut).toBeTruthy() 410 | 411 | }) 412 | 413 | test('put $emit errorOnPut event', async () => { 414 | 415 | axios.put.mockRejectedValue('error msg') 416 | 417 | const props = defaultPropsData() 418 | 419 | const wrapper = mountQuasar(Crud, { 420 | propsData: props 421 | }) 422 | 423 | wrapper.setData({ selected: [returnData[0]] }) 424 | wrapper.vm.save() 425 | 426 | await wrapper.vm.$nextTick() 427 | await wrapper.vm.$nextTick() 428 | 429 | expect(wrapper.emitted().errorOnPut).toBeTruthy() 430 | }) 431 | 432 | test('post $emit successOnPost event', async () => { 433 | 434 | axios.post.mockResolvedValue(); 435 | 436 | const props = defaultPropsData() 437 | 438 | const wrapper = mountQuasar(Crud, { 439 | propsData: props 440 | }) 441 | 442 | wrapper.vm.save() 443 | await wrapper.vm.$nextTick() 444 | 445 | expect(wrapper.emitted().successOnPost).toBeTruthy() 446 | 447 | }) 448 | 449 | test('post $emit errorOnPost event', async () => { 450 | 451 | axios.post.mockRejectedValue('error msg') 452 | 453 | const props = defaultPropsData() 454 | 455 | const wrapper = mountQuasar(Crud, { 456 | propsData: props 457 | }) 458 | 459 | wrapper.vm.save() 460 | 461 | await wrapper.vm.$nextTick() 462 | await wrapper.vm.$nextTick() 463 | 464 | expect(wrapper.emitted().errorOnPost).toBeTruthy() 465 | }) 466 | 467 | test('reset validation when saving with validation errors, dont make requests', () => { 468 | 469 | axios.get.mockResolvedValue(returnData); 470 | axios.post.mockResolvedValue([]); 471 | axios.put.mockResolvedValue([]); 472 | 473 | const props = defaultPropsData() 474 | const wrapper = mountQuasar(Crud, { 475 | propsData: props 476 | }) 477 | 478 | wrapper.setData({ selected: [returnData[0]] }) 479 | 480 | const spyOnResetValidation = jest.spyOn(wrapper.vm, 'resetValitation') 481 | const spyOnPost = jest.spyOn(wrapper.vm, 'post') 482 | const spyOnPut = jest.spyOn(wrapper.vm, 'put') 483 | 484 | jest.spyOn(wrapper.vm, 'hasValidationErrors').mockImplementation(() => true) 485 | 486 | expect(spyOnResetValidation).not.toBeCalled() 487 | 488 | wrapper.vm.save() 489 | 490 | expect(spyOnResetValidation).toHaveBeenCalled() 491 | expect(spyOnPost).not.toHaveBeenCalled() 492 | expect(spyOnPut).not.toHaveBeenCalled() 493 | }) 494 | 495 | 496 | test('saving start loading', () => { 497 | 498 | axios.post.mockResolvedValue([]); 499 | axios.put.mockResolvedValue([]); 500 | 501 | const props = defaultPropsData() 502 | const wrapper = mountQuasar(Crud, { 503 | propsData: props 504 | }) 505 | 506 | wrapper.vm.save() 507 | 508 | expect(wrapper.vm.loading).toBeTruthy() 509 | }) 510 | 511 | test('save start put request because at least one row are selected', () => { 512 | 513 | axios.put.mockResolvedValue([]); 514 | axios.get.mockResolvedValue(returnData); 515 | 516 | const props = defaultPropsData() 517 | const wrapper = mountQuasar(Crud, { 518 | propsData: props 519 | }) 520 | 521 | wrapper.setData({ selected: [returnData[0]] }) 522 | 523 | const spyOnPut = jest.spyOn(wrapper.vm, 'put') 524 | const spyOnPost = jest.spyOn(wrapper.vm, 'post') 525 | 526 | wrapper.vm.save() 527 | 528 | expect(spyOnPut).toBeCalled() 529 | expect(spyOnPost).not.toBeCalled() 530 | }) 531 | 532 | test('request update pagination', () => { 533 | 534 | const props = defaultPropsData() 535 | const wrapper = mountQuasar(Crud, { 536 | propsData: props 537 | }) 538 | 539 | wrapper.vm.request({ pagination: { page: 5, rowsPerPage: 10 } }) 540 | 541 | expect(wrapper.vm.pagination).toStrictEqual({ page: 5, rowsPerPage: 10 }) 542 | }) 543 | 544 | // test('request fetch remote data', async () => { 545 | 546 | // const props = defaultPropsData() 547 | // const wrapper = await mountQuasar(Crud, { 548 | // propsData: props 549 | // }) 550 | 551 | 552 | // const spyOnGet = jest.spyOn(wrapper.vm, 'get') 553 | 554 | // wrapper.vm.request({ pagination: { page: 5, rowsPerPage: 10 } }) 555 | 556 | // await wrapper.vm.$nextTick() 557 | // expect(spyOnGet).toHaveBeenCalledTimes(2) 558 | // }) 559 | 560 | test('reset column values with proper types (string and array)', async () => { 561 | 562 | returnData[1]['first_name'] = 'boy' 563 | returnData[1]['email'] = [{}, {}] 564 | axios.get.mockResolvedValue(returnData); 565 | 566 | const props = defaultPropsData() 567 | 568 | const wrapper = await mountQuasar(Crud, { 569 | propsData: props 570 | }) 571 | 572 | wrapper.setData({ selected: [returnData[1]] }) 573 | wrapper.vm.populateColumnsWithSelectedRow() 574 | wrapper.vm.resetColumnValues() 575 | 576 | expect(wrapper.vm.columns[0].value).toBe("") 577 | expect(wrapper.vm.columns[1].value).toStrictEqual([]) 578 | }) 579 | test('reset column values with proper types ( Boolean and Number)', async () => { 580 | 581 | returnData[1].first_name = 199 582 | returnData[1].email = true 583 | axios.get.mockResolvedValue(returnData); 584 | 585 | const props = defaultPropsData() 586 | 587 | const wrapper = await mountQuasar(Crud, { 588 | propsData: props 589 | }) 590 | 591 | wrapper.setData({ selected: [returnData[1]] }) 592 | wrapper.vm.populateColumnsWithSelectedRow() 593 | wrapper.vm.resetColumnValues() 594 | 595 | expect(wrapper.vm.columns[0].value).toBe(0) 596 | expect(wrapper.vm.columns[1].value).toBe("") 597 | }) 598 | 599 | test('reset column values static config', async () => { 600 | 601 | returnData[1].first_name = 'Brother Lee' 602 | axios.get.mockResolvedValue(returnData); 603 | 604 | const props = defaultPropsData() 605 | props.columns[0].static = true 606 | 607 | const wrapper = await mountQuasar(Crud, { 608 | propsData: props 609 | }) 610 | 611 | wrapper.setData({ selected: [returnData[1]] }) 612 | 613 | expect(wrapper.vm.columns[0].value).toStrictEqual("") 614 | 615 | wrapper.vm.populateColumnsWithSelectedRow() 616 | expect(wrapper.vm.columns[0].value).toStrictEqual("Brother Lee") 617 | 618 | wrapper.vm.resetColumnValues() 619 | expect(wrapper.vm.columns[0].value).toStrictEqual("Brother Lee") 620 | 621 | }) 622 | -------------------------------------------------------------------------------- /test/jest/__tests__/columns.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | name: 'first_name', 4 | required: true, 5 | label: 'Name', 6 | align: 'left', 7 | field: 'first_name', 8 | sortable: true, 9 | qComponent: 'QInput', 10 | value: '', 11 | size: '6', 12 | rules: [val => val && val.length > 0 || 'Please type something'], 13 | showCreate: true, 14 | showUpdate: true 15 | }, 16 | { 17 | name: 'email', 18 | required: true, 19 | label: 'Email', 20 | align: 'center', 21 | field: 'email', 22 | sortable: true, 23 | qComponent: 'QInput', 24 | value: '', 25 | size: '6', 26 | showCreate: true, 27 | showUpdate: true 28 | } 29 | ] -------------------------------------------------------------------------------- /test/jest/__tests__/response.json: -------------------------------------------------------------------------------- 1 | { 2 | "page": 1, 3 | "per_page": 3, 4 | "total": 12, 5 | "total_pages": 4, 6 | "data": [ 7 | { 8 | "id": 1, 9 | "email": "george.bluth@reqres.in", 10 | "first_name": "George", 11 | "last_name": "Bluth", 12 | "avatar": "https://reqres.in/img/faces/1-image.jpg" 13 | }, 14 | { 15 | "id": 2, 16 | "email": "janet.weaver@reqres.in", 17 | "first_name": "Janet", 18 | "last_name": "Weaver", 19 | "avatar": "https://reqres.in/img/faces/2-image.jpg" 20 | }, 21 | { 22 | "id": 3, 23 | "email": "emma.wong@reqres.in", 24 | "first_name": "Emma", 25 | "last_name": "Wong", 26 | "avatar": "https://reqres.in/img/faces/3-image.jpg" 27 | } 28 | ], 29 | "support": { 30 | "url": "https://reqres.in/#support-heading", 31 | "text": "To keep ReqRes free, contributions towards server costs are appreciated!" 32 | } 33 | } -------------------------------------------------------------------------------- /test/jest/index.js: -------------------------------------------------------------------------------- 1 | import { createLocalVue, shallowMount } from '@vue/test-utils' 2 | import * as All from 'quasar' 3 | import Vue from 'vue' 4 | const { Cookies, Quasar } = All 5 | 6 | const components = Object.keys(All).reduce((object, key) => { 7 | const val = All[key] 8 | if (val && val.component && val.component.name != null) { 9 | object[key] = val 10 | } 11 | return object 12 | }, {}) 13 | 14 | const localVue = createLocalVue() 15 | localVue.use(Quasar, { components }) 16 | 17 | export const mountQuasar = (component, componentOptions, options = { plugins: [] }) => { 18 | const app = {} 19 | 20 | if (options) { 21 | const ssrContext = options.ssr ? mockSsrContext() : null 22 | 23 | if (options.cookies) { 24 | const cookieStorage = ssrContext ? Cookies.parseSSR(ssrContext) : Cookies 25 | const cookies = options.cookies 26 | Object.keys(cookies).forEach(key => { 27 | cookieStorage.set(key, cookies[key]) 28 | }) 29 | } 30 | 31 | if (options) { 32 | const plugins = options.plugins 33 | plugins.forEach(plugin => { 34 | plugin({ app, store, router, Vue: localVue, ssrContext }) 35 | }) 36 | } 37 | } 38 | 39 | // mock vue-i18n 40 | const $t = () => { } 41 | const $tc = () => { } 42 | const $n = () => { } 43 | const $d = () => { } 44 | 45 | return shallowMount(component, { 46 | localVue, 47 | ...componentOptions, 48 | mocks: { $t, $tc, $n, $d }, 49 | // Injections for Components with a QPage root Element 50 | provide: { 51 | pageContainer: true, 52 | layout: { 53 | header: {}, 54 | right: {}, 55 | footer: {}, 56 | left: {} 57 | } 58 | } 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /test/jest/jest.setup.js: -------------------------------------------------------------------------------- 1 | // No console.log() / setTimeout 2 | // console.log = jest.fn(() => { throw new Error('Do not use console.log() in production') }) 3 | jest.setTimeout(1000) 4 | 5 | // jest speedup when errors are part of the game 6 | // Error.stackTraceLimit = 0 7 | 8 | global.Promise = require('promise') 9 | 10 | /* 11 | import chai from 'chai' 12 | // Make sure chai and jasmine ".not" play nice together 13 | // https://medium.com/@RubenOostinga/combining-chai-and-jest-matchers-d12d1ffd0303 14 | // updated here: https://www.andrewsouthpaw.com/jest-chai/ 15 | const originalNot = Object.getOwnPropertyDescriptor(chai.Assertion.prototype, 'not').get 16 | Object.defineProperty(chai.Assertion.prototype, 'not', { 17 | get() { 18 | Object.assign(this, this.assignedNot) 19 | return originalNot.apply(this) 20 | }, 21 | set(newNot) { 22 | this.assignedNot = newNot 23 | return newNot 24 | } 25 | }) 26 | 27 | // Combine both jest and chai matchers on expect 28 | const originalExpect = global.expect 29 | 30 | global.expect = (actual) => { 31 | const originalMatchers = originalExpect(actual) 32 | const chaiMatchers = chai.expect(actual) 33 | 34 | // Add middleware to Chai matchers to increment Jest assertions made 35 | const { assertionsMade } = originalExpect.getState() 36 | Object.defineProperty(chaiMatchers, 'to', { 37 | get() { 38 | originalExpect.setState({ assertionsMade: assertionsMade + 1 }) 39 | return chai.expect(actual) 40 | }, 41 | }) 42 | 43 | const combinedMatchers = Object.assign(chaiMatchers, originalMatchers) 44 | return combinedMatchers 45 | } 46 | Object.keys(originalExpect).forEach(key => (global.expect[key] = originalExpect[key])) 47 | */ 48 | 49 | // do this to make sure we don't get multiple hits from both webpacks when running SSR 50 | setTimeout(() => { 51 | // do nothing 52 | }, 1) 53 | -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "test:unit": "jest --runInBand ", 4 | "test:unit:coverage": "jest --runInBand --coverage", 5 | "test:unit:ci": "jest --ci --runInBand --coverage && codecov" 6 | }, 7 | "dependencies": { 8 | "@babel/core": "^7.13.14", 9 | "@babel/preset-env": "^7.13.12", 10 | "@vue/test-utils": "^1.1.3", 11 | "babel-core": "^7.0.0-bridge.0", 12 | "babel-jest": "^26.6.3", 13 | "vue": "^2.6.12", 14 | "vue-jest": "^3.0.7", 15 | "vue-template-compiler": "^2.6.12" 16 | }, 17 | "devDependencies": { 18 | "@babel/plugin-transform-runtime": "^7.13.10", 19 | "axios": "^0.21.1", 20 | "codecov": "^3.8.1", 21 | "jest": "^26.6.3", 22 | "jest-serializer-vue": "^2.0.2", 23 | "jest-transform-stub": "^2.0.0", 24 | "quasar": "^1.15.9" 25 | } 26 | } -------------------------------------------------------------------------------- /ui/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /ui/.npmignore: -------------------------------------------------------------------------------- 1 | /build 2 | /dev 3 | umd-test.html 4 | 5 | .DS_Store 6 | .thumbs.db 7 | yarn.lock 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | 12 | # Editor directories and files 13 | .idea 14 | .vscode 15 | *.suo 16 | *.ntvs* 17 | *.njsproj 18 | *.sln 19 | .editorconfig 20 | .eslintignore 21 | .eslintrc.js 22 | -------------------------------------------------------------------------------- /ui/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14-alpine as develop-stage 2 | WORKDIR /app 3 | 4 | # Install app dependencies 5 | COPY package*.json /app 6 | RUN yarn global add @quasar/cli 7 | RUN yarn 8 | 9 | EXPOSE 8080 10 | 11 | WORKDIR /app/ui 12 | 13 | CMD ["yarn", "dev"] 14 | -------------------------------------------------------------------------------- /ui/build/config.js: -------------------------------------------------------------------------------- 1 | const { name, author, version } = require('../package.json') 2 | const year = (new Date()).getFullYear() 3 | 4 | module.exports = { 5 | name, 6 | version, 7 | banner: 8 | '/*!\n' + 9 | ' * ' + name + ' v' + version + '\n' + 10 | ' * (c) ' + year + ' ' + author + '\n' + 11 | ' * Released under the MIT License.\n' + 12 | ' */\n' 13 | } 14 | -------------------------------------------------------------------------------- /ui/build/entry/index.common.js: -------------------------------------------------------------------------------- 1 | import Plugin from '../../src/index' 2 | 3 | export default Plugin 4 | -------------------------------------------------------------------------------- /ui/build/entry/index.esm.js: -------------------------------------------------------------------------------- 1 | import Plugin from '../../src/index' 2 | 3 | export default Plugin 4 | export * from '../../src/index' 5 | -------------------------------------------------------------------------------- /ui/build/entry/index.umd.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Plugin from '../../src/index' 3 | 4 | Vue.use(Plugin) 5 | 6 | export * from '../../src/index' 7 | -------------------------------------------------------------------------------- /ui/build/index.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = 'production' 2 | 3 | const { join } = require('path') 4 | const { createFolder } = require('./utils') 5 | const { green, blue } = require('chalk') 6 | 7 | console.log() 8 | 9 | require('./script.app-ext.js').syncAppExt() 10 | require('./script.clean.js') 11 | 12 | console.log(` 📦 Building ${green('v' + require('../package.json').version)}...\n`) 13 | 14 | createFolder('dist') 15 | 16 | require(join(__dirname, './script.javascript.js')) 17 | -------------------------------------------------------------------------------- /ui/build/script.app-ext.js: -------------------------------------------------------------------------------- 1 | const 2 | fs = require('fs'), 3 | path = require('path'), 4 | root = path.resolve(__dirname, '../..'), 5 | resolvePath = file => path.resolve(root, file), 6 | { blue } = require('chalk') 7 | 8 | const writeJson = function (file, json) { 9 | return fs.writeFileSync(file, JSON.stringify(json, null, 2) + '\n', 'utf-8') 10 | } 11 | 12 | module.exports.syncAppExt = function (both = true) { 13 | // make sure this project has an app-extension project 14 | const appExtDir = resolvePath('app-extension') 15 | if (!fs.existsSync(appExtDir)) { 16 | return 17 | } 18 | 19 | // make sure this project has an ui project 20 | const uiDir = resolvePath('ui') 21 | if (!fs.existsSync(uiDir)) { 22 | return 23 | } 24 | 25 | // get version and name from ui package.json 26 | const { name, version } = require(resolvePath(resolvePath('ui/package.json'))) 27 | 28 | // read app-ext package.json 29 | const appExtFile = resolvePath('app-extension/package.json') 30 | let appExtJson = require(appExtFile), 31 | finished = false 32 | 33 | // sync version numbers 34 | if (both === true) { 35 | appExtJson.version = version 36 | } 37 | 38 | // check dependencies 39 | if (appExtJson.dependencies !== void 0) { 40 | if (appExtJson.dependencies[name] !== void 0) { 41 | appExtJson.dependencies[name] = '^' + version 42 | finished = true 43 | } 44 | } 45 | // check devDependencies, if not finished 46 | if (finished === false && appExtJson.devDependencies !== void 0) { 47 | if (appExtJson.devDependencies[name] !== void 0) { 48 | appExtJson.devDependencies[name] = '^' + version 49 | finished = true 50 | } 51 | } 52 | 53 | if (finished === true) { 54 | writeJson(appExtFile, appExtJson) 55 | console.log(` ⭐️ App Extension version ${blue(appExtJson.name)} synced with UI version.\n`) 56 | return 57 | } 58 | 59 | console.error(` App Extension version and dependency NOT synced.\n`) 60 | } 61 | -------------------------------------------------------------------------------- /ui/build/script.clean.js: -------------------------------------------------------------------------------- 1 | var 2 | rimraf = require('rimraf'), 3 | path = require('path') 4 | 5 | rimraf.sync(path.resolve(__dirname, '../dist/*')) 6 | console.log(` 💥 Cleaned build artifacts.\n`) 7 | -------------------------------------------------------------------------------- /ui/build/script.javascript.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | const fse = require('fs-extra') 4 | const rollup = require('rollup') 5 | const uglify = require('uglify-es') 6 | const buble = require('@rollup/plugin-buble') 7 | const json = require('@rollup/plugin-json') 8 | const { nodeResolve } = require('@rollup/plugin-node-resolve') 9 | 10 | const buildConf = require('./config') 11 | const buildUtils = require('./utils') 12 | 13 | const rollupPlugins = [ 14 | nodeResolve({ 15 | extensions: ['.js'], 16 | preferBuiltins: false 17 | }), 18 | json(), 19 | buble({ 20 | objectAssign: 'Object.assign' 21 | }) 22 | ] 23 | 24 | const builds = [ 25 | { 26 | rollup: { 27 | input: { 28 | input: pathResolve('entry/index.esm.js') 29 | }, 30 | output: { 31 | file: pathResolve('../dist/index.esm.js'), 32 | format: 'es' 33 | } 34 | }, 35 | build: { 36 | // unminified: true, 37 | minified: true 38 | } 39 | }, 40 | { 41 | rollup: { 42 | input: { 43 | input: pathResolve('entry/index.common.js') 44 | }, 45 | output: { 46 | file: pathResolve('../dist/index.common.js'), 47 | format: 'cjs' 48 | } 49 | }, 50 | build: { 51 | // unminified: true, 52 | minified: true 53 | } 54 | }, 55 | { 56 | rollup: { 57 | input: { 58 | input: pathResolve('entry/index.umd.js') 59 | }, 60 | output: { 61 | name: 'y', 62 | file: pathResolve('../dist/index.umd.js'), 63 | format: 'umd' 64 | } 65 | }, 66 | build: { 67 | unminified: true, 68 | minified: true, 69 | minExt: true 70 | } 71 | } 72 | ] 73 | 74 | // Add your asset folders here, if needed 75 | // addAssets(builds, 'icon-set', 'iconSet') 76 | // addAssets(builds, 'lang', 'lang') 77 | 78 | build(builds) 79 | 80 | /** 81 | * Helpers 82 | */ 83 | 84 | function pathResolve (_path) { 85 | return path.resolve(__dirname, _path) 86 | } 87 | 88 | // eslint-disable-next-line no-unused-vars 89 | function addAssets (builds, type, injectName) { 90 | const 91 | files = fs.readdirSync(pathResolve('../../ui/src/components/' + type)), 92 | plugins = [ buble(bubleConfig) ], 93 | outputDir = pathResolve(`../dist/${type}`) 94 | 95 | fse.mkdirp(outputDir) 96 | 97 | files 98 | .filter(file => file.endsWith('.js')) 99 | .forEach(file => { 100 | const name = file.substr(0, file.length - 3).replace(/-([a-z])/g, g => g[1].toUpperCase()) 101 | builds.push({ 102 | rollup: { 103 | input: { 104 | input: pathResolve(`../src/components/${type}/${file}`), 105 | plugins 106 | }, 107 | output: { 108 | file: addExtension(pathResolve(`../dist/${type}/${file}`), 'umd'), 109 | format: 'umd', 110 | name: `y.${injectName}.${name}` 111 | } 112 | }, 113 | build: { 114 | minified: true 115 | } 116 | }) 117 | }) 118 | } 119 | 120 | function build (builds) { 121 | return Promise 122 | .all(builds.map(genConfig).map(buildEntry)) 123 | .catch(buildUtils.logError) 124 | } 125 | 126 | function genConfig (opts) { 127 | Object.assign(opts.rollup.input, { 128 | plugins: rollupPlugins, 129 | external: [ 'vue', 'quasar' ] 130 | }) 131 | 132 | Object.assign(opts.rollup.output, { 133 | banner: buildConf.banner, 134 | globals: { vue: 'Vue', quasar: 'Quasar' } 135 | }) 136 | 137 | return opts 138 | } 139 | 140 | function addExtension (filename, ext = 'min') { 141 | const insertionPoint = filename.lastIndexOf('.') 142 | return `${filename.slice(0, insertionPoint)}.${ext}${filename.slice(insertionPoint)}` 143 | } 144 | 145 | function buildEntry (config) { 146 | return rollup 147 | .rollup(config.rollup.input) 148 | .then(bundle => bundle.generate(config.rollup.output)) 149 | .then(({ output }) => { 150 | const code = config.rollup.output.format === 'umd' 151 | ? injectVueRequirement(output[0].code) 152 | : output[0].code 153 | 154 | return config.build.unminified 155 | ? buildUtils.writeFile(config.rollup.output.file, code) 156 | : code 157 | }) 158 | .then(code => { 159 | if (!config.build.minified) { 160 | return code 161 | } 162 | 163 | const minified = uglify.minify(code, { 164 | compress: { 165 | pure_funcs: ['makeMap'] 166 | } 167 | }) 168 | 169 | if (minified.error) { 170 | return Promise.reject(minified.error) 171 | } 172 | 173 | return buildUtils.writeFile( 174 | config.build.minExt === true 175 | ? addExtension(config.rollup.output.file) 176 | : config.rollup.output.file, 177 | buildConf.banner + minified.code, 178 | true 179 | ) 180 | }) 181 | .catch(err => { 182 | console.error(err) 183 | process.exit(1) 184 | }) 185 | } 186 | 187 | function injectVueRequirement (code) { 188 | // eslint-disable-next-line 189 | const index = code.indexOf(`Vue = Vue && Vue.hasOwnProperty('default') ? Vue['default'] : Vue`) 190 | 191 | if (index === -1) { 192 | return code 193 | } 194 | 195 | const checkMe = ` if (Vue === void 0) { 196 | console.error('[ Quasar ] Vue is required to run. Please add a script tag for it before loading Quasar.') 197 | return 198 | } 199 | ` 200 | 201 | return code.substring(0, index - 1) + 202 | checkMe + 203 | code.substring(index) 204 | } 205 | -------------------------------------------------------------------------------- /ui/build/script.open-umd.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path') 2 | const open = require('open') 3 | 4 | open( 5 | resolve(__dirname, '../umd-test.html') 6 | ) 7 | -------------------------------------------------------------------------------- /ui/build/utils.js: -------------------------------------------------------------------------------- 1 | const 2 | fs = require('fs'), 3 | path = require('path'), 4 | zlib = require('zlib'), 5 | { green, blue, red, cyan } = require('chalk'), 6 | kebabRegex = /[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g 7 | 8 | function getSize (code) { 9 | return (code.length / 1024).toFixed(2) + 'kb' 10 | } 11 | 12 | module.exports.createFolder = function (folder) { 13 | const dir = path.join(__dirname, '..', folder) 14 | if (!fs.existsSync(dir)) { 15 | fs.mkdirSync(dir) 16 | } 17 | } 18 | 19 | module.exports.writeFile = function (dest, code, zip) { 20 | const banner = dest.indexOf('.json') > -1 21 | ? red('[json]') 22 | : dest.indexOf('.js') > -1 23 | ? green('[js] ') 24 | : dest.indexOf('.ts') > -1 25 | ? cyan('[ts] ') 26 | : blue('[css] ') 27 | 28 | return new Promise((resolve, reject) => { 29 | function report (extra) { 30 | console.log(`${banner} ${path.relative(process.cwd(), dest).padEnd(41)} ${getSize(code).padStart(8)}${extra || ''}`) 31 | resolve(code) 32 | } 33 | 34 | fs.writeFile(dest, code, err => { 35 | if (err) return reject(err) 36 | if (zip) { 37 | zlib.gzip(code, (err, zipped) => { 38 | if (err) return reject(err) 39 | report(` (gzipped: ${getSize(zipped).padStart(8)})`) 40 | }) 41 | } 42 | else { 43 | report() 44 | } 45 | }) 46 | }) 47 | } 48 | 49 | module.exports.readFile = function (file) { 50 | return fs.readFileSync(file, 'utf-8') 51 | } 52 | 53 | module.exports.logError = function (err) { 54 | console.error('\n' + red('[Error]'), err) 55 | console.log() 56 | } 57 | -------------------------------------------------------------------------------- /ui/dev/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "@babel/plugin-syntax-dynamic-import" 4 | ], 5 | "env": { 6 | "test": { 7 | "plugins": [ 8 | "dynamic-import-node" 9 | ], 10 | "presets": [ 11 | [ 12 | "@babel/preset-env", 13 | { 14 | "modules": "commonjs", 15 | "targets": { 16 | "node": "current" 17 | } 18 | } 19 | ] 20 | ] 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /ui/dev/.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 | -------------------------------------------------------------------------------- /ui/dev/.gitignore: -------------------------------------------------------------------------------- 1 | .quasar 2 | .DS_Store 3 | .thumbs.db 4 | node_modules 5 | /dist 6 | /src-cordova/node_modules 7 | /src-cordova/platforms 8 | /src-cordova/plugins 9 | /src-cordova/www 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | -------------------------------------------------------------------------------- /ui/dev/.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | plugins: [ 5 | // to edit target browsers: use "browserslist" field in package.json 6 | require('autoprefixer') 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /ui/dev/README.md: -------------------------------------------------------------------------------- 1 | # Dev app (playground) 2 | 3 | Adding .vue files to src/pages/ will auto-add them to the Index page list. 4 | -------------------------------------------------------------------------------- /ui/dev/babel.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | // eslint-disable-next-line @typescript-eslint/no-var-requires 3 | const fs = require('fs-extra'); 4 | let extend = undefined; 5 | 6 | /** 7 | * The .babelrc file has been created to assist Jest for transpiling. 8 | * You should keep your application's babel rules in this file. 9 | */ 10 | 11 | if (fs.existsSync('./.babelrc')) { 12 | extend = './.babelrc'; 13 | } 14 | 15 | module.exports = { 16 | presets: ['@quasar/babel-preset-app'], 17 | extends: extend, 18 | }; 19 | -------------------------------------------------------------------------------- /ui/dev/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dev", 3 | "version": "1.0.0", 4 | "description": "A Quasar Framework app", 5 | "productName": "Quasar App", 6 | "private": true, 7 | "scripts": { 8 | "dev": "quasar dev", 9 | "dev:ssr": "quasar dev -m ssr", 10 | "dev:ios": "quasar dev -m ios", 11 | "dev:android": "quasar dev -m android", 12 | "dev:electron": "quasar dev -m electron", 13 | "build": "quasar build" 14 | }, 15 | "dependencies": { 16 | "@quasar/extras": "^1.0.0", 17 | "core-js": "^3.0.0", 18 | "highlight.js": "^10.7.1", 19 | "markdown-loader": "^6.0.0" 20 | }, 21 | "devDependencies": { 22 | "@quasar/app": "^2.0.0", 23 | "@quasar/quasar-app-extension-qmarkdown": "^1.4.1", 24 | "eslint-plugin-jest": "^24.1.0" 25 | }, 26 | "browserslist": [ 27 | "last 10 Chrome versions", 28 | "last 10 Firefox versions", 29 | "last 4 Edge versions", 30 | "last 7 Safari versions", 31 | "last 8 Android versions", 32 | "last 8 ChromeAndroid versions", 33 | "last 8 FirefoxAndroid versions", 34 | "last 10 iOS versions", 35 | "last 5 Opera versions" 36 | ], 37 | "engines": { 38 | "node": ">= 8.9.0", 39 | "npm": ">= 5.6.0", 40 | "yarn": ">= 1.6.0" 41 | } 42 | } -------------------------------------------------------------------------------- /ui/dev/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odranoelBR/vue-quasar-crud/33fe100784681eceace644065793d76bc96a9601/ui/dev/public/favicon.ico -------------------------------------------------------------------------------- /ui/dev/public/icons/favicon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odranoelBR/vue-quasar-crud/33fe100784681eceace644065793d76bc96a9601/ui/dev/public/icons/favicon-128x128.png -------------------------------------------------------------------------------- /ui/dev/public/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odranoelBR/vue-quasar-crud/33fe100784681eceace644065793d76bc96a9601/ui/dev/public/icons/favicon-16x16.png -------------------------------------------------------------------------------- /ui/dev/public/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odranoelBR/vue-quasar-crud/33fe100784681eceace644065793d76bc96a9601/ui/dev/public/icons/favicon-32x32.png -------------------------------------------------------------------------------- /ui/dev/public/icons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odranoelBR/vue-quasar-crud/33fe100784681eceace644065793d76bc96a9601/ui/dev/public/icons/favicon-96x96.png -------------------------------------------------------------------------------- /ui/dev/quasar.conf.js: -------------------------------------------------------------------------------- 1 | // Configuration for your app 2 | // https://quasar.dev/quasar-cli/quasar-conf-js 3 | 4 | const path = require('path') 5 | 6 | module.exports = function (ctx) { 7 | return { 8 | // app boot file (/src/boot) 9 | // --> boot files are part of "main.js" 10 | boot: [ 11 | 'register.js' 12 | ], 13 | 14 | css: [ 15 | ], 16 | 17 | extras: [ 18 | // 'ionicons-v4', 19 | // 'mdi-v5', 20 | 'fontawesome-v5', 21 | // 'eva-icons', 22 | // 'themify', 23 | // 'line-awesome', 24 | // 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both! 25 | 26 | 'roboto-font', // optional, you are not bound to it 27 | 'material-icons' // optional, you are not bound to it 28 | ], 29 | 30 | framework: { 31 | iconSet: 'material-icons', // Quasar icon set 32 | lang: 'en-us', // Quasar language pack 33 | config: {}, 34 | 35 | // Possible values for "importStrategy": 36 | // * 'auto' - (DEFAULT) Auto-import needed Quasar components & directives 37 | // * 'all' - Manually specify what to import 38 | importStrategy: 'auto', 39 | 40 | // Quasar plugins 41 | plugins: ['Notify', 'Dialog'] 42 | }, 43 | 44 | // animations: 'all', // --- includes all animations 45 | animations: ['fadeIn', 'fadeOut'], 46 | 47 | // Full list of options: https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-build 48 | build: { 49 | vueRouterMode: 'history', 50 | extendWebpack (cfg, { isServer, isClient }) { 51 | cfg.resolve.alias = { 52 | ...cfg.resolve.alias, // This adds the existing alias 53 | 54 | // Add your own alias like this 55 | '@components': path.resolve(__dirname, './src/components') 56 | } 57 | }, 58 | chainWebpack (chain) { 59 | chain.resolve.alias.merge({ 60 | 'ui': path.resolve(__dirname, '../src/index.js') 61 | }) 62 | }, 63 | }, 64 | 65 | devServer: { 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /ui/dev/quasar.extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "@quasar/qmarkdown": { 3 | "import_md": true, 4 | "import_vmd": true 5 | } 6 | } -------------------------------------------------------------------------------- /ui/dev/quasar.testing.json: -------------------------------------------------------------------------------- 1 | { 2 | "unit-jest": { 3 | "runnerCommand": "jest --ci" 4 | } 5 | } -------------------------------------------------------------------------------- /ui/dev/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /ui/dev/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odranoelBR/vue-quasar-crud/33fe100784681eceace644065793d76bc96a9601/ui/dev/src/assets/.gitkeep -------------------------------------------------------------------------------- /ui/dev/src/assets/columns.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Columns 4 | = 5 | > Additional to Quasar QTable columns, Quasar Crud add some more to dinamically build forms, validations ... 6 | 7 | 8 | | Column name | Description | Type | Required | Default | 9 | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ------ | --------------- | 10 | | qComponent | The Quasar form component to render (QInput, QSelect ...) | String |no | null 11 | | showCreate | The field will show on create form | Boolean | no |true 12 | | showUpdate | The field will show on update form | Boolean | no | true 13 | | size | The size of columns on form | Boolean | yes if showCreate or showUpdate | null 14 | | options | The options Array of QSelect | Array | yes if field is QSelect | [] 15 | | customize | If the column use slot | Boolean | no | null -------------------------------------------------------------------------------- /ui/dev/src/assets/getting_started.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Quasar Crud 4 | = 5 | > Quasar Crud Component based on QTable using Axios API to fetch remote data. 6 | 7 | ## Install / Uninstall 8 | 9 | ### Quasar Extension 10 | ```js 11 | quasar ext add crud //install 12 | 13 | quasar ext remove crud //uninstall 14 | ``` 15 | 16 | **OR**: 17 | Create and register a boot file: 18 | 19 | ```js 20 | npm i quasar-app-extension-crud 21 | yarn add quasar-app-extension-crud 22 | ``` 23 | 24 | ```js 25 | import Vue from 'vue' 26 | import Plugin from 'quasar-ui-crud/src/index.js' 27 | 28 | Vue.use(Plugin) 29 | ``` 30 | 31 | ## Demo 32 | 33 | 34 | -------------------------------------------------------------------------------- /ui/dev/src/boot/register.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VuePlugin from 'ui' // "ui" is aliased in quasar.conf.js 3 | import hljs from 'highlight.js' 4 | import htmlbars from 'highlight.js/lib/languages/htmlbars' 5 | import javascript from 'highlight.js/lib/languages/javascript' 6 | import 'highlight.js/styles/github.css'; 7 | 8 | hljs.registerLanguage('htmlbars', htmlbars); 9 | hljs.registerLanguage('javascript', javascript); 10 | Vue.use(VuePlugin) 11 | Vue.use(hljs.vuePlugin); 12 | -------------------------------------------------------------------------------- /ui/dev/src/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/odranoelBR/vue-quasar-crud/33fe100784681eceace644065793d76bc96a9601/ui/dev/src/components/.gitkeep -------------------------------------------------------------------------------- /ui/dev/src/components/QTabExampleFive.vue: -------------------------------------------------------------------------------- 1 | 70 | 71 | 150 | 151 | -------------------------------------------------------------------------------- /ui/dev/src/components/QTabExampleFour.vue: -------------------------------------------------------------------------------- 1 | 69 | 70 | 153 | 154 | -------------------------------------------------------------------------------- /ui/dev/src/components/QTabExampleOne.vue: -------------------------------------------------------------------------------- 1 | 80 | 81 | 153 | 154 | -------------------------------------------------------------------------------- /ui/dev/src/components/QTabExampleSix.vue: -------------------------------------------------------------------------------- 1 | 79 | 80 | 169 | 170 | -------------------------------------------------------------------------------- /ui/dev/src/components/QTabExampleThree.vue: -------------------------------------------------------------------------------- 1 | 95 | 96 | 179 | 180 | -------------------------------------------------------------------------------- /ui/dev/src/components/QTabExampleTwo.vue: -------------------------------------------------------------------------------- 1 | 65 | 66 | 128 | 129 | -------------------------------------------------------------------------------- /ui/dev/src/css/app.sass: -------------------------------------------------------------------------------- 1 | @import '../../../src/index.sass' 2 | 3 | .q-markdown table 4 | border: none 5 | .q-markdown table td:nth-child(1) 6 | color: $teal-9 7 | .q-markdown table tr 8 | background: $teal-3 9 | border: none 10 | .q-markdown table thead tr th 11 | border: none 12 | .q-markdown table td 13 | border: none 14 | .q-markdown table tr 15 | background: white 16 | .q-markdown table tr:nth-child(odd) 17 | background: $teal-3 18 | -------------------------------------------------------------------------------- /ui/dev/src/css/quasar.variables.sass: -------------------------------------------------------------------------------- 1 | // Quasar Sass (& SCSS) Variables 2 | // -------------------------------------------------- 3 | // To customize the look and feel of this app, you can override 4 | // the Stylus variables found in Quasar's source Stylus files. 5 | 6 | // Check documentation for full list of Quasar variables 7 | 8 | // Your own variables (that are declared here) and Quasar's own 9 | // ones will be available out of the box in your .vue/.scss/.sass files 10 | 11 | // It's highly recommended to change the default colors 12 | // to match your app's branding. 13 | // Tip: Use the "Theme Builder" on Quasar's documentation website. 14 | 15 | $primary : #009BEE 16 | $secondary : #005E86 17 | $accent : #EB8330 18 | 19 | $positive : #87BF57 20 | $negative : #C10015 21 | $info : #31CCEC 22 | $warning : #F2C037 23 | 24 | 25 | -------------------------------------------------------------------------------- /ui/dev/src/index.template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= productName %> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /ui/dev/src/layouts/Drawer.vue: -------------------------------------------------------------------------------- 1 | 59 | 83 | 93 | -------------------------------------------------------------------------------- /ui/dev/src/layouts/Layout.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 64 | -------------------------------------------------------------------------------- /ui/dev/src/pages/PageApi.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 31 | 32 | 34 | -------------------------------------------------------------------------------- /ui/dev/src/pages/PageIndex.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 30 | 31 | 48 | -------------------------------------------------------------------------------- /ui/dev/src/pages/PageMoreExamples.vue: -------------------------------------------------------------------------------- 1 | 81 | 82 | 95 | 96 | 100 | -------------------------------------------------------------------------------- /ui/dev/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | 4 | import routes from './routes' 5 | 6 | Vue.use(VueRouter) 7 | 8 | /* 9 | * If not building with SSR mode, you can 10 | * directly export the Router instantiation 11 | */ 12 | 13 | export default function (/* { store, ssrContext } */) { 14 | const Router = new VueRouter({ 15 | scrollBehavior: () => ({ x: 0, y: 0 }), 16 | routes, 17 | 18 | // Leave these as is and change from quasar.conf.js instead! 19 | // quasar.conf.js -> build -> vueRouterMode 20 | // quasar.conf.js -> build -> publicPath 21 | mode: process.env.VUE_ROUTER_MODE, 22 | base: process.env.VUE_ROUTER_BASE 23 | }) 24 | 25 | // we get each page from server first! 26 | if (process.env.MODE === 'ssr' && process.env.CLIENT) { 27 | console.log('!!!!') 28 | console.log('On route change we deliberately load page from server -- in order to test hydration errors') 29 | console.log('!!!!') 30 | 31 | let reload = false 32 | Router.beforeEach((to, _, next) => { 33 | if (reload) { 34 | window.location.href = to.fullPath 35 | return 36 | } 37 | reload = true 38 | next() 39 | }) 40 | } 41 | 42 | return Router 43 | } 44 | -------------------------------------------------------------------------------- /ui/dev/src/router/pages.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Export files list for /pages folder 3 | */ 4 | 5 | function kebabCase (str) { 6 | const result = str.replace( 7 | /[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g, 8 | match => '-' + match.toLowerCase() 9 | ) 10 | return (str[0] === str[0].toUpperCase()) 11 | ? result.substring(1) 12 | : result 13 | } 14 | 15 | function slugify (str) { 16 | return encodeURIComponent(String(str).trim().replace(/\s+/g, '-')) 17 | } 18 | 19 | export default require.context('../pages', true, /^\.\/.*\.vue$/) 20 | .keys() 21 | .map(page => page.slice(2).replace('.vue', '')) 22 | .filter(page => page !== 'Index') 23 | .map(page => ({ 24 | file: page, 25 | title: page + '.vue', 26 | path: slugify(kebabCase(page)) 27 | })) 28 | -------------------------------------------------------------------------------- /ui/dev/src/router/routes.js: -------------------------------------------------------------------------------- 1 | import pages from './pages' 2 | 3 | const children = pages.map(page => ({ 4 | path: page.path, 5 | component: () => import('pages/' + page.file + '.vue') 6 | })) 7 | 8 | const routes = [ 9 | { 10 | path: '/', 11 | component: () => import('layouts/Layout.vue'), 12 | children: [ 13 | { path: '/', component: () => import('pages/PageIndex.vue'), name: 'started' }, 14 | { path: '/more-examples', component: () => import('pages/PageMoreExamples.vue'), name: 'more-examples' }, 15 | { path: '/api', component: () => import('pages/PageApi.vue'), name: 'api' } 16 | ].concat(children) 17 | } 18 | ] 19 | 20 | export default routes 21 | -------------------------------------------------------------------------------- /ui/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.3" 2 | 3 | services: 4 | my_app: 5 | build: . 6 | ports: 7 | - "8080:8080" 8 | volumes: 9 | - C:\Users\Leo\Documents\Projetos\vue-quasar-crud:/app 10 | stdin_open: true 11 | tty: true 12 | environment: 13 | - CHOKIDAR_USEPOLLING=true -------------------------------------------------------------------------------- /ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quasar-ui-crud", 3 | "version": "1.0.1", 4 | "author": "odranoelo_O@hotmail.com", 5 | "description": "Crud quasar on rest apis", 6 | "license": "MIT", 7 | "module": "dist/index.esm.js", 8 | "main": "dist/index.common.js", 9 | "scripts": { 10 | "dev": "cd dev && yarn dev && cd ..", 11 | "docgen": "yarn vue-docgen src/components/**/*.vue . -r", 12 | "dev:umd": "yarn build && node build/script.open-umd.js", 13 | "dev:ssr": "cd dev && yarn 'dev:ssr' && cd ..", 14 | "dev:ios": "cd dev && yarn 'dev:ios' && cd ..", 15 | "dev:android": "cd dev && yarn 'dev:android' && cd ..", 16 | "dev:electron": "cd dev && yarn 'dev:electron' && cd ..", 17 | "build": "node build/index.js", 18 | "build:js": "node build/script.javascript.js" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "" 23 | }, 24 | "bugs": "", 25 | "homepage": "", 26 | "devDependencies": { 27 | "@babel/preset-env": "^7.13.12", 28 | "@rollup/plugin-buble": "^0.20.0", 29 | "@rollup/plugin-json": "^4.0.0", 30 | "@rollup/plugin-node-resolve": "^10.0.0", 31 | "@vue/test-utils": "^1.1.3", 32 | "autoprefixer": "^10.0.2", 33 | "axios": "^0.21.1", 34 | "babel-core": "^6.26.3", 35 | "chalk": "^4.1.0", 36 | "cssnano": "^4.1.10", 37 | "fs-extra": "^8.1.0", 38 | "jest": "^26.6.3", 39 | "open": "^7.3.0", 40 | "postcss": "^8.1.9", 41 | "quasar": "^1.0.0", 42 | "rimraf": "^3.0.0", 43 | "rollup": "^2.33.3", 44 | "rtlcss": "^2.6.1", 45 | "sass": "^1.29.0", 46 | "uglify-es": "^3.3.9", 47 | "vue-docgen-cli": "^4.36.1", 48 | "zlib": "^1.0.5" 49 | }, 50 | "browserslist": [ 51 | "last 1 version, not dead, ie >= 11" 52 | ], 53 | "dependencies": {} 54 | } -------------------------------------------------------------------------------- /ui/src/components/Crud.md: -------------------------------------------------------------------------------- 1 | # Crud 2 | 3 | ## Props 4 | 5 | | Prop name | Description | Type | Values | Default | 6 | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ------ | --------------- | 7 | | api | The rest API endpoint | string | - | | 8 | | columns | The Quasar Columns config
`@link` https://quasar.dev/vue-components/table#defining-the-columns | array | - | [] | 9 | | createRule | | boolean | - | true | 10 | | canCreate | Enable / Disable POST http creation of resource | boolean | - | true | 11 | | canDelete | Enable / Disable DELETE http deletion of resource | boolean | - | true | 12 | | canEdit | Enable / Disable PUT http edit of resource | boolean | - | true | 13 | | http | The Axios instance
`@example` axios.create({ baseURL: 'https://reqres.in/' }) | func | - | | 14 | | iconDelete | The icon of delete button | string | - | 'delete' | 15 | | iconCreate | The icon of create button | string | - | 'add' | 16 | | iconUpdate | The color of edit button | string | - | 'edit' | 17 | | iconDeleteColor | The color of delete button | string | - | 'negative' | 18 | | modalTextTitleCreate | The text of title on modal create | string | - | 'Create new' | 19 | | modalTextTitleUpdate | The text of title on modal update | string | - | 'Update this' | 20 | | getOnStart | The component will fetch remote data on mounted hook | boolean | - | true | 21 | | getOnParamChange | The component will fetch remote data on params props change | boolean | - | false | 22 | | listIndex | The function that will filter the data from the axios response
`@example` axios response from the server
{ data: { people: [..., ...]}, status: 200, headers: {} ... }
So a function can be list => list.people
`@link` https://github.com/axios/axios#response-schema | func | - | | 23 | | selectableRule | The function to allow user select the row
`@example` selectableRule (row) { return row.status === 'ACTIVE' } | func | - | () => true | 24 | | rowKey | The index of rows (id) | string | - | | 25 | | rowsPerPage | The quasar pagination system
`@link` https://quasar.dev/vue-components/table#pagination | number | - | 3 | 26 | | params | Query params of the url
`@link` https://en.wikipedia.org/wiki/Query_string | string | - | '' | 27 | | paginationPageIndex | The quasar pagination system
`@link` https://quasar.dev/vue-components/table#pagination | string | - | 'page' | 28 | | paginationRowsPerPageIndex | The quasar pagination system
`@link` https://quasar.dev/vue-components/table#pagination | string | - | 'per_page' | 29 | | paginationSortIndex | The quasar pagination system
`@link` https://quasar.dev/vue-components/table#pagination | string | - | 'sort' | 30 | | paginationTotalIndex | The quasar pagination system
`@link` https://quasar.dev/vue-components/table#pagination | string | - | 'total' | 31 | | paginationServerSide | Define if pagination will be server side | boolean | - | true | 32 | | msgDelete | The message on dialog to confirm deletation | string\|func | - | 'Delete item ?' | 33 | | titleDelete | The title of modal delete | string\|func | - | 'Delete' | 34 | 35 | ## Events 36 | 37 | | Event name | Properties | Description | 38 | | --------------- | ---------- | --------------------------------------------------------------- | 39 | | successOnGet | | \$emit('successOnGet', response) on sucefull get request. | 40 | | errorOnGet | | Emit the ERROR Object of axios catch. | 41 | | successOnDelete | | \$emit('successOnDelete', selected) on sucefull delete request. | 42 | | errorOnDelete | | \$emit('errorOnDelete', error) Object of axios catch. | 43 | | successOnPost | | \$emit('successOnPost', selected) on sucefull created request. | 44 | | errorOnPost | | \$emit('errorOnDelete', error) Object of axios catch. | 45 | | successOnPut | | \$emit('successOnPut', selected) on sucefull update request. | 46 | | errorOnPut | | \$emit('errorOnPut', error) Object of axios catch. | 47 | 48 | ## Slots 49 | 50 | | Name | Description | Bindings | 51 | | -------------- | ----------- | -------- | 52 | | default | |
| 53 | | customSelected | | | 54 | 55 | --- 56 | -------------------------------------------------------------------------------- /ui/src/components/Crud.vue: -------------------------------------------------------------------------------- 1 | 137 | 138 | 500 | -------------------------------------------------------------------------------- /ui/src/components/helper.js: -------------------------------------------------------------------------------- 1 | export function formatForPostValidator (columns) { 2 | return columns.some(column => { 3 | if (column.hasOwnProperty('formatForPost') && typeof column.formatForPost !== 'function') { 4 | console.warn(`formatForPost must be function on column ${column.name}`) 5 | return false 6 | } 7 | return true 8 | }) 9 | } -------------------------------------------------------------------------------- /ui/src/index.js: -------------------------------------------------------------------------------- 1 | import { version } from '../package.json' 2 | 3 | import Crud from './components/Crud.vue' 4 | 5 | 6 | export { 7 | version, 8 | Crud 9 | 10 | } 11 | 12 | export default { 13 | version, 14 | Crud, 15 | 16 | install (Vue) { 17 | Vue.component(Crud.name, Crud) 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ui/umd-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | UMD test 12 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | quasar-ui-crud v{{ version }} 24 | 25 | 26 |
Quasar v{{ $q.version }}
27 |
28 |
29 | 30 | 31 | 32 |
    33 |
  • In /ui, run: "yarn build"
  • 34 |
  • You need to build & refresh page on each change manually.
  • 35 |
  • Use self-closing tags only!
  • 36 |
  • Example: <my-component></my-component>
  • 37 |
38 |
39 |
40 |
41 |
42 | 43 | 44 | 45 | 46 | 47 | 48 | 59 | 60 | 61 | --------------------------------------------------------------------------------