Angular: manipulating trusted HTML before binding - angularjs

In my Angular app, I receive some HTML as a string from an API and then I want to render it in the page. So I do:
var myHtml = $sce.valueOf(inputString);
return myHtml;
And then I include it in my template:
<div ng-bind-html="myHtml"></div>
This works fine. But before I render it, I also want to make some changes to the text content of the HTML nodes. (I don't want to do this to the original inputString because it's hard to avoid affecting HTML tags.) So I tried:
var myHtml = $sce.valueOf(inputHtml);
// myHtml is an object of type TrustedValueHolderType
// now I want to access the HTML nodes inside it so I do:
for (var i = 0; i < myHtml.length; ++i) {
if (myHtml[i].nodeType === 3) {
myHtml[i].nodeValue = myHtml[i].nodeValue.replace(/a/, 'b');
}
}
return myHtml;
And then include it in my template:
<div ng-bind-html="myHtml"></div>
But this doesn't work because myHtml is not a list of nodes but an object of type TrustedValueHolderType and I cannot access the HTML nodes inside it.

Related

Highlight text as you type in textarea Reactjs

I need to perform a behavior in FrontEnd but I don't know how to do it: Inside the textarea I have to put a background on certain keywords like "+project", "#context", while the user types, as if it were a markup text similar to testing tools for Regex.
Its not the complete solution, but you can adapt this example:
https://jsfiddle.net/julmot/hdyLpy37/
It uses the markjs library:
https://markjs.io/
Here is the javascript code:
// Create an instance of mark.js and pass an argument containing
// the DOM object of the context (where to search for matches)
var markInstance = new Mark(document.querySelector(".context"));
// Cache DOM elements
var keywordInput = document.querySelector("input[name='keyword']");
var optionInputs = document.querySelectorAll("input[name='opt[]']");
function performMark() {
// Read the keyword
var keyword = keywordInput.value;
// Determine selected options
var options = {};
[].forEach.call(optionInputs, function(opt) {
options[opt.value] = opt.checked;
});
// Remove previous marked elements and mark
// the new keyword inside the context
markInstance.unmark({
done: function(){
markInstance.mark(keyword, options);
}
});
};
// Listen to input and option changes
keywordInput.addEventListener("input", performMark);
for (var i = 0; i < optionInputs.length; i++) {
optionInputs[i].addEventListener("change", performMark);
}

Loop Object and Array when Array is inside of an Object

I am using a query to receive a JSON response. I would like to loop each object (VGF, SSR, BCV, etc..) and output them to premade divs, then the arrays within those objects will loop and create divs within that matching object container.
This is a shortened down version of what I have, and it works mostly. (hopefully, I haven't screwed it up here).
The problem is I have to repeat the searchresult function by copying and pasting the entire function for each object (VGF, SSR, BCV, etc). I would really like to learn how to loop this and not have the same code pasted more than a dozen times.
If I have messed up or left something out of this question, please let me know and I will take care of it.
Here is my ajax request and javascript. I know my problem lies within this loop. I have tried to do a loop inside of a loop, etc. But, when I do that I get no results at all. I am baffled and ready to learn.
$(function getData() {
$("#searchbtn").click(function () {
$.ajax({
url: "action.php",
type: "POST",
data: {},
dataType: "json",
success: function (response) {
console.log(response);
searchresult(response);
}
});
});
});
let searchresult = function(response) {
let container = document.getElementById('VGFresults');
let output = "";
for (let j = 0; j < response.length; j++) {
if (response[j].rcode == "VGF") {
output +=
`<div id="person${response[j].code}">
<p>${response[j].firstname} ${response[j].lastname}</p>
</div>`
}
$(container).html(output);
}
};
Here is my response (Same layout as I am currently receiving but shortened the objects in the arrays).
response =
{"VGF":
[{"code":"TU","rcode":"VGF","firstname":"Tom","lastname":"Riddle"},
{"code":"AZ","rcode":"VGF","firstname":"Harry","lastname":"Potter"},
{"code":"FR","rcode":"VGF","firstname":"Hermoine","lastname":"Granger"}],
"SSR":
[{"code":"HG","rcode":"SSR","firstname":"Walt","lastname":"Disney"},
{"code":"TR","rcode":"SSR","firstname":"H.R.","lastname":"Pickins"},
{"code":"ED","rcode":"SSR","firstname":"Tom","lastname":"Ford"}],
"BCV":
[{"code":"YH","rcode":"BCV","firstname":"Tom","lastname":"Clancy"},
{"code":"RS","rcode":"BCV","firstname":"Robin","lastname":"Williams"},
{"code":"AB","rcode":"BCV","firstname":"Brett","lastname":"Favre"}]}
Here is the HTML that the searchresult function is working with. Currently, it works fine.
To clarify, I would like each object to insert its arrays within the corresponding div. Example:
SSR arrays will go into <div id="SSRresults">
BCV arrays will go into <div id="BCVresults">
From there, each array will create a div within that *results div for each array.
<div id="VGFresults">
<div id="VGFheader">This is the VGF Header</div>
<div id="VGFresults">The Javascript Creates Divs for each array here.</div>
</div>
<div id="SSRresults">
<div id="SSRheader">This is the SSR Header</div>
<div id="SSRresults">The Javascript Creates Divs for each array here.</div>
</div>
<div id="BCVresults">
<div id="BCVheader">This is the BCV Header</div>
<div id="BCVresults">The Javascript Creates Divs for each array here.</div>
</div>
Thanks, any help is much appreciated.
I would do like this:
I declare the response as variable (but sure it will work with your ajax response.
var response =
{"VGF":
[{"code":"TU","rcode":"VGF","firstname":"Tom","lastname":"Riddle"},
{"code":"AZ","rcode":"VGF","firstname":"Harry","lastname":"Potter"},
{"code":"FR","rcode":"VGF","firstname":"Hermoine","lastname":"Granger"}],
"SSR":
[{"code":"HG","rcode":"SSR","firstname":"Walt","lastname":"Disney"},
{"code":"TR","rcode":"SSR","firstname":"H.R.","lastname":"Pickins"},
{"code":"ED","rcode":"SSR","firstname":"Tom","lastname":"Ford"}],
"BCV":
[{"code":"YH","rcode":"BCV","firstname":"Tom","lastname":"Clancy"},
{"code":"RS","rcode":"BCV","firstname":"Robin","lastname":"Williams"},
{"code":"AB","rcode":"BCV","firstname":"Brett","lastname":"Favre"}]}
let searchresult = function(response) {
// let container = document.getElementById('VGFresults');
let output = "";
for (var key in response) {
// skip loop if the property is from prototype
if (!response.hasOwnProperty(key)) continue;
var obj = response[key];
let container = document.getElementById(key+'results');
for (var prop in obj) {
// skip loop if the property is from prototype
if (!obj.hasOwnProperty(prop)) continue;
// your code
//alert(prop + " = " + obj[prop]);
console.log(obj[prop])
output += "<div id="+prop+"><p>"+obj[prop].firstname+" "+ obj[prop].lastname+"</p></div>"
}
}
container.innerText = output;
console.log(output);
};
<div id="VGFresults"></div>
each property VGF, SSR, BCV and so on can be handled now.
EDIT: based on users request, I guess you can edit the selector like this:
let container = document.getElementById(key+'results');

String HTML is not compiling using angular $compile having scope variables in it

I have a html string which uses the ngrepeat to render the required div..
var templateHTML = "<div ng-repeat='entity in createCtrl.finalCCList'>\
<span>{{entity.name}}</span>\
</div>";
Here "createCtrl.finalCCList" has list of entity object which has name and id properties in it. Now when I try to compile this using -
var compiledTemplateHTML = $compile(templateHTML)($scope);
if (compiledTemplateHTML && compiledTemplateHTML[0]) {
return compiledTemplateHTML[0].outerHTML;
} else {
return "";
}
I get nothing. Whereas i checked and $scope.createCtrl.finalCCList does have the required values.
Am I missing anything here.

How can I get ng-click to function with ng-repeat and ng-bind-html?

I am trying to get a ng-click directive to function within an ng-repeat and ng-bind-html. The ng-click code is added to a string of html from data pulled from the server (hence the ng-bind-html). The setup has a controller, a base template that is put onto the page with Drupal, and a partial that is loaded via the template from Drupal.
The controller looks like this at the moment:
var guideListController = angular.module('app')
.controller('guideListController', [
'$scope',
'$sce',
'$compile',
'ViewService',
'ToolKit',
function($scope, $sce, $compile, ViewService, ToolKit) {
// Storage for which rows need more/less links
this.rowIndex = [];
this.showFull = false;
this.showFullClick = function() {
alert('Showfull');
};
this.trustIntro = function(code) {
return $sce.trustAsHtml(code);
};
// Fetch the guide list view from services
var data = ViewService.get({view_endpoint:'guide-service', view_path: 'guide-list'}, function(data) {
//console.log(data);
// Update/process results
for (var row in data.results) {
// Create short intro w/ truncate.js
data.results[row].Intro_short = $sce.trustAsHtml($scope.guideList.getShortIntro(data.results[row], row));
//data.results[row].Intro_short = $scope.guideList.getShortIntro(data.results[row], row);
// Update intro
data.results[row].Introduction = $sce.trustAsHtml($scope.guideList.updateIntro(data.results[row], row));
//data.results[row].Introduction = $scope.guideList.updateIntro(data.results[row], row);
}
$scope.guideList.guides = data.results;
});
// Add a read less anchor tag at the end of the main intro
this.updateIntro = function(row, row_index) {
var intro = row['Introduction'].trim();
if ($scope.guideList.rowIndex[row_index]) { // only apply Less link if needed
var index = intro.length - 1;
var tag = [];
if (intro.charAt(index) === '>') { // we have a tag at the end
index--;
do {
tag.push(intro.charAt(index));
index--;
} while (intro.charAt(index) != '/'); // the closing tag
index--; // we move the index one more for the "<"
tag.reverse(); // Reverse
tag = tag.join('');
}
var inserts = ['div', 'p']; // we insert the Less link here.
if (jQuery.inArray(tag, inserts) >= 0) { // insert into the tag
intro = intro.substr(0, index) + ' <a class="less" ng-click="$parent.guideList.showFull = false">Less</a>' + intro.substr(index);
}
else { // insert at the end of the html
intro = intro + '<a class="less" ng-click="this.showFull = false">Less</a>';
}
}
return intro;
};
// Truncate the long intro into a shorter length blurb
this.getShortIntro = function(row, row_index) {
// Truncate if necc.
var short_intro = jQuery.truncate(row['Introduction'], {
length: 250,
words: true,
ellipsis: '\u2026 <a class="more moreish" attr-ng-click="guideList.showFullClick()">Read on</a>'
});
var more = jQuery('.more', short_intro); // select more link
if (more.length) { // do we have a more link
$scope.guideList.rowIndex[row_index] = true;
}
else { // no more link
$scope.guideList.rowIndex[row_index] = false;
}
$compile(short_intro)($scope);
return short_intro;
};
}]);
As you can see in the ViewService.get() call, data is fetched and then processed. The processing simply involves putting a link at the end of the "Intro" field that is intended to be clickable.
For a while I was having a tough time to even get the ng-click directive to even show (it was being filtered out w/out $sce.trustAsHtml). Now it is there but clicking it has no effect.
The main template (from Drupal) currently looks like:
<div class="guide-listing" ng-controller="guideListController as guideList">
<a ng-click="guideList.showFullClick()">Click me</a>
<div class="guide-teaser"
ng-repeat="guide in guideList.guides"
ng-include src="'/sites/all/themes/ngTheme/ngApp/partials/guide_teaser.html'">
<!-- See guide_teaser.html partial for guide teasers -->
</div>
</div>
The ng-click as placed in the Drupal template above works as expected.
And for the partial that is used in the ng-repeat, it looks like so:
<div ng-controller="guideListController as guideList">
<h2 class="guide-teaser-title">{{guide.node_title}}</h2>
<div class="guide-teaser-intro" ng-bind-html="guide.Introduction" ng-show="guide.showFull">
{{guide.Introduction}}
</div>
<div class="guide-teaser-intro-short" ng-bind-html="guide.Intro_short" ng-show="!guide.showFull">
{{guide.Intro_short}}
</div>
</div>
So far I have only been working on getting the ng-click to work on the short_intro and have had no success so far. Any ideas as to what I am doing wrong would be greatly appreciated!
Ok, So I did get some traction! I used the ngHtmlCompile (http://ngmodules.org/modules/ng-html-compile) directive that was created by https://github.com/francisbouvier (thanks!)
The issue was that the new (dynamic) html wasn't being compiled.
At first it didn't work. I had two issues that prevented it from firing:
A: I stopped using $sce.trustAsHtml. Using this in conjunction with the directive caused the content to disappear!
B: The other issue was one of scope. After I changed the directive such that transclude was set to false it worked just fine!

Display a angular variable in my html page

as the title says I'm trying to display an Angular variable in a html page. I have this function inside the controller I'm using :
$http.get('/api/tasks/?format=json').success(function(data) {
$scope.tasks = data;
for (var i=0; i < $scope.tasks.results.length; i++)
{
if ($scope.tasks.results.status == 0)
{
tobedone++;
}
}
});
And now I'd like to display tobedone inside my html page. I've tried [[tobedone]] (I'm using Django, thus custom providers), but nothing displays. Any help please ?
In your template, you have access to all the variables that are members of the current $scope. So, tobedone should be $scope.tobedone, and then you can display it with {{tobedone}}, or [[tobedone]] in your case.

Resources