Print with Zebra BrowserPrint SDK, Laravel, AlpineJS and Pusher

ยท

5 min read

Today, I want to show a way to print labels from a Zebra label printer (with BrowserPrint support) from any computer using Laravel broadcast feature.

Before we start, I want to clarify that we can achieve the same result using different frameworks and languages. Obviously, all the custom method/class/event names can be renamed as you wish.

I am assuming you already have a blade view with the necessary boilerplate and the SDK included.

Firstly, we need to create the following resources:

  • an event, App\Events\PrintLabel
  • a blade component, label-printing-listener.blade.php
  • a model, App\Models\Setting

We start from the latter.

The model Setting is used to store the channel name as it will constantly change. You'll understand the reason towards the end of this post.

The model migration will look like this:

Schema::create('settings', function ( Blueprint $table ) {
    $table->id();
    $table->string('name');
    $table->string('value');
    $table->timestamps();
});

The model class will have the following method:

public static function printingLabelChannel( $forceNew = false )
{
    if ( $forceNew ) {
        return self::updateOrCreate([ 'name' => 'printing-label-channel' ], [
            'value' => 'printing-' . Str::uuid()
        ]);
    }

    return self::firstOrCreate([ 'name' => 'printing-label-channel' ], [
        'value' => 'printing-' . Str::uuid()
    ]);
}

The above method will check the value of $forceNew (will see the usage of it later) and will update or get the record based on its value. The name of the channel doesn't need to start with the word printing, but it helps if we need to debug it in the future. It's also not necessary to be followed by a UUID record, it can be replaced by a random string, number, timestamp, etc. as long it's a unique string.


My blade component has some styling that I will avoid sharing, to keep the code as simple as possible.

The first part of the component is the html code, which is:

<div x-data="labelPrintingListenerData()" x-on:new-label.window="users.push($event.detail)">
    <template x-for="user in users" :key="user.id">
        <template x-if="user.printingSucceeded">
            The label for the user <span x-text="user.name"></span> has been printed successfully.
        </template>

        <template x-if="!user.printingSucceeded">
            The label for the user <span x-text="user.name"></span> has NOT been printed.
        </template>
    </template>
</div>

This part is self explanatory, the data will be generated by the labelPrintingListenerData function which we will see below; we then listen for the window event new-label, we'll take its data and push into the users state. We then check if the printing of the label went well or not.

And now the second part of the component with the javascript code:

function labelPrintingListenerData () {
    const pusher = new Pusher( 'YOUR_PUSHER_CLIENT_KEY', {
        cluster: 'ap4'
    } );

    const channel = pusher.subscribe( '{{ \App\Models\Setting::printingLabelChannel(true)->value }}' );

    channel.bind( "App\\Events\\PrintLabel", ( data ) => {
        const user = data;

        let command = 'YOUR ZEBRA INSTRUCTION COMMAND HERE';

        BrowserPrint.getDefaultDevice( "printer", ( device ) => {
            user.printingSucceeded = true;

            const event = new CustomEvent( 'new-label', { detail: user } );
            window.dispatchEvent( event )

            device.send( command, undefined );
        }, ( error ) => {
            user.printingSucceeded = false;

            const event = new CustomEvent( 'new-label', { detail: user } );
            window.dispatchEvent( event )
        } );


    } );

    return {
        users: [],
    };
}

The above code will initialise the subscription to the channel name getting it from the database. Notice that we force the creation of a new record passing true to the function printingLabelChannel(true). The reason is that we are using this page to listen for events sent by other browser instances, and we want to make sure that only one listener instance is active, otherwise each instance will print labels having then duplicates.


The event class is the following

<?php

namespace App\Events;

use App\Models\ServiceTicket;
use App\Models\User;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class PrintLabel implements ShouldBroadcast
{
    use Dispatchable;
    use InteractsWithSockets;
    use SerializesModels;

    public $user;

    public function __construct( User $user )
    {
        $this->user = $user;
    }

    public function broadcastOn()
    {
        return new Channel(Setting::printingLabelChannel()->value);
    }
}

Notice that this time we don't set the $forceNew to true, as we want to get the current channel name that the last browser instance page has generated.


Conclusion

The code is complete, all you have to do now is call the event from anywhere inside your Laravel application

$user = User::firstOrFail();

event(new PrintLabel($user));