elm-core-sources-1.0.0: src/Native/Graphics/Element.js
// setup
Elm.Native = Elm.Native || {};
Elm.Native.Graphics = Elm.Native.Graphics || {};
Elm.Native.Graphics.Element = Elm.Native.Graphics.Element || {};
// definition
Elm.Native.Graphics.Element.make = function(localRuntime) {
'use strict';
// attempt to short-circuit
if ('values' in Elm.Native.Graphics.Element) {
return Elm.Native.Graphics.Element.values;
}
var Color = Elm.Native.Color.make(localRuntime);
var List = Elm.Native.List.make(localRuntime);
var Utils = Elm.Native.Utils.make(localRuntime);
function createNode(elementType) {
var node = document.createElement(elementType);
node.style.padding = "0";
node.style.margin = "0";
return node;
}
function setProps(elem, node) {
var props = elem.props;
var element = elem.element;
var width = props.width - (element.adjustWidth || 0);
var height = props.height - (element.adjustHeight || 0);
node.style.width = (width |0) + 'px';
node.style.height = (height|0) + 'px';
if (props.opacity !== 1) {
node.style.opacity = props.opacity;
}
if (props.color.ctor === 'Just') {
node.style.backgroundColor = Color.toCss(props.color._0);
}
if (props.tag !== '') {
node.id = props.tag;
}
if (props.hover.ctor !== '_Tuple0') {
addHover(node, props.hover);
}
if (props.click.ctor !== '_Tuple0') {
addClick(node, props.click);
}
if (props.href !== '') {
var anchor = createNode('a');
anchor.href = props.href;
anchor.style.display = 'block';
anchor.style.pointerEvents = 'auto';
anchor.appendChild(node);
node = anchor;
}
return node;
}
function addClick(e, handler) {
e.style.pointerEvents = 'auto';
e.elm_click_handler = handler;
function trigger(ev) {
e.elm_click_handler(Utils.Tuple0);
ev.stopPropagation();
}
e.elm_click_trigger = trigger;
e.addEventListener('click', trigger);
}
function removeClick(e, handler) {
if (e.elm_click_trigger) {
e.removeEventListener('click', e.elm_click_trigger);
e.elm_click_trigger = null;
e.elm_click_handler = null;
}
}
function addHover(e, handler) {
e.style.pointerEvents = 'auto';
e.elm_hover_handler = handler;
e.elm_hover_count = 0;
function over(evt) {
if (e.elm_hover_count++ > 0) return;
e.elm_hover_handler(true);
evt.stopPropagation();
}
function out(evt) {
if (e.contains(evt.toElement || evt.relatedTarget)) return;
e.elm_hover_count = 0;
e.elm_hover_handler(false);
evt.stopPropagation();
}
e.elm_hover_over = over;
e.elm_hover_out = out;
e.addEventListener('mouseover', over);
e.addEventListener('mouseout', out);
}
function removeHover(e) {
e.elm_hover_handler = null;
if (e.elm_hover_over) {
e.removeEventListener('mouseover', e.elm_hover_over);
e.elm_hover_over = null;
}
if (e.elm_hover_out) {
e.removeEventListener('mouseout', e.elm_hover_out);
e.elm_hover_out = null;
}
}
function image(props, img) {
switch (img._0.ctor) {
case 'Plain': return plainImage(img._3);
case 'Fitted': return fittedImage(props.width, props.height, img._3);
case 'Cropped': return croppedImage(img,props.width,props.height,img._3);
case 'Tiled': return tiledImage(img._3);
}
}
function plainImage(src) {
var img = createNode('img');
img.src = src;
img.name = src;
img.style.display = "block";
return img;
}
function tiledImage(src) {
var div = createNode('div');
div.style.backgroundImage = 'url(' + src + ')';
return div;
}
function fittedImage(w, h, src) {
var div = createNode('div');
div.style.background = 'url(' + src + ') no-repeat center';
div.style.webkitBackgroundSize = 'cover';
div.style.MozBackgroundSize = 'cover';
div.style.OBackgroundSize = 'cover';
div.style.backgroundSize = 'cover';
return div;
}
function croppedImage(elem, w, h, src) {
var pos = elem._0._0;
var e = createNode('div');
e.style.overflow = "hidden";
var img = createNode('img');
img.onload = function() {
var sw = w / elem._1, sh = h / elem._2;
img.style.width = ((this.width * sw)|0) + 'px';
img.style.height = ((this.height * sh)|0) + 'px';
img.style.marginLeft = ((- pos._0 * sw)|0) + 'px';
img.style.marginTop = ((- pos._1 * sh)|0) + 'px';
};
img.src = src;
img.name = src;
e.appendChild(img);
return e;
}
function goOut(node) {
node.style.position = 'absolute';
return node;
}
function goDown(node) {
return node;
}
function goRight(node) {
node.style.styleFloat = 'left';
node.style.cssFloat = 'left';
return node;
}
var directionTable = {
DUp : goDown,
DDown : goDown,
DLeft : goRight,
DRight : goRight,
DIn : goOut,
DOut : goOut
};
function needsReversal(dir) {
return dir == 'DUp' || dir == 'DLeft' || dir == 'DIn';
}
function flow(dir,elist) {
var array = List.toArray(elist);
var container = createNode('div');
var goDir = directionTable[dir];
if (goDir == goOut) {
container.style.pointerEvents = 'none';
}
if (needsReversal(dir)) {
array.reverse();
}
var len = array.length;
for (var i = 0; i < len; ++i) {
container.appendChild(goDir(render(array[i])));
}
return container;
}
function toPos(pos) {
switch(pos.ctor) {
case "Absolute": return pos._0 + "px";
case "Relative": return (pos._0 * 100) + "%";
}
}
// must clear right, left, top, bottom, and transform
// before calling this function
function setPos(pos,elem,e) {
var element = elem.element;
var props = elem.props;
var w = props.width + (element.adjustWidth ? element.adjustWidth : 0);
var h = props.height + (element.adjustHeight ? element.adjustHeight : 0);
e.style.position = 'absolute';
e.style.margin = 'auto';
var transform = '';
switch(pos.horizontal.ctor) {
case 'P': e.style.right = toPos(pos.x); e.style.removeProperty('left'); break;
case 'Z': transform = 'translateX(' + ((-w/2)|0) + 'px) ';
case 'N': e.style.left = toPos(pos.x); e.style.removeProperty('right'); break;
}
switch(pos.vertical.ctor) {
case 'N': e.style.bottom = toPos(pos.y); e.style.removeProperty('top'); break;
case 'Z': transform += 'translateY(' + ((-h/2)|0) + 'px)';
case 'P': e.style.top = toPos(pos.y); e.style.removeProperty('bottom'); break;
}
if (transform !== '') {
addTransform(e.style, transform);
}
return e;
}
function addTransform(style, transform) {
style.transform = transform;
style.msTransform = transform;
style.MozTransform = transform;
style.webkitTransform = transform;
style.OTransform = transform;
}
function container(pos,elem) {
var e = render(elem);
setPos(pos, elem, e);
var div = createNode('div');
div.style.position = 'relative';
div.style.overflow = 'hidden';
div.appendChild(e);
return div;
}
function rawHtml(elem) {
var html = elem.html;
var guid = elem.guid;
var align = elem.align;
var div = createNode('div');
div.innerHTML = html;
div.style.visibility = "hidden";
if (align) {
div.style.textAlign = align;
}
div.style.visibility = 'visible';
div.style.pointerEvents = 'auto';
return div;
}
function render(elem) {
return setProps(elem, makeElement(elem));
}
function makeElement(e) {
var elem = e.element;
switch(elem.ctor) {
case 'Image': return image(e.props, elem);
case 'Flow': return flow(elem._0.ctor, elem._1);
case 'Container': return container(elem._0, elem._1);
case 'Spacer': return createNode('div');
case 'RawHtml': return rawHtml(elem);
case 'Custom': return elem.render(elem.model);
}
}
function updateAndReplace(node, curr, next) {
var newNode = update(node, curr, next);
if (newNode !== node) {
node.parentNode.replaceChild(newNode, node);
}
return newNode;
}
function update(node, curr, next) {
var rootNode = node;
if (node.tagName === 'A') {
node = node.firstChild;
}
if (curr.props.id === next.props.id) {
updateProps(node, curr, next);
return rootNode;
}
if (curr.element.ctor !== next.element.ctor) {
return render(next);
}
var nextE = next.element;
var currE = curr.element;
switch(nextE.ctor) {
case "Spacer":
updateProps(node, curr, next);
return rootNode;
case "RawHtml":
// only markdown blocks have guids, so this must be a text block
if (nextE.guid === null) {
if(currE.html.valueOf() !== nextE.html.valueOf()) {
node.innerHTML = nextE.html;
}
updateProps(node, curr, next);
return rootNode;
}
if (nextE.guid !== currE.guid) {
return render(next);
}
updateProps(node, curr, next);
return rootNode;
case "Image":
if (nextE._0.ctor === 'Plain') {
if (nextE._3 !== currE._3) {
node.src = nextE._3;
}
} else if (!Utils.eq(nextE,currE) ||
next.props.width !== curr.props.width ||
next.props.height !== curr.props.height) {
return render(next);
}
updateProps(node, curr, next);
return rootNode;
case "Flow":
var arr = List.toArray(nextE._1);
for (var i = arr.length; i--; ) {
arr[i] = arr[i].element.ctor;
}
if (nextE._0.ctor !== currE._0.ctor) {
return render(next);
}
var nexts = List.toArray(nextE._1);
var kids = node.childNodes;
if (nexts.length !== kids.length) {
return render(next);
}
var currs = List.toArray(currE._1);
var dir = nextE._0.ctor;
var goDir = directionTable[dir];
var toReverse = needsReversal(dir);
var len = kids.length;
for (var i = len; i-- ;) {
var subNode = kids[toReverse ? len - i - 1 : i];
goDir(updateAndReplace(subNode, currs[i], nexts[i]));
}
updateProps(node, curr, next);
return rootNode;
case "Container":
var subNode = node.firstChild;
var newSubNode = updateAndReplace(subNode, currE._1, nextE._1);
setPos(nextE._0, nextE._1, newSubNode);
updateProps(node, curr, next);
return rootNode;
case "Custom":
if (currE.type === nextE.type) {
var updatedNode = nextE.update(node, currE.model, nextE.model);
updateProps(updatedNode, curr, next);
return updatedNode;
} else {
return render(next);
}
}
}
function updateProps(node, curr, next) {
var nextProps = next.props;
var currProps = curr.props;
var element = next.element;
var width = nextProps.width - (element.adjustWidth || 0);
var height = nextProps.height - (element.adjustHeight || 0);
if (width !== currProps.width) {
node.style.width = (width|0) + 'px';
}
if (height !== currProps.height) {
node.style.height = (height|0) + 'px';
}
if (nextProps.opacity !== currProps.opacity) {
node.style.opacity = nextProps.opacity;
}
var nextColor = nextProps.color.ctor === 'Just'
? Color.toCss(nextProps.color._0)
: '';
if (node.style.backgroundColor !== nextColor) {
node.style.backgroundColor = nextColor;
}
if (nextProps.tag !== currProps.tag) {
node.id = nextProps.tag;
}
if (nextProps.href !== currProps.href) {
if (currProps.href === '') {
// add a surrounding href
var anchor = createNode('a');
anchor.href = nextProps.href;
anchor.style.display = 'block';
anchor.style.pointerEvents = 'auto';
node.parentNode.replaceChild(anchor, node);
anchor.appendChild(node);
} else if (nextProps.href === '') {
// remove the surrounding href
var anchor = node.parentNode;
anchor.parentNode.replaceChild(node, anchor);
} else {
// just update the link
node.parentNode.href = nextProps.href;
}
}
// update click and hover handlers
var removed = false;
// update hover handlers
if (currProps.hover.ctor === '_Tuple0') {
if (nextProps.hover.ctor !== '_Tuple0') {
addHover(node, nextProps.hover);
}
}
else {
if (nextProps.hover.ctor === '_Tuple0') {
removed = true;
removeHover(node);
}
else {
node.elm_hover_handler = nextProps.hover;
}
}
// update click handlers
if (currProps.click.ctor === '_Tuple0') {
if (nextProps.click.ctor !== '_Tuple0') {
addClick(node, nextProps.click);
}
}
else {
if (nextProps.click.ctor === '_Tuple0') {
removed = true;
removeClick(node);
} else {
node.elm_click_handler = nextProps.click;
}
}
// stop capturing clicks if
if (removed
&& nextProps.hover.ctor === '_Tuple0'
&& nextProps.click.ctor === '_Tuple0')
{
node.style.pointerEvents = 'none';
}
}
function htmlHeight(width, rawHtml) {
// create dummy node
var temp = document.createElement('div');
temp.innerHTML = rawHtml.html;
if (width > 0) {
temp.style.width = width + "px";
}
temp.style.visibility = "hidden";
temp.style.styleFloat = "left";
temp.style.cssFloat = "left";
document.body.appendChild(temp);
// get dimensions
var style = window.getComputedStyle(temp, null);
var w = Math.ceil(style.getPropertyValue("width").slice(0,-2) - 0);
var h = Math.ceil(style.getPropertyValue("height").slice(0,-2) - 0);
document.body.removeChild(temp);
return Utils.Tuple2(w,h);
}
return Elm.Native.Graphics.Element.values = {
render: render,
update: update,
updateAndReplace: updateAndReplace,
createNode: createNode,
addTransform: addTransform,
htmlHeight: F2(htmlHeight),
guid: Utils.guid
};
};