├── .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 | 
3 | > Quasar Crud Component based on QTable using Axios API to fetch remote data.
4 |
5 |
6 |
7 |
8 | [](https://codecov.io/gh/odranoelBR/vue-quasar-crud)
9 | 
10 | [](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 | [](https://www.npmjs.com/package/quasar-app-extension-crud)
6 | [](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 |
2 |
3 |
4 |
5 |
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 |
2 |
3 |
9 |
14 |
19 |
24 |
25 |
26 |
33 |
37 |
52 |
53 |
54 |
55 |
59 |
60 |
61 |
62 |
66 |
67 |
68 |
69 |
70 |
71 |
150 |
151 |
--------------------------------------------------------------------------------
/ui/dev/src/components/QTabExampleFour.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
14 |
19 |
24 |
25 |
26 |
33 |
37 |
51 |
52 |
53 |
54 |
58 |
59 |
60 |
61 |
65 |
66 |
67 |
68 |
69 |
70 |
153 |
154 |
--------------------------------------------------------------------------------
/ui/dev/src/components/QTabExampleOne.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
14 |
19 |
24 |
25 |
26 |
33 |
37 |
48 |
49 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
69 |
70 |
71 |
72 |
76 |
77 |
78 |
79 |
80 |
81 |
153 |
154 |
--------------------------------------------------------------------------------
/ui/dev/src/components/QTabExampleSix.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
14 |
19 |
24 |
25 |
26 |
33 |
37 |
54 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
68 |
69 |
70 |
71 |
75 |
76 |
77 |
78 |
79 |
80 |
169 |
170 |
--------------------------------------------------------------------------------
/ui/dev/src/components/QTabExampleThree.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
14 |
19 |
24 |
25 |
26 |
33 |
37 |
60 |
61 |
77 |
78 |
79 |
80 |
84 |
85 |
86 |
87 |
91 |
92 |
93 |
94 |
95 |
96 |
179 |
180 |
--------------------------------------------------------------------------------
/ui/dev/src/components/QTabExampleTwo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
14 |
19 |
24 |
25 |
26 |
33 |
37 |
47 |
48 |
49 |
50 |
54 |
55 |
56 |
57 |
61 |
62 |
63 |
64 |
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 |
2 |
7 |
12 |
13 |
21 |
22 |
27 |
28 |
32 | {{ link.name }}
33 |
34 |
35 |
36 |
37 |
38 |
45 |
46 |
47 | {{link.name}}
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
83 |
93 |
--------------------------------------------------------------------------------
/ui/dev/src/layouts/Layout.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
13 |
14 | Quasar Crud v{{ version }}
15 |
16 |
17 | Quasar v{{ $q.version }}
18 |
23 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
64 |
--------------------------------------------------------------------------------
/ui/dev/src/pages/PageApi.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
31 |
32 |
34 |
--------------------------------------------------------------------------------
/ui/dev/src/pages/PageIndex.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
30 |
31 |
48 |
--------------------------------------------------------------------------------
/ui/dev/src/pages/PageMoreExamples.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 | Can only delete.
10 | Ex: The resource has soft delete
11 |
12 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | Only fetch after user select.
24 | Using form outside of table to filter requests
25 | Fetch when param changes
26 |
27 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | Rule to edit only the completed tasks.
39 |
40 |
41 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | Changing types of form for edit / create
53 | Notify on update
54 |
55 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | Custom pagination indexes
67 | Custom columns ( from QTable)
68 | Custom icons
69 |
70 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
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 |
2 |
3 |
13 |
17 |
18 |
22 |
26 |
27 |
28 |
29 |
33 |
37 |
38 |
42 | {{ column.format ? column.format(props.row[column.name]) : props.row[column.name] }}
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
55 |
59 |
60 |
64 |
69 |
70 |
74 |
79 |
80 |
84 |
89 |
90 |
91 |
92 |
93 |
97 |
98 |
99 | {{ modalTextTitle }}
100 |
101 |
102 |
103 |
117 |
118 |
119 |
120 |
125 |
126 |
132 |
133 |
134 |
135 |
136 |
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 |
--------------------------------------------------------------------------------