diff --git a/quasar.conf.js b/quasar.conf.js index f0400c0..c6f9fe2 100644 --- a/quasar.conf.js +++ b/quasar.conf.js @@ -83,6 +83,7 @@ module.exports = function (context) { 'QCheckbox', 'QSelect', 'QRadio', + 'QOptionGroup', 'QCard', 'QCardSection', 'QCardActions', diff --git a/src/App.vue b/src/App.vue index ad78797..de17315 100644 --- a/src/App.vue +++ b/src/App.vue @@ -6,7 +6,7 @@ /> - .github-fork-ribbon.left-bottom:before + .github-fork-ribbon.left-top:before background-color #333 diff --git a/src/app/Prototype/Base.js b/src/app/Prototype/Base.js index d2420a5..99602c3 100644 --- a/src/app/Prototype/Base.js +++ b/src/app/Prototype/Base.js @@ -1,5 +1,6 @@ /* eslint-disable no-underscore-dangle */ import components from 'src/config/app/components' +import { apply } from 'src/app/Util' /** * @typedef {Base} @@ -74,6 +75,11 @@ export default class Base { */ static mixins = [] + /** + * @type {boolean} + */ + i18n = true + /** * @param {Object} options * @returns {this} @@ -91,7 +97,10 @@ export default class Base { this.__actions = {} this.__sections = {} + this.__loaded = {} + this.init() + this.locale() if (this.defaults && typeof this.defaults === 'function') { this.defaults() @@ -111,6 +120,29 @@ export default class Base { this.constructor.mixins.forEach(mixin => this.mixin(mixin)) } + /** + */ + locale () { + if (!this.i18n) { + return + } + this.namespace = this.constructor.domain.replace(/\//, '.') + const map = (piece) => piece.charAt(0).toUpperCase() + piece.substring(1) + const domain = this.namespace.split('.').map(map).join('/') + + const locale = window.app.i18n.locale + const path = `src/domains/${domain}/${locale}` + if (this.__loaded[path]) { + return + } + this.__loaded[path] = true + + const messages = require(`src/domains/${domain}/${locale}`) + const translations = apply({}, `domains.${this.namespace}`, messages.default) + + window.app.i18n.mergeLocaleMessage(locale, translations) + } + /** * @param {Object} mixin */ diff --git a/src/app/Prototype/Components/Form/PrototypeComponents.js b/src/app/Prototype/Components/Form/PrototypeComponents.js index 08a27b0..865c193 100644 --- a/src/app/Prototype/Components/Form/PrototypeComponents.js +++ b/src/app/Prototype/Components/Form/PrototypeComponents.js @@ -37,13 +37,10 @@ export default { const key = field.$key const error = this.fieldHasError(key) - const style = { - display: field.$layout.formHidden ? 'none' : '' - } const data = { key: key, class: this.fieldClass(field.$layout.formWidth, field.$layout.formHeight, error), - domProps: { style } + style: { display: field.$layout.formHidden ? 'none' : '' } } const children = [ diff --git a/src/app/Prototype/Contracts/Basic.js b/src/app/Prototype/Contracts/Basic.js index ff6d00b..e0270d7 100644 --- a/src/app/Prototype/Contracts/Basic.js +++ b/src/app/Prototype/Contracts/Basic.js @@ -123,6 +123,19 @@ export default { getFieldLayout () { // will override by specialists }, + /** + * @param {string} property + * @param {*} value + */ + setRecord (property, value) { + // will override by specialists + }, + /** + * @param {string} property + */ + getRecord (property) { + // will override by specialists + }, /** * @param {Function} h * @param {string} position diff --git a/src/app/Prototype/Contracts/Button.js b/src/app/Prototype/Contracts/Button.js index eba0ef5..4ef2d2f 100644 --- a/src/app/Prototype/Contracts/Button.js +++ b/src/app/Prototype/Contracts/Button.js @@ -49,6 +49,15 @@ export default { return action }) } + + const label = this.$lang([ + `domains.${this.domain}.actions.${button.$key}.label`, + `prototype.actions.${button.$key}.label` + ]) + if (label) { + button.attrs.label = label + } + buttons[button.$key] = button return buttons }, diff --git a/src/app/Prototype/Contracts/Form/FormComponents.js b/src/app/Prototype/Contracts/Form/FormComponents.js index 40cbe03..fc5d750 100644 --- a/src/app/Prototype/Contracts/Form/FormComponents.js +++ b/src/app/Prototype/Contracts/Form/FormComponents.js @@ -162,6 +162,16 @@ export default { field.attrs.after = this.parseFieldAfter(field) } + if (field.attrs.label) { + field.attrs.label = this.$lang( + [ + `domains.${this.domain}.fields.${field.$key}.${field.attrs.label}`, + `domains.${this.domain}.${field.attrs.label}`, + field.attrs.label + ], + field.attrs.label + ) + } if (!field.parseInput) { field.parseInput = (value) => value } diff --git a/src/app/Prototype/Contracts/Form/FormField.js b/src/app/Prototype/Contracts/Form/FormField.js index a5c2cf3..bc8f32f 100644 --- a/src/app/Prototype/Contracts/Form/FormField.js +++ b/src/app/Prototype/Contracts/Form/FormField.js @@ -67,6 +67,20 @@ const FormField = { getFieldLayout (component, attr) { return this.getFieldLayouts(component)[attr] }, + /** + * @param {string} property + * @param {*} value + */ + setRecord (property, value) { + this.record[property] = value + return this + }, + /** + * @param {string} property + */ + getRecord (property) { + return this.record[property] + }, /** * @param {string} component * @param {Boolean} error diff --git a/src/app/Prototype/Prototype.js b/src/app/Prototype/Prototype.js index 08fb2c3..2072c7b 100644 --- a/src/app/Prototype/Prototype.js +++ b/src/app/Prototype/Prototype.js @@ -1,7 +1,6 @@ /* eslint-disable no-underscore-dangle */ import Skeleton from './Skeleton' import { lang } from 'src/app/Util/Lang' -import { apply } from 'src/app/Util' import Field from 'src/app/Prototype/Prototype/Field' import FieldForm from 'src/app/Prototype/Prototype/FieldForm' @@ -18,28 +17,6 @@ export default class Prototype extends Skeleton { */ static mixins = [Field, FieldForm, FieldIs, FieldTable, Action] - /** - * @type {boolean} - */ - i18n = true - - /** - * @param {Function} callback - */ - locale (callback = undefined) { - this.namespace = this.domain.replace(/\//, '.') - const map = (piece) => piece.charAt(0).toUpperCase() + piece.substring(1) - const domain = this.namespace.split('.').map(map).join('/') - const locale = this.$i18n.locale - - import(/* webpackChunkName: "lang-[request]" */ `src/domains/${domain}/${locale}`) - .then((messages) => { - const translations = apply({}, `domains.${this.namespace}`, messages.default) - this.$i18n.mergeLocaleMessage(locale, translations) - }) - .finally(callback) - } - /** * @param {String|Array} key * @param {string} [fallback] @@ -81,7 +58,7 @@ export default class Prototype extends Skeleton { */ configureView () { Object.keys(this.components).forEach(key => { - this.setFieldAttrs(key, { readonly: true }) + this.setFieldAttrs(key, { readonly: true, disable: true }) }) this.fetchRecord(this.$route.params[this.primaryKey]) } @@ -150,7 +127,7 @@ export default class Prototype extends Skeleton { if (this.debuggers) { window.alert(JSON.stringify(response)) } - this.$message.success(this.$lang(`prototype.operation.${scope}.success`)) + this.$message.success(this.$lang(`prototype.operations.${scope}.success`)) if (scope === 'create') { this.$browse(`${this.path}/${response[this.primaryKey]}/edit`, true) } @@ -167,66 +144,53 @@ export default class Prototype extends Skeleton { const prototype = this this.hook('created:default', function () { - /** - */ - const run = () => { - // Call component setup method - if (this.setup && typeof this.setup === 'function') { - this.setup() - } - - // Call global prototype configure - prototype.configure.call(this) + // Call component setup method + if (this.setup && typeof this.setup === 'function') { + this.setup() + } - // Call configure of each field - this.configure() + // Call global prototype configure + prototype.configure.call(this) - if (this.scope === 'index') { - // Call configure to index scope - return prototype.configureIndex.call(this) - } + // Call configure of each field + this.configure() - if (this.scope === 'update') { - // Call configure to update scope - return prototype.configureEdit.call(this) - } + if (this.scope === 'index') { + // Call configure to index scope + return prototype.configureIndex.call(this) + } - if (this.scope === 'read') { - // Call configure to read scope - return prototype.configureView.call(this) - } + if (this.scope === 'update') { + // Call configure to update scope + return prototype.configureEdit.call(this) + } - if (this.scope === 'create') { - // Call configure to create scope - return prototype.configureAdd.call(this) - } + if (this.scope === 'read') { + // Call configure to read scope + return prototype.configureView.call(this) } - // load i18n async - if (prototype.i18n) { - prototype.locale.call(this, run) - return + + if (this.scope === 'create') { + // Call configure to create scope + return prototype.configureAdd.call(this) } - run() }) this.action('add') .actionScopes(['index']) .actionPositions(['table-top']) - .actionLabel(this.$lang('prototype.action.add.label')) .actionIcon('add') .actionColor('primary') this.action('back') .actionScopes(['index', 'create', 'read', 'update']) .actionPositions(['form-footer']) - .actionLabel(this.$lang('prototype.action.back.label')) .actionIcon('reply') this.action('cancel') .actionFloatRight() .actionScopes(['index', 'create', 'read', 'update']) .actionPositions(['form-footer']) - .actionLabel(this.$lang('prototype.action.cancel.label')) .actionIcon('close') this.action('refresh') @@ -240,13 +204,12 @@ export default class Prototype extends Skeleton { .actionScopes(['create', 'update']) .actionPositions(['form-footer']) .actionFloatRight() - .actionLabel(this.$lang('prototype.action.save.label')) .actionIcon('save') .actionColor('primary') .actionOn('click', function () { this.$v.$touch() if (this.$v.$error || this.hasErrors) { - this.$message.error(this.$lang('prototype.action.save.validation')) + this.$message.error('prototype.actions.save.validation') return } if (this.debuggers) { @@ -258,20 +221,17 @@ export default class Prototype extends Skeleton { this.action('view') .actionScopes(['index']) .actionPositions(['table-top', 'table-cell']) - .actionLabel(this.$lang('prototype.action.view.label')) .actionIcon('visibility') this.action('edit') .actionScopes(['index']) .actionPositions(['table-top', 'table-cell']) - .actionLabel(this.$lang('prototype.action.edit.label')) .actionColor('primary') .actionIcon('edit') this.action('destroy') .actionScopes(['index']) .actionPositions(['table-top', 'table-cell']) - .actionLabel(this.$lang('prototype.action.destroy.label')) .actionColor('negative') .actionIcon('delete') } diff --git a/src/app/Prototype/Prototype/FieldIs.js b/src/app/Prototype/Prototype/FieldIs.js index 55292e4..a8da1c3 100644 --- a/src/app/Prototype/Prototype/FieldIs.js +++ b/src/app/Prototype/Prototype/FieldIs.js @@ -23,81 +23,96 @@ export default { }, /** - * @param {Boolean} upperCase + * @param {Object} attrs * @returns {Prototype} */ - fieldIsInput (upperCase = true) { + fieldIsInput (attrs = {}) { this.setComponent('input') - return this.setAttrs({ upperCase }) - }, - - /** - * @returns {Prototype} - */ - fieldIsNumber () { - this.setComponent('input') - this.setAttrs({ type: 'number' }) + this.setAttrs({ ...attrs }) return this }, /** - * @param {Array} options + * @param {Object} attrs * @returns {Prototype} */ - fieldIsSelect (options) { - this.setComponent('select') - this.setAttrs({ options }) - this.setLayout({ - tableFormat (value/* , row */) { - return options.find((option) => option.value === value).label - } - }) + fieldIsNumber (attrs = {}) { + this.setComponent('number') + this.setAttrs({ ...attrs }) return this }, /** - * @param {Number} rows + * @param {Object} attrs * @returns {Prototype} */ - fieldIsText (rows = 3) { - this.setComponent('text') - return this.setAttrs({ rows }) + fieldIsPassword (attrs = {}) { + this.setComponent('password') + this.setAttrs({ ...attrs }) + return this }, /** + * @param {Object} attrs * @returns {Prototype} */ - fieldIsPassword () { - return this.setComponent('password') + fieldIsEmail (attrs = {}) { + this.setComponent('email') + this.setAttrs({ ...attrs }) + return this }, /** + * @param {Number} rows + * @param {Object} attrs * @returns {Prototype} */ - fieldIsRadio () { - return this.setComponent('radio') + fieldIsText (rows = 4, attrs = {}) { + this.setComponent('text') + this.setAttrs({ ...attrs, rows }) + return this }, /** + * @param {Object} attrs * @returns {Prototype} */ - fieldIsHtml () { - return this.setComponent('html') + fieldIsCheckbox (attrs = {}) { + this.setComponent('checkbox') + this.setAttrs({ ...attrs }) + return this }, /** + * @param {Array} options + * @param {Object} attrs * @returns {Prototype} */ - fieldIsFile () { - return this.setComponent('file') + fieldIsRadio (options = undefined, attrs = {}) { + if (!Array.isArray(options)) { + options = [ + { value: true, label: 'Yes' }, + { value: false, label: 'No' } + ] + } + this.setComponent('radio') + this.setAttrs({ ...attrs, options }) + return this }, /** + * @param {Array} options + * @param {Object} attrs * @returns {Prototype} */ - fieldIsEmail () { - this.setComponent('input') - this.setAttrs({ type: 'password' }) + fieldIsSelect (options, attrs = {}) { + this.setComponent('select') + this.setAttrs({ ...attrs, options }) + this.setLayout({ + tableFormat (value/* , row */) { + return options.find((option) => option.value === value).label + } + }) return this } } diff --git a/src/app/Prototype/Skeleton.js b/src/app/Prototype/Skeleton.js index a3c1471..3e8d34a 100644 --- a/src/app/Prototype/Skeleton.js +++ b/src/app/Prototype/Skeleton.js @@ -13,6 +13,10 @@ export default class Skeleton extends Base { */ field (name, label = '', type = undefined) { this.__currentField = name + if (this.__fields[name]) { + return this + } + let is = this.is const attrs = { value: undefined, disable: false } diff --git a/src/app/Util/Lang.js b/src/app/Util/Lang.js index 4e03e49..0c9d35c 100644 --- a/src/app/Util/Lang.js +++ b/src/app/Util/Lang.js @@ -17,7 +17,7 @@ export const lang = (key, fallback = '') => { continue } - let clean = key[path].replace(/\//g, '.') + let clean = String(key[path]).replace(/\//g, '.') if (!window.app.i18n.te(clean)) { continue } diff --git a/src/boot/message.js b/src/boot/message.js index cbee91d..cfb90c4 100644 --- a/src/boot/message.js +++ b/src/boot/message.js @@ -33,7 +33,7 @@ const base = (options, action = {}) => { * @param options */ export const toast = (message, options = {}) => { - Notify.create(base({ message })) + Notify.create(base({ message, ...options })) } /** @@ -41,7 +41,7 @@ export const toast = (message, options = {}) => { * @param options */ export const success = (message, options = {}) => { - Notify.create(base({ message, color: 'positive' })) + Notify.create(base({ message, ...options, color: 'positive' })) } /** @@ -49,7 +49,7 @@ export const success = (message, options = {}) => { * @param options */ export const error = (message, options = {}) => { - Notify.create(base({ message, color: 'negative' })) + Notify.create(base({ message, ...options, color: 'negative' })) } /** diff --git a/src/config/app/components.js b/src/config/app/components.js index d167caf..2148a53 100644 --- a/src/config/app/components.js +++ b/src/config/app/components.js @@ -5,22 +5,46 @@ export default { is: 'q-input', attrs: { ...attrs } }, - text: { + number: { is: 'q-input', attrs: { - type: 'textarea', - rows: 4, + type: 'number', ...attrs } }, password: { is: 'q-input', - attrs: { ...attrs } + attrs: { + type: 'number', + ...attrs + } }, - image: { + email: { is: 'q-input', + attrs: { + type: 'email', + ...attrs + } + }, + text: { + is: 'q-input', + attrs: { + type: 'textarea', + rows: 4, + ...attrs + } + }, + checkbox: { + is: 'q-checkbox', attrs: { ...attrs } }, + radio: { + is: 'q-option-group', + attrs: { + inline: true, + ...attrs + } + }, select: { is: 'q-select', attrs: { ...attrs } diff --git a/src/domains/Common/options.js b/src/domains/Common/options.js new file mode 100644 index 0000000..c7af8bc --- /dev/null +++ b/src/domains/Common/options.js @@ -0,0 +1,8 @@ +/** + * @param {string} domain + * @returns {Array} + */ +export const gender = (domain) => [ + { value: 'male', label: `domains.${domain}.gender.male` }, + { value: 'female', label: `domains.${domain}.gender.female` } +] diff --git a/src/domains/Example/Test/Prototype/TestWithHooks.js b/src/domains/Example/Test/Prototype/TestWithHooks.js index 5e7979a..bc1dd72 100644 --- a/src/domains/Example/Test/Prototype/TestWithHooks.js +++ b/src/domains/Example/Test/Prototype/TestWithHooks.js @@ -1,5 +1,6 @@ import Test from './Test' import { path } from 'src/domains/Example/Test/Routes' +import { gender } from 'src/domains/Common/options' /** * @type {TestWithHooks} @@ -21,14 +22,36 @@ export default class TestWithHooks extends Test { construct () { // construct the parent super.construct() - + // configure some fields + this.configureFields() // configure some actions this.configureActions() - // configure some hooks this.configureHooks() } + /** + */ + configureFields () { + this.field('active') + .fieldIsCheckbox({ label: 'active.label' }) + .fieldFormWidth(45) + .fieldFormOrder(3, true) + .fieldOn('input', function ({ $event }) { + this.setFieldLayout('description', 'formHidden', $event) + }) + + this.field('gender') + .fieldIsRadio(gender(TestWithHooks.domain)) + .fieldFormOrder(4, true) + .fieldFormWidth(55) + .fieldOn('input', function ({ $event }) { + this.setFieldLayout('active', 'formHidden', $event === 'male') + }) + + this.field('description').fieldOn('input', this.changeDescriptionLabel) + } + /** */ configureActions () { @@ -36,13 +59,13 @@ export default class TestWithHooks extends Test { .actionColor('red') .actionTextColor('white') - this.action('go-to-test') + this.action('goToTest') .actionScopes(['index', 'create', 'read', 'update']) .actionPositions(['form-footer']) .actionIcon('send') .actionColor('yellow') .actionOrder(2) - .actionLabel(this.$lang(`domains.${TestWithHooks.domain}.actions.goToTest`)) + .actionLabel(this.$lang(`domains.example.test.actions.goToTest`)) .actionOn('click', function ({ $event, context }) { this.$log('~> $event', $event) this.$log('~> context', context) @@ -56,13 +79,32 @@ export default class TestWithHooks extends Test { /** */ this.hook('fetch:record', function () { - this.$message.toast(this.$lang(`domains.${this.domain}.messages.record`)) + this.$message.toast(this.$lang(`domains.${this.domain}.messages.record`), { position: 'top-right' }) + if (this.record.active === undefined) { + this.setRecord('active', false) + } }) /** */ this.hook('fetch:records', function () { - this.$message.toast(this.$lang(`domains.${this.domain}.messages.records`)) + this.$message.toast(this.$lang(`domains.${this.domain}.messages.records`), { position: 'top-left' }) }) } + + /** + * @param $event + * @param field + */ + changeDescriptionLabel ({ $event, field }) { + if (!field.originalLabel) { + field.originalLabel = field.label + } + let label = $event + if (!label) { + label = field.originalLabel + field.originalLabel = undefined + } + field.label = label + } } diff --git a/src/domains/Example/Test/en-us.js b/src/domains/Example/Test/en-us.js index fdccdf7..2e9d82e 100644 --- a/src/domains/Example/Test/en-us.js +++ b/src/domains/Example/Test/en-us.js @@ -11,8 +11,17 @@ export default { id: 'Id', name: 'Name', age: 'Age', + active: 'Active', + gender: 'Gender', description: 'Description' }, + gender: { + male: 'Male', + female: 'Female' + }, + active: { + label: 'if checked will hide "Description"' + }, actions: { goToTest: 'Go to Test' }, diff --git a/src/i18n/en-us/prototype/index.js b/src/i18n/en-us/prototype/index.js index 32b588d..7d6c8fe 100644 --- a/src/i18n/en-us/prototype/index.js +++ b/src/i18n/en-us/prototype/index.js @@ -16,15 +16,15 @@ export default { }, components: { }, - operation: { + operations: { create: { success: 'Record created successfully' }, update: { - success: 'Record update successfully' + success: 'Record updated successfully' } }, - action: { + actions: { save: { label: 'Save', validation: 'Check the fields'