FLOCSS概要
FLOCSS については、Qiitaなど各所の記事を参照しました。
簡単に概要を書くと、CSSをレイヤーで分けて、
- Foundation
- Layout
- Object
- Component
- Project
- Utility
にて管理することを指しています。
そのPrefixをとって、FLOなCSSで、FLOCSS(フロックス)です。
Object をどう分けるのか?は悩むところです。
脱・CSS無法地帯。FLOCSSで指針のある設計を。やFLOCSSを使ったCSS設計での悩みどころと解決案が参考になりました。
階層
FLOCSS そのままに、
- foundation
- layout
- object
- component
- project
- utility
としていますが、Railsとの関係で一つだけアレンジを加えています。
- actionview
- foundation
- layout
- object
- component
- project
- utility
actionviewを追加しています。これは Rails の ActionView に相当するものを置いていきます。たとえば scripts#show だけで使うCSSは、再利用性や拡張性に乏しいため、 actionview/scripts.scss として配置し、#a-scirpt_show のID/クラスを定義していきます。
また、application.scss では layout の次に読み込むようにします。
命名規則(FLOCSS単体)
prefixも、基本的にはFLOCSSで使われているものを踏襲しました。
命名規則は Rails のViewに一致するようにしています。
layout : l-[layout]_[view]
component : c-[component]_[view]
project : p-[project]_[view]
utility : u-[util]_[view]
l-[layout] は、CSSでいうLayoutだけではなく、Railsの views/layout も指すことにしました。l-header.scss だけではなく、views/layouts/manage.html.haml があれば l-manage.scss が存在しえます(存在しなくてもいい)。
p-[project]_[view] は、project名 script
がlistというView(pertial)を持つ場合は
project : p-script_list
とすることにしました。
これによりRailsの構成によってFLOCCS内での役割が自動的に決まることになりました。
Rails
Railsでは MVC に基づいて、ActionViewが自動的に決まります。
また、各Viewで使う記述を部品化するために partial をつくることができます。
partial
部品化された単位は、独立して使えるようにします。
そのため partial の階層は views/*/projects/*
に置くことにします。
この projects は、FLOCSS の Object/project と同じです。
project名 script
の list
は、
views/*/projects/scripts/_list.html.haml
となりました。
FLOCSS x View
ファイルとディレクトリの構成を定義
CSSとHTML(+JS)のComponent化には、お互いのカプセル化が一致しているほうが管理しやすいだろうという前提で、上述のようにFLOCSSとRailsのViewの構成を定義しました。
ここからは、お互いを合致させるために決めた事を説明します。
呼び出しルール
RailsのViewでは、次のようにpartialを呼び出します。
= render partial: '*/projects/script/list', locals: {script: @script}
(* はアプリケーションの階層を伏字にしているだけなので、このままコピペではないです。)
ここで、ひとつルールを設けました。
c_selector: 'menu'
のように、partialのローカル変数を渡すことです。
= render partial: '*/projects/script/list', locals: {script: @script, c_selector: 'menu'}
これにより、list のHTMLを menu 形式で表示したり、navi 形式で表示したりすることが可能になりました。
他の方法として、
.menu
= render partial: '*/projects/script/list', locals: {script: @script}
とする方法もあります。
しかしこの方法は、呼び出し元が project(あるいはcomponent)について詳しく知る必要があります。同じ menu という文字を使っていますが、後者は階層構造を呼び出し元が把握しています。
何が起こるかというと、partial内のHTML構造/CSS構造を変えた時に、全ての呼び出し元に変更が入る可能性があります。
そこで、HTMLとCSSをできる限り分離し、かつカプセル化するためには、お互いを知らずに使えるようにします。HTML(partial)側がローカル変数でCSS selectorの名前だけを受け取り、階層構造が変わった場合も、partial だけを変更すれば良いようにします。
- c_selector ||= '' # for nil
%ul.p-script_list{class: c_selector}
- script.to_hash.each do |key, value|
%li{class: key}= value
- c_selector ||= ''
では、c_selector が指定されていない場合でも生成されるようにしています。
なお、c_selector2 を増やすことは禁止しました。部品の肥大化が目に見えているからです。
CSS の記述
object/project/_script.scss
のなかには、命名規則に従った定義と、呼び出される時に指定される c_selector の定義をしました。
.p-script_list{
// 未指定の場合のレイアウト
}
.p-script_list.menu{
// menu表示の場合のレイアウト
}
.p-script_list.navi{
// navi表示の場合のレイアウト
}
partial のなかで、menu や navi の階層を変えた場合は、どうでしょうか?
- c_selector ||= '' # for nil
.p-script_list
%ul{class: c_selector}
- script.to_hash.each do |key, value|
%li{class: key}= value
CSS側も
.p-script_list{
// 未指定の場合のレイアウト
}
.p-script_list > .menu{
// menu表示の場合のレイアウト
}
.p-script_list > .navi{
// navi表示の場合のレイアウト
}
のように変更すれば良いことになります。
この時、呼び出し元を意識する必要はありません。
c_selector の名前を変えたときは、すでに使われている箇所の名称は置き換える必要があります。ただこれも単純な置換だけになります。
役割(責務)
FLOCSSの Object/projects を Rails View の projects/[partial] に対応させたことで、project の役割が明確になりました。
partial に切り出して再利用する単位が project であり、また切り出すほどでもないが再利用する部品が FLOCSS の Object/component になります。
例えば、Rails内で「ボタンだけを切り出してpartialに書く」ということは考えにくいため、これはprojectではありません。
list構造は同じだが見せ方を各Viewで変えたいものや、共通で入る件数表示、どこにでも置くコメント欄などがprojectに該当してきます。
まとめ
Rails と CSS の組み合わせにおいて、FLOCSS を取り入れながらパーツの部品化・カプセル化をうまく行えるようにしました。
ポイントは、CSSとHTMLをセットで部品化していくことです。
Object指向でアプリケーションを開発するときには、そのオブジェクトは意味を持っています。それがMVCモデルのなかでは View として構成されています。
CSSも、素直にアプリケーションの意図(設計)であるViewに対応しているほうが分かりやすいのではないか?と思い、今回のような設計にしました。
CSS単独で汎用性を考えると、どうしても使い回しやすさや拡張性だけに目がいってしまいます。
そのため、共通部品化しようとして、 .button や .comments といった汎用的なパーツに落とし込みがちです。あるいは .**box などの、CSSをライブラリ化しただけのクラスが増えてしまいます。
しかし、これだけではCSSを狙い通りに適用できないため、Viewのなかで階層を組み立てることになります。
であれば、CSSとRailsがお互いに近づく方が分かりやすいのではないか?と思いました。その結果、 Object/project を中心に構成したうえで、呼び出しルールのようなルールを設けました。