In this tutorial you will master custom package development by learning how to use custom assets like CSS and JavaScript and how to build upon the defaults provided by the core application.
In previous tutorials on package development you've always used assets provided by the core application, like Bootstrap for styling. In this tutorial we'll talk about what assets are provided by default and how you can add to them with your own. If you haven't done the previous tutorials yet, start there and come back later.
The BIIGLE frontend is built upon two frameworks, Bootstrap 3 for CSS and Vue.js (exdended with the vue-resource plugin) for JavaScript. Using vue-strap you can use some interactive components of Bootstrap, too.
In addition to the basic frameworks, the BIIGLE core application also provides Vue components and other objects e.g. for easy interaction with the RESTful API.
Each view extending the base app
template automatically has all these assets available. While you are able to ignore them and use your own frameworks for package development, you are highly encouraged to stick to the default frameworks, keeping the application lean and consistent.
Using Bootstrap for styling is really simple. Just use the documentation as reference for what classes and components are available and you are done. You'll recall having used it already, implementing a panel in the dashboard view mixin or using the grid system in the new view of your quotes
module.
For using Vue.js you can stick to their documentation as well. If you are not familiar with it, you should start here first since we can't give you a crash course in the scope of this tutorial. If you already have some experience with Vue.js you should be able to follow along fine. And maybe reading this tutorial will help you understanding the basics of Vue.js, too.
To show you how to use the provided JavaScript codebase and how to extend it with custom assets, we will extend the previously developed quotes
module yet again. First we will implement a button that should interactively refresh the displayed quote in the quotes view and then we will add some custom styling.
To give an example on how to use the provided codebase we would like our refresh button to simply display a user feedback message through the integrated messaging system first, without interacting with the backend. This will show you how to add core BIIGLE code as a dependency to your custom Vue.js components.
To add custom JavaScript to a view, we need to add to the scripts section of the base app
template. The scripts are usually located at the bottom of a page body so if we wanted to use the default assets already in the content
section of the template it wouldn't work. To append our JavaScript to the scripts section, add the following to the index.blade.php
template of our quotes
package:
@push('scripts') <script type="text/javascript"> // your script goes here </script> @endpush
Looking at the HTML of the rendered page, you'll notice that the new script
tag is already appended at the correct location following all default scripts. So let's populate the tag with the following script:
biigle.$viewModel('quotes-container', function (element) { var messages = biigle.$require('messages.store'); new Vue({ el: element, methods: { refreshQuote: function () { messages.info("I don't do anything yet!"); }, }, }); });
The script already shows two of the special functions that BIIGLE uses for its JavaScript code $viewModel
and $require
. These functions make sure that the code is executed at the right time and in the right order. Modern JavaScript has different techniques to accomplish this but this is how BIIGLE evolved and it works well for BIIGLEs module system.
The $viewModel
function is used whenever a new Vue instance should be mounted to the DOM of a view. It accepts the ID of the DOM element as a first argument and a callback function that is executed when this element is encountered in the DOM as second argument. Most importantly, it dies not create the Vue instance if the specified element is not found in the DOM. This way you can have many BIIGLE modules active on the same page without them interfering with their Vue instances.
The $require
function returns an object that has been registered using the $declare
function before. That's basically all you need to know about it. Take a look at the source code if you want to know more about these functions.
The script above creates a new Vue instance when a DOM element with ID quotes-container
is encountered. It also uses the object that has been registered as messages.store
. The Vue instance has a single method which calls the info
function of the messages object.
Let's edit the content
section of our quotes view to connect the refreshQuote
function with a button and see if everything works:
<div id="quotes-container" class="container"> <div class="col-sm-8 col-sm-offset-2 col-lg-6 col-lg-offset-3"> <blockquote> {{ Inspiring::quote() }} </blockquote> <button class="btn btn-default" v-on:click="refreshQuote">refresh</button> </div> </div>
We add the expected ID to the container element so the Vue instance is created on this page and we tell the Vue instance to call the refreshQuote
function whenever a button is pressed. Try it out; press the new button and see how the messages
object works.
Now you know how to add your own JavaScript code to BIIGLE, how to create new Vue instances and how to make use of the JavaScript codebase that is already there. Let's go on to extend the new Vue instance and include it as custom asset.
In the little JavaScript example above we implemented a new Vue instance using the script
tag, putting the code directly into the HTML. When working with real JavaScript and CSS you usually load these assets as external files. Now all public files - including CSS and JavaScript assets - reside in the public
directory of a Laravel application. When custom packages like to use their own assets there needs to be a mechanism to publish the package assets to the public directory.
Let's see how this works by extending our Vue instance to asynchronously refresh the quotes.
First, we want to outsource the code written above to its own JavaScript file. Create a new file src/public/assets/scripts/main.js
and populate it with the previously written code. Then remove the script
tag from the view (not the section, though).
Now we have to tell Laravel that our quotes
package has some assets to publish. This is done by adding the following to the boot
function of the QuotesServiceProvider
:
$this->publishes([ __DIR__.'/public/assets' => public_path('vendor/quotes'), ], 'public');
Now Laravel can copy anything located in src/public/assets
(of the module) to public/vendor/quotes
(of the core), making the assets of the package available for use in the frontend. If you take a look at the public/vendor
directory, you'll see assets of other packages there, too. Let's add our quotes
assets by running the artisan
utility from the root of the BIIGLE installation:
php artisan vendor:publish --tag=public --provider="Biigle\Modules\Quotes\QuotesServiceProvider" --force
We tell artisan
to publish only the assets of our package so it doesn't overwrite the assets (e.g. configuration files) of other packages. It would do so because we used the force
flag, since we want the files to be replaced during developing the JavaScript application. From now on you always have to run this command again after any changes to the JavaScript application, otherwise the public files wouldn't be refreshed.
Our JavaScript is now publicly available so let's re-populate the scripts
stack of the view template and everything should be back working again:
@push('scripts') <script src="{{ cachebust_asset('vendor/quotes/scripts/main.js') }}"></script> @endpush
The cachebust_asset
helper function is a convenient way to generate URLs to files located in the public
directory of the application. It also automatically appends a timestamp to the URL so browser caches are renewed when the file is updated.
To asynchronously load new quotes from the server, we need a new route and controller method. Since you already know about routes and controllers, let's make it quick:
The test in QuotesControllerTest.php
:
public function testQuoteProvider() { $user = UserTest::create(); // Redirect to login page. $this->get('quotes/new')->assertStatus(302); $this->be($user); $this->get('quotes/new')->assertStatus(200); }
The route in routes.php
:
$router->get('quotes/new', [ 'middleware' => 'auth', 'uses' => 'QuotesController@quote', ]);
The controller function in QuotesController.php
:
/** * Returns a new inspiring quote. * * @return \Illuminate\Http\Response */ public function quote() { return \Illuminate\Foundation\Inspiring::quote(); }
When all tests pass, you have done everything right! Now let's rewrite our little Vue instance in main.js
of the package:
biigle.$viewModel('quotes-container', function (element) { var messages = biigle.$require('messages.store'); new Vue({ el: element, data: { quote: '', }, methods: { refreshQuote: function () { this.$http.get('quotes/new') .then(this.handleResponse, messages.handleErrorResponse) }, handleResponse: function (response) { this.quote = response.body; }, }, created: function () { this.refreshQuote(); }, }); });
We now use the $http.get
function of vue-resource to call the new quotes/new
route whenever the refresh button is clicked. The response is written to the reactive property quote
. If the response is an error, the handleErrorResponse
function of the messages object is used to do error handling. In addition to the click on the button, the quote is also initially refreshed when the Vue instance is created.
Finally, we have to rewire the view a little bit to display the dynamically loaded quote. To do so, replace the old blockquote
element by this one:
<blockquote v-text="quote"></blockquote>
This reactively sets the content of the element to whatever the content of the quote
property is. Now publish the new JavaScript file, refresh the view and enjoy your asynchronously reloaded quote!
Publishing custom CSS files is very similar to publishing JavaScript. Having the boot
method of the service provider already prepared, we now only have to create a new CSS file and include it in the view template. So let's see if we can style the displayed quote a little.
Create a new public/assets/styles/main.css
file for your package and add the style:
blockquote { border-color: #398439; }
Then add the new asset to the styles
section of the view template:
@push('styles') <link href="{{ cachebust_asset('vendor/quotes/styles/main.css') }}" rel="stylesheet"> @endpush
Publish the assets (the command stays the same) and reload the page. Stylish, isn't it?
You probably noticed that manually publishing your assets every time you make a change can be very annoying. Also, it's common to publish JavaScript and CSS as minified files that are smaller and can be transferred faster to the clients. Build systems can do this automatically for you. A thorough explanation of build systems would be out of scope of this tutorial. Look for examples in the official BIIGLE modules on how to configure and use a build system. The BIIGLE modules use gulp.js with a custom extension that was written specially for BIIGLE module development.
Congratulations! Now you know (almost) anything there is to know about developing custom packages for BIIGLE. What's still left is how you can implement your views so they can be extended yet by others. You have done it yourself, implementing the dashboard view mixin. There are a few things to keep in mind when registering your own areas for view mixins but we'll cover that in another tutorial.