<!DOCTYPE html>
<html ng-app="myApp">
<head>
<script data-require="angular.js@1.3.5" data-semver="1.3.5" src="https://code.angularjs.org/1.3.5/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<link rel="stylesheet" href="resizable.css" />
<script src="resizable.js"></script>
<script src="script.js"></script>
</head>
<body ng-controller="MainCtrl">
<div id="bluebg" class="bluebg" ui-resizable="{containment: 'parent'}">
<h1 ui-resizable="{containment: 'bluebg'}" class="whitebg">{{content}}</h1>
<div class="whitebg" ui-resizable="resizableOptions">
<h2>Hello Plunker</h2>
</div>
</div>
</body>
</html>
var app = angular.module('myApp', ['ui.resizable']);
app.controller('MainCtrl', function($scope) {
$scope.content = 'Hello World';
$scope.resizableOptions = {handles: ['w','e','s','n','se','sw','ne','nw'],
containment: 'parent'
};
//$scope.bluebg = document.getElementById('body');
$scope.bluebg = 'body';
});
/* Styles go here */
html {
background: green;
}
body {
background: wheat;
margin: 0 auto;
width: 500px;
height: 400px;
overflow: auto;
}
.whitebg {
background: white;
width: 300px;
height: 60px;
min-width: 100px;
}
.bluebg {
background: blue;
width: 500px;
height: 200px;
}
/* Resizable element must have position attribute
specified for correct handle positions */
.ui-resizable {
position: relative;
}
/* Handle default style */
.ui-resizable-handle {
position: absolute;
font-size: 1px;
display: block;
border-style: dotted;
border-color: #aaa;
border-width: 0;
}
/* Resizing styles */
.ui-resizing-e, .ui-resizing-w { cursor: e-resize; }
.ui-resizing-s, .ui-resizing-n { cursor: s-resize; }
.ui-resizing-se, .ui-resizing-nw { cursor: se-resize; }
.ui-resizing-sw, .ui-resizing-ne { cursor: sw-resize; }
.ui-resizing .ui-resizable-handle { display: none; }
/* Disable borders when resizing or showBorders is false */
.handle-s.no-border,
.handle-w.no-border,
.handle-e.no-border,
.handle-n.no-border {
border-width: 0;
}
/* Handle position styles */
.handle-n, .handle-s { cursor: n-resize; height: 12px; left: 0px; right: 0px; }
.handle-w, .handle-e { cursor: w-resize; width: 12px; top: 0px; bottom: 0px; }
.handle-n { top: -1px; }
.handle-s { bottom: -1px; }
.handle-w { left: -1px; }
.handle-e { right: -1px; }
.handle-sw { cursor: sw-resize; left: -1px; bottom: -1px; }
.handle-se { cursor: se-resize; right: -1px; bottom: -1px; }
.handle-nw { cursor: nw-resize; top: -1px; left: -1px; }
.handle-ne { cursor: ne-resize; top: -1px; right: -1px; }
.handle-sw, .handle-se,
.handle-nw, .handle-ne {
border-right-color: transparent;
border-left-color: transparent;
}
/* Show borders on hover */
.ui-resizable:hover > .handle-n { border-top-width: 2px; }
.ui-resizable:hover > .handle-s { border-bottom-width: 2px; }
.ui-resizable:hover > .handle-w { border-left-width: 2px; }
.ui-resizable:hover > .handle-e { border-right-width: 2px; }
.ui-resizable:hover > .handle-sw { border-width:0 13px 13px 0; }
.ui-resizable:hover > .handle-se { border-width:0 0 13px 13px; }
.ui-resizable:hover > .handle-nw { border-width:13px 13px 0 0; }
.ui-resizable:hover > .handle-ne { border-width:13px 0 0 13px; }
'use strict';
/**
* Make any element resizable using the uiResizable directive.
* The only handlers that are currently implemented are e,s,se.
*
* - CONTAINMENT -
* You can contain the element using the containment option (default: false)
* It can contain:
* the id of the containment element
* the string 'body' (contained by window.body)
* the string 'parent' (contained by element's parent)
* the name of a scope variable that evaluates to any of the above
*
* @param resizableOptions {object} Configuration object for the directive
*/
angular.module('ui.resizable', []).directive('uiResizable', ['$document', '$log', function($document, $log) {
return {
restrict: 'A',
link: function(scope, element, attributes) {
var defaultOptions = {
additionalClassNames: '',
containment: false, //possible string values 'body'/'parent'
containmentHeight: Infinity,
containmentWidth: Infinity,
defaultClassName: 'ui-resizable',
handles: ['e', 's', 'se'],
handleClassName: 'ui-resizable-handle',
handleDirectionClassPrefix: 'handle-',
maxHeight: Infinity,
maxWidth: Infinity,
minHeight: 0,
minWidth: 0,
noBorderClassName: 'no-border',
resizingClassName: 'ui-resizing',
showBorders: false
};
/* setup options */
var options = scope.$eval(attributes.uiResizable) || {};
options = angular.extend(defaultOptions, options);
/* helper functions */
var getOffsetRect = function(elem) {
var box = elem.getBoundingClientRect();
var body = document.body;
var docElem = document.documentElement;
var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
var clientTop = docElem.clientTop || body.clientTop || 0;
var clientLeft = docElem.clientLeft || body.clientLeft || 0;
var top = box.top + scrollTop - clientTop;
var left = box.left + scrollLeft - clientLeft;
return {top: Math.round(top), left: Math.round(left)};
};
var getStyle = function(el, styleProp) {
var value, defaultView = el.ownerDocument.defaultView;
// W3C standard way:
if (defaultView && defaultView.getComputedStyle) {
// sanitize property name to css notation (hypen separated words eg. font-Size)
styleProp = styleProp.replace(/([A-Z])/g, '-$1').toLowerCase();
return defaultView.getComputedStyle(el, null).getPropertyValue(styleProp);
} else if (el.currentStyle) { // IE
// sanitize property name to camelCase
styleProp = styleProp.replace(/\-(\w)/g, function(str, letter) {
return letter.toUpperCase();
});
value = el.currentStyle[styleProp];
// convert other units to pixels on IE
if (/^\d+(em|pt|%|ex)?$/i.test(value)) {
return (function(value) {
var oldLeft = el.style.left, oldRsLeft = el.runtimeStyle.left;
el.runtimeStyle.left = el.currentStyle.left;
el.style.left = value || 0;
value = el.style.pixelLeft + 'px';
el.style.left = oldLeft;
el.runtimeStyle.left = oldRsLeft;
return value;
})(value);
}
return value;
}
};
/* Event handler functions */
var contElem = null,
contRect, rect;
var handler = function(event) {
event.preventDefault();
// get handle position
var regexp = new RegExp(options.handleDirectionClassPrefix + '[swen]+');
scope.handlePos = event.target.className.match(regexp)[0]
.replace(options.handleDirectionClassPrefix, '');
element.addClass(options.resizingClassName)
.addClass(options.resizingClassName+'-'+scope.handlePos);
// find containment element
if (options.containment) {
contElem = scope.$eval(options.containment) || options.containment;
if (!angular.isElement(contElem)) {
if (contElem === 'body') {
contElem = document.body;
} else if (contElem === 'parent') {
contElem = element.parent();
} else {
contElem = document.getElementById(contElem);
}
}
// get raw dom element
contElem = angular.element(contElem)[0];
}
// Calculate resizing and containment element rectangle positions
rect = getOffsetRect(element[0]);
if (contElem) contRect = getOffsetRect(contElem);
// Calculate padding and border width/height
rect.left += parseInt(getStyle(element[0],'padding-left'), 10) +
parseInt(getStyle(element[0],'padding-right'), 10) +
parseInt(getStyle(element[0],'border-left-width'), 10) +
parseInt(getStyle(element[0],'border-right-width'), 10);
rect.top += parseInt(getStyle(element[0],'padding-top'), 10) +
parseInt(getStyle(element[0],'padding-bottom'), 10) +
parseInt(getStyle(element[0],'border-top-width'), 10) +
parseInt(getStyle(element[0],'border-bottom-width'), 10);
// bind event handlers
$document.bind('mousemove', mousemove);
$document.bind('mouseup', mouseup);
};
var mousemove = function(event) {
var x, y, newWidth, newHeight, pos = scope.handlePos;
// calculate containment width/height if contElem exists
if (contElem) {
options.containmentWidth = contElem.offsetWidth + contRect.left - rect.left;
options.containmentHeight = contElem.offsetHeight + contRect.top - rect.top;
}
// handle x-axis
if (pos === 'w' || pos === 'e' || pos.length === 2) {
// TODO implement w,nw,ne,sw
if (pos === 'e' || pos === 'se') {
x = event.pageX;
newWidth = x - rect.left + 5;
if (newWidth >= options.minWidth && newWidth <= options.maxWidth
&& newWidth <= options.containmentWidth)
element.css('width', newWidth + 'px');
}
}
// handle y-axis
if (pos === 'n' || pos === 's' || pos.length === 2) {
// TODO implement n,nw,ne,sw handles
if (pos === 's' || pos === 'se') {
y = event.pageY;
newHeight = y - rect.top + 5;
if (newHeight >= options.minHeight && newHeight <= options.maxHeight
&& newHeight <= options.containmentHeight)
element.css('height', newHeight + 'px');
}
}
};
var mouseup = function() {
$document.unbind('mousemove', mousemove);
$document.unbind('mouseup', mouseup);
element.removeClass(options.resizingClassName)
.removeClass(options.resizingClassName+'-'+scope.handlePos);
};
/* Add classes */
element
.addClass(options.defaultClassName)
.addClass(options.additionalClassNames);
/* Generate all resizable handles */
// sort handles array so that corner handles will get appended last
// (position matters to prevent corners get overlapped)
var handles = options.handles.sort(function(a, b) {
return a.length - b.length;
});
var handleElem;
for (var i in handles) {
// TODO implement n,w,nw,ne,sw handles
if (handles[i] !== 'e' && handles[i] !== 's' && handles[i] !== 'se') {
$log.info('ui.resizable: ' + handles[i] + ' direction not implemented');
continue;
}
// create the handle
handleElem = angular.element('<div>').addClass(options.handleClassName);
if (!options.showBorders && handles[i].length !== 2)
handleElem.addClass(options.noBorderClassName);
// bind events to the handle
handleElem.bind('mousedown', handler);
// append handle to element
element.append(handleElem.addClass(options.handleDirectionClassPrefix + handles[i]));
}
}
};
}]);