Skip to content

Instantly share code, notes, and snippets.

@leibowitz
Last active July 29, 2024 08:28
Show Gist options
  • Save leibowitz/b94fc4eb2b8ab745267db99941757190 to your computer and use it in GitHub Desktop.
Save leibowitz/b94fc4eb2b8ab745267db99941757190 to your computer and use it in GitHub Desktop.
AdminBro Many to Many component for sequelizejs
const { Database, Resource: SequelizeResource } = require('admin-bro-sequelizejs')
const { BaseRecord } = require('admin-bro')
const { Op } = require('sequelize')
class Resource extends SequelizeResource {
titleField() {
return this.decorate().titleProperty().name()
}
wrapObjects(sequelizeObjects){
return sequelizeObjects.map(sequelizeObject => new BaseRecord(sequelizeObject.toJSON(), this))
}
async findRelated(record, toResourceId, options = {}) {
const instance = this.getInstance(record)
const association = this.getAssociationsByResourceId(toResourceId)[0]
return await instance[association.accessors.get](options)
}
getAssociationsByResourceId(resourceId) {
return Object.values(this.SequelizeModel.associations).filter(association => association.target.name === resourceId)
}
getInstance(record) {
return new this.SequelizeModel(record.params, {isNewRecord: false})
}
async saveRecords(record, resourceId, ids) {
const instance = this.getInstance(record)
const association = this.getAssociationsByResourceId(resourceId)[0]
await association.set(instance, ids)
}
primaryKeyField() {
return this.SequelizeModel.primaryKeyField
}
getManyProperties() {
return this.decorate().getProperties({where: 'edit'}).filter(p => p.type() === 'many').map(p => p.name())
}
}
module.exports = { Database, Resource }
const AdminBro = require('admin-bro')
const Adapter = require('./adapter.js')
const { after: manyToManyAfterHook } = require('./manytomany.hook')
// Import your model
const db = require('./models');
AdminBro.registerAdapter(Adapter)
const manyToManyComponent = {
type: 'many',
components: {
edit: AdminBro.bundle('./manytomany.edit.jsx'),
}
}
const manyToManyActionHooks = {
new: {
after: manyToManyAfterHook,
},
edit: {
after: manyToManyAfterHook,
}
}
// Let's say you have two models, user and group
// Linked by a many-to-many relationship
//
// You could just add the "group" property to
// the User resource to edit the association
// with groups from the user edit page
const adminBro = new AdminBro({
resources: [
{
resource: db.sequelize.models.user,
options: {
actions: manyToManyActionHooks,
editProperties: [
'name',
'email',
'group',
],
properties: {
group: manyToManyComponent
}
}
},
{
resource: db.sequelize.models.group
}
]
})
import React, { useState } from 'react'
import { FormGroup, Label, ApiClient } from 'admin-bro'
import Select from 'react-select/lib/Async'
const getOptionsFromRecords = (records) => {
return records.map(r => ({value: r.id, label: r.title}))
}
const getItems = (record, name) => {
if (record.populated && record.populated[name]) {
return getOptionsFromRecords(record.populated[name])
}
return []
}
const ResourceSelection = (props) => {
const { onChange, name, selected: initialSelection, resourceId } = props
const [options, setOptions] = useState([])
const [selected, setSelected] = useState(initialSelection)
const api = new ApiClient()
const loadOptions = async (inputValue) => {
const records = await api.searchRecords({ resourceId: resourceId, query: inputValue })
const options = getOptionsFromRecords(records)
setOptions(options)
return options
}
const handleChange = (selectedOptions) => {
setSelected(selectedOptions)
onChange(name, selectedOptions.map(v => v.value))
}
return (
<Select isMulti defaultOptions loadOptions={loadOptions} value={selected} onChange={handleChange} />
)
}
const ManyToManyEdit = (props) => {
const { property, record, onChange } = props;
const items = getItems(record, property.name)
return (
<FormGroup>
<Label>{property.label}</Label>
<ResourceSelection onChange={onChange} name={property.name} resourceId={property.name} selected={items} />
</FormGroup>
)
}
export default ManyToManyEdit
const { unflatten } = require('flat')
const setResponseItems = async (context, response, toResourceId) => {
const { _admin, resource, record } = context
const toResource = _admin.findResource(toResourceId)
const options = {order: [toResource.titleField()]}
const throughItems = await resource.findRelated(record, toResourceId, options)
const items = toResource.wrapObjects(throughItems)
if (items.length !== 0) {
const primaryKeyField = toResource.primaryKeyField()
response.record.populated[toResourceId] = items
response.record.params[toResourceId] = items.map(v => v.params[primaryKeyField || 'id'])
}
}
const after = async (response, request, context) => {
if (request && request.method) {
const manyProperties = context.resource.getManyProperties()
if (context.action.name == 'edit' && request.method === 'get') {
// Load all linked data
await Promise.all(manyProperties.map(async (toResourceId) => {
await setResponseItems(context, response, toResourceId)
}))
}
const { record } = context
if (request.method === 'post' && record.isValid()) {
const params = unflatten(request.payload)
await Promise.all(manyProperties.map(async (toResourceId) => {
const ids = params[toResourceId] ? params[toResourceId].map(v => parseInt(v)) : []
await context.resource.saveRecords(record, toResourceId, ids)
}))
}
}
return response
}
module.exports = { after }
@0xDaksh
Copy link

0xDaksh commented Mar 5, 2021

any way to do this in typeorm?

@leibowitz
Copy link
Author

Most likely. In this case you just have to adapt the adapter.js to the underlying adapter

@chakritp
Copy link

chakritp commented Jun 2, 2021

any way to do this in typeorm?

@DakshMiglani were you able to get it working with typeorm?

@mokshmodi96
Copy link

Most likely. In this case you just have to adapt the adapter.js to the underlying adapter

@leibowitz can you show some guide or some help for this ?

@ammar-aboukhriba
Copy link

ammar-aboukhriba commented Dec 20, 2021

I got this error , can you help ?
React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined

@rahul-p-vebsign
Copy link

any way to do this in typeorm?

I want to implement in typeorm for many to many

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment