Angular rocks my socks
GeekcampSG 2013
7 Sep 2013
Ruiwen / Conor
The Pinchos
angularjs
Because mental Javascript-to-HTML translation is a PITA
Because widgets are fantastic
because our asses are worth saving
“I am not a compiler.”
angular
<ul id="items">
<li ng-repeat="i in items">
<div class="view">
<input class="toggle" type="checkbox" {{ i.checked ? 'checked': '' }}>
<label>{{ i.title }}</label>
<a class="destroy"></a>
</div>
<input class="edit" type="text" value="{{ i.title }}">
</li>
</ul>
<script type="text/template" id="item-template">
<div class="view">
<input class="toggle" type="checkbox" <%= done ? 'checked="checked"' : '' %> />
<label><%- title %></label>
<a class="destroy"></a>
</div>
<input class="edit" type="text" value="<%- title %>" />
</script>
backbone
// The DOM element for a todo item...
var TodoView = Backbone.View.extend({
//... is a list tag.
tagName: "li",
// Cache the template function for a single item.
template: _.template($('#item-template').html()),
// The DOM events specific to an item.
events: {
"click .toggle" : "toggleDone",
"dblclick .view" : "edit",
"click a.destroy" : "clear",
"keypress .edit" : "updateOnEnter",
"blur .edit" : "close"
},
// The TodoView listens for changes to its model, re-rendering. Since there's
// a one-to-one correspondence between a **Todo** and a **TodoView** in this
// app, we set a direct reference on the model for convenience.
initialize: function() {
this.listenTo(this.model, 'change', this.render);
this.listenTo(this.model, 'destroy', this.remove);
},
// Re-render the titles of the todo item.
render: function() {
this.$el.html(this.template(this.model.toJSON()));
this.$el.toggleClass('done', this.model.get('done'));
this.input = this.$('.edit');
return this;
},
// Toggle the `"done"` state of the model.
toggleDone: function() {
this.model.toggle();
},
// Switch this view into `"editing"` mode, displaying the input field.
edit: function() {
this.$el.addClass("editing");
this.input.focus();
},
// Close the `"editing"` mode, saving changes to the todo.
close: function() {
var value = this.input.val();
if (!value) {
this.clear();
} else {
this.model.save({title: value});
this.$el.removeClass("editing");
}
},
// If you hit `enter`, we're through editing the item.
updateOnEnter: function(e) {
if (e.keyCode == 13) this.close();
},
// Remove the item, destroy the model.
clear: function() {
this.model.destroy();
}
});
<pony pinkie-pie>
<!-- Facebook SDK -->
<fb app-id='12345678'></fb>
<!-- Facebook SDK -->
<fb app-id='12345678'
fb-init="$scope.doStuff()"></fb>
<fb app-id='12345678'
fb-init="$scope.doStuff()"></fb>
// Facebook SDK angular.module('bp-utils') .directive('fb', ['$FB', function($FB) { return { restrict: "E", replace: true, template: '<div id="fb-root"></div>', compile: function(tElem, tAttrs) { return { post: function(scope, iElem, iAttrs, controller) { var fbAppId = iAttrs.appId || ''; var fb_params = { appId: iAttrs.appId || "", cookie: iAttrs.cookie || true, status: iAttrs.status || true, xfbml: iAttrs.xfbml || true, }; // Setup the post-load callback window.fbAsyncInit = function() { if ('fbInit' in iAttrs) { iAttrs.fbInit(); } }; (function(d, s, id, fbAppId) { var js, fjs = d.getElementsByTagName(s)[0]; if (d.getElementById(id)) return; js = d.createElement(s); js.id = id; js.async = true; js.src = "//connect.facebook.net/en_US/all.js"; fjs.parentNode.insertBefore(js, fjs); }(document, 'script', 'facebook-jssdk', fbAppId)); } }; } }; }])
use-ajax
angular.module('bp-utils')
.directive('useAjax', [
'$rootScope',
'$location',
'progressbar',
'bp-utils.pushState',
function($rootScope, $location, progressbar, pushState) {
return {
restrict: 'A',
link: function(scope, element, attr) {
var url = attr.action || 'search'
, method = attr.method || 'get'
, action = attr.useAjax || function() {}
, afterSubmit = scope.$eval(attr.uaAfter)
, beforeSubmit = scope.$eval(attr.uaBefore);
// override
beforeSubmit = beforeSubmit || function(event, options) {};
// override
afterSubmit = beforeSubmit || function(event, options) {};
submit = function(event, pageNumber) {
var data = '';
data += $(element[0]).serialize();
beforeSubmit(data);
$.ajax({
url: url,
data: data,
type: method,
success: function(data, status, xhr) {
if (action && scope[action]) scope[action](data);
if (!scope.$$phase) scope.$apply();
},
error: function(xhr, status, error) {
},
complete: function(data) {
afterSubmit(data);
}
});
event.preventDefault();
return false;
};
element.on('submit', submit);
}
};
}
])
;
Asians love tests
describe('BillPin Desktop Web', function() {
var $LS; // Embedded iframe's localStorage
var user;
beforeEach(function() {
// Do some setup
});
describe('When logging in through Landing', function() {
it('should show the Landing page', function() {
browser().navigateTo('/');
expect(element('#id_username').count()).toBe(1);
expect(element('#id_password').count()).toBe(1);
});
it('logging in should show the app page', function() {
element('#login_link').click();
element('#id_username').val('billtest@billpin.com');
element('#id_password').val('test1234');
element('.red_button').click();
});
});
TIME FOR YOUR DEPENDENCY INJECTION
TRADITIONAL MODULE Structure
var billpinModule = angular.module('billpin', [
'models',
'controllers',
'directives',
'filters',
'services'
]);
TOO MAINSTREAM. ANOTHER WAY?
CAN SOCKS SHOW US THE WAY?
var billpinModule = angular.module('billpin', [
'controllers',
'utils',
'ngCookies',
'bp-facebook',
'bp-invite',
'bp-user',
'bp-history',
'bp-currency',
'bp-home',
'bp-photos',
'bp-pay-me',
'app-templates',
'component-templates'
]);
grunt
all at once
HEAVY MUCH?
HATE IS THE PUREST FORM OF LOVE
THE HONEYMOON PERIOD
ADVANCED
Title
Geekcamp2013
By Ruiwen Chua
Geekcamp2013
- 899