Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
// this global var is used to prevent multiple fires of the drop event. Needs to be global to the textAngular file.
var dropFired = false;
var textAngular = angular.module("textAngular", ['ngSanitize', 'textAngularSetup', 'textAngular.factories', 'textAngular.DOM', 'textAngular.validators', 'textAngular.taBind']); //This makes ngSanitize required
textAngular.config([function () {
// clear taTools variable. Just catches testing and any other time that this config may run multiple times...
angular.forEach(taTools, function (value, key) {
delete taTools[key];
});
}]);
textAngular.directive("textAngular", [
'$compile', '$timeout', 'taOptions', 'taSelection', 'taExecCommand',
'textAngularManager', '$document', '$animate', '$log', '$q', '$parse',
function ($compile, $timeout, taOptions, taSelection, taExecCommand,
textAngularManager, $document, $animate, $log, $q, $parse) {
return {
require: '?ngModel',
scope: {},
restrict: "EA",
priority: 2, // So we override validators correctly
link: function (scope, element, attrs, ngModel) {
// all these vars should not be accessable outside this directive
var _keydown, _keyup, _keypress, _mouseup, _focusin, _focusout,
_originalContents, _editorFunctions,
_serial = (attrs.serial) ? attrs.serial : Math.floor(Math.random() * 10000000000000000),
_taExecCommand, _resizeMouseDown, _updateSelectedStylesTimeout;
var _resizeTimeout;
scope._name = (attrs.name) ? attrs.name : 'textAngularEditor' + _serial;
var oneEvent = function (_element, event, action) {
$timeout(function () {
_element.one(event, action);
}, 100);
};
_taExecCommand = taExecCommand(attrs.taDefaultWrap);
// get the settings from the defaults and add our specific functions that need to be on the scope
angular.extend(scope, angular.copy(taOptions), {
// wraps the selection in the provided tag / execCommand function. Should only be called in WYSIWYG mode.
wrapSelection: function (command, opt, isSelectableElementTool) {
// we restore the
/* NOT FUNCTIONAL YET */
/* textAngularManager.restoreFocusSelection(scope._name, scope); */
if (command.toLowerCase() === "undo") {
scope['$undoTaBindtaTextElement' + _serial]();
} else if (command.toLowerCase() === "redo") {
scope['$redoTaBindtaTextElement' + _serial]();
} else {
// catch errors like FF erroring when you try to force an undo with nothing done
_taExecCommand(command, false, opt, scope.defaultTagAttributes);
if (isSelectableElementTool) {
// re-apply the selectable tool events
scope['reApplyOnSelectorHandlerstaTextElement' + _serial]();
}
// refocus on the shown display element, this fixes a display bug when using :focus styles to outline the box.
// You still have focus on the text/html input it just doesn't show up
scope.displayElements.text[0].focus();
}
},
showHtml: scope.$eval(attrs.taShowHtml) || false
});
// setup the options from the optional attributes
if (attrs.taFocussedClass) scope.classes.focussed = attrs.taFocussedClass;
if (attrs.taTextEditorClass) scope.classes.textEditor = attrs.taTextEditorClass;
if (attrs.taHtmlEditorClass) scope.classes.htmlEditor = attrs.taHtmlEditorClass;
if (attrs.taDefaultTagAttributes) {
try {
// TODO: This should use angular.merge to enhance functionality once angular 1.4 is required
angular.extend(scope.defaultTagAttributes, angular.fromJson(attrs.taDefaultTagAttributes));
} catch (error) {
$log.error(error);
}
}
// optional setup functions
if (attrs.taTextEditorSetup) scope.setup.textEditorSetup = scope.$parent.$eval(attrs.taTextEditorSetup);
if (attrs.taHtmlEditorSetup) scope.setup.htmlEditorSetup = scope.$parent.$eval(attrs.taHtmlEditorSetup);
// optional fileDropHandler function
if (attrs.taFileDrop) scope.fileDropHandler = scope.$parent.$eval(attrs.taFileDrop);
else scope.fileDropHandler = scope.defaultFileDropHandler;
_originalContents = element[0].innerHTML;
// clear the original content
element[0].innerHTML = '';
// Setup the HTML elements as variable references for use later
scope.displayElements = {
// we still need the hidden input even with a textarea as the textarea may have invalid/old input in it,
// wheras the input will ALLWAYS have the correct value.
forminput: angular.element(""),
html: angular.element(""),
text: angular.element(""),
// other toolbased elements
scrollWindow: angular.element(""),
popover: angular.element(''),
popoverArrow: angular.element(''),
popoverContainer: angular.element(''),
resize: {
overlay: angular.element(''),
background: angular.element(''),
anchors: [
angular.element(''),
angular.element(''),
angular.element(''),
angular.element('')
],
info: angular.element('')
}
};
// Setup the popover
scope.displayElements.popover.append(scope.displayElements.popoverArrow);
scope.displayElements.popover.append(scope.displayElements.popoverContainer);
scope.displayElements.scrollWindow.append(scope.displayElements.popover);
scope.displayElements.popover.on('mousedown', function (e, eventData) {
/* istanbul ignore else: this is for catching the jqLite testing*/
if (eventData) angular.extend(e, eventData);
// this prevents focusout from firing on the editor when clicking anything in the popover
e.preventDefault();
return false;
});
/* istanbul ignore next: popover resize and scroll events handled */
scope.handlePopoverEvents = function () {
if (scope.displayElements.popover.css('display') === 'block') {
if (_resizeTimeout) $timeout.cancel(_resizeTimeout);
_resizeTimeout = $timeout(function () {
//console.log('resize', scope.displayElements.popover.css('display'));
scope.reflowPopover(scope.resizeElement);
scope.reflowResizeOverlay(scope.resizeElement);
}, 100);
}
};
/* istanbul ignore next: browser resize check */
angular.element(window).on('resize', scope.handlePopoverEvents);
/* istanbul ignore next: browser scroll check */
angular.element(window).on('scroll', scope.handlePopoverEvents);
// we want to know if a given node has a scrollbar!
// credit to lotif on http://stackoverflow.com/questions/4880381/check-whether-html-element-has-scrollbars
var isScrollable = function (node) {
var cs;
var _notScrollable = {
vertical: false,
horizontal: false,
};
try {
cs = window.getComputedStyle(node);
if (cs === null) {
return _notScrollable;
}
} catch (e) {
/* istanbul ignore next: error handler */
return _notScrollable;
}
var overflowY = cs['overflow-y'];
var overflowX = cs['overflow-x'];
return {
vertical: (overflowY === 'scroll' || overflowY === 'auto') &&
/* istanbul ignore next: not tested */
node.scrollHeight > node.clientHeight,
horizontal: (overflowX === 'scroll' || overflowX === 'auto') &&
/* istanbul ignore next: not tested */
node.scrollWidth > node.clientWidth,
};
};
// getScrollTop
//
// we structure this so that it can climb the parents of the _el and when it finds
// one with scrollbars, it adds an EventListener, so that no matter how the
// DOM is structured in the user APP, if there is a scrollbar not as part of the
// ta-scroll-window, we will still capture the 'scroll' events...
// and handle the scroll event properly and do the resize, etc.
//
scope.getScrollTop = function (_el, bAddListener) {
var scrollTop = _el.scrollTop;
if (typeof scrollTop === 'undefined') {
scrollTop = 0;
}
/* istanbul ignore next: triggered only if has scrollbar */
if (bAddListener && isScrollable(_el).vertical) {
// remove element eventListener
_el.removeEventListener('scroll', scope._scrollListener, false);
_el.addEventListener('scroll', scope._scrollListener, false);
}
/* istanbul ignore next: triggered only if has scrollbar and scrolled */
if (scrollTop !== 0) {
return {node: _el.nodeName, top: scrollTop};
}
/* istanbul ignore else: catches only if no scroll */
if (_el.parentNode) {
return scope.getScrollTop(_el.parentNode, bAddListener);
} else {
return {node: '', top: 0};
}
};
// define the popover show and hide functions
scope.showPopover = function (_el) {
scope.getScrollTop(scope.displayElements.scrollWindow[0], true);
scope.displayElements.popover.css('display', 'block');
// we must use a $timeout here, or the css change to the
// displayElements.resize.overlay will not take!!!
// WHY???
$timeout(function () {
scope.displayElements.resize.overlay.css('display', 'block');
});
scope.resizeElement = _el;
scope.reflowPopover(_el);
$animate.addClass(scope.displayElements.popover, 'in');
oneEvent($document.find('body'), 'click keyup', function () {
scope.hidePopover();
});
};
/* istanbul ignore next: browser scroll event handler */
scope._scrollListener = function (e, eventData) {
scope.handlePopoverEvents();
};
scope.reflowPopover = function (_el) {
var scrollTop = scope.getScrollTop(scope.displayElements.scrollWindow[0], false);
var spaceAboveImage = _el[0].offsetTop - scrollTop.top;
//var spaceBelowImage = scope.displayElements.text[0].offsetHeight - _el[0].offsetHeight - spaceAboveImage;
//console.log(spaceAboveImage, spaceBelowImage);
/* istanbul ignore if: catches only if near bottom of editor */
if (spaceAboveImage < 51) {
scope.displayElements.popover.css('top', _el[0].offsetTop + _el[0].offsetHeight + scope.displayElements.scrollWindow[0].scrollTop + 'px');
scope.displayElements.popover.removeClass('top').addClass('bottom');
} else {
scope.displayElements.popover.css('top', _el[0].offsetTop - 54 + scope.displayElements.scrollWindow[0].scrollTop + 'px');
scope.displayElements.popover.removeClass('bottom').addClass('top');
}
var _maxLeft = scope.displayElements.text[0].offsetWidth - scope.displayElements.popover[0].offsetWidth;
var _targetLeft = _el[0].offsetLeft + (_el[0].offsetWidth / 2.0) - (scope.displayElements.popover[0].offsetWidth / 2.0);
var _rleft = Math.max(0, Math.min(_maxLeft, _targetLeft));
var _marginLeft = (Math.min(_targetLeft, (Math.max(0, _targetLeft - _maxLeft))) - 11);
_rleft += window.scrollX;
_marginLeft -= window.scrollX;
scope.displayElements.popover.css('left', _rleft + 'px');
scope.displayElements.popoverArrow.css('margin-left', _marginLeft + 'px');
};
scope.hidePopover = function () {
scope.displayElements.popover.css('display', 'none');
scope.displayElements.popoverContainer.attr('style', '');
scope.displayElements.popoverContainer.attr('class', 'popover-content');
scope.displayElements.popover.removeClass('in');
scope.displayElements.resize.overlay.css('display', 'none');
};
// setup the resize overlay
scope.displayElements.resize.overlay.append(scope.displayElements.resize.background);
angular.forEach(scope.displayElements.resize.anchors, function (anchor) {
scope.displayElements.resize.overlay.append(anchor);
});
scope.displayElements.resize.overlay.append(scope.displayElements.resize.info);
scope.displayElements.scrollWindow.append(scope.displayElements.resize.overlay);
// A click event on the resize.background will now shift the focus to the editor
/* istanbul ignore next: click on the resize.background to focus back to editor */
scope.displayElements.resize.background.on('click', function (e) {
scope.displayElements.text[0].focus();
});
// define the show and hide events
scope.reflowResizeOverlay = function (_el) {
_el = angular.element(_el)[0];
scope.displayElements.resize.overlay.css({
'display': 'block',
'left': _el.offsetLeft - 5 + 'px',
'top': _el.offsetTop - 5 + 'px',
'width': _el.offsetWidth + 10 + 'px',
'height': _el.offsetHeight + 10 + 'px'
});
scope.displayElements.resize.info.text(_el.offsetWidth + ' x ' + _el.offsetHeight);
};
/* istanbul ignore next: pretty sure phantomjs won't test this */
scope.showResizeOverlay = function (_el) {
var _body = $document.find('body');
_resizeMouseDown = function (event) {
var startPosition = {
width: parseInt(_el.attr('width')),
height: parseInt(_el.attr('height')),
x: event.clientX,
y: event.clientY
};
if (startPosition.width === undefined || isNaN(startPosition.width)) startPosition.width = _el[0].offsetWidth;
if (startPosition.height === undefined || isNaN(startPosition.height)) startPosition.height = _el[0].offsetHeight;
scope.hidePopover();
var ratio = startPosition.height / startPosition.width;
var mousemove = function (event) {
// calculate new size
var pos = {
x: Math.max(0, startPosition.width + (event.clientX - startPosition.x)),
y: Math.max(0, startPosition.height + (event.clientY - startPosition.y))
};
// DEFAULT: the aspect ratio is not locked unless the Shift key is pressed.
//
// attribute: ta-resize-force-aspect-ratio -- locks resize into maintaing the aspect ratio
var bForceAspectRatio = (attrs.taResizeForceAspectRatio !== undefined);
// attribute: ta-resize-maintain-aspect-ratio=true causes the space ratio to remain locked
// unless the Shift key is pressed
var bFlipKeyBinding = attrs.taResizeMaintainAspectRatio;
var bKeepRatio = bForceAspectRatio || (bFlipKeyBinding && !event.shiftKey);
if (bKeepRatio) {
var newRatio = pos.y / pos.x;
pos.x = ratio > newRatio ? pos.x : pos.y / ratio;
pos.y = ratio > newRatio ? pos.x * ratio : pos.y;
}
var el = angular.element(_el);
function roundedMaxVal(val) {
return Math.round(Math.max(0, val));
}
el.css('height', roundedMaxVal(pos.y) + 'px');
el.css('width', roundedMaxVal(pos.x) + 'px');
// reflow the popover tooltip
scope.reflowResizeOverlay(_el);
};
_body.on('mousemove', mousemove);
oneEvent(_body, 'mouseup', function (event) {
event.preventDefault();
event.stopPropagation();
_body.off('mousemove', mousemove);
// at this point, we need to force the model to update! since the css has changed!
// this fixes bug: #862 - we now hide the popover -- as this seems more consitent.
// there are still issues under firefox, the window does not repaint. -- not sure
// how best to resolve this, but clicking anywhere works.
scope.$apply(function () {
scope.hidePopover();
scope.updateTaBindtaTextElement();
}, 100);
});
event.stopPropagation();
event.preventDefault();
};
scope.displayElements.resize.anchors[3].off('mousedown');
scope.displayElements.resize.anchors[3].on('mousedown', _resizeMouseDown);
scope.reflowResizeOverlay(_el);
oneEvent(_body, 'click', function () {
scope.hideResizeOverlay();
});
};
/* istanbul ignore next: pretty sure phantomjs won't test this */
scope.hideResizeOverlay = function () {
scope.displayElements.resize.anchors[3].off('mousedown', _resizeMouseDown);
scope.displayElements.resize.overlay.css('display', 'none');
};
// allow for insertion of custom directives on the textarea and div
scope.setup.htmlEditorSetup(scope.displayElements.html);
scope.setup.textEditorSetup(scope.displayElements.text);
scope.displayElements.html.attr({
'id': 'taHtmlElement' + _serial,
'ng-show': 'showHtml',
'ta-bind': 'ta-bind',
'ng-model': 'html',
'ng-model-options': element.attr('ng-model-options')
});
scope.displayElements.text.attr({
'id': 'taTextElement' + _serial,
'contentEditable': 'true',
'ta-bind': 'ta-bind',
'ng-model': 'html',
'ng-model-options': element.attr('ng-model-options')
});
scope.displayElements.scrollWindow.attr({'ng-hide': 'showHtml'});
if (attrs.taDefaultWrap) {
// taDefaultWrap is only applied to the text and not the html view
scope.displayElements.text.attr('ta-default-wrap', attrs.taDefaultWrap);
}
if (attrs.taUnsafeSanitizer) {
scope.displayElements.text.attr('ta-unsafe-sanitizer', attrs.taUnsafeSanitizer);
scope.displayElements.html.attr('ta-unsafe-sanitizer', attrs.taUnsafeSanitizer);
}
if (attrs.taKeepStyles) {
scope.displayElements.text.attr('ta-keep-styles', attrs.taKeepStyles);
scope.displayElements.html.attr('ta-keep-styles', attrs.taKeepStyles);
}
// add the main elements to the origional element
scope.displayElements.scrollWindow.append(scope.displayElements.text);
element.append(scope.displayElements.scrollWindow);
element.append(scope.displayElements.html);
scope.displayElements.forminput.attr('name', scope._name);
element.append(scope.displayElements.forminput);
if (attrs.tabindex) {
element.removeAttr('tabindex');
scope.displayElements.text.attr('tabindex', attrs.tabindex);
scope.displayElements.html.attr('tabindex', attrs.tabindex);
}
if (attrs.placeholder) {
scope.displayElements.text.attr('placeholder', attrs.placeholder);
scope.displayElements.html.attr('placeholder', attrs.placeholder);
}
if (attrs.taDisabled) {
scope.displayElements.text.attr('ta-readonly', 'disabled');
scope.displayElements.html.attr('ta-readonly', 'disabled');
scope.disabled = scope.$parent.$eval(attrs.taDisabled);
scope.$parent.$watch(attrs.taDisabled, function (newVal) {
scope.disabled = newVal;
if (scope.disabled) {
element.addClass(scope.classes.disabled);
} else {
element.removeClass(scope.classes.disabled);
}
});
}
if (attrs.taPaste) {
scope._pasteHandler = function (_html) {
return $parse(attrs.taPaste)(scope.$parent, {$html: _html});
};
scope.displayElements.text.attr('ta-paste', '_pasteHandler($html)');
}
// compile the scope with the text and html elements only - if we do this with the main element it causes a compile loop
$compile(scope.displayElements.scrollWindow)(scope);
$compile(scope.displayElements.html)(scope);
scope.updateTaBindtaTextElement = scope['updateTaBindtaTextElement' + _serial];
scope.updateTaBindtaHtmlElement = scope['updateTaBindtaHtmlElement' + _serial];
// add the classes manually last
element.addClass("ta-root");
scope.displayElements.scrollWindow.addClass("ta-text ta-editor " + scope.classes.textEditor);
scope.displayElements.html.addClass("ta-html ta-editor " + scope.classes.htmlEditor);
var testAndSet = function (choice, beforeState) {
/* istanbul ignore next: this is only here because of a bug in rangy where rangy.saveSelection() has cleared the state */
if (beforeState !== $document[0].queryCommandState(choice)) {
$document[0].execCommand(choice, false, null);
}
};
// used in the toolbar actions
scope._actionRunning = false;
var _savedSelection = false;
scope.startAction = function () {
var _beforeStateBold = false;
var _beforeStateItalic = false;
var _beforeStateUnderline = false;
var _beforeStateStrikethough = false;
scope._actionRunning = true;
_beforeStateBold = $document[0].queryCommandState('bold');
_beforeStateItalic = $document[0].queryCommandState('italic');
_beforeStateUnderline = $document[0].queryCommandState('underline');
_beforeStateStrikethough = $document[0].queryCommandState('strikeThrough');
//console.log('B', _beforeStateBold, 'I', _beforeStateItalic, '_', _beforeStateUnderline, 'S', _beforeStateStrikethough);
// if rangy library is loaded return a function to reload the current selection
_savedSelection = rangy.saveSelection();
// rangy.saveSelection() clear the state of bold, italic, underline, strikethrough
// so we reset them here....!!!
// this fixes bugs #423, #1129, #1105, #693 which are actually rangy bugs!
testAndSet('bold', _beforeStateBold);
testAndSet('italic', _beforeStateItalic);
testAndSet('underline', _beforeStateUnderline);
testAndSet('strikeThrough', _beforeStateStrikethough);
//console.log('B', $document[0].queryCommandState('bold'), 'I', $document[0].queryCommandState('italic'), '_', $document[0].queryCommandState('underline'), 'S', $document[0].queryCommandState('strikeThrough') );
return function () {
if (_savedSelection) rangy.restoreSelection(_savedSelection);
// perhaps if we restore the selections here, we would do better overall???
// BUT what we do above does well in 90% of the cases...
};
};
scope.endAction = function () {
scope._actionRunning = false;
if (_savedSelection) {
if (scope.showHtml) {
scope.displayElements.html[0].focus();
} else {
scope.displayElements.text[0].focus();
}
// rangy.restoreSelection(_savedSelection);
rangy.removeMarkers(_savedSelection);
}
_savedSelection = false;
scope.updateSelectedStyles();
// only update if in text or WYSIWYG mode
if (!scope.showHtml) scope['updateTaBindtaTextElement' + _serial]();
};
// note that focusout > focusin is called everytime we click a button - except bad support: http://www.quirksmode.org/dom/events/blurfocus.html
// cascades to displayElements.text and displayElements.html automatically.
_focusin = function (e) {
scope.focussed = true;
element.addClass(scope.classes.focussed);
/******* NOT FUNCTIONAL YET
if (e.target.id === 'taTextElement' + _serial) {
console.log('_focusin taTextElement');
// we only do this if NOT focussed
textAngularManager.restoreFocusSelection(scope._name);
}
*******/
_editorFunctions.focus();
element.triggerHandler('focus');
// we call editorScope.updateSelectedStyles() here because we want the toolbar to be focussed
// as soon as we have focus. Otherwise this only happens on mousedown or keydown etc...
/* istanbul ignore else: don't run if already running */
if (scope.updateSelectedStyles && !scope._bUpdateSelectedStyles) {
// we don't set editorScope._bUpdateSelectedStyles here, because we do not want the
// updateSelectedStyles() to run twice which it will do after 200 msec if we have
// set editorScope._bUpdateSelectedStyles
//
// WOW, normally I would do a scope.$apply here, but this causes ERRORs when doing tests!
$timeout(function () {
scope.updateSelectedStyles();
}, 0);
}
};
scope.displayElements.html.on('focus', _focusin);
scope.displayElements.text.on('focus', _focusin);
_focusout = function (e) {
/****************** NOT FUNCTIONAL YET
try {
var _s = rangy.getSelection();
if (_s) {
// we save the selection when we loose focus so that if do a wrapSelection, the
// apropriate selection in the editor is restored before action.
var _savedFocusRange = rangy.saveRange(_s.getRangeAt(0));
textAngularManager.saveFocusSelection(scope._name, _savedFocusRange);
}
} catch(error) { }
*****************/
// if we are NOT runnig an action and have NOT focussed again on the text etc then fire the blur events
if (!scope._actionRunning &&
$document[0].activeElement !== scope.displayElements.html[0] &&
$document[0].activeElement !== scope.displayElements.text[0]) {
element.removeClass(scope.classes.focussed);
_editorFunctions.unfocus();
// to prevent multiple apply error defer to next seems to work.
$timeout(function () {
scope._bUpdateSelectedStyles = false;
element.triggerHandler('blur');
scope.focussed = false;
}, 0);
}
e.preventDefault();
return false;
};
scope.displayElements.html.on('blur', _focusout);
scope.displayElements.text.on('blur', _focusout);
scope.displayElements.text.on('paste', function (event) {
element.triggerHandler('paste', event);
});
// Setup the default toolbar tools, this way allows the user to add new tools like plugins.
// This is on the editor for future proofing if we find a better way to do this.
scope.queryFormatBlockState = function (command) {
// $document[0].queryCommandValue('formatBlock') errors in Firefox if we call this when focussed on the textarea
return !scope.showHtml && command.toLowerCase() === $document[0].queryCommandValue('formatBlock').toLowerCase();
};
scope.queryCommandState = function (command) {
// $document[0].queryCommandValue('formatBlock') errors in Firefox if we call this when focussed on the textarea
return (!scope.showHtml) ? $document[0].queryCommandState(command) : '';
};
scope.switchView = function () {
scope.showHtml = !scope.showHtml;
$animate.enabled(false, scope.displayElements.html);
$animate.enabled(false, scope.displayElements.text);
//Show the HTML view
/* istanbul ignore next: ngModel exists check */
/* THIS is not the correct thing to do, here....
The ngModel is correct, but it is not formatted as the user as done it...
var _model;
if (ngModel) {
_model = ngModel.$viewValue;
} else {
_model = scope.html;
}
var _html = scope.displayElements.html[0].value;
if (getDomFromHtml(_html).childElementCount !== getDomFromHtml(_model).childElementCount) {
// the model and the html do not agree
// they can get out of sync and when they do, we correct that here...
scope.displayElements.html.val(_model);
}
*/
if (scope.showHtml) {
//defer until the element is visible
$timeout(function () {
$animate.enabled(true, scope.displayElements.html);
$animate.enabled(true, scope.displayElements.text);
// [0] dereferences the DOM object from the angular.element
return scope.displayElements.html[0].focus();
}, 100);
} else {
//Show the WYSIWYG view
//defer until the element is visible
$timeout(function () {
$animate.enabled(true, scope.displayElements.html);
$animate.enabled(true, scope.displayElements.text);
// [0] dereferences the DOM object from the angular.element
return scope.displayElements.text[0].focus();
}, 100);
}
};
// changes to the model variable from outside the html/text inputs
// if no ngModel, then the only input is from inside text-angular
if (attrs.ngModel) {
var _firstRun = true;
ngModel.$render = function () {
if (_firstRun) {
// we need this firstRun to set the originalContents otherwise it gets overrided by the setting of ngModel to undefined from NaN
_firstRun = false;
// if view value is null or undefined initially and there was original content, set to the original content
var _initialValue = scope.$parent.$eval(attrs.ngModel);
if ((_initialValue === undefined || _initialValue === null) && (_originalContents && _originalContents !== '')) {
// on passing through to taBind it will be sanitised
ngModel.$setViewValue(_originalContents);
}
}
scope.displayElements.forminput.val(ngModel.$viewValue);
// if the editors aren't focused they need to be updated, otherwise they are doing the updating
scope.html = ngModel.$viewValue || '';
};
// trigger the validation calls
if (element.attr('required')) ngModel.$validators.required = function (modelValue, viewValue) {
var value = modelValue || viewValue;
return !(!value || value.trim() === '');
};
} else {
// if no ngModel then update from the contents of the origional html.
scope.displayElements.forminput.val(_originalContents);
scope.html = _originalContents;
}
// changes from taBind back up to here
scope.$watch('html', function (newValue, oldValue) {
if (newValue !== oldValue) {
if (attrs.ngModel && ngModel.$viewValue !== newValue) {
ngModel.$setViewValue(newValue);
}
scope.displayElements.forminput.val(newValue);
}
});
if (attrs.taTargetToolbars) {
_editorFunctions = textAngularManager.registerEditor(scope._name, scope, attrs.taTargetToolbars.split(','));
}
else {
var _toolbar = angular.element('