Browse Source

feat: has many relation creation

Signed-off-by: Pranav C <61551451+pranavxc@users.noreply.github.com>
pull/341/head
Pranav C 3 years ago
parent
commit
2e86e8f448
  1. 12
      packages/nc-gui/components/project/spreadsheet/components/editColumn.vue
  2. 55
      packages/nc-gui/components/project/spreadsheet/components/pagination.vue
  3. 132
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/components/listItems.vue
  4. 161
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/hasManyCell.vue
  5. 68
      packages/nc-gui/components/project/spreadsheet/rowsXcDataTable.vue
  6. 18
      packages/nocodb/src/lib/noco/rest/RestApiBuilder.ts

12
packages/nc-gui/components/project/spreadsheet/components/editColumn.vue

@ -88,6 +88,7 @@
ref="relation"
:column="newColumn"
:nodes="nodes"
:meta="meta"
@onColumnSelect="onRelColumnSelect"
></linked-to-another-options>
</v-col>
@ -319,6 +320,7 @@ export default {
value: Boolean
},
data: () => ({
valid: false,
relationDeleteDlg: false,
newColumn: {},
uiTypes,
@ -368,6 +370,11 @@ export default {
return this.$toast.info('Coming Soon...').goAway(3000)
}
if (this.isLinkToAnotherRecord && this.$refs.relation) {
await this.$refs.relation.saveRelation();
return this.$emit('saved');
}
this.newColumn.tn = this.nodes.tn;
this.newColumn._cn = this.newColumn.cn;
@ -494,7 +501,10 @@ export default {
},
computed: {
isEditDisabled() {
return this.editColumn && this.sqlUi === SqliteUi;
return this.editColumn && this.sqlUi === SqliteUi;
},
isSQLite() {
return this.sqlUi === SqliteUi;
},
dataTypes() {
return this.sqlUi.getDataTypeListForUiType(this.newColumn)

55
packages/nc-gui/components/project/spreadsheet/components/pagination.vue

@ -0,0 +1,55 @@
<template>
<v-pagination
v-if="count !== Infinity"
style="max-width: 100%"
v-model="page"
:length="Math.ceil(count / size)"
:total-visible="8"
@input="$emit('input',page)"
color="primary lighten-2"
></v-pagination>
<div v-else class="mx-auto d-flex align-center mt-n1 " style="max-width:250px">
<span class="caption" style="white-space: nowrap"> Change page:</span>
<v-text-field
class="ml-1 caption"
:full-width="false"
outlined
dense
hide-details
v-model="page"
@keydown.enter="$emit('input',page)"
type="number"
>
<template #append>
<x-icon tooltip="Change page" small icon.class="mt-1" @click="$emit('input',page)">mdi-keyboard-return
</x-icon>
</template>
</v-text-field>
</div>
</template>
<script>
export default {
props: {
count: Number,
value: Number,
size: Number,
},
data: () => ({
page: 1
}),
mounted() {
this.page = this.value;
},
watch: {
value(v) {
this.page = v;
}
},
name: "pagination"
}
</script>
<style scoped>
</style>

132
packages/nc-gui/components/project/spreadsheet/components/virtualCell/components/listItems.vue

@ -0,0 +1,132 @@
<template>
<v-dialog v-if="value" v-model="show" width="600">
<v-card width="600" color="backgroundColor">
<v-card-title class="textColor--text mx-2">Add Record
<v-spacer>
</v-spacer>
<v-btn small class="caption" color="primary">
<v-icon small>mdi-plus</v-icon>&nbsp;
Add New Record
</v-btn>
</v-card-title>
<v-card-text>
<v-text-field
hide-details
dense
outlined
placeholder="Search record"
class="mb-2 mx-2 caption"
/>
<div class="items-container">
<template v-if="data">
<v-card
v-for="(ch,i) in data.list"
class="ma-2 child-card"
outlined
v-ripple
@click="$emit('add',ch)"
:key="i"
>
<v-card-title class="primary-value textColor--text text--lighten-2">{{ ch[primaryCol] }}
<span class="grey--text caption primary-key"
v-if="primaryKey">(Primary Key : {{ ch[primaryKey] }})</span>
</v-card-title>
</v-card>
</template>
</div>
</v-card-text>
<v-card-actions class="justify-center py-2 flex-column">
<pagination
v-if="list"
:size="size"
:count="count"
v-model="page"
@input="loadData"
class="mb-3"
></pagination>
</v-card-actions>
</v-card>
</v-dialog>
<span v-else></span>
</template>
<script>
export default {
name: "listItems",
props: {
value: Boolean,
title: String,
primaryKey: String,
primaryCol: String,
meta: Object,
size: Number,
api: [Object, Function]
},
data: () => ({
data: null,
page: 1
}),
mounted() {
},
methods: {
async loadData() {
this.data = await this.api.paginatedList({
limit: this.size,
offset: this.size * (this.page - 1),
})
}
},
computed: {
show: {
set(v) {
this.$emit('input', v)
}, get() {
return this.value;
}
}
},
watch: {
value(v) {
if (v) {
this.loadData();
}
}
}
}
</script>
<style scoped lang="scss">
.child-list-modal {
position: relative;
.remove-child-icon {
position: absolute;
right: 10px;
top: 10px;
bottom: 10px;
opacity: 0;
}
&:hover .remove-child-icon {
opacity: 1;
}
}
.child-card {
cursor: pointer;
&:hover {
box-shadow: 0 0 .2em var(--v-textColor-lighten5)
}
}
</style>

161
packages/nc-gui/components/project/spreadsheet/components/virtualCell/hasManyCell.vue

@ -15,10 +15,25 @@
<x-icon x-small :color="['primary','grey']" @click="showChildListModal" class="ml-2">mdi-arrow-expand</x-icon>
</div>
<!-- <list-items
:count="10"
:meta="childMeta"
:primary-col="childPrimaryCol"
:primary-key="childPrimaryKey"
v-model="newRecordModal"
:api="childApi"
></list-items>-->
<v-dialog v-if="newRecordModal" v-model="newRecordModal" width="600">
<v-card width="600" color="backgroundColor">
<v-card-title class="textColor--text mx-2">Add Record</v-card-title>
<v-card-title class="textColor--text mx-2">Add Record
<v-spacer>
</v-spacer>
<v-btn small class="caption" color="primary">
<v-icon small>mdi-plus</v-icon>&nbsp;
Add New Record
</v-btn>
</v-card-title>
<v-card-text>
<v-text-field
hide-details
@ -43,14 +58,21 @@
v-if="childPrimaryKey">(Primary Key : {{ ch[childPrimaryKey] }})</span>
</v-card-title>
</v-card>
</template>
</div>
</v-card-text>
<v-card-actions class="justify-center pb-6 ">
<v-btn small outlined class="caption" color="primary">
<v-icon>mdi-plus</v-icon>
Add New Record
</v-btn>
<v-card-actions class="justify-center py-2 flex-column">
<pagination
v-if="list"
:size="listPagination.size"
:count="list.count"
v-model="listPagination.page"
@input="showNewRecordModal"
class="mb-3"
></pagination>
</v-card-actions>
</v-card>
</v-dialog>
@ -58,7 +80,16 @@
<v-dialog v-if="childListModal" v-model="childListModal" width="600">
<v-card width="600" color="backgroundColor">
<v-card-title class="textColor--text mx-2">{{ childMeta ? childMeta._tn : 'Children' }}</v-card-title>
<v-card-title class="textColor--text mx-2">{{ childMeta ? childMeta._tn : 'Children' }}
<v-spacer>
</v-spacer>
<v-btn small class="caption" color="primary" @click="showNewRecordModal">
<v-icon small>mdi-plus</v-icon>&nbsp;
Add Record
</v-btn>
</v-card-title>
<v-card-text>
<div class="items-container">
@ -70,13 +101,23 @@
:key="i"
@click="editChild(ch)"
>
<x-icon
class="remove-child-icon"
:color="['error','grey']"
small
@click.stop="removeChild(ch,i)"
>mdi-delete-outline
</x-icon>
<div class="remove-child-icon d-flex align-center">
<x-icon
:tooltip="`Unlink this '${childMeta._tn}' from '${meta._tn}'`"
:color="['error','grey']"
small
@click.stop="unlinkChild(ch,i)"
icon.class="mr-1 mt-n1"
>mdi-link-variant-remove
</x-icon>
<x-icon
:tooltip="`Delete row in '${childMeta._tn}'`"
:color="['error','grey']"
small
@click.stop="deleteChild(ch,i)"
>mdi-delete-outline
</x-icon>
</div>
<v-card-title class="primary-value textColor--text text--lighten-2">{{ ch[childPrimaryCol] }}
<span class="grey--text caption primary-key"
@ -86,11 +127,15 @@
</template>
</div>
</v-card-text>
<v-card-actions class="justify-center pb-6">
<v-btn small outlined class="caption" color="primary" @click="showNewRecordModal">
<v-icon>mdi-plus</v-icon>
Add Record
</v-btn>
<v-card-actions class="justify-center py-2 flex-column">
<pagination
v-if="childList"
:size="childListPagination.size"
:count="childList.count"
v-model="childListPagination.page"
@input="showChildListModal"
class="mb-3"
></pagination>
</v-card-actions>
</v-card>
</v-dialog>
@ -127,8 +172,13 @@
:sql-ui="sqlUi"
:primary-value-column="childPrimaryCol"
:api="childApi"
:available-columns="childAvailableColumns"
icon-color="warning"
:nodes="nodes"
:query-params="childQueryParams"
></component>
{{childQueryParams}}
</v-dialog>
</div>
@ -138,10 +188,12 @@
import colors from "@/mixins/colors";
import ApiFactory from "@/components/project/spreadsheet/apis/apiFactory";
import DlgLabelSubmitCancel from "@/components/utils/dlgLabelSubmitCancel";
import Pagination from "@/components/project/spreadsheet/components/pagination";
export default {
name: "has-many-cell",
components: {
Pagination,
DlgLabelSubmitCancel
},
mixins: [colors],
@ -165,6 +217,14 @@ export default {
confirmMessage: '',
selectedChild: null,
showExpandModal: false,
listPagination: {
page: 1,
size: 10
},
childListPagination: {
page: 1,
size: 10
}
}),
methods: {
@ -174,10 +234,13 @@ export default {
const pid = this.meta.columns.filter((c) => c.pk).map(c => this.row[c._cn]).join('___');
const _cn = this.childMeta.columns.find(c => c.cn === this.hm.cn)._cn;
this.childList = await this.childApi.paginatedList({
where: `(${_cn},eq,${pid})`
where: `(${_cn},eq,${pid})`,
limit: this.childListPagination.size,
offset: this.childListPagination.size * (this.childListPagination.page - 1),
...this.childQueryParams
})
},
async removeChild(child) {
async deleteChild(child) {
this.dialogShow = true;
this.confirmMessage =
'Do you want to delete the record?';
@ -193,6 +256,23 @@ export default {
}
}
},
async unlinkChild(child) {
// todo:
// this.dialogShow = true;
// this.confirmMessage =
// 'Do you want to delete the record?';
// this.confirmAction = async act => {
// if (act === 'hideDialog') {
// this.dialogShow = false;
// } else {
// const id = this.childMeta.columns.filter((c) => c.pk).map(c => child[c._cn]).join('___');
// await this.childApi.delete(id)
// this.showChildListModal();
// this.dialogShow = false;
// this.$emit('loadTableData')
// }
// }
},
async getChildMeta() {
// todo: optimize
if (!this.childMeta) {
@ -202,13 +282,17 @@ export default {
}, 'tableXcModelGet', {
tn: this.hm.tn
}]);
this.childMeta = JSON.parse(childTableData.meta)
this.childMeta = JSON.parse(childTableData.meta);
// this.childQueryParams = JSON.parse(childTableData.query_params);
}
},
async showNewRecordModal() {
this.newRecordModal = true;
await this.getChildMeta();
this.list = await this.childApi.paginatedList({})
this.list = await this.childApi.paginatedList({
limit: this.listPagination.size,
offset: this.listPagination.size * (this.listPagination.page - 1)
})
},
async addChildToParent(child) {
const id = this.childMeta.columns.filter((c) => c.pk).map(c => child[c._cn]).join('___');
@ -223,8 +307,11 @@ export default {
});
this.$emit('loadTableData')
if(this.childListModal){
await this.showChildListModal()
}
},
async editChild(child) {
async editChild(child) {
this.selectedChild = child;
this.showExpandModal = true;
}
@ -242,10 +329,30 @@ export default {
return this.childMeta && (this.childMeta.columns.find(c => c.pk) || {})._cn
},
// todo:
form(){
form() {
return () => import("@/components/project/spreadsheet/components/expandedForm")
}
},
childAvailableColumns() {
const hideCols = ['created_at', 'updated_at'];
if (!this.childMeta) return [];
const columns = [];
if (this.childMeta.columns) {
columns.push(...this.childMeta.columns.filter(c => !(c.pk && c.ai) && !hideCols.includes(c.cn)))
}
if (this.childMeta.v) {
columns.push(...this.childMeta.v.map(v => ({...v, virtual: 1})));
}
return columns;
},
childQueryParams() {
if (!this.childMeta) return {}
return {
childs: (this.childMeta && this.childMeta.hasMany && this.childMeta.hasMany.map(hm => hm.tn).join()) || '',
parents: (this.childMeta && this.childMeta.belongsTo && this.childMeta.belongsTo.map(hm => hm.rtn).join()) || '',
many: (this.childMeta && this.childMeta.manyToMany && this.childMeta.manyToMany.map(mm => mm.rtn).join()) || ''
}
}
}
}
</script>

68
packages/nc-gui/components/project/spreadsheet/rowsXcDataTable.vue

@ -70,11 +70,11 @@
<fields
v-model="showFields"
:field-list="fieldList"
:meta="meta"
:is-locked="isLocked"
:fieldsOrder.sync="fieldsOrder"
:sqlUi="sqlUi"
:showSystemFields.sync="showSystemFields"
:meta="meta"
:is-locked="isLocked"
:fieldsOrder.sync="fieldsOrder"
:sqlUi="sqlUi"
:showSystemFields.sync="showSystemFields"
/>
<sort-list
@ -220,33 +220,39 @@
</div>
<template v-if="data">
<v-pagination
v-if="count !== Infinity"
style="max-width: 100%"
<pagination
:count="count"
:size="size"
v-model="page"
:length="Math.ceil(count / size)"
:total-visible="8"
@input="loadTableData"
color="primary lighten-2"
></v-pagination>
<div v-else class="mx-auto d-flex align-center mt-n1 " style="max-width:250px">
<span class="caption" style="white-space: nowrap"> Change page:</span>
<v-text-field
class="ml-1 caption"
:full-width="false"
outlined
dense
hide-details
/>
<!-- <v-pagination
v-if="count !== Infinity"
style="max-width: 100%"
v-model="page"
@keydown.enter="loadTableData"
type="number"
>
<template #append>
<x-icon tooltip="Change page" small icon.class="mt-1" @click="loadTableData">mdi-keyboard-return
</x-icon>
</template>
</v-text-field>
</div>
:length="Math.ceil(count / size)"
:total-visible="8"
@input="loadTableData"
color="primary lighten-2"
></v-pagination>
<div v-else class="mx-auto d-flex align-center mt-n1 " style="max-width:250px">
<span class="caption" style="white-space: nowrap"> Change page:</span>
<v-text-field
class="ml-1 caption"
:full-width="false"
outlined
dense
hide-details
v-model="page"
@keydown.enter="loadTableData"
type="number"
>
<template #append>
<x-icon tooltip="Change page" small icon.class="mt-1" @click="loadTableData">mdi-keyboard-return
</x-icon>
</template>
</v-text-field>
</div>-->
</template>
<!-- <div v-else class="d-flex justify-center py-4">-->
<!-- <v-alert type="info" dense class="ma-1 flex-shrink-1">Table is empty</v-alert>-->
@ -411,7 +417,7 @@
@commented="reloadComments"
:availableColumns="availableColumns"
:nodes="nodes"
:queryParams="queryParams"
:query-params="queryParams"
></expanded-form>
</v-dialog>
@ -452,11 +458,13 @@ import SpreadsheetNavDrawer from "@/components/project/spreadsheet/components/sp
import spreadsheet from "@/components/project/spreadsheet/mixins/spreadsheet";
import LockMenu from "@/components/project/spreadsheet/components/lockMenu";
import ExpandedForm from "@/components/project/spreadsheet/components/expandedForm";
import Pagination from "@/components/project/spreadsheet/components/pagination";
export default {
mixins: [spreadsheet],
name: "rows-xc-data-table",
components: {
Pagination,
ExpandedForm,
LockMenu,
SpreadsheetNavDrawer,

18
packages/nocodb/src/lib/noco/rest/RestApiBuilder.ts

@ -1020,6 +1020,13 @@ export class RestApiBuilder extends BaseApiBuilder<Noco> {
hasMany: meta.hasMany,
});
/* Add new has many relation to virtual columns */
oldMeta.v = oldMeta.v || [];
oldMeta.v.push({
hm: meta.hasMany.find(hm => hm.rtn === tnp && hm.tn === tnc),
_cn: `${this.getTableNameAlias(tnp)} => ${this.getTableNameAlias(tnc)}`
})
await this.xcMeta.metaUpdate(this.projectId, this.dbAlias, 'nc_models', {
title: tnp,
meta: JSON.stringify(oldMeta),
@ -1093,6 +1100,14 @@ export class RestApiBuilder extends BaseApiBuilder<Noco> {
Object.assign(oldMeta, {
belongsTo: meta.belongsTo,
});
/* Add new belongs to relation to virtual columns */
oldMeta.v = oldMeta.v || [];
oldMeta.v.push({
bt: meta.belongsTo.find(hm => hm.rtn === tnp && hm.tn === tnc),
_cn: `${this.getTableNameAlias(tnp)} <= ${this.getTableNameAlias(tnc)}`
})
await this.xcMeta.metaUpdate(this.projectId, this.dbAlias, 'nc_models', {
title: tnc,
meta: JSON.stringify(oldMeta)
@ -1660,5 +1675,4 @@ export class RestApiBuilder extends BaseApiBuilder<Noco> {
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
*/
Loading…
Cancel
Save