AngularJS/Dynamic <svg> not compiling into DOM in Microsoft IE 11 - angularjs

My problem is that my AngularJS (latest v1.X) can't add to the DOM svg elements I'm generating from it in IE.
I have no errors showing in the dev console. My app is loaded (and thus, the controller), since the width and height of the svg container are set by Angular.
The application is working fine on Firefox, Chrome, Tablets Chrome and Android browser...
My directive :
'use strict';
My_App.directive('ngHtmlCompile', ["$compile", function ($compile) {
return {
restrict: 'A',
replace: true,
link: function (scope, element, attrs) {
scope.$watch(function () {
return scope.$eval(attrs.ngHtmlCompile);
}, function (value) {
element.html(value);
$compile(element.contents())(scope);
});
}
}
}]);
My HTML :
<!DOCTYPE html>
<html lang="fr" ng-app="My_App" ng-controller="App">
<head>
<meta charset="UTF-8">
<title>My App</title>
<style>
* { margin:0; padding:0; #import url('https://fonts.googleapis.com/css?family=Open+Sans'); font-family: 'Open Sans', sans-serif; font-weight: bold; } /* Retire les espaces autour du canvas */
html, body { width:100%; height:100%; } /* Override le comportement des navigateurs */
svg { display:block;background-color: dimgrey;} /* Retire la scrollbar */
svg text {
cursor: default;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
::selection, ::-moz-selection{background: none;}
</style>
<script type="text/javascript" src="angular/angular.min.js"></script>
<script type="text/javascript" src="js/MyApp.js"></script>
</head>
<body>
<svg id="MyApp" ng-cloak ng-attr-width="{{svg.width}}" ng-attr-height="{{svg.height}}" ng-html-compile="html"></svg>
</body>
</html>
Controller side, my $scope.html is updated once at startup with a XHR request, then every 5 seconds after it.th ngHtmlCompile is then called to read it, and add the html stored in as childrens of svg.
If anything is missing, you can ask me. First post, so feel free to tell me if I did anything wrong on my request. And last, sorry if my english is broken, I'm French.
Thanks for your help.

Like a comment above said (thanks to Robert Longson), the html() function doesn't work on IE 11. It just doesn't even appear in dev console that something is wrong.
As a solution (mainly, I just did it another way), I used the ngRoute module for angular.
The index.html is now only a container of a <div ng-view> tag, and my <svg> is placed in an include.html. In this, you can use any $scope var in any attribute, and you're done.
I'm not accepting this answer as long no one can tell that we can't generate html on IE11, as it was my original purpose.

If appears as if you can use Element.insertAdjacentHTML() instead of Element.html(). It seems to work on IE11.
var svgStr='<svg><rect width="100%" height="100%" fill="red"/></svg>';
var div = document.getElementById("test");
div.insertAdjacentHTML('afterbegin', svgStr);
<div id="test"></div>

Related

Angular Google Maps won't show anything

From what I gather the below code is the minimum necessary to run Angular Google Maps, but it won't show the map on screen. Looking at #my-map component in Chrome, it shows 0x0px and I haven't been able to change that despite the css and dynamic height declarations. Apart from that it seems to be running fine, there are no console errors and the appMaps module is at least initialized correctly.
<!DOCTYPE html>
html>
<head>
<title>New Map</title>
<style>
html, body {
height: 2000px;
width: 100%;
margin: 0;
padding: 0;
}
.angular-google-map-container { height: 400px; }
</style>
<script src="lodash/lodash.js"></script>
<script src="angular/angular.js"></script>
<script src="angular-google-maps/dist/angular-google-maps.js"></script>
<script>
var appMaps = angular.module('appMaps', ['uiGmapgoogle-maps']);
appMaps.config(function(uiGmapGoogleMapApiProvider) {
uiGmapGoogleMapApiProvider.configure({
key: 'My Key Is Entered Here',
v: '3.20',
libraries: 'weather,geometry,visualization'
});
});
appMaps.controller('mainCtrl', function($scope) {
$scope.map = { center: { latitude: 45, longitude: -73 }, zoom: 8 };
$scope.$on('$viewContentLoaded', function () {
var mapHeight = 400; // or any other calculated value
$("#my-map .angular-google-map-container").height(mapHeight);
});
});
</script>
</head>
<body>
<h1>TEST</h1>
<div id="map_canvas" ng-controller="mainCtrl">
<ui-gmap-google-map id="my-map" center="map.center" zoom="map.zoom"></ui-gmap-google-map>
</div>
</body>
</html>
Edit: Yep, so Roux was correct, added the google maps API script (with key) at the top and angular-simple-logger after angular and that lead me pretty close. Then I just needed to add ng-app="appMaps" to the body element (I'm new to angular in general) and it worked. Thanks!
By following the Quickstart, you can see that you missed to call a few library/api.
Add the angular-simple-logger.js in your project (I think it comes with angular-ui packages or angular-google-maps)
Don't forget to add the google api, without it, nothing will work. <script src='//maps.googleapis.com/maps/api/js?sensor=false'></script>

Angular, ng-show and relative location

Alright, so this might either be an Angular thing (in which case I might also be doing things completely wrong), but it could also be a browser / CSS thing.
"As simple as possible" plunker: https://plnkr.co/edit/t3woLDwynHYgbRDGNZDK
Also, full code (runnable as code snippet) attached to this post.
The example should be pretty clear, though certainly not pretty or designed in any way. If you don't feel like reading the ~20 lines of code, the following is what should happen:
There is a "button", the area with the paste icon in it. When you click it, an operation starts. With it, a spinner (controlled by a variable on the scope) is shown inside the button. When the operation finishes (simulated by the $timeout in the example), a message is set on the scope (imagine a result info message) and the spinner-controlling variable is turned off.
The intent is of course that the spinner should always be rendered inside (position: relative) the box, but during a brief moment while elements are being re-compiled / re-evaluated, it's instead rendered just above the box (or somewhere in between the middle and above).
If you don't see the effect I am describing straightaway, click the "Clear state..." button and try again. Also, you can try different values for the $timeout. For me, a $timeout of ~ 20 ms pretty much always gives me a spinner above the box in Chrome. Firefox seems to require a slightly higher timeout, or it won't even render the spinner half the time.
I initially assumed this to be an effect of Angular re-evaluating the two elements one at a time (re-rendering them), which gave the browser a rendering tick or two to actually draw the spinner incorrectly while it was still showing. However:
What confuses me a bit is that by turning up the $timeout time to something like 200 ms, the problem goes away (or at least my eyes make me believe that), leading me to believe it might not be an Angular problem after all, but a rendering timing one? Perhaps the browser (Chrome in this case, but reproducable in at least Firefox as well) doesn't like hiding the element again after just a tick?
ng-show / ng-if doesn't make a difference here.
Changing the spinner icon for other icons (and removing the spin) doesn't make a difference.
Full code, since it seems to be required now:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope, $timeout) {
$scope.message = undefined;
$scope.loading = false;
$scope.perform = function(event) {
event.stopImmediatePropagation();
$scope.loading = true;
$timeout(function() {
$scope.message = "Some message...";
$scope.loading = false;
}, 35);
};
$scope.clear = function() {
$scope.loading = false;
delete $scope.message;
};
});
#element .resource {
position: relative;
width: 120px;
margin: 0;
padding: 10px;
float: left;
box-sizing: border-box;
-moz-box-sizing: border-box;
font-size: 14px;
text-align: center;
}
#element .resource .shadow {
height: 124px;
box-sizing: border-box;
-moz-box-sizing: border-box;
border-radius: 4px;
border: 1px dashed #ccc;
padding-top: 32px;
color: #bbb;
cursor: pointer;
}
#element .resource .shadow:hover {
color: #888;
border-color: #999;
}
#element .resource .shadow.inactive {
color: #bbb;
border-color: #999;
cursor: default;
padding-top: 48px;
}
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<link data-require="bootstrap-css#3.3.6" data-semver="3.3.6" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.css" />
<link data-require="font-awesome#*" data-semver="4.5.0" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.css" />
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js#1.3.x" src="https://code.angularjs.org/1.3.20/angular.js" data-semver="1.3.20"></script>
<script data-require="jquery#*" data-semver="2.2.0" src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<script data-require="bootstrap#*" data-semver="3.3.6" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<div class="alert alert-info" ng-show="message">{{message}}</div>
<div id="element">
<div class="resource">
<div x-ng-show="!loading" class="shadow" x-ng-click="perform($event);">
<i class="fa fa-paste fa-2x"></i>
</div>
<div x-ng-show="loading" class="shadow inactive">
<i class="fa fa-spinner fa-spin fa-stack-2x text-info"></i>
</div>
</div>
</div>
<button ng-click="clear();">Clear state...</button>
</body>
</html>

Angular 1.4 View Encapsulation and Shadow DOM - is it possible?

I am in the process of changing my app from using Polymer to Angular 1.4 for stability reasons. Because of my familiarity with Polymer and web components (and future use of Angular 2) I am choosing to architect my app using the Component Pattern.
I have searched and thus far only come across this question and the solution was a no longer maintained github repo. I want to know if it is possible to have view encapsulation with Angular 1.4. In case I am misstating, specifically, can my directives with templates have their own encapsulated styles like is done in Polymer and Angular 2 without relying on Grunt?
Seems like it is. I am using Angular 1.6 in my test, but I was able to get shadow DOM working inside an angular directive including bindings.
Here is my sample code:
<!DOCTYPE html>
<html ng-app="app">
<head>
<meta charset="utf-8">
<title>Angular 1.x ShadowDOM example</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
<script>
var app = angular.module('app', []);
app.controller('myElController', ($scope) => {
});
var template = `
<style>
:host {
left: 50%;
position: fixed;
top: 50%;
transform: translate(-50%,-50%);
border: 1px solid black;
box-shadow: 4px 4px 4px rgba(0,0,0,.4);
display: inline-block;
padding: 6px 12px;
}
h3 {
border-bottom: 1px solid #999;
margin: 0 -12px 8px;
padding: 0 12px 8px;
}
p {
margin: 0;
}
</style>
<h3>{{title}}</h3>
<p>{{info}}</p>
`;
app.directive('myEl', ($compile) => {
return {
"restrict": "E",
"controller": "myElController",
"template": '',
"scope": {
"title": "#",
"info": "#"
},
"link": function($scope, $element) {
console.log('here');
$scope.shadowRoot = $element[0].attachShadow({mode:'open'});
$scope.shadowRoot.innerHTML = template;
$compile($scope.shadowRoot)($scope);
}
};
});
</script>
</head>
<body>
<div>
<my-el title="This is a title" info="This is the info of the element"></my-el>
</div>
<div class="shell">
<h3>Outside title (H3)</h3>
<p>Outside content (P)</p>
</div>
</body>
</html>
The trick is to not have a default template for the directive. Just us an empty string. Then, in the link function you would create the shadow root and set it's innerHTML to your real template. Then you need to run $compile on the shadow root to bind it back to the directive's $scope.
Be aware that this will only work on a browser that natively supports shadow DOM. Any Polyfill will not support real CSS encapsulation. For that it is better to use a form of BEM CSS: http://getbem.com/introduction/

Include external js widget in angular template

I need to include script tag which will render widget on my angularjs template.
For example I'd include this
<script type="text/javascript" src="http://100widgets.com/js_data.php?id=106"></script>
But angular will not render it.
Since the 100widgets scripts manipulate DOM and add other <script> tags to HTML file it could not work properly.
Morover some of these widgets are Flash based so the script add reference to SWF objects.
I think one possibility is to analyze the output of the request to url in src attribute (in your example http://100widgets.com/js_data.php?id=106) and trying to add the corresponding DOM manipulation and scripts to the template in which you desire the widget will appear.
Following is an example showing a page (page1) NOT WORKING (simply added the script tag as you typed) and a second page (page2) whose template has the insertions needed to show up the calendar widget.
PS: due to sandbox restriction this snippet could not work here on SO; try this CodePen version in debug mode: http://codepen.io/beaver71/pen/MyjBoP, or create your own version deployed on your local web server.
(function() {
'use strict';
angular.
module('myApp', ['ui.router']).
config(configRouteProvider).
controller('AppCtrl', AppCtrl);
function AppCtrl($location, $rootScope) {
$rootScope.$on('$stateChangeStart', onStateChangeStart);
function onStateChangeStart(event, toState, toParams, fromState, fromParams, options) {
console.log('From:', fromState.name,
'to:', toState.name);
}
}
function configRouteProvider($stateProvider, $urlRouterProvider) {
$stateProvider
.state('home', {
url: '/',
templateUrl: 'views/home.html'
})
.state('page1', {
url: '/pag1',
templateUrl: 'views/page1.html',
})
.state('page2', {
url: '/pag2',
templateUrl: 'views/page2.html',
});
$urlRouterProvider.otherwise('/');
}
}());
body {
margin: 0px;
}
hr {
margin: 0px;
}
.tabs {
padding: 8px;
background-color: black;
color: white;
}
.tabs a,
.tabs a:visited,
.tabs a:active,
.tabs a:hover {
color: white;
}
.my-tab {
height: 100%;
width: 100%;
position: absolute;
padding: 8px;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src='//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>
<script src='https://code.angularjs.org/1.4.0/angular.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.18/angular-ui-router.js'></script>
</head>
<body ng-app="myApp" ng-controller="AppCtrl as app">
<div class="tabs">
Home
Page1
Page2
</div>
<hr />
<div ui-view></div>
<script id="views/home.html" type="text/ng-template">
<div class="my-tab">
<h3>Home</h3>
<p>bla bla bla</p>
</div>
</script>
<script id="views/page1.html" type="text/ng-template">
<div class="my-tab">
<h3>Page1</h3>
<p>Using script type="text/javascript"... NOT WORKING:</p>
<script type="text/javascript" src="http://100widgets.com/js_data.php?id=106"></script>
</div>
</script>
<script id="views/page2.html" type="text/ng-template">
<div class="my-tab">
<h3>Page2</h3>
<p>Workaround</p>
<!--code1-->
<div class="scriptcode">
<!--ecode1-->
<a target='_blank' href='http://100widgets.com/calendars/106-calendar.html'>
<embed align="middle" id="calendar" width="170" height="156.111111111" allowscriptaccess="always" quality="high" salign="lt" wmode="transparent" src="http://100widgets.com/js-files/postit.swf?UTCoffset=0&gid=0&text1=St Valentines is on 14th February 2012&&event_time=&rec=&rnd=0&gha=0&ghb=0&ghf=1&gbc=FFB200&gfc=040244&gtc=F9F9FF&gnu=http://mycalendar.org/widget/&fna=&ims="
type="application/x-shockwave-flash" />
</a>
<!--code2-->
</div>
<!--ecode2-->
<!--below commented cause it's not necessary -->
<!--script type="text/javascript">
var js = document.createElement("script");
js.type = "text/javascript";
js.src = "http://100widgets.com/stat.js.php";
document.body.appendChild(js);
</script-->
</div>
</script>
</body>
Due to safety constraints, Angular does not parse <script> tags inside templates.
Then the widget you are referring to, is utilizing document.write. Document.write is unavailable once your page is done loading.
So, there seems to be no easy way out here.
However, as what you are trying to do, is something that is quite usual in add-script, krux created postscribe. A way around this issue. On my turn I created a small directive that utilizes this library.
In your template it will look something like this:
<div ps-src="http://100widgets.com/js_data.php?id=21"></div>
the directive:
function psScr($document) {
return {
restrict: 'A',
scope: {
psSrc: '#'
},
link: link
}
function link(scope, elm) {
if (typeof postscribe !== 'undefined') {
postscribe(elm[0], `<script src='${scope.psSrc}'></script>`);
} else {
// If postscibe isn't loaded, first load the needed libarary
var script = document.createElement('script');
script.src = 'https://gitcdn.xyz/repo/krux/postscribe/master/dist/postscribe.js';
script.onload = function () {
// once postscibe is in, kick it into action.
link(scope,elm);
};
document.head.appendChild(script);
}
}
}
angular.module('psSrcModule', [])
.directive('psSrc', psScr);
You can see it in action in this plunk
Not all of the widgets behave nicely in combination with postscribe though, some of them seem to display some artefacts in the html. I currently lack the time to find out who is to blame for this (100widgets or postscribe), but if you really need this, this is something that can be worked out.

Page Transition with AngularJS and CSS Animations

I'm working on a basic AngularJS app. This app will have pages that are going to be defined within templates. When a user changes pages, I want to be able to show the other page with a basic animation. Currently, my app is doing this. However, the previous page drops below the new page and sort of flickers. I can't figure out why this is happening. Here is my code:
<!doctype html>
<html lang="en" ng-app="app">
<head>
<meta charset="utf-8">
<title>My HTML File</title>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0rc1/angular.js"></script>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0rc1/angular-route.js"></script>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0rc1/angular-animate.js"></script>
<style type="text/css">
.myAnimation { height:480px; width:640px; overflow:hidden; }
.myAnimation.ng-enter { animation-duration: .3s; animation-name: fadeIn; animation-timing-function: cubic-bezier(.71,.55,.62,1.57); }
.myAnimation.ng-leave { animation-duration: .3s; animation-name: fadeOut; animation-timing-function: cubic-bezier(.71,.55,.62,1.57); }
#keyframes fadeIn {
from { opacity: 0; transform: scale(.9, .9); }
to { opacity: 1; transform: scale(1, 1); }
}
#keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
</style>
</head>
<body style="margin:0; padding:0;">
<ul style="list-style-type:none; margin:0px; padding:0px;">
<li style="display:inline;"><a href='#page1'>Page 1</a> </li>
<li style="display:inline;"><a href='#page2'>Page 2</a> </li>
</ul>
<div style="display:block; border:1px solid black;">
<div ng-view class="myAnimation"></div>
</div>
<script type="text/javascript">
angular.module('app', ['ngRoute', 'ngAnimate']).
config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/page1', {templateUrl: 'views/page1.html', controller: Page1Ctrl}).
when('/page2', {templateUrl: 'views/page2.html', controller: Page2Ctrl}).
otherwise({redirectTo: '/page1'});
}]);
function Page1Ctrl($scope, $http) {
}
function Page2Ctrl($scope, $http) {
}
</script>
</body>
</html>
How do I prevent the scrollbar from appearing when I change pages? And prevent the older page from dropping below the new page?
Thank you!
It's likely that you need to give the animated divs a position:absolute; style during the animations.
What is happening is that the second template is being loaded into the DOM while the first template is still doing the fade-out animation. Without position:absolute they do what is natural and start block stacking until the first template has fully faded out and Angular removes it.
Keep in mind if you add position:absolute you may also have to play with the "Top" value to position it correctly depending on which parent element has position:relative or absolute.
You can put the position:absolute class on the animated div generally, or just specifically on the animating classes depending on what works better for your app structure.

Resources