How to Create a Simple Angular Contact Form at Keystone.js

2015/11/016 min read
bookmark this
Responsive image

Table of Contents

  1. Define API Routing
  2. API backend code
  3. Enquiry Model - MongoDB document
  4. Test the API
  5. Angular Form App Structure
  6. Create Angular Javascript Code
  7. Create an Angular Controller
  8. Factory for share scope globally at the app
  9. Create Html page
  10. Angular Validation class
  11. Angular Validation Style
  12. ConfirmController
  13. ConfirmController's HTML
  14. Load Script for Angular App

This is a tutorial on how to create a contact form with AngularJS at [keystone.js. It is for the keystone-classic, not the latest keystone. The keystone-classic is built on top of node.js, express.js on server-side, and database using MongoDB. Use Jade to translate the template and translate server-side data to the UI side. It has the contact form, but this blog shows how to build Angularjs form inside the keystone-classic. Don't ask why, it is just something comes into your mind, and want to give it a try, but hopefully this would help you if this is what you're looking for.

Define API Routing

We will create a contact form, so first we'll need to define the API route at keystone.js server side./p>

We gonna define an HTTP post /api/contact for our angular form to use later to post contact form data. The second and third parameter is to define API routing at keystone.js.routes.views.contact.create is a point to a file that contains the actual API code.

// index.js
exports = module.exports = function (app) {
app.post('/api/contact', keystone.initAPI, routes.views.contact.create);
}

API backend code

At the backend of the API code, we call our Enquiry model to create data then return either error or success message based on the logic.

exports.create = function (req, res) {
    var application = new Enquiry.model(),
        updater = application.getUpdateHandler(req);

    updater.process(req.body, {
        flashErrors: true,
        fields: 'name, email, phone, enquiryType, message',
        errorMessage: 'There was a problem submitting your enquiry:'
    }, function (err) {
        if (err) {
            res.apiResponse(err.errors);
        } else {
            res.apiResponse();
        }
    });
}

Enquiry Model - MongoDB document

Define MongoDB schema, so Keystone.js is using MongoDB for database storage. You can host your own MongoDB or use any MongoDB cloud service. Keystone.js is also using npm package Mongoose to access MongoDB. Following is the code from keystone.js, I only modify a couple of things.

var keystone = require('keystone'),
	Types = keystone.Field.Types;

var Enquiry = new keystone.List('Enquiry', {
	nocreate: true,
	noedit: true
});

Enquiry.add({
	name: { type: Types.Name },
	email: { type: Types.Email },
	phone: { type: String },
	enquiryType: { type: Types.Select, options: [
		{ value: 'message', label: "Just leaving a message" },
		{ value: 'question', label: "I've got a question" },
		{ value: 'other', label: "Something else..." }
	] },
	message: { type: Types.Markdown, required: true },
	createdAt: { type: Date, default: Date.now }
});

Enquiry.schema.pre('save', function(next) {
	this.wasNew = this.isNew;
	next();
})

Enquiry.schema.post('save', function() {
	if (this.wasNew) {
		this.sendNotificationEmail();
	}
});

Enquiry.schema.methods.sendNotificationEmail = function(callback) {

	var enqiury = this;

	keystone.list('User').model.find().where('isAdmin', true).exec(function(err, admins) {

		if (err) return callback(err);

		new keystone.Email('enquiry-notification').send({
			to: admins,
			from: {
				name: 'someone is sending message',
				email: 'contact@mytech.com'
			},
			subject: 'New Enquiry for me',
			enquiry: enqiury
		}, callback);

	});

}

Enquiry.defaultSort = '-createdAt';
Enquiry.defaultColumns = 'name, email, enquiryType, createdAt';
Enquiry.register();

Test the API

Before creating any client-side code, you need to make sure the server-side API is available. Following this, I'm using Postman, a chrome extension tool for testing API. After you verified the result, now I'm ready to create an angular form app since the server is ready.

Angular Form App Structure

First, let's define the app structure, I'll define my angular form app as following. It'll contain two pages, one is a page for users to submit contact information. Another is the confirmation page.

Create Angular Javascript Code

At the app.js, I define my pages, create and confirm the page's angular controller and Html. Also, define my module's name and this angular app's dependencies, which so far is the only route.

var contactUsApp = angular.module('dhe.contactUs', ['ngRoute']);
contactUsApp.config(["$routeProvider", function ($routeProvider) {
        'use strict';
        $routeProvider.
      when('/', {
            templateUrl: '/apps/contactUs/views/create.html',
            controller: 'createCtrl'
        }).
      when('/confirm', {
            templateUrl: '/apps/contactUs/views/confirm.html',
            controller: 'confirmCtrl'
        }).
      otherwise({
            redirectTo: '/'
        });
    }]);

Create an Angular Controller

Here we create an Angular controller, I only defined ui submit function, which is posting the UI form to the server-side API and redirect to the confirm page. To share scope information I create another service as scopeServcie.

/* globals contactUsApp */
contactUsApp.controller('createCtrl',
    function ($scope, $http, $window, scopeService) {
    'use strict';
    function formPost() {
        if ($scope.contactForm.$valid) {
            $http.post("/api/contact", $scope.cu).success(function () {
                scopeService.cu = $scope.cu;
                $window.location.href = "#confirm";
            });

        }
        else {
        }
    }

    $scope.ui = {
        submit: formPost
    };
});

Factory for share scope globally at the app

This is just a code snippet for creating a scopeService that shared objects between controllers.

contactUsApp.factory('scopeService',
    function () {
    'use strict';
    return {};
});

Create Html page

Compare to the controller part, HTML is a little more code. I need to add ng-model to the text box so angular validation will work. Also when the user submits the button add the ng-click to the createController's submit function.


    Contact
    Do you need to contact me? You can just go to the message and write to me, rest of fields are all optional.






                    Name



                    Email



                    Phone



                    What are you contacting about?

                        (select one)
                        Just leaving a message
                        I've got a question
                        Something else...



                    Message



                    Send](https://github.com/keystonejs/keystone-classic#getting-started)





Angular Validation class

Angular had added the following CSS class for style in a different case. Each following CSS class shows a different state of the ng-model.

Detailed description checkout this link.

ng-valid the model contains a valid value ng-invalid the model contains an invalid value ng-valid-[key]

the model contains a valid value, the key can be added as $setValidity

ng-invalid-[key]

the model contains an invalid value, the key can be added as $setValidity

ng-pristine user has not used the control yet ng-dirty user already used the control. ng-touched the control has been blurred ng-untouched the control hasn't been blurred ng-pending any $asyncValidators are unfulfilled

Angular Validation Style

Following style is only when the user had entered something into the form and the form contains invalid input.

input.ng-dirty.ng-invalid, select.ng-dirty.ng-invalid, textarea.ng-dirty.ng-invalid{
    border: 1px solid #dd4b39;
}

ConfirmController

ConfirmController almost like just one line of code, basically using the scopeService create above to get the scope from the previous page, createController

contactUsApp.controller('confirmCtrl',
    function ($scope, scopeService) {
    'use strict';
    $scope.cu = scopeService.cu;
});

ConfirmController's HTML

The confirmController's HTML just binds the scope to the UI.


    Contact
    Thanks, I got your message...

        Name:
        {{cu.name}}


        Email:
        {{cu.email}}


        Phone:
        {{cu.phone}}


        Enquiry Type
        {{cu.enquiryType}}


        Message:
        {{cu.message}}


Load Script for Angular App

I'm using jade, so the following is how I defined the angular script to my jade page. In production mode, should bundle all the script together. Also, for angular script loader and the order of how to script load. You only need to be aware of the first there javascript here, angular.js, angular-route, app.js. If you need to load any other angular app to your own app, you have to put that app before your app.js. That's the only rule for loading angular javascript in this way. But the rest of the script, controllers, services, views, directives, angular will take care of that. You only need to be aware of which one should be put first.

block js
	script(src='/js/vendor/angular.js')
	script(src='/js/vendor/angular-route.js')
	script(src='/apps/contactUs/app.js')
	script(src='/apps/contactUs/controllers/confirmController.js')
	script(src='/apps/contactUs/controllers/createController.js')
	script(src='/apps/contactUs/services/scopeService.js')

That's, the following is a sample image of how our two angular page contact form page will look like.

CreateController

ConfirmController