28
Nov '20

by john

One of the sites I run is a craft business where we want to have a paginated product listing on the front page, but we also want it to be randomised, so that you don’t always get the same products when you first load it.

I wasn’t even sure this was possible when I began looking into it, but thanks to PHP’s ability to seed the randomisation functions, and a bit of careful tapping into Laravel’s core pagination library it is possible.

I’ve split the logic into two functions. One that generates a repeatably randomised set of product records, and another that gives the ability to use Laravel’s paginator on a Collection instance, rather than a query builder instance:

<?php

use Illuminate\Support\Collection;
use Illuminate\Pagination\Paginator;
use Illuminate\Pagination\LengthAwarePaginator;

class ProductController extends \Controller
{
    /**
     * Product index page
     *
     * @return \Illuminate\Contracts\Support\Renderable
     */
    public function index()
    {
        return view('product.index')->with('title', 'Products')
                    ->with('products', $this->randomisedPaginatedProducts());
    }

    /**
     * Get randomised paginated products
     * @return \Collection
     */
    protected function randomisedPaginatedProducts() {

        if(session()->has('seed'))
            $seed = session('seed');
        else {
            $seed = rand(1, 100);
            session()->put('seed', $seed);
        }

        mt_srand($seed);

        $records = \App\Models\Product::get();

        $range = range(0, $records->count() - 1);
        shuffle($range);

        $randomised = collect($range)->map(function($index) use ($records) {
            return $records[$index];
        });

        return $this->paginateCollection($randomised);
    }

    /**
     * Paginate collection
     *
     * @param Collection $items
     * @param int $per_page
     * @param int|null $page
     * @param array $options (path, query, fragment, pageName)
     * @return LengthAwarePaginator
     */
    protected function paginateCollection(Collection $items, int $per_page = null, int $current_page = null, array $options = [])
    {
        $current_page = $current_page ?: (Paginator::resolveCurrentPage() ?: 1);

        return new LengthAwarePaginator($items->forPage($current_page, $per_page), $items->count(), $per_page, $current_page, $options);
    }
}

The key to this is the seed that we generate and keep in the session (we could equally pass it around in the pagination links if we wanted). The seed here is a number between 1 and 100, but it could be anything, really this just means that there are 100 different combinations of products it could come up with. This seed is used to generate a randomised array of numbers which will correspond to the numeric keys of the list of products, and then the products are sorted into a new array accordingly. Pass these to the paginator and you’re done.

Do be aware that this method requires you to pull all the products out of the database upfront, but for a relatively small total product count it’s no issue.

04
Sep '20

by john

I’m putting this out here right now, as I just spend two hours banging my head against a wall, becoming more and more convinced that witchcraft was afoot.

If you are using boostrap tabs, and have a set displaying generally correctly, but with just one or two that won’t open no matter what you try then I may have the answer for you:

If your target pane ID begins with a digit then it WILL NOT WORK.

Whack on a prefix and all will come right. Good lord that was frustrating!

25
Apr '20

by john

I make a lot of use of bootstrap modals in my code, and I find it incredibly useful to load the content dynamically, rather than have everything embedded in the page just in case it may be needed. To do this I tend to have a modal container (sometimes multiple, allowing for different sizes or styles), and target it from the trigger link.

<a href="/remote/content" class="btn btn-xs btn-primary modal-toggle" data-toggle="modal" data-target="#remote-modal" title="Load remote modal">Load Modal</a>

<div class="modal fade remote-modal" id="remote-modal" role="dialog" aria-hidden="true">
    <div class="modal-dialog" role="document">
        <div class="modal-content">

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

Then all it takes is a modal show listener that grabs the URL from the trigger, and loads the content via AJAX into the modal-content section. This also gives me the opportunity to add a new modal event that is fired when the content has completed loading; ‘loaded.bs.modal’. This new event is hugely useful as the core ‘shown.bs.modal’ event will often trigger before the content is loaded.

$('.remote-modal').on('show.bs.modal', function (e) {
    var trigger = $(e.relatedTarget);
    var modal = $(this);

    $(this).find('.modal-content').load(trigger.attr('href'), function() {
        modal.trigger('loaded.bs.modal', []);
    });
});

Simple as that.

03
Mar '20

by john

I try to return the correct HTTP codes from my AJAX calls whenever possible, and I often use these for different purposes, for example a 401 indicating that a session has expired, and a 403 meaning that the permissions are lacking on the current session.
These can be caught and acted on very easily with jQuery:

$(document).ajaxError(function(event, jqxhr, settings, exception) {
    if(jqxhr.status == 401) {
        alert('Unathorised');
    }
    else if(jqxhr.status == 403) {
        alert('Forbidden');
    }
    else if(jqxhr.status >= 500) {
        alert('An error occurred');
    }
});
26
Jan '20

by john

When working with Eloquent recordsets, I tend to expect them to behave exactly like core Laravel collections. Indeed why wouldn’t they? Why wouldn’t a recordset exist as a collection, with all the properties and functions that come with them?
On the most part you can use an Eloquent collection just like a core Laravel collection, however some functions have different behaviours, such as the ‘merge’ function, which in Eloquent is hard-wired to key by the record primary key, and will overwrite a duplicate key, even if the records are otherwise different.

Enter the completely undocumented ‘toBase’ function.

This is buried within the Eloquent collection class, and will spit out the recordset as a core Laravel collection, and allow you to perform all the normal collection operations in the manner you would expect.

13
Dec '19

by john

Very quick one, because this took me waaaaaaay too long to figure out, due to the error message having no apparent relation to the issue. If you get presented with the error:

Fatal error: Uncaught RuntimeException: A facade root has not been set.

Then this probably means that there is a syntax error in one of your config files.
I hope that’ll save a few hours of head-scratching!

05
Oct '19

by john

We’ve all been there. Customer after customer calls saying they haven’t received the email notifications they were expecting from a system. The issues are pretty much always an incorrect address or an over-enthusiastic spam filter, but even so, I decided to start logging all the notifications sent from my applications. If I can track dates, times, senders, and recipients then I’ve got all the evidence I need if there is a query.

Starting with a table for the logs, allowing for tracking of the notifiable record, the recipient user and the sender:

Schema::create('log_notifications', function ($table) {
    $table->increments('id')->unsigned();
    $table->integer('parent_id');
    $table->string('parent_type');
    $table->integer('user_id')->unsigned()->index();
    $table->integer('sender_id')->unsigned()->index();
    $table->string('email');
    $table->string('type');
    $table->timestamps();

    $table->index(['parent_id', 'parent_type']);
});

Then the main code is a trait that can be used by all notifiable models:

trait Notifiable {

    use \Illuminate\Notifications\Notifiable {
        notify as coreNotify;
    }

    /**
     * Log notification relationship
     */
    public function logNotifications()
    {
        return $this->morphMany('\App\Model\LogNotification', 'parent');
    }

    /**
     * Get Sender ID attribute
     * @param  mixed  $value Original value
     * @return integer
     */
    public function getNotifiableSenderIdAttribute($value) {
        if($value)
            return $value;

        return \Auth::check() ? \Auth::user()->id : 0;
    }

    /**
     * Get User ID attribute
     * @param  mixed  $value Original value
     * @return integer
     */
    public function getNotifiableUserIdAttribute($value) {
        if($value)
            return $value;

        if(in_array(class_basename(get_called_class()), ['User']))
            return $this->id;

        return \Auth::check() ? \Auth::user()->id : 0;
    }

    /**
     * Send the given notification
     * @param  mixed  $notification
     * @return void
     */
    public function notify($instance)
    {
        $this->logNotifications()->create([
            'user_id' => $this->notifiable_user_id,
            'sender_id' => $this->notifiable_sender_id,
            'email' => $this->email,
            'type' => get_class($notification)
        ]);

        $this->coreNotify($notification);
    }
}

This assumes that you have made a model for the notification logs at ‘\App\Model\LogNotification’, but you can change that to however your architecture is set up.

It also assumes a ‘User’ model class that contains your application users. It may be that it’s only your User model that will be notifiable, in which case you can happily drop the polymorphic parent record as it should always match your user_id.

And there you go. not the most eath-shattering of scripts, but definitely a good one to help ensure your notifications are behaving themselves.

13
Sep '19

by john

I recently decided that (after far too long) I needed to get up-to-date with how I manage my package dependencies for one of my bigger projects. I’d been using gulp with bower components hooked into Laravel Elixir basically since I first ported the project to Laravel, and the deprecation was getting beyond a joke.

Despite there being tons of information out there on how to do achieve individual parts of the configuration, nowhere could I find a single example that would show me how to configure NPM for compiling assets to work with Laravel Mix (the version of elixir) and give me a ‘watch’ option to auto-compile new asset changes.

The first job was to rename my gulpfile.js to webpack.mix.js, and then convert the syntax to use Laravel Mix, rather than Elixir. I’m not going to go into the details of that as it’s extremely straightforward.

Removing bower was simply a case of finding the relevant packages in npm, and amending the paths in the webpack.mix.js accordingly.

The hardest part was getting NPM’s package.json setup with everything I needed. All references to gulp and laravel-elixir came out, webpack and webpack-cli went in along with npm-watch and laravel-mix. My full package.json is below:

{
  "private": true,
  "devDependencies": {
    "resolve-url-loader": "^3.1.0",
    "sass-loader": "^7.3.1",
    "webpack": "^4.39.3",
    "webpack-cli": "^3.3.8"
  },
  "dependencies": {
    "@fortawesome/fontawesome-free": "^5.10.2",
    "@storyous/responsive-bootstrap-toolkit": "^2.6.1",
    "babel-core": "^6.26.3",
    "bootstrap": "^3.4.1",
    "bootstrap-datepicker": "^1.6.4",
    "bootstrap-sass": "^3.3.7",
    "clipboard": "^2.0.1",
    "datatables": "^1.10.18",
    "datatables-responsive": "^1.0.7",
    "datatables.net": "^1.10.19",
    "datatables.net-bs": "^1.10.19",
    "datatables.net-plugins": "^1.10.19",
    "graceful-fs": "^4.0.0",
    "jquery": "^3.4.1",
    "jquery-ui": "^1.12.1",
    "jquery-ui-dist": "^1.12.1",
    "jquery-validation": "^1.19.1",
    "laravel-mix": "^4.1.4",
    "node-sass": "^4.12.0",
    "npm-watch": "^0.6.0",
    "pnotify": "^4.0.0",
    "requirejs": "^2.3.6",
    "select2": "^4.0.10"
  },
  "watch": {
    "dev": {
      "patterns": [
        "resources/assets"
      ],
      "extensions": "js,css",
      "quiet": false,
      "delay": 1000,
      "runOnChangeOnly": true
    }
  },
  "scripts": {
    "dev": "node_modules/.bin/webpack --config=node_modules/laravel-mix/setup/webpack.config.js",
    "start": "npm run dev",
    "watch": "node_modules/.bin/npm-watch"
  }
}

With packages in place the last part was to set up the script definitions to compile my assets. The definitions shown will allow the following commands:

npm run dev – Compiles all assets following the script in webpack.min.js

npm run watch dev – Launches an active task that watches all CSS and JS files in resources/assets for changes, and recompiles whenever changes are detected.

So that’s a working overview start to finish. Hopefully it helps to see the setup in its entirety. It’s a really slick system once it’s all configured, but good grief it takes some effort to get there!

01
Jul '19

by john

When moving to a different hosting provider, or doing some housekeeping with shared databases, sometimes it’s necessary to change the wordpress table prefix (the ‘wp_’ bit, by default) to something else. The table rename is easy enough, but after the rename, when trying to use your admin panel you will likely be greeted with a message saying:

Sorry, you are not allowed to access this page.

This is a small undocumented knock-on from the table name change, as there is also an option value that you need to change too. The _options table has an option called ‘wp_user_roles’, which also needs to have its prefix changed to match the tables. Do that and all should come good.

In case you’re interestered this option defines the user roles available to your users. Without it users can’t have assigned roles, and hence they have no permissions for any administrative function.

05
Mar '19

by john

I spent some time messing about with Bootstrap popovers, and other similar tools but was finding that they tended to be overly complicated to use and skin, and also mostly wouldn’t allow form elements to be inside them. I found myself with a task where I was very tight on space and needed to add several textareas to my form. A popover was the obvious choice, but many relied on convoluted attributes on the trigger element or involved javascript handlers. Even more importantly most tend to destroy the popover when it is closed, which is clearly no use I want a form element in there.

After a bit of playing around I came up with this script myself:

CSS:

.popover-container {
    position: relative;
}

.popover-content {
    z-index: 10000;
    border: 1px solid #555555;
    padding: 8px;
    margin-top: 10px;
    width: 300px;
    position: absolute;
    left: -1000000px;
}

.popover-content.open {
    left: -150px;
}

.popover-content.open:after {
    content: '';
    display: block;
    position: absolute;
    top: -6px;
    left: 150px;
    width: 10px;
    height: 10px;
    background: #FFFFFF;
    border-right:1px solid #555555;
    border-top:1px solid #555555;
    -moz-transform:rotate(-45deg);
    -webkit-transform:rotate(-45deg);
}

JS:

$().ready(function() {

    $('.popover-container').on('click', 'button', function() {
        var parent = $(this).parents('.popover-container');
        var content = parent.find('.popover-content');

        if(content.hasClass('open')) {
            closePopover(content);
        }
        else {
            //Clear down any others first
            $('.popover-content.open').each(function() {
                closePopover($this);
            });

            content.addClass('open');

            var offset = 150 - (parent.width() / 2) - 2;

            content.css('left', '-' + offset + 'px');
        }
    });

    //Close on blur
    $('.popover-container').on('blur', '.popover-content', function() {
        closePopover($(this));
    });

    //Close on escape key
    $(document).keyup(function(e) {
        if (e.keyCode === 27) {
            $('.popover-content.open').each(function() {
                closePopover($(this));
            });
        }
    });
});

function closePopover(element) {
    element.removeClass('open');
    element.css('left', '');
}

Example HTML:

<div class="popover-container">

    <button type="button" class="btn btn-xs btn-default">
        Show Popover
    </button>

    <div class="popover-content">
        <div class="form-group">
            <p>This is my popover</p>
            <textarea name="my-textarea"></textarea>
        </div>
    </div>
</div>

Note that the CSS and JS above are based around the popover being 300px wide. You can change this to whatever you like, but bear in mind that the centering (the 150px offsets) needs to be adjusted in a corresponding way.