Advertisement
  1. Code
  2. Laravel

AngularJS и Laravel: Заканчиваем Создание CRM

Scroll to top
Read Time: 13 min
This post is part of a series called Building a Customer Management App Using AngularJS and Laravel.
AngularJS and Laravel: Begin Building a CRM

Russian (Pусский) translation by Ilya Nikov (you can also view the original English article)

При создании одностраничного приложения мы должны использовать какой-то фреймворк для выполнения некоторых задач для нас, чтобы мы могли сосредоточиться на фактической функциональности.

AngularJS отлично подходит здесь, потому что такие функции, как динамическая инъекция зависимостей и привязка двунаправленных данных, просто великолепны. Иногда нам также нужен какой-то сервер. Если вы выбрали PHP, то Laravel может быть вашим лучшим вариантом, так как с ним легко и довольно эффективно работать.

В этой части учебника мы будем строить интерфейс нашего приложения с помощью AngularJS. Angular - действительно уникальный фреймворк. Вместо абстрагирования HTML или предоставления некоторого способа манипуляции с DOM они расширили HTML, чтобы иметь дело с тем, для чего он, конечно же, не предназначен - для работы с динамическими данными.

Из-за этого чтобы освоить Angular может понадобиться немного больше обучения, чем для других фреймворков, но это действительно стоит потраченного времени.


Подготовка

Прежде чем мы начнем программировать наш интерфейс, мы должны немного изменить часть Laravel. Перейдите в app/views, удалите код примера, который там есть, и создайте файл с именем home.php. Теперь давайте создадим макет.

Начните с DOCTYPE и тега html:

1
<!DOCTYPE html>
2
<html ng-app="app">

Как вы можете видеть, мы уже используем некоторые вещи AngularJS - директиву ng-app. Это говорит, что Angular использует модуль с именем app для этого приложения (мы его определим позже). После этого добавьте head с заголовком и CSS:

1
<title>Customer Management</title>  
2
<link rel="stylesheet" href="style.css">

Теперь вы можете поместить теги script c Angular, его модуль маршрутизации и само наше приложение:

1
<script src="http://code.angularjs.org/1.2.3/angular.js"></script>
2
<script src="http://code.angularjs.org/1.2.3/angular-route.js"></script>
3
<script src="./app.js"></script>

Эта директива сообщает Angular, что нужно поместить шаблон, который был запрошен в этот элемент.

После этого нам нужно добавить маршрут, чтобы отобразить шаблон (в app/routes.php). Добавьте это до маршрутов для контроллеров:

1
Route::get('/', function () { return View::make('layout'); });

Теперь, если вы запустите сервер (с помощью php artisan serve), вы должны увидеть наш основной макет, когда вы переходите по адресу http://localhost:8000/ в вашем браузере:

Стили

В этой статье не будет сосредоточено внимание на чем-либо, связанном с CSS, но чтобы во время разработки сделать приложение более приятным для глаз, мы добавим к нему какой-нибудь стиль. Перейдите в каталог public/ вашего приложения (это рядом с app/) и создайте style.css с этим кодом внутри:

1
body {
2
    font-family: Calibri, sans-serif;
3
	width: 800px;
4
	margin: auto;
5
}
6
7
a {
8
	cursor: pointer;
9
	color: blue;
10
	text-decoration: none;
11
}
12
13
table {
14
	width: 100%;
15
}
16
17
table thead tr {
18
	background: #ccc;
19
}
20
21
table tbody tr {
22
	background: #ddd;
23
}
24
25
table tbody tr:nth-child(2n + 1) {
26
	background: #eee;
27
}
28
29
table tr td:nth-child(1) {
30
	text-align: center;
31
}
32
33
table tr td:nth-child(3), table tr td:nth-child(4) {
34
	text-align: right;
35
}
36
37
.error {
38
	color: red;
39
}

Теперь откройте приложение в своем браузере, и макет должен быть ориентирован на более удобный шрифт в заголовке:

Основная структура приложения

Мы начнем с объявления модуля. Модули в Angular - это почти то же самое, что и в любой библиотеке AMD, но с добавлением инъекции зависимостей, которая является действительно полезной вещью, как вы увидите дальше. Вот объявление нашего модуля app:

1
var app = angular.module('app', [ 'ngRoute' ]);

Синтаксис прост - сначала идет имя модуля, а затем массив зависимостей - мы будем использовать ngRoute здесь для обработки навигации.

Маршрутизация

Маршрутизация определена в методе модуля config():

1
app.config(function configure($routeProvider) {

Вот когда инъекция зависимостей запускается в первый раз - наш обратный вызов возьмет $routeProvider в качестве единственного аргумента, и Angeular сам подставим этот модуль.

Вы должны убедиться, что имена аргументов точно такие же, как имена модулей, потому что Angular использует их для соответствия соответствующим модулям.

Теперь давайте уже фактически использовать $routeProvider для настройки маршрутов:

1
$routeProvider
2
	.when('/', { controller: 'CustomersController', templateUrl: './templates/customers.html' })
3
	.when('/customer/:id', { controller: 'CustomerController', templateUrl: './templates/customer.html' })
4
	.otherwise({ redirect: '/' });
5
});

Как вы можете видеть, чтобы определить маршрут, вы должны вызвать метод when() провайдера (обратите внимание, что они могут быть выстроены в цепочку).

Первым параметром является URI, а второй - объект с параметрами маршрута. Здесь мы прикрепляем соответствующие контроллеры и шаблоны к каждому маршруту. Во втором мы также используем в конце :id, чтобы отметить параметр маршрута, который мы будем использовать позже. Метод otherwise() определяет, что произойдет, если будет совершен доступен любому другому URI.


Factory

Прежде чем писать контроллеры, мы должны создать вещь, называемую factory. factory - это функция, которая возвращает сервис, что полезно, если вы хотите отделить любые функции получения/настройки данных от контроллеров (что, конечно же, вы всегда хотите сделать). Мы определяем его с использованием метода factory() модуля:

1
app.factory('Data', function Data($http) {

Первый параметр - это имя службы, а второе - функция, которая возвращает службу, которая будет создана с использованием этой фабрики.

Мы будем использовать модуль $http для доступа к нашему серверу с помощью Ajax. Он предоставляет методы быстрого доступа для всех HTTP-методов, и каждый из них возвращает обещание (если вы не знаете, что это такое, посмотрите здесь и здесь).

Мы должны вернуть сервис как объект со всеми методами, которые будут использоваться в наших контроллерах:

1
return {

Первый получит с помощью GET запроса всех клиентов, чтобы мы могли показать их в списке:

1
getCustomers: function getCustomers() { return $http.get('/customers/all'); },

Второй из них будет получать через GET только одного клиента по его id:

1
getCustomer: function getCustomer(id) { return $http.get('/customers?id='+ id); },

Третий будет отправлять POST запрос на добавление пользователя в базу данных:

1
addCustomer: function addCustomer(data) { return $http.post('/customers', data); },

Второй аргумент в $http.post() - это данные, которые будут отправлены на сервер.

Далее DELETE клиента с предоставленным id:

1
removeCustomer: function removeCustomer(id) { return $http.delete('/customers?id='+ id); }, 

Теперь для транзакций будет несколько похоже. Один, чтобы получить все из них:

1
getTransactions: function getTransactions(id) { return $http.get('/transactions?id='+ id); },

Один для добавления нового:

1
addTransaction: function addTransaction(data) { return $http.post('/transactions', data); },

И один для удаления:

1
removeTransaction: function removeTransaction(id) { return $http.delete('/transactions?id='+ id); } } });

Контроллер клиентов

Контроллеры в Angular (как и предполагает название) позволяют управлять поведением приложения. У нас будет один для каждого шаблона. Сначала мы сделаем один для главной страницы. Начните с определения:

1
app.controller('CustomersController', function CustomersController($scope, Data) {

Второй параметр здесь - это функция конструктора для контроллера. Это первый аргумент ($scope) - это связь между DOM и контроллером. Это сердце двунаправленной привязки данных. Второй - это сервис с фабрикой, который мы создали ранее.

Получение списка

Теперь мы получим список клиентов с сервера, используя наш сервис:

1
Data.getCustomers().success(parseCustomers);

Все обещания в Angular предоставляют методы success() и error(), которые могут использоваться для добавления соответствующих обратных вызовов. Теперь давайте определим функцию, которая будет анализировать входящие данные, чтобы отобразить их на странице:

1
 function parseCustomers(data) { $scope.customers = data; }

Да, это все, что требуется чтобы наполнить шаблон данными. Не требуется никакого внутреннего кода вида innerHTML/appendChild().

Добавление новых клиентов

Мы также должны предоставить возможность добавлять и удалять клиентов. Сначала давайте создадим объект в области, где мы будем хранить данные нового клиента:

1
$scope.newCustomer = { name: '', email: '' };

Таким образом, мы можем избежать доступа к DOM, когда пользователь добавляет клиента. Теперь функция, которая фактически добавит клиента:

1
$scope.addCustomer = function addCustomer() {

Поскольку полное имя пользователя будет отображаться в таблице, поле для него будет таким же, поэтому мы должны разбить его, чтобы получить имя и фамилию:

1
var names = $scope.newCustomer.name.split(' ');

Теперь мы вызываем соответствующую функцию нашей фабрики с данными из $scope:

1
Data.addCustomer({ first_name: names[0], last_name: names[1], email: $scope.newCustomer.email })

После этого мы добавляем слушателей для успеха и ошибок к возвращенному обещанию:

1
.success(customerAddSuccess).error(customerAddError); }

Давайте сначала определим обратный вызов успеха:

1
function customerAddSuccess(data) { 

Аргумент data содержит текст ответа. Нам нужно очистить переменную $scope.error:

1
$scope.error = null;

Добавьте нового клиента в $scope.customers:

1
$scope.customers.push(data);

И установите $scope.newCustomer в исходное состояние, чтобы очистить поля ввода:

1
$scope.newCustomer = { name: '', email: '' }; }

Обратный вызов ошибки просто установит переменную $scope.error в текст, полученный с сервера:

1
function customerAddError(data) { $scope.error = data; } 

Удаление клиентов

Функция удаления клиента будет принимать его id в качестве параметра:

1
$scope.removeCustomer = function removeCustomer(id) {

Мы также отобразим окно подтверждения, чтобы у пользователя появилась возможность отменить действие:

1
if (confirm('Do you really want to remove this customer?')) {

Если пользователь уверен, что он хочет продолжить, мы удаляем клиента:

1
Data.removeCustomer(id).success(customerRemoveSuccess); } }

Обратный вызов здесь должен будет удалить клиента из $scope.customers, используя идентификатор, полученный с сервера:

1
function customerRemoveSuccess(data) {
2
	var i = $scope.customers.length;
3
	while (i--) {
4
		if ($scope.customers[i].id == data) {
5
			$scope.customers.splice(i, 1);
6
		}
7
	}
8
}

Результат

Полный код должен выглядеть следующим образом:

1
app.controller('CustomersController', function CustomersController($scope, Data) {
2
    Data.getCustomers().success(parseCustomers);
3
	
4
	function parseCustomers(data) {
5
		$scope.customers = data;
6
	}
7
	
8
	$scope.newCustomer = { name: '', email: '' };
9
	
10
	$scope.addCustomer = function addCustomer() {
11
		var names = $scope.newCustomer.name.split(' ');
12
		Data.addCustomer({
13
			first_name: names[0],
14
			last_name: names[1],
15
			email: $scope.newCustomer.email
16
		})
17
		.success(customerAddSuccess).error(customerAddError);
18
	}
19
	
20
	function customerAddSuccess(data) {
21
		$scope.error = null;
22
		$scope.customers.push(data);
23
		$scope.newCustomer = { name: '', email: '' };
24
	}
25
26
	function customerAddError(data) {
27
		$scope.error = data;
28
	}
29
	
30
	$scope.removeCustomer = function removeCustomer(id) {
31
		if (confirm('Do you really want to remove this customer?')) {
32
			Data.removeCustomer(id).success(customerRemoveSuccess);
33
		}
34
	}
35
	
36
	function customerRemoveSuccess(data) {
37
		var i = $scope.customers.length;
38
		while (i--) {
39
			if ($scope.customers[i].id == data) {
40
				$scope.customers.splice(i, 1);
41
			}
42
		}
43
	}
44
});

Шаблоны клиентов

Теперь, чтобы фактически показать данные нашим пользователям, мы должны создать шаблон. Мы определили его в маршруте, который должен быть ./templates/customers.html, поэтому создайте каталог public/templates и в нем файл customers.html.

Сначала добавьте заголовок, чтобы пользователь знал, где он:

1
<h2>Customers</h2>

Затем нам понадобится таблица с хорошим заголовком для отображения данных:

Теперь добавьте элемент tbody. И вот тут снова появляется волшебство Angular's. Используя директиву ng-repeat, мы указываем Angular, чтобы он повторил элемент:

1
<tr ng-repeat="customer in customers">

Синтаксис такой же, как у цикла JavaScript for ... in. Теперь мы можем получить доступ к переменной customer, чтобы получить все необходимые нам данные. В Angular вы вставляете переменные, используя двойные фигурные скобки:

1
<tbody>
2
    <tr>
3
        <td>{{ customer.id }}</td>
4
        <td>
5
	        <a ng-click="removeCustomer({{ customer.id }})">[-]</a>
6
	        <a href="#/customer/{{ customer.id }}">
7
		        {{ customer.first_name }} {{ customer.last_name }}
8
	        </a>
9
        </td>
10
        <td>{{ customer.email }}</td>
11
    </tr>
12
</tbody>

Существует также директива ng-click, которая будет действовать как обратный вызов события onclick, мы используем ее для добавления возможности удаления клиентов. Далее есть футер с полями воода, чтобы пользователь мог добавлять новых клиентов:

1
<tfoot>
2
	<tr>
3
		<td></td>
4
		<td><input ng-model="newCustomer.name" style="width: 99%"></td>
5
		<td><input ng-model="newCustomer.email" style="width: 170px"><a ng-click="addCustomer()">[+]</a></td>
6
	</tr>
7
</tfoot>

Мы используем директиву ng-model для привязки соответствующих переменных из области видимости к полям, поэтому они обновляются каждый раз, когда происходит изменение значения входных данных.

Последнее, что нужно сделать, это показать сообщение об ошибке, если оно есть. Для этого мы будем использовать директиву ng-show, которая будет отображать элемент только в том случае, если указанное выражение истинно:

1
<p ng-show="error" class="error">
2
    {{ error }}
3
</p>

Это оно! Теперь вы можете открыть приложение в своем браузере, и вы должны увидеть это:

Вы можете добавить нового клиента, щелкнув знак «плюс» в правом нижнем углу таблицы.


Клиентский контроллер

Теперь давайте создадим контроллер для представления с одним клиентом:

1
app.controller('CustomerController', function CustomerController($scope, $routeParams, Data) {

Получение данных

Мы получаем данные клиента с помощью модуля $routeParams, который содержит все параметры маршрута, такие как :id, указанный нами ранее:

1
Data.getCustomer($routeParams.id).success(parseCustomer);
2
	
3
function parseCustomer(data) {
4
	$scope.customer = data;
5
}

Обратный вызов почти такой же, как в CustomerController. Теперь давайте получим все транзакции клиента:

1
Data.getTransactions($routeParams.id).success(parseCustomersTransactions);
2
	
3
function parseCustomersTransactions(data) {
4
	$scope.transactions = data;
5
	$scope.sum = 0;
6
	for (var k in data) {
7
		$scope.sum += parseFloat(data[k].amount);
8
	}
9
}

Обратный вызов немного отличается от последнего, потому что мы также хотим показать сумму сумм транзакций. Нам нужно использовать parseFloat(), потому что Laravel отправляет float как строки.

Добавление новых транзакций

Код будет очень похож на тот, который используется для создания новых клиентов:

1
$scope.newTransaction = { name: '', amount: 0 };
2
3
$scope.addTransaction = function addTransaction() {
4
    $scope.newTransaction.customer_id = $scope.customer.id;
5
	Data.addTransaction($scope.newTransaction).success(transactionAddSuccess).error(transactionAddError);
6
}
7
8
function transactionAddSuccess(data) {
9
	$scope.error = null;
10
	data.amount = parseFloat(data.amount);
11
	$scope.transactions.push(data);
12
	
13
	$scope.sum += data.amount;
14
	$scope.newTransaction = { name: '', amount: 0 };
15
}
16
17
function transactionAddError(data) {
18
	$scope.error = data;
19
}

Единственное различие заключается в том, что мы добавляем идентификатор клиента к данным, чтобы сервер знал, какая транзакция есть. Обратный вызов успеха также немного изменен, потому что мы должны проанализировать float перед добавлением его в область $scope, и мы должны прибавить эту сумму к нашей итоговой сумме.

Удаление транзакций

Код для функции removeTransaction() почти идентичен removeCustomer, и отличается только именами переменных:

1
$scope.removeTransaction = function removeTransaction(id) {
2
    if (confirm('Do you really want to remove this transaction?')) {
3
		Data.removeTransaction(id).success(transactionRemoveSuccess);
4
	}
5
}
6
7
function transactionRemoveSuccess(data) {
8
	var i = $scope.transactions.length;
9
	while (i--) {
10
		if ($scope.transactions[i].id == data) {
11
			$scope.sum -= $scope.transactions[i].amount;
12
			$scope.transactions.splice(i, 1);
13
		}
14
	}
15
}

Результат

Весь контроллер должен выглядеть так:

1
app.controller('CustomerController', function CustomerController($scope, $routeParams, Data) {
2
    Data.getCustomer($routeParams.id).success(parseCustomer);
3
	
4
	function parseCustomer(data) {
5
		$scope.customer = data;
6
	}
7
	
8
	Data.getTransactions($routeParams.id).success(parseCustomersTransactions);
9
	
10
	function parseCustomersTransactions(data) {
11
		$scope.transactions = data;
12
		$scope.sum = 0;
13
		for (var k in data) {
14
			$scope.sum += parseFloat(data[k].amount);
15
		}
16
	}
17
	
18
	$scope.newTransaction = { name: '', amount: 0 };
19
	
20
	$scope.addTransaction = function addTransaction() {
21
		$scope.newTransaction.customer_id = $scope.customer.id;
22
		Data.addTransaction($scope.newTransaction).success(transactionAddSuccess).error(transactionAddError);
23
	}
24
	
25
	function transactionAddSuccess(data) {
26
		$scope.error = null;
27
		data.amount = parseFloat(data.amount);
28
		$scope.transactions.push(data);
29
		
30
		$scope.sum += data.amount;
31
		$scope.newTransaction = { name: '', amount: 0 };
32
	}
33
	
34
	function transactionAddError(data) {
35
		$scope.error = data;
36
	}
37
	
38
	$scope.removeTransaction = function removeTransaction(id) {
39
		if (confirm('Do you really want to remove this transaction?')) {
40
			Data.removeTransaction(id).success(transactionRemoveSuccess);
41
		}
42
	}
43
	
44
	function transactionRemoveSuccess(data) {
45
		var i = $scope.transactions.length;
46
		while (i--) {
47
			if ($scope.transactions[i].id == data) {
48
				$scope.sum -= $scope.transactions[i].amount;
49
				$scope.transactions.splice(i, 1);
50
			}
51
		}
52
	}
53
});

Шаблон клиента

Шаблон для одного клиента не имеет новых директив Angular, поэтому просто создайте файл customer.html в каталоге public/templates/ и поместите туда весь этот код:

1
<h2>Customer Info</h2>
2
<p>Name: <strong>{{ customer.first_name }} {{ customer.last_name }}</strong></p>
3
<p>E-mail: <strong>{{ customer.email }}</strong></p>
4
5
<h3>Transactions List</h3>
6
<table>
7
    <thead>
8
		<tr>
9
			<th width="25">ID</th>
10
			<th width="*">Name</th>
11
			<th width="85">Amount</th>
12
			<th width="160">Date</th>
13
		</tr>
14
	</thead>
15
	<tbody>
16
		<tr ng-repeat="transaction in transactions">
17
			<td>{{ transaction.id }}</td>
18
			<td><a ng-click="removeTransaction({{ transaction.id }})">[-]</a> {{ transaction.name }}</td>
19
			<td>${{ transaction.amount.toFixed(2) }}</td>
20
			<td>{{ transaction.created_at }}</td>
21
		</tr>
22
	</tbody>
23
	<tfoot>
24
		<tr>
25
			<td></td>
26
			<td><input type="text" ng-model="newTransaction.name" style="width: 99%"></td>
27
			<td><input type="text" ng-model="newTransaction.amount" style="width: 85px"></td>
28
			<td><a ng-click="addTransaction()">[+]</a></td>
29
		</tr>
30
		<tr>
31
			<td></td><td>Sum:</td><td>${{ sum.toFixed(2) }}</td>
32
		</tr>
33
	</tfoot>
34
</table>
35
<p ng-show="error" class="error">
36
	{{ error }}
37
</p>

Обратите внимание, что мы используем toFixed(2) для округления float, поэтому у них будет только два десятичных знака.

Теперь вы можете открыть браузер и нажать на одного из созданных вами клиентов. Вы должны увидеть контроллер и шаблон в действии:


Вывод

Теперь, если вы добавили некоторые функции после первой части, в том числе и в интерфейсе, нужно добавить еще несколько строк в разных местах.

Я надеюсь, что после того, как вы прочтете эту статью, и закончите работу над вашим приложением, вы начнете думать о том, как создавать другие одностраничные приложения с AngularJS и  Laravel. Дайте мне знать, если у вас возникли проблемы с любым из представленных здесь фреймворком.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.