目次

読み込むだけでWebページの「おもてなし度」を向上 - Pryn.js & Pryn.css

コメントと更新履歴はゼロと無限の間のログ » Pryn.js & cssへどうぞ。

よく使う初期設定用のJavaScriptとCSSをまとめたもの。
主にユーザビリティの向上が目的。JavaScriptとCSSを読み込めば、あとは自動で設定される。
使いやすくするためにMITライセンスで公開した。

主な効能

Pryn.js

Pryn.css

サンプル

Pryn.js & Pryn.cssのサンプル

※サンプルで使っているのは最新のBeta版の場合あり

ライセンス

MITライセンスで。
内部的に使っているYUIのCSSはBSDライセンス

Pryn.cssではGoogleによってホスティングされているYUIのCSSをimportする。これについてはGoogle AJAX Libraries APIの利用規約も確認のこと。

ダウンロード

pryn.js

pryn.css

※ダウンロードのリンク先が最新のBeta版の場合あり

ソースコード

Pryn.js

/**
 *  Pryn.js
 *  @see       http://0-oo.net/sbox/javascript/pryn-js-css
 *  @version   0.2.4
 *  @copyright 2007-2009 dgbadmin@gmail.com
 *  @license   http://0-oo.net/pryn/MIT_license.txt (The MIT license)
 *
 *  連携するCSSクラス
 *    (a, form).open            別Windowで開く
 *    (a, form).same-window     同じWindowで開く
 *    (input, textarea).focused 入力状態になった要素
 *    (input, textarea).ready   デフォルトでfocusを当てる要素
 *    (input, textarea).hint    ヒント付テキストボックス
 *                               ・入力後のclassは ex-hint になる
 *                               ・ヒントはtitle属性にセットしておく
 *    (input, textarea).copy    コピー用テキスト
 *    (input, img).clickable    クリックできる要素
 *    table.stripes             シマシマにしたい表
 *    tr.stripe                 シマシマにされた行
 */
 
 
/** IE高速化 @see http://d.hatena.ne.jp/amachang/20071010/1192012056 */
/*@cc_on _d = document; eval("var document = _d")@*/
 
 
/***** 組み込みオブジェクトの拡張 *****/
 
/**
 *  文字列のtrim
 *  @return trim後の文字列
 */
String.prototype.trim = function() {
    return this.replace(/(^\s+|\s+$)/g, "");
};
/**
 *  1単語を表す正規表現を取得する
 *  @return 正規表現オブジェクト
 */
String.prototype.word2regexp = function() {
    return new RegExp("(^|\\s)" + this + "(\\s|$)", "ig");
};
/**
 *  子要素全てに関数の処理を実行する
 *  @param  Object  it  子要素をループ処理できるオブジェクト
 */
Function.prototype.foreach = function(it) {
    for (var i = 0, len = it.length; i < len; i++) {
        this(it[i]);
    }
};
 
 
/***** グローバル関数/オブジェクト *****/
 
/**
 *  Firebugが無い場合はconsole.log()を空振りさせる
 */
window.console = window.console || {log: function(s){}, dummy: true};
 
/**
 *  html要素をidで取得する(既に$()があればそちらを使う)
 *  @param  String  id
 *  @retrun html要素
 */
var $ = $ || function(id) {
    return document.getElementById(id);
};
/**
 *  html要素をタグ名で取得する
 *  @param  String  tagName
 *  @retrun NodeList
 */
function $T(tagName) {
    return document.getElementsByTagName(tagName);
}
/**
 *  ページを遷移する
 *  @param  String  url
 */
function go(url) {
    location.href = url;
}
 
 
/***** Pryn *****/
 
var Pryn = {
    /** ページ表示時のユーザビリティ向上用の設定 */
    Init: {
        /** 別ドメインへの遷移を別Windowで開くかどうか */
        keepThisSite: true,
 
        /** formの二重送信防止を解除するまでの時間(秒) */
        frozenTime: 5,
        /** formの二重送信防止対象 */
        frozen: [],
 
        /** label要素と対応させるidの番号 */
        labelForIdNo: 0,
        /** label要素と対応させるidのプリフィクス */
        labelForIdPrefix: "pryn-label-for-id-",
 
        /** imageボタンの配列 */
        inputImages: []
    }
};
 
/**
 *  イベントを追加する
 *  @param  HTMLElement elm
 *  @param  String      eventName
 *  @param  Function    fnc
 */
Pryn.addEvent = (function(){
    if (window.attachEvent) {
        return function(elm, eventName, fnc){
            //IEでもfunction内でthisで自分(elm)を参照できるようにする
            elm.attachEvent("on" + eventName, function(){ fnc.call(elm); });
        };
    }
    //Safari3.0.x以前はDOMContentLoadedに未対応
    var webKit = navigator.userAgent.match(/AppleWebKit\/([0-9]+)/);
    var oldWebKit = (webKit && webKit[1] < 525);
    return function(elm, eventName, fnc){
        if (elm == window && eventName == "load" && !oldWebKit) {
            document.addEventListener("DOMContentLoaded", fnc, false);
        } else {
            elm.addEventListener(eventName, fnc, false);
        }
    };
})();
 
/**
 *  リンクのURLからドメイン部分を取り出す
 *  @param  HTMLElement link
 *  @return メイン(ドメインを含んでいない場合はnull)
 */
Pryn.getDomain = function(link) {
    var href = link.href;
    if (href.match(/^http(|s):/i)) {
        return href.split("/")[2];
    } else {
        return null;
    }
};
 
 
/***** Pryn.ClassAccessor *****/
 
/**
 *  html要素のclass属性へのアクセサクラス
 *  @param  HTMLElement elm
 */
Pryn.ClassAccessor = function(elm) {
    /**
     *  html要素にclassがあるか確かめる
     *  @param  String  cName
     */
    this.hasClass = function(cName) {
        var cn = elm.className;
        return (cn && cn.match(cName.word2regexp()));
    };
    /**
     *  html要素にclassを追加する
     *  @param  String  cName
     */
    this.addClass = function(cName) {
        if (!this.hasClass(cName)) {
            elm.className += " " + cName;
        }
    };
    /**
     *  html要素からclassを削除する
     *  @param  String  cName
     */
    this.removeClass = function(cName) {
        elm.className = elm.className.replace(cName.word2regexp(), " ").trim();
    };
    /**
     *  html要素のclassを変更する
     *  @param  String  oldClass
     *  @param  String  newClass
     */
    this.convertClass = function(oldClass, newClass) {
        this.removeClass(oldClass);
        this.addClass(newClass);
    };
};
 
 
/***** Pryn.Init *****/
 
/**
 *  表をシマシマにする(奇数番目のtr要素のclassに stripe を追加する)
 *  シマシマにしたくない行(見出し行など)はthead要素で囲っておくこと
 *  @param  HTMLElement table    table要素
 */
Pryn.Init.stripeTable = function(table) {
    if (!(new Pryn.ClassAccessor(table)).hasClass("stripes")) {
        return;
    }
    var rows = table.getElementsByTagName("tbody")[0].getElementsByTagName("tr");
    for (var i = 0, len = rows.length; i < len; i += 2) {
        (new Pryn.ClassAccessor(rows[i])).addClass("stripe");
    }
};
/**
 *  formの子要素のstyle等の設定
 *  CSSにてclass "clickable" "focused" のstyleを定義すること
 *  @param  HTMLElement fElm    formの子要素
 */
Pryn.Init.setStyles = function(fElm) {
    var acs = new Pryn.ClassAccessor(fElm);
    if (acs.hasClass("ready")) {
        fElm.focus();               //デフォルトでfocusを当てる
    }
    var type = fElm.type;
    if (fElm.tagName == 'TEXTAREA' || type.match(/^(text|password)$/i)) {
        Pryn.Init.setTextStyle(fElm, acs);
    } else if (fElm.tagName == 'INPUT' && type != "hidden") {
        acs.addClass("clickable");  //ファイルアップロードはFirefoxは非対応
        if (type == "image") {
            Pryn.Init.setSmartImage(fElm);
            Pryn.Init.inputImages.push(fElm);
        }
    }
};
/**
 *  テキスト入力系要素のstyle等の設定
 *  @param  HTMLElement         fElm    formの子要素
 *  @param  Pryn.ClassAccessor  acs
 */
Pryn.Init.setTextStyle = function(fElm, acs) {
    Pryn.addEvent(fElm, "focus", function() {   //フォーカスされた時
        if (!fElm.readOnly) {
            acs.addClass("focused");
        }
        if (acs.hasClass("hint")) {             //ヒント付テキストボックス
            this.value = "";
            acs.convertClass("hint", "ex-hint");
        } else if (acs.hasClass("copy")) {      //コピー用テキスト
            setTimeout(function() { fElm.select(); }, 100); //selectし損ねるのを防ぐ
        }
    });
    Pryn.addEvent(fElm, "blur", function() {    //フォーカスが外れた時
        acs.removeClass("focused");
        if (acs.hasClass("ex-hint") && !fElm.value) {   //ヒント付テキストボックスで未入力
            acs.convertClass("ex-hint", "hint");
            fElm.value = fElm.title;
        }
    });
    if (acs.hasClass("hint")) {                 //ヒント付テキストボックス
        if (fElm.value && fElm.value != fElm.title) {   //既に値がある場合
            acs.convertClass("hint", "ex-hint");
        } else {
            fElm.value = fElm.title;            //title属性の値を表示
        }
    } else if (acs.hasClass("copy")) {          //コピー用テキスト
        fElm.readOnly = true;
    }
};
/**
 *  別Windowで開くリンク・formの設定
 *  @param  HTMLElement link    a要素 or form要素(自動判断にはhref属性が必要)
 */
Pryn.Init.setTarget = function(link) {
    var acs = new Pryn.ClassAccessor(link);
    var ext = false;
    if (Pryn.Init.keepThisSite && !acs.hasClass("same-window")) {
        var next = Pryn.getDomain(link);
        ext = (next && next != location.hostname);   //外部へのリンクかどうか
    }
    if (ext || acs.hasClass("open")) {
        link.target = "_blank";
    }
};
/**
 *  formの二重送信防止設定
 *  @param  HTMLElement frm
 */
Pryn.Init.setSmartSubmit = function(frm) {
    Pryn.addEvent(frm, "submit", function() {
        Pryn.Init.setSubmitStyles.foreach(this);
       //imageボタンはformの子要素にならないので別途処理
        Pryn.Init.setSubmitStyles.foreach(Pryn.Init.inputImages);
    });
    frm.href = frm.action;          //別Windowで開くための前処理
    Pryn.Init.setTarget(frm);       //必要に応じて別Windowで開く
};
/**
 *  formのsubmit時の処理
 *  @param  HTMLElement formの子要素
 */
Pryn.Init.setSubmitStyles = function(fElm) {
    if ((new Pryn.ClassAccessor(fElm)).hasClass("hint")) {  //送信前にヒントをクリア
        fElm.value = "";
    }
    //textareaはFirefoxで戻ったときに初期値が消えるので除外
    if (fElm.disabled || fElm.tagName == "TEXTAREA") {
        return;
    }
    //操作不可にして、しばらくしたら戻す
    setTimeout(function() { fElm.disabled = true; }, 1);
    setTimeout(function() { fElm.disabled = false; }, Pryn.Init.frozenTime * 1000);
    Pryn.Init.frozen.push(fElm);
};
/**
 *  label要素のクロスブラウザ対応(Safari2以前は非対応)
 *  @param  HTMLElement label
 */
Pryn.Init.setSmartLabel = function(label) {
    if (!label.htmlFor) {
        var input = label.getElementsByTagName("INPUT")[0];
        if (!input) {
            return;
        } else if (!input.id) {
            input.id = Pryn.Init.labelForIdPrefix + Pryn.Init.labelForIdNo++;
        }
        label.htmlFor = input.id;   //IE6ではfor属性の省略ができない
    }
    //IEはlabel要素内のimg要素をクリックしてもチェックが付かない
    var images = label.getElementsByTagName("IMG");
    var len = images.length;
    if (!len) {
        return;
    }
    var clickIt = function() { document.getElementById(label.htmlFor).click(); };
    for (var i = 0; i < len; i++) {
        Pryn.addEvent(images[i], "click", clickIt);
    }
};
/**
 *  img要素のtitleとstyleの設定
 *  CSSにてclass "clickable" のstyleを定義すること
 *  @param  HTMLElement img
 */
Pryn.Init.setSmartImage = function(img) {
    //title属性をセット(IEの動きに合わせる)
    if (!img.title && img.alt) {
        img.title = img.alt;
    }
    if (img.onclick) {
        (new Pryn.ClassAccessor(img)).addClass("clickable");
    }
};
 
/**
 *  初期処理(見た目の影響のあるものを優先)
 */
Pryn.addEvent(window, "load", function() {
    Pryn.Init.stripeTable.foreach($T("table"));
 
    Pryn.Init.setStyles.foreach($T("input"));
    Pryn.Init.setStyles.foreach($T("textarea"));
    Pryn.Init.setStyles.foreach($T("button"));
 
    Pryn.Init.setTarget.foreach($T("a"));
 
    Pryn.Init.setSmartSubmit.foreach($T("form"));
 
    Pryn.Init.setSmartLabel.foreach($T("label"));
 
    Pryn.Init.setSmartImage.foreach($T("img"));
});
/**
 *  formの二重送信防止の凍結解除
 */
Pryn.addEvent(window, "unload", function() {
    (function(fElm) { fElm.disabled = false; }).foreach(Pryn.Init.frozen);
});

Pryn.css

@charset "UTF-8";
/**
 *  Pryn.css
 *  @see       http://0-oo.net/sbox/javascript/pryn-js-css
 *  @version   0.2.4
 *  @copyright 2007-2009 dgbadmin@gmail.com
 *  @license   http://0-oo.net/pryn/MIT_license.txt (The MIT license)
 */
 
 
/***** YUIのimport *****/
 
/**
 *  Googleによるホスティングを利用
 *  @see http://developer.yahoo.com/yui/articles/hosting/
 *  @see http://code.google.com/intl/ja/apis/ajaxlibs/documentation/#yui
 */
@import "//ajax.googleapis.com/ajax/libs/yui/2.7.0/build/reset-fonts-grids/reset-fonts-grids.css";
@import "//ajax.googleapis.com/ajax/libs/yui/2.7.0/build/base/base-min.css";
 
/**
 *  YUI適用後の調整
 */
body{
    margin: 0;
}
input, textarea{
    margin-right: 1px;
    padding: 1px;
}
select{
    margin-right: 1px;
}
 
/**
 *  YUIをSticy Footer化
 *  @see http://0-oo.net/sbox/css-small-box/yahho-sticky-footer
 */
html, body{
    height: 100%;
}
div#doc, div#doc2, div#doc3, div#doc4, div#custom-doc{
    position: relative;
    min-height: 100%;
    _height: 100%;          /* for IE6 */
}
div#ft{
    position: absolute;
    bottom: 0;
    width: 100%;
}
 
/* フッターの高さはサイトに合わせて変えること */
div#bd{
    padding-bottom: 4em;    /* div#ftの高さと同じか、それより大きくする */
}
div#ft{
    height: 3em;
}
 
 
 
/***** 汎用的なstyle *****/
 
html{
    overflow-y: scroll;     /* Firefox:常に縦のスクロールバーを表示 */
}
select, label, button, input.copy, textarea.copy, .clickable{
    cursor: pointer;        /* マウスカーソルを手にする(IE7-:選択リスト非対応) */
}
select[disabled], input[disabled], textarea[disabled], button[disabled]{
    cursor: not-allowed;    /* マウスカーソルを禁止マークに(IE6:非対応、IE7+:一部のみ) */
}
textarea{  
    overflow: auto;         /* IE:スクロールバー不要なら非表示 */
}  
input, textarea{
    ime-mode: active;       /* IME On */
}
input.han, input.number, textarea.han, select{
    ime-mode: inactive;     /* IME Off */
}
input[type="password"]{
    ime-mode: auto;         /* Firefox:パスワードでautoだとIME不可になる */
}
input.number{               /* 数値 */
    text-align: right;
}
.left{
    float: left;
}
.right{
    float: right;
}
.center{
    text-align: center;
}
.clear{                     /* floatのクリア */
    clear: both;
}
/**
 *  Googleカスタム404ページ対応
 *  @see http://www.google.com/support/webmasters/bin/answer.py?hl=jp&answer=100044
 */
#goog-wm li.search-goog{
    display: block;
}
 
 
/***** サイトごとのstyle(サイトに合わせて上書きする) *****/
 
div#bd{
    font-size: 116%;        /* @see http://developer.yahoo.com/yui/fonts/#using */
    line-height: 1.7;       /* 単位を付けない */
}
div.error, span.error{      /* 入力エラーメッセージのフォント */
    color: #f00;
    font-weight: bold;
}
input.error, textarea.error, select.error{
    background-color: #f99; /* 入力エラーの要素の背景色(Firefox:check系非対応) */
}
input.focused, textarea.focused{
    background-color: #feb; /* 入力要素のfocus時の背景色 */
}
input.hint, textarea.hint{  /* テキストボックス内の説明 */
    color: #999;
}
input.copy, textarea.copy{
    background-color: #ddd; /* コピー用テキストの背景色 */
}
table.stripes tr.stripe{
    background-color: #eee; /* シマシマにされる行 */
}
optgroup{
    color: #999;
}
option{
    color: #000;            /* optgroupから引き継ぐcolorを元に戻す */
}