-
Notifications
You must be signed in to change notification settings - Fork 5
Working with JavaScript and Larasset
Larasset is a library for Laravel 4+ which manage assets in an easy way.
This guide covers the built-in Ajax/JavaScript functionality of Larasset (and more); it will enable you to create rich and dynamic Ajax applications with ease!
After reading this guide, you will know:
- The basics of Ajax.
- Unobtrusive JavaScript.
- How Larasset' built-in helpers assist you.
- How to handle Ajax on the server side.
In order to understand Ajax, you must first understand what a web browser does normally.
When you type http://localhost:8000
into your browser's address bar and hit
'Go,' the browser (your 'client') makes a request to the server. It parses the
response, then fetches all associated assets, like JavaScript files,
stylesheets and images. It then assembles the page. If you click a link, it
does the same process: fetch the page, fetch the assets, put it all together,
show you the results. This is called the 'request response cycle.'
JavaScript can also make requests to the server, and parse the response. It also has the ability to update information on the page. Combining these two powers, a JavaScript writer can make a web page that can update just parts of itself, without needing to get the full page data from the server. This is a powerful technique that we call Ajax.
As an example, here's some JavaScript code that makes an Ajax request using the jQuery library:
$.ajax({
url: "/test"
}).done(function(html) {
return $("#results").append(html);
});
This code fetches data from "/test", and then appends the result to the div
with an id of results
.
Larasset provides quite a bit of built-in support for building web pages with this technique. You rarely have to write this code yourself. The rest of this guide will show you how Larasset can help you write websites in this way, but it's all built on top of this fairly simple technique.
Larasset uses a technique called "Unobtrusive JavaScript" to handle attaching JavaScript to the DOM. This is generally considered to be a best-practice within the frontend community, but you may occasionally read tutorials that demonstrate other ways.
Here's the simplest way to write JavaScript. You may see it referred to as 'inline JavaScript':
<a href="#" onclick="this.style.backgroundColor='#990000'">Paint it red</a>
When clicked, the link background will become red. Here's the problem: what happens when we have lots of JavaScript we want to execute on a click?
<a href="#" onclick="this.style.backgroundColor='#009900';this.style.color='#FFFFFF';">Paint it green</a>
Awkward, right? We could pull the function definition out of the click handler:
var paintIt = function(element, backgroundColor, textColor) {
element.style.backgroundColor = backgroundColor;
if (textColor != null) {
return element.style.color = textColor;
}
};
And then on our page:
<a href="#" onclick="paintIt(this, '#990000')">Paint it red</a>
That's a little bit better, but what about multiple links that have the same effect?
<a href="#" onclick="paintIt(this, '#990000')">Paint it red</a>
<a href="#" onclick="paintIt(this, '#009900', '#FFFFFF')">Paint it green</a>
<a href="#" onclick="paintIt(this, '#000099', '#FFFFFF')">Paint it blue</a>
Not very DRY, eh? We can fix this by using events instead. We'll add a data-*
attribute to our link, and then bind a handler to the click event of every link
that has that attribute:
var paintIt = function(element, backgroundColor, textColor) {
element.style.backgroundColor = backgroundColor;
if (textColor != null) {
return element.style.color = textColor;
}
};
$(function() {
return $("a[data-background-color]").click(function(e) {
var backgroundColor, textColor;
e.preventDefault();
backgroundColor = $(this).data("background-color");
textColor = $(this).data("text-color");
return paintIt(this, backgroundColor, textColor);
});
});
<a href="#" data-background-color="#990000">Paint it red</a>
<a href="#" data-background-color="#009900" data-text-color="#FFFFFF">Paint it green</a>
<a href="#" data-background-color="#000099" data-text-color="#FFFFFF">Paint it blue</a>
We call this 'unobtrusive' JavaScript because we're no longer mixing our JavaScript into our HTML. We've properly separated our concerns, making future change easy. We can easily add behavior to any link by adding the data attribute. We can run all of our JavaScript through a minimizer and concatenator. We can serve our entire JavaScript bundle on every page, which means that it'll get downloaded on the first page load and then be cached on every page after that. Lots of little benefits really add up.
The Larasset team strongly encourages you to write your JavaScript in this style, and you can expect that many libraries will also follow this pattern.
Laravel and Larasset provides a bunch of view helper methods written in PHP to assist you in generating HTML. Sometimes, you want to add a little Ajax to those elements, and Larasset has got your back in those cases.
Because of Unobtrusive JavaScript, the Larasset "Ajax helpers" are actually in two parts: the JavaScript half and the PHP half.
jquery_ujs.js
provides the JavaScript half, and the regular PHP view helpers add appropriate
tags to your DOM. The JavaScript in jquery_ujs.js
then listens for these
attributes, and attaches appropriate handlers.
form_for()
is a helper that assists with writing forms. form_for
takes a 'data-remote'
option and it's based on Form::model()
method. It works like this:
{{ form_for($article, ['route' => 'articles.store', 'data-remote' => true]) }}
...
{{ form_for_close() }}
This will generate the following HTML:
<form method="POST" action="/articles" accept-charset="UTF-8" data-remote="1" id="create_article" class="create_article">
...
</form>
Note the data-remote="1"
. Now, the form will be submitted by Ajax rather
than by the browser's normal submit mechanism.
You probably don't want to just sit there with a filled out <form>
, though.
You probably want to do something upon a successful submission. To do that,
bind to the ajax:success
event. On failure, use ajax:error
. Check it out:
$(document).ready(function() {
return $("#new_article").on("ajax:success", function(e, data, status, xhr) {
return $("#new_article").append(xhr.responseText);
}).on("ajax:error", function(e, xhr, status, error) {
return $("#new_article").append("<p>ERROR</p>");
});
});
Obviously, you'll want to be a bit more sophisticated than that, but it's a start. You can see more about the events in the jquery-ujs wiki.
Form::open()
is very similar to form_for()
. You can add a 'data-remote'
option that you can use like
this:
{{ Form::open(['route' => 'articles.index', 'data-remote' => true]) }}
...
{{ Form::close() }}
This will generate the following HTML:
<form method="POST" action="/articles" accept-charset="UTF-8" data-remote="1">
...
</form>
Everything else is the same as form_for
. See its documentation for full
details.
link_to()
and its variants: link_to_route()
, link_to_action()
.
is a helper that assists with generating links. You can add a 'data-remote'
option that you
can use like this:
{{ link_to(route('articles.show', $article->id), 'an article', ['data-remote' => true]) }}
which generates
<a href="/articles/1" data-remote="1">an article</a>
You can bind to the same Ajax events as form_for()
. Here's an example. Let's
assume that we have a list of articles that can be deleted with just one
click. We would generate some HTML like this:
{{ link_to(route('articles.destroy', $article->id), 'Delete article', ['data-remote' => true, 'data-method' => 'delete']) }}
and write some JavaScript like this:
$(function() {
return $("a[data-remote]").on("ajax:success", function(e, data, status, xhr) {
return alert("The article was deleted.");
});
});
button_to()
is a helper that helps you create buttons. You can add a 'data-remote'
option that you can call like this:
{{ button_to("An article", ['route' => ['articles.show', $article->id], 'data-remote' => true]) }}
this generates
<form method="POST" action="/articles/1" accept-charset="UTF-8" class="button_to" data-remote="1">
<input name="_token" type="hidden" value="JcU31wYcFlo5vq3k28IzlCMOKaD5CGF0Bv91xyyz">
<div><button type="submit" class="btn-default btn">An article</button></div>
</form>
Since it's just a <form>
, all of the information on Form::open()
also applies.
Ajax isn't just client-side, you also need to do some work on the server side to support it. Often, people like their Ajax requests to return JSON rather than HTML. Let's discuss what it takes to make that happen.
Imagine you have a series of users that you would like to display and provide a form on that same page to create a new user. The index action of your controller looks like this:
<?php
class UsersController extends \BaseController
{
public function index()
{
$users = User::all();
$user = new User;
return $this->render('users.index', compact('users', 'user'));
}
// ...
}
The index view (app/views/users/index.blade.php
) contains:
<b>Users</b>
<ul id="users">
{{-- renders app/views/users/_user.blade.php for each user --}}
@foreach ($users as $user)
@include('users._user')
@endforeach
</ul>
<br>
{{ form_for($user, ['route' => 'user.store', 'data-remote' => true]) }}
{{ Form::label('name') }}
{{ Form::text('name') }}
{{ Form::submit('Create User') }}
{{ form_for_close() }}
The app/views/users/_user.blade.php
partial contains the following:
<li>{{{ $user->name }}}</li>
The top portion of the index page displays the users. The bottom portion provides a form to create a new user.
The bottom form will call the store
action on the UsersController
. Because
the form's remote option is set to true, the request will be posted to the
UsersController
as an Ajax request, looking for JavaScript. In order to
serve that request, the store
action of your controller would look like
this:
<?php
// app/controllers/UsersController.php
// ......
public function store()
{
$rules = ['name' => 'required'];
$validator = \Validator::make(\Input::all(), $rules);
$format = \Request::format();
if ($validator->passes()) {
$user = User::create(\Input::all());
switch ($format) {
case 'html':
$render = \Redirect::route('users.show', $user->id)->with('message', 'User was successfully created.');
break;
case 'js':
$render = $this->render(['js' => 'users.store'], ['user' => $user]);
break;
case 'json':
$render = \Response::json(['user' => $user], 201/* status: Created */, ['Location' => route('users.show', $user->id)]);
break;
default:
$render = \Redirect::route('home')->with('message', "Error: Unknown request");
break;
}
} else {
switch ($format) {
case 'html':
$render = \Redirect::route('users.create')->with('message', 'Error: Unable to create the User.');
break;
case 'js':
case 'json':
$render = \Response::json(['error' => ['messages' => $validator->messages()]], 422/* status: Unprocessable Entity */);
break;
default:
$render = \Redirect::route('home')->with('message', "Error: Unknown request");
break;
}
}
return $render;
}
Notice the case 'js':
statement; that allows the controller to
respond to your Ajax request. You then have a corresponding
app/views/users/store_js.blade.php
view file that generates the actual JavaScript
code that will be sent and executed on the client side.
// <script type="text/javascript"> {{-- Keep this line to have syntax highlight in IDE --}}
$({{ json_encode(render_view('users/_user', ['user' => $user])) }}).appendTo("#users");
// </script>{{-- Keep this line to have syntax highlight in IDE --}}
Here are some helpful links to help you learn even more:
- The Asset Pipeline Guide.
- Server generated JavaScript Responses with Laravel and Larasset.
- jquery-ujs wiki:
- Rails 3 Remote Links and Forms: A Definitive Guide
- Railscasts: Unobtrusive JavaScript
The Guide Working with JavaScript in Rails.