angularjs regex filter syntax highlighter - javascript

please take a look at this plunker: http://plnkr.co/edit/OIFu07GS0ZJQOdmpDnXB?p=preview
i am trying to create a syntax highlighter with angular. I have the filter working 80%. The regex is correct but the .replace() it "writes" the html as text on the pre and not rendering it as html.
take a look and you will understand what I am trying to do.
I know there are codemirror and ace directives but they are just too big for what I need.
anyone know how to fix it?
the correct output should be something like this:
<pre>
<span class="string">1</span>
<span class="string">2</span>
<span class="string">3</span>
#This is a mysql syntax highlighter
-- This is a comment/*
And this is a multi line comment
SELECT things FROM table;*/
</pre>
currently everything between pre is rendered as text.
any ideas?
thanx

I don't think you can do this with a filter, try a directive.
Below is an example, fairly straightforward. I first changed the filter to a service (though you can use the filter similarly with $filter if you want but I don't see why). Then I use $interpolate to create an interpolation function, described here:
Compiles a string with markup into an interpolation function. This service is used by the HTML $compile service for data binding. See $interpolateProvider for configuring the interpolation markup.
You can see in the example that the strings '1', '2' and '3' are highlighted because I added style="color:red", and they have the class as well.
Edit: edited solution with usage of ngModelController to make changes to the textarea appear in the element below with angular's data binding. Note the change to the snippet element: <snippet ng-model="txt">
var app = angular.module('plunker', ['syntax']);
app.controller('MainCtrl', function($scope) {
$scope.txt = "1 2 3 \n #This is a mysql syntax highlighter\n-- This is a comment/*\nAnd this is a multi line comment\nSELECT things FROM table;*/\nSELECT \nag.name AS agent, `d.date`, d.first_payment_date, d.account_number, ";
});
angular.module('syntax', [])
.service('formatter', function () {
return function (input) {
if (input) {
return input.replace(/(\d+)/g, "<span class=\"string\" style=\"color:red\">$1</span>");
}
}
})
.directive('snippet', function($timeout, $interpolate, formatter) {
return {
restrict: 'E',
template:'<pre><code></code></pre>',
replace:true,
require: '?ngModel',
link:function(scope, elm, attrs, ngModel){
ngModel.$render = function(){
var tmp = $interpolate(scope.txt)(scope);
elm.find('code').html(formatter(tmp));
}
}
};
});
http://plnkr.co/edit/783ML4Y2qH8oMLarf0kg?p=preview

Related

Unit Testing: Directive with Dynamic Template

I am running across an issue when trying to use an unusual method to grab templates for my page-content. I have abstracted it but suffice to say that the code works fine but the testing is throwing up some issues. I have listed my directive below:
Directive:
return {
restrict: 'A',
scope: {
page: '=pageCode',
},
link: function (scope, element) {
$http.get('templates/' + scope.page + '.html', {cache: $templateCache})
.success(function(html) {
element.replaceWith($compile(html)(scope));
});
}
};
Basically, when this directive is called with the page-code attribute, it grabs it and pulls in a specific partial for that page content. I have tried mocking out a $scope to contain the code but that wasn't working so I am hardcoding the data-page-code="404" for now. The test is as follows:
Directive Test:
it('should compile and pass through the scope', function() {
mockDirectiveHtml= '<div data-page-content data-page-code="404"></div>';
mockTemplateHtml= '<h1>Hi</h1>';
$httpBackend.expectGET('templates/404.html').respond(mockTemplateHtml);
pageContentHtml= $compile(mockDirectiveHtml)($scope);
$scope.$digest();
$httpBackend.flush();
expect(pageContentHtml.html()).toEqual('<h1>Hi</h1>');
});
However when this runs, the console is telling me it is trying to hit GET('templates/undefined.html'). Now I am unsure if I have things in the right order, or if that is even the solution but has anyone came across something like this in the past? I assume the directive is running before the attributes are passed to it so when it comes to grab templates/' + 'scope.code' that value is undefined.

AngularJS SVG Path directive

Bit of a short question, but wondering why this is happening:
In the example posted below, there are two SVG elements, each containing a directive assigned to a element. The first directive doesn't use a template, and is the setup I currently use to assign actions to paths.
The second directive is what I was trying to do in order to clean up my code a bit, but it won't be drawn.
I googled a bit around, and read there is a difference between SVG & DOM nodes? But since both ng-repeat's are working, I'm a bit puzzled on what is going on.
Link to plunkr: http://plnkr.co/edit/cok6O58SOUyaGHG5Jtu5?p=preview
angular.module('app', []).controller("AppCtrl", function($scope) {
$scope.items = [1,2,3,4];
}).directive('svgPathNoReplace', function() {
return {
restrict: 'A',
scope: true,
link: function(scope, element, attrs) {
}
}
}).directive('svgPathReplace', function() {
return {
restrict: 'A',
replace: true,
template: '<path id="1" d="M 85.750001,0 C 38.393651,0 0,39.02444 0,87.15625 c 0,48.13181 38.393651,87.1875 85.750001,87.1875 C 133.10635,174.34375 171.5,135.28806 171.5,87.15625 171.5,39.02444 133.10635,0 85.750001,0 z m 0,1.5 C 132.28206,1.5 170,39.8217 170,87.15625 c 0,47.33455 -37.71794,85.6875 -84.249999,85.6875 C 39.217941,172.84375 1.5,134.4908 1.5,87.15625 1.5,39.8217 39.217941,1.5 85.750001,1.5 z" '+
'style="stroke:#000000;stroke-width:1px;"></path>'
}
});
The most current beta (1.3) allows another property on the directive called type where you can specify svg content now.
ie.
return {
restrict : 'AE',
type : 'svg',
template : '<path></path>'
link : function (..)
}
Your initial research was correct -- in AngularJS, templates are rendered as DOM nodes, not SVG nodes. This means the directive's template is rendering your <path> tag as a "dummy" non-standard HTML tag that does nothing.
In other words, although HTML normally recognizes path nodes as non-standard nodes, it's preconfigured to know how to handle them using SVG. But when HTML sees a non-standard DOM node rendered by the template (which in this example just happens to be named path), it doesn't recognize it and therefore does nothing. Think of it as trying to use <foo></foo>.
However, this doesn't mean the ng-repeat directive won't work on the custom DOM node, since it doesn't care what type of node (i.e., standard or custom) it is repeating.
Updated Plunker
The above example solves your problem by manually creating the HTML node using the directive's link function instead of its template key. The helper function creates the path node in a way that can be recognized by SVG, which eliminates the need to use a template.
My answer is inspired by this solution.

angularjs sanitize to remove only JS

I intend to allow users to input iframes, custom inlines styles and maybe other things.
But I would like to remove anything JS.
By using $sanitize I seem to remove too much.
If you only want to strip out script tags you should do something like this:
angular.module('app', ['ngSanitize'])
.directive('testDir', function() {
return {
restrict: 'A',
controller: function($scope, $sce) {
$scope.sanitizeIt = function() {
var replacer = /<script>.*(<\/?script>?)?/gi;
var stripped = $scope.html.replace(replacer, '');
$scope.sanitizedHtml = $sce.trustAsHtml(stripped);
};
$scope.$watch('html', $scope.sanitizeIt);
}
};
});
The regular expression will wipe out all script tags and $sce tells the application that you can trust the string that is stripped as HTML.
In your application you can then write something like this:
<body ng-app="app">
<div test-dir>
<textarea ng-model="html"></textarea>
<div ng-bind-html="sanitizedHtml"></div>
</div>
</body>
When you persist to the database, make sure you save the sanitized HTML string instead of the html one.
Here's more documentation on $sce:
https://docs.angularjs.org/api/ng/service/$sce
It's basically what $sanitize uses behind the scenes. NOTE: I have not tested this regular expression in browsers aside from Chrome, so you might have to do some of your own tweaking to get it to work to suit your needs. However, the idea is the same.

ng-bind-html not processing INPUT

I would like to bind html with the content of $scope.value = "<input type=text name=a>"
Nothing is inserted inf the DOM, but if $scope.value = "Hello <i>Guys</i>" everything is fine.
Is there a limitation/bug with ng-bind-html? Is there a workaround?
I am using 1.2.4 version of angularJS
Thanks for your help, this is a big issue for my development
Christophe
Can't you use a Directive instead of that? This way I think you get rid of your problem
http://docs.angularjs.org/guide/directive
https://egghead.io/search?q=directive
A small example:
angular.module('myApp').directive('myDirective', function(){
return {
restrict: 'E',
replace: true,
// you can set 'transclude: true' instead of the following line to create a new scope but inheriting from the parent
scope: false, // this will make the directive have the same scope as the parent
templateUrl: 'my-html-template.html'// you can load the template like this
// You can also use 'template' and include the html code here
}
});

Multiple Angular Filters for ng-repeat - Is there a better way?

I'm currently working on a car rental site which I'm building with angular.
In the car selection section the user needs to be able to filter on a lot of different properties, such as 4x4, Automatic or Manual gear, and the categories of the cars, like compact, premium, sports car etc. On top of that there needs to be some pretty extensive work on ordering the cars as well.
This was easy to achieve by the standard filters and a small directive for each filter-button, but I can imagine how long the ng-repeat attribute is going to be with like 12 filters. Probably nothing I should be afraid of, but still.
What I wanted to run by you guys is if there is a better solution than this.
This is bound to be unnecessarily messy in the end.
This is how it's running now:
Html:
<div filter-btn="4x4" ng-model="filters" class="btn">4x4</div>
<div filter-btn="manual" ng-model="filters" class="btn">manual</div>
<input type="text" ng-model="filters.searchCar">
<div class="car-cont">
<div ng-repeat="car in filteredCars = (cars | filter:filters.4x4 | filter:filters.manual | filter:filters.searchCar)" class="car">{{car.model}}</div>
<div ng-show="!filteredCars.length">No cars</div>
</div>
JS:
angular.module('mabi').directive('filterBtn',[ function () {
var linkFunction = function(scope, elem, attr){
var activeFilter = attr.filterBtn;
var clickFunction = function(){
scope.$apply(function(){
if (scope.model[activeFilter] != activeFilter){
scope.model[activeFilter] = activeFilter;
} else {
scope.model[activeFilter] = "";
}
});
console.log(scope.model);
}
elem.bind('click', clickFunction);
}
return {
restrict: "A",
require: 'ngModel',
link: linkFunction,
scope: {
model: "=ngModel"
}
}
}]);
I understand your concern, but I would say that the first thing you should do is try it out. Make a test with as much data as you could ever reasonably expect and try all the filters on a slow machine with a bad browser (cough! IE8... cough!).
If performance truly is unacceptable under those conditions, then you could try making a custom filter that takes parameters instead of chaining a bunch of filters. I'm not sure if this would be faster, but it would at least eliminate a lot of individual function calls. And it would also give you full control of optimizing the code.

Categories

Resources