Grails And Angular

Bridging The Gap

What I do

Explore
Create
TEST

What is Grails?

  • JVM based solution for web applications
  • Built on proven technologies like Spring and Groovy
  • Mature and Established in many businesses

WhY Grails?

  • JAVA!!!!!

  • Large pool of developers
  • Groovy/Spring

Resonable Rates

Source Business Insider

Flourishing Community

Source Redmonk |  X: # Github Y: # Stackoverflow

Typical Layout

Grails V1 

  • One page per action

  • Offload complexity to services

Grails V2

  • Start using ajax with special GSP tags

  • Provide respond in addition to render

Next Gen 2.4/3.0

  • Gradle over GANT
  • Spring Web Components
  • Java 8
  • Multiple Sever Environments
   |-grails-app
   |---assets
   |-----images
   |-----javascripts
   |-----stylesheets
   |---conf
   |-----hibernate
   |-----spring
   |---controllers
   |-----org
   |-------gleason
   |---------test
   |---domain
   |---i18n
   |---migrations
   |---services
   |-----org
   |-------gleason
   |---------test
   |---taglib
   |---utils
   |---views
   |-----layouts
   |-----socket
   |-----thefirm
   |-scripts
   |-src
   |---groovy
   |-----org
   |-------gleason
   |---------test
   |-----------command
   |---java
   |-test

Grails Structure

class QuestionController {
	def questionService
	def index(){
	}
	def get() {
		render questionService.get(params.id) as JSON
	}
	def update() {
		def obj = JSON.parse(request.reader.text);
		Question question = new Question();
		question.key = obj.key
		question.text = obj.text
		question.desc = obj.desc
		question.voteCount = obj.voteCount
		render questionService.update(question) as JSON
	}
	def create(Question q){
		render questionService.create(q) as JSON
	}
	def delete(){
		def text = request.reader.text;
		def slurper = new JsonSlurper();
		def result = slurper.parseText(text)
		render questionService.delete(result.key)
	}
}

Grails Controller

class UrlMappings {
	static mappings = {
		"/Note/$id?"(controller: "question", parseRequest: true) {
			action = [GET: "get", POST: "create", PUT: "update", DELETE: "delete"]
		}
		"/socket"(controller:"socket"){
			action=[GET:"index"]
		}
		"/"(view:"/app")
		"500"(view:'/error')
	}
}

URL Mapping

class SocketController {
	def index(){
		
	}
	@MessageMapping("/hello")
	@SendTo("/topic/hello")
	protected String hello(String world) {
	   return "hello from controller, ${world}!"+new String(UUID.randomUUID().getMostSignificantBits());
	}
}

Grails STomp

import grails.validation.Validateable

@Validateable
class Question {
	Integer key
	String text
	String desc
	Integer voteCount
	List<String> errorMessages
	static constraints = {
		key unique: true
		text blank: false
		desc blank: false
		errorMessages nullable: true
	}
}

Command Object And Validation

question.desc.nullable=Description cannot be null
question.desc.blank=Description cannot be blank
obj.validate()
if(!obj.hasErrors()){
    notes.put(obj.key.toString(), obj)
}
else{
    obj.errorMessages = obj?.errors?.allErrors?.collect{messageSource.getMessage(it,null)
}
window.appContext = '${request.contextPath}';

Setting Up the Context Root

What is ANGular?

  • Javascript
  • MVC platform for the client
  • Allows for efficient 2-way Data Binding
  • Supported by Google

Why Angular

  • Smaller Requests == Less Bandwidth == $$
  • Realtime Data-Binding
  • Single Page Paradigm
module.exports = function(grunt) {
	grunt.loadNpmTasks('grunt-html2js');
	grunt.loadNpmTasks('grunt-contrib-concat');
	grunt.loadNpmTasks('grunt-contrib-watch');
	grunt.loadNpmTasks('grunt-contrib-clean');
	grunt.loadNpmTasks('grunt-ng-annotate');
	grunt.loadNpmTasks('grunt-contrib-uglify');
	grunt.loadNpmTasks('grunt-contrib-copy');
	grunt.loadNpmTasks('grunt-cucumber');
	grunt.initConfig({
		pkg : grunt.file.readJSON('package.json'),
		html2js : {
			build : {
				src : [ 'src/main/partials/**/*.html', 
				        'src/main/partials/**/*.jade' ],
				dest : 'build/ng-grails-templates.js'
			}
		},
		concat : {
			core : {
				src : [ 'src/main/angular/**/app.js',
						'src/main/angular/**/*.js'],
				dest : 'build/ng-grails-core.js',
			},
		    combine: {
		    	src:['build/ng-grails-templates.js',
		    	     'build/ng-grails-core.js'],
		    	dest : 'build/ng-grails.js',     
		    },
			jasmine: {
				src:['src/test/jasmine/**/*.js'],
				dest:'build/ng-grails-test.js'
			}
			
		},
		clean: {
			  build: ["build",'web-app/js/ng-grails.js','web-app/js/templates.js']
	    },
		watch : {
			html2js : {
				files : [ 'src/main/partials/**/*.html', 
				          'src/main/partials/**/*.jade' ],
				tasks : [ 'html2js' ]
			},
			concat : {
				files : ['src/main/angular/**/app.js',
							'src/main/angular/**/*.js',
							'build/ng-grails-templates.js'],
				tasks : [ 'concat' ]
			}
		},
		ngAnnotate: {
	        options: {
	        	remove: true
	        },
	        release: {
	        	files: {
	        	  'build/ng-grails-annotated.js': ['build/ng-grails.js']
	        	}
	        },
	    },
	    uglify: {
	        release: {
	          files: {
	            'build/ng-grails-min.js': ['build/ng-grails-annotated.js']
	          }
	        }
	    },
        copy: {
          release: {
              files: [
                  {src: ['build/ng-grails-min.js'], dest: 'web-app/js/ng-grails.js'},
              ],
          },
          build: {
        	  files: [
                  {src: ['build/ng-grails.js'], dest: 'web-app/js/ng-grails.js'},
              ],
          },
          jasmine: {
        	  files: [
        	          {src: ['build/ng-grails-test.js'], dest: 'web-app/js/ng-grails-test.js'}
        	          ]
          }
        },
        cucumberjs: {
            src: 'src/test/features',
            options: {
              steps: "src/test/features/step_definitions"
            }
        }
	});
	grunt.registerTask('default', [ 'build' ]);
	grunt.registerTask('build', [ 'clean', 'html2js', 'concat:core', 'concat:combine', 'copy:build']);
	grunt.registerTask('release', [ 'clean', 'html2js', 'concat:core', 'concat:combine', 'ngAnnotate', 'uglify', 'copy:release' ]);
	grunt.registerTask('test', ['build','concat:jasmine', 'copy:jasmine']);
	return grunt;
}

Grunt File

Copy of deck

By Jackie Gleason

Copy of deck

  • 594