ng-hide & ng-show in leaflet legend - arrays

I tried to create a leaflet legend using the 'ng-show' and 'ng-hide' attributes.
Unfortunately, the legend is not created on site load but on map load.
The attributes don't seem to work if they are added with javascript directly.
This code:
onAdd: function() {
var controlDiv = L.DomUtil.create('div', 'air-quality-legend');
controlDiv.setAttribute('ng-hide', 'true');
controlDiv.className = "airQualityIndex";
L.DomEvent
.addListener(controlDiv, 'click', L.DomEvent.stopPropagation)
.addListener(controlDiv, 'click', L.DomEvent.preventDefault);
var table = document.createElement('table');
var tr = document.createElement('table');
var td = document.createElement('table');
td.innerHTML = "test";
tr.appendChild(td);
table.appendChild(tr);
controlDiv.appendChild(table);
return controlDiv;
}
Produces that output.
As described there is a table when there should not.
Is there any way to add 'ng-hide' or 'ng-show' via javascript on runtime?
Thank you for your help in advance.

You'll need to compile the DOM of your custom control. To do that, you'll need to inject $compile into your controller, then after having added the control to your map use the getContainer method on your control instance and run $compile on it and attach it to the scope:
Control:
L.Control.Custom = L.Control.extend({
onAdd: function () {
var container = L.DomUtil.create('div', 'leaflet-control-custom')
header = L.DomUtil.create('h1', 'leaflet-control-custom-header', container);
header.textContent = 'NG-Hide test';
header.setAttribute('ng-hide', 'hide');
return container;
}
});
Controller:
angular.module('app').controller('controller', [
'$scope', 'leaflet', '$compile',
function ($scope, leaflet, $compile) {
$scope.hide = false;
leaflet.map.then(function (map) {
var control = new L.Control.Custom().addTo(map);
$compile(control.getContainer())($scope);
});
}
]);
Here's a working example on Plunker: http://plnkr.co/edit/xzRwTp9OZ8Zp8v7ktt2c?p=preview

Related

AngularJS ng-click linking to a model

I am building a small rss reader using Express(ie Jade) and Angular. I have a dropdown menu, where the menu items are populated by a list of items in a model.
Whatever the user chooses as an item, there is a rss url attached to it and it should trigger a factory.
This is the jade part:
div.btn-group
button.btn.btn-info(type='button') {{loadButtonText}}
button.btn.btn-info.dropdown-toggle(data-toggle='dropdown')
span.caret
span.sr-only Toggle Dropdown
ul.dropdown-menu(role='menu')
li(ng-repeat='rss in RSSList')
a(href='#', ng-click="feedSrc='{{rss.url}}';loadFeed($event);") {{rss.Title}}
input.form-control(type='text', autocomplete='off', placeholder="This is where your feed's url will appear" data-ng-model='feedSrc')
This is my angular controller:
var News = angular.module('myApp', []);
News.controller('FeedCtrl', ['$scope','FeedService', function($scope, Feed){
$scope.loadButtonText = 'Choose News Feed';
$scope.RSSList = [
{Title: "CNN", url: 'http://rss.cnn.com/rss/cnn_topstories.rss'},
{Title: "Reuters", url: 'http://feeds.reuters.com/news/usmarkets'}
];
$scope.loadFeed = function (e) {
Feed.parseFeed($scope.feedSrc).then(function (res) {
$scope.loadButtonText=angular.element(e.target).text();
$scope.feeds = res.data.responseData.feed.entries;
}); }}]);
News.factory('FeedService', ['$http', function($http){
return {parseFeed: function (url){
return $http.jsonp('//ajax.googleapis.com/ajax/services/feed/load?v=1.0&num=50&callback=JSON_CALLBACK&q='+encodeURIComponent(url));}}
}]);
It seems feedSrc in ng-click doesn't capture rss.url and can not be passed as argument to the parseFeed function.
I tried to pass rss.url directly into loadFeed, like this ng-click="loadFeed({{rss.url}});" and even ng-click="loadFeed('{{rss.url}}');" I didn't work either.
Simply pass it this way :
ng-click="loadFeed(rss.url)"
No need to use the {{ }} in ng-click
Why not to use just:
Jade:
a(href='#', ng-click="loadFeed(rss.url,$event)") {{rss.Title}}
Controller:
$scope.loadFeed = function (url, e) {
Feed.parseFeed(url).then(function (res) {
$scope.loadButtonText=angular.element(e.target).text();
$scope.feeds = res.data.responseData.feed.entries;
}); }}]);

How to factor scope dependent plugins into a service for ng-grid

I am developing a project that uses ngGrid to enable the user to view and select data.
I am using a couple plugins I have pick up from the community.
a double click listener taken from ngGrid double click row to open pop-up for editing a row and a filter bar taken from http://plnkr.co/edit/c8mHmAXattallFRzXSaG?p=preview
var ngGridDoubleClick = function() {
var self = this;
self.$scope = null;
self.myGrid = null;
// The init method gets called during the ng-grid directive execution.
self.init = function(scope, grid, services) {
// The directive passes in the grid scope and the grid object which
// we will want to save for manipulation later.
self.$scope = scope;
self.myGrid = grid;
// In this example we want to assign grid events.
self.assignEvents();
};
self.assignEvents = function() {
// Here we set the double-click event handler to the header container.
self.myGrid.$viewport.on('dblclick', self.onDoubleClick);
};
// double-click function
self.onDoubleClick = function(event) {
self.myGrid.config.dblClickFn(self.$scope.selectedItems[0]);
};
}
var filterBarPlugin = {
init: function(scope, grid) {
filterBarPlugin.scope = scope;
filterBarPlugin.grid = grid;
$scope.$watch(function() {
var searchQuery = "";
angular.forEach(filterBarPlugin.scope.columns, function(col) {
if (col.visible && col.filterText) {
var filterText = (col.filterText.indexOf('*') == 0 ? col.filterText.replace('*', '') : "^" + col.filterText) + ";";
searchQuery += col.displayName + ": " + filterText;
}
});
return searchQuery;
}, function(searchQuery) {
filterBarPlugin.scope.$parent.filterText = searchQuery;
filterBarPlugin.grid.searchProvider.evalFilter();
});
},
scope: undefined,
grid: undefined
};
I am loading them like this:
$scope.gridOptions = {
data : 'parts.partlist',
columnDefs : 'parts_fields',
dblClickFn : $scope.addToConstruct,
multiSelect : false,
showGroupPanel : true,
jqueryUIDraggable: true,
plugins: [ filterBarPlugin, ngGridDoubleClick ],
headerRowHeight : 60,
sortInfo : SortOpts,
selectedItems : $scope.selection
}
So the problem is is that these plugin definitions exist in a controller and I would like to pull them out to a service so that I can use them anywhere without copy/pasting ~50 lines of code.
I have tried to put them in a factory but they need access to the $scope object and I was unsuccessful in injecting $scope into my factory.
Any ideas on how to best turn these plugins into reusable components?
You don't need to inject $scope into your factory, the init() function of the plugin does that for you in this line of each plugin (ref ngGrid Wiki):
self.init = function(scope, grid) {
All you have to do is stick them in a factory, and return each function from the factory. I reconfigured filterBarPlugin into a function instead of a JSON object in this case... See sample code here: http://plnkr.co/edit/Uw28KUbIOtDps2GaJahb?p=preview

angularjs directive communication

I am new to angularjs, and I have been reading a ton of documentation and reading through various articles and tutorials as well as videos to help me figure this stuff out.
I am trying to get two directives to interchange information between themselves. a really simplified version of what i am trying to do is at odetocode (http://odetocode.com/blogs/scott/archive/2013/09/11/moving-data-in-an-angularjs-directive.aspx) where k scott allen has wrapped his directives with a div that has the ng-controller attribute it works beautifully.
I have a slightly more complex test I am working on, and I am trying to get it to work similarly to the code I have mentioned.
my two directives talk to each other when I list the ng-controller attribute in the actual template for each directive. it works, but I don't think it is correct. the actual controller code is run twice, once for each directive.
when I move the controller into the div that wraps the two directives, the two directives stop interacting (the change event in the location-selector template doesn't change the park in the controller). I am pretty sure it has something to do with the scope. if anyone can point me in the right direction, or where I should look for information, that would be much appreciated.
here is my fiddle showing my code http://jsfiddle.net/jgbL9/25/
<div ng-app="myApp">
<location-selector ></location-selector ><br/>
<portal-map ></portal-map >
</div>
var App = angular.module('myApp', ['ngResource']);
App.directive('locationSelector',['parkList', function(parkList) {
return {
restrict: 'E',
scope: {
parkId : '=',
parkName : '='
},
template: '<select ng-controller="portalMapCtrl"'+
' ng-model="listParks" ng-change="changePark()" '+
' park-id="parkId" park-name="parkName" ' +
' ng-options="park as park.attributes.NAME for park in Parks" >'+
'</select>',
link: function (scope,element,attrs){
parkList.getListFromGIS().success(function(data) {
scope.Parks = data.features;
});
}
};
}]);
App.directive('portalMap', function(){
return {
restrict: 'E',
scope:{
parkId: "=",
parkName: "="
},
template: '<style type="text/css" media="screen">'+
'#mapCanvas {height: 500px; width:75%; border:thin black solid; }'+
'</style>'+
'<div id="mapCanvas" park-id="parkId" park-name="parkName" ng-controller="portalMapCtrl" ></div>'
}
});
App.controller('portalMapCtrl',['$scope','parkList', function( $scope, parkList ){
var map = {};
var STREETMAPSERVICE = "https://gis.odot.state.or.us/ArcGIS/rest/services/BASEMAPS/Basemap_Streets/MapServer";
var FOTOSSERVICE = "https://maps.prd.state.or.us/arcgis/rest/services/ESRI_TEST/MapServer?f=jsapi";
var UTILSSERVICE = "http://gis.prd.state.or.us/ArcGIS/rest/services/OPRDAssets/MapServer";
var UTILSSERVICE_PARKLAYER = 0;
var UTILSSERVICE_STRUCTUREPOLY = 7;
var UTILSSERVICE_SURFACE = 11;
var UTILSSERVICE_PARCELS = 12;
var timer;
var ALL_LAYERS = [UTILSSERVICE_PARKLAYER,UTILSSERVICE_STRUCTUREPOLY,UTILSSERVICE_SURFACE,UTILSSERVICE_PARCELS];
$scope.parkId = 0;
$scope.parkName = "";
$scope.changePark = function (){
require(["esri/SpatialReference","esri/geometry/Polygon"],
function(SpatialReference,Polygon){
console.log('change park');
$scope.parkId = $scope.listParks.attributes.PARK_HUB_ID;
$scope.parkName = $scope.listParks.attributes.NAME;
parkList.getParkFromGIS($scope.parkId).then(function(data){
var x = data.data;
var y = x.features[0];
var rings = y['geometry'];
var poly = new Polygon(rings);
var xtnt = poly.getExtent();
var sr = new SpatialReference({wkid:2992});
xtnt.setSpatialReference (sr);
map.setExtent(xtnt,true);
});
});
};
function addService(srvc, srvcType, lyrId){require([
"esri/layers/ArcGISTiledMapServiceLayer",
"esri/layers/ArcGISDynamicMapServiceLayer",
"esri/layers/ImageParameters"], function(Tiled,Dynamic,Parameters){
var mapService = {};
if(srvcType == 'Tiled'){
mapService = new Tiled(srvc);
}else{
var imageParameters = new Parameters();
imageParameters.layerIds = lyrId;
imageParameters.transparent = true;
mapService = new Dynamic(srvc,{"imageParameters":imageParameters});
}
map.addLayer(mapService);
});
}
function createMap(){
require(["esri/map"],function(Map){
console.log('create map');
map = new Map("mapCanvas");
addService(STREETMAPSERVICE,'Tiled');
addService(FOTOSSERVICE,'Tiled');
addService(UTILSSERVICE,'Dynamic',ALL_LAYERS);
});
}
createMap();
}]);
App.factory('parkList',['$http', function($http) {
return {
getListFromGIS: function() {
var myUrl = 'http://maps.prd.state.or.us/arcgis/rest/services/ESRI_TEST/MapServer/0/query?where=OBJECTID+%3E+0&geometryType=esriGeometryEnvelope&spatialRel=esriSpatialRelIntersects&outFields=PARK_HUB_ID%2CNAME&returnGeometry=false&&returnIdsOnly=false&returnCountOnly=false&orderByFields=NAME&returnZ=false&returnM=false&returnDistinctValues=true&f=pjson&callback=JSON_CALLBACK';
return $http ({ url: myUrl, method: 'JSONP'});
},
getParkFromGIS: function (id){
var myUrl = "http://maps.prd.state.or.us/arcgis/rest/services/ESRI_TEST/MapServer/0/query?where=PARK_HUB_ID%3d"+id+"&f=pjson&callback=JSON_CALLBACK";
return $http ({ url: myUrl, method: 'JSONP'});
},
JSON_CALLBACK: function(data) {}
};
}]);
(this is the code working with the ng-controller listed in the template of each directive).
any other comments or suggestions you would like to offer about my code structure or code choices will be appreciated also, as I mentioned, I am learning, and the more I can learn the more fun I will have coding.
I believe you're having problems due to your isolate scopes on your directives (the scope: { } in your link function).
I would suggest inlining the templates into your application, instead of trying to make them directives.
In particular, the locationSelector is going to be hard to make a directive - it's usually easier to have your input elements be a part of the element their controller is on.
If you did want to have them be a directive, I'd suggest passing the listParts value into the changePark function:
<select ... ng-change="changePark(listParks)" ...>

AngularJS-ui toggle accordion depending on window width

I am looking to add the accordion functionality programatically when the browser is under a certain width. I thought I might just destroy the accordion when the $window watch reports a width under, say 400px, and re-initiate it again when not. But that seems to be a silly idea after searching for a couple hours. Is there a way to do this or a better way to archive the same result?
Ok so for everyone that might face a similar issue, this is what we ended up doing:
In the Angular config we added a dynamic router:
$routeProvider
.when('/:a', {
template: '<div data-ng-include="templateUrl">Loading...</div>',
controller: 'DynamicController'
})
Then the controller looks like this:
.controller('DynamicController', function ($scope, $rootScope, $routeParams) {
var mobile = false; //set desktop first as mobil has more logic
var maxWidth = 768; //adjust this value in media queries aswell!
var _isMobile = "";
//listen for changes on the scope var windowWidth
$rootScope.$watch('windowWidth',function(){
//check that we have not exceeded our max width
if($rootScope.windowWidth < maxWidth) {
_isMobile = "mobile"; //set the current view based on the width to mobile
}
else {
_isMobile = "desktop"; //set the current view based on the width to desktop
}
$scope.templateUrl = function() {
var temp = (_isMobile ? _isMobile : 'desktop');
return 'views/' + temp + '.html';
}(); //immediate gets called every time the windowWidth var changes passes in the current required view as a string.
});
})
Which has a watch set up for this directive:
angular.module('app.config',[])
.directive('resize', function ($window) {
return {
controller: function ($scope, $rootScope) {
$rootScope.windowWidth = $window.innerWidth;
angular.element($window).bind('resize', function () {
$rootScope.$apply(function () {
$rootScope.windowWidth = $window.innerWidth;
});
});
}
};
});
Now we got two views for mobile and desktop which both draw in the same content but wrap them into either an accordion or in another container.
That seems to work nicely and hope will help some other internet traveler.

Twitter typeahead.js: Possible to use Angular JS as template engine? If not how do I replace "{{}}" for Hogan/Mustache js?

I am working with twitter's typeahead.js and I was wondering if it was possible to modify hogan.js to use something other than {{}}?
I am looking at the minified code now and I have no idea what to change for something so simple. Doing a find and replace breaks it.
I am asking this mainly because I'm using Angular JS but twitter's typeahead requires a templating engine, causing hogan and angular's {{}} to clash. An even better solution would be simply modifying Angular JS (I know it's not a templating engine) and ditching Hogan to fit the following criteria:
Any template engine will work with typeahead.js as long as it adheres to the following API:
// engine has a compile function that returns a compiled template
var compiledTemplate = ENGINE.compile(template);
// compiled template has a render function that returns the rendered template
// render function expects the context to be first argument passed to it
var html = compiledTemplate.render(context);
Ignore the documentation on this, just look at the source code:
function compileTemplate(template, engine, valueKey) {
var renderFn, compiledTemplate;
if (utils.isFunction(template)) {
renderFn = template;
} else if (utils.isString(template)) {
compiledTemplate = engine.compile(template);
renderFn = utils.bind(compiledTemplate.render, compiledTemplate);
} else {
renderFn = function(context) {
return "<p>" + context[valueKey] + "</p>";
};
}
return renderFn;
}
It happens you can just pass a function to template, callable with a context object which contains the data you passed in the datum objects at the time of instantiation, so:
$('#economists').typeahead({
name: 'economists',
local: [{
value: 'mises',
type: 'austrian economist',
name: 'Ludwig von Mises'
}, {
value: 'keynes',
type: 'keynesian economist',
name: 'John Maynard Keynes'
}],
template: function (context) {
return '<div>'+context.name+'<span>'+context.type.toUpperCase()+'</span></div>'
}
})
If you want to use Hogan.js with Angular, you can change the delimiters by doing something like:
var text = "my <%example%> template."
Hogan.compile(text, {delimiters: '<% %>'});
It appears that the template engine result that typeahead.js expects is an html string and not the dom element (in dropdown_view.js). So I am not sure there is a good solution for using an angular template. As a test I was able to get it binding the result to an angular template but it has to render to an element and then get the html value from the element after binding with the data. I don't like this approach but I figured someone might find it useful. I think I will go with a template function like in the previous post.
Jade template looks like
.result
p {{datum.tokens}}
p {{datum.value}}
Directive
angular.module('app').directive('typeahead', [
'$rootScope', '$compile', '$templateCache',
function ($rootScope, $compile, $templateCache) {
// get template from cache or you can load it from the server
var template = $templateCache.get('templates/app/typeahead.html');
var compileFn = $compile(template);
var templateFn = function (datum) {
var newScope = $rootScope.$new();
newScope.datum = datum;
var element = compileFn(newScope);
newScope.$apply();
var html = element.html();
newScope.$destroy();
return html;
};
return {
restrict: 'A',
link: function (scope, element, attrs, ctrl) {
element.typeahead({
name: 'server',
remote: '/api/search?q=%QUERY',
template: templateFn
});
element.on('$destroy', function () {
element.typeahead('destroy');
});
element.on('typeahead:selected', function () {
element.typeahead('setQuery', '');
});
}
};
}
]);

Resources