0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GROWIでフォームを表示するプラグインを作りました

Posted at

オープンソースのWikiであるGROWIにはプラグイン機能が用意されています。自社のデータを表示したり、表示をカスタマイズするのに利用できます。

今回は、GROWIプラグインとしてフォームを表示するプラグインを作りました。GROWIページで、お問い合わせフォームのような仕組みを実現できます。

image.png

プラグインの動作

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
        }
    ]
}
:::

上記の場合、以下のようなフォームが表示されます。

image.png

オプションとして、入力した内容を保存するページのパスと、そのページの読み取り権限を持つグループなどが指定できます。たとえば問合せフォームの場合は、 path=/contact と指定して、 role=Admin などとして Admin グループに所属するユーザーだけが閲覧できる形にすると良いでしょう。

パラメータ名 説明
path 保存するページのパス
role 閲覧権限のグループ
message 保存後のメッセージ
submit 送信ボタンのラベル

保存データは、入力値をYAMLに変換した形になります。

プラグインを追加する

利用する際には、GROWIの管理画面の プラグイン にて追加してください。URLは https://github.com/goofmint/growi-plugin-form です。

Admin

コードについて

コードは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 Slackへの参加はこちらから

まとめ

GROWIプラグインを使うと、表示を自由に拡張できます。足りない機能があれば、どんどん追加できます。ぜひ、自分のWikiをカスタマイズしましょう。

OSS開発wikiツールのGROWI | 快適な情報共有を、全ての人へ

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?