【NYAGOS】Nyagos のプロンプトに git の現在の branch 名を表示する

2018/12/01 追記

前書き

「ちょっと作業するかー → コミットするかー → ここmasterだ!あびゃー!」って言うことがよくあります。

病院に行くお金と時間がないので、Nyagos のプロンプトに現在の branch 名を表示するようにしました。

しくみ

git で現在の branch 名を取得するにはgit rev-parse--abbrev-refオプションを渡すのが一番簡単です。

nyagos.evalに上記コマンドを渡せば OK です。当然 git で管理されていないフォルダだと怒られるので、エラー出力は捨てます。具体的にはこんな感じ。

local git_branch_name = nyagos.eval('git rev-parse --abbrev-ref HEAD 2>nul')

if (git_branch_name ~= '') then
  -- TODO: プロンプトにgit_branch_nameを表示する
end

実例

デフォルトの.nyagosでやるとしたら、こんな感じになります。

nyagos.env.prompt='$L'.. nyagos.getenv('COMPUTERNAME') .. ':$P$G'
share.org_prompter=nyagos.prompt

nyagos.prompt = function(this)
    local prompt_message = this
    -- プロンプトにgit_branch_nameを表示する
    local git_branch_name = nyagos.eval('git rev-parse --abbrev-ref HEAD 2>nul')
    if (git_branch_name ~= '') then
        -- もちろんgit_branch_nameに適当な色をつけたりしてもOK
        prompt_message = prompt_message .. ' [' .. git_branch_name .. ']'
    end

    -- ここで改行をくっつける
    prompt_massage = prompt_message .. '$_$$$s'

    return share.org_prompter('$e[36;40;1m'..prompt_message..'$e[37;1m')
end

まとめ

同じ要領で色んなバージョン管理システムのアレができるんですが、git 以外は特に使ってないのでこれで十分です。

毎回 git コマンドを叩くので若干重くなったりするのが難点です。

参考

【Atom】入れておきたいパッケージメモ 2015 年版

前書き

以前、【Atom】やっておきたい設定+入れておきたいパッケージメモと言う記事を書いたんですが、思った以上に閲覧、リンクされ、未だに読まれています。

それは別にいいんですが、今となっては流石に内容が古すぎて申し訳ないので、最新のおすすめパッケージを書いておきます。前回の記事と全く同じものもありますが、それだけ使えるってことです。

Atom 自体の更新によって必要性が薄くなったパッケージ

前回の記事には載せていたものの、Atom 自体のアップデートによってわざわざパッケージを入れなくても良くなったものがいくつかあります。

Japanese Wrap

Atom1.2 で CJK(Chinese Japanese Korean)の禁則処理が公式対応となったため、必要なくなりました。

むしろ導入するとバグが発生するとの情報もあるので、今まで入れてた人はアンインストールしておきましょう。

Save Session

デフォルトで前回開いていたプロジェクトやファイルを表示できるようになったので、不要になりました。

このパッケージの README にもProject Status: Deprecatedと記載されています。

Autocomplete Plus

必要なくなったのではなく、公式のパッケージとなったため、明示的にインストールする必要がなくなりました。(Atom をインストールすると最初からバンドルされている)

入れないと辛すぎるパッケージ

Auto Encoding

読み込んだファイルの文字コードを推測し、自動で Atom の設定を変更してくれます。

「えーマジ Shift-JIS!?」「キモーイ」「UTF-8 以外の文字コードが許されるのは小学生までだよねー」「キャハハハハ」なプロジェクトで大活躍します。

Color Picker

カラーコードを Atom 上でプレビューしながらゴニョゴニョできます。後述するPigmentsと組み合わせて使うと非常に便利です。

Docblockr

ブロックコメントを自動生成してくれます。

かなり便利…と言うか、これなしでブロックコメントを書こうとすると死ぬほど辛いです。

Pigments

カラーコードに応じた色を表示してくれたり、CSS 内のカラーコードをパレットとして保存しておいてくれたり、HEX から RGB に変換してくれたりと、これでもかってほど便利です。

LESS / SCSS などの変数や関数にも対応してます。無敵かよ。

Project Manager

頻繁に使うプロジェクトを登録することが出来ます。

保存方法は「プロジェクトを開く」→[Packages]→[Project Manager]→[Save Project]

呼び出すときはCtrl + Shift + Alt + P

Symbols Tree View

画面右側にシンボル(変数とか関数とか)のツリービューを出してくれます。

めちゃめちゃに長いソースを読まなきゃいけない時に頼りになります。

入れておくとそこそこ便利なパッケージ

Command Toolbar

「たまに使うんだけどキーバインドを覚えるほどではない」みたいなものを登録しておくと捗ります。

私は主にGit Plusと組み合わせて「キーバインドも git の引数もパッと思い出せないけどたまに使う」みたいなものを登録しています。

Git Plus

元々 Atom は Git に中途半端に対応しているんですが、中途半端すぎていらいらしてくるので、精神衛生上これを入れておくのが無難だと思われます。

ただ、ぶっちゃけた話、ほとんどの場合は Source Tree を使った方が早いし便利です。

Highlight Selected

文字列を選択すると同ファイル内の同じ文字列をハイライトしてくれます。

ソースをじっくりと追う時に非常にありがたい存在です。

Live Archive

Atom での編集履歴を保存しておき、簡単に比較することが可能です。

ぶっちゃけそんなに使わないんですが、本当にたまに役立ってくれます。

Minimapとそのプラグイン

コードのミニマップをちょろっと出してくれます。

これ自体は便利ともどうでもいいともなんとも言えないんですが、プラグインが色々と便利です。

Tree View Finder

画面左側の TreeView を拡張し、ファイルサイズや更新日付を表示してくれます。

ソート機能もつきます。個人的にはむしろそっちがメインです。

わりかしどうでもいい or 趣味的なもの

File Icons

アイコンがちょっとオシャレになります。デフォルトよりはファイルが一目で見分けられるので意外と便利です。

Japanese Menu

Atom のメニューバーや設定画面などを日本語化してくれます。

そんなに難しい英語は使われていないのでいらないっちゃいらないんですが、プロジェクト全員で使いたい!って時に、導入のハードルを下げる効果ぐらいはあるかもしれません。むしろ git の方がハードル高そう。

また、当然ですが、個別パッケージの設定画面なんかは一切翻訳されません。

テーマ

色々入れてみて気に入ったものを使えばいいと思います。私はatom-dark-ui-slimrailscast-themeを使っています。

まとめ

他にもいくつかパッケージを入れてるんですが、特定の言語用のパッケージ(atom-typescriptとか)なので、説明は割愛します。

【NYAGOS】Nyaos から Nyagos へ移行する

2018/12/01 追記

前書き

以前Nyagosをインストールしたものの、Nyaosにあった便利機能が色々なかったので結局 Nyaos を使い続けていたんですが、Nyagos の方も大分アップデートが進んで色々便利になったようなので、そろそろ移行しようかなと。

移行しようと思ったものの、(多分、まだまだ破壊的変更が入るからでしょうか)今までの設定とかどーやって移行すればいいのかって言う説明が無いに等しいので、適当にメモしておきます。

インストールした Nyagos

4.0.9_10 の 64bit 版をインストールしました。

設定ファイルの場所

今までは_nyaに書いており、読み込む場所はこんな風な説明がありました。

# _nya は NYAOS の初期設定ファイルです.
#   (1) NYAOS.EXE のあるディレクトリ
#   (2) %HOME% の下(未定義であれば %USERPROFILE%)
#   (3) カレントディレクトリ

Nyagos の場合は.nyagos%HOME%ないしは%USERPROFILE%へ置く必要があります。nyagos.exeのあるディレクトリの.nyagosは読み込みません。

マニュアルの「起動処理」にはnyagos.exeと同じディレクトリ内のnyagos.luaも読み込むと書かれていますが、こちらのファイルには先頭にこんなことが書かれています。

--------------------------------------------------------------------------
-- DO NOT EDIT THIS. PLEASE EDIT ~\.nyagos OR ADD SCRIPT INTO nyagos.d\ --
--------------------------------------------------------------------------

と言うわけで、おとなしく.nyagosを書いていきましょう。

環境変数の設定

Nyaos ではsetを使って環境変数を積んでいくことが可能でした。Nyagos でもsetを使って同様のことができますが、更に簡単に書けるようになっています。

デフォルトの.nyagosに書かれている内容を見てみましょう。

set{
    PROMPT='$L'.. nyagos.getenv('COMPUTERNAME') .. ':$P$G$_$$$s'
}

この中にぼんぼん設定したい環境変数をぶちこめば OK です。(ここじゃなくて別のところに書いてもいいですけど。)

今後の設定でもそうですが、何らかのパスを記載するときはLua のヒアドキュメント記法である[[ ]]でくくるとエスケープをしなくて済むので楽ちんです。

set{
    PROMPT='$L'.. nyagos.getenv('COMPUTERNAME') .. ':$P$G$_$$$s',
    LESS='-gj3R --no-init --quit-if-one-screen',
    LESSCHARSET='japanese',
    EDITOR=[[C:\Program Files (x86)\vim\vim74\vim.exe]]
}

プロンプトの文字列や色を変更する

若干ここがややこしくなっています。

Nyaos ではoption promptで指定していましたが、Nyagos ではsetPROMPTが表示する文字列、nyagos.promptで色付け、となっています。

デフォルト設定だとこんな感じになってますね。

-- Simple Prompt for CMD.EXE
set{
    PROMPT='$L'.. nyagos.getenv('COMPUTERNAME') .. ':$P$G$_$$$s'
}

-- Coloring Prompt for NYAGOS.exe
local prompter=nyagos.prompt
nyagos.prompt = function(this)
    return prompter('$e[36;40;1m'..this..'$e[37;1m')
end

Nyaos(もとい、_nya)と同じ設定にする場合は、$e~;1mの部分をnyagos.promptに持ってきます。

nyagos.prompt = function(this)
    return prompter('$e[36;40;1m'..this..'$e[37;1m')
end

あとはその他の部分をset内のPROMPTに設定してあげれば OK です。

set{
    PROMPT='[$P]$_$$ ',
}

alias の設定

基本的にはマニュアルに書いてあることをよく読んで.nyagosに書けば OK です。

デフォルトのエイリアスnyagos.d\aliases.luaにあるので参考にしましょう。

alias{
    ls='ls -oF $*',
    lua_e=function(args) assert(load(args[1]))() end,
    ["for"]='%COMSPEC% /c "@set PROMPT=$G & @for $*"',
}

私はもともと_nyaにこんなエイリアスを作ってました。

set MSYS=C:\MinGW\msys\1.0\bin
alias ls=$MSYS\ls.exe --color=auto --show-control-chars
alias ll=$MSYS\ls.exe -agoFh --time-style=+"%Y/%m/%d %H:%M" --color=auto --show-control-chars
alias find=$MSYS\find.exe
alias sjis=nkf.exe -s
alias git=git --no-pager

.nyagosに書き換えるとこんな感じですね。

local MSYS=[[C:\MinGW\msys\1.0\bin]]

alias{
    ls=MSYS..[[\ls.exe --color=auto --show-control-chars]],
    ll=MSYS..[[\ls.exe -agoFh --time-style=+"%Y/%m/%d %H:%M" --color=auto --show-control-chars]],
    find=MSYS..[[\find.exe]],
    sjis="nkf.exe -s",
    git="git --no-pager",
}

suffix の設定

マニュアルに設定方法が書いてあります。見出しはnyagos.argsfilterってなってますが…。

デフォルトのサフィックスnyagos.d\suffix.luaの一番下に書かれています。

suffix.pl="perl"
if nyagos.which("ipy") then
  suffix.py="ipy"
elseif nyagos.which("py") then
  suffix.py="py"
else
  suffix.py="python"
end
suffix.rb="ruby"
suffix.lua="lua"
suffix.awk={"awk","-f"}
suffix.js={"cscript","//nologo"}
suffix.vbs={"cscript","//nologo"}
suffix.ps1={"powershell","-file"}

これを見るだけでも何となくわかると思うので説明は割愛。

History の移行

Nyaos ではoption savehistを設定することで History を保存できましたが、Nyagos ではデフォルトで保存されるようになりました。

場所は~\AppData\Roaming\NYAOS_ORG\nyagos.historyです。

NyaosHistory は先頭にタイムスタンプが付与されていましたが、Nyagos ではそれがなくなりました。

適当な正規表現でタイムスタンプを削って保存すればそれだけで移行完了です。

Lua スクリプトのロード

nyagos.luaを読む限りloadfileなる関数にファイルパスを突っ込めばよさそうですが、普通にnyagos.dLua ファイルを入れておくのが一番いいと思います。

おまけ:gpath の移行

Nyagos には gpath がまだ実装されていません。

割と頻繁に使う機能なので是非とも欲しいのですが、そもそも Nyaos の gpath は Lua で書かれているので自分で移行してしまいます。

とは言え、流石に単純に_nya.dからコピペしただけで移行するのは不可能なので、ちょっとだけ直します。

nyagos.alias("gpath", function(args)
    local argv=args
    local shell=nyagos.ole.create_object_utf8("WScript.Shell")
    local sysEnv=shell:Environment("System")
    local path={}
    for p in string.gmatch(sysEnv:Item("PATH"),"[^;]+") do
        table.insert( path , p )
    end
    if argv[1] == "del" then
        if argv[2] then
            if string.match(argv[2],"^%d+$") then
                table.remove( path , argv[2] )
            else
                print("gpath del [POSITION]")
                return
            end
        else
            table.remove( path )
        end
    elseif argv[1] == "add" then
        if argv[2] then
            if argv[3] then
                if string.match(argv[2],"^%d+$") then
                    table.insert( path , argv[2] , argv[3] )
                else
                    print("gpath add [POSITION] DIRECTORY")
                    return
                end
            else
                table.insert( path , argv[2] )
            end
        else
            print("gpath add [POSITION] DIRECTORY")
            return
        end
    elseif argv[1] == "swap" then
        local tmp1 = path[tonumber(argv[2])]
        local tmp2 = path[tonumber(argv[3])]
        if tmp1 and tmp2 then
            path[ tonumber(argv[2]) ] = tmp2
            path[ tonumber(argv[3]) ] = tmp1
        else
            print("gpath swap POSITION-1 POSITION-2")
            return
        end
    else
        print("Usage:")
        print("  gpath add [POSITION] DIRECTORY")
        print("  gpath del [POSITION]")
        print("  gpath swap POSITION-1 POSITION-2")
        print("")
        for i=1,#path do
            print(i,path[i])
        end
        return
    end
    path = table.concat(path,";")
    sysEnv:__put__("Item","PATH",path)
    if sysEnv:__get__("Item","PATH") ~= path then
        print("Fail to set PATH="..path)
    end
end)

どれぐらい「ちょっと」なのか diff をとってみましょう。

< nyagos.alias("gpath", function(args)
<     local argv=args
<     local shell=nyagos.ole.create_object_utf8("WScript.Shell")
---
> function nyaos.command.gpath(...)
>     local argv={...}
>     local shell=nyaos.create_object("WScript.Shell")
62c62
< end)
\ No newline at end of file
---
> end

ざっとこんなもんです。

まとめ

と言うわけで、おおよそ 1 時間もあれば移行できました。

もっと色々カスタマイズしている人はさらに大変でしょうが頑張ってください。

【less】今更始める less の基本文法と tips(2) - mixin 編

2018/12/01 追記

前書き

前回の続きです。

outofmem.hatenablog.com

mixin

less の特色はほとんどこの mixin に集約されていると言っても過言ではありません。

mixin の宣言

宣言と言うほどでもなく、既に定義したセレクタを呼び出せる機能です。

// less
.mixin {
  color: #000;
}

div {
  .mixin;
}
/* css */
.mixin {
  color: #000;
}
div {
  color: #000;
}

クラスではなく ID でも呼べますが、あんまりやらないっぽいですね。

// less
#mixin {
  color: #000;
}

div {
  #mixin;
}
/* css */
#mixin {
  color: #000;
}
div {
  color: #000;
}

名前空間

mixin はネストされた要素もすべて継承します。

// less
.mixin {
  span {
    color: #000;
  }
  .hoge {
    color: #fff;
  }
}

div {
  .mixin;
}
/* css */
div span {
  color: #000;
}
div .hoge {
  color: #fff;
}

ある特定の要素だけを mixin したい場合はセレクタ > セレクタとすることで可能です。

// less
.mixin {
  span {
    color: #000;
  }
  .hoge {
    color: #fff;
  }
}

div {
  .mixin > .hoge;
}
/* css */
div {
  color: #fff;
}

この機能を使うことで、mixin に名前空間ライクなものを付与することができます。

// less
// ベンダプレフィックスを吸収するためのmixin
#vendor {
  .border-radius {
    -moz-border-radius: 4px;
    -webkit-border-radius: 4px;
    border-radius: 4px;
  }
}

div {
  #vendor > .border-radius;
}
/* css */
div {
  -moz-border-radius: 4px;
  -webkit-border-radius: 4px;
  border-radius: 4px;
}

!importantの扱い

mixin 時に!importantをつけると mixin される全プロパティに!importantが付与されます。

// less
#vendor {
  .border-radius {
    -moz-border-radius: 4px;
    -webkit-border-radius: 4px;
    border-radius: 4px;
  }
}

div {
  #vendor > .border-radius !important;
}
/* css */
div {
  -moz-border-radius: 4px !important;
  -webkit-border-radius: 4px !important;
  border-radius: 4px !important;
}

引数つきの mixin

mixin には引数をつけることが可能です。

// less
#vendor {
  .border-radius(@param) {
    -moz-border-radius: @param;
    -webkit-border-radius: @param;
    border-radius: @param;
  }
}

div {
  #vendor > .border-radius(4px);
}
/* css */
div {
  -moz-border-radius: 4px;
  -webkit-border-radius: 4px;
  border-radius: 4px;
}

複数の引数を渡す

引数は,;で区切って宣言することができます。

less としては;を推奨しています。理由は、引数にデフォルト値を与えるときに,を使えるからです。(デフォルト値については後述)

// less
#vendor {
  .border-radius(@left-top; @right-top; @right-bottom; @left-bottom) {
    -moz-border-radius: @left-top @right-top @right-bottom @left-bottom;
    -webkit-border-radius: @left-top @right-top @right-bottom @left-bottom;
    border-radius: @left-top @right-top @right-bottom @left-bottom;
  }
}

div {
  // 引数を渡す時も";"が使える
  #vendor > .border-radius(4px; 2px; 3px; 5px);
}

しかしまぁ、上記の記述だとちょっと冗長すぎます。@argumentsと言う暗黙的に定義される変数を使うことでもうちょっと楽ができます。

// less
#vendor {
  .border-radius(@left-top; @right-top; @right-bottom; @left-bottom) {
    -moz-border-radius: @arguments;
    -webkit-border-radius: @arguments;
    border-radius: @arguments;
  }
}

div {
  #vendor > .border-radius(4px; 2px; 3px; 5px);
}
/* css */
div {
  -moz-border-radius: 4px 2px 3px 5px;
  -webkit-border-radius: 4px 2px 3px 5px;
  border-radius: 4px 2px 3px 5px;
}

ぶっちゃけてしまうと、,ないしは;が含まれていなければ一つの引数とみなすので、例のようにベンダプレフィックスを吸収するだけとかであれば、わざわざ複雑なことをせずにこれでいいと思います。

// less
#vendor {
  .border-radius(@param) {
    -moz-border-radius: @param;
    -webkit-border-radius: @param;
    border-radius: @param;
  }
}

div {
  // 半角スペースでわたす
  #vendor > .border-radius(4px 2px 3px 5px);
}
/* css */
div {
  -moz-border-radius: 4px 2px 3px 5px;
  -webkit-border-radius: 4px 2px 3px 5px;
  border-radius: 4px 2px 3px 5px;
}

ちょっと厄介なのが、パラメータとして,を含める可能性があるもの。

例えばbox-shadowですかね。下記のようにするとコンパイル時に「引数の数が一致する mixin が見つからないよ」と怒られてしまいます。

// less
#vendor {
  .box-shadow(@param) {
    -moz-box-shadow: @param;
    -webkit-box-shadow: @param;
    box-shadow: @param;
  }
}

div {
  // 複数の値を渡したいが…。
  #vendor > .box-shadow(0 0 4px #f00, 0 0 12px #000);
}

less はさりげなく可変長引数...をサポートしているのでこれで…!と思うのですが、それだと,が消滅してしまいます。

// less
#vendor {
  .box-shadow(@param...) {
    -moz-box-shadow: @param;
    -webkit-box-shadow: @param;
    box-shadow: @param;
  }
}

div {
  #vendor > .box-shadow(0 0 4px #f00, 0 0 12px #000);
}
/* css */
div {
  -moz-box-shadow: 0 0 4px #ff0000 /* ここの","が消滅 */ 0 0 12px #000000;
  -webkit-box-shadow: 0 0 4px #ff0000 0 0 12px #000000;
  box-shadow: 0 0 4px #ff0000 0 0 12px #000000;
}

じゃあどうするのかですが、~を使ってエスケープしてしまいましょう。

// less
#vendor {
  .box-shadow(@param) {
    -moz-box-shadow: @param;
    -webkit-box-shadow: @param;
    box-shadow: @param;
  }
}

div {
  #vendor > .box-shadow(~"0 0 4px #F00, 0 0 12px #000");
}
/* css */
div {
  -moz-box-shadow: 0 0 4px #f00, 0 0 12px #000;
  -webkit-box-shadow: 0 0 4px #f00, 0 0 12px #000;
  box-shadow: 0 0 4px #f00, 0 0 12px #000;
}

可変長引数

説明が前後しますが、less は...を使うことで可変長引数とすることができます。

引数が可変長引数のみの場合は識別子をつけなくても構いません。その場合は@argumentsで取得しましょう。

// less
.hoge(...) {
  border: @arguments;
}

.fuga(@color; @margin...) {
  color: @color;
  margin: @margin;
}

div {
  .hoge(solid, 10px, #00f);
  .fuga(#000, 0, 1px, 0, 0);
}
/* css */
div {
  border: solid 10px #0000ff;
  color: #000000;
  margin: 0 1px 0 0;
}

引数のデフォルト値

mixin の引数に@識別子: 値としておくことでデフォルト値を設定できます。

このデフォルト値は他の引数の値でも問題ありません。

// less
.hoge(@color: #000; @background-color: @color) {
  color: @color;
  background-color: @background-color;
}

div {
  .hoge();
}

span {
  .hoge(#fff);
}

table {
  .hoge(#fff, #f00);
}
/* css */
div {
  color: #000000;
  background-color: #000000;
}
span {
  color: #ffffff;
  background-color: #ffffff;
}
table {
  color: #ffffff;
  background-color: #ff0000;
}

名前付き引数

mixin を作っていると引数がめちゃめちゃ多くなったり、デフォルト値があったりなかったりする mixin を作成したくなることもあります。

そう言うときに便利なのが名前付き引数です。

// less
.hoge(@color: #000; @border; @background-color: @color) {
  // 第二引数にだけデフォルト値なし(こんなもん作らないようにしましょう)
  color: @color;
  border: @border;
  background-color: @background-color;
}

div {
  // 名前付き引数を使わない場合、
  // @colorもちゃんと引数で渡さなくてはならない
  .hoge(#000, solid 10px #00f);
}

span {
  // 名前付き引数を使っているので
  // @colorと@background-colorはちゃんとデフォルト値になる
  .hoge(@border: solid 10px #00f);
}

table {
  // 名前付き引数の場合は順番がむちゃくちゃでもOK
  .hoge(@background-color: #f00, @color: #fff, @border: solid 10px #00f);
}
/* css */
div {
  color: #000000;
  border: solid 10px #0000ff;
  background-color: #000000;
}
span {
  color: #000000;
  border: solid 10px #0000ff;
  background-color: #000000;
}
table {
  color: #ffffff;
  border: solid 10px #0000ff;
  background-color: #ff0000;
}

ルールセットを引数として渡す

CSS のルールそのものを引数として渡すことも可能です。@keyframesとかで便利です。

// less
#vendor {
  .keyframes(@name, @prop) {
    @-moz-keyframes @name {
      @prop();
    }
    @-webkit-keyframes @name {
      @prop();
    }
    @-o-keyframes @name {
      @prop();
    }
    @keyframes @name {
      @prop();
    }
  }

  .animation(@param) {
    -moz-animation: @param;
    -webkit-animation: @param;
    -o-animation: @param;
    -ms-animation: @param;
    animation: @param;
  }
}

div {
  #vendor > .keyframes(hoge, {0% {background-color: #fff;} 100%
        {background-color: #000}});

  #vendor > .animation(hoge 5s ease 0 infinite alternate);
}
/* css */
div {
  -moz-animation: hoge 5s ease 0 infinite alternate;
  -webkit-animation: hoge 5s ease 0 infinite alternate;
  -o-animation: hoge 5s ease 0 infinite alternate;
  -ms-animation: hoge 5s ease 0 infinite alternate;
  animation: hoge 5s ease 0 infinite alternate;
}
@-moz-keyframes hoge {
  0% {
    background-color: #fff;
  }
  100% {
    background-color: #000000;
  }
}
@-webkit-keyframes hoge {
  0% {
    background-color: #fff;
  }
  100% {
    background-color: #000000;
  }
}
@-o-keyframes hoge {
  0% {
    background-color: #fff;
  }
  100% {
    background-color: #000000;
  }
}
@keyframes hoge {
  0% {
    background-color: #fff;
  }
  100% {
    background-color: #000000;
  }
}

mixin と変数のスコープ

mixin 内で変数を宣言した場合、mixin を呼び出したスコープでその変数を使用することができます。

// less
.hoge {
  @base-color: #fff;
  color: @base-color;
}

div {
  .hoge();
  background-color: @base-color; // mixin内で宣言している変数を使用
}
/* css */
div {
  color: #ffffff;
  background-color: #ffffff;
}

mixin で呼び出した変数を書き換えてもそのスコープ内でしか値が変わりません。

また、変数を書き換えても mixin 内の値は変わりません。

// less
.hoge {
  @base-color: #fff;
  color: @base-color;
}

div {
  .hoge();
  @base-color: #000;
  background-color: @base-color;
}

span {
  .hoge();
  background-color: @base-color;
}
/* css */
div {
  color: #ffffff; /* @base-colorを書き換えたが、mixin内なので反映されない */
  background-color: #000000;
}
span {
  /* @base-colorを他スコープ(div)で書き換えられたが何も影響しない */
  color: #ffffff;
  background-color: #ffffff;
}

これを使うことで「mixin の返り値」を作成することができます。

// less
.average(@x; @y) {
  @average: ((@x + @y) / 2); // そう言えば演算関連まだ説明してないですね…。
}

div {
  .average(20px, 40px);
  margin: @average;
  padding: @average;
}
/* css */
div {
  margin: 30px;
  padding: 30px;
}

パターンマッチングと mixin のオーバーロード

同名かつ引数の数が違う mixin を作ることは可能なんですが、呼び出せる mixin はすべて呼び出すが基本ルールです。

// less
.hoge(@color) {
  color: @color;
}

.hoge(@margin) {
  margin: @margin;
}

div {
  .hoge(#fff); // @colorの方に行ってほしいが…
}
/* css */
div {
  color: #ffffff;
  margin: #ffffff; /* @marginの方も引数の数が一緒なので呼び出されてしまう */
}

上記の例を回避する方法はいくつかあります。まずはガードを使用し、引数の型をチェックする方法。

// less
.hoge(@color) when (iscolor(@color)) {
  color: @color;
}

.hoge(@margin) when (isnumber(@margin)) {
  margin: @margin;
}

div {
  .hoge(#fff);
}
/* css */
div {
  color: #ffffff;
}

キーワードを入れる方法もあります。

// less
.hoge(color, @color) {
  color: @color;
}

.hoge(margin, @margin) {
  margin: @margin;
}

div {
  .hoge(color, #fff);
}

span {
  .hoge(margin, 0);
}
/* css */
div {
  color: #ffffff;
}
span {
  margin: 0;
}

根本的な対処ではないですが、引数の数そのものが違ってもマッチングしないとみなしてくれます。

ただし、デフォルト値が設定されていると面倒なことになったりします。

// less
.hoge(@color) when (iscolor(@color)) {
  color: @color;
}

.hoge(@margin) when (isnumber(@margin)) {
  margin: @margin;
}

.hoge(@color, @border) {
  colol: @color;
  border: @border;
}

div {
  .hoge(#fff);
}

span {
  .hoge(#fff, solid 10px #0000ff);
}
/* css */
div {
  color: #ffffff;
}
span {
  color: #ffffff;
  border: solid 10px #0000ff;
}

ガード

ガードはかなり細かく設定できます。面倒ですが。

複数のガードを設定するときはandを使います。

// less
.hoge(@color, @margin) when (iscolor(@color)) and (isnumber(@margin)) {
  color: @color;
  margin: @margin;
}

div {
  .hoge(#fff, 10px);
}
/* css */
div {
  margin: 10px;
  color: #ffffff;
}

否定する場合はnotをつけます。

// less
.hoge(@color, @margin) when (iscolor(@color)) and (isnumber(@margin)) {
  color: @color;
  margin: @margin;
}

.hoge(@color, @border) when (iscolor(@color)) and not(isnumber(@border)) {
  color: @color;
  border: @border;
}

div {
  .hoge(#fff, 10px);
}

span {
  .hoge(#fff, solid 10px #0000ff);
}
/* css */
div {
  color: #ffffff;
  margin: 10px;
}
span {
  color: #ffffff;
  border: solid 10px #0000ff;
}

or 条件を指定したい場合は,を使います。

// less
.hoge(@margin, @padding: @margin) when (isnumber(@margin)), (isnumber(@padding)) {
  margin: @margin;
  padding: @padding;
}

div {
  .hoge(10px);
}

span {
  .hoge(20px, fuga); // @marginがnumberなのでコンパイルが通る
}
/* css */
div {
  margin: 10px;
  padding: 10px;
}
span {
  margin: 20px;
  padding: fuga;
}

また、数値であれば<>も使えますし、文字列やキーワードの判定なんかもできます。

// less
.hoge(@margin, @padding) when (@padding <= 0px) {
  @padding: 5px;
  margin: @margin;
  padding: @padding;
}

.hoge(@margin, @padding) when (@padding > 10px) {
  @padding: 10px;
  margin: @margin;
  padding: @padding;
}

div {
  .hoge(10px, -1px);
}

span {
  .hoge(20px, 20px);
}
/* css */
div {
  margin: 10px;
  padding: 5px;
}
span {
  margin: 20px;
  padding: 10px;
}
// less
// !=は用意されてないのでnotを使う
.hoge(@keyword, @color) when not(@keyword=bg) {
  color: @color;
}

.hoge(@keyword, @color) when (@keyword=bg) {
  background-color: @color;
}

div {
  .hoge(hoge, #fff);
}

span {
  .hoge(bg, #000);
}
/* css */
div {
  color: #ffffff;
}
span {
  background-color: #000000;
}

どこにもマッチングしなかった場合のフォールバックとして、default()を使うこともできます。

この辺を使いこなせるようになると本当にオーバーロードっぽくなるのでぜひマスターしましょう。

// less
.hoge(@margin, @padding) when (default()) {
  margin: @margin;
  padding: @padding;
}

.hoge(@margin, @padding) when (@padding <= 0px) {
  .hoge(@margin, 5px); // when (default())
}

.hoge(@margin, @padding) when (@padding > 10px) {
  .hoge(@margin, 10px); // when (default())
}

div {
  .hoge(10px, -1px); // when (@padding <= 0px)
}

span {
  .hoge(20px, 20px); // when (@padding > 10px)
}

table {
  .hoge(2px, 2px); // when (default())
}
/* css */
div {
  margin: 10px;
  padding: 5px;
}
span {
  margin: 20px;
  padding: 10px;
}
table {
  margin: 2px;
  padding: 2px;
}

まとめ

やはり長くなってしまった…。

次回はまだ説明してないあれこれを適当に説明していく予定です。

【less】今更始める less の基本文法と tips(1) - ネストと変数編

2018/12/01 追記

前書き

お仕事の方で 5000 ステップほどある CSS を編集したりするんですが、人間が管理するにはあまりにも厳しいので、lessを導入することにしました。

流石に 1 から自分の手で書き直すのは辛いのでcss2lessを使って一度変換し、後は黙々と mixin なりなんなりを作るだけです。

CSS を知っていれば文法的に難しいところもほとんどなかったんですが、ちょっとわかりづらかったり、そのうち忘れそうなところもいくつかあったので適当にメモしていきます。

文法そのものの解説はぐぐるといっぱいでてきますが、公式のドキュメントがやはり最強でした。文章量もそんなにないので、下手にどこかの解説を読むよりこっちを全部読んだ方が早い気がします。

コンパイラのインストール

基本的にはnpm -g install lessだけでいいんですが、minify するために必要なプラグインが別途用意されているのでnpm install -g less-plugin-clean-cssもしておきましょう。

セレクタのネスト

基本形式

less ではセレクタをネストして書くことができます。例えばこんな CSS があったとします。

#header nav ul li {
  display: inline;
}

#header hoge {
  color: #000;
}

こんな風に書けます。

#header {
  nav {
    ul {
      li {
        display: inline;
      }
    }
  }

  #hoge {
    color: #000;
  }
}

疑似要素

:hover:afterなどの疑似要素は&を使うことで簡単に宣言できます。

// less
#header {
  background-color: #fff;

  &:hover {
    // #header:hover
    background-color: #000;
  }

  div {
    &:hover {
      // #header div:hover
      background-color: #ff0;
    }
  }
}
/* css */
#header {
  background-color: #fff;
}
#header:hover {
  background-color: #000;
}
#header div:hover {
  background-color: #ff0;
}

&は単純にセレクタ名を格納した擬似変数なので、疑似要素の定義以外にもこんなことができます。

// less
#header {
  &-hoge {
    color: #fff;
  }
  &-fuga {
    color: #000;
  }
}
/* css */
#header-hoge {
  color: #fff;
}
#header-fuga {
  color: #000;
}

メディアクエリのネスト

ネストした要素内でそれぞれのメディアクエリを書くことができます。さらに、メディアクエリ自体のネストも可能です。

// less
#header {
  font-size: 100%;
  @media screen and (max-width: 768px) {
    font-size: 90%;
  }
  @media screen and (max-width: 560px) {
    font-size: 80%;
  }

  div {
    @media screen and (max-width: 768px) {
      color: #ff0;
      @media screen and (min-width: 560px) {
        color: #f00;
      }
    }
  }
}
/* css */
#header {
  font-size: 100%;
}
@media screen and (max-width: 768px) {
  #header {
    font-size: 90%;
  }
}
@media screen and (max-width: 560px) {
  #header {
    font-size: 80%;
  }
}
@media screen and (max-width: 768px) {
  #header div {
    color: #ff0;
  }
}
@media screen and (max-width: 768px) and screen and (min-width: 560px) {
  #header div {
    color: #f00;
  }
}

出来上がった CSS は中々おぞましいものになりますが、まぁ、気にしないことにしましょう。

@keyframes@font-faceの扱い

自動で外に吐き出されますので、どんどんネスト内で書きましょう。

// less
#header {
  background-color: #fff;
  div {
    @font-face {
      font-family: "hoge";
      src: url("/hoge.ttf");
    }
    font-family: "hoge";
  }
}
/* css */
#header {
  background-color: #fff;
}
#header div {
  font-family: "hoge";
}
@font-face {
  font-family: "hoge";
  src: url("/hoge.ttf");
}

変数

変数宣言

@識別子:値と言う形で変数宣言が可能です。

// less
@base-color: #fff;

div {
  color: @base-color;
}
/* css */
div {
  color: #ffffff;
}

変数として宣言できるもの

割となんでもいけます。

// less
// カラーコード
@base-color: #fff;
@rgba-color: rgba(0, 0, 0, 0.5); // RGBAもOK

div {
  color: @base-color;
  background-color: @rgba-color;
}

// もちろん数値もOK
@width: 100%;
@height: 50px;
@margin: 0;

div {
  width: @width;
  height: @height;
  margin: @margin;
}

// URLなんかもOK
@image-dir: "../images";
div {
  background-image: url("@{image-dir}/image.jpg");
}

// 変数にCSSのルールセットを定義することも可能
@rules: {
  color: @base-color;
  background-color: @rgba-color;
  width: @width;
  height: @height;
  margin: @margin;
  background-image: url("@{image-dir}/image.jpg");
};

div {
  @rules(); // ルールセットを展開する場合は"()"
}
/* css */
div {
  color: #ffffff;
  background-color: rgba(0, 0, 0, 0.5);
}
div {
  width: 100%;
  height: 50px;
  margin: 0;
}
div {
  background-image: url("../images/image.jpg");
}
div {
  color: #ffffff;
  background-color: rgba(0, 0, 0, 0.5);
  width: 100%;
  height: 50px;
  margin: 0;
  background-image: url("../images/image.jpg");
}

変数として宣言できるけど使い道が微妙なもの

セレクタを宣言しておくことも可能です。複数のセレクタを一まとめにしたいとき便利かもしれません。

// less
@copyright: #footer address#copyright;

@{copyright} {
  color: #000;
}
/* css */
#footer address#copyright {
  color: #000;
}

また、プロパティ名も宣言できます。長いプロパティ名を省略するとき…とか?

// less
@ff: font-family;

div {
  @{ff}: monospace;
}
/* css */
div {
  font-family: monospace;
}

変数名そのものを宣言しておくことも可能です。この辺まで来ると普通に可読性が落ちそうです。

// less
@var-name: "mono";
@mono: monospace;
@ff: font-family;

div {
  @{ff}: @@var-name;
}
/* css */
div {
  font-family: monospace;
}

メディアクエリを変数として宣言

メディアクエリの条件を変数として宣言しておくことも可能です。

// less
@tablet: ~"screen and (max-width: 768px)"; // "()"が入っているものは"~"をつけてエスケープする

@media @tablet {
  div {
    color: #fff;
  }
}
/* css */
@media screen and (max-width: 768px) {
  div {
    color: #fff;
  }
}

変数の型

厳密には less に型と言う概念はないんですが、型チェックっぽい関数が用意されています。

該当するもの 該当しないもの
Number
  • 1234
  • 56px
  • 7.8%
  • 9em
  • #ff0
  • blue
  • "string"
  • keyword
  • url(...)
  • { color: #fff; }
String
  • "string"
  • #ff0
  • blue
  • 1234
  • 56px
  • 7.8%
  • 9em
  • keyword
  • url(...)
  • { color: #fff; }
Color
  • #ff0
  • blue
  • "string"
  • 1234
  • 56px
  • 7.8%
  • 9em
  • keyword
  • url(...)
  • { color: #fff; }
Keyword
  • keyword
  • #ff0
  • blue
  • "string"
  • 1234
  • 56px
  • 7.8%
  • 9em
  • url(...)
  • { color: #fff; }
Url
  • url(...)
  • #ff0
  • blue
  • "string"
  • 1234
  • 56px
  • 7.8%
  • 9em
  • keyword
  • { color: #fff; }
Pixel
  • 56px
  • #ff0
  • blue
  • "string"
  • 1234
  • 7.8%
  • keyword
  • url(...)
  • { color: #fff; }
Em
  • 9em
  • #ff0
  • blue
  • "string"
  • 1234
  • 56px
  • 7.8%
  • keyword
  • url(...)
  • { color: #fff; }
Percentage
  • 7.8%
  • #ff0
  • blue
  • "string"
  • 1234
  • 56px
  • 9em
  • keyword
  • url(...)
  • { color: #fff; }
Ruleset
  • { color: #fff; }
  • #ff0
  • blue
  • "string"
  • 1234
  • 56px
  • 7.8%
  • 9em
  • keyword
  • url(...)

スコープ

変数にはスコープがあります。あるセレクタ内で宣言された変数は、他のセレクタから呼び出せません。

@global-color: #000;

#hoge {
  @inner-color: #fff;
  color: @global-color;

  div {
    color: @inner-color;
  }
}

#fuga {
  color: @inner-color; // スコープ外なので呼び出せない
}

他のスコープで宣言された変数を書き換えると、そのスコープ内でのみ値が書き換わります。

// less
@global-color: #000;

#hoge {
  color: @global-color;
}

#fuga {
  @global-color: #fff; // fugaで@global-colorの値を変更する
  color: @global-color;
}

#piyo {
  color: @global-color;
}
/* css */
#hoge {
  color: #000000;
}
#fuga {
  color: #ffffff;
}
#piyo {
  color: #000000; /* piyoの@global-colorは変わらない */
}

ただし、同スコープ内で変数の値を書き換えると、変更した場所に関わらず後に宣言した値が適用されます。

// less
@global-color: #000;

#hoge {
  color: @global-color;
}

@global-color: #fff; // hogeの後に@global-colorを書き換える

#fuga {
  color: @global-color;
}

#piyo {
  color: @global-color;
}
/* css */
/* @global-colorの値はすべて#fffになる */
#hoge {
  color: #ffffff;
}
#fuga {
  color: #ffffff;
}
#piyo {
  color: #ffffff;
}

なるべくであればグローバルなスコープでの変数は書き換えないようにしましょう。危なっかしいので…。

まとめ

すごい長くなりそうなので続き物にします。

次回は mixin 関連を予定。早ければ明日に。

(2015/08/30)書きました。:-)

【Atom】Windows + Proxy 環境下で Atom を使うための準備

前書き

最近 Web エンジニア化が激しく、VSCode を使って頑張っていろいろ作っていたんですが、流石にこう、「もうちょっといいエディタ絶対あるわ…。」と言う気分になってきました。

いやまぁ悪いエディタではないんですけどね。いいエディタでは決してないですからね。

じゃあ他にどんな選択肢があるよ?って言われても、AtomSublime Text か Brackets か、って話ですし、以前 Atom を触ってたこともあるので迷わず Atom を入れたんですが、プロキシ環境下だとクソ面倒だったって話をします。

厳密に言えば別にプロキシ環境下でも何の問題もなく動くんですが、パッケージの検索、インストール関連は何一つ動きません。やってくれるぜって感じですね。

パッケージの入れられない Atom なんてそれこそ使う理由が一ミリもないのでちゃんと動くように準備します。

ちなみに環境は Windows です。まぁ MacLinux もやらなきゃいけないことは大体一緒だと思います。多分。

準備 1:apm のプロキシ設定をする

ぐぐるといっぱい出てきます。ぶっちゃけこれ読むのが一番早いです。

この設定を入れてないとパッケージを検索しようとしても真っ赤な血のような色でconnect ETIMEDOUTと言われてしまいます。

Atom をインストールし終わったら%USERPROFILE%\.atom\内に.apmrcファイルを作成し、以下の記述を追加しましょう。

(※%USERPROFILE%\.atom\.apm\.apmrcなるものがデフォルトで置かれているけど更新しても意味ないので無視するように。)

https-proxy = http://[UserName]:[Password]@[Host]:[Port]/
http-proxy = http://[UserName]:[Password]@[Host]:[Port]/
strict-ssl = false

記述し終わったらコマンドプロンプトからapm config listと叩いて反映されているか確認しましょう。

準備 2:パッケージインストール時に出る謎のエラーを解消する

準備 1 を終えることでパッケージが検索できるようになりました。満を持してパッケージをインストールするとCompiler tools not foundとかPackages that depend on modules that contain C/C++ code will fail to install.とか言われます。

なんでコンパイラが必要なんじゃボケ、と思うんですが、これもプロキシが原因だったりします。

これの解決方法が中々見つからず、それっぽい Issueを探して読んでいたら唐突に Close されました。Close の理由は別の Issue に書かれた対処法で大体対処可能だったからです。

と言うわけで、このコマンドを打ち込んで謎の URL を環境変数に追加しましょう。

setx ATOM_NODE_URL http://gh-contractor-zcbenz.s3.amazonaws.com/atom-shell/dist /M

この環境変数が設定されていればパッケージがインストールできるようになっているはずです。お疲れさまでした。

2016/08/20 追記

はてブに「1.9.1だとダメだった」と書かれてますが、普通に上記設定で問題ないです。

ご丁寧にリンクまで張ってくれていたのでそちらを見てみたところ、setx/Mを外したら動いた、とのこと。

setx/Mは「ユーザ環境変数ではなくシステム環境変数に追加する」スイッチなので、単に管理者権限がないコマンドプロンプトから実行したのだと思われます。

そりゃそうだ以上の言葉はないので、/Mを入れるかどうかはお好きな方でやってください。

まとめ

最近なんだかエディタの話ばっかりですね。

参考

【Javascript】Knockuout.js で option のカスケード処理を実装する

2018/12/01 追記

前書き

最近お仕事の方でKnockout.jsを使ってごにょごにょやっています。

で、あるフォーム画面を作るときにとあるselect optionのカスケードをやりたいな、と思って色々試行錯誤したんですが、思ったより大変だったのでメモしておきます。

optionsバインディングの使い方

先にoptionsバインディングについて簡単に説明しておきましょう。(日本語版

ドキュメントを読めばわかると思うんですが、data-bindingで指定できるものは以下の通りです。

パラメータ 概要
options select内のoptionとして使う配列。
optionsCaption selectの初期表示として表示する文言。optionの先頭に生成され、value""(空文字)になる。
optionsText optionsの中身がオブジェクトの場合にoptionのラベルとして使用するプロパティ名。
optionsValue optionsTextと同様に、optionsの中身がオブジェクトの場合にoptionvalueとして使用するプロパティ名。
optionsIncludeDestroyed 論理削除フラグ。boolean を指定する。
optionsAfterRender 生成したoptionに対し何らかの処理を加えるためのコールバック。
selectedOptions optionの初期選択値。selectmultiple属性を指定している場合は配列を指定すればよい。
valueAllowUnset valueバインディング日本語版)でoption内に存在しない値が指定されたときの挙動を制御する。false(デフォルト値)だと、valueバインディングしたものにoptionに指定されていない値が指定されると強制的にundifinedへ上書きするが、trueならばちゃんとその値がvalueとして保持される。(ただし、selectの見た目は空白になる。)*1

公式で紹介されている方法(withバインディング

公式ページのデモの一つにCart editorなるものがあります。(日本語版

ここで使われている方法を使えば比較的簡単かつ直感的にカスケード処理を実装することができます。と言うか、このデモでやってることがカスケードそのものです。

ただちょっと内容が豪華すぎて本当に知りたい部分がわかりにくいので、もうちょっと簡素な形で作ってみましょう。JSFiddleも用意してあるので動作確認はそちらでどうぞ。

まずはこんな ViewModel を用意します。

function Parent(label, value, children) {
  this.label = label;
  this.value = value;
  this.children = ko.observableArray(children);
}

function Child(label, value) {
  this.label = label;
  this.value = ko.observable(value);
}

function ViewModel() {
  var self = this;
  this.opts = ko.observableArray([
    new Parent("Parent1", 1, [new Child("Child1", 1), new Child("Child2", 2)]),
    new Parent("Parent2", 2, [new Child("Child3", 3), new Child("Child4", 4)])
  ]);
  this.parentSelected = ko.observable();
  this.childSelected = ko.observable();
  this.checkValue = function() {
    var vParent = self.parentSelected()
      ? self.parentSelected().value
      : "undifined";
    var vChild = self.childSelected() ? self.childSelected() : "undifined";
    alert("Parent: " + vParent);
    alert("Child: " + vChild);
  };
}

ko.applyBindings(new ViewModel());

で、HTML はこんな感じです。

<table>
  <tr>
    <td>Parent</td>
    <td>
      <select
        name="parent"
        data-bind="options: opts
                    , optionsText: 'label'
                    , value: parentSelected
                    , optionsCaption: '選択してください'"
      >
      </select>
    </td>
  </tr>
  <tr>
    <td>Children</td>
    <td data-bind="with: parentSelected">
      <select
        name="child"
        data-bind="options: children
                    , optionsText: 'label'
                    , optionsValue: 'value'
                    , optionsCaption: '選択してください'
                    , value: $parent.childSelected"
      >
      </select>
    </td>
  </tr>
</table>

<button data-bind="click: checkValue">値確認</button>

流れとしてはこんな感じですね。

ParentのoptionがViewModel.optsの配列でセットされる
(表示される文言はParent.label、値はParent.value)
↓
Parentで選択した値(Parentクラス)がViewModel.parentSelectedにセットされる
↓
select#childにparentSelectedが渡される
↓
optionsにParent.childrenがセットされ、表示される文言はChild.label、値はChild.valueの値になる。
**ここで選択された値をViewModel.childSelectedに反映させるため、valueは$parent.childSelectedにする。**

カスケードされるselectを最初から表示する

上記の方法ではselect#parentが選択されるまでselect#childは非表示の状態になっています。

何らかの理由でselect#childを先に表示させておきたい場合は、ifを使って初期表示用のselectを html に書いてしまいましょう。

visibleでも見た目上同じことができますが、select#childが DOM 上に二つ存在することになってしまいます。ifであれば DOM の再作成 / 削除を行ってくれるのでその心配もなくなります。form使わないならどっちでもいいですが。

JSFiddle

<table>
  <tr>
    <td>Parent</td>
    <td>
      <select
        name="parent"
        data-bind="options: opts
                    , optionsText: 'label'
                    , value: parentSelected
                    , optionsCaption: '選択してください'"
      >
      </select>
    </td>
  </tr>
  <tr>
    <td>Children</td>
    <!-- ko if: parentSelected() -->
    <td data-bind="with: parentSelected">
      <select
        name="child"
        data-bind="options: children
                    , optionsText: 'label'
                    , optionsValue: 'value'
                    , optionsCaption: '選択してください'
                    , value: $parent.childSelected"
      >
      </select>
    </td>
    <!-- /ko -->
    <!-- ko ifnot: parentSelected() -->
    <td>
      <select name="child" disabled>
        <option value="">先にParentを選択してください</option>
      </select>
    </td>
    <!-- /ko -->
  </tr>
</table>

<button data-bind="click: checkValue">値確認</button>

親要素が選択されていない場合は全子要素を選択できるようにする

これも初期表示用のselectとカスケード用のselectを作ってしまいましょう。

あわせて事前に全子要素を持った配列を作ってもいいですが、ko.computedを使った方がスマートですし変更にも強いです。

(JSFiddle)

// ViewModelに以下を追加
this.allChildren = ko.computed(function() {
  // flatten
  return [].concat.apply(
    [],
    self.opts().map(function(x) {
      return x.children();
    })
  );
});
<!-- ko if: parentSelected() -->
<td data-bind="with: parentSelected">
  <select
    name="child"
    data-bind="options: children
            , optionsText: 'label'
            , optionsValue: 'value'
            , optionsCaption: '選択してください'
            , value: $parent.childSelected"
  >
  </select>
</td>
<!-- /ko -->
<!-- ko ifnot: parentSelected() -->
<td data-bind="ifnot: parentSelected()">
  <select
    name="child"
    data-bind="options: allChildren
            , optionsText: 'label'
            , optionsValue: 'value'
            , optionsCaption: '選択してください'
            , value: childSelected"
  >
  </select>
</td>
<!-- /ko -->

formsubmitと併用する

カスケードとかは Knockout.js で全部設定するけどsubmitに関してはもうformの機能でやってしまいたい、と言うこともあるでしょう。

もちろん可能ですが、withバインディングを使う方法だとこれはできません…。

値の確認で使っているViewModel.checkValueをフォームのvalueで表示するようちょっと変更してみましょうか。(JSFiddle

this.checkValue = function() {
  /* var vParent = self.parentSelected() ? self.parentSelected().value : "undifined"; 
    var vChild = self.childSelected() ? self.childSelected() : "undifined"; */
  var vParent = window.test.parent.value;
  var vChild = window.test.child.value;
  alert("Parent: " + vParent);
  alert("Child: " + vChild);
};

で、実際に動かしてみると Parent の value は絶対にとれないと思います。

理由はこれ、非常に簡単で、selectoptionsバインディングJavascript のオブジェクトが指定されている場合、optionsValueバインディングを指定しないとoptionvalueはすべて空文字になるからです。(実際に FireBug とかで見てみるとわかる)

んじゃあoptionsValueを設定してやるかーとこんな感じにしてみます。(JSFiddle

<td>Parent</td>
<td>
  <select
    name="parent"
    data-bind="options: opts
            , optionsValue: 'value'
            , optionsText: 'label'
            , value: parentSelected
            , optionsCaption: '選択してください'"
  >
  </select>
</td>

動かしてみるとどんな Parent を選択しても Children が出てこなくなります。

これまた理由は非常に簡単で、optionsValueが設定されたことで、valueバインディングに設定したparentSelectedにはParent.valueが入ってくるからです。今回の例で言えば「1」とか「2」みたいなただの数字が渡されています。

となれば、withバインディングされているselect#childからしたら、optionsに指定されたchildrenなんて取れるわけがない(そもそもそんなプロパティがない)ので、空白のコンボボックスを表示するしかない、と言うわけです。

じゃあどうすればいいのかってとこですが、選択された値から Array を検索してしまいます。ダサいですね。

ViewModelにこんなコードを追加します。parentSelectedに値が入っていたらViewModel.opt.valueを検索して該当するParentを返すようにします。(JSFiddle

this.cascade = ko.computed(function() {
  var vParent = self.parentSelected();
  return vParent
    ? self.opts().filter(function(x) {
        return x.value == vParent;
      })[0]
    : null;
});

html はこんな感じ。

<form name="test">
  <table>
    <tr>
      <td>Parent</td>
      <td>
        <select
          name="parent"
          data-bind="options: opts
                        , optionsText: 'label'
                        , optionsValue: 'value'
                        , value: parentSelected
                        , optionsCaption: '選択してください'"
        >
        </select>
      </td>
    </tr>
    <tr>
      <td>Children</td>
      <td data-bind="if: cascade">
        <select
          name="child"
          data-bind="options: cascade().children
                        , optionsText: 'label'
                        , optionsValue: 'value'
                        , optionsCaption: '選択してください'
                        , value: childSelected"
        >
        </select>
      </td>
    </tr>
  </table>

  <button data-bind="click: checkValue">値確認</button>
</form>

これで値確認用のボタンをクリックすればちゃんと Parent も Child も値が取得できます。

まとめ

と言うわけで、ここ最近得た知見をいろいろまとめました。

*1:ちゃんと動かないことで有名。