こんにちは!
2013年度新卒入社の佐伯航平と申します。
現在私はギャングソウルというチームでフロントエンドの実装を担当しています。
日々の学びを自身のサイト、「kotazi.com」にて綴ってますので、よかったらこちらも訪れてみてください。

さて、私達のチームでは開発のチャットやタスク管理のツールとして「ChatWork」を利用しています。便利なサービスですが、時々メッセージに気づかず数時間放置してしまうとすごく申し訳ない気持ちになってしまいますね。

今回はそんな自分と同じ悩みを抱える皆様のためにChatWorkのデスクトップアプリ化とGrowl通知の実装を試みたので紹介したいと思います。
$1 pixel|サイバーエージェント公式クリエイターズブログ
JavaScriptを使ってこのような通知が出せるようになります。是非読んでみてくださいね。

関連するアプリケーションの紹介

今回ChatWorkのデスクトップアプリ化を図るにあたり、必要なものを紹介します。

▼ ChatWork
クラウド型ビジネスチャットツールの「ChatWork」です。
普段のチャットはもちろんのこと、チームで共有すべきタスクやファイルを管理するツールとして、現場での開発にとても適したサービスではないかと私は思っています。

▼ Fluid
こちらが今回の肝となる「Fluid」です。
WebアプリケーションをMacのデスクトップアプリに変身させてくれる素敵なアプリです。
今回は具体的なサンプルとしてChatWorkのデスクトップアプリ化を行っていきますが、基本的にはどんなWebサービスもかかってこいだと思うので、 個人的には是非是非一押しのアプリとなっております。

▼ Growl
Macの通知といえば「Growl」一択ですね。
Growl一つで様々なアプリケーションからの通知を管理することができます。

Fluidを使ってデスクトップアプリ化をしてみる

以前、自分のブログにも書かせていただいたのですが、Fluidを用いたWebアプリのデスクトップアプリ化を行います。

Fulidを起動すると下記画像のようなウィンドウが表示されます。

URL: アプリケーションURL
Name: アプリケーションの名前
Location: アプリケーションの場所
Icon: アプリケーションの画像

といった風に作成するアプリケーションの情報を入力してみてください。
画像ではChatWork用のアプリを作成しています。
ちなみにChatWorkのアイコンはこちらから公式のものを入手することができます。

$1 pixel|サイバーエージェント公式クリエイターズブログ

試しに起動してみましょう。

専用のブラウザが立ち上がり、Chatworkが起動します。
Chromeなどで表示しておくと、タブの切替がいちいち面倒だったり誤って消してしまうこともあるので、毎日使うものや、常時起動されるようなサービスはどんどんFluid化してみてもいいのではと思います。

$1 pixel|サイバーエージェント公式クリエイターズブログ

ちなみにFluid化しておくことで、Alfredから呼び出すこともできるようになります。

$1 pixel|サイバーエージェント公式クリエイターズブログ

Growl通知ができるように、拡張してみる

さて、先ほど作成したChatWorkのデスクトップアプリにGrowlの通知機能をつけていきます。

ただ、残念なお知らせですがこちらの拡張にはFluidの有料ライセンスが必要になります。
デスクトップアプリ化までは無料でできるので、使ってみて良さそうでしたらこちらも試してみてください。

▼ AppControllerの宣言
AppControllerを作ります。今回の実装ではChatWorkの部屋のリストの状態を監視し、変化があればGrowl通知を出す。というような仕組みにしました。

/**
* コントローラ
* @constructor
*/
var AppController = function() {
this.chatRoomCollection = new ChatRoomCollection();
};

/**
* 初期化処理
*/
AppController.prototype.init = function () {
var roomListItemsEl = document.getElementById('_roomListItems'),
roomListElsArr = roomListItemsEl.childNodes;

//部屋の数だけモデルを生成する
for (var i = 0; i < roomListElsArr.length; i++){
var chatRoomModel = new ChatRoomModel(roomListElsArr[i]);
this.chatRoomCollection.push(chatRoomModel);

if (chatRoomModel.isIncompletion) {
mediator.publish('new_message', chatRoomModel);
}
}

//監視を始める
this.observeRoomListItems();
};

/**
* 監視する
*/
AppController.prototype.observeRoomListItems = function() {
var self = this;
setTimeout(function() {
self.createRoomModel();
self.observeRoomListItems();
}, 3000);
};

/**
* リストからModelを生成する
*/
AppController.prototype.createRoomModel = function() {
var roomListItemsEl = document.getElementById('_roomListItems'),
roomListElsArr = roomListItemsEl.childNodes;

for (var i = 0; i < roomListElsArr.length; i++){
var chatRoomModel = new ChatRoomModel(roomListElsArr[i]);
this.chatRoomCollection.checkUpdate(chatRoomModel);
}
};

/**
* Growlを表示する
* @param obj
*/
AppController.prototype.showGrowl = function(obj) {
window.fluid.showGrowlNotification({
title: obj.roomName,
description: "New: " + obj.unreadNum + " To: " + obj.mentionNum + " Task: " + obj.taskNum,
priority: 1,
sticky: false,
identifier: obj.roomId,
onclick: null,
icon: null // or URL string
})
};

ちなみにFluidにはwindow.fluid.showGrowlNotification()なる関数が用意されていて、こちらを利用することで簡単にGrowlを呼び出すことができます。

window.fluid.showGrowlNotification({
title: "title",
description: "description",
priority: 1,
sticky: false,
identifier: "foo",
onclick: callbackFunc,
icon: imgEl // or URL string
})

▼ ModelとCollectionを用意する
DOMの変更を検知するためには、新しいものと古いものを比較する必要があります。Modelに以下のようなパラメータを持たせて最新の部屋のリストをCollection内で管理するようにしました。

/**
* ChatRoomModel
* チャット部屋のモデル
* @param el
* @constructor
*/
var ChatRoomModel = function (el) {
this.roomId = null; //チャット部屋のユニークなID
this.roomName = ""; //チャット部屋の名前
this.roomImgURL = ""; //チャット部屋のアイコンのURL
this.isIncompletion = false; //未読 or 自分宛 or タスク があるか
this.unreadNum = 0; //未読メッセージ数
this.mentionNum = 0; //自分宛メッセージ数
this.taskNum = 0; //タスク数

if (el) {
this.setData(el);
}
};

▼ メディエータの実装
AppControllerとModelを分けるために簡易的なメディエータを入れています。
'new_message'というイベントが発行された時にGrowlの通知を呼び出すようにしております。

/**
* メディエータ
* @constructor
*/
var Mediator = function() {
this.topics = {};
};

/**
* サブスクライバ
* @param topic
* @param fn
* @returns {*}
*/
Mediator.prototype.subscribe = function(topic, fn) {
if (!this.topics[topic]) {
this.topics[topic] = [];
}
this.topics[topic].push({
context: this,
callback: fn
});
return this;
};

/**
* パブリッシュ
* @param topic
* @returns {*}
*/
Mediator.prototype.publish = function(topic) {
var args;
if (!this.topics[topic]) {
return false;
}
args = Array.prototype.slice.call(arguments, 1);
for (var i = 0, len = this.topics[topic].length; i < len; i++) {
var subscription = this.topics[topic][i];
subscription.callback.apply(subscription.context, args);
}
return this;
};

ざっと説明させていただきましたが、GitHubの方に実際のコードを上げておいたので宜しければ御覧ください。

実際に使う

では最後に、先ほどのスクリプトの使い方を説明します。

まずFluidで作成したChatWorkを起動していただき、
メニューバーからWindow => Usescriptsを選択してください。

すると図のようなウィンドウが表示されると思います。
左カラムの部分に「chatwork_growl」などと(何でもいいです)タイトルを作成していただき、pattern部分にChatWorkのURLを追加します。そして右下のボックスに先ほどのスクリプトを追加すれば完成です!

$1 pixel|サイバーエージェント公式クリエイターズブログ

こんな形で新規メッセージ数の確認ができるようになります。
$1 pixel|サイバーエージェント公式クリエイターズブログ

最後に

最後まで読んでいただきどうもありがとうございました!

作ってみたら、じきにChatWorkのAPIが公開されるとのことで少々焦りました。笑
でも、APIの公開をとても楽しみにしています。

チームでの開発にあたり、様々な人とのコミュニケーションは欠かせないものです。それが全ての人にとって快適で楽しいものになればとても素晴らしいことではないかと考えています。

最後になりますが、私は現場で毎日たくさんのことを学ばせてもらっております。
せっかく教えていただいたことを忘れぬよう日々ブログを綴っていますので、よかったらのぞいてみてください。

- kotazi.com

今後もチームとともに成長し、よりよいサービスを提供できるよう努めていきます。
ありがとうございました!