Articles tagged with: カスタマイズ

Ship Vol.2に参加してきました


2023年12月8日に『Ship Vol.2』に参加してきました。

会場はサイボウズ社本社。前日のMOVED MeetUpも同じ場所だったので、二日連続の訪問です。

今回のShipへの参加は私にとって初めてです。4/21にVol.1が催された際は、私に参加する余裕がなかったため、弊社のメンバーに代わりに参加してもらいました。

あれから約8カ月近く経ち、弊社の状況にも変化が生じました。
そして私自身の考えも変化しました。

どう変わったかと言うと、kintoneシステム開発においてJavaScriptを主に使ったカスタマイズの限界を悟りました。

つまり、もう、案件の数が増えすぎて、JavaScriptだけで対応しきれなくなりました。
そもそもkintone自体が、容易に変化するお客様の仕様に柔軟に対応することを前提にしています。kintoneが変わるなら、カスタマイズ要件も頻繁に変わります。
どれだけ要件定義を入念に行ってもそれは変わりません。お客様自身が確定させたつもりの仕様も、社会情勢の変動によって変更を余儀なくされます。しかもその速度はますます速まっている昨今です。
kintoneシステム開発のやり方もアジャイル開発の考えが主流になっている今、いちど決まった要件が覆らないかつての常識は通用しません。
そして、JavaScript開発ではどうしてもその速度においてプラグインにかないません。つまり、JavaScript開発を続けていると、いつまでたっても生産性が上がりません。

弊社はJavaScriptで頑張ってやってきました。うちのメンバーもJavaScript開発では多層Promiseを扱った実装もこなしてくれるようになっています。
が、今年に入ってエンジニアの育成が必要になった途端、JavaScriptを書く前提の育成の速度ではプラグインに及ばない現実にやられました。
ということは、JavaScriptでやり切れるとすれば、最初からスキルのあるエンジニアを抱えている場合のみです。
この半年でその現実を痛感しました。

JavaScriptに成り代わり、kintoneのカスタマイズを行う手段はいろいろとあります。大きく分けるとプラグインか外部サービスでしょうか。
外部サービスは著名なところではgusuku Customineやトヨクモシリーズがあります。
一方、プラグインは有償無償を問わず、各社様から多種多様のプラグインが提供されています。

krewDataやkrewSheetは、一つのプラグインだけで、アプリ連携やデータ連携処理を何重にもつなげて行えることが特徴です。
と、偉そうなことを書いていますが、実は私がkrewDataを触り始めたのはつい最近です。この半年どころか、2カ月以内です。

gusuku Customineは5、6年ほど前から触っていたにもかかわらず、krewDataを触るのが遅れたのは、弊社のお客様が使っていらっしゃらなかったことが理由です。

ところが、今年になって二つの案件でkrewDataを扱うことになり、私も実際に触ることになりました。そして、その機能に舌を巻きました。

実際、krewDataはよくできていると思います。
フローをつなげていき、上流の設定値が変わったら、連結される下流のフローに一気に反映する点もなかなか魅力的です。
慣れてくると、JavaScriptよりもかなり簡単です。

正直にいうと、krewDataで実現できることはJavaScriptでも書けます。フローの連携もできます。
ところが、案件によっては、複雑な連携を何十にも連ねる実装をJavaScriptで書こうとするとかなりのコード量になるでしょう。
その中のどこか1カ所でもPromiseのReturnを置く位置を間違えれば、またはResolveやRejectの値を間違えれば、途端によくない結果を招きます。

また、せっかく実装してもお客様側の仕様が変更になると、その全てがやり直しになってしまいます。
krewDataは、そうしたコーディングにまつわるあらゆる手間を省いてくれます。私がkrewDataの機能に舌を巻いたのもそれが理由です。

上記のようないきさつをへて変化しつつあった私の心を待っていたかのように、今回、Ship開催のアナウンスがありました。
そして、私自身がまだグレープシティ、現在のメシウス社の文化や社風を十分に理解していません。
そこで、今回はShipに参加しようと思いました。
ところが、私の調整がなかなかつかずにいるうちに、ページからShipが申し込めなくなってしまいました。

今回は運営するメシウス様のご好意で、後から追加枠として入れていただけることになりました。感謝いたします。

さて、今回はサイボウズ社27Fに繁るシンボル「サイボウ樹」の前に集ってのセミナー形式。私にとってこの形式は久々です。


最初にメシウス社の佐藤さんと大江さんより開会の挨拶。


そして、株式会社ZOZOの新井さん(あーけん)からは「krewDataのすゝめ」。
ふむ、Char関数はVBAを扱う際にはよく使いますが、krewDataでも使えるんですね。
そして、膨大なデータチェック自体もkrewDataだけで完結できる可能性を見せてもらえたのは、まさにイベントならではの気づきです。あーけんさんに感謝です。


続いて、NPO法人チャリティーサンタの青山さんから「全国のサンタクロースを支えるkintone 開発」。
こちらは主にkrewSheetをお使いの事例でした。
krewSheetは、私も以前からお客様の案件によっては導入したりして使っていました。
Excelからkintoneにチャレンジしたお客様に、高いハードルを与えずにkintoneになじんでもらう際、krewSheetは最適なツールです。
krewSheetを使って運用を直観的にしながら、集計もkrewDataを効果的に使っていらっしゃる良い事例でした。
また、チーム応援ライセンスが適用できるユーザーはkrewシリーズが安価に使えるプランがあることを私はあまり認識していませんでした。この可能性を知った事も、ありがたい情報でした。青山さんに感謝です。


続いて、マルテー大塚の石井さんより「キントーンとアクセスの話」。
上に書いた通り、私はkrewDataを使っていて、kintoneではできなかったSQLを活用した可能性に驚きました。
kintoneではOUTER JOINもUNIONもできません。Group ByもHavingもそれを使ったデータ加工やアプリ連携をするにあたってはJavaScriptやAlaSQLなどのライブラリが必要です。
石井さんのセッションでは、こうしたSQLを起点としたさまざまな事例を取り上げてくださいました。
私もそうですが、RDBやSQLに慣れている者にとってkintoneの癖と呼べる点こそ、このSQL的なアプローチができない点です。
技術者にさらにkintoneを使っていただくうえで、石井さんのセッションは参考になるはずです。石井さん、ありがとうございました。


続いてメシウス社の佐藤さんより「krewのマーケチームではこうやって予実管理してます」。
これも、Excelでは直感的にできていたのに、kintoneでは直感的な操作ができないと考える人への好事例ですね。
こうした事例は、最終的なデータ加工や見せ方に関する部分なので、私もそこまで積極的にお客様に提案してこなかったのですが、より最後の一塩の意味をこめて、今後は提案していきたいと思いました。
佐藤さんには今回追加枠のご案内もいただきました。それも含めてありがとうございました。


さて、その後はkrewラベル付きのビールやジュースが配られ、交流タイムです。
5つほど属性ごとに分かれて集まってもらい、そこで輪になって語ってもらう趣旨がアナウンスされました。
さて、私の属性はkintone初心者ではないし、情シスでもありません。どちらかといえばkrewData初心者のようなもの。近くにたまたま近くにいたいた中尾さんと、どのグループに行きましょうかね~とのんきに話していたら、いつのまにかグループが定まっていました。なので、私は中尾さんに加え、同じようにはみ子になってしまったお二方と四人で話をしていました。


なるほど、こういう運営方法なのですね。理解できたところで、次の懇親会第二部の時間になりました。
ところが私はこの日の夜、横浜で忘年会の約束がありました。そのため、ここで辞去しました。

雰囲気も把握したし、また次の機会があれば参加したいと思います。メシウスさん、参加者の皆さん、ありがとうございました。


Cybozu OfficeのスケジュールをCalendar Plusで(だいぶ)再現!


カレンダーPlus Advent Calendar 2020の5日目の記事です。

  Topへ↓

先日のCybozu Days 2020 Osakaの後、Calendar PlusのAdvent Calendarに空きがあると聞き、やってみましょうか?と言ってしまいまして。
言った以上はエントリーせねば。というわけで翌日、神戸で商談する前に中華街で飯を食べながら記事にエントリー宣言。

さて、エントリーしたのはいいが、何を書けばええんやろ?しかもエントリーしたタイミングは前日やし(^^;)。これはひょっとして勇み足をやらかしてしまったのではなかろうか。

迷いを振り切った私が商談に臨むと、なんとお客様がCybozu Officeのスケジュールを愛用しており、それをkintoneに載せ替えたいとのこと。しかもkintone標準の標準のカレンダー機能は表現に乏しく、Calendar Plusを検討してみたけど、プラグイン設定の色設定の種類が少なくてなぁ。と悩みを吐露されまして。商談からネタが転がってきた瞬間です。
これはCybozu Officeのスケジュールアプリの画面です。

私は今までも何回かCalendar Plusは提案してきました。けれど、本格的なユーザーではないし、弊社のkintone納入実績からするとCalendar Plusの納品実績は一割もありません。熟練のユーザーではないのです。

が、Calendar Plus JavaScript APIを使ったカスタマイズの経験はあります。API使えば、ある程度ならCybozu Officeの体裁にCalendar Plusを合わせられるんちゃうの?という見切り発車でした。

Calendar Plusのプラグイン設定画面

  Topへ↑

うーむ、確かにCalendar Plusの色設定は12種類か。

そして、お客様のCybozu Officeのスケジュールの色設定で設けた種別は25種類。つまり倍。

確かにお客様がkintoneへの移行をためらわれるのもわかるなあ。

アプリを作る

  Topへ↑

じゃあ、ちょっと試しにアプリを作ってみよう。こんな感じで。

あらよっと、Cybozu Officeと同じようにレコードを登録してみました。さて、Calendar Plusで見るとどうなる・・・?

あかん・・・Calendar Plus側の色設定をしていないのですべて同じ緑地になってる。
しかも予定の状況がカレンダー上に表示されておらず、直行なのか直帰なのかが一目で区別できない。
やはり見にくいなあ・・・・これは、Calendar Plus JavaScript APIの出番かな。

コーダーモード発動

  Topへ↑

ということで、ガリガリとコーディングすること2時間。うん、こんな感じになりました。


というわけで最低限、記事にできるところまでこぎつけたのでいったん寝ます。アップ当日になってしまいましたが、AM2時前なのにもう眠い。年や。

プログラム開示

  Topへ↑
さて、アップ当日の朝、旅に出かける前に少しだけ記事を書きまして。
ここにコードを開示しておきます。すみません。洗練されたコードとは程遠くて・・・・。

(function () {
  "use strict"

  // 関数本体
  const calenderformat = function (event) {
    // Calendar Plusのイベントリスナー定義
    const calendareventslist = ["cp.event.show"];
    // kintoneのイベントの書き方と似ててわかりみ
    calendarplus.events.on(calendareventslist, function (calendarevent) {
      // kintoneのイベントのjson(上記イベントで対象データループしてくれます)
      const record = calendarevent.record;
      // kintoneのイベントの要素
      const element = calendarevent.element;
      // テーブルだけkintoneのイベントの要素
      const tdelement = document.getElementsByClassName("fc-event-container");
      // Cybozu Officeの時刻表示に合わせるため(ださいかも)
      const starttime =
        ("00" + new Date(record.開始時刻.value).getHours()).slice(-2) +
        ":" +
        ("00" + new Date(record.開始時刻.value).getMinutes()).slice(-2);
      const endtime =
        ("00" + new Date(record.終了時刻.value).getHours()).slice(-2) +
        ":" +
        ("00" + new Date(record.終了時刻.value).getMinutes()).slice(-2);
      // 左端の行高がCalendar Plusでは縮まるため、Cybozu Officeの行高っぽくする
      tdelement[0].style.minHeight = "90px";
      tdelement[1].style.minHeight = "90px";
      tdelement[2].style.minHeight = "90px";
      tdelement[3].style.minHeight = "90px";
      tdelement[4].style.minHeight = "90px";
      // リンクするためのurl
      const baseurl =
        kintone.api.url("/k/").replace(".json", "") +
        kintone.app.getId() +
        "/show#record=" +
        record.$id.value;
      // 予定の状況にあわせてSwitch(ださい)
      switch (record.予定の状況.value) {
        case "ーーー":
          // 枠の背景色
          element[0].style.backgroundColor = "#FFFFFF";
          // 文字色(本当はhoverとかの定義も必要。そもそもcssにまとめたほうがよい)
          element[0].style.color = "#006bac";
          // 折り返してくれなかったので折り返し(そもそもcssにまとめたほうがよい)
          element[0].style.whiteSpace = "break-spaces";

          // 終日フラグかどうか
          if (record.終日.value.length > 0) {
            // 頭に・をつける
            element[0].innerText = "・" + element[0].innerText;
          } else {
            // 頭に時刻をつけて改行
            element[0].innerHTML =
              starttime +
              "-" +
              endtime +
              "
 " +
              '<a href="' +
              baseurl +
              '">' +
              record.予定.value +
              "</a>";
          }
          break;
        case "直行":
          // 枠の背景色
          element[0].style.backgroundColor = "#ebf7ef";
          // 枠の線色
          element[0].style.borderColor = "#d8f2e0";
          // 文字色(本当はhoverとかの定義も必要。そもそもcssにまとめたほうがよい)
          element[0].style.color = "#006bac";
          // 折り返してくれなかったので折り返し(そもそもcssにまとめたほうがよい)
          element[0].style.whiteSpace = "break-spaces";
          // 終日フラグかどうか
          if (record.終日.value.length > 0) {
            // 頭に・をつけ、予定の状況を枠で囲む
            element[0].innerHTML =
              "・<span style="background-color:#32a759;color:#fff;" class="scheduleMarkEventMenu">" +
              record.予定の状況.value +
              "</span>" +
              record.予定.value;
          } else {
            // 頭に時刻をつけ、予定の状況を枠で囲む(以下コメントは省略)
            element[0].innerHTML =
              starttime +
              "-" +
              endtime +
              "
 " +
              '<a href="' +
              baseurl +
              '">' +
              '<span style="background-color:#32a759;color:#fff;" class="scheduleMarkEventMenu">' +
              record.予定の状況.value +
              "</span>" +
              record.予定.value +
              "</a>";
          }
          break;
        case "直帰":
        case "直行/直帰":
          element[0].style.backgroundColor = "#e8f2fc";
          element[0].style.borderColor = "#dae8f7";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#3182dc;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "転送":
        case "納品":
        case "設定":
        case "設置":
        case "移設":
          element[0].style.backgroundColor = "#f4fce8";
          element[0].style.borderColor = "#e5f2d3";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#83cb26;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "点検":
          element[0].style.backgroundColor = "#fff4e3";
          element[0].style.borderColor = "#fae8cd";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#ef9201;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "デモ":
        case "営業":
        case "商談":
        case "打合せ":
        case "来客":
          element[0].style.backgroundColor = "#faf6f2";
          element[0].style.borderColor = "#f2e9df";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#c3a88b;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "当番":
          element[0].style.backgroundColor = "#fcfae8";
          element[0].style.borderColor = "#f2efd3";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#dfc506;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "休み":
        case "有休":
        case "リフ":
        case "代休":
          element[0].style.backgroundColor = "#ffebeb";
          element[0].style.borderColor = "#fcdede";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#f44848;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "午前半休":
        case "午後半休":
          element[0].style.backgroundColor = "#fff2f5";
          element[0].style.borderColor = "#f7e9d2";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#f3a4b4;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "土曜当番出勤":
        case "出張":
        case "会議":
          element[0].style.backgroundColor = "#f5edfc";
          element[0].style.borderColor = "#ebe1f5";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#b592d8;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "土曜振休":
          element[0].style.backgroundColor = "#ffebeb";
          element[0].style.borderColor = "#fcdede";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#f44848;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
      }
      return calendarevent;
    });
  };
  // kintone自体のイベント
  const eventslist = ["app.record.index.show"];
  kintone.events.on(eventslist, (event) => {
    // ビューが一致したら本体の関数呼び出し
    if (event.viewId === 5543487) {
      calenderformat(event);
    }
    return event;
  });
})(jQuery);

あと、cssも全体のフォントに関する設定と予定の状況の枠に対して設定しています。

div#calendarPlus {
    font-family:"ヒラギノ角ゴ Pro W3", "Hiragino Kaku Gothic Pro", ヒラギノ角ゴシック, "Hiragino Sans", メイリオ, Meiryo, "MS Pゴシック", "MS PGothic", sans-serif;
    font-size:14.4px;
    font-weight:400;
}
    
div#calendarPlus span.scheduleMarkEventMenu{
    display: inline-block;
    padding: 1px 2px;
    color: #fff;
    margin: 0 1px 2px 1px;
    line-height: 1.2em;
    border-radius: 2px;
    font-size: 12px;
    vertical-align: middle;
}
    

まとめ

  Topへ↑

てなわけで、日中は記事のことは一切忘れて徳島のあちこちをめぐっておりました。で、21時前に実家に帰ってきました。
では、早速記事の続きを。うん、あらためて確認しても表示はこんな感じで大丈夫かと。

本当は実装すべきなのは週だけでなく、他の日や月でも同じように表示できるか試さねばならないですね。また、Cybozu Officeのスケジュールアプリの日時と曜日の行見出しの部分はCalendar Plusでは再現していません。
まあこんな感じですが、記事の主旨は満たせたと判断し、今宵はここまでにしてアップしたいと思います。

あとは、金曜日に商談したお客様がこの実装でご満足いただけるかどうかでしょうか。なにしろ、お客様に商談の御礼のメールを送るより先に、こちらの記事をアップしてしまったので(^^;)。おいおい。

本稿がCybozu Officeからkintoneへの移行をお考えの皆様にとって少しでも手助けになれば幸せです。

もしお困りの際は弊社までご連絡くださいませ。

当エントリーの参考にさせていただいたブログ

  Topへ↑

最後になりましたが、このエントリー作成にあたり、以下のサイトからの情報を参考にさせていただきました。
ラジカルブリッジの斎藤さんにはいつも感謝です。内容にやばい内容が含まれていたら直しますね。
ありがとうございました。

 Calendar Plus JavaScript APIのリファレンス