Warum?
Weil mit jedem Boilerplate Theme, welches auf einem Framework basiert auch eine große Anzahl an Überschreibungen auf uns zu kommt, erst recht wenn es um so komplexe Systeme wie Magento und um ein Framework wie Bootstrap geht. Ein wenig besser wird es, wenn wir statt dem normalen Bootstrap CSS zu less oder SASS greifen, aber um eine wirklich saubere Basis zu bekommen, empfiehlt es sich im ersten Schritt auf weitestgehend alles Vorhandene zu verzichten und nur das Magento HTML als Basis zu sehen.
Ja es ist viel Arbeit doch es zahlt sich aus, zumindest gedanklich, dieses Experiment zu machen. Durch einige wirklich hilfreiche Funktionen von Bootstrap ist es nämlich einfacher als man denkt.
Da ich keineswegs von Boilerplates abraten will, möchte ich an dieser Stelle die Webcomm HTML5 Magento Bootstrap Boilerplate hervorheben, mit der ich gute Erfahrung sammeln konnte. Neben einer gelungenen Integration finden wir hier sehr gute CSS Klassen-Mappings zwischen Magento und Bootstrap.
Und los geht's
Wir könnten natürlich nun, wie es so manch erster Gedanke ist, die ganzen .phtml
Template-Dateien von Magento ändern und Bootstrap CSS-Klassen einfügen aber wir wollen möglichst wenig an diesen Dateien verändern. Für einige wenige Templates könnte man dies natürlich machen aber wann hört es auf? Bei dem Einsatz eines Frameworks wie Bootstrap ist wichtig, dass man möglichst viel davon benutzt, damit man das ohnehin schon große Stylesheet nicht noch zusätzlich aufbläht.
Die Lösung der benannten Probleme ist wie angedeutet der Einsatz einer Präprozessor Sprache wie SASS/SCSS oder less. Da Bootstrap mittlerweile SASS/SCSS auch offiziell unterstützt, kann man sich hier sein Mittel der Wahl aussuchen, ich persönlich tendiere aber zu SASS/SCSS, da mir die Syntax angenehmer ist, ich bisher alles in SCSS mache und die Community aktiver zu sein scheint.
Nun gibt es hier erstmal ein wenig mehr Vorarbeit zu leisten als einfach eine CSS-Datei an Ort und Stelle zu kopieren.
1. Bootstrap aufsetzen
Der einfache Weg ist, die gewünschten Bootstrap Version als gepackte Datei herunter zu laden und in ein Magento Theme Package einzufügen. Dazu wählt man auf der Bootstrap Download-Page zwischen den Optionen "Source Code" (less) oder der SASS/SCSS Variante.
Bootstrap liefert hier ein komplettes Paket, inklusive einem Bower-file, welches das Updaten der Bootstrap Dateien vereinfacht, insofern man Bower nutzen will. Bower ist ein Package-Manager der es uns Entwicklern einfacher machen soll z.B. Frameworks wie Bootstrap aktuell zu halten. Ich kann es an dieser Stelle nur empfehlen, da es den Update-Prozess enorm vereinfacht. Für unser Beispiel in diesem Artikel reicht uns aber der Ordner "assets" aus dem heruntergeladenen Paket, vollkommen aus.
Eure Dateistruktur könnte nun wie folgt aussehen:
skin/frontend/bootstrap/default
|- bootstrap
|- assets
|- scss
|- css
|- js
|- fonts
|- bootstrap
Die Ordner css
, js
und fonts/bootstrap
sind dynamisch befüllte Ordner. Im Ordner scss
werden im späteren Verlauf die Style-Änderungen gemacht.
2. Magento Dateien entfernen und Bootstrap laden
Als nächstes müssen wir in der local.xml
des neuen Magento Themes die Meta-Tags für viewport
und http-equiv X-UA-Compatible
einfügen. Weiterhin entfernen wir das bisherige Magento CSS und weitere störende Dateien wie z.B. JavaScript Bibliotheken, mit Funktionen, welche in Bootstrap ebenfalls verfügbar sind. Die Einbindung der Datei styles.css
kann bestehen bleiben, da wir später genau diese Datei dynamisch ausliefern werden um den Theme-Fallback nicht zu brechen.
app/design/frontend/bootstrap/default
|- layout
|- local.xml
<?xml version="1.0"?>
<layout version="0.1.0">
<default>
<reference name="head">
<!-- Entfernen von Magento Default Dateien -->
<action method="removeItem">
<type>skin_css</type>
<name>css/print.css</name>
</action>
<action method="removeItem">
<type>skin_css</type>
<name>css/styles-ie.css</name>
</action>
<action method="removeItem">
<type>skin_css</type>
<name>css/widgets.css</name>
</action>
<action method="removeItem">
<type>skin_js</type>
<name>js/ie6.js</name>
</action>
<action method="removeItem">
<type>js</type>
<name>lib/ds-sleight.js</name>
</action>
<action method="removeItem">
<type>js</type>
<name>varien/menu.js</name>
</action>
<!-- Laden der Bootstrap JS Datei -->
<action method="addItem">
<type>skin_js</type>
<name>js/bootstrap.min.js</name>
</action>
<!-- Setzen der neuen Meta Tags -->
<block type="core/text" name="head.bs.ux">
<action method="setText"><text><![CDATA[<meta http-equiv="X-UA-Compatible" content="IE=edge">]]> </text></action>
</block>
<block type="core/text" name="head.bs.viewport">
<action method="setText"><text><![CDATA[<meta name="viewport" content="width=device-width, initial-scale=1">]]> </text></action>
</block>
</reference>
</default>
</layout>
Weiterhin haben wir mit Hilfe der neuen Fallback-Konfiguration in der Datei app/design/frontend/bootstrap/default/etc/theme.xml
das Theme auf default/default
aufgebaut. Der Grund dafür ist nur, damit wir im Frontend keine Broken-Images sehen und das RWD-Theme, Magentos eigene Antwort auf Responsive Design, leider noch nicht ausgereift ist. Generell ist es natürlich möglich, das neue Theme auch auf dem RWD-Theme aufzusetzen. In diesem Fall sollte man nur beachten, dass wir schon den HTML5
Doctype und das Meta-Tag für viewport
im head
haben aber dafür noch weitere CSS-Dateien entfernt werden müssen. Weiterhin fehlt beim RWD-Theme noch der Meta-Tag <meta http-equiv="X-UA-Compatible" content="IE=edge">
um dem IE beizubringen was Sache ist.
3. Kompilierung der Bootstrap SCSS- und JS-Dateien
Um die Bootstrap Komponenten nun vom Assets-Ordner zu JS und CSS Dateien zu Kompilieren nutzen wir Gulp und einfache Gulp-Tasks. Das Gulp-File, die Konfigurations-Datei für Tasks, legen wir für unser Beispiel ebenfalls im Theme-Ordner ab, genauso wie die NPM Package-Datei zum Installieren der Gulp-Module.
skin/frontend/bootstrap/default
|- gulpfile.js
|- package.json
Wer sich bisher nicht mit diesem Workflow vertraut gemacht hat, sollte einen der vielen guten Artikel dazu lesen wie zum Beispiel "Building With Gulp" auf smashingmagazine.com. Im Groben ist die Installation, zumindest unter Windows, sehr einfach:
- Node installieren
- Da NPM zusammen mit Node installiert wurde, nun einfach die Konsole öffnen und mit der Eingabe von
npm --version
schauen ob es korrekt installiert wurde. - Gulp global installieren
npm install -g gulp
. - Testen ob Gulp korrekt installiert wurde
gulp --version
- Party on.
Das Gulp-File:
var gulp = require('gulp'),
sass = require('gulp-sass'),
rimraf = require('gulp-rimraf')
concat = require('gulp-concat');
// SCSS
gulp.task('scss', ['clean'], function() {
return gulp.src('scss/styles.scss')
.pipe(sass())
.pipe(gulp.dest('./css'));
});
// Bootstrap JavaScript
gulp.task('js', ['clean'], function() {
return gulp.src([
"bootstrap/assets/javascripts/bootstrap/modal.js"
])
.pipe(concat('bootstrap.min.js'))
.pipe(gulp.dest('./js'));
});
// Moving Bootstrap Fonts
gulp.task('fonts', ['clean'], function () {
return gulp.src(
'bootstrap/assets/fonts/bootstrap/*'
)
.pipe(gulp.dest('fonts/bootstrap'));
});
// Clean
gulp.task('clean', function() {
return gulp.src([
'./css',
'./js',
'./fonts/bootstrap'
],{read: false})
.pipe(rimraf({
force: true
}));
});
gulp.task('default', ['clean', 'scss', 'js', 'fonts']);
Um sicher zu gehen, dass unsere dynamisch erstellten Dateien gelöscht werden bevor wir sie neu erstellen, benötigen wir den clean
Task, welcher als Abhängigkeit in jedem anderen Task gesetzt wird und somit als erster ausgeführt wird.
Wenn wir mehrere Bootstrap JavaScript Komponenten nutzen wollen, können wir diese über den js
Task zusammenschreiben lassen. Welche Module benutzt werden sollen, muss im src
des Tasks angegeben werden wie im Beispiel das "Bootstrap-Modal". Schön hieran ist, dass wir kontrollieren können wie groß unsere JavaScript Datei am Ende wird und wir wirklich benötigte Module laden. Über weitere Gulp-Module wie z.B. gulp-uglify kann man die JavaScript Module auch komprimieren oder auch über gulp-jshint testen.
Der scss
Task für das Kompilieren der SCSS-Dateien benötigt nur die Angabe der Datei styles.scss
, denn alle weiteren Dateien werden in der besagten SCSS-Datei verknüpft. Auch hier haben wir mit weiteren Gulp-Modulen die Möglichkeit mehr als nur CSS zu erstellen, hier ein paar Beispiele:
- Komprimieren mit gulp-minify-css
- Aufteilung in mehrere CSS-Dateien falls das Klassen-Maximum erreicht ist (IE) mit gulp-bless
- Above the fold Optimierung critical-path-css-demo
Die Package Datei:
Die Package Datei benötigen wir um alle Gulp-Module zu installieren, d.h. egal wo wir mit diesem Code arbeiten, durch das Ausführen von npm install
werden alle benötigten Module installiert.
{
"name": "bootstrap_magento_theme",
"version": "0.0.1",
"description": "Example of how to handle magento frontend together with bootstrap",
"author": "Tobias Hartmann",
"dependencies": {
"gulp": "^3.8.10",
"gulp-rimraf": "^0.1.1",
"gulp-concat": "^2.4.2",
"gulp-sass": "^1.2.4"
}
}
4. Aufsetzen der Style SCSS
Nun kommen wir zur Style-Datei, sozusagen dem CSS-Herzstück unseres Themes.
Um herauszufinden, welche Bootstrap Standard-Dateien wir benötigen, öffnen wir im Ordner assets
die Datei _bootstrap.scss
. Wir könnten diese Datei auch direkt in unserer styles.scss
mit @import
einbinden, dabei würden wir aber sämtliche Komponenten laden, was in den meisten Fällen unnötig ist.
In der Bootstrap-Datei befinden sich glücklicher weise Kommentare, welche uns helfen zu identifizieren was wir benötigen und was nicht. Alles was mit Components beschrieben ist, ist optional, alles andere wird dringend benötigt, easy.
/ Core variables and mixins
@import "bootstrap/assets/stylesheets/bootstrap/variables";
@import "bootstrap/assets/stylesheets/bootstrap/mixins";
//-------------------
// User Settings:
// Deine Bootstrap Einstellungen sollten später hier eingefügt werden:
//-------------------
// Reset and dependencies
@import "bootstrap/assets/stylesheets/bootstrap/normalize";
@import "bootstrap/assets/stylesheets/bootstrap/print";
@import "bootstrap/assets/stylesheets/bootstrap/glyphicons";
// Core CSS
@import "bootstrap/assets/stylesheets/bootstrap/scaffolding";
@import "bootstrap/assets/stylesheets/bootstrap/type";
@import "bootstrap/assets/stylesheets/bootstrap/code";
@import "bootstrap/assets/stylesheets/bootstrap/grid";
@import "bootstrap/assets/stylesheets/bootstrap/tables";
@import "bootstrap/assets/stylesheets/bootstrap/forms";
@import "bootstrap/assets/stylesheets/bootstrap/buttons";
//-------------------
// Bootstrap Modules:
// Hier kannst du weitere Komponenten einfügen, je nachdem, welche benötig werden.
@import "bootstrap/assets/stylesheets/bootstrap/close";
@import "bootstrap/assets/stylesheets/bootstrap/modals";
//-------------------
// Utility classes
@import "bootstrap/assets/stylesheets/bootstrap/utilities";
@import "bootstrap/assets/stylesheets/bootstrap/responsive-utilities";
//-------------------
// User Modules:
// Deine SCSS Dateien sollten später hier eingefügt werden:
//-------------------
Ich habe mir erlaubt schon mal die Stellen mit Kommentaren zu versehen, an denen wir später unsere Styles und Module hinterlegen.
Die Sektion User Settings wird dabei weitestgehend mit Variablen aus der Datei skin/frontend/bootstrap/default/bootstrap/assets/stylesheets/bootstrap/_variables.scss
befüllt, welche dort auf das eigene Theme angepasst werden. Natürlich ist es auch ein perfekter Platz um eigene Variablen abzulegen, insofern diese global verfügbar sein sollen.
In der Sektion Bootstrap Modules führen wir alle Module auf, welche wir später im Shop benutzen wollen. Eine Liste mit allen verfügbaren Modulen findet ihr in der Datei skin/frontend/bootstrap/default/bootstrap/assets/stylesheets/_bootstrap.scss
unter dem Kommentar // Components
und // Components w/ JavaScript
.
Die Sektion User Modules ist den eigenen Modulen vorbehalten, von denen wir ein paar im Folgenden beschreiben werden.
5. Das erste Mal Gulp
Super, wir sind soweit, nun können wir unseren Code das erste Mal über Gulp in den CSS-, JS- und Font-Ordner schreiben lassen. Also nur Mut, gulp
in die Konsole eingeben und Return/Enter drücken. Eure Konsole wird nun die Einzelnen Tasks ausgeben und am Ende sollten wir in den oben genannten Ordnern die generierten Dateien vorfinden.
SCSS, Mappings, Tipps und Tricks
Da wir in diesem Artikel keine komplette Boilerplate bauen wollen, möchte ich hier nur auf ein paar der wichtigsten und hilfreichsten Bootstrap Mixins eingehen. Leider zeigt uns die Bootstrap Dokumentation, im oben gesetzten link nur less Mixins, schön ist aber, dass diese Mixins auch in SCSS zur Verfügung stehen, wie ihr später noch sehen werdet.
Neben SCSS-Mixins, bei denen es sich sozusagen um "Methoden" zum effektiveren Erstellen von CSS handelt, benutzen wir auch Extends um vorhandene CSS-Klassen zu erweitern.
Damit wir ein wenig die Ordnung behalten, denn es können wirklich sehr viele Mappings werden, empfiehlt es sich die Mappings nicht nur in eine, sondern in mehrere Dateien auszulagern. Für mich hat sich dabei die folgende Struktur bewährt:
skin/frontend/bootstrap/default
|- scss
|- styles.scss
|- _Globale-Klassen.scss
|- Blockname
|- _Block-Klassen.scss
|- __Kind-Klassen.scss
|- Verschachtelter Block
|- _Block-Klassen.scss
|- __Kind-Klassen.scss
Also für jeden Block für den es sich lohnt z.B. "Page", mache ich dabei einen eigenen Ordner auf. Darunter lege ich eine Datei ab, welche die Block-Klassen enthält und mit __
gekennzeichnete Dateien, welche jeweils einen Kind-Block enthalten. Wenn Blöcke wie "Catalog/Product" verschachtelt sind, können wir diese auch genau so anlegen z.B.:
skin/frontend/bootstrap/default
|- scss
|- styles.scss
|- Catalog
|- Product
|- _product.scss
|- __grid.scss
|- __list.scss
Bootstrap konfigurieren:
Da Bootstrap von vorn herein ein gewisses Styling mit sich bringt, könnte man, falls dieses Styling passend ist, darauf verzichten die Konfiguration zu überschreiben. Wir wollen uns aber trotzdem zumindest anschauen wie es geht.
Die Konfiguration nehmen wir direkt in der styles.scss
vor und halten uns dabei an die Bootstrap-Variablen, welche unter bootstrap/assets/stylesheets/bootstrap/_variables.scss
zu finden sind. Ein paar der interessantesten habe ich im Folgenden aufgezeigt.
// Grid:
$grid-columns: 12;
$grid-gutter-width: 20px;
$grid-float-breakpoint: $screen-sm-min;
$grid-float-breakpoint-max: ($grid-float-breakpoint - 1);
// Container:
$container-tablet: (720px + $grid-gutter-width);
$container-sm: $container-tablet;
$container-desktop: (940px + $grid-gutter-width);
$container-md: $container-desktop;
$container-large-desktop: (1140px + $grid-gutter-width);
$container-lg: $container-large-desktop;
// Schriften:
$font-family-base: $font-family-sans-serif !default;
$font-size-base: 14px !default;
$font-size-large: ceil(($font-size-base * 1.25)) !default; // ~18px
$font-size-small: ceil(($font-size-base * 0.85)) !default; // ~12px
// Buttons:
$btn-default-color: #333;
$btn-default-bg: #fff;
$btn-default-border: #ccc;
// Farben:
$brand-primary: darken(#428bca, 6.5%);
$brand-success: #5cb85c;
$brand-info: #5bc0de;
$brand-warning: #f0ad4e;
$brand-danger: #d9534f;
Ihr seht also, wir können anhand von wenigen Bootstrap-Variablen massiv modifizieren und dies sollten wir auch nutzen. Wenn ihr über die _variables.scss
geht fällt euch auch bestimmt !default
ins Auge. Dies hat keineswegs irgendetwas mit dem aus CSS bekannten !important
zu tun, vielmehr bezeichnet es das der aktuelle Wert dieser Variable "Default" ist und überschrieben werden kann. Wenn ein Wert in einer Variable gesetzt wurde, wird er bei der Benutzung von "Default" nicht erneut gesetzt (Überschrieben):
$content: "First content";
$content: "Second content?" !default;
$new_content: "First time reference" !default;
#main {
content: $content;
new-content: $new_content;
}
wird kompiliert zu:
#main {
content: "First content";
new-content: "First time reference"; }
Wenn wir neue globale Variablen anlegen, sollten diese also immer die Bezeichnung !default
bekommen, damit wir für diese Variablen einen Fallback haben.
Das Page Grid:
Es ist einfacher als man denkt, auf die bestehenden Magento CSS-Klassen das Bootstrap Grid zu mappen. Die Macher von Bootstrap waren nämlich so nett uns auch hierfür einige Mixins zu liefern. Ein super Vorteil davon ist, dass der Shop sogleich einen gewaltigen Schritt in Sachen Responsiveness nach vorn macht.
Die Grid-Mixins könnt ihr in diesem Ordner finden: bootstrap\assets\stylesheets\bootstrap\mixins\__grid.scss
Bringen wir also unser Magento Frontend wieder etwas in Form. Als erstes kümmern wir uns um die Pages, also die generelle Seitenstruktur. Dazu erweitern wir unser SCSS um folgende Datei:
skin/frontend/bootstrap/default
|- scss
|- styles.scss
|- page
|- __grid.scss
und natürlich referenzieren wir diese auch in der styles.scss
mit @import "page/__grid";
.
In unserer Page-Grid page/__grid.scss
sammeln wir nun die wichtigsten gegebenen CSS Struktur-Klassen und erweitern diese mit den zur Verfügung stehenden Mixins. Ich habe mir erlaubt, dies im Folgenden schon einmal vorzubereiten:
.page {
@extend .container;
}
.main-container {
@extend .row;
//==== col 1 layout ====
&.col1-layout {
.col-main {
@include make-md-column(12);
}
}
//==== col 2 layout ====
&.col2-left-layout,
&.col2-right-layout {
.col-main {
@include make-md-column(8);
}
}
&.col2-left-layout {
.col-left {
@include make-md-column(4);
}
}
&.col2-right-layout {
.col-right {
@include make-md-column(4);
}
}
//==== col 3 layout ====
&.col3-layout {
.col-main {
@include make-md-column(6);
}
.sidebar {
@include make-md-column(3);
}
}
}
Zur Erklärung, die Mixins, die uns hier das Leben erleichtern, sind folgende
make-row($gutter)
make-[breakpoint]-column($columns, $gutter)
Sicherlich kann man den SCSS-Code auch noch weiter zusammenfassen, ich habe aber wegen der Übersichtlichkeit darauf verzichtet.
Warum benutze ich nicht überall @extend
?
Gute Frage, ich habe erstens extra hierauf verzichtet damit ich euch zeigen kann, wie flexibel Bootstrap ist und zweitens war @extend
bis zu der Version 1.2.0 von "gulp-sass" nicht in der Lage z.B. Media-Queries, welche in dem zu erweiternden Element gesetzt wurden, zu berücksichtigen.
Flexibel wird Bootstrap hier weil man diesen Mixins sowohl die Spaltenanzahl als auch die Gutter-Breite mitgeben kann, wir sind also in der Lage, das Grid in Abhängigkeit eines Scopes anzupassen. Wichtig ist zudem zu erwähnen, dass man Bootstrap-Grids verschachteln kann, dabei müssen diese allerding nochmals von einer .row
umgeben werden.
Leider müssen wir an dieser Stelle auch an die Magento Page-Templates ran. Deren HTML Struktur lässt nämlich ob im RWD-Theme oder im Default zu wünschen übrig, also reduzieren wir diese etwas und schieben vor allem die linke Spalte vor die Haupt-Spalte.
app/design/frontend/bootstrap/default
|- layout
|- templates
|- themes
|- 1column.phtml
|- 2columns-left.phtml
|- 2columns-right.phtml
|- 3columns.phtml
Hier ein Beispiel anhand der 3columns.phtml
. Nicht zu vergessen, dass wir einen HTML5 Doctype benötigen. Wir werfen also den .col-wrapper
raus, drehen .col-left
und .col-main
und den Wrapper .main
entfernen wir ebenfalls, da es eine Doppelung zu .main-container
ist.
<!DOCTYPE html>
<head>
<?php echo $this->getChildHtml('head') ?>
</head>
<body<?php echo $this->getBodyClass()?' class="'.$this->getBodyClass().'"':'' ?>>
<?php echo $this->getChildHtml('after_body_start') ?>
<div class="wrapper">
<?php echo $this->getChildHtml('global_notices') ?>
<div class="page">
<?php echo $this->getChildHtml('header') ?>
<div class="main-container col3-layout">
<?php echo $this->getChildHtml('breadcrumbs') ?>
<div class="col-left sidebar"><?php echo $this->getChildHtml('left') ?></div>
<div class="col-main">
<?php echo $this->getChildHtml('global_messages') ?>
<?php echo $this->getChildHtml('content') ?>
</div>
<div class="col-right sidebar"><?php echo $this->getChildHtml('right') ?></div>
</div>
<?php echo $this->getChildHtml('footer') ?>
<?php echo $this->getChildHtml('global_cookie_notice') ?>
<?php echo $this->getChildHtml('before_body_end') ?>
</div>
</div>
<?php echo $this->getAbsoluteFooter() ?>