WinJS 4.0 では HTML コントロールにスタイルを当てるためにクラスを明示的に指定する必要がある
WinJS 4.0 がリリースされましたね!! めでたい!
2015 年 6 月 14 日時点では、Try WinJS のダウンロードページのリンク先がバージョン 4.0.0 になっていますが、最新バージョンは 4.0.1 です。 (Windows Phone 10 におけるバグの修正がされたようです。)
私も Windows ストアアプリ開発で使用している WinJS のバージョンを 4.0 にしてみましたが、便利なコントロールがいくつか追加されていたり、見た目がかっこよくなっていたりして良いです。 ロードマップを見たところ、バージョン 4.1 のリリースは Windows 10 リリースと同時とのことなので、Windows ユニバーサルアプリ開発には WinJS 4.1 を使えそうです。 UX に磨きがかけられるとのことなので、それも今から楽しみですね。
スタイルの適用方法の変更
変更点のうちの 1 つ、スタイルの適用方法についてこのエントリでは紹介します。
Changelog · winjs/winjs Wiki · GitHubStyling
Styling of intrinsic elements is no longer by default
Changelog に書かれているように、HTML の通常の要素にはデフォルトでは WinJS のスタイルが当たらないように変更されました。 WinJS 3.0 では、例えば button
要素そのものに WinJS 用のスタイルが当たるように CSS が書かれていましたが、WinJS 4.0 では button
要素を単に書くだけでは WinJS のスタイルは適用されません。 WinJS のスタイルを適用したい場合には、明示的にクラスを指定する必要があります。
ボタンの場合は、win-button
クラスです。
<button class="win-button">WinJS のスタイルが適用されたボタン</button> <button>WinJS のスタイルは適用されないボタン</button>
詳細は次のページに書かれています。
ボタンのようにスタイルが当たっていないことがすぐにわかるものは動作確認時に気づきやすいのですが良いのですが、h1
要素や progress
要素など、スタイルが当たっているのかどうか動作確認時に見てもわかりづらいものもあるので注意しましょう。 body
要素に win-type-body
クラスを付与することが推奨されている のも移行当初は見落としがちなので気を付けたいですね。 win-button-primary
の存在も見落としやすそうです。
Windows Runtime の IMap<K, V> インターフェイスを実装したオブジェクトを JS で使用する (ワークアラウンド)
Windows Runtime の API を JS から使用する際に問題となることの一つとして、JS ではインターフェイスを扱うことができない、というものがあります。 Windows Runtime の API には、引数として IIterable<IKeyValuePair<string, string>>
オブジェクトや IMap<string, string>
オブジェクトを取るものがありますが、そういったインターフェイスを実装するオブジェクトを JS から作成するのは Windows Runtime API を使うだけでは難しそうでした。 *1
しょうがないので、ワークアラウンドとして C# などで独自の Windows Runtime コンポーネントを生成するという方法を採ります。
具体的な問題の例
例えば、Windows.Web.Http.HttpFormUrlEncodedContent
クラスのインスタンスを生成したいとします。 HttpFormUrlEncodedContent
のコンストラクタは、Windows.Foundation.Collections.IIterable<IKeyValuePair<string, string>>
オブジェクトを引数に取ります。
なので、JS で IIterable<IKeyValuePair<string, string>>
オブジェクトを生成できれば良いのですが、その方法が見つかりませんでした。 下記フォーラムで質問を見つけましたが、ちゃんとした解決には至っていませんでした。
- Using HttpFormUrlEncodedContent
- collections - How do I pass a Dictionary from Javascript to C# Runtime Component? - Stack Overflow
ちなみに
ちなみに IIterable<IKeyValuePair<string, Object>>
というインターフェイスや IMap<string, Object>
というインターフェイスを実装したオブジェクトが欲しい場合は PropertySet
クラスを使うと良いです。
ワークアラウンド : 独自の Windows Runtime コンポーネントを生成して JS から使用する
Windows.Foundation.Collections.IMap<K, V>
インターフェイス は IIterable<IKeyValuePair<K, V>>
インターフェイスを継承しているので、ここでは IMap<K, V>
オブジェクトを返すことを考えてみます。 言語は C# を使います。 (C++ や VB でも可能なはずです。)
まずはカスタムコンポーネント用のプロジェクトを作成します。 次のエントリが参考になります。
プロジェクトができたら、C# でクラスを作成します。 次のような感じです。 下の例は IMap<string, string>
オブジェクトを返すメソッドの例です。 (IMap<K, V>
インターフェイスは C# では System.Collections.Generic.IDictionary<K, V
らしい。)
<feff>using System; using System.Collections.Generic; namespace YourNameSpace { public sealed class CollectionsUtil { /// <summary> /// <code>Windows.Foundation.Collections.IMap<string, string></code> オブジェクトを返す。 /// JS でこのインターフェイスを実装したオブジェクトを生成する方法がわからないので苦肉の策。 /// </summary> public static IDictionary<string, string> CreateStringMap() { return new Dictionary<string, string>(); } } }
System.Type
を使って JS 側から文字列で型を指定してオブジェクトを生成させるとかもできそうだけど今のところ必要ないのでそこまではやってません。
どう考えてもおかしい
こんなことしないといけないのはどう考えてもおかしいのでもっといい方法があるはず……!!
*1:もしかしたら方法があるのかもしれませんが、見つけられませんでした。
WebAuthenticationBroker を使用して OAuth による認可処理を Windows ストアアプリ内に組み込む
Windows ストアアプリ開発の話です。 Windows 8.1 および Windows Phone 8.1 を対象とした内容です。 (8.0 以前あるいは 10 以降については触れません。)
「Authentication and User Identity (HTML)」 に書かれているように、Windows ストアアプリ内にユーザー認証の機能を組み込むための選択肢はいろいろあります。 このエントリでは、その中の一つである Web 認証ブローカー (Web authentication broker) について紹介します。
Web 認証ブローカー (Web authentication broker) の概要
Web 認証ブローカーは、OpenID や OAuth などのインターネット経由の認証プロトコル *1 を使うオンライン ID プロバイダーに接続してユーザー認証する (あるいは認可を得る) 際に利用できる機能です。
OAuth などのプロトコルでは、クライアントに許可を与えるためにユーザー (OAuth ではリソース保持者) がサーバー (ID プロバイダー) の web ページ上で操作する必要があります。 ユーザーにサーバーの web ページを表示するためにアプリ内に web ビューを表示して操作させる場合もありますが、自前で web ビューを用意する代わりに Web 認証ブローカーを使用することができます。
Web 認証ブローカーの仕組みなどは、次のページを見ると良いでしょう。
Web 認証ブローカーの使い方
OAuth 1.0 Protocol を用いて認可を得る場合を例に、どのように web 認証ブローカーを使用するかを見ていきます。
前準備
Web 認証ブローカーを使用する前に、予め Temporary Credentials を取得しておきます。 このとき、oauth_callback
パラメータによりコールバック URI を指定できますが、任意の URI (「http://localhost/」 など) を指定して良いです。
サーバー側が対応しているのであれば WebAuthenticationBroker.getCurrentApplicationCallbackUri
メソッド の返り値 (「ms-app://s-1-15-2-9999-999999999-999999-99999/」 みたいな URI) を使うのが良いです。 ただ、サーバー側が 「http(s)」 プロトコルしか許可していない場合もありますので、その場合は適当な http プロトコルの URI を指定しましょう。
Web 認証ブローカーによる Resource Owner Authorization
Temporary Credentials を取得した後、リソース保持者による認可処理があります。 すなわち、ユーザーをサーバーに送り、リクエストを認可してもらう必要があります。 ここで web 認証ブローカーを使用できます。
- Windows 8.1 プラットフォーム用:
WebAuthenticationBroker.authenticateAsync
メソッド - Windows Phone 8.1 プラットフォーム用:
WebAuthenticationBroker.authenticateAndContinue
メソッド
Web 認証ブローカーには、最初にユーザーに表示する web ページの URI (OAuth では Resource Owner Authorization endpoint URI) と、認可された後のリダイレクト先 URI (Temporary Credentials 取得時に指定したコールバック URI) を指定します。
var Uri = Windows.Foundation.Uri; var WebAuthenticationBroker = Windows.Security.Authentication.Web.WebAuthenticationBroker; var WebAuthenticationOptions = Windows.Security.Authentication.Web.WebAuthenticationOptions; var WebAuthenticationStatus = Windows.Security.Authentication.Web.WebAuthenticationStatus; // はてなの OAuth の場合。 `tempCred.identifier` は先に取得しておいた temporary credentials の identifier。 var startUri = new Uri("https://www.hatena.ne.jp/oauth/authorize?oauth_token=" + encodeURIComponent(tempCred.identifier)); // Temporary credentials 取得時に `oauth_callback` で http://localhost/ を指定していた場合。 var endUri = new Uri("http://localhost/"); // WebAuthenticationBroker が authenticateAndContinue メソッドを持っているかどうかで処理を変える。 var authenticateAndContinue = WebAuthenticationBroker.authenticateAndContinue; if (authenticateAndContinue) { // Windows Phone 8.1 用 authenticateAndContinue(startUri, endUri, null, WebAuthenticationOptions.none); // 結果の受け取り方は下の記述を見ること。 } else { // Windows 8.1 用 WebAuthenticationBroker.authenticateAsync(WebAuthenticationOptions.none, startUri, endUri).done(function (result) { if (result.responseStatus === WebAuthenticationStatus.success) { // `result.responseData` にはリダイレクト先 URI 文字列が入っている。 // 例 : "http://localhost/?oauth_token=xxxxxxoauth_verifier=xxxxxx" } else { // 失敗 (ユーザー操作で戻って来た場合など。) } }, function (err) { // 失敗 (HTTP レベルでのエラーが発生した場合など。) }); }
Windows Phone 8.1 でのレスポンスの受け取り方
Windows 8.1 の場合は、WebAuthenticationBroker.authenticateAsync
メソッドを呼ぶとアプリ内にサーバーのページが表示され、ユーザーによる認可が完了すると Promise
から結果を受け取ることができます。
一方、Windows Phone 8.1 では、WebAuthenticationBroker.authenticateAndContinue
メソッドを呼ぶと一旦アプリが停止してアプリの外部でサーバーのページが表示され、ユーザーによる認可が完了するとアプリがアクティベートされます。 メモリ使用量を抑えるために Windows Phone プラットフォームではこのような挙動になっているようです。
authenticateAndContinue
メソッドのように xxxAndContinue
という名前のメソッドがいくつかありますが、それらのメソッドを呼びだした後の結果はアクティベーションイベントとして受け取ることができます。 authenticateAndContinue
メソッドではなくファイルピッカーの例ですが、次のページが参考になります。
XAML 版なので直接は利用できませんが、次のページの情報も参考になります。
ページナビゲーションをサポートするアプリの場合、次のような感じで WinJS.Application
の activated
イベントのリスナーを登録し、アクティベーションの情報を WinJS.Navigation.navigate
メソッドに渡すようにします。
var SESSION_STAT_NAV_HISTORY = "nav_history"; var activation = Windows.ApplicationModel.Activation; var app = WinJS.Application; var nav = WinJS.Navigation; var sched = WinJS.Utilities.Scheduler; var ui = WinJS.UI; var activationKinds = Windows.ApplicationModel.Activation.ActivationKind; function activateOnLaunchOrContinuationProcs(args) { if (args.previousExecutionState !== activation.ApplicationExecutionState.terminated) { // TODO: This application has been newly launched. Initialize your application here. } else { // TODO: This application has been reactivated from suspension. Restore application state here. } // Optimize the load of the application and while the splash screen is shown, execute high priority scheduled work. ui.disableAnimations(); var p = ui.processAll().then(function () { var url = Application.navigator.home; var activationKind = args.kind; var activatedEventArgs = args; // アクティベーションの情報。 `xxxAndContinue` メソッドの結果もここに含まれる。 var initialState = { activationKind: null, activatedEventArgs: null }; var navHistory = app.sessionState[SESSION_STAT_NAV_HISTORY]; if (navHistory) { url = navHistory.current.location; initialState = navHistory.current.state || initialState; } initialState.activationKind = activationKind; initialState.activatedEventArgs = activatedEventArgs; nav.history = navHistory || {}; nav.history.current.initialPlaceholder = true; return nav.navigate(url, initialState); }).then(function () { return sched.requestDrain(sched.Priority.aboveNormal + 1); }).then(function () { ui.enableAnimations(); }); return p; } app.addEventListener("activated", (args) => { // Windows.ApplicationModel.Activation.IActivatedEventArgs インターフェイスを実装したオブジェクト。 var activatedEventArgs = args.detail; switch (activatedEventArgs.kind) { case activationKinds.launch: case activationKinds.pickFileContinuation: case activationKinds.pickSaveFileContinuation: case activationKinds.pickFolderContinuation: case activationKinds.webAuthenticationBrokerContinuation: args.setPromise(activateOnLaunchOrContinuationProcs(activatedEventArgs)); break; default: break; } });
上のようにしてアクティベーションの情報を WinJS.Navigation.navigate
メソッドに渡すようにすると、その情報がページコントロールにわたってくるので、ページコントロールの ready
で次のように情報を受け取ることができます。
var ActivationKind = Windows.ApplicationModel.Activation.ActivationKind; var WebAuthenticationStatus = Windows.Security.Authentication.Web.WebAuthenticationStatus; WinJS.UI.Pages.define("/pages/auth/auth.html", { ready: function (element, options) { // Continuation handlers are specific to Windows Phone. if (options && options.activationKind === ActivationKind.webAuthenticationBrokerContinuation) { // Windows.ApplicationModel.Activation.IWebAuthenticationBrokerContinuationEventArgs インターフェイスを実装したオブジェクト。 var eventArgs = options.activatedEventArgs; // eventArgs から WebAuthenticationBroker からの結果を取りだして処理する。 var result = eventArgs.webAuthenticationResult; if (result.responseStatus === WebAuthenticationStatus.success) { // `result.responseData` にはリダイレクト先 URI 文字列が入っている。 // 例 : "http://localhost/?oauth_token=xxxxxxoauth_verifier=xxxxxx" } else { // 失敗 (HTTP レベルでのエラーが発生した場合やユーザーがキャンセルした場合。) // ユーザーによるキャンセルと HTTP レベルでのエラーが発生した場合の区別ができなさそう? // (手元で試したところ HTTP レベルでのエラーでも `result.responseStatus` の値が 1 になっていた。) } } } });
ちょっと面倒ですがしょうがないですね。
WebAuthenticationBroker から結果を受け取った後
ユーザーが認可を行った場合は WebAuthenticationBroker
からの結果に oauth_verification
が含まれているはずですので、それを使って Token Credentials の取得処理を行えば良いです。
サンプルプロジェクト
WebAuthenticationBroker
を使用するサンプルプロジェクトがあります。 JS のものも他の言語のものもあるので参考になるでしょう。 ただ、OAuth 周りの処理は完全ではなさそう (OAuth のパーセントエンコードの代わりに encodeURIComponent
が使われていたりする) なので、あくまで参考程度に。
セキュリティ的な話
アプリ内に web ビューを表示する方法だと、アプリが信頼できないものの場合にパスワードが盗まれる可能性があるという問題があるのでセキュリティ的には外部ブラウザを使う方法を提供するべき (下記ページ参照) なわけですが、web 認証ブローカーについても同様の問題がありそうな気がします。
Web 認証ブローカーは Windows 側が提供している API なので、「接続先 URL が表示される」 + 「Web 認証ブローカーを用いた場合と同様の表示をアプリ側が独自に実装できないようになっている」 という条件が満たされれば外部ブラウザを使用するのと同等になるのかなーと思いますが。 *2
そもそも Android アプリと違って審査があって、要件に 『4.3 アプリはユーザーのセキュリティ、Windows デバイス、システム、関連するシステムのセキュリティまたは機能を危険にさらしたり侵害したりしてはならない。また、Windows ユーザーまたは他の個人に被害を与える可能性があってはならない』 ってのがあるから、パスワードを抜こうとするようなアプリは審査で落とされるから大丈夫なのかもですけど。
Code 2013 のコードゴルフに挑戦してみた
Code 2013 における JavaScript のコードゴルフが面白そうだったので挑戦してみました。 JavaScript でデジタル時計を表示するプログラムを短くするという問題です。
先にコードを出してる人よりも短くはできなかったのであんまり意味はないけどコードを載せておきます。
初期
最初の頃は 2 桁の数字を 1 つのデータで表現しようとしてました。
2 桁の数字から 1 桁ずつ値を取り出す処理を省ける分短くなるかなと思ってこうしていたのですが、実際のところは 2 桁の数字用のデータを生成する処理が長くなってしまって逆効果でしたね。 分と秒を分ける処理も面倒だと思っていたのでもともと秒と分を別個の変数で持たせたりしてました。
あと 「ブラウザの種類は問わない」 って書かれていたのでここぞとばかりに Arrow Function を使ってます *1。
インデントとか含めた状態で 434 バイト。
自力で到達した最後
で、結局 1 桁ずつ数字を書きだすように変更して、いろいろ書きかえていって、他の人のコードを見ずにたどり着いたのがこの状態です。
関数定義の function
とか return
とかに結構字数を取られてしまうので、関数定義は 1 つだけにしておいて引数で関数の挙動を変えるということをしてます。
除去できる改行と空白を除去して 283 バイト。
nanto さんのコードを見て改良
nanto さんのコードを見て、文字列に charCodeAt
メソッドを使って値を取り出すようにしたらバイト数減らせるのだなー、と思って改良しました。
- nanto さんのコード: 呼ばれていないけど、私もコードゴルフしてみました: Days on the Moon
除去できる空白や改行を除去して 263 バイト。
感想
コードゴルフ難しいってことだけはわかった!
— ねぷねぷ (@nobuoka) 2013, 8月 10
普段だったら絶対書かないようなコードを書くことになるので、普段のコーディング経験が邪魔になってしまうなーって思いました。 nanto さん 230 バイト切っててすごすぎます。
私は関数定義を 1 個だけ書くようにしてみたわけですが、他の人のコードを見ると eval
を使って関数定義をそもそも書かないようにしてたりして、なんていうか次元が違うなーと感じました。 怖い世界です。
- 作者: 桜澤麻衣,三浦佑之
- 出版社/メーカー: G.B.
- 発売日: 2007/07
- メディア: 単行本
- クリック: 3回
- この商品を含むブログ (3件) を見る
関連エントリ
JavaScript と関数とクロージャ
JavaScript でどれがクロージャなのか、という話が興味深かったです!
ほむほむ。 クロージャなのかどうか考える以前に、どういう挙動を示すのかを考えるのが難しいですよね>< eval
の挙動の違いなんかは狂気を感じざるを得ません!
そうそう、"use strict"
の有無 (strict コードかどうか) でも eval コードの変数環境の初期化処理が変化します。 難しいですね!!
var e1 = (function () { var msg = "関数の中です"; return eval("var msg; (function() { return msg; })"); }).call(this); console.log(e1()); //=> "関数の中です" // strict コードだと eval コード用に新たなスコープが作られるので、 // eval コード中の msg (undefined) が関数内の msg ("関数の中です") を隠ぺいする var e2 = (function () { "use strict"; var msg = "関数の中です"; return eval("var msg; (function() { return msg; })"); }).call(this); console.log(e2()); //=> undefined
この挙動についても ECMA-262 5.1th の 10.4.2 節 に書かれています。
ECMA-262 5.1th の 10.4.2 節
- 3. If the eval code is strict code, then
- a. Let strictVarEnv be the result of calling NewDeclarativeEnvironment passing the LexicalEnvironment as the argument.
- b. Set the LexicalEnvironment to strictVarEnv.
- c. Set the VariableEnvironment to strictVarEnv.
それはともかくクロージャについて
関数の中に関数を書ける、というのは説明するのに都合が良いというだけで、クロージャの本質ではないと思うわけですが、 この考えだとどうしても世間とずれが生じているように思えます。 はっきりとした答えが出せないのです。皆さんはどうお考えでしょうか? 何らかの反応があると嬉しいです。
回答:どれが「クロージャ」でしょうか? - hogehoge @teramako
クロージャの説明として 『ローカル変数を参照している関数内関数』 みたいなことが書かれているのは確かによく目にしますが、説明としては微妙だなー、という気はします。
- ECMAScript (JavaScript) ではスコープを形作るのは主に関数
- スコープチェインのすぐ上にグローバルスコープがある場合はクロージャだとみなしてもクロージャじゃないとみなしても実用上どちらでもよい (ECMA-262 の仕様的には原理上はクロージャだと思うけど)
という 2 点から、JavaScript における クロージャの説明としては 『関数内で定義された関数がクロージャ (であり、外側の関数のローカル変数にアクセスできるとかなんとか)』、という説明がわかりやすく一般受けしやすいものなんだと思います。 しかし、『関数内で定義された』 ということは特に本質的ではないですよね。
原理上は全ての ECMAScript の関数はクロージャ?
本質的には、クロージャとは
- 関数オブジェクトであり、
- 自身が生成された環境の変数束縛を保持しているもの
だと思いますので、原理上は ECMAScript の関数は全てクロージャと言って良いのではないかなーと思ったりしてます。 (よくわかんないです。)
- とはいえグローバルスコープで定義された関数をクロージャとみなすかどうかは微妙な感じはする
- 実用上どちらでもよいし、あえてクロージャだとみなしても特に利点はない気がする
少なくとも 『関数内で定義された関数がクロージャだ』、というのはおかしいと思う
例えば catch ブロック中で定義された関数は catch ブロックのスコープを持つわけなので、たとえ関数の中で定義されてなくてもそれはクロージャなわけなので、『関数内で定義された関数』 であることはクロージャであるための必要条件ではないですよね。
var count = 100; try { throw 0; } catch (count) { var countup = function () { return ++count }; } // 関数 countup はグローバルスコープではない変数環境への参照を保持している // countup はクロージャ! console.log(countup()); //=> 1 console.log(countup()); //=> 2 console.log(countup()); //=> 3
とかなんとか考えたりしました。