Skip to content

Instantly share code, notes, and snippets.

@real34
Created April 12, 2012 08:16
Show Gist options
  • Save real34/2365583 to your computer and use it in GitHub Desktop.
Save real34/2365583 to your computer and use it in GitHub Desktop.
CakePHP2.x : Twitter bootstrap base for projects
/**
* CakePHP generic stylesheet to make default markup as compatible as possible
* with Twitter bootstrap
*
* Depends on https://github.com/jlong/sass-twitter-bootstrap
*/
// Variables
$footer-height: 25px !default;
$cakeActionsColumns: 3 !default;
$cakeMainColumns: $gridColumns - $cakeActionsColumns !default;
// Layout
body {
padding-top: 50px;
padding-bottom: 25px;
}
div.form,
div.index,
div.view {
@include makeColumn($cakeMainColumns);
margin-left: $gridGutterWidth - 2 * 1px; // Borders of the .actions (.well)
float:right;
}
div.actions {
@include makeColumn($cakeActionsColumns);
@extend .well;
-webkit-border-top-left-radius: 0px;
-webkit-border-bottom-left-radius: 0px;
-moz-border-radius-topleft: 0px;
-moz-border-radius-bottomleft: 0px;
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
margin: $baseLineHeight 0;
padding: 5px 0;
h3 {
color: $grayLight;
font-size: 1.1em;
text-shadow: 0 1px 0 rgba(255,255,255,.5);
text-transform: uppercase;
padding: 3px 15px;
}
ul {
@extend .nav;
@extend .nav-list;
}
}
footer {
@extend .navbar;
height: $footer-height;
line-height: $footer-height;
text-align: center;
}
// Tables
table {
@extend .table;
@extend .table-striped;
@extend .table-bordered;
}
th a.asc:after {
content: ' ⇣'; // TODO replace with icon
}
th a.desc:after {
content: ' ⇡'; // TODO replace with icon
}
tr .actions {
text-align: center;
}
table td.actions a {
@extend .btn;
@extend .btn-mini;
margin-right: 2px;
&:hover {
text-decoration: none;
}
&.delete {
@extend .btn-danger;
}
}
// Forms
form {
@extend .form-horizontal;
label {
@extend .control-label;
}
.error-message {
@extend .help-inline;
}
.submit {
@extend .form-actions;
padding-top: 7px;
padding-bottom: 8px;
}
input[type=submit] {
@extend .btn;
@extend .btn-success;
}
}
// Flash Messages
.message {
@extend .alert;
@extend .alert-block;
@extend .alert-info;
}
.cake-debug {
@extend .message;
}
.success {
@extend .message;
@extend .alert-success;
}
.cake-error, p.error {
@extend .message;
@extend .alert-error;
}
.notice {
@extend .message;
@extend .alert;
}
// Pagination
.pager {
li a {
background-color: $grayLighter;
}
li a, .current {
@include border-radius(5px);
padding: 2px 8px;
}
.previous a, .next a {
padding: 2px 15px;
}
.disabled {
display: none;
}
.current {
font-weight: bold;
}
}
<?php
/**
* Action Helper
* Allows to generate simple CRUD links for a given object and customize its rendering
* It only works for default baked urls
*
* Usage:
* <code>
* $this->Actions->setActionsOptions($order['Order']['id'], true);
* echo $this->Actions->view();
* echo $this->Actions->edit();
* echo $this->Actions->deletePost(
* null,
* __('Are you sure you want to delete the Order #%s?', $order['Order']['id'])
* );
* </code>
*
* Note: For now it is really specific to Twitter Bootstrap
*
* @todo Reduce code duplication: it might be possible to create a generic data structure to represent each action
* and remove the "setActionsOptions" with something else to be stateless
* @todo Make me more and more generic by adding options to each method when needed
* @todo Allow to define different url schemes, using Configure for instance
*/
class ActionsHelper extends AppHelper {
/**
* Helpers used in the class
*
* @var array
*/
public $helpers = array('Form', 'Html');
/**
* Id of the current object to generate links for
*
* @var string
*/
protected $_id = null;
/**
* Whether a tooltip must be displayed instead of the text
*
* @var boolean
*/
protected $_tooltip = false;
/**
* Set options to use in future calls
*
* @param string $id Object id
* @param boolean $tooltip Set to true to use icon + tooltips instead of icon + text
*/
public function setActionsOptions($id, $tooltip = false) {
$this->_id = $id;
$this->_tooltip = $tooltip;
}
public function add($title = null, $controller = null, $options = array()) {
if (is_null($title)) {
$title = __('Ajouter');
}
return $this->action($title, 'add', 'icon-plus-sign', false, $controller, $options);
}
public function index($title = null, $controller = null, $options = array()) {
if (is_null($title)) {
$title = __('Lister');
}
return $this->action($title, 'index', 'icon-th-list', false, $controller, $options);
}
public function view($title = null, $controller = null, $options = array()) {
if (is_null($title)) {
$title = __('Voir');
}
return $this->action($title, 'view', 'icon-screenshot', true, $controller, $options);
}
public function edit($title = null, $controller = null, $options = array()) {
if (is_null($title)) {
$title = __('Modifier');
}
return $this->action($title, 'edit', 'icon-edit', true, $controller, $options);
}
/**
* Link to the delete action for an object, using a Post form
*
* @param string $title Link title, optional
* @param mixed $confirmMessage Confirmation message, false if no confirmation is needed
* @param string $controller Controller related. If not set, use the current controller.
* @return string Html link to this action
*/
public function deletePost($title = null, $confirmMessage = false, $controller = null, $options = array()) {
$this->_requireId();
if (is_null($title)) {
$title = __('Supprimer');
}
$options = array_merge(
array('escape' => false, 'class' => 'delete', 'title' => $title, 'rel' => 'tooltip'),
$options
);
$url = $this->_makeUrl(array('action' => 'delete', $this->_id), $controller);
return $this->Form->postLink(
$this->_linkTitle('icon-remove-circle', $title),
$url,
$options,
$confirmMessage
);
}
/**
* Link to an action for an object.
*
* @param string $title Link title.
* @param string $action The action.
* @param string $withId Do the action need the object id.
* @param string $controller Controller related. If not set, use the current controller.
* @return string Html link to this action.
*/
public function action($title, $action, $icon, $withId = false, $controller = null, $options = array()){
$url = array('action' => $action);
if ($withId) {
$this->_requireId();
$url[] = $this->_id;
}
$options = array_merge(
array('escape' => false, 'class' => $action, 'title' => $title, 'rel' => 'tooltip'),
$options
);
$url = $this->_makeUrl($url, $controller);
return $this->Html->link($this->_linkTitle($icon, $title), $url, $options);
}
/**
* Generate a link title
*
* @param string $icon Icon to use
* @param string $title Title of the link
* @param boolean $isTooltip True if this is a title for a tooltip, optional [default: $this->_tooltip]
* @return string Title to use: if tooltip, just the icon
*/
protected function _linkTitle($icon, $title, $isTooltip = null) {
if (is_null($isTooltip)) {
$isTooltip = $this->_tooltip;
}
$linkTitle = '<i class="' . $icon . '"></i>';
if (!$isTooltip) {
$linkTitle .= $title;
}
return $linkTitle;
}
/**
* Checks if an object id has been set and trigger an error if it is not the case
*
* @return void
*/
protected function _requireId() {
if (is_null($this->_id)) {
trigger_error('No object has been defined. Have you forgotten to call $this->Actions->setActionsOptions()?');
}
}
/**
* Allows to generate an url with a mandatory part, and optionnaly a customized base
*
* @param array $url Parts of the url that are mandatory
* @param mixed $customBase Custom url base (array elements) to change from the router defaults
* As a shorthand, if this is a string it will be considered as a custom controller
* @return array Url
*/
protected function _makeUrl($url, $customBase) {
if (!empty($customBase)) {
if (!is_array($customBase)) {
$customBase = array('controller' => $customBase);
}
$url = array_merge($customBase, $url);
}
return $url;
}
}
<?php
/**
* Twitter Bootstrap Form Helper
* Allows to generate the very minimum markup for each type of form
*
* Note: it is a WIP. For now it only works for horizontal forms by default, and must be coupled
* with the SASS stylesheet
*/
App::uses('FormHelper', 'View/Helper');
class TwitterBootstrapFormHelper extends FormHelper {
/**
* Default options for Twitter Bootstrap inputs
*
* @var arrays
*/
private $__inputDefaults = array(
'none' => array(),
'horizontal' => array(
'div' => array('class' => 'control-group'),
'between' => '<div class="controls">',
'after' => '</div>',
'format' => array('before', 'label', 'between', 'input', 'error', 'after')
),
'search' => array(
'div' => false
)
);
/**
* Returns an HTML FORM element.
* Uses correct inputDefaults
*
* @todo Unit test me
* @param string $model Model to generate the form for
* @param array $options Form options. Same than the FormHelper, with the following differences
* about the inputDefaults key:
* - can be an array for the same behavior than Form::helper
* - can be a string to use one of the preset available
* (see TwitterBootstrapFormHelper::__inputDefaults)
* - can be an array, with the "preset" key to a valid preset. In this case
* the preset will be used as base and overriden by the passed keys
* @return string
*/
public function create($model = null, $options = array()) {
$presetType = '';
if (!empty($options['inputDefaults'])) {
if (is_string($options['inputDefaults'])) {
$presetType = $options['inputDefaults'];
$options['inputDefaults'] = array();
} elseif (array_key_exists('preset', $options['inputDefaults'])) {
$presetType = $options['inputDefaults']['preset'];
unset($options['inputDefaults']['preset']);
}
} else {
$options['inputDefaults'] = array();
}
$presetType = array_key_exists($presetType, $this->__inputDefaults) ? $presetType : 'horizontal';
$options['inputDefaults'] = array_merge($this->__inputDefaults[$presetType], $options['inputDefaults']);
return parent::create($model, $options);
}
/**
* Overrides the parent input to wrap it with "prepended" and/or "appended" icons
*
* @param string $fieldName Field name
* @param array $options Input options. Same than FormHelper::input, with 2 additional keys:
* - prepend: text to prepend
* - append: text to append
* - wrappindDivClass: array of classes for the wrapping div used "in between"
* @return string Html markup
*/
public function iconizedInput($fieldName, $options = array()) {
$prepend = empty($options['prepend']) ? false : $options['prepend'];
$append = empty($options['append']) ? false : $options['append'];
unset($options['prepend'], $options['append']);
if ($prepend || $append) {
foreach(array('between', 'after') as $key) {
if (empty($options[$key])) {
$options[$key] = '';
}
}
$wrappingDivClasses = empty($options['wrappingDivClasses']) ? array() : (array) $options['wrappingDivClasses'];
if ($prepend) {
$wrappingDivClasses[] = 'input-prepend';
$options['between'] .= $this->Html->tag('span', $prepend, array('class' => 'add-on'));
}
if ($append) {
$wrappingDivClasses[] = 'input-append';
$options['after'] = $this->Html->tag('span', $append, array('class' => 'add-on')) . $options['after'];
}
$options['between'] = '<div class="' . implode(' ', $wrappingDivClasses) . '">' . $options['between'];
$options['after'] .= '</div>';
}
return parent::input($fieldName, $options);
}
public function input($fieldName, $options = array()) {
if (is_string($options)) {
$options = array('label' => $options);
}
if (isset($options['type']) && $options['type'] === 'radio') {
$options['after'] = '';
} else {
if (isset($options['between']) && !isset($options['after'])) {
$options['after'] = '';
}
if (isset($options['after']) && !isset($options['between'])) {
$options['after'] .= '</div>';
}
}
return parent::input($fieldName, $options);
}
/**
* Creates a set of radio widgets. Will create a legend and fieldset
* by default. Use $options to control this
*
* ### Attributes:
*
* - `separator` - define the string in between the radio buttons
* - `between` - the string between legend and input set
* - `legend` - control whether or not the widget set has a fieldset & legend
* - `value` - indicate a value that is should be checked
* - `label` - boolean to indicate whether or not labels for widgets show be displayed
* - `hiddenField` - boolean to indicate if you want the results of radio() to include
* a hidden input with a value of ''. This is useful for creating radio sets that non-continuous
* - `disabled` - Set to `true` or `disabled` to disable all the radio buttons.
*
* @param string $fieldName Name of a field, like this "Modelname.fieldname"
* @param array $options Radio button options array.
* @param array $attributes Array of HTML attributes, and special attributes above.
* @return string Completed radio widget set.
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#options-for-select-checkbox-and-radio-inputs
*/
public function radio($fieldName, $options = array(), $attributes = array()) {
$label = '';
if (!empty($attributes['legend'])) {
$label = $attributes['legend'];
} elseif(!isset($attributes['legend'])) {
if (count($options) > 1) {
$label = __(Inflector::humanize($this->field()));
}
}
$globalLabel = empty($label) ? '' : $this->Html->tag('label', $label, array('class' => 'control-label'));
$attributes['legend'] = false;
$radio = parent::radio($fieldName, $options, $attributes);
return $globalLabel . $this->Html->div('controls', $radio);
}
}
<?php
/**
* Twitter Bootstrap Html Helper
* Provides shorthand methods to generate Twitter bootstrap ompatible markup
*
*/
App::uses('HtmlHelper', 'View/Helper');
class TwitterBootstrapHtmlHelper extends HtmlHelper {
/**
* Constructor
* Overrides the parent one to add some specific Twitter tags to the list
*
* @param View $View
* @param array $settings
*/
public function __construct(View $View, $settings = array()) {
parent::__construct($View, $settings);
$this->_tags += array(
'help-inline' => '<span class="help-inline">%s</span>',
'help-block' => '<p class="help-block">%s</p>',
);
}
/**
* Overrides the parent link to allow prepending / appending an icon or a text
*
* @param string $title The content to be wrapped by <a> tags.
* @param mixed $url Cake-relative URL or array of URL parameters, or external URL (starts with http://)
* @param array $options Array of HTML attributes. Same than HtmlHelper::link, with 2 additional keys:
* - prepend: class of the icon to prepend
* - append: class of the icon to append
* - before: text to prepend
* - after: text to append
* @param string $confirmMessage JavaScript confirmation message.
* @return string An `<a />` element.
*/
public function link($title, $url = null, $options = array(), $confirmMessage = false) {
$_defaults = array('prepend' => false, 'append' => false, 'before' => '', 'after' => '');
$selfOptions = array_intersect_key(
array_merge($_defaults, $options),
$_defaults
);
$options = array_diff_key($options, $selfOptions);
extract($selfOptions);
if ($prepend) {
$title = $this->tag('i', '', array('class' => $prepend)) . ' ' . $title;
}
if ($append) {
$title .= ' ' . $this->tag('i', '', array('class' => $append));
}
if ($prepend || $append) {
$options['escape'] = false;
}
return $before . parent::link($title, $url, $options, $confirmMessage) . $after;
}
/**
* Generates a dropdown trigger link element
*
* @param string $title The content to be wrapped by <a> tags.
* @return string An `<a />` element.
*/
public function dropdownTrigger($title) {
return $this->link(
$title . ' <b class="caret"></b>',
'#',
array('class' => 'dropdown-toggle', 'data-toggle' => 'dropdown', 'escape' => false)
);
}
/**
* Adds a link to the breadcrumbs array.
* Overrides the method to return the Helper object allowing for chained calls
*
* @param string $name Text for link
* @param string $link URL for link (if empty it won't be a link)
* @param mixed $options Link attributes e.g. array('id' => 'selected')
* @return TwitterBootstrapHtmlHelper
*/
public function addCrumb($name, $link = null, $options = null) {
parent::addCrumb($name, $link, $options);
return $this;
}
public function hasCrumbs() {
return !empty($this->_crumbs);
}
/**
* Prepends startText to crumbs array if set
* and appends a Twitter bootstrap compatible divider to each crumb
*
* @param $startText
* @return array Crumb list including startText (if provided)
*/
protected function _prepareCrumbs($startText) {
$separator = $this->tag('span', '/', array('class' => 'divider'));
$crumbs = parent::_prepareCrumbs($startText);
$last = array_pop($crumbs);
foreach ($crumbs as &$crumb) {
$crumb[2]['after'] = $separator;
}
array_push($crumbs, $last);
return $crumbs;
}
}
@real34
Copy link
Author

real34 commented Apr 12, 2012

This is raw code for turning your CakePHP baked application into a TwitterBootstrap compatible theme, as a dropin replacement for the default theme.
Here are some suggestions / documentation for using these files.

_cake.scss

You just need to include the _cake.scss file after the bootstrap main file in your application stylesheet. All your application will be themed.

@import "bootstrap/bootstrap";
@import "cake";

TwitterBootstrapFormHelper

This is a replacement class for the default FormHelper. You can leverage Cake alias to update all your forms with the "horizontal" form layout. It also contains default values for search forms.
Suggestion : alias the default FormHelper using the code below

class AppController extends Controller {
    public $helpers = array(
        'Form' => array('className' => 'TwitterBootstrapForm'),
    );
}

ActionsHelper

This helper is a quick draft for allowing you to reduce code duplication in the default "Actions" sidebar and listing column baked by CakePHP. It is not needed, but allows you to add some eye candy using TwitterBootstrap icons in your application.
After adding it to your helper list, you can use it as follow

Icon with tooltip

Ideal for listings!

<td class="actions">
    <?php
        $this->Actions->setActionsOptions($order['Order']['id'], true);
        echo $this->Actions->view();
        echo $this->Actions->edit();
        echo $this->Actions->deletePost(
            null,
            __('Are you sure you want to delete the Order #%s?', $order['Order']['id'])
        );
    ?>
</td>

Icon with text

Ideal for sidebars!

<div class="actions">
    <h3><?php echo __('Actions'); ?></h3>
    <ul>
        <li><?php echo $this->Actions->add(__('New Order')); ?></li>
        <li><?php echo $this->Actions->add(__('New Product'), 'products'); ?></li>
        <li><?php
            echo $this->Html->link(
                '<i class="icon-share"></i>' . __('Share this page'),
                array('action' => 'share'),
                array('escape' => false)
            );
        ?></li>
    </ul>
</div>

Feedbacks / Suggestions

All feedbacks are welcome!
Please contact me by email (contact@pierre-martin.fr) or twitter (@PierreMartin).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment