facuz/laravel-themes

Theme will help you organize your themes inside Laravel projects easily and maintain its related assets, layouts and partials for the theme in single directory. (Based on teepluss/theme)

3.2.1 2020-08-09 00:23 UTC

README

Laravel-Theme is a theme management for Laravel 5+ (last check 6.3), it is the easiest way to organize your skins, layouts and assets.

This package is based on teepluss\theme

Differences with teepluss version:
  • Compatible with laravel 5+
  • Removed twig compatibility (Reduces the package by 94%).
  • Blade directives
  • Better base template.
  • Simplified configuration.
  • More commands and helper functions.
  • Better README file.
  • Manifest file (Get and set theme info)
  • Middleware to define theme and layout

Usage

Theme has many features to help you get started with Laravel

Installation

To get the latest version of laravel-themes simply require it in your composer.json file.

"facuz/laravel-themes": "^3.2"

You'll then need to run composer install to download it and have the autoloader updated.

Once Theme is installed you need to register the service provider with the application. Open up config/app.php and find the providers key.

'providers' => [
	...
	Facuz\Theme\ThemeServiceProvider::class,

]

Theme also ships with a facade which provides the static syntax for creating collections. You can register the facade in the aliases key of your config/app.php file.

'aliases' => [
	...
	'Theme' => Facuz\Theme\Facades\Theme::class,

]

Publish config using artisan CLI.

php artisan vendor:publish --provider="Facuz\Theme\ThemeServiceProvider"

It's recommended to add to the .env file the theme that we are going to use

APP_THEME=default

Create new theme

The first time you have to create theme "default" structure, using the artisan command:

php artisan theme:create default

If you change the facade name you can add an option --facade="Alias".

This will create the following directory structure:

├── public/
    └── themes/
	└── default/
		├── assets
        	|	├── css/
		|	├── img/
            	|	└── js/
            	├── layouts/
            	├── partials/
           	|	└── sections/
            	├── views/
	        └── widgets/

To delete an existing theme, use the command:

php artisan theme:destroy default

If you want to list all installed themes use the command:

php artisan theme:list

You can duplicate an existing theme:

php artisan theme:duplicate name new-theme

Create from the application without CLI.

Artisan::call('theme:create', ['name' => 'foo']);

Basic usage

To display a view from the controller:

namespace App\Http\Controllers;

use Theme;

class HomeController extends Controller {

	public function getIndex()
	{
		return Theme::view('index');
	}
	...
}

This will use the theme and layout set by default on .env

You can add data or define the theme and layout:

...		
Theme::uses('themename');
        
$data['info'] = 'Hello World'; 

return Theme::view('index', $data);
...

Or you can do:

$cookie = Cookie::make('name', 'Tee');

return Theme::view([
		    'view' => 'index',
		    'theme' => 'default',
		    'layout' => 'layout',
		    'statusCode' => 200,
		    'cookie'  => $cookie,
		    'args' => $data
		   ]);

All values except 'view' are optional

To check whether a theme exists.

Theme::exists('themename');

Each theme must come supplied with a manifest file theme.json stored at the root of the theme, which defines supplemental details about the theme.

{
    "slug": "default",
    "name": "Default",
    "author": "John Doe",
    "email": "johndoe@example.com",
    "description": "This is an example theme.",
    "web": "www.example.com",
    "license": "MIT",
    "version": "1.0"
}

The manifest file can store any property that you'd like. These values can be retrieved and even set through a couple helper methods:

// Get all: (array)
Theme::info(); 
// Get:
Theme::info("property"); 
// Set:
Theme::info("property", "new data"); 

Other ways to display a view:

$theme = Theme::uses('default')->layout('mobile');

$data = ['info' => 'Hello World'];
// It will look up the path 'resources/views/home/index.php':
return $theme->of('home.index', $data)->render();
// Specific status code with render:
return $theme->of('home.index', $data)->render(200);
// It will look up the path 'resources/views/mobile/home/index.php':
return $theme->ofWithLayout('home.index', $data)->render();
// It will look up the path 'public/themes/default/views/home/index.php':
return $theme->scope('home.index', $data)->render();
// It will look up the path 'public/themes/default/views/mobile/home/index.php':
return $theme->scopeWithLayout('home.index', $data)->render();
// Looking for a custom path:
return $theme->load('app.somewhere.viewfile', $data)->render();
// Working with cookie:
$cookie = Cookie::make('name', 'Tee');
return $theme->of('home.index', $data)->withCookie($cookie)->render();
// Get only content:
return $theme->of('home.index')->content();

Finding from both theme's view and application's view:

$theme = Theme::uses('default')->layout('default');

return $theme->watch('home.index')->render();

To find the location of a view:

$which = $theme->scope('home.index')->location();

echo $which; // theme::views.home.index

$which = $theme->scope('home.index')->location(true);

echo $which; // ./public/themes/name/views/home/index.blade.php

Render from string:

return $theme->string('<h1>{{ $name }}</h1>', ['name' => 'Teepluss'], 'blade')->render();

Compile string:

$template = `<h1>Name: {{ $name }}</h1>
		     <p>
		      {{ Theme::widget("WidgetIntro", ["title" => "Demo Widget"])->render() }}
		     </p>`;

echo Theme::blader($template, ['name' => 'Teepluss']);

Symlink from another view

This is a nice feature when you have multiple files that have the same name, but need to be located as a separate one.

// Theme A : /public/themes/a/views/welcome.blade.php
// Theme B : /public/themes/b/views/welcome.blade.php

// File welcome.blade.php at Theme B is the same as Theme A, so you can do link below:

Theme::symlink('a'); 
// Location: public/themes/b/views/welcome.blade.php

Configuration

After the config is published, you will see a global config file /config/theme.php, all the configuration can be replaced by a config file inside a theme: /public/themes/[theme]/config.php

The config is convenient for setting up basic CSS/JS, partial composer, breadcrumb template and also metas.

'events' => [

	/* 
	 * Before event inherit from package config and the theme that call
	 * before, you can use this event to set meta, breadcrumb
	 * template or anything you want inheriting.
	 */
	'before' => function($theme)
	{
		// You can remove this lines anytime.
		$theme->setTitle('Title Example');
		$theme->setAuthor('John Doe');
		$theme->setKeywords('Example, Web');
	
		// Breadcrumb template.
		$theme->breadcrumb()->setTemplate(`        
			 <ul class="breadcrumb">
			 @foreach($crumbs as $i => $crumb)
				 @if($i != (count($crumbs) - 1))
					<li>
                    	<a href="{{ $crumb["url"] }}">{{ $crumb["label"] }}</a>
                        <span class="divider">/</span>
					</li>
				 @else
					<li class="active">{{ $crumb["label"] }}</li>
				 @endif
			 @endforeach
			 </ul>             
		 `);
	 },
    
    /*
	 * Listen on event before render a theme, this
	 * event should call to assign some assets.
	 */
	'asset' => function($asset)
	{
		$asset->themePath()->add([
					['style', 'css/style.css'],
					['script', 'js/script.js']
					 ]);

		// You may use elixir to concat styles and scripts.
		$asset->themePath()->add([
					['styles', 'dist/css/styles.css'],
					['scripts', 'dist/js/scripts.js']
					 ]);

		// Or you may use this event to set up your assets.
		$asset->themePath()->add('core', 'core.js');			
		$asset->add([
			['jquery', 'vendor/jquery/jquery.min.js'],
			['jquery-ui', 'vendor/jqueryui/jquery-ui.min.js', ['jquery']]
			 ]);
	},
   

	/*
	 * Listen on event before render a theme, this event should
	 * call to assign some partials or breadcrumb template.
	 */
	'beforeRenderTheme' => function($theme)
	{
		$theme->partialComposer('header', function($view){
			$view->with('auth', Auth::user());
		});
	},

	/*
	 * Listen on event before render a layout, this should 
	 * call to assign style, script for a layout.
	 */
	'beforeRenderLayout' => [
		'mobile' => function($theme){
			$theme->asset()->usePath()->add('ipad', 'css/layouts/ipad.css');
		}
	]
];

Basic usage of assets

You can add assets on the asset method of the config file. If yo want to add assets in your route you can get $asset variable from $theme->asset().

$asset->add('core-style', 'css/style.css');
// path: public/css/style.css

$asset->container('footer')->add('core-script', 'js/script.js');
// path: public/js/script.css

$asset->themePath()->add('custom', 'css/custom.css', ['core-style']);
// path: public/themes/[current-theme]/assets/css/custom.css
// This case has dependency with "core-style".

$asset->container('footer')->themePath()->add('custom', 'js/custom.js', array('core-script'));
// path: public/themes/[current theme]/assets/js/custom.js
// This case has dependency with "core-script".

You can force use theme to look up existing theme by passing parameter to method: $asset->themePath('default')

Writing in-line style or script:

// Dependency with.
$dependencies = [];

// Writing an in-line script.
$asset->writeScript('inline-script', '
	$(function() {
		console.log("Running");
	})', $dependencies);

// Writing an in-line style.
$asset->writeStyle('inline-style', 'h1{ font-size: 0.9em; }', $dependencies);

// Writing an in-line script, style without tag wrapper.
$asset->writeContent('custom-inline-script', '
	<script>
		$(function() {
			console.log("Running");
		});
	</script>', $dependencies);

Render styles and scripts in your blade layout:

// Without container
@styles()

// With "footer" container
@scripts('footer')

// Get a specific path from the asset folder
@asset('img/image.png')

Scripts and Style can be used with or without container

or a more complex way:

{!! Theme::asset()->styles() !!}

{!! Theme::asset()->container('footer')->scripts() !!}

Direct path to theme asset:

{!! Theme::asset()->url('img/image.png') !!}

Preparing group of assets:

Some assets you don't want to add on a page right now, but you still need them sometimes, so cook and serve is your magic.

Cook your assets.

Theme::asset()->cook('backbone', function($asset)
{
	$asset->add('backbone', '//cdnjs.cloudflare.com/ajax/libs/backbone.js/1.0.0/backbone-min.js');
	$asset->add('underscorejs', '//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js');
});

You can prepare on a global in package config:

// Location: config/theme/config.php
....
	'events' => array(
		....
		// This event will fire as a global you can add any assets you want here.
		'asset' => function($asset)
		{
			// Preparing asset you need to serve after.
			$asset->cook('backbone', function($asset)
			{
				$asset->add('backbone', '//cdnjs.cloudflare.com/ajax/libs/backbone.js/1.0.0/backbone-min.js');
				$asset->add('underscorejs', '//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js');
			});
		}
	)
....

Serve theme when you need:

// At the controller.
Theme::asset()->serve('backbone');

Then you can get output:

<html>
  <head>
      @styles()
      @styles('your-container')
  </head>
  <body>
      ...
      @scripts()
      @scripts('your-container')
  </body>
<html>

Partials

Render a partial in your layouts or views:

@partial('header', ['title' => 'Header']);

This will look up to /public/themes/[theme]/partials/header.php, and will add a variable $title (optional)

Partial with current layout specific:

Theme::partialWithLayout('header', ['title' => 'Header']);

This will look up up to /public/themes/[theme]/partials/[CURRENT_LAYOUT]/header.php

Finding from both theme's partial and application's partials:

Theme::watchPartial('header', ['title' => 'Header']);
Partial composer:
$theme->partialComposer('header', function($view)
{
	$view->with('key', 'value');
});

// Working with partialWithLayout.
$theme->partialComposer('header', function($view)
{
	$view->with('key', 'value');
}, 'layout-name');

Sections

The @sections blade directive simplify the access to /partials/sections/ path:

@sections('main')

It's the same as:

@partial('sections.main')

Magic methods

Magic methods allow you to set, prepend and append anything.

$theme->setTitle('Your title');

$theme->appendTitle('Your appended title');

$theme->prependTitle('Hello: ....');

$theme->setAnything('anything');

$theme->setFoo('foo');

$theme->set('foo', 'foo');

Render in your blade layout or view:

@get('foo')

@get('foo', 'Default msj')

Theme::getAnything();

Theme::getFoo();

Theme::get('foo', 'Default msj');
Check if the place exists or not:
@getIfHas('title')

It's the same as:

@if(Theme::has('title'))
	{{ Theme::get('title') }}
@endif
@if(Theme::hasTitle())
	{{ Theme::getTitle() }}
@endif

Get argument assigned to content in layout or region:

Theme::getContentArguments();
Theme::getContentArgument('name');

To check if it exists:

Theme::hasContentArgument('name');

Theme::place('content') is a reserve region to render sub-view.

Preparing data to view

Sometimes you don't need to execute heavy processing, so you can prepare and use when you need it.

$theme->bind('something', function()
{
	return 'This is bound parameter.';
});

Using bound data on view:

echo Theme::bind('something');

Breadcrumb

In order to use breadcrumbs, follow the instruction below:

$theme->breadcrumb()->add('label', 'http://...')->add('label2', 'http:...');

// or

$theme->breadcrumb()->add([[
			 'label' => 'label1',
			 'url'   => 'http://...'
			],[
			 'label' => 'label2',
			 'url'   => 'http://...'
			]]);

To render breadcrumbs:

{!! $theme->breadcrumb()->render() !!}

or

{!! Theme::breadcrumb()->render() !!}

You can set up breadcrumbs template anywhere you want by using a blade template.

$theme->breadcrumb()->setTemplate('
	<ul class="breadcrumb">
	  @foreach ($crumbs as $i => $crumb)
		  @if ($i != (count($crumbs) - 1))
			  <li><a href="{{ $crumb["url"] }}">{{ $crumb["label"] }}</a><span class="divider">/</span></li>
		  @else
			  <li class="active">{{ $crumb["label"] }}</li>
		  @endif
	  @endforeach
	</ul>
');

Widgets

Theme has many useful features called "widget" that can be anything. You can create a global widget class using artisan command:

php artisan theme:widget demo --global

Widget tpl is located in "resources/views/widgets/{widget-tpl}.blade.php"

Creating a specific theme name.

php artisan theme:widget demo default 

Widget tpl is located in "public/themes/[theme]/widgets/{widget-tpl}.blade.php"

Now you will see a widget class at /app/Widgets/WidgetDemo.php

<h1>User Id: {{ $label }}</h1>
Calling your widget in layout or view:
@widget('demo', ['label' => 'Hi!'])

or

{!! Theme::widget('demo', ['label' => 'Hi!'])->render() !!}

Using theme global

use Facuz\Theme\Contracts\Theme;
use App\Http\Controllers\Controller;

class BaseController extends Controller {

	/**
	 * Theme instance.
	 *
	 * @var \Facuz\Theme\Theme
	 */
	protected $theme;

	/**
	 * Construct
	 *
	 * @return void
	 */
	public function __construct(Theme $theme)
	{
		// Using theme as a global.
		$this->theme = $theme->uses('default')->layout('ipad');
	}

}

To override theme or layout.

public function getIndex()
{
	$this->theme->uses('newone');

	// or just override layout
	$this->theme->layout('desktop');

	$this->theme->of('somewhere.index')->render();
}

Middleware:

A middleware is included out of the box if you want to define a theme or layout per route. For Laravel 5.4+ the middleware is installed by default.

To install it in Laravel before 5.4:

Only register it in app\Http\Kernel.php

protected $routeMiddleware = [
    ...
    'setTheme' => \Facuz\Theme\Middleware\ThemeLoader::class,
];
Usage:

You can apply the middleware to a route or route-group with the string 'theme:[theme],[layout]'

Route::get('/', function () {
	...
	return Theme::view('index');
})->middleware('theme:default,layout');

Or using groups:

Route::group(['middleware'=>'theme:default,layout'], function() {
    ...
});

Helpers

Protect emails:

Protect the email address against bots or spiders that index or harvest addresses for sending you spam.

{!! protectEmail('email@example.com') !!}

or shorter

@protect('email@example.com')
Metadata init:

Print meta tags with common metadata.

{!! meta_init() !!}

Returns: <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta name="viewport" content="width=device-width, initial-scale=1">

Cheatsheet

Commands:
Command Description
artisan theme:create name Generate theme structure.
artisan theme:destroy name Remove a theme.
artisan theme:list Show a list of all themes.
artisan theme:duplicate name new Duplicate theme structure from other theme.
artisan theme:widget demo default Generate widget structure.
Blade Directives:
Blade Description
@get('value') Magic method for get.
@getIfHas('value') Like @get but show only if exist.
@partial('value', ['var'=> 'optional']) Load the partial from current theme.
@section('value', ['var'=> 'optional']) Like @partial but load from sections folder.
@content() Load the content of the selected view.
@styles('optional') Render styles declared in theme config.
@scripts('optional') Render scripts declared in theme config.
@widget('value', ['var'=> 'optional']) Render widget.
@protect('value') Protect the email address against bots or spiders.
@dd('value') Dump and Die.
@d('value') Only dump.
@dv() Dump, Die and show all defined variables.
Helpers:
Helper Description
protectEmail('email') Protect the email address against bots or spiders.
meta_init() Print meta tags with common metadata.