I have two directives, my-svg and my-rect. I want to use them like this:
<svg my-svg>
<my-rect/>
</svg>
my-rect creates an SVG rect and my-svg creates an svg node with the transcluded rectangle inside. In the end, what I want to get is:
<svg width='300' height='300'>
<rect x="140" y="30" width="25" height="25" fill="red"></rect>
</svg>
See example here: http://plnkr.co/edit/UIyUtX?p=preview
As you can see, the red rectangle isn't displayed, even though it exists in the DOM. According to this discussion, it seems that the rectangle isn't displayed because it is an HTMLElement when it should be an SVGElement.
As suggested in that same discussion, I'm using a custom directive compiler to transform the DOM nodes from type HTMLElement to SVGElement, but even that doesn't seem to work in my use-case.
What am I doing wrong?
Thanks
Under the hoods AngularJS uses JQuery or JQLite to create elements from templates to replace with.
JQuery and JQLite both use document.createElement rather than document.createElementNS with the correct SVG namespace.
In your directive you need to take over the creation of SVG elements from AngularJS.
You can inject the following helper function into your directive:
.value('createSVGNode', function(name, element, settings) {
var namespace = 'http://www.w3.org/2000/svg';
var node = document.createElementNS(namespace, name);
for (var attribute in settings) {
var value = settings[attribute];
if (value !== null && !attribute.match(/\$/) && (typeof value !== 'string' || value !== '')) {
node.setAttribute(attribute, value);
}
}
return node;
})
And make use of it in the link function rather than using a template (either external or inline) - something like:
link: function(scope, element, attrs) {
var cx = '{{x}';
var cy = '{{y}}';
var r = '{{r}}';
var circle = createSVGNode('circle', element, attrs);
angular.element(circle).attr('ng-attr-cx', cx);
angular.element(circle).attr('ng-attr-cy', cy);
angular.element(circle).attr('ng-attr-r', r);
element.replaceWith(circle);
$compile(circle)(scope);
}
You can see an example of this working - in a piechart context - over at https://github.com/mjgodfrey83/angular-piechart/.
A fix landed in angular 1.3.0-beta8 to allow non html directive template types to be specified - see here. For an example of it being used check out angular-charts.
Hope that helps.
Putting
<g>
<my-rect></my-rect>
</g>
will display the rectangle.
It doesn't answer the question what are you doing wrong, but it does get the code to display what you want. I spent some time looking at this problem myself but I could not get it to work , and so solved the problem in a different way. What is the problem you are trying to solve using this method?
Related
I'm using openlayers3 (ol3) and angular 1.5.6 on IE Edge.
I have two modules. Each has their own controller and component. Each controller wants to have a map in the view. One view is for interactively querying data off its map. The other view is for displaying interactive query results.
Under the hood, I provide a MapFactory which returns an instance of a object, containing the said openlayers map.
PROBLEM: The one displays while the other does not.
Here's a sample of my code (some details are left out for simplicity. For example the dependency injection checks. All of this code is being called as expected.):
Module A definition
angular.module('ModuleA').controller('ModuleAController',ModuleAController);
ModuleAController.$inject = ['MapFactory'];
function ModuleAController(MapFactory){
var vm = this;
var vm.map = MapFactory.getMapInstance({
id:'module-A-map',
otherOption:true
});
}
In ModuleA's view:
<div id='module-A-map' class="map-classes"></div>
Module B definition
angular.module('ModuleB').controller('ModuleBController',ModuleBController);
ModuleBController.$inject = ['MapFactory'];
function ModuleBController(MapFactory){
var vm = this;
var vm.map = MapFactory.getMapInstance({
id:'module-B-map',
otherOption:true
});
}
In ModuleB's view:
<div id='module-B-map' class="map-classes"></div>
MapFactory's definition:
angular.module('common').factory('MapFactory',MapFactory);
MapFactory.$inject = [];
function MapFactory(){
var factory = {
getMapInstance : getMapInstance
};
return factory;
function getMapInstance(options){
return new _MapConstructor(options);
}
function _MapConstructor(options){
var _map = new ol.Map({
target : options.id,
logo : false,
view : new ol.View({...}),
layers : [some,layers,here]
});
return {
publicMethod : publicMethod
};
function publicMethod(){...}
function privateMethod(){...}
... other stuff ...
}
}
Please, let me know if any clarification is needed to answer the question.
MORE:
This issue: https://github.com/openlayers/ol3/issues/4601 might be part of the problem. I am using collapsable DIVs with bootstrap. The ModuleA is in the default displayed one, while ModuleB is hidden at first. More to come.
I wrote this up as an OL3 issue as well: https://github.com/openlayers/ol3/issues/5789
ABSTRACT ANSWER:
http://getbootstrap.com/javascript/#collapse-events
I need to add a _map.updateSize() on a show.bs.collapse or shown.bs.collapse event. Now, I need to figure out how to do that in Angular, and post it (unless somebody gets to it first).
Ah, this is in Bootstrap's collapse class. So, let's back up to the Module-B view. Each of my Module's is a panel within a Bootstrap panel accordian. The ModuleA map that displays is the default open panel (the one that has the in class). The ModuleB map is not open by default, and thus, OL3 gives the canvas a display:none in the map's div's style.
<div id="module-B-collapse" class="panel-collapse collapse" >
<div id='module-B-map' class="map-classes"></div>
....
</div>
In my ModuleBController, I simply added:
angular.element('#module-B-collapse').on('shown.bs.collapse',function(){
_map.updateSize();
});
ng-img-crop is an awesome directive however I am having trouble adapting it to my scenario. My issue is that when a user has an image I would like to give them the option to resize the image if they would like to.
So here is the code I am attempting to use:
js:
vm.userImageOriginal = vm.editUser.image_pkey ? 'api/file/' + vm.editUser.image_pkey : null;
html:
<img-crop image="profileVM.userImageOriginal" result-image="profileVM.userImageNew"
area-type="square" result-image-size="300" on-change="profileVM.imageCropped = true;"></img-crop>
So I two issues:
1) I only want to upload the new image if the user has indeed changed the cropping. I tried setting a flag in on-change but it looks like on-change gets executed on initialization as well. Is there any way to know if the user has actually cropped?
2) Is there any way to set the position of the square/circle. In my scenario, if there is an existing user image, I would like to set the cropping square to the dimensions of the current image (i.e. the border of the image).
Thanks in advance.
Solved like this:
Add the following attribute to ng-img-crop directive in html:
on-load-done="profileVM.addCroppingWatcher()"
Here is the function:
function addCroppingWatcher(){
if (croppingWatcher)
return;
$window.setTimeout(function(){
croppingWatcher = $scope.$watch(
function(){ return vm.userImageNew; },
function(newVal, oldVal){
if (oldVal && oldVal != newVal) {
vm.imageCropped = true;
croppingWatcher();
}
}
);
}, 0);
}
The following code is needed in 2 different controllers (at the moment, maybe more controllers later). The code works around a problem I've found in ng-grid and allows the delayed selection of a row (once the data has been loaded).
// Watch for the ngGridEventData signal and select indexToSelect from the grid in question.
// eventCount parameter is a hack to hide a bug where we get ngGridEventData spam that will cause the grid to deselect the row we just selected
function selectOnGridReady(gridOptions, indexToSelect, eventCount) {
// Capture the grid id for the grid we want, and only react to that grid being updated.
var ngGridId = gridOptions.ngGrid.gridId;
var unWatchEvent = $scope.$on('ngGridEventData', function(evt, gridId) {
if(ngGridId === gridId) {
//gridEvents.push({evt: evt, gridId:gridId});
var grid = gridOptions.ngGrid;
gridOptions.selectItem(indexToSelect, true);
grid.$viewport.scrollTop(grid.rowMap[0] * grid.config.rowHeight);
if($scope[gridOptions.data] && $scope[gridOptions.data].length) {
eventCount -= 1;
if(eventCount <= 0) {
unWatchEvent(); // Our selection has been made, we no longer need to watch this grid
}
}
}
});
}
The problem I have is where do I put this common code? It's obviously UI code, so it doesn't seem like it belongs in a service, but there is no classical inheritance scheme (that I have been able to discover) that would allow me to put it in a "base class"
Ideally, this would be part of ng-grid, and wouldn't involve such a nasty hack, but ng-grid 2.0 is closed to features and ng-grid 3.0 is who knows how far out into the future.
A further wrinkle is the $scope that I guess I would have to inject into this code if I pull it from the current controller.
Does this really belong in a service?
I would probably just put this in a service and pass $scope into it but you do have other options. You may want to take a look at this presentation as it covers different ways of organizing your code: https://docs.google.com/presentation/d/1OgABsN24ZWN6Ugng-O8SjF7t0e3liQ9UN7hKdrCr0K8/present?pli=1&ueb=true#slide=id.p
Mixins
You could put it in its own object and mix it into any controllers using angular.extend();
var ngGridUtils = {
selectOnGridReady: function(gridOptions, indexToSelect, eventCount) {
...
}
};
var myCtrl = function() {...};
angular.extend(myCtrl, ngGridUtils);
Inheritance
If you use the 'controller as' syntax for your controllers then you can treat them like classes and just use javascript inheritance.
var BaseCtrl = function() {
...
}
BaseCtrl.prototype.selectOnGridReady = function(gridOptions, indexToSelect, eventCount) {
...
};
var MyCtrl = function() {
BaseCtrl.call(this);
};
MyCtrl.prototype = Object.create(BaseCtrl.prototype);
HTML:
<div ng-controller="MyCtrl as ctrl"></div>
I've just started learning AngularJS and I'm approaching directives. I'd need to create a directive named f.e. "thumbnail" which takes as input a "src" (the image) and create a thumbnail of it.
So far I've coded the javascript function to create the thumbnail which does its job correctly:
function readURL(input) {
var imageSize = 50;
if (input.files && input.files[0]) {
var reader = new FileReader();
reader.onload = function (e) {
var blah = $('#myimage');
blah.attr('src', e.target.result);
var height = blah.height();
var width = blah.width();
if (height < imageSize && width < imageSize) {
if (width > height) {
blah.width(imageSize);
}
else {
blah.height(imageSize)
}
}
};
reader.readAsDataURL(input.files[0]);
}
}
</script>
<body onload="readURL(this);">
<img id="myimage" src="../picture.gif" width="50" alt="your image" />
However I have not been able to find a simple example which produces (in AngularJS) an Element, based on a JS function. Any help ?
Thanks!
It depends on what you want to achieve. If your purpose of creating a thumbnail at client-side is to:
Upload in a smaller size in a web application: perhaps you can make use of a library like http://www.plupload.com.
Display only: you should just use CSS resizing with width and height properties.
<img src="/original/320x320.jpg" style="width: 100px; height: 100px">
Reading/storing in an Ionic/PhoneGap app: try make use of angular-thumbnail (disclaimer: I wrote it)
Doing image manipulation of this kind on the client side is not a very good idea , because, as of now there isn't a good support for Filereader in IE.
You can delegate this either to server(you will find lot of node module for image manipulation) or you can use external services like cloudinay
I am trying to integrate Sencha 4.1 (ExtJS) with the Leaflet mapping library while using Sencha Architect.
When the page loads, the tiles are mixed up and appear offset. I need to drag the page up to be able to see the tiles.
The full project is available here: https://github.com/breizo/SenchaLeaflet.
Here is an excerpt of the custom component created (see full code here: https://github.com/breizo/SenchaLeaflet/blob/master/ux/LeafletMap.js).
constructor: function () {
this.callParent(arguments);
this.on({
resize: 'doResize',
scope: this
});
var ll = window.L;
if (!ll) {
this.setHtml('Leaflet library is required');
}
}
onRender: function() {
this.callParent(arguments);
var renderTo = arguments[0].dom.id;
debugger;
var me = this,
ll = window.L,
element = me.mapContainer,
mapOptions = me.getMapOptions(),
map,
tileLayer;
if (ll) {
// if no center property is given -> use default position
if (!mapOptions.hasOwnProperty('center') || !(mapOptions.center instanceof ll.LatLng)) {
mapOptions.center = new ll.LatLng(47.36865, 8.539183); // default: Zuerich
}
me.setTileLayer(new ll.TileLayer(me.getTileLayerUrl(), me.getTileLayerOptions()));
tileLayer = me.getTileLayer();
mapOptions.layers = [tileLayer];
me.setMap(new ll.Map(renderTo, mapOptions));
map = me.getMap();
// track map events
map.on('zoomend', me.onZoomEnd, me);
map.on('movestart', me.onMoveStart, me);
map.on('moveend', me.onMoveEnd, me);
me.fireEvent('maprender', me, map, tileLayer);
}
},
When debugging it appears that when onRender is called, the parent container of the map is not properly sized yet, in particular its height is only enough to contain the attrib text, about 16 pix. WHen the doResize is called, the container is properly sized, but it doesn't change the end result: the tiles are mixed up and offset.
I tried various changes to the container, but nothing worked...
1) Problem with mixed layers is caused by CSS. Your leaflet.css has wrong path in html, so it's not attached in the document. To fix mixing issue set correct path to css file, or attach it from CDN:
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.4/leaflet.css" />
2) Wrong map offset is caused by extjs generated div:
<div class="x-llmap x-fit-item x-llmap-default" ...></div>
It pushes map container to the bottom and wrong offset calculations are made. You can also fix this using inline style or CSS:
.leaflet-map-pane {
top: 0;
}