aboutsummaryrefslogtreecommitdiff
path: root/src/views/nodes/FieldTextBox.tsx
blob: 3b87436270b40558e4c975b128b275471119f227 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import { Key, KeyStore } from "../../fields/Key";
import { Document } from "../../fields/Document";
import { observer } from "mobx-react";
import { TextField } from "../../fields/TextField";
import React = require("react")
import { action, observable, reaction, IReactionDisposer } from "mobx";

import {schema} from "prosemirror-schema-basic";
import {EditorState, Transaction} from "prosemirror-state"
import {EditorView} from "prosemirror-view"
import {keymap} from "prosemirror-keymap"
import {baseKeymap} from "prosemirror-commands"
import {undo, redo, history} from "prosemirror-history"
import { Opt } from "../../fields/Field";

import "./FieldTextBox.scss"

interface IProps {
    fieldKey:Key;
    doc:Document;
}

// FieldTextBox: Displays an editable plain text node that maps to a specified Key of a Document
//
//  HTML Markup:  <FieldTextBox Doc={Document's ID} FieldKey={Key's name + "Key"}
// 
//  In Code, the node's HTML is specified in the document's parameterized structure as:
//        document.SetField(KeyStore.Layout,  "<FieldTextBox doc={doc} fieldKey={<KEYNAME>Key} />");
//  and the node's binding to the specified document KEYNAME as:
//        document.SetField(KeyStore.LayoutKeys, new ListField([KeyStore.<KEYNAME>]));
//  The Jsx parser at run time will bind:
//        'fieldKey' property to the Key stored in LayoutKeys
//    and 'doc' property to the document that is being rendered
//
//  When rendered() by React, this extracts the TextController from the Document stored at the 
//  specified Key and assigns it to an HTML input node.  When changes are made tot his node, 
//  this will edit the document and assign the new value to that field.
//
export class FieldTextBox extends React.Component<IProps> {
    private _ref: React.RefObject<HTMLDivElement>;
    private _editorView: Opt<EditorView>;
    private _reactionDisposer: Opt<IReactionDisposer>;

    constructor(props:IProps) {
        super(props);

        this._ref = React.createRef();

        this.onChange = this.onChange.bind(this);
    }

    dispatchTransaction = (tx: Transaction) => {
        if(this._editorView) {
            const state = this._editorView.state.apply(tx);
            this._editorView.updateState(state);
            const {doc, fieldKey} = this.props;
            doc.SetFieldValue(fieldKey, JSON.stringify(state.toJSON()), TextField);
        }
    }

    componentDidMount() {
        let state:EditorState;
        const {doc, fieldKey} = this.props;
        const config = {
            schema,
            plugins: [
                history(),
                keymap({"Mod-z": undo, "Mod-y": redo}),
                keymap(baseKeymap)
            ]
        };

        let field = doc.GetFieldT(fieldKey, TextField);
        if(field) {
            state = EditorState.fromJSON(config, JSON.parse(field.Data));
        } else {
            state = EditorState.create(config);
        }
        if(this._ref.current) {
            this._editorView = new EditorView(this._ref.current, {
                state,
                dispatchTransaction: this.dispatchTransaction
            });
        }

        this._reactionDisposer = reaction(() => {
            const field = this.props.doc.GetFieldT(this.props.fieldKey, TextField);
            return field ? field.Data : undefined;
        }, (field) => {
            if(field && this._editorView) {
                this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field)));
            }
        })
    }

    componentWillUnmount() {
        if(this._editorView) {
            this._editorView.destroy();
        }
        if(this._reactionDisposer) {
            this._reactionDisposer();
        }
    }

    shouldComponentUpdate() {
        return false;
    }

    @action
    onChange(e: React.ChangeEvent<HTMLInputElement>) {
        const {fieldKey, doc} = this.props;
        doc.SetFieldValue(fieldKey, e.target.value, TextField);
    }

    render() {
        return (<div className="fieldTextBox-cont" ref={this._ref} />)
    }
}