Saturday, December 14, 2019

Using Dynamical components in Vue.js

What is Dynamic component?

The Dynamic components (<component :is="...".../>) in Vue.js enable users to switch between two or more components without routing, and even retain the state of data when switching back to the initial component (<keep-alive>). The central idea is to let users dynamically mount and unmount components in the user interface without using routers.

Bellow is an example of using dynamical components in our project.

As you can see on the screen shot below (click on it to make it larger), there is a stack of collapsible components on the left side. We use dynamical component feature to load them (code line 85).

The code of main container component:
<template>
<tabs-panel>
<tabs class="flex-column flex-1 tabs">
<div slot="nav-right">
<button
class="btn btn-link previuse-step"
type="button"
title="Предыдущий шаг"
:disabled="!isPreviousStepActive"
@click="previousStep"
>Предыдущий шаг</button>
<button
class="btn btn-link next-step"
type="button"
title="Следующий шаг"
:disabled="!isNextStepActive"
@click="nextStep"
>Следующий шаг</button>
<icon-button
default
class="previuse-step-icon"
title="Предыдущий шаг"
icon="icon-arrow-left7"
@click="previousStep"
:disabled="!isPreviousStepActive"
/>
<icon-button
default
class="next-step-icon"
title="Следующий шаг"
icon="icon-arrow-right7"
@click="nextStep"
:disabled="!isNextStepActive"
/>
<icon-button
default
@click="save"
title="Сохранить"
:disabled="!isSaveActive || !canSaveDocument"
icon="icon-floppy-disk"
/>
<icon-button
default
title="Печать"
icon="icon-printer2"
@click="print"
:disabled="!canPrint"
/>
<icon-button
primary
title="Справка"
icon="icon-help"
@click="showHelp"
/>
</div>
<tab title="Атрибуты документа" class="flex-column flex-1">
<div class="panel-group">
<panel
:collapsible="true"
title="Основные сведения"
:iscollapsed="false"
class="category-panel"
:colorTitle="getColor(BasicInformationKey)"
>
<template slot="body">
<basic-information-section
v-if="document"
:sectionData="document"
:readonly="!canUpdateDocument"
:isTerritoryRequired="isTerritoryFieldRequired"
@highlight="setSectionOptions"
@changeHappened="setSaveActive"
/>
</template>
</panel>
<panel
:collapsible="true"
v-for="(item, key) in docSections"
:key="key"
:title="setSectionTitle(item)"
class="category-panel"
:colorTitle="getColor(item)"
>
<template slot="body">
<component
:document="document"
:request="request"
:readonly="!canUpdateDocument"
:sectionData="document.Sections[item]"
:is="getSection(item)"
:options="sectionOptions"
:isMapPanelVisible="isMapPanelVisible"
@showMap="showMap"
@disableSelectionDrawing="disableSelectionDrawing"
@disableSelectionPlanimetry="disableSelectionPlanimetry"
@requestMapScale="requestMapScale"
@requestMapLegends="requestMapLegends"
@changeHappened="setSaveActive"
/>
</template>
</panel>
<panel :collapsible="true" title="Системная информация" class="category-panel">
<template slot="body">
<system-information-section v-if="document" :document="document" />
</template>
</panel>
</div>
</tab>
<tab title="Файлы" class="flex-column flex-1">
<div class="panel-group">
<document-files
:files="document.files"
:newFiles="document.newFiles"
@fileAdded="fileAdded"
@renameFile="renameFile"
@removeFile="removeFile"
:readonly="!canRequestFilesUpdate"
:canCreate="canRequestFilesCreate"
v-if="document && document.files"
/>
</div>
</tab>
<tab
v-for="(item, key) in tabbedSections"
:key="key"
:title="setSectionTitle(item)"
class="flex-column flex-1"
>
<component
:document="document"
:request="request"
:readonly="!canUpdateDocument"
:sectionData="document.Sections[item]"
:is="getSection(item)"
:options="sectionOptions"
:isMapPanelVisible="isMapPanelVisible"
@showMap="showMap"
@disableSelectionDrawing="disableSelectionDrawing"
@disableSelectionPlanimetry="disableSelectionPlanimetry"
@requestMapScale="requestMapScale"
@changeHappened="setSaveActive"
/>
</tab>
</tabs>
<select-document-template-dialog ref="selectTemplateDialog" @select="templateSelected" />
<select-document-step-dialog ref="selectStepDialog" @stepSelected="onStepSelected" />
</tabs-panel>
</template>
<script>
import _cloneDeep from 'lodash/cloneDeep'
import _sortBy from 'lodash/sortBy'
import { mapState, mapActions } from 'vuex'
import sectionMapping from '../../helpers/sectionMapping.js'
import developedDocumentsApi from '@/api/developedDocuments'
import reportTemplatesApi from '@/api/reportTemplates'
import BasicInformationSection from '@/components/developedDocuments/Sections/BasicInformationSection'
import AdvertisementPermitSection from '@/components/developedDocuments/Sections/AdvertisementPermitSection'
import AdvertisingConstructionSection from '@/components/developedDocuments/Sections/AdvertisingConstructionSection'
import ArchitecturalSection from '@/components/developedDocuments/Sections/ArchitecturalSection'
import BuildingPermitSection from '@/components/developedDocuments/Sections/BuildingPermitSection'
import CommissioningPermitSection from '@/components/developedDocuments/Sections/CommissioningPermitSection'
import ConclusionSection from '@/components/developedDocuments/Sections/ConclusionSection'
import DevelopmentRestrictionSection from '@/components/developedDocuments/Sections/DevelopmentRestrictionSection'
import DrawingSection from '@/components/developedDocuments/Sections/DrawingSection'
import GpzuInfoSection from '@/components/developedDocuments/Sections/GpzuInfoSection'
import GpzuOksSection from '@/components/developedDocuments/Sections/GpzuOksSection'
import IsogdRegistrationSection from '@/components/developedDocuments/Sections/IsogdRegistrationSection'
import PlanimetrySection from '@/components/developedDocuments/Sections/PlanimetrySection'
import PlanningProjectSection from '@/components/developedDocuments/Sections/PlanningProjectSection'
import RequestSection from '@/components/developedDocuments/Sections/RequestSection'
import SpecialZoneSection from '@/components/developedDocuments/Sections/SpecialZoneSection'
import SteadSection from '@/components/developedDocuments/Sections/SteadSection'
import TurningPointsSection from '@/components/developedDocuments/Sections/TurningPointsSection'
import SystemInformationSection from '@/components/developedDocuments/Sections/SystemInformationSection'
import NotificationMismatchSection from '@/components/developedDocuments/Sections/NotificationMismatchSection'
import ConstructionMismatchSection from '@/components/developedDocuments/Sections/ConstructionMismatchSection'
import DocumentFiles from '@/components/developedDocuments/DocumentFiles'
import SelectDocumentTemplateDialog from '@/components/print/SelectDocumentTemplateDialog'
import SelectDocumentStepDialog from './SelectDocumentStepDialog'
import handlers from '@/components/developedDocuments/documentHandlers/handlers'
import resources from 'auth/resources'
import actions from 'auth/actions'
const BasicInformationKey = 'BasicInformation'
export default {
components: {
BasicInformationSection,
AdvertisementPermitSection,
AdvertisingConstructionSection,
ArchitecturalSection,
BuildingPermitSection,
CommissioningPermitSection,
ConclusionSection,
DevelopmentRestrictionSection,
DrawingSection,
GpzuInfoSection,
GpzuOksSection,
IsogdRegistrationSection,
PlanimetrySection,
PlanningProjectSection,
RequestSection,
SpecialZoneSection,
SteadSection,
TurningPointsSection,
SystemInformationSection,
NotificationMismatchSection,
ConstructionMismatchSection,
DocumentFiles,
SelectDocumentTemplateDialog,
SelectDocumentStepDialog
},
props: ['viewParams', 'isMapPanelVisible'],
data () {
return {
request: null,
cardParams: _cloneDeep(this.viewParams),
document: null,
isSaveActive: false,
documentTypeSections: [],
sectionOptions: {
isMapPanelVisible: false,
highlight: false,
disableSelectionDrawing: false,
disableSelectionPlanimetry: false,
mapScale: '',
deactivateSelectBtnPlanimetry: false,
deactivateSelectBtnDrawing: false,
mapLegends: []
},
isTerritoryFieldRequired: false,
currentStep: null,
currentHandler: null,
BasicInformationKey: BasicInformationKey,
sectionRequiredFieldFuncs: {
[BasicInformationKey]: this.checkBI,
'AdvertisementPermit': this.checkAdvertisementPermit,
'BuildingPermit': this.checkBuildingPermit,
'ComissioningPermit': this.checkComissioningPermit,
'AdvertisingConstruction': this.checkAdvertisingConstruction,
'ConstructionMismatch': this.checkConstructionMismatch
},
permissions: {
document: {
delete: true,
update: true,
read: true
},
files: {
read: true,
create: true,
delete: true,
update: true
}
}
}
},
computed: {
...mapState({
documentSections: state => state.developedDocuments.documentSections,
user: state => state.user,
settingsProfile: state => state.settingsProfile
}),
tabbedSections () {
if (!this.document) {
return []
}
if (this.document && this.document.Sections) {
const documentSectionKeys = Object.keys(this.document.Sections).filter(x => this.document.Sections[x])
const filteredSectionKeys = documentSectionKeys.filter(x => this.documentTypeSections.some(s => s.sectionId === x && s.showAsTab))
const sortedSectionKeys = _sortBy(filteredSectionKeys, x => this.documentTypeSections.find(s => s.sectionId === x).orderNumber)
return sortedSectionKeys
} else {
return []
}
},
orderedDocSections () {
if (this.cardParams && this.cardParams.documentTypeId) {
const sections = Object.keys(this.documentTypeSections)
.filter(x => this.documentTypeSections[x].showAsTab === false)
.map(x => this.documentTypeSections[x])
if (this.document && this.document.Sections) {
const documentSectionKeys = Object.keys(this.document.Sections).filter(x => this.document.Sections[x])
const filteredSectionKeys = documentSectionKeys.filter(x => sections.some(s => s.sectionId === x))
const sortedSectionKeys = _sortBy(filteredSectionKeys, x => sections.find(s => s.sectionId === x).orderNumber)
return sortedSectionKeys
} else {
return []
}
}
},
unorderedDocSections () {
if (this.cardParams && this.cardParams.documentTypeId) {
const sections = Object.keys(this.documentTypeSections)
.map(x => this.documentTypeSections[x])
if (this.document && this.document.Sections) {
const documentSectionKeys = Object.keys(this.document.Sections).filter(x => this.document.Sections[x])
return documentSectionKeys.filter(x => !sections.some(s => s.sectionId === x))
} else {
return []
}
}
},
docSections () {
return [...this.orderedDocSections, ...this.unorderedDocSections]
},
canUpdateDocument () {
if (this.document) {
if (!this.user.can(resources.developedDocument, actions.update)) {
return false
}
return this.permissions.document.update
}
return false
},
canSaveDocument () {
if (this.document) {
if (this.document.id && !this.user.can(resources.developedDocument, actions.update)) {
return false
}
if (!this.document.id && !this.user.can(resources.developedDocument, actions.create)) {
return false
}
return this.permissions.document.update
}
return false
},
canPrint () {
return this.document && this.document.id
},
canRequestFilesCreate () {
if (this.document) {
if (!this.user.can(resources.developedDocument, actions.create)) {
return false
}
return this.permissions.files.create
}
return false
},
canRequestFilesUpdate () {
if (this.document) {
if (!this.user.can(resources.developedDocument, actions.update)) {
return false
}
return this.permissions.files.update
}
return false
},
isPreviousStepActive () {
if (this.currentStep && !this.currentStep.start) {
return true
}
return false
},
isNextStepActive () {
if (this.currentStep && !this.currentStep.finish) {
return true
}
return false
}
},
watch: {
isMapPanelVisible () {
this.sectionOptions.isMapPanelVisible = this.isMapPanelVisible
},
'document.newFiles': {
handler: function (newValue, oldValue) {
if (oldValue === undefined) {
return
}
this.isSaveActive = true
}
}
},
async mounted () {
try {
this.blockUI(this.$el, true)
await this.refresh()
this.sectionOptions.isMapPanelVisible = this.isMapPanelVisible
} finally {
this.blockUI(this.$el, false)
}
},
methods: {
getColorBI () {
if (this.document) {
const docDate = this.document.docDate
const territory = this.document.Territory
return !docDate || !territory ? 'red' : ''
}
},
getColor (item) {
if (!this.document) {
return ''
}
const checkFunc = this.sectionRequiredFieldFuncs[item]
if (!checkFunc) {
return ''
}
return checkFunc(this.document) ? '' : 'red'
},
checkBI (document) {
if (this.isTerritoryFieldRequired && !document.territoryId) {
return false
}
const docDate = document.docDate
return !!docDate
},
checkAdvertisementPermit (document) {
const section = document.Sections.AdvertisementPermit
if (!section) {
return true
}
const permitStart = section.permitStart
const permitFinish = section.permitFinish
return permitStart && permitFinish
},
checkBuildingPermit (document) {
const section = document.Sections.BuildingPermit
if (!section) {
return true
}
const oksName = section.oksName
return oksName
},
checkComissioningPermit (document) {
const section = document.Sections.ComissioningPermit
if (!section) {
return true
}
const oksName = section.oksName
return oksName
},
checkAdvertisingConstruction (document) {
const section = document.Sections.AdvertisingConstruction
if (!section) {
return true
}
const construction = section.Construction
return construction
},
checkConstructionMismatch (document) {
const section = document.Sections.ConstructionMismatch
if (!section) {
return true
}
const objectType = section.objectType
const constructionType = section.constructionType
return objectType && constructionType
},
setSectionOptions (optionHighlight) {
this.sectionOptions.highlight = optionHighlight
},
...mapActions({
loadDocumentSections: 'developedDocuments/loadDocumentSections',
navigate: 'developedDocuments/navigate',
renameLastBreadcrumbItem: 'renameLastBreadcrumbItem',
setCurrentDocument: 'developedDocuments/setCurrentDocument',
setDrawingsAllowed: 'developedDocuments/setDrawingsAllowed'
}),
checkRequiredFields () {
const sectionKeys = Object.keys(this.document.Sections)
sectionKeys.push(this.BasicInformationKey)
for (const sectionKey of sectionKeys) {
const checkFunc = this.sectionRequiredFieldFuncs[sectionKey]
if (checkFunc && !checkFunc(this.document)) {
return false
}
}
return true
},
async save () {
const checkResult = this.checkRequiredFields()
if (!checkResult) {
this.$error(`Не заполнено одно или несколько обязательных полей`)
return
}
this.blockUI(this.$el, true)
if (!this.document.id) {
await this.createDocument(this.document)
} else {
if (this.sectionOptions.highlight && this.document.Sections.Stead) {
if (!await this._confirm('Данные в сегменте "Сведения о ЗУ" не соответствуют кадастровому номеру и будут удалены.', 'Уведомление', 'Продолжить', 'Отмена', 'danger')) {
this.blockUI(this.$el, false)
return
}
}
await this.updateDocument(this.document.id, this.document)
}
this.blockUI(this.$el, false)
},
async createDocument (params) {
try {
const result = await developedDocumentsApi.createDocument(params)
this.$success('Документ успешно создан')
if (this.cardParams.isFromRequest) {
this.$store.dispatch('isogd/navigateReplace', {
name: `${this.cardParams.documentTypeName} Рег.№ ${result.docNumber} по заявке ${this.cardParams.requestRegistrationNumber}`,
component: 'DevelopedDocumentsCard',
params: {
request: this.request,
documentId: result.id,
documentTypeId: result.documentTypeId,
documentName: result.docNumber
}
})
return
}
this.cardParams.documentId = result.id
await this.refresh()
this.renameLastBreadcrumbItem(result.docNumber)
this.isSaveActive = false
} catch (error) {
console.error(error)
this.$error('Ошибка сохранения')
this.isSaveActive = true
}
},
async updateDocument (id, params) {
try {
await developedDocumentsApi.updateDocument(id, params)
this.$success('Карточка документа успешно сохранена')
await this.refresh()
this.isSaveActive = false
} catch (error) {
console.error(error)
this.$error('Ошибка при сохранении данных')
this.isSaveActive = true
}
},
async refresh () {
this.request = this.cardParams.request
try {
// Load document's sections from state in order to display
// the sections's name in Russian
await this.loadDocumentSections()
if (this.cardParams.documentId !== null) {
// Get the existing document
const document = await developedDocumentsApi.getDocument(this.cardParams.documentId)
this.setCurrentDocument(_cloneDeep(document))
this.currentStep = await developedDocumentsApi.getCurrentStep(document.documentTypeId, document.statusId)
if (this.currentStep) {
this.currentHandler = handlers.find(x => x.id === this.currentStep.handlerId)
if (this.currentHandler) {
this.permissions = this.currentHandler.permissions
} else {
this.permissions = this.getDefaultPermissions()
}
} else {
this.permissions = this.getDefaultPermissions()
}
this.documentTypeSections = await developedDocumentsApi.getDocumentTypeSections(document.DocumentType.id)
document.newFiles = []
this.document = document
this.setDrawingsAllowed(this.canUpdateDocument)
this.isTerritoryFieldRequired = await developedDocumentsApi.isTerritoryFieldRequired(document.DocumentType.id)
} else {
// Prepare the creation of new document
if (this.cardParams.documentTypeId !== null) {
const document = await developedDocumentsApi.getNewDocument(this.cardParams.documentTypeId)
this.isTerritoryFieldRequired = await developedDocumentsApi.isTerritoryFieldRequired(this.cardParams.documentTypeId)
this.documentTypeSections = await developedDocumentsApi.getDocumentTypeSections(this.cardParams.documentTypeId)
document.newFiles = []
this.document = document
}
this.isSaveActive = this.cardParams.isSaveActive
this.currentStep = null
}
} catch (error) {
console.error(error)
this.$error('Ошибка при получении данных')
}
},
setTabTitle (item) {
return item.displayName
},
setSectionTitle (key) {
for (let documentSection of this.documentSections) {
if (documentSection.sectionId === key) {
return documentSection.displayName
}
}
},
getSection (key) {
return sectionMapping[key]
},
showMap () {
this.$emit('showMap')
},
disableSelectionDrawing () {
this.sectionOptions.disableSelectionDrawing = true
this.sectionOptions.disableSelectionPlanimetry = false
this.sectionOptions.deactivateSelectBtnPlanimetry = false
this.sectionOptions.deactivateSelectBtnDrawing = true
},
disableSelectionPlanimetry () {
this.sectionOptions.disableSelectionPlanimetry = true
this.sectionOptions.disableSelectionDrawing = false
this.sectionOptions.deactivateSelectBtnPlanimetry = true
this.sectionOptions.deactivateSelectBtnDrawing = false
},
requestMapScale (customResolution) {
this.$emit('requestMapScale', customResolution)
},
setScale (scale) {
this.sectionOptions.mapScale = scale
},
requestMapLegends () {
this.$emit('requestMapLegends')
},
setMapLegends (legends) {
this.sectionOptions.mapLegends = legends
},
deactivateSelectFromMapButton () {
this.sectionOptions.deactivateSelectBtnPlanimetry = true
this.sectionOptions.deactivateSelectBtnDrawing = true
},
setSaveActive (isDataValid) {
this.isSaveActive = isDataValid
},
fileAdded (files) {
this.document.newFiles = files
this.setSaveActive(true)
},
renameFile (file) {
const index = this.document.files.findIndex(x => x.id === file.id)
this.document.files[index] = file
this.setSaveActive(true)
},
removeFile (files) {
this.document.files = files
this.setSaveActive(true)
},
async print () {
const reportTemplates = await developedDocumentsApi.getReportTemplates(this.document.DocumentType.id)
if (reportTemplates.length === 0) {
this.$notice('Не настроены печатные формы для этого вида документа в профиле настроек территориальной службы')
return
}
if (reportTemplates.length === 1) {
await this.templateSelected(reportTemplates[0].id)
return
}
this.$refs.selectTemplateDialog.open(reportTemplates)
},
async templateSelected (templateId) {
try {
await reportTemplatesApi.printTemplate(templateId, {
DocumentId: this.document.id,
UserId: this.user.id,
ProfileId: this.settingsProfile.id
})
} catch (err) {
console.error(err)
this.$error('Ошибка печати документа')
}
},
async previousStep () {
const previousSteps = await developedDocumentsApi.getPrevSteps(this.document.id)
this.$refs.selectStepDialog.open(previousSteps)
},
async nextStep () {
const nextSteps = await developedDocumentsApi.getNextSteps(this.document.id)
this.$refs.selectStepDialog.open(nextSteps)
},
async onStepSelected (step) {
await developedDocumentsApi.gotoStep({ documentId: this.document.id, statusId: this.document.statusId, stepId: step.stepId })
await this.refresh()
this.isSaveActive = false
},
showHelp () {
window.open(this.$url(`/help/index.html?ddoc00.htm`), '_blank')
},
getDefaultPermissions () {
const permissions = {
document: {
delete: true,
update: true,
read: true
},
files: {
read: true,
create: true,
delete: true,
update: true
}
}
return permissions
}
}
}
</script>
<style lang="stylus" scoped>
.card-container
border 1px solid grey
.panel
width 100%
.panel >>> .panel-title
font-weight 400
.panel.category-panel
height auto
margin-bottom 5px
.panel.category-panel >>> .panel-body
flex 1 1 auto
overflow auto
.category-panel >>> .panel-heading
padding 0px 15px
.heading-elements
flex 0
.category-panel >>> .panel-body
padding 0px 15px 10px 15px
@media screen and (max-width: 1250px)
.previuse-step
display none
.next-step
display none
@media screen and (min-width: 1250px)
.previuse-step-icon
display none
.next-step-icon
display none
.previuse-step
color white
background-color rgba(255,165,0, 1)
border-radius 3px
.previuse-step:hover
background-color #CAA24E
.next-step
border-radius 3px
color white
background-color rgba(76, 175, 80, 1)
.next-step:hover
background-color #049600
</style>
view raw gistfile2.vue hosted with ❤ by GitHub

0 comments:

Post a Comment