with the flow

WEBプログラマを目指すWEBデザイナーが書き綴る開発日誌のようなもの

RFC非準拠(@前にドット、ダブルドットがある)のアドレスにメールが送れない問題を解決する(PHPのSendmail編)

PHPSendmail関数でメール送信しているWEBアプリで、
稀に送信自体がされないという現象が起こっていた為、原因を探っていたら分かったことです。。

結論としては、
・メールアドレスの@直前にドットがある
・または@より前のどこかにダブルドットがある
場合は、メールアドレスの形式としてSendmailの処理が認識せず、内部で送信処理前に弾かれていたことが原因。

abc.@example.com(“.”をローカル部の末尾に使用している)
abc..123@example.com(“.”が連続している)

何故認識しないかというと、上記はメールアドレスのRFC的には非準拠な、「デタラメ」なアドレス扱いになるから。
(つまり、Sendmailの処理の方が正しい)

ただ、auとdocomoは2009年頃までこの形式のメールアドレスの取得・変更が行えていた経緯があります。
auの場合、携帯だけでなく、一時期はプロバイダ(dion.ne.jp と auone-net.jp)でもこの形式のメールアドレスが取得できていたようです(ここで知りました)。

主に海外からのスパムメール対策に一定の効果があったことから、
自分の周りでも結構上記の形式にしていた友人がいました。

ただ、実害として、メジャーなメールクライアントOutlookやWindows Mailでメールが送られないという事態になっていました。

参考:「メール アドレスの @ の直前にピリオドがあるなど RFC に準拠していない宛先に Outlook からメールを送信すると、配信不能のメールが返され送信できない」
http://support.microsoft.com/kb/940620/ja

今確認した限りでは以下の様な感じでした。

HotmailやOutlook、Windows Mail: 送信自体ができない。
Gmail: 送信は一応出来るようになっているが、アドレスとしては取得できない。
iPhone(OS5): RFC非準拠のアドレスに送信しようとするとアラート(警告だけで送ることは出来る)。

対策としては、Sendmail関数の内部処理自体を変更してしまうという方法を取りました。

主にiOSで入力補助機能をオフにする(スペルチェック, 自動補完,先頭文字の自動大文字化)

以下のコードで可能。

<input type="text" name="hoge" autocorrect="off" autocomplete="off" autocapitalize="off">

デモ: 以下を実際にiOSなどでフォーカスしてみて下さい。

先頭文字の大文字化はAndroidでは行われないことが多いのでiOS限定で効果を発揮する場合がほとんど。

autocorrect: スペルチェック機能
autocomplete: 自動補完
autocapitalize: 先頭文字を大文字にする

Android実機から自PCのlocalhost(開発環境)へアクセスするには?

結論から書くと、調べた限りでは以下の方法しかなかった。。。

Android実機→(セキュアなWifiネットワーク)←ローカルPC

Android実機→WEBサーバ→(SSHでポート転送)→ローカルPC

Android実機からUSBケーブルでPCにつなぐことは出来るけど、
そもそも開発環境が入っているローカルPCと通信する手段自体が用意されていない。
初めこれで出来るんじゃないかという淡い期待を抱いて色々試したけど駄目だった。

なお、ローカルPCでAVD(Androidエミュレータ)を立ち上げている時は、
10.0.2.2でローカルPCのlocalhostにアクセスできるとのこと。

参考: http://stackoverflow.com/questions/3378501/how-to-browse-localhost-on-android-device

CSS3の-ms-linear-gradientが、いつの間にかいらない子になってた

最近はCSS3のlinear-gradientをよく使うようになってきたこともあり、
今linear-gradientはどう書くのが適切なのかを調べてみました。


今までは、毎回コツコツ書いてられないので、ColorZillaのCSS Gradient Generatorを使ってたのですが、いかんせん長い!
このサイトでグラデーション作るとこんな感じのコードになります↓

.gradient {
  background: url(bg_sample-gradient.png) 0 0 repeat-x;/* Old browsers */
  background: -moz-linear-gradient(top,  #ffffff 0%, #dddddd 100%); /* FF3.6+ */
  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(100%,#dddddd)); /* Chrome,Safari4+ */
  background: -webkit-linear-gradient(top,  #ffffff 0%,#dddddd 100%); /* Chrome10+,Safari5.1+ */
  background: -o-linear-gradient(top,  #ffffff 0%,#dddddd 100%); /* Opera 11.10+ */
  background: -ms-linear-gradient(top,  #ffffff 0%,#dddddd 100%); /* IE10+ */
  background: linear-gradient(to bottom,  #ffffff 0%,#dddddd 100%); /* W3C */
  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#dddddd',GradientType=0 ); /* IE6-9 */
}


長すぎ。。。コピペじゃないとやってられないです。


ということで、実はもういらないものがあるんじゃないかと思って調べたら、
結果的にはこんな感じに短くできました。
"-ms-linear-gradient"が必要なくなったというのが大きいです。

.gradient {
  background: url(bg_sample-gradient.png) 0 0 repeat-x;/* IE6-9, other old browsers */
  background: -moz-linear-gradient(top, #fff, #ddd);/* FF3.6+ */
  background: -webkit-linear-gradient(top, #fff, #ddd);/* Chrome10+, Safari5.1+ */
  background: -o-linear-gradient(top, #fff, #ddd);/* Opera 11.10+ */
  background: linear-gradient(to bottom, #fff, #ddd);/* IE10+, W3C */
}


以下に実際に作ってみました。Chrome10~, Firefox3.6~, Opera11.10~, IE10などではグラデーションが見えるはずです(IEは画像作ってないのでborderしか映りません)。



削除したものとその理由

"filter"(IE6-9)
→3色以上グラデーションできない、このfilterかけたボックスはoverflow:hiddenな状態となる罠があり、子要素をはみ出させられない...などなどこいつには色々と言いたい事がある為、問答無用で削除。
画像をfallbackには使いたくないけどfilterよりはまだマシということで。


”-ms-linear-gradient"
→IE10 Consumer Previewでは有効だったが、IE10 Release Previewでは必要なくなっているっぽい。このブログで触れられていたので確認してみると、確かにIE謹製のGradient Generatorで、linear-gradientの所に "IE10 Release Preview"と書いてある。つまり、リリース時には対応するよということっぽい。
また、HTML5界の有名人PaulIrish氏によると、もうサポートしているブラウザがないので、"-ms-linear-gradient"は削除したとのこと。

Windows 8 Release Previewを実際に触った訳ではないので未確認情報ですが。おそらく大丈夫なはず。
#8/27追記: IE Blogのこの記事で、もうprefixつける必要がなくなったのでgradient markupの修正を推奨しますとしっかり書かれてました。


"-webkit-gradient(linear..."
→ Safari4.0~5.0、Chrome4.0~9.0に対応するつもりはないので削除。※iOS4.0.4~5.0、Android 2.1~3.0対策をするなら必要。

・・・ということで、既存ソースの膨大な直しが発生することが発覚。わーい。

これから作成するソースはこれで統一しよう。。。

シンプルなマウスオーバー時のツールチッププラグインを作ってみた

最近勉強してるjQueryでプラグイン作ってみた。
作り方などは以下を参考に。。。
http://net.tutsplus.com/articles/news/you-still-cant-create-a-jquery-plugin/

↑「jQueryプラグイン、まだ作れないんですか?」と、NET TUTSにしては挑発的なタイトルだったので思わずクリック。
そして実際は、実直に丁寧な解説をしているだけだったのでむしろ好感を持ってしまったw

ただ、初心者向けだからなのか、イベントオブジェクトのe.pageX,e.pageYはIEでは取得できないなどの問題があったので
ちょっくら修正して実装しなおしてみた。

あと、title要素の中身を空にすることで、ブラウザデフォルトのツールチップが出ないようにしてるけど、titleが消えると、後から他のプラグインで利用しようと思った時などに不便になる。
なのでdata-titleの中に入れなおしてみた。

あとはちょこちょこ効率化のための仕込みを行ったくらい。
これをベースに、暇を見てちょこちょこ改善していこうとおもってます。

JavaScript

(function($){
  $.fn.simpleTooltip = function(options) {
    var defaults = {
        background: '#fefefe',
        color: 'black',
        opacity: '0.9',
        anotherTitle: "data-title"
      },
      settings = $.extend({}, defaults, options),
      isIE = /*@cc_on!@*/false,//IE判定
      $tooltip = $('<div id="tooltip" />').appendTo('body').hide();
    
    this.each(function(){//thisは生のDOMエレメントそのもの。
      var $this = $(this),//$(this)はjQueryオブジェクト化されたthisのこと。
        title = this.title;//each内のthisは、順番の回ってきた該当要素(DOMエレメント)。
      if($this.is('a') && title != '') {//is関数はjQuery独自のものなので、$thisと書かないと機能しない。
        $this
          .removeAttr('title')
          .attr(settings.anotherTitle,title)//jquery.tipsyみたいに、titleは消さず、オリジナルのtitleとして残しておく
          .hover(function(e){
            if(isIE) {
              e = {//イベントオブジェクト内の値をIE用に書き換える
                pageX : event.clientX + (document.body.scrollLeft || document.documentElement.scrollLeft),
                pageY : event.clientY + (document.body.scrollTop || document.documentElement.scrollTop)
              };
            }
            $tooltip
              .text(title)
              .hide()
              .css({
                backgroundColor: settings.background,
                color: settings.color,
                opacity: settings.opacity,
                top: e.pageY + 10,
                left: e.pageX + 20
              })
              .fadeIn(250);
          }, function(){
            $tooltip.fadeOut(250);
          })
          .mousemove(function(e){
            $tooltip.css({
              top: e.pageY + 10,
              left: e.pageX + 20
            });
          });
      }
    });
    return this;
  };
})(jQuery);

CSS

#tooltip {
	position: absolute;
	float: left;
	max-width: 160px;
	padding: 10px;
	font-size: 100%;
	line-height: 1.3;
	border: 1px solid #aaa;
	-moz-border-radius: 3px;
	-webkit-border-radius: 3px;
	border-radius: 3px;
	-moz-box-shadow: 0 0 5px #888;
	-webkit-box-shadow: 0 0 5px #888;
	box-shadow: 0 0 5px #888;
}

以下のようなマークアップがある場合、
$('.tooltip').simpleTooltip(); とやることで実行可能。

    <a href="..." class="tooltip" title="ツールチップ内に乗せたい文章">リンク</a> 

jQueryでhover時の処理を指定時間だけ遅らせる方法

jQueryを使っているサイトで、以下のことをやりたいときにどうすればいいのか、調べてみました。


・特定要素にmouseenterした時に、別途作ったプルダウンで情報を出したい。

・そのプルダウン内には、マウスオーバー時にDBへAjaxでリクエストし、動的に取ってきたデータを入れたい。

・但しこれだと、マウスホバーするたびにリクエストされてしまう(マウスポインタがチラッと乗っかっただけでも行われる)。負荷軽減のため、mouseenterして一定時間経過後(500ミリ秒とか)にリクエストを出すようにしたい。

hoverIntentという有名なjQueryプラグイン使うと可能にはなるが、プラグイン使わずにもっと手軽にやりたいというわがまま付き。


やり方が悪いのか、普通にsetTimeout/clearTimeout使っても反映されない(タイマー自体はセットされるがmouseleave時にクリアされない)ので、いろいろ調べていると、data()メソッド使うといいということが分かりました。

$('#tg').hover(function() {
    var t = setTimeout(function() {
      //マウスオーバー時に行いたいAjax処理
    }, 500);
    $(this).data('timeout', t);
}, function() {
    clearTimeout($(this).data('timeout'));
});

上記コードで、IDが"tg"の要素にマウスが乗った時、500ミリ後に各種処理を実行できるようになります。

対象要素自体にdata()でタイマーをセットするとうまくクリアできるようです。

text-overflowはFirefox7から使える/複数行の時はJavaScriptで対応

まんまです。使えるようになってるのを最近知りました。

text-overflowはIEが6から独自仕様として実装していたものですが、
Microsoft珍しく素晴らしい先見性によって、CSS3の仕様にも組み込まれたものです。今までもwebkit,Operaでは使えていました。
それが、Firefox7からようやく実装されたということですね。

以下のクラスを作っておいて、横幅の指定された対象要素のクラスに追加してあげれば良いです。

.ellipsis {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  -webkit-text-overflow: ellipsis;
  -o-text-overflow: ellipsis;
}

はてなCSSが使えるようなので、以下実際にやってみますね。

わかりやすいようにボーダーで囲ってみます。

IE6〜,Firefox7.0〜,Opera,Chrome,Safariで見ると「…」が文末に表示されているはずです。

この文章は横幅400px以上になると省略記号が表示されます。この文章は横幅400px以上になると省略記号が表示されます。


これで今の主要ブラウザに対応できるというわけですね。

ただ、text-overflowは、文章が横1行の時しか使えないです。
当たり前ですが。


複数行に渡る文章の文字数を削りたいときはphpとかだとsubstr使ってこんな感じにするんでしょうし、
DBから抜いてくるときにLEFTで文字数指定したりするんでしょうけど、


・「行を含むp要素の高さが50pxを超えた時」など、ビジュアルを基準にして削りたい場合、マルチバイトの日本語だと全角と半角が入り乱れており、文字数をそのまま画面上の幅や高さとして考えることができない為難しい。

・文章の終端に「…」と書いたspan要素なんかを文字の上からz-indexで重ねて文字をもみ消す方法などもあるが、そもそも文章の背景が透明な場合(画像とか)は使えない。結局文字がはみ出すかどうかを判定するのにjs使うし。。。


といった問題があります。


このような場合は致し方ないので、JavaScriptで削るわけですが、みんなどうしてんだろ?と思ってgoogle先生に聞いてみました。
普通にググッても出て来ず、stackoverflowでやっと満足の行く回答を見つけたので、共有しておきます。


Insert ellipsis (…) into HTML tag if content too wide
http://stackoverflow.com/questions/536814/insert-ellipsis-into-html-tag-if-content-too-wide


詳しい内容はサイトを見ていただくとして、js部分だけ抜き出すと以下のようになります。

(function($) {
        $.fn.ellipsis = function()
        {
                return this.each(function()
                {
                        var el = $(this);

                        if(el.css("overflow") == "hidden")
                        {
                                var text = el.html();
                                var multiline = el.hasClass('multiline');
                                var t = $(this.cloneNode(true))
                                        .hide()
                                        .css('position', 'absolute')
                                        .css('overflow', 'visible')
                                        .width(multiline ? el.width() : 'auto')
                                        .height(multiline ? 'auto' : el.height())
                                        ;

                                el.after(t);

                                function height() { return t.height() > el.height(); };
                                function width() { return t.width() > el.width(); };

                                var func = multiline ? height : width;

                                while (text.length > 0 && func())
                                {
                                        text = text.substr(0, text.length - 1);
                                        t.html(text + "…");
                                }

                                el.html(t.html());
                                t.remove();
                        }
                });
        };
})(jQuery);

jQuery必須。

対象要素をコピーして、中身を一文字ずつ減らしていきその要素の高さがカットしたい高さと一致する部分を探すという方法。
bindされてるかもしれないイベントまでコピーする必要は無いので、jQueryのclone()ではなくJS標準のcloneNodeを使っているんだと解釈した。
ブラウザ・PCの性能が良くなった今だからこそできる力技ですね。

ただ一文字ずつ削っている為、対象要素が多いとさずがに負荷がかかります。
text = text.substr(0, text.length - 3)とかでもいいかもしれません。

#追記:
この方法は、記事のタイトルなど「出力される文字は基本的に少ないけど、時々多くなることがある」対象に対して使うのが吉。
記事本文などを削る場合は、削りたい文字数よりも若干多め(30文字以降削りたいなら35文字とか)にDBからLEFT使って吐き出しておいて、それを削るとかにしないと、ブラウザが悲鳴を上げるので注意。