In this tutorial I’m going to show you how to create an “Events” app using a little bit of Node, Angular JS and Cosmic JS. For the sake of understanding how to consume Restful APIs, this tutorial will show how to make simple AJAX requests to the Cosmic JS API in order to retrieve, update, and delete data in our Cosmic JS Bucket. Let’s get started.
Download the GitHub repo.Check out the demo.
First, let’s make a new directory to build our project in and lets also make a package.json file.
mkdir events-app
events-app$ touch package.json
Now, in your package.json, copy and paste the code below:
//events-app/package.json{ "name": "events-app", "version": "1.0.0", "main": "app-server.js", "engines": { "node": "4.1.2", "npm": "3.5.2" }, "description": "", "dependencies": { "bower": "^1.7.7", "http-server": "^0.9.0", "gulp": "^3.9.1", "gulp-autoprefixer": "^3.1.0", "gulp-concat": "^2.6.0", "gulp-concat-css": "^2.2.0", "gulp-minify-css": "^1.2.4", "gulp-webserver": "^0.9.1", "wiredep": "^3.0.0", "express": "^4.13.3" }, "scripts": { "postinstall": "bower install", "start": "npm run production", "production": "node app-server.js" }, "author": "", "license": "ISC"}
Second, let’s make a bower.json file.
events-app$ touch bower.json
Now, in your bower.json, copy and paste the code below:
//events-app/bower.json{ "name": "events-app", "description": "Events App", "version": "0.0.0", "homepage": "https://github.com/cosmicjs/events-app", "license": "MIT", "private": true, "dependencies": { "angular": "~1.4.x", "angular-mocks": "~1.4.x", "angular-bootstrap": "~1.1.x", "angular-cookies": "~1.4.x", "angular-route": "~1.4.x", "angular-ui-router": "0.2.x", "angular-resource": "1.4.x", "angular-animate": "~1.4.x", "ng-dialog": "0.6.1", "bootstrap": "3.3.x", "cr-acl": "", "angular-chosen-localytics": "*", "bootstrap-chosen": "*", "ng-flow": "^2.7.4", "angular-mask": "*", "checklist-model": "0.9.0", "angular-ui-notification": "^0.2.0", "angular-ui-calendar": "^1.0.2", "angular-ui-switch": "^0.1.1", "ng-scrollbars": "^0.0.11", "jquery.scrollbar": "*", "angular-nvd3": "*", "infinity-angular-chosen": "^0.2.0", "angular-flash-alert": "^2.4.0", "components-font-awesome": "^4.7.0", "textAngular": "^1.5.16", "angular-loading-bar": "^0.9.0" }, "resolutions": { "angular": "~1.4.x" }, "devDependencies": { "cr-acl": "^0.5.0" }}
Config app server:
events-app$ touch app-server.js
//events-app/app-server.jsvar express = require('express')var app = express()app.set('port', process.env.PORT || 3000)app.use(express.static(__dirname))var http = require('http').Server(app)// Routeapp.get('/', (req, res) => { res.sendFile(__dirname + '/index.html')})http.listen(app.get('port'), () => { console.log('Wedding Site listening on ' + app.get('port'))})
Now we’re going to build out our file structure a bit more so that we can organize our angular modules and js files. This is what our events-app directory should look like:
events-app|----app| |----auth| |----auth.ctrl.js| |----auth.service.js| |----config| |----config.js| |----event| |----add| |----event.add.ctrl.js| |----event.add.mdl.js| |----feed| |----event.feed.ctrl.js| |----event.feed.mdl.js| |----profile| |----event.profile.ctrl.js| |----event.profile.mdl.js| |----event.ctrl.js| |----event.mdl.js| |----event.service.js| |----user| |----profile| |----user.profile.ctrl.js| |----user.profile.mdl.js| |----settings| |----user.settings.ctrl.js| |----user.settings.mdl.js| |----user.ctrl.js| |----user.mdl.js| |----user.service.js| |----main.mdl.js|----dist| |----css| |----img| |----js|----css|----views|----gulpfile.js|----app-server.js|----bower.json|----package.json
Now we we will set up our index.html. Copy and paste the following code into your index.html file:
<!DOCTYPE html><html lang="en" ng-app="main"><head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="description" content=""> <meta name="author" content="">
<title>Events App</title>
<!-- bower:css --> <!-- endbower -->
<!-- Bootstrap Core CSS --> <link href="bower_components/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom CSS -->
<link href="dist/css/main.min.css" rel="stylesheet">
<!--[if lt IE 9]> <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script> <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script> <![endif]-->
</head><body>
<div ui-view></div>
<!-- bower:js --><!-- endbower -->
<script src="dist/js/main.js"></script></body></html>
Here, we are going to target our “root” view to place our angular modules in later. The main.js file located in our dist directory is what our gulpfile.js file will spit out after bundling all of our angular modules. Now, set up our gulpfile.js file to bundle all of our js files and export that bundle file to our dist directory. Copy the following code into your gulpfile.js file:
//events-app/gulpfile.js'use strict';
var gulp = require('gulp'), webserver = require('gulp-webserver'), minifyCSS = require('gulp-minify-css'), concatCss = require('gulp-concat-css'), concat = require('gulp-concat'), wiredep = require('wiredep').stream, autoprefixer = require('gulp-autoprefixer');
gulp.task('css', function () { return gulp.src('css/**/*.css') .pipe(minifyCSS()) .pipe(concat('main.min.css')) .pipe(autoprefixer()) .pipe(gulp.dest('dist/css'));});
gulp.task('js', function() { return gulp.src('app/**/**/*.js') .pipe(concat('main.js')) .pipe(gulp.dest('dist/js/'));});
gulp.task('default', function () { gulp.watch('css/**/*.css', ['css']); gulp.watch('app/**/**/*.js', ['js']); gulp.watch('bower.json', ['bower']);});
gulp.task('bower', function () { gulp.src('index.html') .pipe(wiredep({ directory: 'bower_components' })) .pipe(gulp.dest(''));});
After that we can create config.js. Copy and paste the following code into your app/config/config.js file:
(function () { 'use strict';
var app = angular .module('main');
app.constant('BUCKET_SLUG', 'your-bucket-slug'); app.constant('URL', 'https://api.cosmicjs.com/v1/'); app.constant('MEDIA_URL', 'https://api.cosmicjs.com/v1/your-bucket-slug/media'); app.constant('READ_KEY', 'your-read-key'); app.constant('WRITE_KEY', 'your-write-key'); app.constant('DEFAULT_EVENT_IMAGE', 'url-image');
})();
After that we can create our main module. Copy and paste the following code into the app/main.mdl.js file:
(function () { 'use strict';
angular .module('main', [ 'ui.router', 'ui.bootstrap', 'ngMask', 'ngCookies', 'ngRoute', 'ngDialog', 'cr.acl', 'ui-notification', 'ngFlash', 'textAngular', 'flow', 'angular-loading-bar',
'event', 'user' ]) .config(config) .run(run);
config.$inject = ['$stateProvider', '$urlRouterProvider', 'cfpLoadingBarProvider', 'WRITE_KEY']; function config($stateProvider, $urlRouterProvider, cfpLoadingBarProvider, WRITE_KEY) { cfpLoadingBarProvider.includeSpinner = false;
$urlRouterProvider.otherwise(function ($injector) { var $state = $injector.get("$state"); var $location = $injector.get("$location"); var crAcl = $injector.get("crAcl");
var state = "";
switch (crAcl.getRole()) { case 'ROLE_USER': state = 'main.event.feed'; break; }
if (state) $state.go(state); else $location.path('/login'); });
$stateProvider .state('main', { url: '/', abstract: true, templateUrl: '../views/main.html', controller: 'UserCtrl as global', data: { is_granted: ['ROLE_USER'] } }) .state('auth', { url: '/login', templateUrl: '../views/auth/login.html', controller: 'AuthCtrl as auth', onEnter: ['AuthService', function(AuthService) { AuthService.clearCredentials(); }], data: { is_granted: ['ROLE_GUEST'] } }); }
run.$inject = ['$rootScope', '$cookieStore', '$http', 'crAcl', 'AuthService']; function run($rootScope, $cookieStore, $http, crAcl, AuthService) {
$rootScope.globals = $cookieStore.get('globals') || {}; $http.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
crAcl .setInheritanceRoles({ 'ROLE_SUPER_ADMIN': ['ROLE_SUPER_ADMIN', 'ROLE_GUEST'], 'ROLE_USER': ['ROLE_USER', 'ROLE_GUEST'], 'ROLE_GUEST': ['ROLE_GUEST'] });
crAcl .setRedirect('auth');
if ($rootScope.globals.currentUser) {
crAcl.setRole($rootScope.globals.currentUser.metadata.role); } else { crAcl.setRole(); }
}
})();
Now we we will set up our Auth Controller. Copy and paste the following code into your auth.ctrl.js file:
(function () { 'use strict';
angular .module('main') .controller('AuthCtrl', AuthCtrl);
function AuthCtrl(crAcl, $state, AuthService, Flash, $log) { var vm = this;
vm.login = login; vm.register = register;
vm.showRegisterForm = false;
vm.loginForm = null; vm.registerForm = null;
vm.credentials = {}; vm.user = {};
function login(credentials) { function success(response) { function success(response) { if (response.data.status !== 'empty') { var currentUser = response.data.objects[0];
crAcl.setRole(currentUser.metadata.role); AuthService.setCredentials(currentUser); $state.go('main.event.feed'); } else Flash.create('danger', 'Incorrect username or password'); }
function failed(response) { $log.error(response); }
if (response.data.status !== 'empty') AuthService .checkPassword(credentials) .then(success, failed); else Flash.create('danger', 'Incorrect username or password');
$log.info(response); }
function failed(response) { $log.error(response); }
if (vm.loginForm.$valid) AuthService .checkUsername(credentials) .then(success, failed); }
function register(credentials) { function success(response) { $log.info(response);
var currentUser = response.data.object.metafields;
Flash.create('success', 'You have successfully signed up!'); vm.credentials = { username: currentUser[0].value, password: currentUser[3].value }; vm.showRegisterForm = false; }
function failed(response) { $log.error(response); }
if (vm.registerForm.$valid) AuthService .register(credentials) .then(success, failed); }
}})();
Now we will create our Auth Service in app/auth/auth.service.js:
(function () { 'use strict';
angular .module('main') .service('AuthService', function ($http, $cookieStore, $q, $rootScope, URL, BUCKET_SLUG, READ_KEY, WRITE_KEY) { var authService = this; $http.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
authService.checkUsername = function (credentials) { return $http.get(URL + BUCKET_SLUG + '/object-type/users/search', { params: { metafield_key: 'username', metafield_value_has: credentials.username, limit: 1, read_key: READ_KEY } }); }; authService.checkPassword = function (credentials) { return $http.get(URL + BUCKET_SLUG + '/object-type/users/search', { ignoreLoadingBar: true, params: { metafield_key: 'password', metafield_value: credentials.password, limit: 1, read_key: READ_KEY } }); }; authService.register = function (user) {
return $http.post(URL + BUCKET_SLUG + '/add-object', { title: user.full_name, type_slug: 'users', slug: user.username, metafields: [ { key: "username", type: "text", value: user.username }, { key: "email", type: "text", value: user.email }, { key: "full_name", type: "text", value: user.full_name }, { key: "password", type: "text", value: user.password }, { key: "image", type: "file", value: "3b2180f0-2c40-11e7-85ac-e98751218524-1493421969_male.png" }, { key: "role", type: "radio-buttons", options: [ { value: "ROLE_USER" }, { value: "ROLE_SUPER_ADMIN" } ], value: "ROLE_USER" } ],
write_key: WRITE_KEY }); }; authService.setCredentials = function (user) { $rootScope.globals = { currentUser: user };
$cookieStore.put('globals', $rootScope.globals); }; authService.clearCredentials = function () { var deferred = $q.defer(); $cookieStore.remove('globals');
if (!$cookieStore.get('globals')) { $rootScope.globals = {}; deferred.resolve('Credentials clear success'); } else { deferred.reject('Can\'t clear credentials'); }
return deferred.promise; }; });})();
Create User Service for get and update User, copy and paste the code below:
(function () { 'use strict';
angular .module('main') .service('UserService', function ($http, $cookieStore, $q, $rootScope, URL, BUCKET_SLUG, READ_KEY, WRITE_KEY) { $http.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
this.getCurrentUser = function (ignoreLoadingBar) { return $http.get(URL + BUCKET_SLUG + '/object/' + $rootScope.globals.currentUser.slug, { ignoreLoadingBar: ignoreLoadingBar, params: { read_key: READ_KEY } }); }; this.getUser = function (slug, ignoreLoadingBar) { return $http.get(URL + BUCKET_SLUG + '/object/' + slug, { ignoreLoadingBar: ignoreLoadingBar, params: { read_key: READ_KEY } }); }; this.updateUser = function (user) { user.write_key = WRITE_KEY;
return $http.put(URL + BUCKET_SLUG + '/edit-object', user, { ignoreLoadingBar: false }); };
});})();
Create User Controller for get current user and log out, copy and paste the code below:
(function () { 'use strict';
angular .module('main') .controller('UserCtrl', UserCtrl);
function UserCtrl($rootScope, $scope, $state, AuthService, Flash, $log) { var vm = this;
vm.currentUser = $rootScope.globals.currentUser.metadata;
vm.logout = logout;
function logout() { function success(response) { $state.go('auth');
$log.info(response); }
function failed(response) { $log.error(response); }
AuthService .clearCredentials() .then(success, failed); }
$scope.state = $state;
}})();
Next we will create our Create User Module:
(function () { 'use strict';
angular .module('user', [ 'user.profile', 'user.settings' ]) .config(config);
config.$inject = ['$stateProvider', '$urlRouterProvider']; function config($stateProvider, $urlRouterProvider) {
$stateProvider .state('main.user', { url: 'user', abstract: true, data: { is_granted: ['ROLE_USER'] } }); }})();
Next we will create our Event Service to get, update, add delete Events from the Cosmic JS API:
(function () { 'use strict';
angular .module('main') .service('EventService', function ($http, $cookieStore, $q, $rootScope, URL, BUCKET_SLUG, READ_KEY, WRITE_KEY, MEDIA_URL) {
$http.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
this.getEvents = function () { return $http.get(URL + BUCKET_SLUG + '/object-type/events', { params: { limit: 100, read_key: READ_KEY } }); }; this.getEventsByUsername = function (username, ignoreLoadingBar) { return $http.get(URL + BUCKET_SLUG + '/object-type/events/search', { ignoreLoadingBar: ignoreLoadingBar, params: { metafield_key: 'user', metafield_object_slug: username, limit: 10, read_key: READ_KEY } } ); }; this.getEventById = function (slug) { return $http.get(URL + BUCKET_SLUG + '/object/' + slug, { params: { read_key: READ_KEY } }); }; this.updateEvent = function (event) { event.write_key = WRITE_KEY;
return $http.put(URL + BUCKET_SLUG + '/edit-object', event); }; this.removeEvent = function (slug) { return $http.delete(URL + BUCKET_SLUG + '/' + slug, { ignoreLoadingBar: true, headers:{ 'Content-Type': 'application/json' }, data: { write_key: WRITE_KEY } }); }; this.createEvent = function (event) { event.write_key = WRITE_KEY;
var beginDate = new Date(event.metafields[1].value); var endDate = new Date(event.metafields[2].value);
event.metafields[1].value = beginDate.getFullYear() + '-' + (beginDate.getMonth() + 1) + '-' + beginDate.getDate(); event.metafields[2].value = endDate.getFullYear() + '-' + (beginDate.getMonth() + 1) + '-' + endDate.getDate();
event.slug = event.title; event.type_slug = 'events';
event.metafields[4] = { key: "user", type: "object", object_type: "users", value: $rootScope.globals.currentUser._id }; return $http.post(URL + BUCKET_SLUG + '/add-object', event); }; this.upload = function (file) { var fd = new FormData(); fd.append('media', file); fd.append('write_key', WRITE_KEY);
var defer = $q.defer();
var xhttp = new XMLHttpRequest();
xhttp.upload.addEventListener("progress",function (e) { defer.notify(parseInt(e.loaded * 100 / e.total)); }); xhttp.upload.addEventListener("error",function (e) { defer.reject(e); });
xhttp.onreadystatechange = function() { if (xhttp.readyState === 4) { defer.resolve(JSON.parse(xhttp.response)); //Outputs a DOMString by default } };
xhttp.open("post", MEDIA_URL, true);
xhttp.send(fd);
return defer.promise; } });})();
Our Event Controller will get all events and remove events:
(function () { 'use strict';
angular .module('main') .controller('EventCtrl', EventCtrl);
function EventCtrl(EventService, Notification, $log, $rootScope, DEFAULT_EVENT_IMAGE) { var vm = this;
vm.getEvents = getEvents; vm.removeEvent = removeEvent; vm.DEFAULT_EVENT_IMAGE = DEFAULT_EVENT_IMAGE;
function getEvents(username) { function success(response) { $log.info(response);
vm.events = response.data.objects; }
function failed(response) { $log.error(response); } console.log(username);
EventService .getEventsByUsername(username) .then(success, failed); }
function removeEvent(slug) { function success(response) { $log.info(response);
getEvents($rootScope.globals.currentUser.metadata.username);
Notification.success('Deleted'); }
function failed(response) { Notification.error(response.data.message);
$log.error(response); }
EventService .removeEvent(slug) .then(success, failed); } }})();
Our Event Module will render our events view:
(function () { 'use strict';
angular .module('event', [ 'event.profile', 'event.feed', 'event.add' ]) .config(config);
config.$inject = ['$stateProvider', '$urlRouterProvider']; function config($stateProvider, $urlRouterProvider) {
$stateProvider .state('main.event', { url: 'events', views: { '': { templateUrl: '../views/event/events.html', controller: 'EventCtrl as vm' } }, data: { is_granted: ['ROLE_USER'] } }); }
})();
Now let’s create our Event Add Controller for adding events:
(function () { 'use strict';
angular .module('main') .controller('EventAddCtrl', EventAddCtrl);
function EventAddCtrl(EventService, Notification, $state, $log, $scope, MEDIA_URL, DEFAULT_EVENT_IMAGE, $timeout) { var vm = this;
vm.createEvent = createEvent; vm.cancelUpload = cancelUpload; vm.upload = upload;
vm.dateBeginPicker = false; vm.dateEndPicker = false; vm.contentEditor = true; vm.uploadProgress = 0;
vm.event = { title: null, slug: null, content: null, metafields: [ { key: "image", type: "file", value: null }, { key: "date_begin", type: "date", value: null }, { key: "date_end", type: "date", value: null }, { key: "type", type: "select-dropdown", options: [ { key: "social", value: "Social" }, { key: "fun", value: "Fun" } ], value: "Social" } ] };
$timeout(function() { vm.event.metafields[1].value = new Date(); vm.event.metafields[2].value = new Date(); }, 100);
vm.flow = {}; vm.background = { 'background-image': 'url(' + DEFAULT_EVENT_IMAGE + ')' };
vm.flowConfig = { target: MEDIA_URL, singleFile: true };
function createEvent() { if (vm.flow.files[0]) upload(); else _createEvent(vm.event); }
function _createEvent(event) { function success(response) { $log.info(response);
Notification.success( { message: 'Created', delay: 800, replaceMessage: true } );
$state.go('main.event'); }
function failed(response) { Notification.error( { message: response.data.error, delay: 4000, replaceMessage: true } );
$log.error(response); }
EventService .createEvent(event) .then(success, failed); }
function cancelUpload() { vm.flow.cancel(); vm.background = { 'background-image': 'url(' + DEFAULT_EVENT_IMAGE.url + ')' }; }
$scope.$watch('vm.flow.files[0].file.name', function () { if (!vm.flow.files[0]) { return ; } var fileReader = new FileReader(); fileReader.readAsDataURL(vm.flow.files[0].file); fileReader.onload = function (event) { $scope.$apply(function () { vm.background = { 'background-image': 'url(' + event.target.result + ')' }; }); }; });
function upload() { EventService .upload(vm.flow.files[0].file) .then(function(response){
vm.event.metafields[0].value = response.media.name;
createEvent(vm.event);
vm.flow.cancel(); vm.uploadProgress = 0;
}, function(){ console.log('failed :('); }, function(progress){ vm.uploadProgress = progress; });
}
}})();
Next we create the Event Module:
(function () { 'use strict';
angular .module('event.add', []) .config(config);
config.$inject = ['$stateProvider', '$urlRouterProvider']; function config($stateProvider, $urlRouterProvider) {
$stateProvider .state('main.event.add', { url: '/add', views: { '@main': { templateUrl: '../views/event/event.profile.html', controller: 'EventAddCtrl as vm' } }, data: { is_granted: ['ROLE_USER'] } }); }
})();
We were able to create a pretty complicated app to manage users, sessions, adding / editing events all through the Cosmic JS API. I hope you enjoyed this tutorial as much as I did, if you have any questions reach out to us on Twitter and join our community on Slack.
This article originally appeared on the Cosmic JS Blog.