オープンソースのWikiであるGROWIにはプラグイン機能が用意されています。自社のデータを表示したり、表示をカスタマイズするのに利用できます。
今回は、GROWIプラグインとしてフォームを表示するプラグインを作りました。GROWIページで、お問い合わせフォームのような仕組みを実現できます。
プラグインの動作
Remark Directiveとして、以下のように記述します。フォームの内容はJavaScript Powered Forms and Form.io SDKを使って作成します。
:::form[contact]{}
{
"display": "form",
"settings": {
"pdf": {
"id": "1ec0f8ee-6685-5d98-a847-26f67b67d6f0",
"src": "https://files.form.io/pdf/5692b91fd1028f01000407e3/file/1ec0f8ee-6685-5d98-a847-26f67b67d6f0"
}
},
"components": [
{
"label": "Text Field",
"applyMaskOn": "change",
"tableView": true,
"validateWhenHidden": false,
"key": "textField",
"type": "textfield",
"input": true
}
]
}
:::
上記の場合、以下のようなフォームが表示されます。
オプションとして、入力した内容を保存するページのパスと、そのページの読み取り権限を持つグループなどが指定できます。たとえば問合せフォームの場合は、 path=/contact
と指定して、 role=Admin
などとして Admin
グループに所属するユーザーだけが閲覧できる形にすると良いでしょう。
パラメータ名 | 説明 |
---|---|
path | 保存するページのパス |
role | 閲覧権限のグループ |
message | 保存後のメッセージ |
submit | 送信ボタンのラベル |
保存データは、入力値をYAMLに変換した形になります。
プラグインを追加する
利用する際には、GROWIの管理画面の プラグイン
にて追加してください。URLは https://github.com/goofmint/growi-plugin-form
です。
コードについて
コードはgoofmint/growi-plugin-form: GROWI form pluginにて公開しています。ライセンスはMIT Licenseになります。
最初に :::form
というRemark Directiveを処理します。この記述があれば、 code
タグに変換します。他、 {}
内で指定した情報は title
に設定します。また、 .language-form
を追加して、他の code
タグとの区別をつけています。
vist
メソッドで、RemarkのAST containerDirective
を処理します。2つ目の引数を指定すると、そのディレクティブの場合のみ呼び出されるので便利です。
export const remarkPlugin: Plugin = () => {
return (tree: Node) => {
visit(tree, 'containerDirective', (node: Node) => {
const n = node as unknown as GrowiNode;
if (n.name !== 'form') return;
const id = (n.children[0] as GrowiNode).children[0].value;
const value = (n.children[1] as GrowiNode).children.map(ele => ele.value).join('');
const data = n.data || (n.data = {});
// Render your component
try {
JSON.parse(value);
data.hName = 'code'; // Tag name
data.hProperties = { id, class: 'language-form', title: JSON.stringify(n.attributes) }; // Properties
data.hChildren = [{ type: 'text', value }]; // Children
}
catch (err) {
console.log(err);
// console.error(err);
return;
}
});
};
};
code
タグを表示する際には、 .language-form
の指定があれば、<growi-form />
で表示します。それ以外の場合は、元々のタグを表示します。
<growi-form />
タグは、Stehcil.jsを使って作成したWebコンポーネントです。
export const helloGROWI = (Tag: React.FunctionComponent<any>): React.FunctionComponent<any> => {
return ({ children, ...props }) => {
try {
if (props.className === 'language-form') {
const {
submit, path, role, message,
} = (props.title && props.title !== '')
? JSON.parse(props.title)
: {
submit: 'Submit', path: '', role: '', message: '',
};
return (
<>
<growi-form
code={children}
path={path}
submit={submit}
role={role}
message={message}
>
</growi-form>
</>
);
}
// your code here
// return <>Hello, GROWI!</>;
}
catch (err) {
// console.error(err);
}
// Return the original component if an error occurs
return (
<Tag {...props}>{children}</Tag>
);
};
};
growi-formタグの動作
growi-formタグでは、コンポーネントをロードしたタイミング connectedCallback
で、GROWIのJavaScript SDKを初期化します。その後、 getHtml
メソッドでフォームを表示します。
Formio.create
で、指定されたDOM <growi-form><div></div></growi-form>
にフォームを表示します。フォームの内容は、Remark Directiveで指定したJSONを使います。フォームの内容には、送信ボタンを追加しています。
@Component({
tag: 'growi-form',
styleUrls: [
'./growi-form.css',
'../../../node_modules/formiojs/dist/formio.embed.min.css',
'../../../node_modules/bootstrap/dist/css/bootstrap.min.css',
],
shadow: true,
})
export class GrowiForm {
growi: GROWI;
@Element() el: HTMLElement;
/**
* The name
*/
@Prop() name: string;
@Prop() path: string;
@Prop() role: string;
@Prop() message: string;
@Prop() submit: string;
@Event() saved: EventEmitter<string>;
/**
* The parametar1
*/
@Prop() code: string;
componentDidRender(): void {
this.getHtml();
return;
}
connectedCallback(): void {
this.growi = new GROWI({ url: window.location.origin });
}
private getHtml = async(): Promise<void> => {
const { Formio } = window;
const ele = this.el.shadowRoot.querySelector('div');
const code = JSON.parse(this.code);
code.components.push({
label: this.submit || 'Submit',
showValidations: false,
tableView: false,
key: 'submit',
type: 'button',
input: true,
saveOnEnter: false,
});
const form = await Formio.createForm(ele, code, {
noAlerts: true,
disableAlerts: true,
submitMessage: '',
});
form.on('submit', async(submission: any) => {
// 後述
});
};
render(): JSX.Element {
return (<div></div>);
}
}
Tips
Formioでは、現状Bootstrap4のみ対応しています。GROWIのBootstrapとはバージョンが合わないため、デザインが適用されません。
Stencil.js(というかWeb Components)では、デザインをコンポーネント内だけに留められるので、本プラグインの中だけBootstrap4になっています。
@Component({
tag: 'growi-form',
styleUrls: [
'./growi-form.css',
'../../../node_modules/formiojs/dist/formio.embed.min.css',
'../../../node_modules/bootstrap/dist/css/bootstrap.min.css',
],
shadow: true,
})
フォームの送信処理
フォームの送信処理は form.on('submit')
が呼ばれます。ここでは入力内容を受け取って、GROWIの指定されたページ以下に現在日時をページ名として、新しいページを保存します。
以下の処理でグループを取得 growi.groups()
していますが、これはAPIトークンを使っての取得はできません。セッションを利用しているので、プラグイン内部でしか現状では使えないメソッドになります。
form.on('submit', async(submission: any) => {
// 入力された内容
const { data } = submission;
// ロールを取得
const role = (await this.growi.groups()).groups.find(group => group.name === this.role);
// ページを取得
const page = await this.growi.root();
const newPage = await page.create({
name: `${this.path}/${(new Date()).toUTCString()}`,
});
// ページの内容
newPage.contents(`\`\`\`yaml\n${YAML.stringify(data)}\n\`\`\``);
await newPage.save(role ? {
grant: 5,
userRelatedGrantUserGroupIds: [{
type: 'UserGroup',
item: role.id,
}],
} : null);
// スピナーが消えないため、強制削除
const spinner = this.el.shadowRoot.querySelector('i.spinner-border.spinner-border-sm.button-icon-right');
if (spinner) spinner.remove();
// メッセージが指定されていれば、表示
if (this.message && this.message !== '') {
const alert = document.createElement('div');
alert.className = 'alert alert-success';
alert.innerText = this.message;
form.element.appendChild(alert);
setTimeout(() => {
form.resetValue();
alert.remove();
}, 5000);
}
else {
form.resetValue();
}
// this.saved.emit(newPage.path);
});
これで、問い合わせ内容を指定したページ内に保存する流れができあがります。
GROWIコミュニティについて
プラグインの使い方や要望などがあれば、ぜひGROWIコミュニティにお寄せください。実現できそうなものがあれば、なるべく対応します。他にもヘルプチャンネルなどもありますので、ぜひ参加してください!
まとめ
GROWIプラグインを使うと、表示を自由に拡張できます。足りない機能があれば、どんどん追加できます。ぜひ、自分のWikiをカスタマイズしましょう。