In this chapter we will add Twitter's bootstrap to our project, and add some style and layout to the project.
First, we need to add Bootstrap 4 to our project - so let's do that.
Run the following command in your Terminal:
$ meteor npm install --save [email protected]
Import Bootstrap's styles into your project:
1
2
3
4
5
@import "{}/node_modules/bootstrap/scss/bootstrap.scss";
.sebm-google-map-container {
width: 400px;
height: 400px;
Now let's add some style! we will add navigation bar in the top of the page.
We will also add a container with the router-outlet
to keep that content of the page:
1
2
3
4
5
6
<nav class="navbar navbar-light bg-faded">
<a class="navbar-brand" href="#">Socially</a>
</nav>
<div class="container-fluid">
<router-outlet></router-outlet>
</div>
So first thing we want to do now, is to move the login buttons to another place - let's say that we want it as a part of the navigation bar.
So first let's remove it from it's current place (parties list), first the view:
2
3
4
5
6
7
<parties-form [hidden]="!user" style="float: left"></parties-form>
<input type="text" #searchtext placeholder="Search by Location">
<button type="button" (click)="search(searchtext.value)">Search</button>
<h1>Parties:</h1>
And add it to the main component, which is the component that responsible to the navigation bar, so the view first:
1
2
3
4
5
6
<nav class="navbar navbar-light bg-faded">
<a class="navbar-brand" href="#">Socially</a>
<login-buttons class="pull-right"></login-buttons>
</nav>
<div class="container-fluid">
<router-outlet></router-outlet>
Meteor gives you the control of your head
tag, so you can import fonts and add your meta
tags.
We will add a cool font and add FontAwesome style file, which also contains it's font:
1
2
3
4
5
6
7
8
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<base href="/">
<link href='http://fonts.googleapis.com/css?family=Muli:400,300' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
</head>
<body>
<app>Loading...</app>
So now we will take advantage of all Bootstrap's features - first let's update the layout of the form:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<form [formGroup]="addForm" (ngSubmit)="addParty()" class="form-inline">
<fieldset class="form-group">
<label for="partyName">Party name</label>
<input id="partyName" class="form-control" type="text" formControlName="name" placeholder="Party name" />
<label for="description">Description</label>
<input id="description" class="form-control" type="text" formControlName="description" placeholder="Description">
<label for="location_name">Location</label>
<input id="location_name" class="form-control" type="text" formControlName="location" placeholder="Location name">
<div class="checkbox">
<label>
<input type="checkbox" formControlName="public">
Public
</label>
</div>
<button type="submit" class="btn btn-primary">Add</button>
</fieldset>
</form>
And now the parties list:
1
2
3
4
5
6
7
8
9
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
<div class="row">
<div class="col-md-12">
<div class="jumbotron">
<h3>Create a new party!</h3>
<parties-form [hidden]="!user"></parties-form>
<div [hidden]="user">You need to login to create new parties!</div>
</div>
</div>
</div>
<div class="row ma-filters">
<div class="col-md-6">
<h3>All Parties:</h3>
<form class="form-inline">
<input type="text" class="form-control" #searchtext placeholder="Search by Location">
<button type="button" class="btn btn-primary" (click)="search(searchtext.value)">Search</button>
Sort by name: <select class="form-control" #sort (change)="changeSortOrder(sort.value)">
<option value="1" selected>Ascending</option>
<option value="-1">Descending</option>
</select>
</form>
</div>
</div>
<div class="row">
<div class="col-md-6">
<ul class="list-group">
<li class="list-group-item">
<pagination-controls (pageChange)="onPageChanged($event)"></pagination-controls>
</li>
<li *ngFor="let party of parties | async"
class="list-group-item ma-party-item">
<div class="row">
<div class="col-sm-8">
<h2 class="ma-party-name">
<a [routerLink]="['/party', party._id]">{{party.name}}</a>
</h2>
@ {{party.location.name}}
<p class="ma-party-description">
{{party.description}}
</p>
</div>
<div class="col-sm-4">
<button class="btn btn-danger pull-right" [hidden]="!isOwner(party)" (click)="removeParty(party)"><i
class="fa fa-times"></i></button>
</div>
</div>
<div class="row ma-party-item-bottom">
<div class="col-sm-4">
<div class="ma-rsvp-sum">
<div class="ma-rsvp-amount">
<div class="ma-amount">
{{party | rsvp:'yes'}}
</div>
<div class="ma-rsvp-title">
YES
</div>
</div>
<div class="ma-rsvp-amount">
<div class="ma-amount">
{{party | rsvp:'maybe'}}
</div>
<div class="ma-rsvp-title">
MAYBE
</div>
</div>
<div class="ma-rsvp-amount">
<div class="ma-amount">
{{party | rsvp:'no'}}
</div>
<div class="ma-rsvp-title">
NO
</div>
</div>
</div>
</div>
</div>
</li>
<li class="list-group-item">
<pagination-controls (pageChange)="onPageChanged($event)"></pagination-controls>
</li>
</ul>
</div>
<div class="col-md-6">
<ul class="list-group">
<li class="list-group-item">
<sebm-google-map
[latitude]="0"
[longitude]="0"
[zoom]="1">
<div *ngFor="let party of parties | async">
<sebm-google-map-marker
*ngIf="party.location.lat"
[latitude]="party.location.lat"
[longitude]="party.location.lng">
</sebm-google-map-marker>
</div>
</sebm-google-map>
</li>
</ul>
</div>
</div>
We will create style file for each component.
So let's start with the parties list, and add some style (it's not that critical at the moment what is the effect of those CSS rules)
1
2
3
4
5
6
7
8
9
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
@import "../colors";
.ma-add-button-container {
button.btn {
background: $color3;
float: right;
margin-right: 5px;
outline: none;
i {
color: $color5;
}
}
}
.ma-parties-col {
padding-top: 20px;
}
.ma-filters {
margin-bottom: 10px;
}
.ma-party-item {
.ma-party-name {
margin-bottom: 20px;
a {
color: $color6;
text-decoration: none !important;
font-weight: 400;
}
}
.ma-party-description {
color: $color6;
font-weight: 300;
padding-left: 18px;
font-size: 14px;
}
.ma-remove {
color: lighten($color7, 20%);
position: absolute;
right: 20px;
top: 20px;
&:hover {
color: $color7;
}
}
.ma-party-item-bottom {
padding-top: 10px;
.ma-posted-by-col {
.ma-posted-by {
color: darken($color4, 30%);
font-size: 12px;
}
.ma-everyone-invited {
@media (max-width: 400px) {
display: block;
i {
margin-left: 0px !important;
}
}
font-size: 12px;
color: darken($color4, 10%);
i {
color: darken($color4, 10%);
margin-left: 5px;
}
}
}
.ma-rsvp-buttons {
input.btn {
color: darken($color3, 20%);
background: transparent !important;
outline: none;
padding-left: 0;
&:active {
box-shadow: none;
}
&:hover {
color: darken($color3, 30%);
}
&.btn-primary {
color: lighten($color3, 10%);
border: 0;
background: transparent !important;
}
}
}
.ma-rsvp-sum {
width: 160px;
@media (min-width: 400px) {
float: right;
}
@media (max-width: 400px) {
margin: 0 auto;
}
}
.ma-rsvp-amount {
display: inline-block;
text-align: center;
width: 50px;
.ma-amount {
font-weight: bold;
font-size: 20px;
}
.ma-rsvp-title {
font-size: 11px;
color: #aaa;
text-transform: uppercase;
}
}
}
}
.ma-angular-map-col {
.angular-google-map-container, .angular-google-map {
height: 100%;
width: 100%;
}
}
.search-form {
margin-bottom: 1em;
}
Note that we used the "colors.scss" import - don't worry - we will add it soon!
And now let's add SASS file for the party details:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
.ma-party-details-container {
padding: 20px;
.angular-google-map-container {
width: 100%;
height: 100%;
}
.angular-google-map {
width: 100%;
height: 400px;
}
.ma-map-title {
font-size: 16px;
font-weight: bolder;
}
.ma-invite-list {
margin-top: 20px;
margin-bottom: 20px;
h3 {
font-size: 16px;
font-weight: bolder;
}
ul {
padding: 0;
}
}
}
Now let's add some styles and colors using SASS to the main file and create the colors definitions file we mentioned earlier:
1
2
3
4
5
6
7
$color1 : #2F2933;
$color2 : #01A2A6;
$color3 : #29D9C2;
$color4 : #BDF271;
$color5 : #FFFFA6;
$color6 : #2F2933;
$color7 : #FF6F69;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@import "../node_modules/bootstrap/scss/bootstrap.scss";
@import "./imports/app/colors.scss";
html, body {
height: 100%;
}
body {
background-color: #f8f8f8;
font-family: 'Muli', sans-serif;
}
.sebm-google-map-container {
width: 450px;
height: 450px;
}
.navbar {
background-color: #ffffff;
border-bottom: #eee 1px solid;
color: $color3;
font-family: 'Muli', sans-serif;
a {
color: $color3;
text-decoration: none !important;
}
.navbar-right-container {
position: absolute;
top: 17px;
right: 17px;
}
}
We defined our colors in this file, and we used it all across the our application - so it's easy to change and modify the whole theme!
Now let's use Angular 2 Component styles, which bundles the styles into the Component, without effecting other Component's styles (you can red more about it here)
So let's add it to the parties list:
13
14
15
16
17
18
19
26
27
28
29
30
31
32
33
import { Party } from '../../../../both/models/party.model';
import template from './parties-list.component.html';
import style from './parties-list.component.scss';
interface Pagination {
limit: number;
...some lines skipped...
@Component({
selector: 'parties-list',
template,
styles: [ style ]
})
@InjectUser('user')
export class PartiesListComponent implements OnInit, OnDestroy {
And to the party details:
15
16
17
18
19
20
21
22
23
24
25
26
import { User } from '../../../../both/models/user.model';
import template from './party-details.component.html';
import style from './party-details.component.scss';
@Component({
selector: 'party-details',
template,
styles: [ style ]
})
@InjectUser('user')
export class PartyDetailsComponent implements OnInit, OnDestroy {
And use those new cool styles in the view of the party details:
1
2
3
4
5
6
7
8
9
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
54
55
56
57
58
59
60
61
<div class="row ma-party-details-container">
<div class="col-sm-6 col-sm-offset-3">
<legend>View and Edit Your Party Details:</legend>
<form class="form-horizontal" *ngIf="party" (submit)="saveParty()">
<div class="form-group">
<label>Party Name</label>
<input [disabled]="!isOwner" type="text" [(ngModel)]="party.name" name="name" class="form-control">
</div>
<div class="form-group">
<label>Description</label>
<input [disabled]="!isOwner" type="text" [(ngModel)]="party.description" name="description" class="form-control">
</div>
<div class="form-group">
<label>Location name</label>
<input [disabled]="!isOwner" type="text" [(ngModel)]="party.location.name" name="location" class="form-control">
</div>
<div class="form-group">
<button [disabled]="!isOwner" type="submit" class="btn btn-primary">Save</button>
<a [routerLink]="['/']" class="btn">Back</a>
</div>
</form>
<ul class="ma-invite-list" *ngIf="isOwner || isPublic">
<h3>
Users to invite:
</h3>
<li *ngFor="let user of users | async">
<div>{{ user | displayName }}</div>
<button (click)="invite(user)" class="btn btn-primary btn-sm">Invite</button>
</li>
</ul>
<div *ngIf="isInvited">
<h2>Reply to the invitation</h2>
<input type="button" class="btn btn-primary" value="I'm going!" (click)="reply('yes')">
<input type="button" class="btn btn-warning" value="Maybe" (click)="reply('maybe')">
<input type="button" class="btn btn-danger" value="No" (click)="reply('no')">
</div>
<h3 class="ma-map-title">
Click the map to set the party location
</h3>
<div class="angular-google-map-container">
<sebm-google-map
[latitude]="lat || centerLat"
[longitude]="lng || centerLng"
[zoom]="8"
(mapClick)="mapClicked($event)">
<sebm-google-map-marker
*ngIf="lat && lng"
[latitude]="lat"
[longitude]="lng">
</sebm-google-map-marker>
</sebm-google-map>
</div>
</div>
</div>
So in this chapter of the tutorial we added the Bootstrap library and used it's layout and CSS styles.
We also learned how to integrate SASS compiler with Meteor and how to create isolated SASS styles for each component.