LoginSignup
1
0

More than 3 years have passed since last update.

Ember.jsでstorybookを使う

Last updated at Posted at 2020-11-08

Ember.jsでもStorybookを利用することが出来ます。

参考用ソースコード:https://github.com/mk2/ember-storybook-demo

プロジェクトのセットアップ

  • ember new ...で新規プロジェクトは生成済み
  • ember-bootstrapember-bootstrap-cp-validationsember-cp-validationsはインストール済み(例示のために入れてるだけで、必須ではないです)

storybookをセットアップ

npx -p @storybook/cli sb init
ember install @storybook/ember-cli-storybook

ここまでやると、package.jsonにstorybookというタスクが追加されているので、npm run storybookで起動するはずです。

...
  "scripts":
    ...
    "storybook": "start-storybook -p 6006 -s dist --no-dll",
    "build-storybook": "build-storybook -s dist --no-dll"
...

ただし、2020/11/8時点ではnpm run storybookしてもエラーが出て起動しないので、

このissueにあるように、core-js@3.6.5を依存に追加してあげます。

npm i --save-dev core-js@3.6.5

これで起動するようになります。

開始できるようにする

storybookは起動しているemberからコンポーネントを取得するので、事前にemberを起動しておきます。

ember s

その後、storybookを起動します。

npm run storybook

これで、localhost:6006でstorybookが動くようになりました。

storyを記述していく

例: ember-bootstrapのフォーム

ember-bootstrapで作成したフォームをstorybook上に出してみたいと思います。ember-cp-validationsでバリデーションも出来るようにしてちゃんと動くかチェックします。

# `-cc`でClassic Componentスタイルのjsファイルを、hbsファイルに加えて生成する
ember g component -cc SampleForm
app/components/sample-form.hbs
<BsForm @formLayout="vertical" @model={{this}} @onSubmit={{action "submit"}} as |form|>
  <form.element
    @controlType="text"
    @label="タイトル"
    @placeholder="タイトル"
    @property="title"
    @required={{true}}
  />
  <form.element @controlType="textarea" @label="説明" @property="description" />
  <form.submitButton>
    作成
  </form.submitButton>
</BsForm>
app/components/sample-form.js
import Component from "@ember/component";
import { buildValidations, validator } from "ember-cp-validations";

const Validations = buildValidations({
  title: validator("presence", { presence: true, message: "入力が必要です" }),
});

export default Component.extend(Validations, {
  title: null,
  description: null,
  actions: {
    submit() {
      this.onSubmit(this.title, this.description);
    },
  },
});
stories/sample-form.stories.js
import { action } from "@storybook/addon-actions";
import { hbs } from "ember-cli-htmlbars";

export default {
  title: "SampleForm",
  argTypes: {},
};

export const Template = () => ({
  template: hbs`<SampleForm />`,
  context: {
    submit: () => action("onSubmit")(),
  },
});

これで画面を見ると、動いているのがわかります。

スクリーンショット 2020-11-08 16.12.16.png

ちゃんとバリデーションも動いているようです。

スクリーンショット 2020-11-08 16.12.21.png

例: 少しだけ複雑な画面

下図のような、ちょっとだけ複雑そうな画面を作ってみます。(月〜金の時間帯を選んで保存できる、という画面です)

  • 9:00~19:00の時間帯はstorybookのcontrols機能で画面上からいじれるようにする

スクリーンショット 2020-11-08 16.20.27.png

ember g component day-input
# `-gc`でOctane Componentスタイルのjsファイルを、hbsファイルに加えて生成する
ember g component -gc week-input
app/components/day-input.hbs
<BsButtonGroup @value={{@selectedHours}} @type="checkbox" @onChange={{@onChange}} as |bg|>
  {{#each @hours as |time|}}
    <bg.button @type="info" @outline={{true}} @value={{time}}>
      {{time}}
    </bg.button>
  {{/each}}
</BsButtonGroup>
app/components/week-input.hbs
<div class="row">
  <div class="mx-auto col-md-8" style="margin-bottom: 20px">
    <div class="row">
      <div class="col-md-8">
        <BsButton @type="success" @onClick={{@onSave}}>
          保存
        </BsButton>
        <BsButton @type="danger" @onClick={{@onCancel}}>
          キャンセル
        </BsButton>
      </div>
      <div class="col-md-4">
        <div class="input-group flex-nowrap">
          <div class="input-group-prepend">
            <span class="input-group-text" id="addon-wrapping">
              記入者名
            </span>
          </div>
          <input
            type="text"
            class="form-control"
            placeholder=""
            aria-label="表示名"
            aria-describedby="addon-wrapping"
            value={{@scheduleInput.name}}
            {{on "change" this.updateName}}
          />
        </div>
      </div>
    </div>
  </div>
</div>

<div class="row" {{did-insert setupHours}}>
  <div class="mx-auto col-md-8">
    <table>
      <tbody>
        <tr>
          <td></td>
          <td>
            <DayInput
              @hours={{hours}}
              @selectedHours={{@scheduleInput.monHours}}
              @onChange={{action (mut this.args.scheduleInput.monHours)}}
            />
          </td>
        </tr>

        <tr>
          <td></td>
          <td>
            <DayInput
              @hours={{hours}}
              @selectedHours={{@scheduleInput.tueHours}}
              @onChange={{action (mut @scheduleInput.tueHours)}}
            />
          </td>
        </tr>

        <tr>
          <td></td>
          <td>
            <DayInput
              @hours={{hours}}
              @selectedHours={{@scheduleInput.wedHours}}
              @onChange={{action (mut @scheduleInput.wedHours)}}
            />
          </td>
        </tr>

        <tr>
          <td></td>
          <td>
            <DayInput
              @hours={{hours}}
              @selectedHours={{@scheduleInput.thuHours}}
              @onChange={{action (mut @scheduleInput.thuHours)}}
            />
          </td>
        </tr>

        <tr>
          <td></td>
          <td>
            <DayInput
              @hours={{hours}}
              @selectedHours={{@scheduleInput.friHours}}
              @onChange={{action (mut @scheduleInput.friHours)}}
            />
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</div>
app/components/week-input.js
import { action } from "@ember/object";
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";

function generateHours(startHour, endHour) {
  return Array.from({ length: endHour - startHour + 1 }, (_, v) => v + startHour).map(
    (v) => `${v}:00`
  );
}

export default class ScheduleWeekInputComponent extends Component {
  @tracked
  hours = [];

  @action
  setupHours() {
    const startHour = this.args.schedule.startHour;
    const endHour = this.args.schedule.endHour;
    this.hours = generateHours(startHour, endHour);
  }

  @action
  updateName(e) {
    this.args.scheduleInput.name = e.target.value;
  }
}

これで画面のコードは用意できました。storybookのコードを作成します。

stories/week-input.stories.js
import { action } from "@storybook/addon-actions";
import { hbs } from "ember-cli-htmlbars";

export default {
  title: "WeekInput",
  argTypes: {
    startHour: { control: { type: "range", min: 0, max: 24, step: 1 } },
    endHour: {
      control: { type: "range", min: 0, max: 24, step: 1 },
    },
  },
};

export const Template = ({ startHour, endHour, ...args }) => ({
  template: hbs`
  <WeekInput
    @schedule={{schedule}}
    @scheduleInput={{scheduleInput}}
    @onSave={{onSave}}
    onCancel={{onCancel}} />
  `,
  context: {
    onSave: () => action("onSave")(),
    onCancel: () => action("onCancel")(),
    scheduleInput: {
      monHours: [],
      tueHours: [],
      wedHours: [],
      thuHours: [],
      friHours: [],
      satHours: [],
      sunHours: [],
    },
    schedule: {
      startHour,
      endHour,
    },
    ...args,
  },
});

Template.args = {
  startHour: 9,
  endHour: 19,
};

storybookの画面を見ると、ちゃんと動いているのがわかります。

スクリーンショット 2020-11-08 16.24.55.png

スライダーをぐりぐりすると良い感じに画面の表示がリアクティブに切り替わるのがわかります。やったね!

Nov-08-2020 16-30-19.gif

1
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
1
0