Статьи

AngularJS и Laravel: завершаем создание CRM

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

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

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

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


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

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

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

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

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

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

1
2
3
<script src=»http://code.angularjs.org/1.2.3/angular.js»></script>
<script src=»http://code.angularjs.org/1.2.3/angular-route.js»></script>
<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 со следующим кодом:

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

1
return {

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

1
getCustomers: function getCustomers() { return $http.get(‘/customers/all’);

Второй получит только одного клиента по его 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 и контроллером. Это сердце двунаправленной привязки данных Angular. Второй — сервис от фабрики, которую мы создали ранее.

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

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

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

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

Да, это все, что нужно для заполнения шаблона данными. Нет необходимости в каком-либо appendChild() / 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 в его начальное состояние, чтобы очистить $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
2
3
4
5
6
7
8
function customerRemoveSuccess(data) {
    var i = $scope.customers.length;
    while (i—) {
        if ($scope.customers[i].id == data) {
            $scope.customers.splice(i, 1);
        }
    }
}

Полный код должен выглядеть так:

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

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

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

1
<h2>Customers</h2>

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

Теперь добавьте элемент tbody . И здесь снова появляется магия Ангулара. Используя директиву ng-repeat мы сообщаем Angular повторить элемент:

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

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

01
02
03
04
05
06
07
08
09
10
11
12
<tbody>
    <tr>
        <td>{{ customer.id }}</td>
        <td>
            <a ng-click=»removeCustomer({{ customer.id }})»>[-]</a>
            <a href=»#/customer/{{ customer.id }}»>
                {{ customer.first_name }} {{ customer.last_name }}
            </a>
        </td>
        <td>{{ customer.email }}</td>
    </tr>
</tbody>

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
$scope.newTransaction = { name: », amount: 0 };
 
$scope.addTransaction = function addTransaction() {
    $scope.newTransaction.customer_id = $scope.customer.id;
    Data.addTransaction($scope.newTransaction).success(transactionAddSuccess).error(transactionAddError);
}
 
function transactionAddSuccess(data) {
    $scope.error = null;
    data.amount = parseFloat(data.amount);
    $scope.transactions.push(data);
     
    $scope.sum += data.amount;
    $scope.newTransaction = { name: », amount: 0 };
}
 
function transactionAddError(data) {
    $scope.error = data;
}

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
$scope.removeTransaction = function removeTransaction(id) {
    if (confirm(‘Do you really want to remove this transaction?’)) {
        Data.removeTransaction(id).success(transactionRemoveSuccess);
    }
}
 
function transactionRemoveSuccess(data) {
    var i = $scope.transactions.length;
    while (i—) {
        if ($scope.transactions[i].id == data) {
            $scope.sum -= $scope.transactions[i].amount;
            $scope.transactions.splice(i, 1);
        }
    }
}

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

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

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

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

Обратите внимание, что мы используем toFixed(2) чтобы округлить числа с плавающей точкой, чтобы у них было только два десятичных поля, потому что способ, которым Laravel обрабатывает числа с плавающей точкой в ​​JSON.

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


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

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