I have some anchor tags where I'm using an angular directive to decorate (underline) the text(to indicate a keyboard shortcut). So far my code only works if the specified character (for "amt-alt-key") is at the beginning of the first word.
What I need to do is search the whole string and underline the first occurrence of the specified character. So right now if I specified an amt-alt-key="A" in the example below it would work fine as is. However, the problem is the first occurrence could be anywhere in the anchor text. Any help with writing the correct JavaScript would be greatly appreciated.
--Jason
In my html
Agent Data
In my angular code
app.directive("amtAltKey", function () {
return {
link: function (scope, elem, attrs) {
var altKey = attrs.amtAltKey.toUpperCase();
var text = el.innerText;
var textUpper = text.toUpperCase();
var indexOfKey = textUpper.indexOf(altKey);
var newText = text.substring(0, indexOfKey);
newText += '<u>' + text.substring(indexOfKey, 1) + '</u>';
if (indexOfKey + 1 < text.length) { newText += text.substring(indexOfKey + 1); }
el.innerHTML = newText;
keyListeners[altKey] = el;
}
};
});
You can use Regular expression to check the required pattern and a replaceText utility function to replace the matched pattern and once you have the text, replace the existing content of the element as below:
.directive('amtAltKey', function () {
return {
link: function (scope, elem, attrs) {
var altKey = new RegExp(attrs.amtAltKey, 'i');
var text = elem.text();
function replaceText (txt) {
function underline(match) {
return '<u>' + match +'</u>';
}
return txt.replace(altKey, underline);
}
var newText = replaceText(text);
elem.html(newText);
}
};
});
Here is a working example: https://jsbin.com/zefodo/2/edit?html,js,console,output
Use this:
app.directive("amtAltKey", function () {
return {
link: function (scope, elem, attrs) {
var altKey = attrs.amtAltKey;
var text = elem.innerText;
elem.innerHTML = text.replace(new RegExp(altKey, 'i'), '<u>$&</u>');
keyListeners[altKey] = elem;
}
};
});
Try this
app.directive("amtAltKey", function() {
return {
link: function(scope, elem, attrs) {
var el = elem[0];
var altKey = "" + attrs.amtAltKey.toUpperCase();
var text = el.innerText;
var textUpper = text.toUpperCase();
var indexOfKey = textUpper.indexOf(altKey);
if (indexOfKey > -1) {
var newText = text.substr(0, indexOfKey);
newText += '<u>' + text.substr(indexOfKey, 1) + '</u>';
if (indexOfKey + 1 < text.length) {
newText += text.substr(indexOfKey + 1);
}
el.innerHTML = newText;
keyListeners[altKey] = el;
}
}
};
});
Related
I'm using the directive for displaying money in proper format from this url http://jsfiddle.net/ExpertSystem/xKrYp/
Does anyone know how can I remove the $ symbol from the output value and have only the actual amount converted?
Tried few different things but I was not able to accomplish what I was looking for.
Here is the directive as well:
app.directive('realTimeCurrency', function ($filter, $locale) {
var decimalSep = $locale.NUMBER_FORMATS.DECIMAL_SEP;
var toNumberRegex = new RegExp('[^0-9\\' + decimalSep + ']', 'g');
var trailingZerosRegex = new RegExp('\\' + decimalSep + '0+$');
var filterFunc = function (value) {
return $filter('currency')(value);
};
function getCaretPosition(input){
if (!input) return 0;
if (input.selectionStart !== undefined) {
return input.selectionStart;
} else if (document.selection) {
// Curse you IE
input.focus();
var selection = document.selection.createRange();
selection.moveStart('character', input.value ? -input.value.length : 0);
return selection.text.length;
}
return 0;
}
function setCaretPosition(input, pos){
if (!input) return 0;
if (input.offsetWidth === 0 || input.offsetHeight === 0) {
return; // Input's hidden
}
if (input.setSelectionRange) {
input.focus();
input.setSelectionRange(pos, pos);
}
else if (input.createTextRange) {
// Curse you IE
var range = input.createTextRange();
range.collapse(true);
range.moveEnd('character', pos);
range.moveStart('character', pos);
range.select();
}
}
function toNumber(currencyStr) {
return parseFloat(currencyStr.replace(toNumberRegex, ''), 10);
}
return {
restrict: 'A',
require: 'ngModel',
link: function postLink(scope, elem, attrs, modelCtrl) {
modelCtrl.$formatters.push(filterFunc);
modelCtrl.$parsers.push(function (newViewValue) {
var oldModelValue = modelCtrl.$modelValue;
var newModelValue = toNumber(newViewValue);
modelCtrl.$viewValue = filterFunc(newModelValue);
var pos = getCaretPosition(elem[0]);
elem.val(modelCtrl.$viewValue);
var newPos = pos + modelCtrl.$viewValue.length -
newViewValue.length;
if ((oldModelValue === undefined) || isNaN(oldModelValue)) {
newPos -= 3;
}
setCaretPosition(elem[0], newPos);
return newModelValue;
});
}
};
});
Try https://github.com/bcherny/format-as-currency :)
Or if you want to use your own version, pass an empty string as a 2nd argument to the currency filter. See https://docs.angularjs.org/api/ng/filter/currency
I have a directive that convert to 2 decimal places (input number textbox) and
it is working well but I would like to pad the whole number with zeros.
Anyone knows how I can achieve whole number with padded zeros? Example below.
2 -> 2.00 - not done
10.666 -> 10.67 - done
app.directive('toPrecision',['$filter', function ($filter) {
return {
replace: false,
restrict: 'A',
link: function(scope, element, attr) {
var input = angular.element(element);
var precisionValue = attr.toPrecision;
input.on('keyup', function() {
var parsed = parseFloat(input.val());
if (!isNaN(parsed)) {
if (parseInt(parsed, 10) !== parsed && String(parsed).split('.')[1].length > 2) {
var result = parsed.toFixed(precisionValue);
input.val(result);
}
}
});
}
}
}]);
HTML
<input type="number" class="form-control" ng-model="rate" to-precision="2" min="0" step="1">
Ended rewriting the directives for what I want.
app.directive('toPrecision', function() {
return {
replace: false,
restrict: 'EA',
require: 'ngModel',
link: function(scope, element, attr, ngModelCtrl) {
var input = angular.element(element);
var precisionValue = attr.toPrecision;
ngModelCtrl.$parsers.push(function(value) {
var clean = value.replace(/[^-0-9\.]/g, '');
if (value !== clean) {
ngModelCtrl.$setViewValue(clean);
ngModelCtrl.$render();
}
return clean;
});
ngModelCtrl.$formatters.push(function(value) {
if (angular.isUndefined(value)) {
return "";
}
var parsed = parseFloat(value);
return parsed.toFixed(precisionValue);
});
input.on('blur', function() {
var parsed = parseFloat(input.val());
if (!isNaN(parsed)) {
var result = parsed.toFixed(precisionValue);
input.val(result);
ngModelCtrl.$setViewValue(result);
}
});
}
}});
Here's code to do it taken from the answer I voted to close as duplicate:
var s = number.toString();
if (s.indexOf('.') == -1) s += '.';
while (s.length < s.indexOf('.') + 4) s += '0';
Let math do it, Try this.
if (!isNaN(parsed)) {
parsed += Math.pow(10, -1 * (precisionValue + 2)); // 2 + 0.0001
var result = parsed.toFixed(precisionValue); // 2.00
input.val(result);
}
See fiddle
I have created a access key Angular directive.
angular.module('tcne.common').directive("accessKey", function () {
return {
restrict: "A",
scope: {
},
link: function (scope, element, attrs) {
var $element = $(element);
$element.attr("accesskey", attrs.accessKey);
var content = $element.html();
for (var i = 0; i < content.length; i++) {
var char = content[i];
if (char.toLowerCase() === attrs.accessKey.toLowerCase()) {
content = content.substr(0, i) + "<u>" + char + "</u>" + content.substr(i + 1);
break;
}
}
$element.html(content);
},
replace: false
};
});
It underscores the access key in the button Label and adds the access key attribute to the element. Can I somehow prevent the accesskey from setting the button in focus? It kills the purpose of keyboard short cuts
edit: Rolled my own acccess key
angular.module('tcne.common').directive('accessKey', ['$compile', '$interval', function ($compile, $interval) {
var modifierPressed = false;
$("body").keyup(function (e) {
if (modifierPressed && !e.altKey) {
modifierPressed = false;
digestScopes();
}
});
$("body").keydown(function (e) {
modifierPressed = e.altKey;
if (modifierPressed && scopes.hasOwnProperty(String.fromCharCode(e.which).toLowerCase())) {
var scope = scopes[String.fromCharCode(e.which).toLowerCase()];
scope.handle();
return;
}
if (modifierPressed) {
e.preventDefault();
digestScopes();
}
});
function digestScopes() {
for (var index in scopes) {
if (scopes.hasOwnProperty(index)) {
var scope = scopes[index]
scope.$digest();
}
}
}
function isModifierPressed() {
return modifierPressed;
}
var scopes = {};
return {
restrict: 'A',
scope: {
},
link: function (scope, element, attrs) {
var key = attrs.accessKey.toLowerCase();
var content = element.html();
var char;
for (var i = 0; i < content.length; i++) {
char = content[i];
if (char.toLowerCase() === key) {
content = content.substr(0, i) + '<u><strong ng-if="highlight()">{{char}}</strong><span ng-if="!highlight()">{{char}}</span></u>' + content.substr(i + 1);
break;
}
}
element.html(content);
var underscoreScope = scope.$new();
underscoreScope.char = char;
underscoreScope.highlight = isModifierPressed;
underscoreScope.handle = element.click.bind(element);
scopes[key] = underscoreScope;
scope.$on('$destroy', function () {
delete scopes[key];
});
$compile(element.find("u"))(underscoreScope);
},
replace: false
};
}]);
It also highlights the access key button when alt key is pressed which is nice
Any pit falls with this code? Thanks
Found a pitfall, element.html and then $compile will break any directives inside the element that is already compiled. So I changed to
var captionElement = element.contents().first(":text");
var content = captionElement.text();
And then I add my custom content like
var view = $("<span>").html(content);
captionElement.replaceWith(view);
$compile(view)(vm);
Please let me know if this is considered bad practice
Using regex to link #something and #something in a string. In both cases it's working fine. Trying to use same method to link /something, but it breaks everything.
Works for # and #
http://jsbin.com/todomuca/3/edit
Doesn't work for / due to link 44:
http://jsbin.com/todomuca/4/edit
Any idea why it's breaking?
<!DOCTYPE html>
<html ng-app="app">
<head>
<title>Title</title>
</head>
<body>
<p linkify="twitter">some #username and #hashtag and now trying /test</p>
<!-- javascript -->
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.min.js"></script>
<script>
angular.module('linkify', []);
angular.module('linkify')
.filter('linkify', function () {
'use strict';
function linkify (_str, type) {
if (!_str) {
return;
}
var _text = _str.replace( /(?:https?\:\/\/|www\.)+(?![^\s]*?")([\w.,#?^=%&:\/~+#-]*[\w#?^=%&\/~+#-])?/ig, function(url) {
var wrap = document.createElement('div');
var anch = document.createElement('a');
anch.href = url;
anch.target = "_blank";
anch.innerHTML = url;
wrap.appendChild(anch);
return wrap.innerHTML;
});
// bugfix
if (!_text) {
return '';
}
// Twitter
if (type === 'twitter') {
_text = _text.replace(/(|\s)*#(\w+)/g, '$1#$2');
_text = _text.replace(/(^|\s)*#(\w+)/g, '$1#$2');
_text = _text.replace(/(^|\s)*\/(\w+)/g, '$1#$2');
}
// Github
if (type === 'github') {
_text = _text.replace(/(|\s)*#(\w+)/g, '$1#$2');
}
return _text;
}
//
return function (text, type) {
return linkify(text, type);
};
})
.factory('linkify', ['$filter', function ($filter) {
'use strict';
function _linkifyAsType (type) {
return function (str) {(type, str);
return $filter('linkify')(str, type);
};
}
return {
twitter: _linkifyAsType('twitter'),
github: _linkifyAsType('github'),
normal: _linkifyAsType()
};
}])
.directive('linkify', ['$filter', '$timeout', 'linkify', function ($filter, $timeout, linkify) {
'use strict';
return {
restrict: 'A',
link: function (scope, element, attrs) {
var type = attrs.linkify || 'normal';
$timeout(function () { element.html(linkify[type](element.html())); });
}
};
}]);
var app = angular.module("app", ['linkify']);
</script>
</body>
</html>
You’re running replacements on raw HTML, which, after earlier replacements, include </a>, which matches your /-matching regular expression. You can try matching them all at the same time:
var TWITTER_LINK = /(?:^|\s)([##/])(\w+)/g;
var html = text.replace(TWITTER_LINK, function (match, type, name) {
var url = {
'#': 'https://twitter.com/' + name,
'#': 'https://twitter.com/search?q=%23' + name,
'/': 'https://twitter.com/search?q=' + name
}[type];
return '' + match + '';
});
That’s still not really pleasant, though; I would use the DOM.
var TWITTER_LINK = /(?:^|\s)([##/])(\w+)/g;
var result = document.createDocumentFragment();
var lastIndex = 0;
var match;
while ((match = TWITTER_LINK.exec(text)) {
result.appendChild(document.createTextNode(text.substring(lastIndex, match.index)));
lastIndex = match.index;
var type = match[1];
var name = match[2];
var url = {
'#': 'https://twitter.com/' + encodeURIComponent(name),
'#': 'https://twitter.com/search?q=%23' + encodeURIComponent(name),
'/': 'https://twitter.com/search?q=' + encodeURIComponent(name)
}[type];
var link = document.createElement('a');
link.href = url;
link.appendChild(document.createTextNode(match[0]));
result.appendChild(link);
}
result.appendChild(document.createTextNode(text.substring(lastIndex)));
How would I create a directive in angularjs that for example takes this element:
<div>Example text http://example.com</div>
And convert it in to this
<div>Example text http://example.com</div>
I already have the functionality written to auto link the text in a function and return the html (let's call the function "autoLink" ) but i'm not up to scratch on my directives.
I would also like to add a attribute to the element to pass a object in to the directive. e.g.
<div linkprops="link.props" >Example text http://example.com</div>
Where link.props is object like {a: 'bla bla', b: 'waa waa'} which is to be passed to the autoLink function as a second param (the first been the text).
Two ways of doing it:
Directive
app.directive('parseUrl', function () {
var urlPattern = /(http|ftp|https):\/\/[\w-]+(\.[\w-]+)+([\w.,#?^=%&:\/~+#-]*[\w#?^=%&\/~+#-])?/gi;
return {
restrict: 'A',
require: 'ngModel',
replace: true,
scope: {
props: '=parseUrl',
ngModel: '=ngModel'
},
link: function compile(scope, element, attrs, controller) {
scope.$watch('ngModel', function (value) {
var html = value.replace(urlPattern, '<a target="' + scope.props.target + '" href="$&">$&</a>') + " | " + scope.props.otherProp;
element.html(html);
});
}
};
});
HTML:
<p parse-url="props" ng-model="text"></p>
Filter
app.filter('parseUrlFilter', function () {
var urlPattern = /(http|ftp|https):\/\/[\w-]+(\.[\w-]+)+([\w.,#?^=%&:\/~+#-]*[\w#?^=%&\/~+#-])?/gi;
return function (text, target, otherProp) {
return text.replace(urlPattern, '<a target="' + target + '" href="$&">$&</a>') + " | " + otherProp;
};
});
HTML:
<p ng-bind-html-unsafe="text | parseUrlFilter:'_blank':'otherProperty'"></p>
Note: The 'otherProperty' is just for example, in case you want to pass more properties into the filter.
jsFiddle
Update: Improved replacing algorithm.
To answer the first half of this question, without the additional property requirement, one can use Angular's linky filter: https://docs.angularjs.org/api/ngSanitize/filter/linky
The top voted answer does not work if there are multiple links. Linky already does 90% of the work for us, the only problem is that it sanitizes the html thus removing html/newlines. My solution was to just edit the linky filter (below is Angular 1.2.19) to not sanitize the input.
app.filter('linkyUnsanitized', ['$sanitize', function($sanitize) {
var LINKY_URL_REGEXP =
/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+#)\S*[^\s.;,(){}<>]/,
MAILTO_REGEXP = /^mailto:/;
return function(text, target) {
if (!text) return text;
var match;
var raw = text;
var html = [];
var url;
var i;
while ((match = raw.match(LINKY_URL_REGEXP))) {
// We can not end in these as they are sometimes found at the end of the sentence
url = match[0];
// if we did not match ftp/http/mailto then assume mailto
if (match[2] == match[3]) url = 'mailto:' + url;
i = match.index;
addText(raw.substr(0, i));
addLink(url, match[0].replace(MAILTO_REGEXP, ''));
raw = raw.substring(i + match[0].length);
}
addText(raw);
return html.join('');
function addText(text) {
if (!text) {
return;
}
html.push(text);
}
function addLink(url, text) {
html.push('<a ');
if (angular.isDefined(target)) {
html.push('target="');
html.push(target);
html.push('" ');
}
html.push('href="');
html.push(url);
html.push('">');
addText(text);
html.push('</a>');
}
};
}]);
I wanted a pause button that swaps text. here is how I did it:
in CSS:
.playpause.paused .pause, .playpause .play { display:none; }
.playpause.paused .play { display:inline; }
in template:
<button class="playpause" ng-class="{paused:paused}" ng-click="paused = !paused">
<span class="play">play</span><span class="pause">pause</span>
</button>
Inspired by #Neal I made this "no sanitize" filter from the newer Angular 1.5.8. It also recognizes addresses without ftp|http(s) but starting with www. This means that both https://google.com and www.google.com will be linkyfied.
angular.module('filter.parselinks',[])
.filter('parseLinks', ParseLinks);
function ParseLinks() {
var LINKY_URL_REGEXP =
/((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+#)\S*[^\s.;,(){}<>"\u201d\u2019]/i,
MAILTO_REGEXP = /^mailto:/i;
var isDefined = angular.isDefined;
var isFunction = angular.isFunction;
var isObject = angular.isObject;
var isString = angular.isString;
return function(text, target, attributes) {
if (text == null || text === '') return text;
if (typeof text !== 'string') return text;
var attributesFn =
isFunction(attributes) ? attributes :
isObject(attributes) ? function getAttributesObject() {return attributes;} :
function getEmptyAttributesObject() {return {};};
var match;
var raw = text;
var html = [];
var url;
var i;
while ((match = raw.match(LINKY_URL_REGEXP))) {
// We can not end in these as they are sometimes found at the end of the sentence
url = match[0];
// if we did not match ftp/http/www/mailto then assume mailto
if (!match[2] && !match[4]) {
url = (match[3] ? 'http://' : 'mailto:') + url;
}
i = match.index;
addText(raw.substr(0, i));
addLink(url, match[0].replace(MAILTO_REGEXP, ''));
raw = raw.substring(i + match[0].length);
}
addText(raw);
return html.join('');
function addText(text) {
if (!text) {
return;
}
html.push(text);
}
function addLink(url, text) {
var key, linkAttributes = attributesFn(url);
html.push('<a ');
for (key in linkAttributes) {
html.push(key + '="' + linkAttributes[key] + '" ');
}
if (isDefined(target) && !('target' in linkAttributes)) {
html.push('target="',
target,
'" ');
}
html.push('href="',
url.replace(/"/g, '"'),
'">');
addText(text);
html.push('</a>');
}
};
}
I would analyze the text in the link function on the directive:
directive("myDirective", function(){
return {
restrict: "A",
link: function(scope, element, attrs){
// use the 'element' to manipulate it's contents...
}
}
});