ラベル JavaScript の投稿を表示しています。 すべての投稿を表示
ラベル JavaScript の投稿を表示しています。 すべての投稿を表示

2012年3月8日木曜日

クロスドメインでXMLHttpRequestを送るときの逃げ道≡(;´Д`)ノ

クロスドメインでHTTPヘッダを送信するってこと
( ゚Д゚)<POSTでもGETでもなく、HTTPヘッダですよ!

おぅふ、こんなやっかいなこと言い出すなんて..._ノ乙(、ン、)_
後悔先に立たず。ありますよね?
( ・`д・´)<あるあるー!

今回はシステム間の遷移のお話です。
『ポートもドメイン名すらも違うWebシステムに任意のHTTPヘッダを送信する』
にはどうしましょうってことです。
状況として
振り返れば数週間前...( ´Д`)。oO(

(゚Д゚)<そちらのシステムから、うちのシステムに遷移させてもらえませんか?
はじまりはこの一言でした。
言い出したのは、うちの最大顧客の小会社でシステム開発しているS社。
無視するわけにはいかないし、
ただ遷移するだけなら簡単そうなので、
ヽ(´ー`)ノ<大丈夫ですよ!へっちゃらです。
って営業さんが返事してました。
(し、僕も大丈夫だと思ってました。)
やりたいことって?
要望を大雑把に言うと、
(1)うちのシステムにユーザがログイン
(2)あるボタンをクリック
(3)S社の開発したシステムに遷移。
ってことです。

普通にリンクはっただけでは、ユーザの識別ができないので、
ユーザを識別するコードを、
S社が用意してくれるアルゴリズムにしたがって暗号化して、
それをS社のシステムに送信するってことでした。

暗号化アルゴリズムもくれるっていうし、
POSTかGETで送信すれば全然たいしたことないっすよね。
って思ってました。
<a href="http://sss/xxx?user=0001">S社のシステムに遷移!</a>

とか、
みたくね。
直前になって...データはHTTPヘッダで送って
で、S社のシステムが本番反映をむかえる1日前、この一言がでたのです。

( ゚Д゚)<POSTでもGETでもなく、HTTPヘッダですよ!
正直。
なにいってんだ(゚Д゚)ゴルァ!!
って思いました。
S社はPOSTででもGETでもなく、
HTTPのヘッダに付加してリクエストを送信してほしかったのです。

[HTTPのヘッダ]
Accept-Language: ja
Referer: http://xxxxxxxx/ooooo.html?44
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible;...
USER: 0001

というのを期待しているようでした。

(゚Д゚≡゚Д゚)<素直にGETでおくれよー!
って。
しかしそこは最大顧客の小会社。どうにかしなければなりません。
『うちの方法と違うので、送信方法おしえてくれませんか?』
とS社にも協力を要請し、
どうしようかと調査開始。
制約~HTTPヘッダの付加とクロスドメイン~
とりあえず制約として、
(1)クライアントで任意のHTTPヘッダを付加する。
(2)ポートもドメインも違うWebシステムにリクエストを送信する。
の2点がありますが、
この二つが互いに足をひっぱりあいます。

任意のHTTPヘッダをクライアントで付加してリクエスト送るためには、
XMLHttpRequestを使う必要があります。
しかし、ポートもドメインもうちのシステムと違うシステムへリクエストを送信するため、
クロスドメイン制約にひっかかります。

一方、普通にGETで送れるならば、
anchorタグに href="http://xxxxx/oooo.jsp?user=0123" とかって指定すれば遷移します。
POSTでsubmitしてもいいですし。
でもそれだとHTTPヘッダは付加できません。

((ミ・♀・ミ)) くまった。

ところで、いいだしっぺのS社はというと、
『サーバでのHTTPヘッダの取得方法』とか、
『サーバでHTTPレスポンスにヘッダを設定する方法』とか、
ちょwwよくわかってないんじゃないのww
って返答ばかりが返って来ました...
いろいろな誤解~HTTPレスポンスでもなく、JSONPでもない~
誤解1
ヽ(´ー`)ノ<HTTPヘッダならいつもサーバで付加してるやん!

( ・`д・´) はいこれはS社と同じ誤解ですね。
HTTPにはリクエストとレスポンスがあります。
誤解1の場合はレスポンスのHTTPヘッダのお話です。

クライアント<-----【HTTP】----- サーバ

こんな感じですね。
たとえば、みんないつも

   Response.ContentType="application/octet-stream;name=file.pdf"
   Response.AddHeader "Content-Transfer-Encoding","base64"
みたいな感じでレスポンスのヘッダに付加してるから、
コレじゃない?って思うわけです。
でも、これではないんですね。

クライアント-----【HTTP】-----> サーバ

今回の場合はコレ。リクエストですね。
やり方としては、
具体的にはJavascriptでXMLHttpRequestを生成してヘッダを加えて、送信します。
xmlReq = new XMLHttpRequest();
xmlReq.open("POST","test.php", true);
xmlReq.setRequestHeader("user", inUser);
xmlReq.send(null);


このtest.phpの部分がhttp://xxxxxxになっていたら...
xmlReq.open("POST","http://xxxxxx", true);
とかになっていたら、クロスドメインとみなされて失敗します。もしくはエラーがでます。

誤解2
ヽ(´ー`)ノ<クロスドメインといえば、JSONPとかでできんじゃないの??

( ・`д・´) はいこれは僕が最初勘違いしていました。
これもおんなじ図で説明します。
JSONPは

クライアント<-----【データ】----- ドメインの違うサーバ

こんな感じでドメインの違うサーバからデータを取得する方法なんですね。
今回はこれ。

クライアント-----【データ】-----> ドメインの違うサーバ

しかもHTTPのリクエストに任意のヘッダを追加しないといけないおまけ付きです。
解決方法...ではなく逃げ道≡(;´Д`)ノ
さすがにここまで制約つくと一筋縄ではいかず、
どっちかの制約を緩和する方法が必要になりました。

で、その緩和する方法とは、
『そちらのドメイン名でアクセスできるHTMLファイルをそっちのサーバに置かせて!』
ってことでした。

つまり、S社のサーバに置かせてもらったHTMLから、
XMLHttpRequestでデータを送信しようって魂胆です。
ドメイン名がS社のシステムと同じならクロスドメインにひっかかりません。
図にするとこんな感じ。

ステップは3つ
(1)うちのサーバにあるリンクをクリックして、S社のサーバにうちが置いたHTMLファイルに遷移
(2)javascriptでURL(http://sss/http_header.html#0001)からユーザ情報を取得
(3)XMLHttpRequestを生成し、HTTPヘッダに取得したユーザ情報を付加、送信

(1)うちのサーバでやるのはリンクをはることだけです。URLのおしりにユーザ情報を付加して。
ちょうどこんな感じですね。 <a href=”http://sss/http_header.html#0001”>リンク</a> ユーザはこのリンクをクリックして、まんまとS社のサーバにとびます。

(2)つぎにうちがS社のサーバにおいた静的なファイルhttp_header.htmlに遷移します。
ここではonloadかなんかのイベントでユーザの情報を取得します。
location.hashを用いれば取得できます。
たとえばこんな感じで。
var user =  String(location.hash).substring(1) //#よりうしろが取得される。

(3)あとはこれをXMLHttpRequestで送信すればOKです。
このHTMLファイルはS社のサーバからクライアントに送られているので、
S社のサーバにあるシステムに存分にデータを送信できます。
クロスドメインなんかこわくないヽ(´ー`)ノわーい
雑ですが、http_header.htmlの中身です。
<html>
<head>
<script language='javascript'>
<!--

function send(){
  xmlHR = new XMLHttpRequest();
  
  xmlHR.onreadystatechange = function() {
    if (xmlHR.readyState == 4) {
      if (xmlHR.status == 200) {
        var page = window.open("", "", "");
        page.document.open();
        page.document.write(xmlHR.responseText);
        page.document.close();
        window.close();
        
      } else {
        var page = window.open("", "", "");
        page.document.open();
        page.document.write("false
<\/body><\/html>");        page.document.close();      }    }  }    xmlHR.open("POST","main.php", true);  xmlHR.setRequestHeader("USER", String(location.hash).substring(1));  xmlHR.send(null); } --> </script> </head> <body onload="send()"> </body> </html>
たしかに
当初の目的とはずれましたが、
どんな要望でもお手上げ状態ではなく、
何かしらの案を出せるもんだなって自信がつきましたっ( ・`д・´)

2012年1月16日月曜日

JavaScriptの連想配列でlengthを取得っ?!

JavaScriptでの連想配列と配列
あるブログに連想配列でlengthとれねーよ( `д´) ケッ!ってありました。
今回はJavaScriptでの連想配列と配列についてちょっと。

JavaScriptで連想配列を作成しようとすると、次のようになります。
var ary1 = {}
ary1['smile'] = ':-)'
一方配列はというと、
var ary2 = [];
ary2[0] = ':-)';
そっくりですねー(°Д°)。
でも上の例は一番混乱しやすい書き方をしています。 

ちょっとだけなのに、大きい違い。
ここの大きな違いって、 最初のカッコっこのところなんですね。
 {}と[] 
じつはこのカッコの違いで、生成してるものが違ってくるんです。

上の例をわかりやすく書きなおすと、 次のようになります。
var ary1 = new Object();
ary1['smile'] = ':-)'


var ary2 = new Array();
ary2[0] = ':-)';
つまり、どちらもオブジェクトを生成していて、
その生成しているオブジェクトが違っていたということなんです。
ArrayとObjectはどう違うか。
Arrayオブジェクトは配列用のオブジェクトで、 配列用のlengthも備えています。
一方、Objectはただ連想配列っぽく使える、
空のオブジェクト(prototypeをのぞいて)です。 もっとわかりやすく書けば、
var ary1 = new Object();
ary1.smile = ':-)'
console.log(ary1['smile']);
という感じになりますね。
連想配列は、オブジェクトを生成して、
そのプロパティに値を設定していただけなんです。
つまり連想配列でlengthがとれないのは...
配列は配列用のオブジェクトとしてつくられているので、
lengthがとれて、 連想配列はそれっぽく使えるオブジェクトを、
 連想配列っぽく使ってるだけなので、lengthはとれないってことがわかると思います。
おまけ。consoleの紹介





こんな風に書いてみて、 Google Chromeで開いて、真っ白な画面上で右クリック、 『要素の検証』を選択するとウィンドウが開きます。 そこの『console』タブを開いてみると、 console.dirに設定したオブジェクトをツリー上に展開してみれますので、試してみて下さい。

2012年1月10日火曜日

IEが嫌いな理由〜DOM level2の独自仕様...orz〜

おぅふ、IEで動かない
そういうことが何度あっただろうか。
そんな経験を積み重ねいつしかIE嫌いになってました。
DOM level2の仕様で
IEがなぜか独自仕様を展開し、
XMLやHTMLを操作するためのAPIのDOMのlevel2で、
他のブラウザと違っていることがそもそもの原因。
たとえば、
addEventListnerではなく、IEではattachEvent
こんなメジャーなとこですら違ってる。
対処方法として
javascriptファイルの先頭に、
↓↓こんな感じのをしこんで、おくっていうのはどうでしょうか。

var setFloat = ( "styleFloat" in document.getElementsByTagName("body")[0].style ?
               function(element, value) {
                   element.style.styleFloat = value;
               } :
               function(element, value) {
                   element.style.cssFloat = value;
               });
var addEvent = (window.addEventListener ?
               function(element, type, func) {
                   element.addEventListener(type, func, false);
               } :
               function(element, type, func) {
                   element.attachEvent('on' + type, func);
               });
addEvent(window, "load",initializeSelect )
イベントの追加と、cssのfloatの設定について。

2011年12月25日日曜日

複数列のセレクトボックス~そう見えれば、それは本物よ~

だれがセレクトボックスは1列だって決めたの?
たしかにそうね。
あなたが与えられたものを全てだと思い込んでるなら、
そういうことになるわよね。

こんな感じで、1列しか表示しない。
ええ。たしかに。

女の化粧は詐欺かしら?
私はそうは思わない。
自分を表現するための手段よ。
なら、セレクトボックスもおんなじ。
自分の可能性を広げるために、そういう風にしちゃえばいいのよ。
たとえば、こんな風に。


そう見える。ただそれだけなの。
方法は単純。
(1)セレクトボックスのoptionを全部とりだす。
(2)ひとつずつoptionの値をdivに設定していく。optionのvalueはdivにvalue属性なんかを勝手につけて。
(3)それをm行ずつ作って、列divで囲む。
(4)列divを左からfloatさせて、wrapperのdiv内に順番に配置する。
ただこれだけで、見かけは完成。

振る舞いもそれらしくね。
でも、見かけだけちゃ簡単にあきられちゃう。
それなりに振る舞いなさい。
(1)mouseoverされたら背景が紺色になって、mouseoutされたら背景が白色になる。
(2)クリックされたら、一倍外部のwrapperのdivを消去する。
(3)クリックされた要素が選択された状態にする。

これが私のやり方


2011年12月20日火曜日

追記できるセレクトボックス~はんぐりーな僕たちへ~

与えられただけじゃ物足りない!
もっと、はんぐりーなんだ僕たちはっ(°Д°)!
そんな僕たちにぴったりなのが、
ダブルクリックすると追記できるセレクトボックス。

サンプルを示す
何より、サンプルみたらすぐわかるので。
こんな感じ↓↓




セレクトボックスをダブルクリックするか、
セレクトボックス上でキーボードを押す。

どうやって作ってるのか
作り方は単純で、
(1)セレクトボックスのダブルクリックのイベントに、
『テキストボックスを表示して、セレクトボックスを隠す』処置を設定する。
(2)そのテキストボックスからフォーカスが外れたら、
そのテキストボックスの中にあった文字をセレクトボックスの末尾に追加
(selectのoptionを生成して、selectに追加)
あとはテキストボックスを削除。

っていうだけ。

ソースを眺める。
ソースは↓↓こんな感じ。
/**
 * 追記可能なセレクトボックスを生成する。
* 目的のセレクトボックスのイベントにこの関数を追加することで、
* そのセレクトボックスに当該イベントが発生したとき
* テキストボックスを表示する。
* そのテキストボックスからフォーカスがはずれれば、
* 目的のセレクトボックスに、
* テキストボックスに入力された値が追記されている。
* 
 * @example 
 * var prefecture= document.getElementById("prefecture_id");
 * prefecture.addEventListener("dblclick", addEditableSelect, false);
 * 
 */
function addEditableSelect(){
    var newOptionText;
    // テキストボックス生成
    newOptionText = document.createElement("input");
    newOptionText.type = "text";
    newOptionText.value = "";
    newOptionText.style.width = "100px";
    // フォーカスが外れたとき(2)の処理
    addEvent( newOptionText
            , "blur"
            , (function(elm, pSelect){   
                return function(){
                           addTextValueToSelect(elm, pSelect);
                       }
            })(newOptionText, this));
    this.parentNode.appendChild(newOptionText);
    newOptionText.focus();
    this.style.display="none";
}

/**
 * テキストボックスの値を、セレクトボックスに追加する。
* optionもvalueも同じ値である。
 * 
 * @param {Object} textValue テキストボックスオブジェクト
 * @param {Object} originalSelect セレクトボックスオブジェクト
 * 
 * @example 
 * addTextValueToSelect(newOptionText, pSelect);
 * 
 */
function addTextValueToSelect(textValue, originalSelect){
    if (textValue) {
        var addedOption;
        if (textValue.value != "") {
            // テキストボックスに入ってた値を、セレクトボックスの末尾に追加。
            addedOption = document.createElement("option");
            addedOption.value = textValue.value;
            addedOption.appendChild(document.createTextNode(textValue.value));
            addedOption.style.backgroundColor = "#f0f0ff";
            addedOption.selected = true;
            originalSelect.appendChild(addedOption);
        }
        textValue.parentNode.removeChild(textValue);
        originalSelect.style.display="inline";
    }
}

function initializeSelect(){
    var prefecture= document.getElementById("prefecture_id");
    // ダブルクリックのイベントに追加。
    addEvent(prefecture, "dblclick", addEditableSelect);
    // キーボードを押したときのイベントにも追加。
    addEvent(prefecture, "keypress", addEditableSelect);
}

var addEvent = (window.addEventListener ?
               function(element, type, func) {
                   element.addEventListener(type, func, false);
               } :
               function(element, type, func) {
                   element.attachEvent('on' + type, func);
               });
addEvent(window, "load",initializeSelect );


まあ、たしかに。
セレクトボックスか、テキストボックス
をならべて表示して、
どっちの値を採用するのか
サーバ側で判断してもいいんですけどね。