Created
April 23, 2015 12:55
-
-
Save timruffles/bfbc53c5d7444a506470 to your computer and use it in GitHub Desktop.
source code for demonstrating using ngModelController with SVG and Angular - full post https://truffles.me.uk/turn-anything-into-an-ng-model-with-ngmodelcontroller
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict"; | |
/* | |
the `clock()` directive the only bit of the code that's handling ngModel - the | |
rest is basic angular or DOM code | |
*/ | |
function clockDirective() { | |
return { | |
// we require an instance of ng-model to work | |
require: "ngModel", | |
link: function(scope, el, attrs, ngModel) { | |
// initialize the non-angular widget, and | |
// pass in our model updating fn | |
var clock = donutClock({ | |
onInput: function view2model(fromView) { | |
ngModel.$setViewValue(fromView); | |
}, | |
el: el[0], | |
}); | |
// when angular detects a change to the model, | |
// we update our widget | |
ngModel.$render = function model2view(time) { | |
clock.set(ngModel.$viewValue); | |
} | |
} | |
} | |
} | |
function TimeCtrl($scope, $interpolate, $interval) { | |
var MINUTE = 60 * 1000; | |
var HOUR = 60 * MINUTE; | |
var DAY = 24 * HOUR; | |
var CIRCLE = Math.PI * 2; | |
var COLOUR_TIME_OFFSET = Math.PI / 2; | |
$scope.item = { time: new Date }; | |
var ticker; | |
var h = 201; | |
var s = 80; | |
var hsl = $interpolate("hsl({{h}}, {{s}}%, {{l}}%)"); | |
$scope.timeToColour = function(time) { | |
var ratio = Math.sin((time / DAY) * CIRCLE - COLOUR_TIME_OFFSET); | |
var l = 20 + (70 * ratio); | |
return hsl({h:h,s:s,l:l}); | |
}; | |
Object.defineProperty($scope, "ticking", { | |
get: function() { | |
return !!ticker; | |
} | |
}); | |
$scope.toggle = function() { | |
if(ticker) { | |
$interval.cancel(ticker); | |
ticker = null; | |
} else { | |
ticker = $interval(function() { | |
$scope.item.time = new Date(+$scope.item.time + 5 * MINUTE); | |
}, 16); | |
} | |
} | |
} | |
function donutClock(options) { | |
var CIRCLE = Math.PI * 2; | |
var DAY = 24 * 60 * 60 * 1000; | |
assert(typeof options === "object", "pass object of options"); | |
options.change = options.change || Function.prototype; | |
var pointerHandlerR = 12.5; | |
var el = assert(options.el instanceof Element, "missing element") && options.el; | |
template(el); | |
var pointerHander = qs(el, "#hand-for-pointer"); | |
attr(pointerHander,"r", pointerHandlerR); | |
var face = el.querySelector("#face"); | |
attr(face,"r",150)("cx",150)("cy",150); | |
var removeMask = qs(el, "#remove-centre circle"); | |
attr(removeMask, "cx", 150)("cy", 150)("r", 125); | |
var r = +attr(face, "r"); | |
var handCircuitR = r - pointerHandlerR; | |
pointerHander.addEventListener("mousedown", mousedown); | |
return { | |
set: function(v) { | |
renderAngle(timeToAngle(v)); | |
} | |
}; | |
function qs(el, css) { | |
return el.querySelector(css); | |
} | |
// silly dom tool ;) | |
function attr(el, k, v) { | |
if(v == null) | |
return el.getAttribute(k) | |
else { | |
el.setAttribute(k,v); | |
return attr.bind(null, el); | |
} | |
} | |
// event handlers | |
function mousedown() { | |
document.body.addEventListener("mousemove", update); | |
document.body.addEventListener("mouseup", disable); | |
} | |
function disable() { | |
document.body.removeEventListener("mousemove", update); | |
document.body.removeEventListener("mouseup", disable); | |
} | |
// conversion | |
function angleToTime(angle) { | |
var ratio = angle / CIRCLE; | |
ratio = ratio + 0.25; | |
ratio = ratio < 0 ? 1 + ratio : ratio; | |
return ratio * DAY; | |
} | |
function timeToAngle(time) { | |
var ratio = timeOnly(time) / DAY; | |
if(ratio >= 0.75) { | |
ratio = -(1 - ratio); | |
} | |
ratio -= 0.25; | |
ratio *= CIRCLE; | |
return ratio; | |
} | |
function timeOnly(date) { | |
var mid = new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()); | |
return date - mid; | |
} | |
function update(event) { | |
var rect = el.getBoundingClientRect(); | |
var diffX = event.clientX - (rect.left + rect.width / 2); | |
var diffY = event.clientY - (rect.top + rect.height / 2); | |
var angle = Math.atan2(diffY, diffX); | |
options.onInput( new Date(angleToTime(angle)) ); | |
renderAngle(angle); | |
} | |
function renderAngle(angle) { | |
var x = r + Math.cos(angle) * handCircuitR; | |
var y = r + Math.sin(angle) * handCircuitR; | |
pointerHander.setAttribute("cx", x); | |
pointerHander.setAttribute("cy", y); | |
} | |
function assert(t, msg) { | |
if(!t) throw new Error(msg); | |
return t; | |
} | |
function svgEl(name) { | |
return document.createElementNS("http://www.w3.org/2000/svg", name); | |
} | |
function template(root) { | |
var defs = svgEl("defs"); | |
var mask = svgEl("mask"); | |
mask.id = "remove-centre"; | |
var rect = svgEl("rect"); | |
attr(rect, "width", "100%") | |
("height", "100%") | |
("fill", "#fff"); | |
mask.appendChild(rect); | |
mask.appendChild(svgEl("circle")); | |
defs.appendChild(mask); | |
var face = svgEl("circle"); | |
face.id = "face"; | |
attr(face, "mask", "url(#remove-centre)"); | |
var hand = svgEl("circle"); | |
hand.id = "hand-for-pointer"; | |
root.appendChild(defs); | |
root.appendChild(face); | |
root.appendChild(hand); | |
} | |
} | |
angular.module("demo", []) | |
.directive("clockInput", clockDirective) | |
.controller("TimeCtrl", TimeCtrl); | |
angular.bootstrap(document.documentElement, ["demo"]); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment