Add BaseSequenceBlock superclass

pull/6931/head
Matt Westcott 2021-02-08 22:39:15 +00:00
rodzic 320c639255
commit 4ee65760fe
3 zmienionych plików z 224 dodań i 392 usunięć

Wyświetl plik

@ -1,3 +1,5 @@
/* eslint-disable no-underscore-dangle */
/* global $ */
import { escapeHtml as h } from '../../../utils/text';
@ -151,3 +153,218 @@ export class BaseInsertionControl {
$(this.element).hide().attr('aria-hidden', 'true');
}
}
export class BaseSequenceBlock {
// eslint-disable-next-line no-unused-vars
_createChild(blockDef, placeholder, prefix, index, id, initialState, opts) {
throw new Error('not implemented');
}
// eslint-disable-next-line no-unused-vars
_createInsertionControl(placeholder, opts) {
throw new Error('not implemented');
}
// eslint-disable-next-line no-unused-vars
_getChildDataForInsertion(opts) {
throw new Error('not implemented');
}
clear() {
this.countInput.val(0);
this.sequenceContainer.empty();
this.children = [];
this.blockCounter = 0;
// Create initial insertion control
const placeholder = document.createElement('div');
this.sequenceContainer.append(placeholder);
this.inserters = [
this._createInsertionControl(
placeholder, {
index: 0,
onRequestInsert: (newIndex, opts) => {
this._onRequestInsert(newIndex, opts);
},
strings: this.blockDef.meta.strings,
}
)
];
}
_onRequestInsert(index, opts) {
/* handler for an 'insert new block' action */
const [blockDef, initialState] = this._getChildDataForInsertion(opts);
const newChild = this._insert(blockDef, initialState, null, index, { animate: true });
newChild.focus();
}
_insert(childBlockDef, initialState, id, index, opts) {
const prefix = this.prefix + '-' + this.blockCounter;
const animate = opts && opts.animate;
this.blockCounter++;
/*
a new inserter and block will be inserted AFTER the inserter with the given index;
e.g if there are 3 blocks the children of sequenceContainer will be
[inserter 0, block 0, inserter 1, block 1, inserter 2, block 2, inserter 3]
and inserting a new block at index 1 will create a new block 1 and inserter 2 after the
current inserter 1, and increment everything after that point
*/
const existingInserterElement = this.inserters[index].element;
const blockPlaceholder = document.createElement('div');
const inserterPlaceholder = document.createElement('div');
$(blockPlaceholder).insertAfter(existingInserterElement);
$(inserterPlaceholder).insertAfter(blockPlaceholder);
/* shuffle up indexes of all blocks / inserters above this index */
for (let i = index; i < this.children.length; i++) {
this.children[i].setIndex(i + 1);
}
for (let i = index + 1; i < this.inserters.length; i++) {
this.inserters[i].setIndex(i + 1);
}
const child = this._createChild(childBlockDef, blockPlaceholder, prefix, index, id, initialState, {
animate,
onRequestDuplicate: (i) => { this.duplicateBlock(i); },
onRequestDelete: (i) => { this.deleteBlock(i); },
onRequestMoveUp: (i) => { this.moveBlock(i, i - 1); },
onRequestMoveDown: (i) => { this.moveBlock(i, i + 1); },
strings: this.blockDef.meta.strings,
});
this.children.splice(index, 0, child);
const inserter = this._createInsertionControl(
inserterPlaceholder, {
index: index + 1,
onRequestInsert: (newIndex, inserterOpts) => {
this._onRequestInsert(newIndex, inserterOpts);
},
strings: this.blockDef.meta.strings,
}
);
this.inserters.splice(index + 1, 0, inserter);
this.countInput.val(this.blockCounter);
const isFirstChild = (index === 0);
const isLastChild = (index === this.children.length - 1);
if (!isFirstChild) {
child.enableMoveUp();
if (isLastChild) {
/* previous child (which was previously the last one) can now move down */
this.children[index - 1].enableMoveDown();
}
}
if (!isLastChild) {
child.enableMoveDown();
if (isFirstChild) {
/* next child (which was previously the first one) can now move up */
this.children[index + 1].enableMoveUp();
}
}
return child;
}
deleteBlock(index) {
this.children[index].markDeleted({ animate: true });
this.inserters[index].delete();
this.children.splice(index, 1);
this.inserters.splice(index, 1);
/* index numbers of children / inserters above this index now need updating to match
their array indexes */
for (let i = index; i < this.children.length; i++) {
this.children[i].setIndex(i);
}
for (let i = index; i < this.inserters.length; i++) {
this.inserters[i].setIndex(i);
}
if (index === 0 && this.children.length > 0) {
/* we have removed the first child; the new first child cannot be moved up */
this.children[0].disableMoveUp();
}
if (index === this.children.length && this.children.length > 0) {
/* we have removed the last child; the new last child cannot be moved down */
this.children[this.children.length - 1].disableMoveDown();
}
}
moveBlock(oldIndex, newIndex) {
if (oldIndex === newIndex) return;
const inserterToMove = this.inserters[oldIndex];
const childToMove = this.children[oldIndex];
/* move HTML elements */
if (newIndex > oldIndex) {
$(inserterToMove.element).insertAfter(this.children[newIndex].element);
} else {
$(inserterToMove.element).insertBefore(this.inserters[newIndex].element);
}
$(childToMove.element).insertAfter(inserterToMove.element);
/* reorder items in the `inserters` and `children` arrays */
this.inserters.splice(oldIndex, 1);
this.inserters.splice(newIndex, 0, inserterToMove);
this.children.splice(oldIndex, 1);
this.children.splice(newIndex, 0, childToMove);
/* update index properties of moved items */
if (newIndex > oldIndex) {
for (let i = oldIndex; i <= newIndex; i++) {
this.inserters[i].setIndex(i);
this.children[i].setIndex(i);
}
} else {
for (let i = newIndex; i <= oldIndex; i++) {
this.inserters[i].setIndex(i);
this.children[i].setIndex(i);
}
}
/* enable/disable up/down arrows as required */
const maxIndex = this.children.length - 1;
if (oldIndex === 0) {
childToMove.enableMoveUp();
this.children[0].disableMoveUp();
}
if (oldIndex === maxIndex) {
childToMove.enableMoveDown();
this.children[maxIndex].disableMoveDown();
}
if (newIndex === 0) {
childToMove.disableMoveUp();
this.children[1].enableMoveUp();
}
if (newIndex === maxIndex) {
childToMove.disableMoveDown();
this.children[maxIndex - 1].enableMoveDown();
}
}
setState(values) {
this.clear();
values.forEach((val, i) => {
this.insert(val, i);
});
}
getState() {
return this.children.map(child => child.getState());
}
getValue() {
return this.children.map(child => child.getValue());
}
focus() {
if (this.children.length) {
this.children[0].focus();
}
}
}

Wyświetl plik

@ -1,6 +1,6 @@
/* eslint-disable no-underscore-dangle */
import { BaseSequenceChild, BaseInsertionControl } from './BaseSequenceBlock';
import { BaseSequenceBlock, BaseSequenceChild, BaseInsertionControl } from './BaseSequenceBlock';
import { escapeHtml as h } from '../../../utils/text';
/* global $ */
@ -51,7 +51,7 @@ class InsertPosition extends BaseInsertionControl {
}
}
export class ListBlock {
export class ListBlock extends BaseSequenceBlock {
constructor(blockDef, placeholder, prefix, initialState, initialError) {
this.blockDef = blockDef;
this.type = blockDef.name;
@ -81,7 +81,7 @@ export class ListBlock {
this.inserters = [];
this.blockCounter = 0;
this.countInput = dom.find('[data-streamfield-list-count]');
this.listContainer = dom.find('[data-streamfield-list-container]');
this.sequenceContainer = dom.find('[data-streamfield-list-container]');
this.setState(initialState || []);
if (initialError) {
@ -89,28 +89,6 @@ export class ListBlock {
}
}
clear() {
this.countInput.val(0);
this.listContainer.empty();
this.children = [];
this.blockCounter = 0;
// Create initial insert position
const placeholder = document.createElement('div');
this.listContainer.append(placeholder);
this.inserters = [
this._createInsertionControl(
placeholder, {
index: 0,
onRequestInsert: (newIndex, opts) => {
this._onRequestInsert(newIndex, opts);
},
strings: this.blockDef.meta.strings,
}
)
];
}
_getChildDataForInsertion() {
/* Called when an 'insert new block' action is triggered: given a dict of data from the insertion control,
return the block definition and initial state to be used for the new block.
@ -121,13 +99,6 @@ export class ListBlock {
return [blockDef, initialState];
}
_onRequestInsert(index, opts) {
/* handler for an 'insert new block' action */
const [blockDef, initialState] = this._getChildDataForInsertion(opts);
const newChild = this._insert(blockDef, initialState, null, index, { animate: true });
newChild.focus();
}
_createChild(blockDef, placeholder, prefix, index, id, initialState, opts) {
return new ListChild(blockDef, placeholder, prefix, index, id, initialState, opts);
}
@ -140,163 +111,12 @@ export class ListBlock {
return this._insert(this.blockDef.childBlockDef, value, null, index, opts);
}
_insert(childBlockDef, initialState, id, index, opts) {
const prefix = this.prefix + '-' + this.blockCounter;
const animate = opts && opts.animate;
this.blockCounter++;
/*
a new insert position and block will be inserted AFTER the insert position with the given index;
e.g if there are 3 blocks the children of listContainer will be
[insert pos 0, block 0, insert pos 1, block 1, insert pos 2, block 2, insert pos 3]
and inserting a new block at index 1 will create a new block 1 and insert position 2 after the
current menu 1, and increment everything after that point
*/
const existingInsertPositionElement = this.inserters[index].element;
const blockPlaceholder = document.createElement('div');
const inserterPlaceholder = document.createElement('div');
$(blockPlaceholder).insertAfter(existingInsertPositionElement);
$(inserterPlaceholder).insertAfter(blockPlaceholder);
/* shuffle up indexes of all blocks / insert positions above this index */
for (let i = index; i < this.children.length; i++) {
this.children[i].setIndex(i + 1);
}
for (let i = index + 1; i < this.inserters.length; i++) {
this.inserters[i].setIndex(i + 1);
}
const child = this._createChild(childBlockDef, blockPlaceholder, prefix, index, id, initialState, {
animate,
onRequestDuplicate: (i) => { this.duplicateBlock(i); },
onRequestDelete: (i) => { this.deleteBlock(i); },
onRequestMoveUp: (i) => { this.moveBlock(i, i - 1); },
onRequestMoveDown: (i) => { this.moveBlock(i, i + 1); },
strings: this.blockDef.meta.strings,
});
this.children.splice(index, 0, child);
const inserter = this._createInsertionControl(
inserterPlaceholder, {
index: index + 1,
onRequestInsert: (newIndex, inserterOpts) => {
this._onRequestInsert(newIndex, inserterOpts);
},
strings: this.blockDef.meta.strings,
}
);
this.inserters.splice(index + 1, 0, inserter);
this.countInput.val(this.blockCounter);
const isFirstChild = (index === 0);
const isLastChild = (index === this.children.length - 1);
if (!isFirstChild) {
child.enableMoveUp();
if (isLastChild) {
/* previous child (which was previously the last one) can now move down */
this.children[index - 1].enableMoveDown();
}
}
if (!isLastChild) {
child.enableMoveDown();
if (isFirstChild) {
/* next child (which was previously the first one) can now move up */
this.children[index + 1].enableMoveUp();
}
}
}
duplicateBlock(index) {
const childState = this.children[index].getState();
this.insert(childState, index + 1, { animate: true });
this.children[index + 1].focus();
}
deleteBlock(index) {
this.children[index].markDeleted({ animate: true });
this.inserters[index].delete();
this.children.splice(index, 1);
this.inserters.splice(index, 1);
/* index numbers of children / insert positions above this index now need updating to match
their array indexes */
for (let i = index; i < this.children.length; i++) {
this.children[i].setIndex(i);
}
for (let i = index; i < this.inserters.length; i++) {
this.inserters[i].setIndex(i);
}
if (index === 0 && this.children.length > 0) {
/* we have removed the first child; the new first child cannot be moved up */
this.children[0].disableMoveUp();
}
if (index === this.children.length && this.children.length > 0) {
/* we have removed the last child; the new last child cannot be moved down */
this.children[this.children.length - 1].disableMoveDown();
}
}
moveBlock(oldIndex, newIndex) {
if (oldIndex === newIndex) return;
const inserterToMove = this.inserters[oldIndex];
const childToMove = this.children[oldIndex];
/* move HTML elements */
if (newIndex > oldIndex) {
$(inserterToMove.element).insertAfter(this.children[newIndex].element);
} else {
$(inserterToMove.element).insertBefore(this.inserters[newIndex].element);
}
$(childToMove.element).insertAfter(inserterToMove.element);
/* reorder items in the `insert positions` and `children` arrays */
this.inserters.splice(oldIndex, 1);
this.inserters.splice(newIndex, 0, inserterToMove);
this.children.splice(oldIndex, 1);
this.children.splice(newIndex, 0, childToMove);
/* update index properties of moved items */
if (newIndex > oldIndex) {
for (let i = oldIndex; i <= newIndex; i++) {
this.inserters[i].setIndex(i);
this.children[i].setIndex(i);
}
} else {
for (let i = newIndex; i <= oldIndex; i++) {
this.inserters[i].setIndex(i);
this.children[i].setIndex(i);
}
}
/* enable/disable up/down arrows as required */
const maxIndex = this.children.length - 1;
if (oldIndex === 0) {
childToMove.enableMoveUp();
this.children[0].disableMoveUp();
}
if (oldIndex === maxIndex) {
childToMove.enableMoveDown();
this.children[maxIndex].disableMoveDown();
}
if (newIndex === 0) {
childToMove.disableMoveUp();
this.children[1].enableMoveUp();
}
if (newIndex === maxIndex) {
childToMove.disableMoveDown();
this.children[maxIndex - 1].enableMoveDown();
}
}
setState(values) {
this.clear();
values.forEach(val => {
this.insert(val, this.children.length);
});
}
setError(errorList) {
if (errorList.length !== 1) {
return;
@ -310,20 +130,6 @@ export class ListBlock {
}
}
}
getState() {
return this.children.map(child => child.getState());
}
getValue() {
return this.children.map(child => child.getValue());
}
focus() {
if (this.children.length) {
this.children[0].focus();
}
}
}
export class ListBlockDefinition {

Wyświetl plik

@ -1,6 +1,6 @@
/* eslint-disable no-underscore-dangle */
import { BaseSequenceChild, BaseInsertionControl } from './BaseSequenceBlock';
import { BaseSequenceBlock, BaseSequenceChild, BaseInsertionControl } from './BaseSequenceBlock';
import { escapeHtml as h } from '../../../utils/text';
/* global $ */
@ -126,7 +126,7 @@ class StreamBlockMenu extends BaseInsertionControl {
}
}
export class StreamBlock {
export class StreamBlock extends BaseSequenceBlock {
constructor(blockDef, placeholder, prefix, initialState, initialError) {
this.blockDef = blockDef;
this.type = blockDef.name;
@ -167,7 +167,7 @@ export class StreamBlock {
// Parent element of insert control and block elements (potentially including deleted items,
// which are left behind as hidden elements with a '-deleted' input so that the
// server-side form handler knows to skip it)
this.streamContainer = dom.find('[data-streamfield-stream-container]');
this.sequenceContainer = dom.find('[data-streamfield-stream-container]');
this.setState(initialState || []);
this.container = dom;
@ -176,27 +176,6 @@ export class StreamBlock {
}
}
clear() {
this.countInput.val(0);
this.streamContainer.empty();
this.children = [];
this.blockCounter = 0;
const placeholder = document.createElement('div');
this.streamContainer.append(placeholder);
this.inserters = [
this._createInsertionControl(
placeholder, {
index: 0,
onRequestInsert: (index, opts) => {
this._onRequestInsert(index, opts);
},
strings: this.blockDef.meta.strings,
}
)
];
}
_createChild(blockDef, placeholder, prefix, index, id, initialState, opts) {
return new StreamChild(blockDef, placeholder, prefix, index, id, initialState, opts);
}
@ -212,75 +191,6 @@ export class StreamBlock {
return this._insert(childBlockDef, value, id, index, opts);
}
_insert(childBlockDef, initialState, id, index, opts) {
const prefix = this.prefix + '-' + this.blockCounter;
const animate = opts && opts.animate;
this.blockCounter++;
/*
a new inserter and block will be inserted AFTER the inserter with the given index;
e.g if there are 3 blocks the children of streamContainer will be
[inserter 0, block 0, inserter 1, block 1, inserter 2, block 2, inserter 3]
and inserting a new block at index 1 will create a new block 1 and inserter 2 after the
current inserter 1, and increment everything after that point
*/
const existingMenuElement = this.inserters[index].element;
const blockPlaceholder = document.createElement('div');
const inserterPlaceholder = document.createElement('div');
$(blockPlaceholder).insertAfter(existingMenuElement);
$(inserterPlaceholder).insertAfter(blockPlaceholder);
/* shuffle up indexes of all blocks / inserters above this index */
for (let i = index; i < this.children.length; i++) {
this.children[i].setIndex(i + 1);
}
for (let i = index + 1; i < this.inserters.length; i++) {
this.inserters[i].setIndex(i + 1);
}
const child = this._createChild(childBlockDef, blockPlaceholder, prefix, index, id, initialState, {
animate,
onRequestDuplicate: (i) => { this.duplicateBlock(i); },
onRequestDelete: (i) => { this.deleteBlock(i); },
onRequestMoveUp: (i) => { this.moveBlock(i, i - 1); },
onRequestMoveDown: (i) => { this.moveBlock(i, i + 1); },
strings: this.blockDef.meta.strings,
});
this.children.splice(index, 0, child);
const inserter = this._createInsertionControl(
inserterPlaceholder, {
index: index + 1,
onRequestInsert: (newIndex, inserterOpts) => {
this._onRequestInsert(newIndex, inserterOpts);
},
strings: this.blockDef.meta.strings,
}
);
this.inserters.splice(index + 1, 0, inserter);
this.countInput.val(this.blockCounter);
const isFirstChild = (index === 0);
const isLastChild = (index === this.children.length - 1);
if (!isFirstChild) {
child.enableMoveUp();
if (isLastChild) {
/* previous child (which was previously the last one) can now move down */
this.children[index - 1].enableMoveDown();
}
}
if (!isLastChild) {
child.enableMoveDown();
if (isFirstChild) {
/* next child (which was previously the first one) can now move up */
this.children[index + 1].enableMoveUp();
}
}
return child;
}
_getChildDataForInsertion({ type }) {
/* Called when an 'insert new block' action is triggered: given a dict of data from the insertion control,
return the block definition and initial state to be used for the new block.
@ -291,13 +201,6 @@ export class StreamBlock {
return [blockDef, initialState];
}
_onRequestInsert(index, opts) {
/* handler for an 'insert new block' action */
const [blockDef, initialState] = this._getChildDataForInsertion(opts);
const newChild = this._insert(blockDef, initialState, null, index, { animate: true });
newChild.focus();
}
duplicateBlock(index) {
const childState = this.children[index].getState();
childState.id = null;
@ -305,88 +208,8 @@ export class StreamBlock {
this.children[index + 1].focus();
}
deleteBlock(index) {
this.children[index].markDeleted({ animate: true });
this.inserters[index].delete();
this.children.splice(index, 1);
this.inserters.splice(index, 1);
/* index numbers of children / inserters above this index now need updating to match
their array indexes */
for (let i = index; i < this.children.length; i++) {
this.children[i].setIndex(i);
}
for (let i = index; i < this.inserters.length; i++) {
this.inserters[i].setIndex(i);
}
if (index === 0 && this.children.length > 0) {
/* we have removed the first child; the new first child cannot be moved up */
this.children[0].disableMoveUp();
}
if (index === this.children.length && this.children.length > 0) {
/* we have removed the last child; the new last child cannot be moved down */
this.children[this.children.length - 1].disableMoveDown();
}
}
moveBlock(oldIndex, newIndex) {
if (oldIndex === newIndex) return;
const inserterToMove = this.inserters[oldIndex];
const childToMove = this.children[oldIndex];
/* move HTML elements */
if (newIndex > oldIndex) {
$(inserterToMove.element).insertAfter(this.children[newIndex].element);
} else {
$(inserterToMove.element).insertBefore(this.inserters[newIndex].element);
}
$(childToMove.element).insertAfter(inserterToMove.element);
/* reorder items in the `inserters` and `children` arrays */
this.inserters.splice(oldIndex, 1);
this.inserters.splice(newIndex, 0, inserterToMove);
this.children.splice(oldIndex, 1);
this.children.splice(newIndex, 0, childToMove);
/* update index properties of moved items */
if (newIndex > oldIndex) {
for (let i = oldIndex; i <= newIndex; i++) {
this.inserters[i].setIndex(i);
this.children[i].setIndex(i);
}
} else {
for (let i = newIndex; i <= oldIndex; i++) {
this.inserters[i].setIndex(i);
this.children[i].setIndex(i);
}
}
/* enable/disable up/down arrows as required */
const maxIndex = this.children.length - 1;
if (oldIndex === 0) {
childToMove.enableMoveUp();
this.children[0].disableMoveUp();
}
if (oldIndex === maxIndex) {
childToMove.enableMoveDown();
this.children[maxIndex].disableMoveDown();
}
if (newIndex === 0) {
childToMove.disableMoveUp();
this.children[1].enableMoveUp();
}
if (newIndex === maxIndex) {
childToMove.disableMoveDown();
this.children[maxIndex - 1].enableMoveDown();
}
}
setState(values) {
this.clear();
values.forEach((val, i) => {
this.insert(val, i);
});
super.setState(values);
if (values.length === 0) {
/* for an empty list, begin with the menu open */
this.inserters[0].open({ animate: false });
@ -422,20 +245,6 @@ export class StreamBlock {
}
}
}
getState() {
return this.children.map(child => child.getState());
}
getValue() {
return this.children.map(child => child.getValue());
}
focus() {
if (this.children.length) {
this.children[0].focus();
}
}
}
export class StreamBlockDefinition {