ThingWorx ウィジェット開発 その4 機能を拡張する

#はじめに

この記事は、「ThingWorx ウィジェット開発 その3 マッシュアップへの組み込み」からの続きです。

##目次
- [ThingWorx ウィジェット開発 その1 環境を構築する](https://qiita.com/kanoloa/items/374281b7b6d3536795c9)
- [ThingWorx ウィジェット開発 その2 アナログ時計を作る 前編](https://qiita.com/kanoloa/items/e2c58eb2e223c460b5d7)
- [ThingWorx ウィジェット開発 その2 アナログ時計を作る 後編](https://qiita.com/kanoloa/items/24e99452f8c9109f42e4)
- [ThingWorx ウィジェット開発 その3 マッシュアップへの組み込み](https://qiita.com/kanoloa/items/2ba1bb1cb7fb70e2fc16)
- [ThingWorx ウィジェット開発 その4 機能を拡張する ← 本記事](https://qiita.com/kanoloa/items/41750d322868e8f2e501)


さて、これまでにアナログ時計の機能を持った Analog Clock ウィジェットを作成する方法を見てきました。今回は作ったアナログ時計をアラーム時計に進化させます。アラーム時計としての仕様は下記の通りです。

- アラーム時刻をセットする
- アラーム状態を継続する期間をセットする
- アラーム時刻になると、イベントを発行する
- アラーム時刻になると、文字盤を赤に変える
- アラーム状態を解除する時間になったら、通常の文字盤に戻す

さっそくコードを見ていきましょう。

#第十二歩：マッシュアップビルダー用の改造

今回も先にコードの全体を見渡してみましょう。

```analog_clock.ide.js
TW.IDE.Widgets.analog_clock = function () {

	this.widgetIconUrl = function() {
		return  "'../Common/extensions/AnalogClock/ui/analog_clock/default_widget_icon.ide.png'";
	};

	this.widgetProperties = function () {
		return {
			'name': 'Analog Clock',
			'description': '',
			'category': ['Common'],
			'isResizable': true,
			'supportsAutoResize': true,
			'properties': {
				'date' : {
					'baseType': 'DATETIME',
					'isBindingTarget' : true,
					'isVisible': true
				},
				'alarm date': {
					'baseType': 'DATETIME',
					'isBindingTarget': true,
					'isVisible': true
				},
				'alarm': {
					'baseType': 'BOOLEAN',
					'defaultValue': false,
					'isEditable': true,
					'isBindingTarget': true,
					'isVisible': true
				},
				'reset after': {
					'baseType': 'INTEGER',
					'defaultValue': 300,
					'isEditable': true,
					'isBindingTarget': true,
					'isVisible': true
				},
				'Width': {
					'description': TW.IDE.I18NController.translate('tw.textbox-ide.properties.width.description'),
					'defaultValue': 120,
					'isEditable': true,
					'isVisible': true
				},
				'Height': {
					'description': TW.IDE.I18NController.translate('tw.textbox-ide.properties.height.description'),
					'defaultValue': 120,
					'isEditable': true,
					'isVisible': true
				}
			}
		}
	};

	this.afterSetProperty = function (name, value) {
		var thisWidget = this;
		var refreshHtml = false;
		switch (name) {
			case 'Style':
			case 'Width':
				this.setProperty('Height', value);
			case 'Height':
				this.setProperty('Width', value);
			case 'Alignment':
				refreshHtml = true;
				break;
			default:
				break;
		}
		return refreshHtml;
	};

	this.renderHtml = function () {
		// return any HTML you want rendered for your widget
		// If you want it to change depending on properties that the user
		// has set, you can use this.getProperty(propertyName).
		return 	'<div class="widget-content widget-analog_clock">' +
					'<img class="aClock" src="../Common/extensions/AnalogClock/ui/analog_clock/image/AnalogClock_Back.svg"> ' +
					'<img class="aClock aClock_shortArm" src="../Common/extensions/AnalogClock/ui/analog_clock/image/AnalogClock_ShortArm.svg" />' +
					'<img class="aClock aClock_longArm" src="../Common/extensions/AnalogClock/ui/analog_clock/image/AnalogClock_LongArm.svg" />' +
					'<img class="aClock" src="../Common/extensions/AnalogClock/ui/analog_clock/image/AnalogClock_Center.svg" />' +
				'</div>';
	};

	this.afterRender = function () {
		// NOTE: this.jqElement is the jquery reference to your html dom element
		// 		 that was returned in renderHtml()
	};
	
	this.widgetEvents = function() {
		return {
			'Alarmed': {}
		};
	};
	

};
```

変更点として、``alarm date``・``alarm``・``reset after``という三つのプロパティを新たに定義しました。これらのプロパティの使い道は下記の通りです。

**alarm date**
DATETIME 型。ここに指定した日付・時間になると、Analog Clock ウィジェットをアラーム状態にする。

**alarm**
BOOLEAN 型。true を返すときには、アラーム状態にする。false を返す時には、たとえ``alarm date``がセットされていてもアラーム状態にしない。

**reset after**
INTEGER 型。ここに指定した秒数を経過すると、アラーム状態を解除する。

また、下記のコードで、このウィジェットが``alarmed``というイベントを発行することを指定しています。

```analog_clock.ide.js
	this.widgetEvents = function() {
		return {
			'Alarmed': {}
		};
	};
```

analog_clock.ide.js の変更点は以上ですね。最終的にウィジェットをインポートした後のプロパティペインは下図のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119047/d6fe105d-407d-3f05-a123-08921f9dc7bb.png)


#第十三歩：ランタイム用の改造

次に、ランタイム用のコードを見ていきましょう。まずは全体を。

```analog_clock.runtime.js
TW.Runtime.Widgets.analog_clock= function () {
	var shortArm;
	var longArm;
	var hour;
	var minute;
	var second;
	var basePlate;
	var shortArmRotate;
	var longArmRotate;
	var currentDate;
	var alarmDate;
	var isAlarmSet;
	var hasGoneOff;
	var resetAfter;
	
	this.renderHtml = function () {
		return 	'<div class="widget-content widget-analog_clock">' +
					'<img class="aClock aClock_base"     src="../Common/extensions/AnalogClock/ui/analog_clock/image/AnalogClock_Back.svg" />' +
					'<img class="aClock aClock_shortArm" src="../Common/extensions/AnalogClock/ui/analog_clock/image/AnalogClock_ShortArm.svg" />' +
					'<img class="aClock aClock_longArm"  src="../Common/extensions/AnalogClock/ui/analog_clock/image/AnalogClock_LongArm.svg" />' +
					'<img class="aClock" src="../Common/extensions/AnalogClock/ui/analog_clock/image/AnalogClock_Center.svg" />' +
				'</div>';
	};

	this.afterRender = function () {
		basePlate = this.jqElement.find('.aClock_base');
		shortArm = this.jqElement.find('.aClock_shortArm');
		longArm = this.jqElement.find('.aClock_longArm');
		alarmDate = this.getProperty('alarm date');
		isAlarmSet = this.getProperty('alarm');
		hasGoneOff = false;
		resetAfter = this.getProperty('reset after');
	};

	this.updateProperty = function (updatePropertyInfo) {
		
		if (updatePropertyInfo.TargetProperty === 'alarm') {
			if (updatePropertyInfo.SinglePropertyValue === 'false') {
				isAlarmSet = false;
			} else {
				isAlarmSet = true;
			}
			this.setProperty('alarm', isAlarmSet);
		}
		
		if (updatePropertyInfo.TargetProperty === 'alarm date') {
			alarmDate = new Date(updatePropertyInfo.SinglePropertyValue);
			this.setProperty('alarm date', alarmDate);
		}
		
		if (updatePropertyInfo.TargetProperty === 'reset after') {
			resetAfter = parseInt(updatePropertyInfo.SinglePropertyValue, 10);
			this.setProperty('reset after', resetAfter);
		}
		
		if (updatePropertyInfo.TargetProperty === 'date') {
			
			currentDate = new Date(updatePropertyInfo.SinglePropertyValue);
			hour = currentDate.getHours();
			minute = currentDate.getMinutes();
			second = currentDate.getSeconds();
			
			if (hour > 11) {
				hour = hour - 12;
			} else if (!hour) {
				hour = 0;
			}
			
			if (minute === '60') {
				minute = 0;
			} else if (!minute) {
				minute = 0;
			}
			
			shortArmRotate = (hour * 30) + (minute * 0.5);
			longArmRotate = (minute * 6) + (second * 0.1);

			shortArm.css('transform', 'rotate(' + shortArmRotate + 'deg)');
			longArm.css('transform', 'rotate(' + longArmRotate + 'deg)');
			
			// handle alarm settings
			
			if (isAlarmSet) {
				if (alarmDate) {
					var dateDifference = Math.floor((currentDate.getTime() - alarmDate.getTime()) / (1000));
					if (dateDifference > resetAfter) {
						if (hasGoneOff)  {
						// when 5 minutes passed after go-off time, then reset alarm setting.
							basePlate.attr('src', '../Common/extensions/AnalogClock/ui/analog_clock/image/AnalogClock_Back.svg');
							hasGoneOff = false;
							isAlarmSet = false;
							this.setProperty('alarm', false);
						}
					} else if (dateDifference > 0) {
						// fire event.
						if (! hasGoneOff) {
							this.jqElement.triggerHandler('Alarmed');
							TW.log.debug("Alarm went off at " + currentDate);
							basePlate.attr('src', '../Common/extensions/AnalogClock/ui/analog_clock/image/AnalogClock_BackAlert.svg');
							hasGoneOff = true;
						} else {
							TW.log.debug("Time to reset: " + (resetAfter - dateDifference));
						}
					} else {
						TW.log.debug("Time to go off: " + dateDifference);						
					}
				}
			}
		};
	};
};
```

とくに難しいところはないのですが、順番に見ていきましょう。

冒頭部分で、アラーム機能の実装に必要ないくつかのローカル変数の定義を追加しています。

```analog_clock.runtime.js
	var basePlate;
	var shortArmRotate;
	var longArmRotate;
	var currentDate;
	var alarmDate;
	var isAlarmSet;
	var hasGoneOff;
	var resetAfter;
```
つぎに、``renderHtml()``関数を見てみます。

```analog_clock.runtime.js
	this.renderHtml = function () {
		return 	'<div class="widget-content widget-analog_clock">' +
					'<img class="aClock aClock_base"     src="../Common/extensions/AnalogClock/ui/analog_clock/image/AnalogClock_Back.svg" />' +
					'<img class="aClock aClock_shortArm" src="../Common/extensions/AnalogClock/ui/analog_clock/image/AnalogClock_ShortArm.svg" />' +
					'<img class="aClock aClock_longArm"  src="../Common/extensions/AnalogClock/ui/analog_clock/image/AnalogClock_LongArm.svg" />' +
					'<img class="aClock" src="../Common/extensions/AnalogClock/ui/analog_clock/image/AnalogClock_Center.svg" />' +
				'</div>';
	};
```

小さな違いですが、文字盤の画像を読み込んでいる img タグに ``aClock_base``クラスを新たに追加しています。これは、後ほど「アラーム状態になったときに文字盤を赤くする」という処理のために必要です。

つづいて``afterRender()``関数ですね。

```analog_clock.runtime.js
	this.afterRender = function () {
		basePlate = this.jqElement.find('.aClock_base');
		shortArm = this.jqElement.find('.aClock_shortArm');
		longArm = this.jqElement.find('.aClock_longArm');
		alarmDate = this.getProperty('alarm date');
		isAlarmSet = this.getProperty('alarm');
		hasGoneOff = false;
		resetAfter = this.getProperty('reset after');
	};
```
冒頭に追加したいくつかのローカル変数に値をセットしています。``this.getProperty()``関数は、マッシュアップビルダーで指定したプロパティを読み込むために使います。

どんどんいきましょう。今回の改造の山場、``updateProperties()``関数です。ちょっと長いですが、大丈夫。難しい処理はしていません。

```analog_clock.runtime.js
		if (updatePropertyInfo.TargetProperty === 'alarm') {
			if (updatePropertyInfo.SinglePropertyValue === 'false') {
				isAlarmSet = false;
			} else {
				isAlarmSet = true;
			}
			this.setProperty('alarm', isAlarmSet);
		}
		
		if (updatePropertyInfo.TargetProperty === 'alarm date') {
			alarmDate = new Date(updatePropertyInfo.SinglePropertyValue);
			this.setProperty('alarm date', alarmDate);
		}
		
		if (updatePropertyInfo.TargetProperty === 'reset after') {
			resetAfter = parseInt(updatePropertyInfo.SinglePropertyValue, 10);
			this.setProperty('reset after', resetAfter);
		}
```

まず、関数の最初に alart プロパティや alart date プロパティが変更された際の処理を書いています。``setProperty()``関数は、JavaScript コードの内部からウィジェットのプロパティに値を書き込むために使います。ユーザーやプログラム処理によってウィジェットのプロパティを変更した際は、とくにそのプロパティがバインドソースとして定義されている場合には、忘れずに``setProperty()``を呼ぶようにしましょう。

つづいて、date プロパティが更新された時の処理ですが、後半部分にアラーム処理を書き加えています。下記の部分ですね。

```analog_clock.runtime.js
			// handle alarm settings
			
			if (isAlarmSet) {
				if (alarmDate) {
					var dateDifference = Math.floor((currentDate.getTime() - alarmDate.getTime()) / (1000));
					if (dateDifference > resetAfter) {
						if (hasGoneOff)  {
						// when 5 minutes passed after go-off time, then reset alarm setting.
							basePlate.attr('src', '../Common/extensions/AnalogClock/ui/analog_clock/image/AnalogClock_Back.svg');
							hasGoneOff = false;
							isAlarmSet = false;
							this.setProperty('alarm', false);
						}
					} else if (dateDifference > 0) {
						// fire event.
						if (! hasGoneOff) {
							this.jqElement.triggerHandler('Alarmed');
							TW.log.debug("Alarm went off at " + currentDate);
							basePlate.attr('src', '../Common/extensions/AnalogClock/ui/analog_clock/image/AnalogClock_BackAlert.svg');
							hasGoneOff = true;
						} else {
							TW.log.debug("Time to reset: " + (resetAfter - dateDifference));
						}
					} else {
						TW.log.debug("Time to go off: " + dateDifference);						
					}
				}
			}
```
かなり深い``if``文になっていますが、アラーム状態か否かを判断して、アラーム状態であれば下記の処理を実施しています。

- ``aClock_base``クラスの要素の画像を差し替える（赤い文字盤の画像が指定されています）
- ``alarmed``イベントを発行する

また、アラーム状態になってから reset after 秒以上経過すると、アラーム状態を解除するために次の処理をしています。

- ``aClock_base``クラスの要素の画像を通常のものに差し替える

ほぼ通常の JavaScript か JQuery のシンタックスですので難しいところはないかと思います。ThingWorx の香りがする部分は下記ですね。

```analog_clock.runtime.js
							this.jqElement.triggerHandler('Alarmed');
							TW.log.debug("Alarm went off at " + currentDate);

```
``jqElement.triggerhandler('alarmed')``は JQuery のシンタックスですが、この一行でウィジェットに定義されている``alarmed``イベントを発行しています。この``alarmed``という名前は、analog_clock.ide.js の``this.wigetEvents()``関数で定義したものと名前が一致する必要があるので、注意してください。

つづく``TW.log.debug()``は、ログ出力のためです。このログは、ブラウザのコンソールへ送られます。

#最後の一歩：ウィジェットを使ってみる

以上でアラーム機能の作り込みは終わりました。metadata.xml の packageVersion番号をあげて、エクステンションをビルドして ThingWorx にインポートすれば作業は終了です。

せっかく新しい機能を作ったので、実際に動作するかマッシュアップ上でテストしてみましょう。以下のように前回作った "MyClock"マッシュアップに変更を加えます。

1. Date Time Picker ウィジェットを **ふたつ** 配置する。
2. 新しい Expression を "WentOffAt" という名前で作る。Expression には ``new Date()``を指定し、Output には DATETIME 型を指定する。
3. Check Box ウィジェットをひとつマッシュアップに配置する。Prompt プロパティには "Set Alarm" と入力する。

マッシュアップの登場人物は以上です。マッシュアップビルダー場では次のようにレイアウトエリアに表示されていると思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119047/d24a276d-a04f-55d2-3294-39c1b072e7a7.png)

上記画像では追加で Label ウィジェットも使っていますが、動作に影響しないものなので必須ではありません。Function ペインは次のようになっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119047/9f424107-d689-a8e8-7cb7-11ee4dd42a99.png)

次に、配置したウィジェットをバインドしていきます。最初にバインドした結果の画像をみてみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119047/123e7e2b-4db1-16a7-605e-81d8eb9c8bcc.png)


1. さきほど配置したひとつめの Date Time Picker ウィジェットの "Date Time" プロパティを Analog Clock ウィジェットの "alarm date" プロパティにバインドします。これで Date Time Picker で選択した日時が Analog Clock のアラーム時間としてセットされます。
2. おなじく、Check Box ウィジェットの "State" プロパティを Analog Clock ウィジェットの "alarm" プロパティにバインドします。これはアラーム時計のアラームスイッチに相当します。Check Box ウィジェットにチェックが入っていればアラームが鳴り、そうでなければアラーム時刻をセットしていてもアラームは鳴りません。
3. Analog Clock ウィジェットの "Alarmed" イベントを WentOffAt Expression の "Evaluate" 入力パラメタにバインドします。こうすることで、Analog Clock がアラート状態になった時に、WengOffAt Expression が呼び出されます。

あと一息です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119047/8dfc4716-6bbe-f702-734e-caef0f8158b3.png)

4. WentOffAt Expression の "Output" を、先ほど追加した二つ目の Date Time Picker ウィジェットの "Date Time" プロパティにバインドしてください。この操作で、Analog Clock がアラート状態になった時間（Alarmed イベントが発行された時間）を表示します。

すべてのバインドがうまくいったら、マッシュアップを保存して "View Mashup" ボタンをクリックしてマッシュアップを実行してください。次のような表示になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119047/2f6b31d0-b82b-c09f-b36b-098664867412.png)

一つ目の Date Time Picker （上図では "Go-off Time"）で時間を指定し、"Set Alarm" をチェックすると、Analog Clock にアラームが設定されます。

アラームに設定した時間に到達すると、下記のような表示になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119047/79ad352e-577f-6b90-c625-b48ca3c7119e.png)

文字盤が赤くなり、さらに二つ目の Date Time Picker の表示が "Select Date/Time" からアラームが鳴った時間に変わっていることがわかります。このまま実行を続けると、Analog Clock ウィジェットの "reset After"　プロパティで指定した時間が経過すると、文字盤が元に戻ります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119047/e414fd90-2ac5-4246-f00a-703a926b05f3.png)


#おわりに

これまでの記事で、以下を説明しました。

- ウィジェットをつくるために Eclpise 用のプラグインを入れる方法
- 簡単なウィジェットを作る方法
- プロパティを受け取る方法（時刻情報を受け取りました）
- 入力に応じて表現を変える方法（CSSのtransformを使用しました）
- イベントの発行のしかた（Alarmedイベントを発行しました）

ウィジェットにはこのほか、独自のサービスを提供することもできますが、まずはここまでで紹介した機能である程度「使える」ウィジェットは作れるのではないかと思います。

第一回でも説明しましたが、詳しい情報は下記の URL にあります。残念ながら英語ですが、必要な情報はほぼ網羅されていますので、ご参考までに。

[ThingWorx Extension Development Guide](https://www.ptc.com/support/-/media/FFF75A096E3245C8BA1E42E1C47C04CD.ashx?sc_lang=en)

#余談

ところで、Expression の名前に "WentOffAt" という名前を使いました。また、JavaScript コードの中で``hasGoneOff``というローカル変数を使っています。どちらも原型は "go off" となりますが、この "go off" とは何でしょうか？

グーグル先生の教えてくれるところによると、アラームが「鳴る」ことを英語では "go off" と表現するんだそうです。へぇぇ...。

#今回作成したリソース全掲

```metadata.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Entities>
    <ExtensionPackages>
        <ExtensionPackage dependsOn="" description="" minimumThingWorxVersion="8.3.0" name="AnalogClock" packageVersion="1.4.1" vendor="Kanoloa"> 
        </ExtensionPackage>
    </ExtensionPackages>
    <Widgets>
        <Widget name="analog_clock">
            <UIResources>
                <FileResource description="" file="analog_clock.ide.js" isDevelopment="true" isRuntime="false" type="JS"/>
                <FileResource description="" file="analog_clock.ide.css" isDevelopment="true" isRuntime="false" type="CSS"/>
                <FileResource description="" file="analog_clock.runtime.js" isDevelopment="false" isRuntime="true" type="JS"/>
                <FileResource description="" file="analog_clock.runtime.css" isDevelopment="false" isRuntime="true" type="CSS"/>
            </UIResources>
        </Widget>
    </Widgets>
</Entities>
```

```analog_clock.ide.js
TW.IDE.Widgets.analog_clock = function () {

	this.widgetIconUrl = function() {
		return  "'../Common/extensions/AnalogClock/ui/analog_clock/default_widget_icon.ide.png'";
	};

	this.widgetProperties = function () {
		return {
			'name': 'Analog Clock',
			'description': '',
			'category': ['Common'],
			'isResizable': true,
			'supportsAutoResize': true,
			'properties': {
				'date' : {
					'baseType': 'DATETIME',
					'isBindingTarget' : true,
					'isVisible': true
				},
				'alarm date': {
					'baseType': 'DATETIME',
					'isBindingTarget': true,
					'isVisible': true
				},
				'alarm': {
					'baseType': 'BOOLEAN',
					'defaultValue': false,
					'isEditable': true,
					'isBindingTarget': true,
					'isVisible': true
				},
				'reset after': {
					'baseType': 'INTEGER',
					'defaultValue': 300,
					'isEditable': true,
					'isBindingTarget': true,
					'isVisible': true
				},
				'Width': {
					'description': TW.IDE.I18NController.translate('tw.textbox-ide.properties.width.description'),
					'defaultValue': 120,
					'isEditable': true,
					'isVisible': true
				},
				'Height': {
					'description': TW.IDE.I18NController.translate('tw.textbox-ide.properties.height.description'),
					'defaultValue': 120,
					'isEditable': true,
					'isVisible': true
				}
			}
		}
	};

	this.afterSetProperty = function (name, value) {
		var thisWidget = this;
		var refreshHtml = false;
		switch (name) {
			case 'Style':
			case 'Width':
				this.setProperty('Height', value);
			case 'Height':
				this.setProperty('Width', value);
			case 'Alignment':
				refreshHtml = true;
				break;
			default:
				break;
		}
		return refreshHtml;
	};

	this.renderHtml = function () {
		// return any HTML you want rendered for your widget
		// If you want it to change depending on properties that the user
		// has set, you can use this.getProperty(propertyName).
		return 	'<div class="widget-content widget-analog_clock">' +
					'<img class="aClock" src="../Common/extensions/AnalogClock/ui/analog_clock/image/AnalogClock_Back.svg"> ' +
					'<img class="aClock aClock_shortArm" src="../Common/extensions/AnalogClock/ui/analog_clock/image/AnalogClock_ShortArm.svg" />' +
					'<img class="aClock aClock_longArm" src="../Common/extensions/AnalogClock/ui/analog_clock/image/AnalogClock_LongArm.svg" />' +
					'<img class="aClock" src="../Common/extensions/AnalogClock/ui/analog_clock/image/AnalogClock_Center.svg" />' +
				'</div>';
	};

	this.afterRender = function () {
		// NOTE: this.jqElement is the jquery reference to your html dom element
		// 		 that was returned in renderHtml()
	};
	
	this.widgetEvents = function() {
		return {
			'Alarmed': {}
		};
	};
	

};
```

```analog_clock.ide.css
/* Place custom CSS styling for Analog Clock widget in Composer in this file */

.widget-analog_clock {
	position: relative;
}

.aClock {
	position: absolute;
	left: 0;
	right: 0;
	max-width: 100%;
	height: auto;
	transition: 0.5s;
}
```

```analog_clock.runtime.js
TW.Runtime.Widgets.analog_clock= function () {
	
	// var thisWidget = this;
	var shortArm;
	var longArm;
	var hour;
	var minute;
	var second;
	var basePlate;
	var shortArmRotate;
	var longArmRotate;
	var currentDate;
	var alarmDate;
	var isAlarmSet;
	var hasGoneOff;
	var resetAfter;
	
	this.renderHtml = function () {
		return 	'<div class="widget-content widget-analog_clock">' +
					'<img class="aClock aClock_base"     src="../Common/extensions/AnalogClock/ui/analog_clock/image/AnalogClock_Back.svg" />' +
					'<img class="aClock aClock_shortArm" src="../Common/extensions/AnalogClock/ui/analog_clock/image/AnalogClock_ShortArm.svg" />' +
					'<img class="aClock aClock_longArm"  src="../Common/extensions/AnalogClock/ui/analog_clock/image/AnalogClock_LongArm.svg" />' +
					'<img class="aClock" src="../Common/extensions/AnalogClock/ui/analog_clock/image/AnalogClock_Center.svg" />' +
				'</div>';
	};

	this.afterRender = function () {
		basePlate = this.jqElement.find('.aClock_base');
		shortArm = this.jqElement.find('.aClock_shortArm');
		longArm = this.jqElement.find('.aClock_longArm');
		alarmDate = this.getProperty('alarm date');
		isAlarmSet = this.getProperty('alarm');
		hasGoneOff = false;
		resetAfter = this.getProperty('reset after');
	};

	this.updateProperty = function (updatePropertyInfo) {
		
		if (updatePropertyInfo.TargetProperty === 'alarm') {
			if (updatePropertyInfo.SinglePropertyValue === 'false') {
				isAlarmSet = false;
			} else {
				isAlarmSet = true;
			}
			this.setProperty('alarm', isAlarmSet);
		}
		
		if (updatePropertyInfo.TargetProperty === 'alarm date') {
			alarmDate = new Date(updatePropertyInfo.SinglePropertyValue);
			this.setProperty('alarm date', alarmDate);
		}
		
		if (updatePropertyInfo.TargetProperty === 'reset after') {
			resetAfter = parseInt(updatePropertyInfo.SinglePropertyValue, 10);
			this.setProperty('reset after', resetAfter);
		}
		
		if (updatePropertyInfo.TargetProperty === 'date') {
			
			currentDate = new Date(updatePropertyInfo.SinglePropertyValue);
			hour = currentDate.getHours();
			minute = currentDate.getMinutes();
			second = currentDate.getSeconds();
			
			if (hour > 11) {
				hour = hour - 12;
			} else if (!hour) {
				hour = 0;
			}
			
			if (minute === '60') {
				minute = 0;
			} else if (!minute) {
				minute = 0;
			}
			
			shortArmRotate = (hour * 30) + (minute * 0.5);
			longArmRotate = (minute * 6) + (second * 0.1);

			shortArm.css('transform', 'rotate(' + shortArmRotate + 'deg)');
			longArm.css('transform', 'rotate(' + longArmRotate + 'deg)');
			
			// handle alarm settings
			
			if (isAlarmSet) {
				if (alarmDate) {
					var dateDifference = Math.floor((currentDate.getTime() - alarmDate.getTime()) / (1000));
					if (dateDifference > resetAfter) {
						if (hasGoneOff)  {
						// when 5 minutes passed after go-off time, then reset alarm setting.
							basePlate.attr('src', '../Common/extensions/AnalogClock/ui/analog_clock/image/AnalogClock_Back.svg');
							hasGoneOff = false;
							isAlarmSet = false;
							this.setProperty('alarm', false);
						}
					} else if (dateDifference > 0) {
						// fire event.
						if (! hasGoneOff) {
							this.jqElement.triggerHandler('Alarmed');
							TW.log.debug("Alarm went off at " + currentDate);
							basePlate.attr('src', '../Common/extensions/AnalogClock/ui/analog_clock/image/AnalogClock_BackAlert.svg');
							hasGoneOff = true;
						} else {
							TW.log.debug("Time to reset: " + (resetAfter - dateDifference));
						}
					} else {
						TW.log.debug("Time to go off: " + dateDifference);						
					}
				}
			}
		};
	};
};
```

```analog_clock.runtime.css
/* Place custom CSS styling for Analog Clock widget at runtime in this file */

.widget-analog_clock {
	position: relative;
}

.aClock {
	position: absolute;
	left: 0;
	right: 0;
	max-width: 100%;
	height: auto;
	transition: 0.5s;
}
```

#リソース

ソースコードや画像は下記から実動するものをダウンロードできます。

- [画像ファイルのダウンロードリンク](https://www.dropbox.com/s/kmb5mjm7c5uku9c/AnalogClock_Images.zip?dl=0)
- [インポート可能なエクステンション](https://www.dropbox.com/s/t6xesyzclawohoh/AnalogClock.zip?dl=0)
- [Eclipse プロジェクトファイル](https://www.dropbox.com/s/yat7ovmr6geq3xz/AnalogClock_EclipseProject.zip?dl=0)