Skip to content

Commit

Permalink
NEW LinkFieldController to handle FormSchema
Browse files Browse the repository at this point in the history
  • Loading branch information
emteknetnz committed Oct 31, 2023
1 parent f0a3b25 commit 5331d6a
Show file tree
Hide file tree
Showing 26 changed files with 1,844 additions and 241 deletions.
1 change: 0 additions & 1 deletion _config.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,4 @@

// Avoid creating global variables
call_user_func(function () {

});
10 changes: 1 addition & 9 deletions _config/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,12 @@ Name: linkfield

SilverStripe\Admin\LeftAndMain:
extensions:
- SilverStripe\LinkField\Extensions\LeftAndMain
- SilverStripe\LinkField\Extensions\LeftAndMainExtension

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
2 changes: 1 addition & 1 deletion _graphql/queries.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
'readLinkDescription(dataStr: String!)':
'readLinkDescription(linkID: Int!)':
type: LinkDescription
resolver: ['SilverStripe\LinkField\GraphQL\LinkDescriptionResolver', 'resolve']
'readLinkTypes(keys: [ID])':
Expand Down
1,000 changes: 999 additions & 1 deletion client/dist/js/bundle.js

Large diffs are not rendered by default.

69 changes: 68 additions & 1 deletion client/dist/styles/bundle.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

186 changes: 136 additions & 50 deletions client/src/components/LinkField/LinkField.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,109 @@
import React, { Fragment, useState } from 'react';
import { compose } from 'redux';
import React, { useState, useEffect } from 'react';
import { bindActionCreators, compose } from 'redux';
import { connect } from 'react-redux';
import { inject, injectGraphql, loadComponent } from 'lib/Injector';
import fieldHolder from 'components/FieldHolder/FieldHolder';

const LinkField = ({ id, loading, Loading, data, LinkPicker, onChange, types, linkDescription, ...props }) => {
if (loading) {
return <Loading />;
}
import * as toastsActions from 'state/toasts/ToastsActions';
import backend from 'lib/Backend';
import Config from 'lib/Config';

function LinkField(props) {
const {
loading,
Loading,
data,
LinkPicker,
onChange,
types,
actions
} = props;

const [editing, setEditing] = useState(false);
const [newTypeKey, setNewTypeKey] = useState('');

const onClear = (event) => {
if (typeof onChange !== 'function') {
return;
const [linkID, setLinkID] = useState(data.ID || 0);
const [linkData, setLinkData] = useState({});

function onClear (event) {
// deleteUrl is set via LeftAndMainExtension.php
const endpoint = `${section.form.DynamicLink.deleteUrl}/${linkID}`;
backend.delete(endpoint)
.then(() => {
actions.toasts.success('Deleted link');
})
.catch(() => {
actions.toasts.error('Failed to delete link');
});

setLinkID(0);
if (typeof onChange === 'function') {
onChange(event, {});
}

onChange(event, { id, value: {} });
};

async function onModalSubmit(modalData, action, submitFn) {
const {
SecurityID,
action_submit: actionSubmit,
...data
} = modalData;

// get id from formSchema
let id = 0;
const formSchema = await submitFn();
// formSchema will be undefined for FileLink which uses asset-admin graphql when using InsertMediaModal rather than FormBuilderModal
if (typeof formSchema !== 'undefined') {
// onsuccess formSchema.id
let match = formSchema.id.match(/\/linkForm\/[a-z\-]+\/([0-9]+)$/);
if (match) {
id = parseInt(match[1]);
}
// onfailure formSchema.id
match = formSchema.id.match(/\/schema\/linkfield\/[a-z\-]+\/([0-9]+)$/);
if (match) {
id = parseInt(match[1]);
}
}

// update parent JsonField data id
// if (typeof onChange === 'function') {
// data.ID = id;
// // onChange(null, data);
// }

// update component state
setLinkID(id);
setEditing(false);
setNewTypeKey('');

// trigger success toast
actions.toasts.success('Saved link');

return Promise.resolve();
};

const { typeKey } = data;
const type = types[typeKey];
const modalType = newTypeKey ? types[newTypeKey] : type;

let title = data ? data.Title : '';
const section = Config.getSection('SilverStripe\\Admin\\LeftAndMain');
const typeKey = data.typeKey;
const type = types[typeKey] || {};
const linkType = newTypeKey ? types[newTypeKey] : type;
// const handlerName = modalType ? modalType.handlerName : 'FormBuilderModal';
const handlerName = 'FormBuilderModal';
const LinkModal = loadComponent(`LinkModal.${handlerName}`);

if (!title) {
title = data ? data.TitleRelField : '';
}
// jsonFieldData is the initial value of the field passed from JsonField
// linkData is XHR'd in from the endpoint afterwards
const hasLinkData = Object.keys(linkData).length > 0;
const theData = hasLinkData ? linkData : data;
const title = theData.Title || theData.TitleRelField || '';

const linkProps = {
console.log('LinkField.types', types)
const pickerProps = {
title,
link: type ? { type, title, description: linkDescription } : undefined,
onEdit: () => { setEditing(true); },
// link: type ? { type, title, description: theData.description } : undefined,
description: theData.description,
typeTitle: linkType.title || '',
onEdit: () => {
setEditing(true);
},
onClear,
onSelect: (key) => {
setNewTypeKey(key);
Expand All @@ -41,50 +112,65 @@ const LinkField = ({ id, loading, Loading, data, LinkPicker, onChange, types, li
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,
// type: modalType,
typeTitle: linkType.title,
typeKey: linkType.key,
editing,
onSubmit: onModalSubmit,
onClosed: () => {
setEditing(false);
},
data
data: theData
};

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 = `${section.form.DynamicLink.dataUrl}/${linkID}`;
backend.get(endpoint)
.then(response => response.json())
.then(responseJson => {
setLinkData(responseJson);
});
}
}, [editing, linkID]);

return <Fragment>
<LinkPicker {...linkProps} />
<LinkModal {...modalProps} />
</Fragment>;
if (loading) {
return <Loading />;
}

return <>
<LinkPicker {...pickerProps} />
<LinkModal {...modalProps} />
</>;
};

const stringifyData = (Component) => (({ data, value, ...props }) => {
const stringifyData = (Component) => (({ value, data, ...props }) => {
let dataValue = value || data;
if (typeof dataValue === 'string') {
dataValue = JSON.parse(dataValue);
}
return <Component dataStr={JSON.stringify(dataValue)} {...props} data={dataValue} />;
return <Component
// dataStr={JSON.stringify(dataValue)}
{...props}
data={dataValue}
/>;
});

const mapDispatchToProps = (dispatch) => {
return {
actions: {
toasts: bindActionCreators(toastsActions, dispatch),
},
};
}

export default compose(
inject(['LinkPicker', 'Loading']),
injectGraphql('readLinkTypes'),
stringifyData,
injectGraphql('readLinkDescription'),
fieldHolder
stringifyData, // get rid of this?
// injectGraphql('readLinkDescription'),
fieldHolder,
connect(null, mapDispatchToProps)
)(LinkField);
20 changes: 10 additions & 10 deletions client/src/components/LinkModal/LinkModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,30 @@ import url from 'url';
import qs from 'qs';
import Config from 'lib/Config';

const leftAndMain = 'SilverStripe\\Admin\\LeftAndMain';

const buildSchemaUrl = (key, data) => {

const {schemaUrl} = Config.getSection(leftAndMain).form.DynamicLink;
const {schemaUrl} = Config.getSection('SilverStripe\\Admin\\LeftAndMain').form.DynamicLink;

const parsedURL = url.parse(schemaUrl);
const parsedQs = qs.parse(parsedURL.query);
parsedQs.key = key;
if (data) {
parsedQs.data = JSON.stringify(data);
// parsedQs.key = key; // todo: change to request param
for (const prop of ['href', 'path', 'pathname']) {
const id = data && typeof data.ID !== 'undefined' ? data.ID : '0';
// parsedURL[prop] += `/${id}`;
parsedURL[prop] = `${parsedURL[prop]}/${key}/${id}`;
}
return url.format({ ...parsedURL, search: qs.stringify(parsedQs)});
}

const LinkModal = ({type, editing, data, ...props}) => {
if (!type) {
const LinkModal = ({ typeTitle, typeKey, editing, data, ...props }) => {
if (!typeKey) {
return false;
}

return <FormBuilderModal
title={type.title}
title={typeTitle}
isOpen={editing}
schemaUrl={buildSchemaUrl(type.key, data)}
schemaUrl={buildSchemaUrl(typeKey, data)}
identifier='Link.EditingLinkInfo'
{...props}
/>;
Expand Down
Loading

0 comments on commit 5331d6a

Please sign in to comment.