I have a mean-stack website. I want to dynamically construct a variable that contains a valid html string, then render it in an iframe. After some research, I tried the following code: (JSBin)
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js"></script>
<script src="https://cdn.rawgit.com/jugglinmike/srcdoc-polyfill/master/srcdoc-polyfill.min.js"></script>
</head>
<body ng-app="app" ng-controller="Ctrl">
<iframe srcdoc="{{content | toTrusted}}"></iframe>
<script>
var app = angular.module('app', []);
app.controller("Ctrl", ["$scope", function($scope) {
$scope.content = "<b>hello</b>";
}])
app.filter('toTrusted', ['$sce', function($sce) {
return function(text) {
return $sce.trustAsHtml(text);
};
}]);
</script>
</body>
</html>
It works fine in Chrome, whereas it does not work in IE (eg, IE 11), even though I use src-polyfill.
Does anyone have a solution?
app.directive('iframeDoc', [ '$sce', function ($sce) {
return {
restrict:"A",
scope:{
iframeDoc:'='
},
link: function(scope, elem, attrs) {
elem.attr("srcdoc", $sce.trustAsHtml(scope.iframeDoc));
// Check browser to support scrdoc.
var isSupportSrcDoc= !!("srcdoc" in document.createElement("iframe"));
if(!isSupportSrcDoc){
var jsUrl = "javascript: window.frameElement.getAttribute('srcdoc');";
if (elem[0].contentWindow) {
elem[0].contentWindow.location = jsUrl;
}
elem.attr("src", jsUrl);
}
}
};
}])
In html:
<iframe iframe-doc="content"></iframe>
Related
I have this function in jQuery but I'm facing problem converting that into angularJs function:
$('p').each(function() {
$(this).html($(this).text().split(/([\.\?!])(?= )/).map(
function(v){return '<span class=sentence>'+v+'</span>'}
));
It would really help if someone could explain to me how one would implement these lines of code in angularJs
Thanks in advance guys
easiest way is just splitting into an array and binding that to your controller.
More in depth way would be to roll your own custom directive, let me know if you want to see a way with a directive
<html ng-app="MyCoolApp">
<head>
</head>
<body ng-controller="MyCoolController">
<p ng-repeat="text in Array">
{{text}}
</P>
</body>
</html>
<script type="text/javascript">
var myApp = angular.module('MyCoolApp',[]);
myApp.controller('MyCoolController', ['$scope', function($scope) {
var dataSource = [];//your data split
$scope.Array = dataSource;
}]);
</script>
edit
Updated to use custom directive. YOu will need to tweak the regEx to split properly plunkr example
split.html
<div>
<textarea ng-model="input"></textarea>
<button ng-click="Process(input)">Process</button>
<p>
<span style="border:1px solid red" ng-repeat="text in Output track by $index">
{{text}}
</span>
</p>
</div>
script.js
(function(angular) {
'use strict';
angular.module('splitExample', [])
.directive('mySplit', function() {
return {
templateUrl: 'split.html',
controller: function($scope){
$scope.Process = function(text){
$scope.Output = text.split(/([\.\?!])(?= )/);
console.log(text)
}
}
};
})
})(window.angular);
html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Example - example-directive-tabs-production</title>
<script src="//code.angularjs.org/snapshot/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body ng-app="splitExample">
<my-split>
</my-split>
</body>
</html>
You need to write a directive for it, DOM Manipulation is only allowed in directives in the link function otherwise it is a very bad practice. Here is the code inside link function of directive.
link: function (scope, element, attributes) {
var el= $(element);
el.find('p').each(function(){
el.html(el.text().split(/([\.\?!])(?= )/).map(
function(value){
return '<span class=sentence>'+value+'</span>'
}
));
});
}
Hope it helps you, I am unable to know actually what you are trying to do.. otherwise i would have helped you with full solution and proper directive approach, for any further assistance let me know. Thanks.
Just giving my suggestion : JQuery approach is incompatible with AngularJS.
Updated directive as per your requirement:
angular.module('yourAppModuleName').directive('contentClick', ['$timeout', function($timeout) {
return {
restrict: 'A',
require: 'ngModel',
scope: {
contentEditable: '=contentEditable'
},
link: function(scope, element, attributes) {
scope.$watch(attributes.contentEditable, function(value) {
if (value === undefined)
return;
scope.contentEditable = attributes.contentEditable;
});
if(scope.contentEditable == 'true') {
scope.$watch(attributes.ngModel, function(value) {
if (value === undefined)
return;
value.split(/([\.\?!])(?= )/).map(
function(v){
return '<span onclick="myFunction(this)" >'+v+'</span>'
}
);
});
}
//without timeout
//function myFunction(x) {
// x.style.background="#000000";
//}
//with timeout
function myFunction(x) {
$timeout(function() {
x.style.background = "green";
}, 10000);
}
}
};
}]);
HTML:
<div contentEditable="isEditable" style="cursor:pointer" ng-model="content" content-click></div>
Controller:
yourAppModuleName.controller('myController', ['$scope', function($scope) {
$scope.isEditable = true;
$scope.content;
}]);
<!DOCTYPE HTML>
<html lang="en-US" ng-app="theapp">
<head>
<meta charset="UTF-8">
<title>asd</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<script type="text/javascript">
var mainScope;
angular.module('theapp', []).controller('MainCtrl', function($scope, $injector) {
$scope.demo = "test123";
$scope.scopecomp = function(){
angular.element(document).injector().invoke(function ($compile) {
$compile(document.body)($scope);
});
}
mainScope = $scope;
});
function addDiv(){
var $newDiv = $('<div>{{demo}}</div>');
$(document.body).append($newDiv);
}
function comp(){
mainScope.comp();
}
</script>
</head>
<body ng-controller="MainCtrl" ng-change="comp();">
<h1>{{demo}}</h1>
<input type="text" id="compText" />
<button onclick="addDiv();">add</button>
<button ng-click="scopecomp();">compile with ng-click (works fine)</button>
<button onclick="comp();">compile with onlick (not working)</button>
</body>
</html>
I want to run the comp() function anywhere in my project. I tried button onclick,it didn't work but ng-click works fine. What is the problem ? Why onclick doesn't work ?
New : changeContent function added.
<!DOCTYPE HTML>
<html lang="en-US" ng-app="theapp">
<head ng-controller="MainCtrl as main">
<meta charset="UTF-8">
<title>asd</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<script type="text/javascript">
angular.module("theapp", []).controller("MainCtrl", MainController);
MainController.$injector = ['$timeout'];
var vm;
function MainController($timeout) {
vm = this;
vm.post = null;
function loadStuff(){
$timeout(function() {
vm.post = {
title: "Post Title",
content: "Post Content"
};
}, 1000);
}
loadStuff();
}
function changeContent(){
vm.post.content = "<div>new content </div>";
}
</script>
</head>
<body ng-controller="MainCtrl as main">
<p ng-hide="main.post">Loading...</p>
<h3>{{main.post.title}}</h3>
<p>{{main.post.content}}</p>
<button onclick="changeContent();">change</button>
</body>
</html>
New bodyController()
function bodyController($scope, $injector) {
_bodyController = this;
$scope.title = "ttt";
$scope.content = "aaa";
$scope.comp = function(){
angular.element(document).injector().invoke(function ($compile) {
$compile(document.body)($scope);
});
}
myAPP.Run(function(){
$scope.title = globalOBJ.title;
$scope.content = globalOBJ.content;
$scope.comp();
});
}
You should change your '' to this:
<body ng-controller="MainCtrl" ng-model="demo" ng-change="comp();">
If you check the url from the first line of the error log: https://docs.angularjs.org/error/$compile/ctreq?p0=ngModel&p1=ngChange. The problem is explained:
Controller 'ngModel', required by directive 'ngChange', can't be
found! Description This error occurs when HTML compiler tries to
process a directive that specifies the require option in a directive
definition, but the required directive controller is not present on
the current DOM element (or its ancestor element, if ^ was specified).
To resolve this error ensure that there is no typo in the required
controller name and that the required directive controller is present
on the current element.
The directive 'ng-change' require a 'ng-model' to work properly, that is why you are getting an compile error.
Now your second question, "Why onclick doesn't work ?". You should never manipulate the DOM from the controller, if you have to do that use a directive. When you call "scopecomp()" from the ng-click that method is invoked from "within" the angular engine, it will fire an digest cycle which will process the "html" (it's more than that, I'm trying to keep it simple) and print what you expect, but when you add "{{demo}}" directly to the DOM, that variable will not be processed.
There is no need to change the DOM manually to do what you are looking for, check the snippet below. I simulated your "database request" with a timeout function.
angular.module("app", [])
.controller("MainController", MainController);
MainController.$injector = ['$timeout'];
function MainController($timeout) {
var vm = this;
vm.post = null;
function loadStuff() {
$timeout(function() {
vm.post = {
title: "Post Title",
content: "Post Content"
};
}, 1000);
}
loadStuff();
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="MainController as main">
<p ng-hide="main.post">Loading...</p>
<h3>{{main.post.title}}</h3>
<p>{{main.post.content}}</p>
</div>
$FirebaseJS.Run(function(){
$scope.$apply(function(){
$scope.obj = globalOBJ;
});
});
Finally I got it.
I am trying to create a custom directive..
<head>
<script type="text/javascript" src="../Scripts/angular.min.js"></script>
<script type="text/javascript" src="../Scripts/DataDirectives.js"></script>
</head>
<body style="overflow: hidden" >
<div ng-app="myApp">
<SampleData></SampleData>
</div>
</body>
In a separate Javascript File called DataDirectives.js the followung code is present..
var app = angular.module('myApp', []);
app.directive('SampleData', function () {
return {
restrict: 'E',
template: '<div>Sample Div,To test angular Directives</div>'
};
});
So when I run the page,I cannot see any text on that page of that of the div element.What could be wrong with my above code.
Thanks for the help in Advance... :)
You have 2 ng-app as well as you are not following the required naming convensions
var app = angular.module('myApp', []);
app.directive('sampleData', function() {
return {
restrict: 'E',
template: '<div>Sample Div,To test angular Directives</div>'
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<sample-data></sample-data>
</div>
in html write directive name: sample-data
in js sampleData
var app = angular.module('myApp', []);
app.directive('sampleData', function () {
return {
restrict: 'E',
template: '<div>Sample Div,To test angular Directives</div>'
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="myApp" style="overflow: hidden" >
<div ng-app="myApp">
<sample-data></SampleData>
</div>
</body>
You are defining the angular directive in your template using camelcase naming convention. The angular directive should be declared in template like sample-data. Then in javascript file you can reference it like sampleData.
Taking these into consideration, you should change the code in the following manner:
<div ng-app="myApp">
<sample-data></sample-data>
</div>
Another alternative would be to declare the directive inside an existing element. For example:
<div sample-data="exp"></dir>
And in javascript:
var app = angular.module('myApp', []);
app.directive('sampleData', function () {
return {
restrict: 'E',
template: '<div>Sample Div,To test angular Directives</div>'
};
});
Please see the lower cased version of sampleData.
I'm trying to get the height of elements in a simple AngularJS app.
See below. What am I doing wrong? The height should be different as the lines wrap, but I get 20 reported back to me regardless of what I input in the "labels" array.
The following code can be executed here, otherwise see below.
http://js.do/code/49177
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<base href="/">
<title>height of element in angularjs</title>
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.8/angular.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.8/angular-route.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript">
'use strict';
var app = angular.module('heightApp', ['ngRoute', 'routing']);
app.controller('heightCtrl', ['$scope', function ($scope) {
$scope.labels = [
'Hi there, I\'m a div.',
'Me too, I\'m also a div.',
'Can you see me, because I certainly can\'t see myself. I don\'t even know my own height. Isn\'t that just crazy?'
];
}]);
angular.module('routing', []).config(['$routeProvider', function($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'height.html',
controller: 'heightCtrl'
});
}]);
angular.module('heightApp').directive('reportMyHeight', function() {
return function (scope, el, attrs) {
alert('offsetHeight = ' + el[0].offsetHeight);
}
})
</script>
</head>
<body ng-app="heightApp">
<div class="container">
<div ng-view></div>
</div>
</body>
<script type="text/ng-template" id="height.html">
<div class="row">
<div class="col-sm-4" report-my-height ng-repeat="lbl in labels">
{{ ::lbl }}
</div>
</div>
</script>
</html>
You need to wait till the next digest cycle. When you do it right away in the directive the interpolations {{ ::lbl }} inside the ng-repeat would not have expanded yet. You can place it in a $timeout turning off the applyDigest argument.
i.e, example:
angular.module('heightApp').directive('reportMyHeight', function($timeout) {
return function (scope, el, attrs) {
$timeout(init, false);
//Initialization
function init(){
console.log('offsetHeight = ' + el[0].offsetHeight, el.html());
}
}
});
Plnkr
Another way to make sure you get the height of the element is to use watch.
angular.module('heightApp').directive('reportMyHeight', function($timeout) {
return function (scope, el, attrs) {
scope.$watch('lbl', function(newval, oldval){
alert(newval + '\n\n' + 'offsetHeight = ' + el[0].offsetHeight);
});
}
})
It will only be triggered once since you use ::.
I try to initialize a slider using AngularJS, but the cursor show 100 when the value is over 100.
Setting the value 150 in a range [50,150] fails with this code :
<html ng-app="App">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script>
angular.module('App', ['App.controllers']);
angular.module('App.controllers', []).controller('AppController', function($scope) {
$scope.min = 50;
$scope.max = 150;
$scope.value = 150;
});
</script>
</head>
<body ng-controller="AppController" >
{{min}}<input ng-model="value" min="{{min}}" max="{{max}}" type="range" />{{max}}<br/>
value:{{value}}
</body>
</html>
The cursor is badly placed (it show 100 instead of 150).
How to display the cursor to its correct place ?
An explanation of what occurs could be on this forum
Update
This bug is reported as issue #6726
Update
The issue #14982 is closed by the Pull Request 14996 and solve the issue see answer.
After searches and tries , a possible way is to define a custom directive :
<html ng-app="App">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script>
angular.module('App', ['App.controllers']);
angular.module('App.controllers', []).controller('AppController', function($scope) {
$scope.min = 50;
$scope.max = 150;
$scope.value = 150;
}).directive('ngMin', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attr) { elem.attr('min', attr.ngMin); }
};
}).directive('ngMax', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attr) { elem.attr('max', attr.ngMax); }
};
});
</script>
</head>
<body ng-controller="AppController" >
{{min}}<input ng-model="value" ng-min="{{min}}" ng-max="{{max}}" type="range" />{{max}}<br/>
value:{{value}}
</body>
</html>
Even it is working this is a non standard extension in order to manage a very basic use case.
Try this new one.
You can configure angular to make it interpolate these values.
And you can use your initial code after that ...
Isn't it magic ?
Use this code only once in your app. Once angular is configured, it will be working for all the future ranges you will use.
<html ng-app="App">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script>
angular.module('App', ['App.controllers']);
angular.module('App.controllers', [])
/* Modify angular to make it interpolate min and max, for ngModel when type = range */
.config(['$provide', function($provide) {
$provide.decorator('ngModelDirective', ['$delegate', function($delegate) {
var ngModel = $delegate[0], controller = ngModel.controller;
ngModel.controller = ['$scope', '$element', '$attrs', '$injector', function(scope, element, attrs, $injector) {
if ('range' === attrs.type) {
var $interpolate = $injector.get('$interpolate');
attrs.$set('min', $interpolate(attrs.min || '')(scope));
attrs.$set('max', $interpolate(attrs.max || '')(scope));
$injector.invoke(controller, this, {
'$scope': scope,
'$element': element,
'$attrs': attrs
});
}
}];
return $delegate;
}]);
}])
.controller('AppController', function($scope) {
$scope.min = 50;
$scope.max = 150;
$scope.value = 150;
});
</script>
</head>
<body ng-controller="AppController" >
{{min}}<input ng-model="value" min="{{min}}" max="{{max}}" type="range"/>{{max}}<br/>
value:{{value}}
</body>
</html>
My lazy way of addressing this bug was to divide the min/max and step values by 100. So 300 becomes 3.0, etc. and values fall below 100. Then I multiply things back as needed.
Since this commit, the initial code give the expected result.
Using release 1.6.0 allow to original code to show slider correctly :
<html ng-app="App">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.0/angular.min.js"></script>
<script>
angular.module('App', ['App.controllers']);
angular.module('App.controllers', []).controller('AppController', function($scope) {
$scope.min = 50;
$scope.max = 150;
$scope.value = 150;
});
</script>
</head>
<body ng-controller="AppController" >
{{min}}<input ng-model="value" min="{{min}}" max="{{max}}" type="range" />{{max}}<br/>
value:{{value}}
</body>
</html>