-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #108 from creative-commoners/pulls/4/form-schema
NEW LinkFieldController to handle FormSchema
- Loading branch information
Showing
44 changed files
with
1,143 additions
and
827 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,5 +5,4 @@ | |
|
||
// Avoid creating global variables | ||
call_user_func(function () { | ||
|
||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,6 @@ | ||
--- | ||
Name: linkfield | ||
--- | ||
|
||
SilverStripe\Admin\LeftAndMain: | ||
extensions: | ||
- SilverStripe\LinkField\Extensions\LeftAndMain | ||
|
||
SilverStripe\Admin\ModalController: | ||
extensions: | ||
- SilverStripe\LinkField\Extensions\ModalController | ||
|
||
SilverStripe\Forms\TreeDropdownField: | ||
extensions: | ||
- SilverStripe\LinkField\Extensions\AjaxField | ||
|
||
SilverStripe\CMS\Forms\AnchorSelectorField: | ||
extensions: | ||
- SilverStripe\LinkField\Extensions\AjaxField | ||
|
||
SilverStripe\LinkField\Form\FormFactory: | ||
extensions: | ||
- SilverStripe\LinkField\Extensions\FormFactoryExtension | ||
- SilverStripe\LinkField\Extensions\LeftAndMainExtension |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,6 @@ | ||
--- | ||
Name: linkfield-types | ||
--- | ||
|
||
SilverStripe\LinkField\Type\Registry: | ||
types: | ||
cms: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,3 @@ | ||
'readLinkDescription(dataStr: String!)': | ||
type: LinkDescription | ||
resolver: ['SilverStripe\LinkField\GraphQL\LinkDescriptionResolver', 'resolve'] | ||
'readLinkTypes(keys: [ID])': | ||
type: '[LinkType]' | ||
resolver: ['SilverStripe\LinkField\GraphQL\LinkTypeResolver', 'resolve'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,5 @@ | ||
LinkDescription: | ||
description: Given some Link data, computes the matching description | ||
fields: | ||
description: String | ||
|
||
LinkType: | ||
description: Describe a Type of Link that can be managed by a LinkField | ||
fields: | ||
key: ID | ||
handlerName: String! | ||
title: String! |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,9 @@ | ||
/* global document */ | ||
/* eslint-disable */ | ||
import Config from 'lib/Config'; | ||
import registerReducers from './registerReducers'; | ||
import registerComponents from './registerComponents'; | ||
import registerQueries from './registerQueries'; | ||
|
||
document.addEventListener('DOMContentLoaded', () => { | ||
registerComponents(); | ||
|
||
registerQueries(); | ||
|
||
registerReducers(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,8 @@ | ||
/* eslint-disable */ | ||
import Injector from 'lib/Injector'; | ||
import readLinkTypes from 'state/linkTypes/readLinkTypes'; | ||
import readLinkDescription from 'state/linkDescription/readLinkDescription'; | ||
|
||
const registerQueries = () => { | ||
Injector.query.register('readLinkTypes', readLinkTypes); | ||
Injector.query.register('readLinkDescription', readLinkDescription); | ||
}; | ||
export default registerQueries; |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,90 +1,149 @@ | ||
import React, { Fragment, useState } from 'react'; | ||
import { compose } from 'redux'; | ||
import { inject, injectGraphql, loadComponent } from 'lib/Injector'; | ||
import React, { useState, useEffect } from 'react'; | ||
import { bindActionCreators, compose } from 'redux'; | ||
import { connect } from 'react-redux'; | ||
import { injectGraphql, loadComponent } from 'lib/Injector'; | ||
import fieldHolder from 'components/FieldHolder/FieldHolder'; | ||
import LinkPicker from 'components/LinkPicker/LinkPicker'; | ||
import * as toastsActions from 'state/toasts/ToastsActions'; | ||
import backend from 'lib/Backend'; | ||
import Config from 'lib/Config'; | ||
import PropTypes from 'prop-types'; | ||
|
||
// section used in window.ss config | ||
const section = 'SilverStripe\\LinkField\\Controllers\\LinkFieldController'; | ||
|
||
/** | ||
* value - ID of the Link passed from JsonField | ||
* onChange - callback function passed from JsonField - used to update the underlying <input> form field | ||
* types - injected by the GraphQL query | ||
* actions - object of redux actions | ||
*/ | ||
const LinkField = ({ value, onChange, types, actions }) => { | ||
const linkID = value; | ||
const [typeKey, setTypeKey] = useState(''); | ||
const [data, setData] = useState({}); | ||
const [editing, setEditing] = useState(false); | ||
|
||
const LinkField = ({ id, loading, Loading, data, LinkPicker, onChange, types, linkDescription, ...props }) => { | ||
if (loading) { | ||
return <Loading />; | ||
} | ||
/** | ||
* Call back used by LinkModal after the form has been submitted and the response has been received | ||
*/ | ||
const onModalSubmit = async (modalData, action, submitFn) => { | ||
const formSchema = await submitFn(); | ||
|
||
// slightly annoyingly, on validation error formSchema at this point will not have an errors node | ||
// instead it will have the original formSchema id used for the GET request to get the formSchema i.e. | ||
// admin/linkfield/schema/linkfield/<ItemID> | ||
// instead of the one used by the POST submission i.e. | ||
// admin/linkfield/linkForm/<LinkID> | ||
const hasValidationErrors = formSchema.id.match(/\/schema\/linkfield\/([0-9]+)/); | ||
if (!hasValidationErrors) { | ||
// get link id from formSchema response | ||
const match = formSchema.id.match(/\/linkForm\/([0-9]+)/); | ||
const valueFromSchemaResponse = parseInt(match[1], 10); | ||
|
||
// update component state | ||
setEditing(false); | ||
|
||
const [editing, setEditing] = useState(false); | ||
const [newTypeKey, setNewTypeKey] = useState(''); | ||
// update parent JsonField data id - this is required to update the underlying <input> form field | ||
// so that the Page (or other parent DataObject) gets the Link relation ID set | ||
onChange(valueFromSchemaResponse); | ||
|
||
const onClear = (event) => { | ||
if (typeof onChange !== 'function') { | ||
return; | ||
// success toast | ||
actions.toasts.success('Saved link'); | ||
} | ||
|
||
onChange(event, { id, value: {} }); | ||
return Promise.resolve(); | ||
}; | ||
|
||
const { typeKey } = data; | ||
const type = types[typeKey]; | ||
const modalType = newTypeKey ? types[newTypeKey] : type; | ||
|
||
let title = data ? data.Title : ''; | ||
/** | ||
* Call back used by LinkPicker when the 'Clear' button is clicked | ||
*/ | ||
const onClear = () => { | ||
const endpoint = `${Config.getSection(section).form.linkForm.deleteUrl}/${linkID}`; | ||
// CSRF token 'X-SecurityID' headers needs to be present for destructive requests | ||
backend.delete(endpoint, {}, { 'X-SecurityID': Config.get('SecurityID') }) | ||
.then(() => { | ||
actions.toasts.success('Deleted link'); | ||
}) | ||
.catch(() => { | ||
actions.toasts.error('Failed to delete link'); | ||
}); | ||
|
||
// update component state | ||
setTypeKey(''); | ||
setData({}); | ||
|
||
// update parent JsonField data ID used to update the underlying <input> form field | ||
onChange(0); | ||
}; | ||
|
||
if (!title) { | ||
title = data ? data.TitleRelField : ''; | ||
} | ||
const title = data.Title || ''; | ||
const type = types.hasOwnProperty(typeKey) ? types[typeKey] : {}; | ||
const modalType = typeKey ? types[typeKey] : type; | ||
const handlerName = modalType && modalType.hasOwnProperty('handlerName') | ||
? modalType.handlerName | ||
: 'FormBuilderModal'; | ||
const LinkModal = loadComponent(`LinkModal.${handlerName}`); | ||
|
||
const linkProps = { | ||
const pickerProps = { | ||
title, | ||
link: type ? { type, title, description: linkDescription } : undefined, | ||
onEdit: () => { setEditing(true); }, | ||
description: data.description, | ||
typeTitle: type.title || '', | ||
onEdit: () => { | ||
setEditing(true); | ||
}, | ||
onClear, | ||
onSelect: (key) => { | ||
setNewTypeKey(key); | ||
setTypeKey(key); | ||
setEditing(true); | ||
}, | ||
types: Object.values(types) | ||
}; | ||
|
||
const onModalSubmit = (modalData, action, submitFn) => { | ||
const { SecurityID, action_insert: actionInsert, ...value } = modalData; | ||
|
||
if (typeof onChange === 'function') { | ||
onChange(event, { id, value }); | ||
} | ||
|
||
setEditing(false); | ||
setNewTypeKey(''); | ||
|
||
return Promise.resolve(); | ||
}; | ||
|
||
const modalProps = { | ||
type: modalType, | ||
typeTitle: type.title || '', | ||
typeKey, | ||
editing, | ||
onSubmit: onModalSubmit, | ||
onClosed: () => { | ||
setEditing(false); | ||
}, | ||
data | ||
linkID | ||
}; | ||
|
||
const handlerName = modalType ? modalType.handlerName : 'FormBuilderModal'; | ||
const LinkModal = loadComponent(`LinkModal.${handlerName}`); | ||
// read data from endpoint and update component state | ||
useEffect(() => { | ||
if (!editing && linkID) { | ||
const endpoint = `${Config.getSection(section).form.linkForm.dataUrl}/${linkID}`; | ||
backend.get(endpoint) | ||
.then(response => response.json()) | ||
.then(responseJson => { | ||
setData(responseJson); | ||
setTypeKey(responseJson.typeKey); | ||
}); | ||
} | ||
}, [editing, linkID]); | ||
|
||
return <> | ||
<LinkPicker {...pickerProps} /> | ||
<LinkModal {...modalProps} /> | ||
</>; | ||
}; | ||
|
||
return <Fragment> | ||
<LinkPicker {...linkProps} /> | ||
<LinkModal {...modalProps} /> | ||
</Fragment>; | ||
LinkField.propTypes = { | ||
value: PropTypes.number.isRequired, | ||
onChange: PropTypes.func.isRequired, | ||
}; | ||
|
||
const stringifyData = (Component) => (({ data, value, ...props }) => { | ||
let dataValue = value || data; | ||
if (typeof dataValue === 'string') { | ||
dataValue = JSON.parse(dataValue); | ||
} | ||
return <Component dataStr={JSON.stringify(dataValue)} {...props} data={dataValue} />; | ||
// redux actions loaded into props - used to get toast notifications | ||
const mapDispatchToProps = (dispatch) => ({ | ||
actions: { | ||
toasts: bindActionCreators(toastsActions, dispatch), | ||
}, | ||
}); | ||
|
||
export default compose( | ||
inject(['LinkPicker', 'Loading']), | ||
injectGraphql('readLinkTypes'), | ||
stringifyData, | ||
injectGraphql('readLinkDescription'), | ||
fieldHolder | ||
fieldHolder, | ||
connect(null, mapDispatchToProps) | ||
)(LinkField); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.