Paul Hammant's Blog:
Angular - new Perl backend by Dobrica Pavlinušić
Miško Hevery's
<Angular/>
has
gained many fans over the last few months. I
was seriously looking at if for ThoughtWorks internal app-dev earlier
in the year (we have a slew of LotusNotes applications to
replace). There is nothing wrong with the technology, but
storing TW data in a third party cloud was not going to be an easy
sell. And that was the main choice back then. ThoughtWorks
is deep into the Google Corporate-Gmail solution and leverages the
associated applications including AppEngine where appropriate.
There is no AppEngine deployment capability for the server-data side of
<angular/>,
so I
reluctantly stepped back from my
experiments. I'm just one guy, so may have never been able to
persuade the powers that be that <angular/>
was what we should
choose for internal app dev.
It is just early beginnings, but there's every possibility that this could be an easy LAMP deployment when it progresses a little more. Here is me filling a form on his 'Symposium' demo app:
As is standard for <angular/> clients in development, there's a debug section below the page that shows the JSON stuffed into it. Take a look at the source for this page copied below. Look for use of 'ng:' ( n g colon ) in this gist:
<script> | |
if (typeof (console) === 'undefined') console = { debug: function() {} }; // mock console.debug | |
function Work($resource){ | |
this.master = { | |
type: '', | |
title: '', | |
abstract: '', | |
authors:[ { name:'', surname:'', inst:'', email:'' } ], | |
symposium: { organizer: [ {name:'', surname:'', inst:'', email:'' } ], work_nr: 1, }, | |
}; | |
this.last_saved_work = {}; | |
this.Work = $resource( '/data/conference/Work/:_id', { _id:'' } ); | |
this.Symposium = $resource( '/data/conference/Symposium/:_id', { _id:'' } ); | |
this.reset(); | |
this.$watch('$location.hashPath', this.hash_change); | |
} | |
Work.$inject=['$resource']; | |
Work.prototype = { | |
hash_change: function() { | |
var id = this.$location.hashPath; | |
console.debug( 'hash_change', id, this.work._id ); | |
if ( id != this.work._id ) { | |
if (id) { | |
var self = this; | |
this.work = this.Work.get({ _id: id }, function(work) { | |
self.last_saved_work = angular.copy(work); | |
if ( work.type == 'symposium' ) { | |
var s_id = work.symposium._id || work._id; | |
// first work doesn't have symposium._id, but we used same _id | |
console.debug( 'load symposium ', s_id ); | |
self.symposium = self.Symposium.get({ _id: s_id }); | |
} | |
}); | |
} | |
else this.reset(); | |
} | |
}, | |
reset: function() { | |
console.debug( this.Work ); | |
var current_symposium = null; | |
if ( this.work && this.work.type == 'symposium' ) { | |
current_symposium = this.work.symposium; | |
if ( this.work._id ) current_symposium.work_nr++; // only if saved | |
console.debug( 'current_symposium', current_symposium, this.work ) | |
} | |
this.work = new this.Work( this.master ); | |
if ( current_symposium ) { | |
this.work.symposium = current_symposium; | |
this.work.type = 'symposium'; | |
} | |
this.last_saved_work = {}; | |
console.debug( 'reset', current_symposium, this.work, this.$location.hashPath ); | |
}, | |
save: function(){ | |
var self = this; | |
this.work.$save(function(work){ | |
self.$location.hashPath = work._id; | |
self.last_saved_work = angular.copy(work); | |
// save symposium to separate resource | |
if ( work.type != 'symposium' ) return; | |
if ( ! self.symposium ) { | |
self.work.symposium._id = work._id; // reuse _id of first work for symposium | |
self.symposium = new self.Symposium( work.symposium ); | |
self.symposium.works = []; | |
} | |
self.symposium.works[ work.symposium.work_nr - 1 ] = work; | |
console.debug('save_symposium', self.symposium ); | |
self.symposium.$save(); | |
}); | |
}, | |
get_symposium: function() { this.symposium }, | |
}; | |
</script> | |
<h1>Conference work submission</h1> | |
<div ng:controller="Work" ng:init="$window.$root = this;"> | |
<h2>Type of work:</h2> | |
<label><input type="radio" name="work.type" value="symposium"> Symposium</label> | |
<div ng:show="work.type == 'symposium'" style="background:#f0f0f0"> | |
<!-- | |
<select name="work.symposium_id" ng:show="work.type == 'symposium'" ng:controller="Symposium"> | |
<option ng:repeat="s in symposiums" value="{{s._id}}">{{s.title}}</option> | |
</select> | |
--> | |
<label>Topic of symposium: <input name="work.symposium.title" size="60" ng:required></label><br/> | |
<label>Summary: <br/> | |
<textarea name="work.symposium.abstract" cols="50" rows="5"></textarea> | |
</label> | |
<br/> | |
Organizer: | |
<div ng:repeat="author in work.symposium.organizer"> | |
[<a href="" ng:click="work.symposium.organizer.$remove(author)">X</a>] | |
<input name="author.name" ng:required> | |
<input name="author.surname" ng:required> | |
<input name="author.inst" > | |
<input name="author.email" ng:required> | |
</div> | |
[<a href="" ng:click="work.symposium.organizer.$add()">Add another organizer</a>] | |
</div> | |
<br/> | |
<label><input type="radio" name="work.type" value="lecture"> Lecture</label><br/> | |
<label><input type="radio" name="work.type" value="poster"> Poster</label><br/> | |
<label><input type="radio" name="work.type" value="round"> Round table</label><br/> | |
<hr> | |
<div ng:show="symposium"> | |
Works which are part of this symposium: | |
<ol> | |
<li ng:repeat="w in symposium.works"><a href="#{{w._id}}">{{w.title}}</a></li> | |
</ol> | |
</div> | |
<h2>Autors<span ng:show="work.type == 'symposium'"> of {{work.symposium.work_nr}}. work </span></h2> | |
<div ng:repeat="author in work.authors"> | |
[<a href="" ng:click="work.authors.$remove(author)">X</a>] | |
<input name="author.name" ng:required> | |
<input name="author.surname" ng:required> | |
<input name="author.inst" > | |
<input name="author.email" ng:required> | |
</div> | |
[<a href="" ng:click="work.authors.$add()">Add another author</a>] | |
<hr> | |
<label>Title: <input name="work.title" size="60" ng:required></label><br/> | |
<label>Summary:<br> | |
<textarea name="work.abstract" cols="50" rows="5"></textarea> | |
</label> | |
<br/> | |
<span ng:show="$invalidWidgets.visible() == 0"> | |
<input type="submit" value="Save" ng:click="work.symposium_id=symposium._id; save();" ng:show="! last_saved_work.$equals(work)"> | |
<input type="reset" value="Add another work" ng:click="reset()" ng:show="work && work._id"> | |
</span> | |
<b ng:show="$invalidWidgets.visible() > 0" style="color:#800">{{$invalidWidgets.visible()}} errors to fix in submission form</b> | |
<div ng:show="work._id"> | |
Permalink to <a href="#{{work._id}}">{{work.title}}</a> which you can bookmark | |
</div> | |
<hr> | |
Debug Information: | |
{{$window.location.href}} | |
<pre> | |
work = {{work}} | |
dirty={{! last_saved_work.$equals(work)}} | |
last_saved_work = {{last_saved_work}} | |
master = {{master}} | |
$id={{$id}} | |
work.$id={{work.$id}} | |
work._id={{work._id}} | |
</pre> | |
</div> |
For those that did not know already, <angular/> is a stroke of genius that drags development of web-apps back into the realm of mortals. Its advance was to add attributes to well known HTML elements, send them to the browser with the page, and have some JavaScript in the browser act on them and essentially remove them before rendering. Check out the examples
No word yet, on open source Ruby or Python equivalents.