Advertisement
  1. Code
  2. Angular

Utworznie za pomocą AngularJS prostego koszyka na zakupy – część druga

Scroll to top
Read Time: 8 min

Polish (Polski) translation by Agnieszka Górczyńska (you can also view the original English article)

W poprzedniej części zobaczyłeś, jak rozpocząć pracę nad utworzeniem w AngularJS prostego koszyka na zakupy. Wprawdzie przygotowaliśmy projekt, ale okazał się zbyt prosty, aby móc go określić mianem aplikacji AngularJS. W tej części serii opracujemy własną dyrektywę AngularJS przeznaczoną do implementacji wymaganej funkcjonalności.

Rozpoczęcie pracy

Pracę rozpoczynamy od sklonowania z serwisu GitHub kodu źródłowego utworzonego w poprzednim artykule.

1
git clone https://github.com/jay3dec/AngularShoppingCart_Part1.git

Następnie przechodzimy do katalogu projektu i instalujemy wymagane zależności.

1
cd AngularShoppingCart_Part1

2
npm install

Po zainstalowaniu zależności można uruchomić serwer.

1
node server.js

Jeżeli w przeglądarce internetowej wpiszesz adres http://localhost:3000/, zobaczysz uruchomioną aplikację.

Utworzenie dyrektywy

Wyświetlane na stronie cart.html produkty i opcje powtarzają się. Opracujemy więc dyrektywę AngularJS przeznaczoną do tworzenia produktów i opcji na podstawie dostarczonych danych. W celu zachowania prostoty przyjmujemy założenie, że zawartość koszyka na zakupy przedstawia się następująco:

1
[{
2
    'item': 'Hard Disk',
3
    'id': 'HD',
4
    'selected': 0,
5
    'prices': [{
6
        'size': '200GB',
7
        'price': '2000'
8
    }, {
9
        'size': '400GB',
10
        'price': '4000'
11
    }]
12
}, {
13
    'item': 'CPU',
14
    'id': 'CPU',
15
    'selected': 0,
16
    'prices': [{
17
        'size': 'i3',
18
        'price': '20000'
19
    }, {
20
        'size': 'i5',
21
        'price': '25000'
22
    }]
23
}, {
24
    'item': 'Monitor',
25
    'id': 'MON',
26
    'selected': 0,
27
    'prices': [{
28
        'size': '16\'',
29
        'price': '3000'
30
    }, {
31
        'size': '19\'',
32
        'price': '5000'
33
    }]
34
}, {
35
    'item': 'Optical Mouse',
36
    'id': 'MOU',
37
    'selected': 0,
38
    'prices': [{
39
        'size': 'Optical',
40
        'price': '350'
41
    }, {
42
        'size': 'Advanced',
43
        'price': '550'
44
    }]
45
}, {
46
    'item': 'RAM',
47
    'id': 'RM',
48
    'selected': 0,
49
    'prices': [{
50
        'size': '4GB',
51
        'price': '4000'
52
    }, {
53
        'size': '8GB',
54
        'price': '8000'
55
    }]
56
}, {
57
    'item': 'USB Keyboard',
58
    'id': 'KEY',
59
    'selected': 0,
60
    'prices': [{
61
        'size': 'Standard',
62
        'price': '2500'
63
    }, {
64
        'size': 'Advanced',
65
        'price': '4500'
66
    }]
67
}]

W kontrolerze CartCtrl umieść poniższe dane.

1
$scope.shopData = [{
2
    'item': 'Hard Disk',
3
    'id': 'HD',
4
    'selected': 0,
5
    'prices': [{
6
        'size': '200GB',
7
        'price': '2000'
8
    }, {
9
        'size': '400GB',
10
        'price': '4000'
11
    }]
12
}, {
13
    'item': 'CPU',
14
    'id': 'CPU',
15
    'selected': 0,
16
    'prices': [{
17
        'size': 'i3',
18
        'price': '20000'
19
    }, {
20
        'size': 'i5',
21
        'price': '25000'
22
    }]
23
}, {
24
    'item': 'Monitor',
25
    'id': 'MON',
26
    'selected': 0,
27
    'prices': [{
28
        'size': '16\'',
29
        'price': '3000'
30
    }, {
31
        'size': '19\'',
32
        'price': '5000'
33
    }]
34
}, {
35
    'item': 'Optical Mouse',
36
    'id': 'MOU',
37
    'selected': 0,
38
    'prices': [{
39
        'size': 'Optical',
40
        'price': '350'
41
    }, {
42
        'size': 'Advanced',
43
        'price': '550'
44
    }]
45
}, {
46
    'item': 'RAM',
47
    'id': 'RM',
48
    'selected': 0,
49
    'prices': [{
50
        'size': '4GB',
51
        'price': '4000'
52
    }, {
53
        'size': '8GB',
54
        'price': '8000'
55
    }]
56
}, {
57
    'item': 'USB Keyboard',
58
    'id': 'KEY',
59
    'selected': 0,
60
    'prices': [{
61
        'size': 'Standard',
62
        'price': '2500'
63
    }, {
64
        'size': 'Advanced',
65
        'price': '4500'
66
    }]
67
}];

W pliku cart.html usuń powtarzające się elementy o klasie  .panel. Kod HTML będzie tworzony dynamicznie za pomocą dyrektywy ngRepeat i danych umieszczonych w $scope.shopData. W pierwszej kolumnie znacznika <div> o klasie .row umieść poniższy kod HTML.

1
<div class="col-xs-7 col-md-8 col-sm-8 col-lg-8">
2
    <div class="panel panel-primary" ng-repeat="q in shopData">
3
        <div class="panel-heading">
4
            <h3 class="panel-title">{{q.item}}</h3>
5
        </div>
6
7
    </div>
8
</div>

Jak możesz zobaczyć w powyższym fragmencie kodu, użycie dyrektywy ngRepeat pozwala na przeprowadzenie iteracji przez dane shopData i wygenerowanie kodu HTML. Zapisz wprowadzone zmiany i ponownie uruchom serwer. Po odświeżeniu strony zobaczysz na niej nowo dodane produkty.

Cart Page With ItemsCart Page With ItemsCart Page With Items

Kolejnym krokiem jest wyświetlenie opcji dla poszczególnych produktów, na przykład pojemności dysku, szybkości procesora, ceny itd. (spójrz na przedstawione wcześniej dane JSON). Do tego celu przygotujemy własną dyrektywę. W AngularJS dyrektywa to komponent, który oferuje najpotężniejsze możliwości. Więcej informacji szczegółowych na temat dyrektyw AngularJS znajdziesz w oficjalnej dokumentacji.

Opracujemy własną dyrektywę o nazwie checkList. Otwórz plik cart.js, a następnie wprowadź przedstawiony poniżej kod:

1
.directive('checkList', function() {
2
    return {
3
        restrict: 'E',
4
        template: function(elem, attrs) {
5
            return '<div class="panel-body">\

6
                    <div class="radio">\

7
                        <label><input type="radio">Option1</label>\

8
                    </div>\

9
          <div class="radio">\

10
                        <label><input type="radio">Option2</label>\

11
                    </div>\

12
		    <div class="radio">\

13
                        <label><input type="radio">Option2</label>\

14
                    </div>\

15
                </div>'
16
        }
17
    };
18
})

Jak wcześniej wspomniałem, dyrektywa nosi nazwę checkList. Powyższa dyrektywa ma dwa parametry: restrict i template. Parametr restrict definiuje sposób wywołania dyrektywy. Ponieważ wymienionemu parametrowi przypisaliśmy wartość E, dyrektywa będzie wywoływania podobnie jak znaczniki:

1
<check-list></check-list>

Z kolei parametr template zawiera kod HTML zastępujący dyrektywę checkList na wygenerowanej stronie. Tutaj wykorzystaliśmy dokładnie ten sam statyczny kod HTML, który poznałeś już wcześniej. Teraz możemy już wywołać dyrektywę checkList na stronie cart.html.

1
<div class="panel panel-primary" ng-repeat="q in shopData">
2
    <div class="panel-heading">
3
        <h3 class="panel-title">{{q.item}}</h3>
4
    </div>
5
    <check-list></check-list>
6
</div>

Zapisz wprowadzone zmiany i odśwież stronę koszyka na zakupy. Zobaczysz wyświetlone statyczne opcje HTML dla poszczególnych produktów.

Cart Page with DirectiveCart Page with DirectiveCart Page with Directive

Musimy nieco zmodyfikować dyrektywę, aby odczytywała dane zdefiniowane w $scope.shopData. Przede wszystkim, zamiast powtarzać opcje w dyrektywie, wykorzystamy ngRepeat do iteracji przez przygotowane dane. Po wprowadzeniu poniższych zmian, dyrektywa checkList będzie działała w sposób dynamiczny.

1
template: function(elem, attrs) {
2
    return '<div class="panel-body">\

3
                    <div class="radio" ng-repeat="i in option">\

4
                        <label><input type="radio">{{i.size}} Rs.{{i.price}}</label>\

5
                    </div>\

6
                </div>'
7
}

Jak widać w powyższym fragmencie kodu, dyrektywie należy przekazać pewne dane. Dlatego też musimy zdefiniować atrybut o nazwie option i za jego pomocą przekazać dyrektywie wymagane dane. W pliku cart.html dodaj atrybut option, jak pokazano poniżej:

1
<div class="panel panel-primary" ng-repeat="q in shopData">
2
    <div class="panel-heading">
3
        <h3 class="panel-title">{{q.item}}</h3>
4
    </div>
5
    <check-list option="q.prices"></check-list>
6
</div>

W celu uzyskania w dyrektywie dostępu do przekazanego atrybutu option konieczne jest zdefiniowanie zakresu. W następujący sposób zdefiniuj zakres scope w dyrektywie checkList:

1
.directive('checkList', function() {
2
    return {
3
        restrict: 'E',
4
        scope: {
5
            option: '='
6
        },
7
        template: function(elem, attrs) {
8
            return '<div class="panel-body">\

9
                    <div class="radio" ng-repeat="i in option">\

10
                        <label><input type="radio">{{i.size}} Rs.{{i.price}}</label>\

11
                    </div>\

12
                </div>'
13
        }
14
    };
15
})

Tym samym dyrektywie zostanie przekazana lista cen różnych produktów, których dane znajdują się w $scope.shopData. Zapisz wprowadzone zmiany i ponownie uruchom serwer. Po odświeżeniu strony zobaczysz, że poszczególne produkty na stronie mają przypisane odpowiednie opcje.

Cart Page With Dyancamilly Populated OptionsCart Page With Dyancamilly Populated OptionsCart Page With Dyancamilly Populated Options

Jeżeli teraz spróbujesz kliknąć przycisk opcji wybranego produktu to przekonasz się, że zostają zaznaczone obie opcje. Aby mieć gwarancję wyboru tylko jednej opcji, musimy odpowiednio pogrupować przyciski. To wymaga przekazania z widoku HTML do dyrektywy kolejnego atrybutu o nazwie name. W dyrektywie check-list umieść nowy atrybut o nazwie name. Wartością atrybutu name będzie ID, co zapewni unikatowe rozróżnianie poszczególnych produktów.

1
<check-list name="q.id" option="q.prices"></check-list>

Do zakresu dyrektywy dodajemy kolejną zmienną, która będzie dostępna w szablonie dyrektywy.

1
scope: {
2
    option: '=',
3
    name: '='
4
}

W kodzie HTML parametru template przekazana wartość będzie użyta jako nazwa przycisku opcji, który grupuje opcje poszczególnych produktów.

1
<input type="radio" name="{{name}}"

Zapisz wprowadzone zmiany i odśwież stronę. Spróbuj zaznaczyć opcje dla wybranego produktu, a przekonasz się, że możesz wybrać tylko jedną z nich.

Obliczenie kwoty całkowitej na podstawie wybranych opcji

Na podstawie produktów wybranych przez użytkownika można wyświetlić kwotę całkowitą wszystkich produktów znajdujących się w koszyku. Utworzymy teraz funkcję $scope.total() odpowiedzialną za obliczenie wspomnianej sumy. Gdy użytkownik wybierze produkt, wtedy nastąpi uaktualnienie zmiennej w danych JSON $scope.shopData. Następnie jest przeprowadzana iteracja przez wspomniane dane JSON, aby obliczyć wartość całkowitą wybranych produktów. Poniżej przedstawiłem kod źródłowy funkcji $scope.total():

1
$scope.total = function() {
2
    var t = 0;
3
4
    for (var k in $scope.shopData) {
5
        t += parseInt($scope.shopData[k].selected);
6
    }
7
8
    return t;
9
10
}

Obecnie element <div> wyświetla na stałe zdefiniowaną wartość Rs 100. Wprowadzamy modyfikację, aby zamiast na stałe zdefiniowanej wartości następowało wywołanie przygotowanej wcześniej funkcji.

1
<h2>Rs. {{total()}}</h2>

Zapisz wprowadzone zmiany i odśwież stronę. Zauważ, że mimo wyboru różnych opcji, wartość całkowita nie ulega zmianie. Takie zachowanie wynika z faktu przypisania wartości początkowej 0 zmiennej selected w danych JSON. Wymieniona wartość nie jest uaktualniana po wybraniu opcji. Musimy więc nie tylko przekazać dyrektywie wybraną wartość, ale jeszcze uaktualnić ją po zaznaczeniu przycisku opcji. Zmodyfikuj kod widoku HTML w taki sposób, aby zawierał dodatkowy atrybut selected w elemencie dyrektywy checkList.

1
<check-list name="q.id" selected="q.selected" option="q.prices"></check-list>

Do zakresu dyrektywy dodaj zmienną selected. Tym samym zyskasz możliwość dostępu do wymienionego atrybutu z poziomu dyrektywy.

1
scope: {
2
    option: '=',
3
    name: '=',
4
    selected: '=selected'
5
}

Wprowadzamy kolejne modyfikacje w kodzie. W dyrektywie ngModel przycisku opcji ustawiamy selected, natomiast w dyrektywie ngValue ustawiamy i.price. Teraz po zaznaczeniu przycisku opcji nastąpi uaktualnienie wartości atrybutu selected w danych JSON $scope.shopData.

1
<input type="radio" ng-model="$parent.selected" ng-value="{{i.price}}"  name="{{name}}">

Zapisz wprowadzone zmiany i odśwież stronę. Spróbuj zaznaczać różne przyciski opcji. Zwróć uwagę, że w wyświetlana w ramce Total suma jest obliczana na podstawie wybranych opcji.

Cart Page With TotalCart Page With TotalCart Page With Total

Podsumowanie

W tej części serii opracowaliśmy własną dyrektywę i wykorzystaliśmy ją w naszej prostej aplikacji koszyka na zakupy. W kolejnej części zobaczysz, jak zachować stałe położenie elementu Total na górze ekranu podczas przewijania strony. Zaimplementujemy także stronę finalizacji zamówienia wyświetlającej wybrane produkty i ceny. Wspomniana strona będzie zawierać przycisk pozwalający klientowi na powrót do koszyka i zmianę wybranych produktów.

Kod źródłowy przedstawiony w artykule znajdziesz w serwisie GitHub. Jeżeli masz jakiekolwiek uwagi, sugestie lub znalazłeś błąd w tekście, koniecznie napisz o tym w komentarzu.

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.