Browse Tag

web

Laravel Logo

Laravel tip: enqueue script to end of body

UPDATE (7 Jan 2016): Correction to the blade @append. It doesn’t always work if you have more than a few templates appending to the section.

And so I’ve a partial blade template that contains a custom selection form, which will then be used by multiple other (page layout) templates. This allows me to change the design of the custom selection form at any time in a single location rather than going through multiple templates.

The problem is that I’ve written some JavaScript to manipulate the custom selection form on load and on click. Naturally (or lazily), the script depends on jQuery, which will only be loaded at the end of the body.

I don’t want to separate the script from my partial blade template just to insert it into the page layout templates.

Therefore, I need the custom script to be enqueued to the end, after the jQuery has loaded. I thought I could write a class that does something like WordPress’ wp_enqueue_script.

To my horror… er… surprise, Laravel’s Blade template has already catered to this kind of work using:

@section('script')
    @parent
    {{-- something --}}
@endsection

This may sound complicated, but I have 3 hierarchy of blade templates:

  • Master layout template
    • (Page layout) Single column template
      • Partial Form template
    • (Page layout) Two columns template
      • Partial Form template
  1. Master layout template
    • First I create a general master layout that defines 2 main sections: content and script.
    • <!DOCTYPE html>
      <html lang="en">
        <head>
          <meta charset="utf-8">
          <meta http-equiv="X-UA-Compatible" content="IE=edge">
          <meta name="viewport" content="width=device-width, initial-scale=1">
          <title>Laravel 101 Template</title>
          							<link href="css/style.min.css" rel="stylesheet">
        </head>
        <body>
          @yield('content')
      
          <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
          @section('script')
          @show
        </body>
      </html>
  2. Page layout templates
    • Then I create various page layout templates that inherit from Master layout template.
    • It uses the partial form template to collect user input.
    • @extends('layouts.master')
      
      @section('content')
      
          @include('partials.selection-form')
      
      @stop
  3. Partial form template
    • In the partial form, I can easily append my script to the end of the Master layout template like this:
    • <div class="custom-form">
          <h3>A title</h3>
          <input value="0">
          <ul>
              <li><a href="1">Selection 1</a></li>
              <li><a href="2">Selection 2</a></li>
          </ul>
      </div>
      
      @section('script')
          @parent
          <script>
              $('.custom-form').each(function(){
                  doSomething();
              });
          </script>
      @endsection

That’s it! So simple. Love Laravel.

 

Other consideration which failed to work for me.

window.onload

I’ve tried using JavaScript’s window.onload to wait for all the resources to finish downloading before executing my script. However, due to the multiple partial templates I use within a page layout, the result varies from time to time. It seems that only the first window.onload runs properly. I cannot risk that some users may see what other users may not. It’s probably not designed to work like that.

Redmin Series Part 1: Building a Blog with RedminPortal

redminportal_logo_v2_smIn a series of tutorial, I’ll be showing you the steps of building a blog, a Podcast membership site, and an online shop for selling products, using the backend CMS RedminPortal.

RedminPortal is a Laravel 5 package as a flexible content management system for developers.

 

Prerequisite: This tutorial requires knowledge of HTML, PHP, Twitter Bootstrap and Laravel framework. It is meant for developers working on front end site. We’ll use Laravel 5.1 as the foundation due to its LTS support.

All the source codes will be uploaded to github project https://github.com/redooor/redminstore which will, by the end of this series, be a full front-end store system with RedminPortal. You can then use it as a base to build your own application.

In this first tutorial, we’ll be building a simple blog system (something like WordPress) as a Laravel package, using RedminPortal as the backend CMS.

You’ll learn how to:

  1. Build a Laravel package from scratch
  2. Create controller in Laravel
  3. Use Laravel’s Blade templating engine
  4. Use Laravel’s Localization
  5. Use RedminPortal to generate pages and posts
  6. Create dynamic routes to pages and posts
  7. Catch and show error messages such as HTTP 404

Installation

Laravel 5.1

You need to install a full Laravel 5.1 application. Follow the steps in this link and make sure you have a working local environment before proceeding. You should at least be able to access the home page of the application.

RedminPortal

Next, follow the steps in this link to install RedminPortal to your Laravel application. Take note that some features may not be available in version 0.3 yet. To use the latest (but unstable) version, change this line in composer.json:

"require": {
 "laravel/framework": "5.1.*",
 "redooor/redminportal": "0.3.*"
},

To

"require": {
 "laravel/framework": "5.1.*",
 "redooor/redminportal": "dev-develop"
},

Once you’ve installed, follow the migration and publishing steps to create the admin account. Make sure you can access the admin page and log in using the default admin account.

Laravel Package Creation

Folder Structure

Assuming you have your Laravel application installed inside a folder named “laravelapp”, let’s create a folder structure like this:

laravelapp
|- app
|- bootstrap
|- config
  /* other laravel folders omitted for brevity */
  /* create the following folder and its subfolders */
|- packages
  |- redooor
    |- redminstore
      |- src
        |- App
          |- Http
            |- Controllers
          |- Models
            |- UI
        |- facades
        |- resources
          |- lang
          |- views

Noticed that I have named the folders redooor/redminstore. We’ll be calling this package “Redminstore”. If you decide to rename it to something else, do take note that you need to change all references of “Redminstore” to your new name.

Also, because we’ll be using PSR-4 autoloading in our package, all folders inside folder “src” must stay the same. Otherwise the package will fail to work. Take note of the letter casing too. In Linux (most hosting providers use Linux) and Mac OS, the letter casing will affect autoloading.

I’ve left out the steps for setting up PHPUnit tests. If you’re interested, you may visit the github project to study how I did it.

From this point onwards, when I mention <package>, I’m referring to the path “laravelapp/packages/redooor/redminstore”.

Service Provider

Create a new file inside “<package>/src” folder, name it “RedminstoreServiceProvider.php” with this content:

<?php namespace Redooor\Redminstore;

use Illuminate\Support\ServiceProvider;

class RedminstoreServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {
        // Get routes
        include __DIR__.'/App/Http/routes.php';
        
        // Get views
        $this->loadViewsFrom(__DIR__.'/resources/views', 'redminstore');
        
        // Establish Translator Namespace
        $this->loadTranslationsFrom(__DIR__.'/resources/lang', 'redminstore');
        
        // Allow end users to publish and modify views
        $this->publishes([
            __DIR__.'/resources/views' => base_path('resources/views/vendor/redooor/redminstore'),
        ]);
        
        // Allow end users to publish and modify public assets
        $this->publishes([
            __DIR__.'/public' => public_path('vendor/redooor/redminstore'),
        ], 'public');
    }

    /**
     * Register the application services.
     *
     * @return void
     */
    public function register()
    {
        // Load autoload for package development environment only
        $autoloader = __DIR__ . '/../vendor/autoload.php';
        if (file_exists($autoloader)) {
            require_once $autoloader;
        }
        
        $this->app->register('Orchestra\Imagine\ImagineServiceProvider');
        
        $this->app->booting(function() {
            $loader = \Illuminate\Foundation\AliasLoader::getInstance();
            $loader->alias('Redminstore', 'Redooor\Redminstore\Facades\Redminstore');
            $loader->alias('Redminportal', 'Redooor\Redminportal\Facades\Redminportal');
            $loader->alias('Imagine', 'Orchestra\Imagine\Facade');
        });
    }
}

Package Facade

Create a new file inside “<package>/src/facades” folder, name it “Redminstore.php” with this content:

<?php namespace Redooor\Redminstore\Facades;

use Illuminate\Support\Facades\Facade;

class Redminstore extends Facade {

    /**
    * Get the registered name of the component.
    *
    * @return string
    */
    protected static function getFacadeAccessor() { return 'redminstore'; }

}

Routes

Create a new file inside “<package>/src/App/Http” folder, name it “routes.php” with blank content for now.

Base Controller

Create a new file inside “<package>/src/App/Http/Controllers” folder, name it “Controller.php” with this content:

<?php namespace Redooor\Redminstore\App\Http\Controllers;

use Illuminate\Foundation\Bus\DispatchesCommands;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;

abstract class Controller extends BaseController {

	use DispatchesCommands, ValidatesRequests;

}

Package.json

This file contains the information of our package. It’s used when using with Composer and package repository such as Packagist.org.

Create a new file inside “<package>” folder and name it “package.json” with this content:

{
  "name": "redminstore",
  "version": "0.1.0",
  "description": "RedminStore is a frontend theme for Ecommerce sites using RedminPortal as backend.",
  "keywords": [
    "package",
    "laravel",
    "redooor",
    "redmin",
    "portal",
    "ecommerce",
    "frontend",
    "store"
  ],
  "repository": "https://github.com/redooor/redminstore",
  "homepage": "https://github.com/redooor/redminstore",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Redooor LLP",
  "license": "MIT",
  "engines": {
    "node": ">= 0.10.1"
  },
  "devDependencies": {
    "bower": "^1.7.1",
    "grunt": "^0.4.5",
    "grunt-autoprefixer": "^2.2.0",
    "grunt-banner": "^0.2.3",
    "grunt-contrib-clean": "^0.6.0",
    "grunt-contrib-concat": "^0.5.0",
    "grunt-contrib-connect": "^0.9.0",
    "grunt-contrib-copy": "^0.7.0",
    "grunt-contrib-cssmin": "^0.10.0",
    "grunt-contrib-less": "^0.12.0",
    "grunt-contrib-uglify": "^0.6.0",
    "grunt-contrib-watch": "^0.6.1",
    "grunt-exec": "^0.4.6"
  }
}

You may ignore those lines within devDependencies for now. They are meant for the full package development and not required in this tutorial.

Composer Setup

Create a new file inside “<package>” folder and name it “composer.json” with this content:

{
    "name": "redooor/redminstore",
    "description": "RedminStore is a frontend theme for Ecommerce sites using RedminPortal as backend.",
    "keywords": ["package", "laravel", "redooor", "redmin", "portal", "ecommerce", "frontend", "store"],
    "license": "MIT",
    "authors": [
        {
            "name": "Redooor LLP",
            "email": "support@redooor.com",
            "homepage": "http://www.redooor.com"
        }
    ],
    "require": {
        "php": ">=5.5.9",
        "laravel/framework": "5.1.*",
		"illuminate/html": "~5.0",
        "doctrine/dbal": "~2.4",
        "orchestra/imagine": "3.1.*",
        "phansys/getid3": "2.1.*@dev",
        "redooor/redminportal": "dev-develop"
    },
    "require-dev": {
        "phpunit/phpunit": "~4.0",
		"mockery/mockery": "0.9.*",
        "orchestra/testbench": "3.1.*"
	},
    "autoload": {
        "classmap": [
            "tests/bases",
            "tests/RedminTestCase.php"
        ],
        "psr-4": {
            "Redooor\\Redminstore\\": "src/"
        }
    },
    "minimum-stability": "dev",
    "prefer-stable" : true
}

Once you’ve the composer.json ready, you will need to run composer to install all the dependencies.

Open a Terminal and run the commands:

cd laravelapp/packages/redooor/redminstore
composer install --prefer-dist -vvv --profile

–prefer-dist tells the installer to use distribution source if available.

-vvv is verbose mode, telling the installer to print its actions.

–profile tells the installer to save a cache in the system so next time you run it will be faster.

Visit https://getcomposer.org/ to learn more about Composer.

Root’s Composer.json

Now we need to tell the main application about the location of our newly created package.

Go to “laravelapp/composer.json” and add 1 more line to “autoload” -> “psr-4” section, like this (line 8):

"autoload": {
    "classmap": [
        "database"
    ],
    "psr-4": {
        "App\\": "app/",
        "Redooor\\Redminportal\\": "packages/redooor/redminportal/src",
        "Redooor\\Redminstore\\": "packages/redooor/redminstore/src"
    }
},

Root’s Config App.php

Next, go to “laravelapp/config/app.php” and add 1 more line under “providers” section, like this (line 18):

'providers' => [

    /*
     * Laravel Framework Service Providers...
     */
    Illuminate\Foundation\Providers\ArtisanServiceProvider::class,
    /* omitted for brevity */

    /*
     * Application Service Providers...
     */
    App\Providers\AppServiceProvider::class,
    App\Providers\AuthServiceProvider::class,
    App\Providers\EventServiceProvider::class,
    App\Providers\RouteServiceProvider::class,

    Redooor\Redminportal\RedminportalServiceProvider::class,
    Redooor\Redminstore\RedminstoreServiceProvider::class,

],

Run Composer Update

Open a Terminal and run the command inside the root laravalapp folder:

composer update --prefer-dist -vvv --profile

Master Template

Now, we’re almost ready to get into business. Let’s begin with the master template for the entire User Interface of our site. This template will contain the header and footer of the HTML layout, so we don’t have to repeat them in the rest of the pages we create. It will also define the top navigation bar for the site here.

Create a file inside “<package>/src/resources/views/layouts” folder and name it “master.blade.php” with this content:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
        <!--[if lt IE 9]>
        <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
        <![endif]-->
        @section('head')
        <title>RedminStore by Redooor</title>
        @show
    </head>
    <body>
        <header id="header">
            <div class="navbar navbar-default">
                <div class="container">
                    <div class="navbar-header">
                        <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                            <span class="icon-bar"></span>
                            <span class="icon-bar"></span>
                            <span class="icon-bar"></span>
                        </button>
                        <a href="{{ URL::to('/') }}" class="navbar-brand">{{ trans('redminstore::menus.brandname') }}</a>
                    </div>
                    <div class="navbar-collapse collapse">
                        <?php $menu_pages = Redooor\Redminstore\App\Models\UI\Menu::getPages(); ?>
                        @if (count($menu_pages) > 0)
                        <ul class="nav navbar-nav">
                            <li class="dropdown">
                                <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ trans('redminstore::menus.pages') }} <span class="caret"></span></a>
                                <ul class="dropdown-menu">
                                    @foreach ($menu_pages as $menu_page)
                                    <li><a href="{{ URL::to('page/' . $menu_page->slug) }}">{{ $menu_page->title }}</a></li>
                                    @endforeach
                                </ul>
                            </li>
                        </ul>
                        @endif
                        <?php $menu_posts = Redooor\Redminstore\App\Models\UI\Menu::getPosts(); ?>
                        @if (count($menu_posts) > 0)
                        <ul class="nav navbar-nav">
                            <li class="dropdown">
                                <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ trans('redminstore::menus.posts') }} <span class="caret"></span></a>
                                <ul class="dropdown-menu">
                                    @foreach ($menu_posts as $menu_post)
                                    <li><a href="{{ URL::to('post/' . $menu_post->slug) }}">{{ $menu_post->title }}</a></li>
                                    @endforeach
                                </ul>
                            </li>
                        </ul>
                        @endif
                        <ul class="nav navbar-nav navbar-right">
                            <li><a href="{{ url('/admin') }}">{{ trans('redminstore::menus.adminlogin') }}</a></li>
                        </ul>
                    </div><!--/.nav-collapse -->

                </div>
            </div>
        </header>

        <div id="main">
            <div class="container">
                @yield('content')
            </div>
        </div><!--End main-->
        
        <script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>

        @section('footer')
        @show
    </body>
</html>

If you’re new to Laravel’s Blade template, take special attention to all the code beginning with an @ sign. Read here to study more about Blade template.

The HTML is a bare minimum site to get things working with Bootstrap. You may have a different HTML template altogether. But do take note of the following lines.

Line 10 to 12: I’ve created a section “head” that can be overwritten by any child template that inherits from this master template. You will see this in action soon when we create a child template.

Line 27: I’m calling a static function to grab all the public pages from RedminPortal, and then use the returned list of pages to generate the dropdown menu in lines 28 to 39. We don’t have that static function Menu::getPages() yet. Let’s create it soon under section “Create UI Menu”.

Line 28 to 39: This dropdown menu is generated with an if-else statement and a returned list of public pages from static function Menu::getPages(). If the function returns a null, the dropdown menu will not be created. So you will not see this dropdown menu until you create at least 1 page with “Private” option unchecked in RedminPortal.

Line 24 and 41: The curly brackets {{}} is Laravel’s equivalent of PHP’s echo. So whatever is within the curly brackets will be printed on the page. I’m using the helper function trans() here to print the localization text. Let’s create the localization file soon under section “Localization”. Read more here about Laravel’s Localization.

Line 51: This is where the child template gets its content printed. We will see it in action soon when we create child template.

Line 58 to 59: This is another section where child template can insert its own footer into master template. This is useful if you have specific JavaScript that is used in 1 child template only and doesn’t make sense to put it in master template.

Create UI Menu

As mentioned previously, we need to create a static function to retrieve all public pages to return to the master template.

Create a file inside “<package>/src/App/Models/UI” folder and name it “Menu.php” with this content:

<?php namespace Redooor\Redminstore\App\Models\UI;

use Redooor\Redminportal\App\Models\Page;
use Redooor\Redminportal\App\Models\Post;

class Menu
{
    /**
    /* Get all pages.
    /*
    /* @param bool $private To retrieve private or public pages, default public.
    /* @return array(Page) or null if nothing found
     */
    public static function getPages($private = false)
    {
        $pages = Page::where('private', $private)->get();
        
        return $pages;
    }
    
    /**
    /* Get all posts.
    /*
    /* @param bool $private To retrieve private or public posts, default public.
    /* @return array(Post) or null if nothing found
     */
    public static function getPosts($private = false)
    {
        $posts = Post::where('private', $private)->get();
        
        return $posts;
    }
}

We’re using eloquent model Redooor\Redminportal\App\Models\Page from the CMS RedminPortal. Only those pages with flag “private” marked as false will be returned. To study more about Eloquent model in Laravel, read here.

Localization

All localization files must be stored in the folder “<package>/src/resources/lang”. The language are denoted by the subfolder with “en” as the default. For example, if you want to localize to Simplified Chinese, simply add a subfolder name “sc”. Learn more about Laravel’s Localization here.

Now, let’s create a file inside “<package>/src/resources/lang/en” folder and name it “menus.php” with this content:

<?php

return array(
    'brandname' => 'RedminStore',
    'adminlogin' => 'Admin Login',
    'pages' => 'Pages',
    'posts' => 'Posts'
);

In this tutorial, we’ve only created 3 items for “brandname”, “adminlogin” and “pages”.

To use them in any template, simply call the helper function trans() like this:

{{ trans('redminstore::menus.brandname') }}
{{ trans('redminstore::menus.adminlogin') }}
{{ trans('redminstore::menus.pages') }}

The convention for our tutorial will be:

trans('redminstore::menus.<key>')

Why do we use localization in this simple tutorial? Well, I just wanted to show you how you can use localization. But most importantly, if you decide to use RedminStore as the base to build your product, you can always overwrite the localization file without changing the Master Template. Just go to your root laravelapp folder, and add that edited “menus.php” file to the folder “laravelapp/resources/lang/packages/en/redminstore/” and you’re good to go.

Child Templates

Let’s create our first child templates to test our setup.

404 Template

We’ll start with a simple 404 page to tell users that the page they are trying to view cannot be found. This is useful for returning non-existence page later on.

Create a new file inside “<package>/src/resources/views/general” folder and name it “404.blade.php” with this content:

@extends('redminstore::layouts.master')

@section('content')
    <div class='row'>
        <div class='col-md-12'>
            <div class="jumbotron">
                <h1>Oops! 404, Page Not Found.</h1>
                <p>Unfortunately, we're not able to find that URL.</p>
            </div>
        </div>
    </div>
@stop

This is a much shorter template than our master template because we’re extending all layout from layouts.master template.

Line 1: This template extends the content from views/layouts/master.blade.php.

Line 3 and 12: We can now insert our child template’s content into master within this section “content”.

Home Template

This template will be used as the landing page for our site. So when user goes to the root site (e.g. laravelapp.app), we will show this page to them.

Create a new file inside “<package>/src/resources/views/pages” folder and name it “home.blade.php” with this content:

@extends('redminstore::layouts.master')

@section('content')
    <div class='row'>
        <div class='col-md-8'>
            <div class="panel panel-default">
                <div class="panel-body">
                    <h1>RedminStore <small>by Redooor</small></h1>
                    <p>This is a demo template to help you get started with RedminPortal CMS.</p>
                    <p>Try adding a few pages/posts in RedminPortal and watch the page/post being generated automatically.</p>
                </div>
            </div>
        </div>
        <div class='col-md-4'>
            <div class="well well-sm shadow-depth-1">
                <img src="{{ URL::to('vendor/redooor/redminstore/img/redminportal_logo.png') }}" title="ReminPortal" class="img-responsive pull-right">
                <h3>Contribution</h3>
                <p>Source code at <a href="https://github.com/redooor/redminstore">Github</a></p>
                <h3>Maintenance</h3>
                <p>Maintained by the core team with the help of our contributors.</p>
                <h3>License</h3>
                <p>RedminPortal is open-sourced software licensed under the <a href="http://opensource.org/licenses/MIT">MIT license</a>.</p>
            </div>
        </div>
    </div>
@stop

Our First Routes

In previous section, we’ve created 2 templates but there’s no way to see these pages yet. Do view these pages, we need to set up some routes.

If you go to your root site now (e.g. laravelapp.app), you will see the default page with a big Laravel text. That default page comes with every new Laravel installation.

Removing Default Routes

Let’s remove all the default routes from new Laravel installation by deleting the content in “laravalapp/app/Http/routes.php” file.

When you’re done deleting the content, go to your root site again (e.g. laravelapp.app) and you should see an error message saying the route does not exist.

Inserting Our Package Routes

If you look at previously created file RedminstoreServiceProvider.php (Line 15), you would notice that we are loading our routes.php file on boot. This ensures that our package’s routes come after the application’s routes. With that in place, we can save as many routes in our package’s routes.php as we want without changing the root laravelapp’s routes.php.

Custom Routes

That said, we can now edit our package’s routes at “<package>/src/App/Http/routes.php” with this content:

<?php

/*
|--------------------------------------------------------------------------
| Package Routes
|--------------------------------------------------------------------------
*/

Route::group(['namespace' => 'Redooor\Redminstore\App\Http\Controllers'], function () {
    Route::get('/', 'PageController@showHome');
    Route::get('page', 'PageController@show404');
    Route::get('page/{slug}', 'PageController@loadPage');
    Route::get('post', 'PostController@show404');
    Route::get('post/{slug}', 'PostController@loadPost');
});

Line 10: This line replaces the root path (e.g. laravelapp.app) which we had previously deleted from the root’s routes.php. It’s loading PageController’s function showHome().

Line 11: This line points to a new route with “/page” appended after the site path (e.g. laravelapp.app/page). This page serves as an error page when no slug is given. It’s loading PageController’s function show404().

Line 12: This line points to any route with “/page/<slug>” appended after the site path (e.g. laravelapp.app/page/<slug>). This page shows the content of RedminPortal::Page model that matches the given slug. It’s loading PageController’s function loadPage().

These routes will not work yet. We need the PageController to provide the content of the page to the router.

Next, let’s create the PageController.

PageController

This PageController is responsible of returning the content of the page to the router. I’ve introduced 3 functions in the previous section. They are:

  • showHome()
  • show404()
  • loadPage($slug)

Create a new file in “<package>/src/App/Http/Controllers” folder and name it “PageController.php” with this content:

<?php namespace Redooor\Redminstore\App\Http\Controllers;

use Validator;
use Redooor\Redminportal\App\Models\Page;

class PageController extends Controller
{
    public function showHome()
    {
        return view('redminstore::pages.home');
    }
    
    public function show404()
    {
        return view('redminstore::general.404');
    }
    
    public function loadPage($slug)
    {
        $inputs = array('slug' => $slug);
        $rules = array('slug' => 'required|alpha_dash');
        $validation = Validator::make($inputs, $rules);
        
        if ($validation->passes()) {
            $page = Page::where('slug', $slug)->where('private', false)->first();
            
            if ($page) {
                return view('redminstore::pages.view')
                    ->with('page', $page);
            }
        }
        
        return view('redminstore::general.404');
    }
}

Return View

showHome() and show404() simply return the templates we had created earlier on. showHome() return the view from pages.home while show404() return the view from general.404. In these templates, the content are static and we’re not expect any variable inside.

Notice the naming convention we’re using here.

view('redminstore::pages.home')

The package’s name must be included before the folder.file name convention. Without the package’s name, Laravel will attempt to load the files from the root laravelapp resources/views folder instead.

So in Laravel, this

return view('pages.home');
>>> result folder: laravelapp/resources/views/pages/home.blade.php

is different from this

return view('redminstore::pages.home');
>>> result folder: laravelapp/packages/redooor/redminstore/src/resources/views/pages/home.blade.php

Now, try loading the root site (e.g. laravelapp.app) and ‘/page’ site (e.g. laravelapp.app/page). You should see the respective page. If not, revisit all the steps above to ensure all files and folders are in place.

If you try loading the ‘/page/<slug>’ site (e.g. laravelapp.app/page/test-page’), you should see an error message. This is because we have not created the template for pages.view yet.

Create Dynamic Page View

PageController::loadPage($slug) function searches all non-private (i.e. public) pages that have slug matches the given $slug, and then pass the RedminPortal::Page model to the template before returning the view.

The passing of data into the template is this line:

return view('redminstore::pages.view')->with('page', $page);

And now, create a new file in “<package>/src/resources/views/pages” folder and name it “view.blade.php” with this content:

@extends('redminstore::layouts.master')

@section('head')
<title>Redminstore: {{ $page->title }}</title>
@stop

@section('content')
    {!! $page->content !!}
@stop

As you can see, it is a very simple template that extends from layouts.master.

Line 4: Overwrites the <title> of the page with the Page’s title.

Line 8: Outputs the content of the returned Page result. We want the full content (except the navigation bar) to be whatever the blogger has written.

Catching non-existence page

If no Page is found for the given slug, the loadPage($slug) function will return the 404 page to the router.

You can try it by passing any non-existence slug to the site path (e.g. laravelapp.app/page/no-such-page-exist).

Create Page in RedminPortal

Finally, to see our work in action, login to RedminPortal CMS via “/admin” site (e.g. laravelapp.app/admin) with the default credential (if you’re lost, refer to RedminPortal installation guide).

In the menu, go to “Pages” under “Content Management”. Create a few pages with the “Private” flag unchecked.

Then go to root site (e.g. laravelapp.app) and you should see the “Pages” dropdown menu. Open it to select the pages you have created. It should be (almost) What-You-See-Is-What-You-Get.

End of Part 1

“What? How about Post? I thought you said we will learn how to create posts as well?”

Ah, well, in a way, I lied. :p

However, the steps for generating Post is exactly the same as generating Page. It’s just a matter of duplicating and changing the name.

Give it a try and see if you can create your own PostController, view and route.

I’ve uploaded my code in github. If you really can’t get it, study my code in github or drop me a comment below. I’ll try as much as possible to answer your questions.

Laravel Logo

Setting up Laravel 5 on shared hosting server

UPDATE (7 Jan 2016): Added a final step for pointing domain to subfolder.

UPDATE (4 Apr 2016): I’ve received a number of enquiries with this setup. Please test it on your local environment first. Use homestead to create a domain with the same folder structure. If it’s working on homestead, it’ll most likely work on your shared server.

UPDATE (15 Jun 2016): Added some common problems found from the lovely comments. Scroll to the bottom to see

In this tutorial, I’ll show you some guide to deploy your Laravel 5 application onto a shared hosting server.

By default, the Laravel folder structure assumed that the public folder is within the application itself. This is the high level folder structure:

your_app_name
|--app
|--bootstrap
|--config
|--database
|--public
  |--assets
  |--index.php  <--we need to point here
|--resources
|--storage
|--tests
|--vendor

In an ideal scenario, you can simply point the web domain to the public folder so that the URL loads public\index.php.

However, in a shared hosting server, you are limited to putting your web serving files in a folder probably named public_html or www which is publicly accessible via web domain. You may be tempted to do this (NOT recommended):

shared_hosting_root_NOT_recommended
|--other_folders (not accessible via web domain)
|--public_html
  |--your_app_name
    |--app
    |--bootstrap
    |--config
    |--database
    |--public
      |--assets
      |--index.php  <--we need to point here
    |--resources
    |--storage
    |--tests
    |--vendor

You can then point your web domain to the public folder.

The problem with this is that your application is now publicly accessible and that may expose vulnerability. You cannot be sure if someone somewhere is able to access your config folder and look at all your sensitive information such as database credentials.

Instead, put your application root into a folder outside of public_html, and place only the public folder within public_html.

This is the recommended folder structure:

shared_hosting_root_recommended
|--other_folders (not accessible via web domain)
|--applications  (not accessible via web domain)
  |--your_app_name
    |--app
      |--GoPublic.php <--we'll create this
    |--bootstrap
    |--config
    |--database
    |--public <--copy content to public_html
      |--assets
      |--index.php
    |--resources
    |--storage
    |--tests
    |--vendor
    |--deploy.sh <--we'll create this
|--public_html
  |--your_app_name
    |--assets
    |--index.php <-- we need to point here

In Laravel 4, you can easily tell your application that the public folder is now in another location by simply changing the file bootstrap/path.php. See point 9 of my Laravel 4 tutorial.

However, in Laravel 5, this file no longer exists. I’ll show you how.

1. Create a class to point to new location

Create a new file shared_hosting_root\applications\your_app_root\app\GoPublic.php

<?php namespace App;

class GoPublic extends \Illuminate\Foundation\Application
{
 /**
 * Get the path to the public / web directory.
 *
 * @return string
 */
 public function publicPath()
 {
 return $this->basePath.DIRECTORY_SEPARATOR.'../../public_html/your_app_name';
 }
}

We’re using relative path here so you don’t have to know the absolute path. But it is extremely crucial to follow the same recommended folder structure I’ve mentioned earlier.

Updated:

You may need to run the following command in your root folder to autoload the new class. Otherwise you will get an error saying the class cannot be found.

composer dump-autoload

2. Edit bootstrap\app.php

Now we need to tell our application the location of the new public folder. Open and edit your_app_name\bootstrap\app.php:

<?php

/* -- remove/comment this original code
$app = new Illuminate\Foundation\Application(
 realpath(__DIR__.'/../')
);
-- until here */

/* -- add this new code -- */
$app = new App\GoPublic(
 realpath(__DIR__.'/../')
);

As you can see above, we’re using the new class that we’ve created in step 1.

3. Edit public\index.php

You need to edit these 2 lines.

From line 21:

require __DIR__.'/../bootstrap/autoload.php';

to

require __DIR__.'/../../applications/your_app_name/bootstrap/autoload.php';

And from line 35:

$app = require_once __DIR__.'/../bootstrap/app.php';

to

$app = require_once __DIR__.'/../../applications/your_app_name/bootstrap/app.php';

4. Copy public to public_html

Now, copy the content inside applications\your_app_name\public into public_html\your_app_name.

To simplify the process, I would create a bash file to automatically copy the files to the new location.

Create a new file shared_hosting_root\applications\your_app_name\deploy.sh

#!/bin/bash
echo "Copy public folder to public_html/your_app_name"
rsync -rv ./public/ ../../public_html/your_app_name

Using rsync ensures that if you ever run this deploy.sh several times, only those that are newer will be copied to the new location.

When you’re ready to run it, in the terminal run:

?> cd applications\your_app_name
?> sh deploy.sh

Caution: You have to run deploy.sh within applications\your_app_name, otherwise it will fail.

5. Accessing your app

There are 2 ways of accessing your application from the hosting server. You need to follow either one of the following step depending on your setup.

5a) Using subdomain

The easier way of accessing your application is to set up a subdomain (e.g. your_app_name.example.com) and point it to your application folder (i.e. public_html/your_app_name). This should automagically work without much hassle.

5b) Using primary domain

The other (slightly more complex) way is to point your primary domain (e.g. example.com) to your application folder (i.e. public_html/your_app_name).

By default, the primary domain points to the folder public_html in most of the shared hosting environment.

To do that, you need to create (or edit if it’s already there) a .htaccess file inside the public_html folder.

# Do not change this line. 
RewriteEngine on 

# Change example.com to your domain name
RewriteCond %{HTTP_HOST} ^(www.)?example.com$ 

# Change your_app_name to the subfolder name
RewriteCond %{REQUEST_URI} !^/your_app_name/ 

# Don't change the following two lines. 
RewriteCond %{REQUEST_FILENAME} !-f 
RewriteCond %{REQUEST_FILENAME} !-d 

# Change your_app_name to the subfolder name
# Change example.com to your domain name
RewriteRule ^(.*)$ /your_app_name/$1 
RewriteCond %{HTTP_HOST} ^(www.)?example.com$ 
RewriteRule ^(/)?$ your_app_name/index.php [L]

6. Em…

And that’s it.

I would also recommend using the same folder structure during your development so that you can experience the whole process of deployment before you upload to your hosting server.

Hope this helps!

7. Common Problems

There were a few cases of the following that may cause your Laravel 5 to not work on your live server:

  1. Your PHP version is lower than 5.5.9. Laravel 5 requires PHP 5.5.9 and above.
  2. Your folder structure is different. Make sure your local test environment is exactly the same as your live server. Starting from your server’s root folder.
  3. Re-read the whole tutorial. I find that some of my codes were truncated, so make sure you copy and paste correctly. Try scrolling left and right to see the full code line. Sorry about that.

jQuery using $.on to call newly added elements

More than a year ago, I had written a blog about how to bind events to newly added elements using jQuery.

The scenario can be found here: http://blog.kongnir.com/2012/02/08/jquery-function-not-binding-to-newly-added-dom-elements/

In my previous solution, I had used $.live() in place of $.change(). However, as noted by a commenter in that blog, as of time of writing, $.live() is deprecated.

Instead, we should now use $.on() if you need to capture events of newly added elements.

For example, in normal circumstance, we would do this:

$(".existing-button").click(function() {
    alert("This will work if button already exist on load");
});

However, if you add new elements to the document using AJAX, then the above will not work. That button doesn’t exist.

In both circumstances, this will work:

$(document).on("click",".new-button", function() {
    alert("This will work even if element is newly added");
});

If you notice, the syntax is quite different. The element to call on is the containing element, not the element that the event is called. So let’s say “.new-button” is contained within “#main”, then the following should work too.

$("#main").on("click",".new-button", function() {
    alert("This will work even if element is newly added"); 
});

Reference: http://api.jquery.com/on/

Tricky WebDev: Fix missing list’s text in Mobile browsers

I had this very strange bug that didn’t show up in Desktop browsers (Chrome and Firefox) but in Mobile browsers, specifically iOS Safari and Chrome.

On Desktop:
tags_on_desktop

On Mobile:
tags_on_mobile

Since I couldn’t find a way to debug on the Mobile browsers, I had spent quite a while trying to figure out what went wrong. It’s also very strange to actually see the texts that have more than 1 word to appear, but yet it only shows the first word.

So I checked the HTML code from Chrome and saw this:

<li>
    "                             Face                             "
</li>

That’s because in my PHP code, I had actually echoed the text on a new line like this:

<li>
    <?php echo $tag; ?>
</li>

Out of curiosity and pure trial-and-error, I had decided to try this:

<li><?php echo $tag; ?></li>

To produce this:

<li>Face</li>

Guess what?
That solved the bloody issue I had been investigating for many hours.
I think I really need to re-learn the basics of HTML.

Free Web development tools for Mac users

This is an on-going post which will be updated when I find or learn something better. Please leave a comment if you have any suggestion or if you find any error. Help me to help others! Thanks. 🙂 (I found many spams coming in, so please leave out the website if you don’t have any to share.)

Free Web development tools for Mac, especially for PHP developers. Of course, there’re many other free tools available out there. These are the tools that I have been using personally and I find them very useful. I hope you’ll find them useful too.

1. Local web server for testing

  • MAMP to manage local website (http://www.mamp.info/). This is only essential if you’re working with server side language PHP. Updated: If you’re too lazy to set up the Mac with its included Apache server like me, this is a very good app to start with. Just install, run the app and you’re good to go.
  • This tutorial shows how easy it is to set things up in Mac with MAMP for WordPress. I followed this to install a testing development for my work.
  • If you can’t get Aptana (mentioned below) to read the files from MAMP’s default Document root, scroll to the bottom to read more.
  • Mac’s Web Sharing for developers who are only developing using HTML, CSS and Javascript, Mac comes with a web server installed by default. You may want to google “How to enable web sharing on Mac Lion”. Updated: Thanks Glenn for pointing out. If you’re a little command line savvy, you may do away with MAMP and set up your Mac with PHP and MySQL. Just follow the tutorial here. I haven’t tested it though.
  • OS Mountain Lion: Unfortunately, Apple has removed the web sharing option in the sharing preference panel (see here). You can bing it back using command line by following this tutorial. But then again, they really look too much work. I prefer to use MAMP. And because the configuration is self-contained within MAMP, if I messed up the settings, I can simply delete the app and reinstall to reset to default.
  • AMPPS (http://www.ampps.com/): I was preparing for a web design class and was looking for a tool for Windows users similar to MAMP. And then I found this AMPPS. It supports both Windows and Mac, and is said to be portable. I’ve yet to give it a try, I probably will in the near future although MAMP is enough for me at the moment. Read this article about AMPPS vs MAMP: http://bit51.com/bye-bye-mamp-pro-hello-ampps/

2. Text Editor

Both tools provide syntax-highlighting, but Aptana provides auto-complete and syntax error alert features which are slightly more useful for me.

  • Aptana as web development IDE http://aptana.com/
  • I had been using TextWrangler before I finally found this tool. The syntax error alert feature really saves me a lot of trouble trying to debug. And the auto-complete saves me some typing too.
  • One thing I don’t really like is the default white text on black background theme. To change the white text on black background to black text on white background, click the color wheel icon on the top of the Aptana window and select “Aptana Studio 2.x”, “Eclipse” or “Dreamweaver” theme.
  • I didn’t spend time trying to set up Aptana for debugging web environment because I could use MAMP to run the web and see my development while I’m doing my work.
  • Aptana works even better if you have Git installed. It can track branches and changes by indicating them on the left panel. This is a simple yet extremely useful feature. Apple’s Xcode comes with Git. My iMac (with Xcode and SourceTree installed) worked pretty well for Aptana, but my MacBook (no Xcode, only SourceTree installed) didn’t work. So it’s probably advisable to install Xcode along with Git so you don’t have to figure out how to configure Git to work with Aptana.
  • TextWrangler as text editor http://www.barebones.com/products/textwrangler/
  • TextWrangler doesn’t support auto-complete and doesn’t prompt you if there’s any syntax error. You need to know exactly what you’re doing. I have had hard time debugging when I was using this tool. It is still a very good free tool though.

3. Source repository and management

Source control and management is crucial in a development work. Because it is the core of the business. It is important for us to find a good storage and to keep track of all changes done to the source.

  • BitBucket https://bitbucket.org/
  • You can create unlimited private repositories.
  • Each repository is limited to 5 users for free account.
  • I have been using this for several months. It is very easy to use and configure. The SourceTree client works out of the box with BitBucket. And recently they had refreshed and improved their web UI. Very impressed. Not to mention they provide unlimited private repositories.
  • GitHub https://github.com/
  • You can create unlimited public repositories.
  • Private repositories come with a price.

4. Source repository client

You can always use the web page to upload changes. This is just a convenient way to commit the changes to the repository from the Mac.

  • SourceTree app http://www.sourcetreeapp.com/
  • Any Git or Mercurial repositories.
  • I’ve been using this tool and find it very easy to use.
  • The client allows us to add any git repository, not limited to BitBucket. I had tried adding GitHub and “it just works”.
  • For Git beginners the concept of Fetch, Pull, Push, Commit and Checkout may seem overwhelming… but they are the fundamentals of Git that I think it’s good to know. It becomes very useful if you ever want to use Git with command line.
  • GitHub for Machttp://mac.github.com/
  • Limited to GitHub account.
  • I have not personally tried this but the UI looks very intuitive.
  • The UI is so simple that some concepts of Git have been hidden from users. It may be difficult for you to switch to other clients or command line if you don’t have the concept of Fetch, Pull, Push, etc. Nevertheless, it is a simple client for newbie to start using Git within minutes.

While the 2 clients are extremely useful for synchronizing your work on the cloud (their servers), you can always choose to leave the work in your local machine. Simply use commit and not use the Pull and Push commands. But repositories are best for collaboration, so usually developers will sync them to a server for sharing (and backup too).

Using Git from the Terminal

Sometimes you may need Git from the Terminal. In my case, I wanted to use Composer.org to install some dependencies in my Laravel project. So I need Git in my Terminal. It’s not that difficult to install, just go to this link, download the latest package and install it.

https://code.google.com/p/git-osx-installer/downloads/list

5. Aptana & MAMP troubleshoot (Document Root issue)

I didn’t use the default root in MAMP because it didn’t work for me. Aptana doesn’t seem to be able to see the files in the htdocs folder. It’s better to put your work outside the MAMP app, in case it got corrupted and you have to delete it.

Open MAMP, go to “Preferences”, under “Apache” tab, change the Document Root to somewhere else. I like to put it under

/Users/your_username/Sites

This folder is automatically created prior to Mountain Lion. If you purchased the MacBook with Mountain Lion then most probably this folder doesn’t exist. You can always create it or choose another folder. 🙂

When you launch http://localhost:8888 the next time, it will read directly from this folder.

Additional info:
If you have subdirectories inside this folder, simply use http://localhost:8888/subfolder

6. Managing MySQL database

The best tool to manage MySQL database is the tool made by Oracle themselves. They have clients for many platforms, including Windows, Mac and some Linux.

MySQL Workbench: a very nice tool to manage MySQL databases. http://dev.mysql.com/downloads/tools/workbench/

7. FTP Client

There’re many free FTP clients available in the market but these are the few that I have been using and I find them easy to use.

  1. Cyberduck (http://cyberduck.ch/)
  2. Filezilla (https://filezilla-project.org/)

Warning: Be aware that by default Filezilla stores and transmit your password in plain text. I’m not sure if this has been resolved but it has certainly compromised some of my sites before. There’s a way to “fix” it but it sounds too troublesome.

Cyberduck on the other hand stores your password using Mac OS’ keychain. One slight inconvenience in Cyberduck is that it doesn’t have the feature to only upload files that have changed. Filezilla is better in that sense.

More FTP clients here: http://mac.appstorm.net/roundups/internet-roundup/top-7-free-ftp-clients-for-mac/

8. LESS CSS compiler

LESS is a dynamic stylesheet language which extends CSS with dynamic behavior such as variables, mixins, operations and functions. If you want to learn more about LESS, visit www.lesscss.org.

LESS makes CSS logically easy to write and reusable. The only troublesome part, in my opinion, is that you need to compile LESS script into CSS format. The team that came up with LESS has provided a command-line tool to do that. Command-line?! Sounds complicated? Fortunately, there’re many GUI tools that can help us with that. The 2 that I have been using are simple and free of charge.

  1. SimpLESS (http://wearekiss.com/simpless) – SimpLESS was a very easy-to-use and straight-forward GUI compiler. However, I was having a lot of trouble trying to compile Twitter Bootstrap v3. So I switched to…
  2. Koala (http://koala-app.com/) – Koala is not just a LESS compiler, it also supports Sass, Compass and CoffeeScript. It can run on Windows, Mac and Linux. The GUI is also very straight-forward. The only thing that’s currently quite annoying is that it doesn’t allow user to add a single file to compile. You have a add the whole folder, then delete all the files that you don’t want to compile. Other than that, the functionality is comparable to SimpLESS.

For more GUI compilers, you may want to read up here: https://github.com/less/less.js/wiki/GUI-compilers-that-use-LESS.js

9. Configure AMPPS

  1. Download the latest version from http://www.ampps.com/downloads (as of writing it was version 2.0)
  2. Launch the app and Start both Apache and MySQL.
  3. Launch a browser and go to address http://localhost/
  4. You will be asked if you would like to make AMPPS secure. Go ahead and enter a password.
  5. The root folder is by default at /Applications/AMPPS/www/
  6. If you create a subfolder under the root folder, you can access your subfolder site using the URL http://localhost/subfolder

I’m trying out AMPPS now and considering if I should migrate all my projects from MAMP to AMPPS. The main advantage for me is that AMPPS supports both Windows and Mac, while MAMP is only for Mac. Other than that, I don’t feel much difference between the 2, as of now.

jQuery function not binding to newly added dom elements

Updated (14 Nov 2013): $.live() is deprecated. I had written another blog on the new $.on() which will do the same. http://blog.kongnir.com/2013/11/14/jquery-using-on-to-call-newly-added-elements/

I was trying to use jQuery’s .append() function to insert some HTML code into a div.

This was the HTML (Example):

<div class="conditions-basic">
	<span class="function">
		<select name="someName"
                    id="someID" class="ddlSelect">
		<option selected="selected" value="1">1</option>
		<option value="2">2</option>
		<option value="3">3</option>
		</select>
	</span>
	// More elements here
</div>
<div class="conditions-more"></div>

The div class “conditions-basic” contains a select input. So when a user clicks on an ADD button somewhere, I want to duplicate the same select input in div class “condition-more” by doing this:

$('#btnAnd').click( function () {
	var appendHtml = $('div.conditions-basic').html().trim();
	$('div.conditions-more').append(appendHtml);
});

The select input will make some changes to its own newly appended elements. However, the .change() function doesn’t work for newly added elements.
Upon some research, I had read that instead of using .change(), we should use .live() instead.

Reason: live is a late-binding dynamic handler that can attach to elements added to the DOM after the handler is declared. See documentation for more info.

Instead of using .change() event directly like this:

$('.ddlSelect').change(function () {
    alert("this is not working");
});

Use .live() like this:

$('.ddlSelect').live("change", function () {
    alert("this is working");
});

Reference: http://stackoverflow.com/questions/6537323/jquery-function-not-binding-to-newly-added-dom-elements