<!DOCTYPE html>
<html>
<head>
<link data-require="bootstrap-css@3.1.1" data-semver="3.1.1" rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" />
<script data-require="jquery@*" data-semver="2.0.3" src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
<script data-require="angular.js@*" data-semver="1.3.0-beta.5" src="https://code.angularjs.org/1.3.0-beta.5/angular.js"></script>
<script data-require="bootstrap@*" data-semver="3.1.1" src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="style.css" />
<link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
<script src="script.js"></script>
<script src="bootstrap-colorpicker-module.js"></script>
<script src="angular-wysiwyg.js"></script>
</head>
<body ng-app="app">
<h2>Angular WYSIWYG directive</h2>
<div ng-controller="MyCtrl">
<p>Your models data</p>
<p style="height:80px; width:100%; border: 1px solid #ddd">
{{data.text}}
</p>
<wysiwyg textarea-id="question" textarea-class="form-control" textarea-height="180px" textarea-name="textareaQuestion" textarea-required ng-model="data.text" enable-bootstrap-title="true"></wysiwyg>
</div>
</body>
</html>
var app = angular.module('app', ['colorpicker.module', 'wysiwyg.module'])
app.controller('MyCtrl', function($scope) {
$scope.data = {
text: "hello"
}
})
/* Styles go here */
.colorpicker-visible,
.colorpicker-visible .dropdown-menu {
display: block !important;
}
colorpicker-saturation {
display: block;
width: 100px;
height: 100px;
background-image: url('data:image/png;base64,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');
cursor: crosshair;
float: left;
}
colorpicker-saturation i {
display: block;
height: 7px;
width: 7px;
border: 1px solid #000;
border-radius: 5px;
position: absolute;
top: 0;
left: 0;
margin: -4px 0 0 -4px;
}
colorpicker-saturation i::after {
content: '';
display: block;
height: 7px;
width: 7px;
border: 1px solid #fff;
border-radius: 5px;
}
colorpicker-hue,
colorpicker-alpha {
width: 15px;
height: 100px;
float: left;
cursor: row-resize;
margin-left: 4px;
margin-bottom: 4px;
}
colorpicker-hue i,
colorpicker-alpha i {
display: block;
height: 2px;
background: #000;
border-top: 1px solid #fff;
position: absolute;
top: 0;
left: 0;
width: 100%;
margin-top: -1px;
}
colorpicker-hue {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAABkCAMAAABw8qpSAAABLFBMVEUA/z8AuP//JAAA/33/3AAA/1ABAv8A/7r/AH7/jgD2AP8A//j/AEHmAP/XAP/HAP+4AP//ALyoAP+aAP+JAP97AP9rAP9cAP9MAP8+AP8tAP8fAP8PAP8ATv//AG7/cAD/vgD/APoAmv//ADH/AKwB/wMA5//4Eg4AL///AOr/UQD/nwAA/27/7AAA/+kAe/8Ayf8A/5sA/zEA/6z/ABEAEP8A/17/MgAA/9n/ACL/gAD/AJ0AXP8Aqv//AMoA/yHqFBb/zAD/AGD/ANsA9//1/wDk/wDV/wDF/wC3/wD/AI2m/wD/FACY/wCI/wB5/wBp/wD/YgBb/wBK/wA8/wAs/wAd/wAN/wAAPv8A/xH/AFAAi///rQAA/8r/+gAA1///QwAAH/8Abf8A/43c/JNGAAAAiUlEQVR4AQXBg2EDAAAAsMy2bds2ttp2+/8PTby79mDLsKJPq/oFPdk24dWXAxsGjRg1ZtykKdNmzJozb8GiJct63WjYl7fiWdOZkk0vOpyr2fVtyKl7FX2uXGjpcuxWDy69KdiRk5WRlpIUFxMVERLw78+vH1Unun1YV3ZkwKM1CYfq7nQK22sD03ITV2Aqp0IAAAAASUVORK5CYII=');
}
colorpicker-alpha {
display: none;
}
colorpicker-alpha,
.colorpicker-color {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAABkCAMAAACIElGlAAADAFBMVEUAAADT09PT09P////T09P////e3t7q6urT09Px8fHT09P////////T09PT09P////////T09PT09P////////////T09PT09P////////////T09P////T09PT09PT09P////T09PT09P////////////////T09P////T09PT09P////T09PT09PT09PT09PT09PT09P////T09P////T09PT09P////////////T09P////T09P////////////T09PT09P////T09P////////////////////T09P////////T09PT09P////////////////////////T09PT09P////////////////////////T09PT09P////T09PT09P////////T09P////////////T09P////////T09P////T09P////T09P////T09PT09PT09PT09P////T09PT09PT09PT09PT09PT09P////T09P////T09PT09P////////////T09PT09PT09P////T09PT09PT09PT09PT09PT09PT09P////////////////T09PT09P////////////T09P////////T09P////T09PT09PT09P////////T09P////////T09P////T09PT09P////////////////T09PT09PT09PT09P////T09PT09PT09PT09PT09PT09PT09P////T09P////T09PT09PT09PT09PT09P////////////////////////////////////T09P////T09P////T09P////T09PT09P////////////T09P////T09P////T09P////////////T09PT09P////////////////T09PT09P////T09P////T09PT09P////T09P////T09PT09P////T09PT09PT09PT09P////T09PT09P////////////T09PT09P////T09P////////T09MQsm1FAAABAHRSTlMAgJN8/vcDAfcCnJyGaZmZlomGk4yJOmM/eTxs8wY0YDFC7HNdLx18n5/7aUvzCcW9+qKiK8P0ZiltRwfdw/n8Px3WduJjItj78ss5PDHUNELbwP5wplA2FglEVwvkqNarCs4Z7b2sDLgQ0xNdyLrr0eLLUeW1Vs5TWQLwjPI3ZvQGdvxFyFrAeevaLCLvGd0kpRskGyf4qK605xKvFrGyDRHnBYMEkJaDkIBvB/gpH99O6CrbIC4nH3Lg2SXp4A7Qul/GDEgPSlMQ6LjqFU0SjyCCj5V/gnBN7xglL3O70WBU7gjFFEfft0sPTo1ndRipwXr2yRemWRVENq+ytbGuxGaWGQAAAnpJREFUeNpNxmdcDHAAxvEHOWXvmXX23ntv2XvvMg/Z44x0KaRBSnfcKSqlnYZKp1QqGpT20KZh783/eePj+3vzw3/q/AM9iURPTy6XS+RwcXNzcZMQTE1dTE3bE7y9S0u9xxD600rCGtpI2ErOsbGxzoiMrIqscnaOi4vDdnp4XYSrguwxQSb7KpMllycnl2MFaTSaTxrY5NrY5BoKb2BnZ2doZ29vb2OPSdSd4Bcc7OcXGhqamgqrYCsrq+mEiEtC3gUBDg55Dg53IkQYSYG+gb6+GEqB5wV0o5D8/JAQdKK5hA40n3BS+L6c0JJmE+ZQW8JEak1YQJ0Jq+jbaQEPnotGE+IrK+Pj+xKqU6pTUrIDAgKysZ+WEY5QQkJxcQIO0WrCUbL4YmFhgZ3UlVAUHR1d1JEwjVoRRpDJKxMTE/ShBoRR1I6whLyyvLyyUFAQFhbWhTCFFArFawXOCu/bEHpQLYKnZ0aGZw2CR1qah0dmpqurK+pTU0ILakJoTi+PC7j5RHTrqQg1qRkh3T3d3b0xoSE1ItSleoSe1IswmIYQPl8UlhLWkaOj41tHbKENBLX6j1qt/KFUKnFN+Onk9NvJCQdJpfqlUqGiIjExcRthN91/JsJmGkhYRO/OCBhHwwgxMWUxZQMIu8j8o7m5OfbRLMJh2kTYQ1FRhYVR2EHzCOtpMmEG3SUsJEvLoKAg2Prb2tqOJYSH+/uH33shwlTaS1hLBwhX6AYX2tuiRwStVntOa5yUZJwEqbREKjUi5Bjl5BhZW1uXWOMYGRBOnNDX1zfoxz1FwwkfSJfgo6vr41P7soBBNJ7Qm2YSzHTMzMwmEHRoMf0Fm5mYOUrzNBYAAAAASUVORK5CYII=');
}
.colorpicker {
top: 0;
left: 0;
z-index: 9999;
display: none;
}
.colorpicker colorpicker-hue,
.colorpicker colorpicker-alpha,
.colorpicker colorpicker-saturation {
position: relative;
}
.colorpicker input {
width: 100px;
font-size: 11px;
color: #000;
background-color: #fff;
}
.colorpicker.alpha {
min-width: 140px;
}
.colorpicker.alpha colorpicker-alpha {
display: block;
}
.colorpicker.dropdown {
position: absolute;
}
.colorpicker.colorpicker-fixed-position {
position: fixed;
}
.colorpicker .dropdown-menu::after,
.colorpicker .dropdown-menu::before {
content: '';
display: inline-block;
position: absolute;
}
.colorpicker .dropdown-menu::after {
clear: both;
border: 6px solid transparent;
top: -5px;
left: 7px;
}
.colorpicker .dropdown-menu::before {
border: 7px solid transparent;
top: -6px;
left: 6px;
}
.colorpicker .dropdown-menu {
position: static;
top: 0;
left: 0;
min-width: 129px;
padding: 4px;
margin-top: 0;
}
.colorpicker-position-top .dropdown-menu::after {
border-top: 6px solid #fff;
border-bottom: 0;
top: auto;
bottom: -5px;
}
.colorpicker-position-top .dropdown-menu::before {
border-top: 7px solid rgba(0, 0, 0, 0.2);
border-bottom: 0;
top: auto;
bottom: -6px;
}
.colorpicker-position-right .dropdown-menu::after {
border-right: 6px solid #fff;
border-left: 0;
top: 11px;
left: -5px;
}
.colorpicker-position-right .dropdown-menu::before {
border-right: 7px solid rgba(0, 0, 0, 0.2);
border-left: 0;
top: 10px;
left: -6px;
}
.colorpicker-position-bottom .dropdown-menu::after {
border-bottom: 6px solid #fff;
border-top: 0;
}
.colorpicker-position-bottom .dropdown-menu::before {
border-bottom: 7px solid rgba(0, 0, 0, 0.2);
border-top: 0;
}
.colorpicker-position-left .dropdown-menu::after {
border-left: 6px solid #fff;
border-right: 0;
top: 11px;
left: auto;
right: -5px;
}
.colorpicker-position-left .dropdown-menu::before {
border-left: 7px solid rgba(0, 0, 0, 0.2);
border-right: 0;
top: 10px;
left: auto;
right: -6px;
}
colorpicker-preview {
display: block;
height: 10px;
margin: 5px 0 3px 0;
clear: both;
background-position: 0 100%;
}
'use strict';
/*
Usage: <wysiwyg textarea-id="question" textarea-class="form-control" textarea-height="80px" textarea-name="textareaQuestion" textarea-required ng-model="question.question" enable-bootstrap-title="true"></wysiwyg>
options
textarea-id The id to assign to the editable div
textarea-class The class(es) to assign to the the editable div
textarea-height If not specified in a text-area class then the hight of the editable div (default: 80px)
textarea-name The name attribute of the editable div
textarea-required HTML/AngularJS required validation
ng-model The angular data model
enable-bootstrap-title True/False whether or not to show the button hover title styled with bootstrap
Requires:
Twitter-bootstrap, fontawesome, jquery, angularjs, bootstrap-color-picker (https://github.com/buberdds/angular-bootstrap-colorpicker)
*/
angular.module('wysiwyg.module', ['colorpicker.module'])
.directive('wysiwyg', function ($timeout) {
return {
template: '<div>' +
'<style>' +
' .wysiwyg-btn-group-margin{ margin-right:5px; }' +
' .wysiwyg-select{ height:30px;margin-bottom:1px;}' +
' .wysiwyg-colorpicker{ font-family: arial, sans-serif !important;font-size:16px !important; padding:2px 10px !important;}' +
'</style>' +
'<div class="btn-group btn-group-sm wysiwyg-btn-group-margin">' +
'<button title="Bold" tabindex="-1" type="button" unselectable="on" class="btn btn-default" ng-click="format(\'bold\')" ng-class="{ active: isBold}"><i class="fa fa-bold"></i></button>' +
'<button title="Italic" tabindex="-1" type="button" unselectable="on" class="btn btn-default" ng-click="format(\'italic\')" ng-class="{ active: isItalic}"><i class="fa fa-italic"></i></button>' +
'<button title="Underline" tabindex="-1" type="button" unselectable="on" class="btn btn-default" ng-click="format(\'underline\')" ng-class="{ active: isUnderlined}"><i class="fa fa-underline"></i></button>' +
'<button title="Strikethrough" tabindex="-1" type="button" unselectable="on" class="btn btn-default" ng-click="format(\'strikethrough\')" ng-class="{ active: isStrikethrough}"><i class="fa fa-strikethrough"></i></button>' +
'<button title="Subscript" tabindex="-1" type="button" unselectable="on" class="btn btn-default" ng-click="format(\'subscript\')" ng-class="{ active: isSubscript}"><i class="fa fa-subscript"></i></button>' +
'<button title="Superscript" tabindex="-1" type="button" unselectable="on" class="btn btn-default" ng-click="format(\'superscript\')" ng-class="{ active: isSuperscript}"><i class="fa fa-superscript"></i></button>' +
'</div>' +
'<div class="btn-group btn-group-sm wysiwyg-btn-group-margin">' +
'<select tabindex="-1" unselectable="on" class="form-control wysiwyg-select" ng-model="font" ng-options="f for f in fonts" ng-change="setFont()">' +
'</select>' +
'</div>' +
'<div class="btn-group btn-group-sm wysiwyg-btn-group-margin">' +
'<select unselectable="on" tabindex="-1" class="form-control wysiwyg-select" ng-model="fontSize" ng-options="f.size for f in fontSizes" ng-change="setFontSize()">' +
'</select>' +
'</div>' +
'<div class="btn-group btn-group-sm wysiwyg-btn-group-margin">' +
'<button title="Font Color" tabindex="-1" colorpicker="rgba" type="button" colorpicker-position="top" class="btn btn-default ng-valid ng-dirty wysiwyg-colorpicker wysiwyg-fontcolor" ng-model="fontColor" ng-change="setFontColor()">A</button>'+
'<button title="Hilite Color" tabindex="-1" colorpicker="rgba" type="button" colorpicker-position="top" class="btn btn-default ng-valid ng-dirty wysiwyg-colorpicker wysiwyg-hiliteColor" ng-model="hiliteColor" ng-change="setHiliteColor()">H</button>'+
'</div>' +
'<div class="btn-group btn-group-sm wysiwyg-btn-group-margin">' +
'<button title="Remove Formatting" tabindex="-1" type="button" unselectable="on" class="btn btn-default" ng-click="format(\'removeFormat\')" ><i class="fa fa-eraser"></i></button>' +
'</div>' +
'<div class="btn-group btn-group-sm wysiwyg-btn-group-margin">' +
'<button title="Ordered List" tabindex="-1" type="button" unselectable="on" class="btn btn-default" ng-click="format(\'insertorderedlist\')" ng-class="{ active: isOrderedList}"><i class="fa fa-list-ol"></i></button>' +
'<button title="Unordered List" tabindex="-1" type="button" unselectable="on" class="btn btn-default" ng-click="format(\'insertunorderedlist\')" ng-class="{ active: isUnorderedList}"><i class="fa fa-list-ul"></i></button>' +
'<button title="Outdent" tabindex="-1" type="button" unselectable="on" class="btn btn-default" ng-click="format(\'outdent\')"><i class="fa fa-outdent"></i></button>' +
'<button title="Indent" tabindex="-1" type="button" unselectable="on" class="btn btn-default" ng-click="format(\'indent\')"><i class="fa fa-indent"></i></button>' +
'</div>' +
'<div class="btn-group btn-group-sm wysiwyg-btn-group-margin">' +
'<button title="Left Justify" tabindex="-1" type="button" unselectable="on" class="btn btn-default" ng-click="format(\'justifyleft\')" ng-class="{ active: isLeftJustified}"><i class="fa fa-align-left"></i></button>' +
'<button title="Center Justify" tabindex="-1" type="button" unselectable="on" class="btn btn-default" ng-click="format(\'justifycenter\')" ng-class="{ active: isCenterJustified}"><i class="fa fa-align-center"></i></button>' +
'<button title="Right Justify" tabindex="-1" type="button" unselectable="on" class="btn btn-default" ng-click="format(\'justifyright\')" ng-class="{ active: isRightJustified}"><i class="fa fa-align-right"></i></button>' +
'</div>' +
'<div class="btn-group btn-group-sm wysiwyg-btn-group-margin">' +
'<button title="Code" tabindex="-1" type="button" unselectable="on" class="btn btn-default" ng-click="format(\'formatblock\', \'pre\')" ng-class="{ active: isPre}"><i class="fa fa-code"></i></button>' +
'<button title="Quote" tabindex="-1" type="button" unselectable="on" class="btn btn-default" ng-click="format(\'formatblock\', \'blockquote\')" ng-class="{ active: isBlockquote}"><i class="fa fa-quote-right"></i></button>' +
'<button title="Paragragh" tabindex="-1" type="button" unselectable="on" class="btn btn-default" ng-click="format(\'insertParagraph\')" ng-class="{ active: isParagraph}">P</button>' +
'</div>' +
'<div class="btn-group btn-group-sm wysiwyg-btn-group-margin" >' +
'<button ng-show="!isLink" tabindex="-1" title="Link" type="button" unselectable="on" class="btn btn-default" ng-click="createLink()"><i class="fa fa-link" ></i> </button>' +
'<button ng-show="isLink" tabindex="-1" title="Unlink" type="button" unselectable="on" class="btn btn-default" ng-click="format(\'unlink\')"><i class="fa fa-unlink"></i> </button>' +
'<button title="Image" tabindex="-1" type="button" unselectable="on" class="btn btn-default" ng-click="insertImage()"><i class="fa fa-picture-o"></i> </button>' +
'</div>' +
'<div id="{{textareaId}}" style="resize:vertical;height:{{textareaHeight || \'80px\'}}; overflow:auto" contentEditable="true" class="{{textareaClass}} wysiwyg-textarea" rows="{{textareaRows}}" name="{{textareaName}}" required="{{textareaRequired}}" placeholder="{{textareaPlaceholder}}" ng-model="value"></div>' +
'</div>',
restrict: 'E',
scope:{
value: '=ngModel',
textareaHeight: '@textareaHeight',
textareaName: '@textareaName',
textareaPlaceholder: '@textareaPlaceholder',
textareaClass: '@textareaClass',
textareaRequired: '@textareaRequired',
textareaId: '@textareaId',
},
replace: true,
require: 'ngModel',
link: function (scope, element, attrs, ngModelController) {
var textarea = element.find('div.wysiwyg-textarea');
scope.fonts = [
'Georgia',
'Palatino Linotype',
'Times New Roman',
'Arial',
'Helvetica',
'Arial Black',
'Comic Sans MS',
'Impact',
'Lucida Sans Unicode',
'Tahoma',
'Trebuchet MS',
'Verdana',
'Courier New',
'Lucida Console',
'Helvetica Neue'
].sort();
scope.font = scope.fonts[6];
scope.fontSizes = [
{
value:'1',
size:'10px'
},
{
value:'2',
size:'13px'
},
{
value:'3',
size:'16px'
},
{
value:'4',
size:'18px'
},
{
value:'5',
size:'24px'
},
{
value:'6',
size:'32px'
},
{
value:'7',
size:'48px'
}
];
scope.fontSize = scope.fontSizes[1];
if (attrs.enableBootstrapTitle === "true" && attrs.enableBootstrapTitle !== undefined)
element.find('button[title]').tooltip({container: 'body'})
textarea.on('keyup mouseup', function () {
scope.$apply(function readViewText() {
var html = textarea.html();
if (html == '<br>') {
html = '';
}
ngModelController.$setViewValue(html);
});
});
scope.isLink = false;
//Used to detect things like A tags and others that dont work with cmdValue().
function itemIs(tag){
var selection = window.getSelection().getRangeAt(0);
if(selection){
if (selection.startContainer.parentNode.tagName === tag.toUpperCase() || selection.endContainer.parentNode.tagName === tag.toUpperCase()) {
return true;
} else { return false; }
} else { return false; }
}
//Used to detect things like A tags and others that dont work with cmdValue().
function getHiliteColor(){
var selection = window.getSelection().getRangeAt(0);
if(selection){
var style = $(selection.startContainer.parentNode).attr('style');
if (!angular.isDefined(style))
return false;
var a = style.split(';');
for (var i=0; i<a.length;i++){
var s = a[i].split(':');
if (s[0] === 'background-color')
return s[1];
}
return '#fff';
} else
{
return '#fff';
}
}
textarea.on('click keyup focus mouseup', function(){
$timeout(function(){
scope.isBold = scope.cmdState('bold');
scope.isUnderlined = scope.cmdState('underline');
scope.isStrikethrough = scope.cmdState('strikethrough');
scope.isItalic = scope.cmdState('italic');
scope.isSuperscript = itemIs('SUP');//scope.cmdState('superscript');
scope.isSubscript = itemIs('SUB');//scope.cmdState('subscript');
scope.isRightJustified = scope.cmdState('justifyright');
scope.isLeftJustified = scope.cmdState('justifyleft');
scope.isCenterJustified = scope.cmdState('justifycenter');
scope.isPre = scope.cmdValue('formatblock') == "pre";
scope.isBlockquote = scope.cmdValue('formatblock') == "blockquote";
scope.isOrderedList = scope.cmdState('insertorderedlist');
scope.isUnorderedList = scope.cmdState('insertunorderedlist');
scope.fonts.forEach(function(v,k){ //works but kinda crappy.
if (scope.cmdValue('fontname').indexOf(v) > -1){
scope.font = v;
return false;
}
});
scope.fontSizes.forEach(function(v, k){
if (scope.cmdValue('fontsize') === v.value){
scope.fontSize = v;
return false;
}
})
scope.hiliteColor = getHiliteColor();
element.find('button.wysiwyg-hiliteColor').css("background-color", scope.hiliteColor);
scope.fontColor = scope.cmdValue('forecolor');
element.find('button.wysiwyg-fontcolor').css("color", scope.fontColor);
scope.isLink = itemIs('A');
}, 10);
});
// model -> view
ngModelController.$render = function () {
textarea.html(ngModelController.$viewValue);
};
scope.format = function(cmd, arg){
document.execCommand(cmd, false, arg);
}
scope.cmdState = function(cmd, id) {
return document.queryCommandState(cmd);
}
scope.cmdValue = function(cmd){
return document.queryCommandValue(cmd);
}
scope.createLink = function(){
var input = prompt('Enter the link URL');
if (input && input !== undefined)
scope.format('createlink', input);
}
scope.insertImage = function(){
var input = prompt('Enter the image URL');
if (input && input !== undefined)
scope.format('insertimage', input);
}
scope.setFont = function(){
scope.format('fontname', scope.font)
}
scope.setFontSize = function(){
scope.format('fontsize', scope.fontSize.value)
}
scope.setFontColor = function(){
scope.format('forecolor', scope.fontColor)
}
scope.setHiliteColor = function(){
scope.format('hiliteColor', scope.hiliteColor)
}
scope.format('enableobjectresizing', true);
scope.format('styleWithCSS', true);
}
};
});
'use strict';
angular.module('colorpicker.module', [])
.factory('Helper', function () {
return {
closestSlider: function (elem) {
var matchesSelector = elem.matches || elem.webkitMatchesSelector || elem.mozMatchesSelector || elem.msMatchesSelector;
if (matchesSelector.bind(elem)('I')) {
return elem.parentNode;
}
return elem;
},
getOffset: function (elem, fixedPosition) {
var
x = 0,
y = 0,
scrollX = 0,
scrollY = 0;
while (elem && !isNaN(elem.offsetLeft) && !isNaN(elem.offsetTop)) {
x += elem.offsetLeft;
y += elem.offsetTop;
if (!fixedPosition && elem.tagName === 'BODY') {
scrollX += document.documentElement.scrollLeft || elem.scrollLeft;
scrollY += document.documentElement.scrollTop || elem.scrollTop;
} else {
scrollX += elem.scrollLeft;
scrollY += elem.scrollTop;
}
elem = elem.offsetParent;
}
return {
top: y,
left: x,
scrollX: scrollX,
scrollY: scrollY
};
},
// a set of RE's that can match strings and generate color tuples. https://github.com/jquery/jquery-color/
stringParsers: [
{
re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,
parse: function (execResult) {
return [
execResult[1],
execResult[2],
execResult[3],
execResult[4]
];
}
},
{
re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,
parse: function (execResult) {
return [
2.55 * execResult[1],
2.55 * execResult[2],
2.55 * execResult[3],
execResult[4]
];
}
},
{
re: /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/,
parse: function (execResult) {
return [
parseInt(execResult[1], 16),
parseInt(execResult[2], 16),
parseInt(execResult[3], 16)
];
}
},
{
re: /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/,
parse: function (execResult) {
return [
parseInt(execResult[1] + execResult[1], 16),
parseInt(execResult[2] + execResult[2], 16),
parseInt(execResult[3] + execResult[3], 16)
];
}
}
]
};
})
.factory('Color', ['Helper', function (Helper) {
return {
value: {
h: 1,
s: 1,
b: 1,
a: 1
},
// translate a format from Color object to a string
'rgb': function () {
var rgb = this.toRGB();
return 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ')';
},
'rgba': function () {
var rgb = this.toRGB();
return 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + rgb.a + ')';
},
'hex': function () {
return this.toHex();
},
// HSBtoRGB from RaphaelJS
RGBtoHSB: function (r, g, b, a) {
r /= 255;
g /= 255;
b /= 255;
var H, S, V, C;
V = Math.max(r, g, b);
C = V - Math.min(r, g, b);
H = (C === 0 ? null :
V == r ? (g - b) / C :
V == g ? (b - r) / C + 2 :
(r - g) / C + 4
);
H = ((H + 360) % 6) * 60 / 360;
S = C === 0 ? 0 : C / V;
return {h: H || 1, s: S, b: V, a: a || 1};
},
//parse a string to HSB
setColor: function (val) {
val = val.toLowerCase();
for (var key in Helper.stringParsers) {
if (Helper.stringParsers.hasOwnProperty(key)) {
var parser = Helper.stringParsers[key];
var match = parser.re.exec(val),
values = match && parser.parse(match),
space = parser.space || 'rgba';
if (values) {
this.value = this.RGBtoHSB.apply(null, values);
return false;
}
}
}
},
setHue: function (h) {
this.value.h = 1 - h;
},
setSaturation: function (s) {
this.value.s = s;
},
setLightness: function (b) {
this.value.b = 1 - b;
},
setAlpha: function (a) {
this.value.a = parseInt((1 - a) * 100, 10) / 100;
},
// HSBtoRGB from RaphaelJS
// https://github.com/DmitryBaranovskiy/raphael/
toRGB: function (h, s, b, a) {
if (!h) {
h = this.value.h;
s = this.value.s;
b = this.value.b;
}
h *= 360;
var R, G, B, X, C;
h = (h % 360) / 60;
C = b * s;
X = C * (1 - Math.abs(h % 2 - 1));
R = G = B = b - C;
h = ~~h;
R += [C, X, 0, 0, X, C][h];
G += [X, C, C, X, 0, 0][h];
B += [0, 0, X, C, C, X][h];
return {
r: Math.round(R * 255),
g: Math.round(G * 255),
b: Math.round(B * 255),
a: a || this.value.a
};
},
toHex: function (h, s, b, a) {
var rgb = this.toRGB(h, s, b, a);
return '#' + ((1 << 24) | (parseInt(rgb.r, 10) << 16) | (parseInt(rgb.g, 10) << 8) | parseInt(rgb.b, 10)).toString(16).substr(1);
}
};
}])
.factory('Slider', ['Helper', function (Helper) {
var
slider = {
maxLeft: 0,
maxTop: 0,
callLeft: null,
callTop: null,
knob: {
top: 0,
left: 0
}
},
pointer = {};
return {
getSlider: function() {
return slider;
},
getLeftPosition: function(event) {
return Math.max(0, Math.min(slider.maxLeft, slider.left + ((event.pageX || pointer.left) - pointer.left)));
},
getTopPosition: function(event) {
return Math.max(0, Math.min(slider.maxTop, slider.top + ((event.pageY || pointer.top) - pointer.top)));
},
setSlider: function (event, fixedPosition) {
var
target = Helper.closestSlider(event.target),
targetOffset = Helper.getOffset(target, fixedPosition);
slider.knob = target.children[0].style;
slider.left = event.pageX - targetOffset.left - window.pageXOffset + targetOffset.scrollX;
slider.top = event.pageY - targetOffset.top - window.pageYOffset + targetOffset.scrollY;
pointer = {
left: event.pageX,
top: event.pageY
};
},
setSaturation: function(event, fixedPosition) {
slider = {
maxLeft: 100,
maxTop: 100,
callLeft: 'setSaturation',
callTop: 'setLightness'
};
this.setSlider(event, fixedPosition)
},
setHue: function(event, fixedPosition) {
slider = {
maxLeft: 0,
maxTop: 100,
callLeft: false,
callTop: 'setHue'
};
this.setSlider(event, fixedPosition)
},
setAlpha: function(event, fixedPosition) {
slider = {
maxLeft: 0,
maxTop: 100,
callLeft: false,
callTop: 'setAlpha'
};
this.setSlider(event, fixedPosition)
},
setKnob: function(top, left) {
slider.knob.top = top + 'px';
slider.knob.left = left + 'px';
}
};
}])
.directive('colorpicker', ['$document', '$compile', 'Color', 'Slider', 'Helper', function ($document, $compile, Color, Slider, Helper) {
return {
require: '?ngModel',
restrict: 'A',
link: function ($scope, elem, attrs, ngModel) {
var
thisFormat = attrs.colorpicker ? attrs.colorpicker : 'hex',
position = angular.isDefined(attrs.colorpickerPosition) ? attrs.colorpickerPosition : 'bottom',
fixedPosition = angular.isDefined(attrs.colorpickerFixedPosition) ? attrs.colorpickerFixedPosition : false,
target = angular.isDefined(attrs.colorpickerParent) ? elem.parent() : angular.element(document.body),
withInput = angular.isDefined(attrs.colorpickerWithInput) ? attrs.colorpickerWithInput : false,
inputTemplate = withInput ? '<input type="text" name="colorpicker-input">' : '',
template =
'<div class="colorpicker dropdown">' +
'<div class="dropdown-menu">' +
'<colorpicker-saturation><i></i></colorpicker-saturation>' +
'<colorpicker-hue><i></i></colorpicker-hue>' +
'<colorpicker-alpha><i></i></colorpicker-alpha>' +
'<colorpicker-preview></colorpicker-preview>' +
inputTemplate +
'<button class="close close-colorpicker">×</button>' +
'</div>' +
'</div>',
colorpickerTemplate = angular.element(template),
pickerColor = Color,
sliderAlpha,
sliderHue = colorpickerTemplate.find('colorpicker-hue'),
sliderSaturation = colorpickerTemplate.find('colorpicker-saturation'),
colorpickerPreview = colorpickerTemplate.find('colorpicker-preview'),
pickerColorPointers = colorpickerTemplate.find('i');
$compile(colorpickerTemplate)($scope);
if (withInput) {
var pickerColorInput = colorpickerTemplate.find('input');
pickerColorInput
.on('mousedown', function() {
event.stopPropagation();
})
.on('keyup', function(event) {
var newColor = this.value;
elem.val(newColor);
if(ngModel) {
$scope.$apply(ngModel.$setViewValue(newColor));
}
event.stopPropagation();
event.preventDefault();
});
elem.on('keyup', function() {
pickerColorInput.val(elem.val());
});
}
var bindMouseEvents = function() {
$document.on('mousemove', mousemove);
$document.on('mouseup', mouseup);
};
if (thisFormat === 'rgba') {
colorpickerTemplate.addClass('alpha');
sliderAlpha = colorpickerTemplate.find('colorpicker-alpha');
sliderAlpha
.on('click', function(event) {
Slider.setAlpha(event, fixedPosition);
mousemove(event);
})
.on('mousedown', function(event) {
Slider.setAlpha(event, fixedPosition);
bindMouseEvents();
});
}
sliderHue
.on('click', function(event) {
Slider.setHue(event, fixedPosition);
mousemove(event);
})
.on('mousedown', function(event) {
Slider.setHue(event, fixedPosition);
bindMouseEvents();
});
sliderSaturation
.on('click', function(event) {
Slider.setSaturation(event, fixedPosition);
mousemove(event);
})
.on('mousedown', function(event) {
Slider.setSaturation(event, fixedPosition);
bindMouseEvents();
});
if (fixedPosition) {
colorpickerTemplate.addClass('colorpicker-fixed-position');
}
colorpickerTemplate.addClass('colorpicker-position-' + position);
target.append(colorpickerTemplate);
if(ngModel) {
ngModel.$render = function () {
elem.val(ngModel.$viewValue);
};
$scope.$watch(attrs.ngModel, function() {
update();
});
}
elem.on('$destroy', function() {
colorpickerTemplate.remove();
});
var previewColor = function () {
try {
colorpickerPreview.css('backgroundColor', pickerColor[thisFormat]());
} catch (e) {
colorpickerPreview.css('backgroundColor', pickerColor.toHex());
}
sliderSaturation.css('backgroundColor', pickerColor.toHex(pickerColor.value.h, 1, 1, 1));
if (thisFormat === 'rgba') {
sliderAlpha.css.backgroundColor = pickerColor.toHex();
}
};
var mousemove = function (event) {
var
left = Slider.getLeftPosition(event),
top = Slider.getTopPosition(event),
slider = Slider.getSlider();
Slider.setKnob(top, left);
if (slider.callLeft) {
pickerColor[slider.callLeft].call(pickerColor, left / 100);
}
if (slider.callTop) {
pickerColor[slider.callTop].call(pickerColor, top / 100);
}
previewColor();
var newColor = pickerColor[thisFormat]();
elem.val(newColor);
if(ngModel) {
$scope.$apply(ngModel.$setViewValue(newColor));
}
if (withInput) {
pickerColorInput.val(newColor);
}
return false;
};
var mouseup = function () {
$document.off('mousemove', mousemove);
$document.off('mouseup', mouseup);
};
var update = function () {
pickerColor.setColor(elem.val());
pickerColorPointers.eq(0).css({
left: pickerColor.value.s * 100 + 'px',
top: 100 - pickerColor.value.b * 100 + 'px'
});
pickerColorPointers.eq(1).css('top', 100 * (1 - pickerColor.value.h) + 'px');
pickerColorPointers.eq(2).css('top', 100 * (1 - pickerColor.value.a) + 'px');
previewColor();
};
var getColorpickerTemplatePosition = function() {
var
positionValue,
positionOffset = Helper.getOffset(elem[0]);
if(angular.isDefined(attrs.colorpickerParent)) {
positionOffset.left = 0;
positionOffset.top = 0;
}
if (position === 'top') {
positionValue = {
'top': positionOffset.top - 147,
'left': positionOffset.left
};
} else if (position === 'right') {
positionValue = {
'top': positionOffset.top,
'left': positionOffset.left + 126
};
} else if (position === 'bottom') {
positionValue = {
'top': positionOffset.top + elem[0].offsetHeight + 2,
'left': positionOffset.left
};
} else if (position === 'left') {
positionValue = {
'top': positionOffset.top,
'left': positionOffset.left - 150
};
}
return {
'top': positionValue.top + 'px',
'left': positionValue.left + 'px'
};
};
var documentMousedownHandler = function() {
hideColorpickerTemplate();
};
elem.on('click', function () {
update();
colorpickerTemplate
.addClass('colorpicker-visible')
.css(getColorpickerTemplatePosition());
// register global mousedown event to hide the colorpicker
$document.on('mousedown', documentMousedownHandler);
});
colorpickerTemplate.on('mousedown', function (event) {
event.stopPropagation();
event.preventDefault();
});
var hideColorpickerTemplate = function() {
if (colorpickerTemplate.hasClass('colorpicker-visible')) {
colorpickerTemplate.removeClass('colorpicker-visible');
// unregister the global mousedown event
$document.off('mousedown', documentMousedownHandler);
}
};
colorpickerTemplate.find('button').on('click', function () {
hideColorpickerTemplate();
});
}
};
}]);