├── .gitignore ├── README.md ├── app ├── MainController.js ├── app.js ├── highlightFilter.js ├── mapService.js └── multipleFilter.js ├── assets └── css │ └── style.css ├── index.html ├── slides └── ol3-angular.md └── vendor ├── angular-material-icons.min.js ├── angular-sanitize.min.js ├── angular-sanitize.min.js.map ├── angular.min.js ├── angular.min.js.map ├── bootstrap.min.css ├── bootstrap.min.js ├── jquery.min.js ├── jquery.min.map ├── ol-debug.js ├── ol.css ├── ol.min.js └── ui-bootstrap-tpls-0.12.0.min.js /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Openlayers 3 + AngularJS 1.3.x 2 | [Demo](http://embed.plnkr.co/DHRLO6T89Wn92Dt1KNZT/preview) using an embedded KML source with some London co-working offices taken from Foursquare. 3 | 4 | [Angular — Integration with OpenLayers 3](https://medium.com/angularjs-meetup-south-london/angular-integration-with-openlayers-3-5a6e8d29e635), posted in Medium 5 | 6 | [Slides](https://gnab.github.io/remark/remarkise?url=https%3A%2F%2Frawgit.com%2Fgsans%2Fol3-angular%2Fmaster%2Fslides%2Fol3-angular.md#1) 7 | 8 | ##Features 9 | - loads embedded KML data 10 | - renders markers using a png icon (data:image/png) 11 | - renders list of markers 12 | - added details popup 13 | - switched KML icons with customised styling 14 | - renders markers using svg icon 15 | - added drop-shadow filter to svg (looks too flat otherwise) 16 | - added search filter and highlighting 17 | - added ui bootstrap + tabs 18 | - opens popup on svg markers 19 | - added zoom to max extent 20 | - added scale control 21 | - added config options to init 22 | - added google fonts: Open Sans 23 | - polished ui and format details 24 | - z-index of selected marker is on top 25 | - selects and fills details when clicking on svg marker 26 | - locates marker from list 27 | - fixed ol controls z-index 28 | - added cancel search option 29 | - hides/shows from list 30 | - added multiple queries 31 | - added custom zoom to extent button 32 | - applied [John Papas Guidelines](https://github.com/johnpapa/angular-styleguide) to code 33 | 34 | ##Todo 35 | - all done 36 | 37 | ## Extras 38 | - use material design 39 | - animate icon up on selection 40 | - smoother animations (http://tweene.com/) 41 | - add Google maps integration 42 | - add nice provider control 43 | 44 | ## Want to collaborate? 45 | Email: [gerard.sans@gmail.com](mailto:gerard.sans@gmail.com?Subject=heya%21&Body=%5E_%5E) 46 | 47 | Twitter: [@gerardsans](https://twitter.com/gerardsans) 48 | 49 | Facebook: [AngularJS Meetup Waterloo](https://www.facebook.com/angularjswaterloo) 50 | -------------------------------------------------------------------------------- /app/MainController.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Main Controller 6 | */ 7 | angular 8 | .module('app') 9 | .controller('mainController', Controller); 10 | 11 | Controller.$inject = [ 12 | 'mapService', 13 | '$timeout', 14 | '$rootScope' 15 | ]; 16 | 17 | function Controller(mapService, $timeout, $rootScope) { 18 | var vm = this; 19 | 20 | // map initialisation 21 | mapService.init({ 22 | extractStylesKml: false, 23 | popupOffset: [-4,-43], 24 | featurePropertiesMap: ['name', 'description'], //override default mapping 25 | onFeatureSelected: onFeatureSelected //override default event handler 26 | }); 27 | 28 | vm.staticTabs = { search: true, details: false }; 29 | vm.features = mapService.getFeatures(); 30 | vm.selectFeature = selectFeature; 31 | vm.hideFeatures = hideFeatures; 32 | vm.cancelSearch = cancelSearch; 33 | 34 | 35 | /////////////////////////////////////////////////////////// 36 | // map to view interactions 37 | 38 | /** 39 | * Event handler triggered when a feature is selected 40 | * 41 | * @param {Object} feature - feature selected. 42 | * 43 | * Feature properties are defined by config.featurePropertiesMap. 44 | */ 45 | function onFeatureSelected(feature) { 46 | console.log("feature selected", feature); 47 | // safely run after digest cycle 48 | // needed to handle list selection 49 | $timeout(function(){ 50 | vm.feature = feature; 51 | selectTab("details"); 52 | }); 53 | } 54 | 55 | /** 56 | * Activates tab 57 | * 58 | * @param {String} key - tab id 59 | */ 60 | function selectTab(key){ 61 | if (vm.staticTabs.hasOwnProperty(key)) 62 | vm.staticTabs[key] = true; 63 | } 64 | 65 | /////////////////////////////////////////////////////////// 66 | // view to map interactions 67 | 68 | // subscribe to event 69 | $rootScope.$on("global.hide-features", vm.hideFeatures); 70 | 71 | /** 72 | * Selects a single feature on the map 73 | * 74 | * @param {String} id - feature id 75 | */ 76 | function selectFeature(id){ 77 | mapService.selectFeature(id, true); 78 | } 79 | 80 | /** 81 | * Hides features on the map 82 | * 83 | * @param {Event} event - event object 84 | * @param {Array} features - feature ids that should be shown 85 | */ 86 | function hideFeatures(event, features){ 87 | mapService.hideFeatures(features, vm.search); 88 | }; 89 | 90 | /** 91 | * Cancels search and zoom to extent 92 | */ 93 | function cancelSearch(){ 94 | var undefined, 95 | zoomToExtent = true; 96 | 97 | selectTab("search"); 98 | vm.search = ""; 99 | vm.feature = undefined; 100 | mapService.unselectFeature(zoomToExtent); 101 | }; 102 | } 103 | 104 | })(); -------------------------------------------------------------------------------- /app/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Main app module 3 | */ 4 | angular 5 | .module('app', [ 6 | 'ngMdIcons', 7 | 'ui.bootstrap', 8 | 'ngSanitize' 9 | ]); -------------------------------------------------------------------------------- /app/highlightFilter.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Highlight filter 6 | */ 7 | angular 8 | .module('app') 9 | .filter('highlight', filter); 10 | 11 | /** 12 | * Creates a filter that wraps each search term occurrence in a span element with the 'highlighted' css class 13 | * 14 | * @param {$sceProvider} $sce 15 | * @returns {returnedFunction} highlight filter 16 | * 17 | * See [$sce]{@link https://docs.angularjs.org/api/ng/service/$sce} 18 | * Credits [higlight filter]{@link http://stackoverflow.com/questions/15519713/highlighting-a-filtered-result-in-angularjs/27798600#27798600} 19 | */ 20 | filter.$inject = [ 21 | '$sce' 22 | ]; 23 | 24 | function filter($sce) { 25 | return highlight; 26 | 27 | /** 28 | * Highlight filter 29 | * @param {String} inputText - filter expression 30 | * @param {String} searchTerms - search 31 | */ 32 | function highlight(inputText, searchTerms) { 33 | if (!searchTerms) return inputText; 34 | // split search terms by space character 35 | var terms = searchTerms.split(' ') || [searchTerms]; 36 | 37 | terms.forEach(function(item){ 38 | // avoid messing with HTML tags 39 | // needs a clever regular expression that skips HTML tags matches altogether 40 | if (inputText && inputText.indexOf("<")===-1) 41 | inputText = inputText.replace(new RegExp('(' + item + ')', 'gi'), 42 | '$1') 43 | }); 44 | 45 | return $sce.trustAsHtml(inputText); 46 | } 47 | } 48 | 49 | })(); -------------------------------------------------------------------------------- /app/mapService.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Map Service 6 | */ 7 | angular 8 | .module('app') 9 | .factory('mapService', service); 10 | 11 | function service(){ 12 | // check openlayers is available on service instantiation 13 | // this can be handled with Require later on 14 | if (!ol) return {}; 15 | 16 | var map = {}, //convenience reference 17 | defaults = { 18 | zoom: 15, 19 | startLocation: [0,40], 20 | extractStylesKml: false, 21 | popupOffset: [0,0], 22 | featurePropertiesMap: ['name', 'description', 'address', 'phoneNumber', 'styleUrl'], 23 | onFeatureSelected: function(feature) { console.log("feature selected", feature);} 24 | }, 25 | zIndex = 9999, 26 | popup, 27 | selectedFeature, 28 | myZoomToExtentControl; 29 | 30 | // public API 31 | var ms = { 32 | map: map, // ol.Map 33 | init: init, 34 | getFeatures: getFeatures, 35 | selectFeature: selectFeature, 36 | hideFeatures: hideFeatures, 37 | unselectFeature: unselectFeature 38 | }; 39 | 40 | return ms; 41 | 42 | /////////////////////////////////////////////////////////// 43 | // helper functions 44 | 45 | function olMapFeatures() { 46 | var featuresArray = map //ol.Map 47 | .getLayers() //ol.Collection 48 | .getArray()[1] //ol.layer.Vector 49 | .getSource() //ol.source.KML 50 | .getFeatures() //ol.Feature 51 | return featuresArray; 52 | } 53 | 54 | function getFeatures() { 55 | var f = []; 56 | olMapFeatures() 57 | .forEach(function(olFeature, i) { 58 | var feature = {id: olFeature.getId()}; 59 | f.push(mapFeatureProperties(feature, olFeature)); 60 | }); 61 | return f; 62 | } 63 | 64 | function unselectFeature(zoom) { 65 | var undefined; 66 | selectedFeature = undefined; 67 | $("#map path").each(function(index, item){ 68 | item.setAttribute("class", "icon"); 69 | }); 70 | if (zoom) 71 | zoomToExtent(); 72 | } 73 | 74 | function selectFeature(name, pan){ 75 | var feature; 76 | if (!name) return; 77 | var target = $("#map path[feature='" + escape(name) + "']")[0]; 78 | 79 | //search for feature 80 | olMapFeatures() 81 | .forEach(function(item, i) { 82 | var f = item.get('name'); 83 | if (name==f) 84 | feature = item; 85 | }); 86 | selectedFeature = feature; 87 | 88 | if (feature) { 89 | unselectFeature(); 90 | target.setAttribute("class", "icon selected"); 91 | 92 | //put on top 93 | $(target.parentNode.parentNode) 94 | .parent().parent() 95 | .css('z-index', ++zIndex); 96 | } 97 | 98 | //display feature details and pan 99 | if (pan && feature) { 100 | onFeatureSelected(feature); 101 | panToFeature(feature, map.getView().getZoom()); 102 | 103 | var element = angular.element('#popup'); 104 | $(element).popover('destroy'); 105 | //show popup for feature or hide any previous one 106 | if (feature) { 107 | setTimeout(function(){ 108 | var coord = feature.getGeometry().getCoordinates(); 109 | var title = feature.get('name'); 110 | popup.setPosition(coord); 111 | $(element).popover({ 112 | 'title': title, 113 | 'placement': 'top', 114 | 'animation': false, 115 | 'html': true 116 | //'content': feature.get('description') 117 | }); 118 | $(element).popover('show'); 119 | }, 1000); 120 | } 121 | } 122 | return feature; 123 | } 124 | 125 | function hideFeatures(features, search){ 126 | //hide any popups 127 | var element = angular.element('#popup'); 128 | $(element).popover('destroy'); 129 | 130 | if (!features || features.length===0) { 131 | if (search && search.length>0) 132 | //search with no results: filters all 133 | $("#map path.icon").hide(); 134 | else 135 | //reset after having results 136 | $("#map path.icon").show(); 137 | return; 138 | } 139 | 140 | features.forEach(function(item){ 141 | $("#map path[feature!='" + escape(item) + "'].icon").hide(); 142 | }); 143 | features.forEach(function(item){ 144 | $("#map path[feature='" + escape(item) + "'].icon").show(); 145 | }); 146 | } 147 | 148 | 149 | 150 | function mapFeatureProperties(feature, olFeature) { 151 | if (!olFeature) return feature; 152 | if (!feature) feature = {}; 153 | defaults.featurePropertiesMap.forEach(function(key){ 154 | feature[key] = olFeature.get(key); 155 | }); 156 | return feature; 157 | } 158 | 159 | function onFeatureSelected(olFeature) { 160 | if (!olFeature) return; 161 | var feature = mapFeatureProperties({}, olFeature); 162 | if(defaults.onFeatureSelected) 163 | defaults.onFeatureSelected(feature); 164 | } 165 | 166 | 167 | 168 | // Creates an overlays in the given coordinates 169 | function createSVGOverlay(position, feature) { 170 | if (defaults.extractStylesKml) return; 171 | 172 | var elem = document.createElement('div'); 173 | var svg = angular.element('#svgmarker ng-md-icon').clone(); 174 | 175 | //change path attributes 176 | var path = svg.find('path'); 177 | path.attr('class', 'icon'); 178 | path.attr('filter', 'url(#blur)'); 179 | path.attr('feature', escape(feature.get('name')) ); 180 | 181 | var filter = document.createElement('filter'); 182 | var fe = document.createElement('feGaussianBlur'); 183 | filter.setAttribute('id', 'blur'); 184 | fe.setAttribute('stdDeviation', 3); 185 | filter.appendChild(fe); 186 | svg.find('svg')[0].appendChild(filter); 187 | 188 | elem.appendChild(svg[0]); 189 | 190 | return new ol.Overlay({ 191 | offset: [-2, 12], 192 | element: elem, 193 | position: position, 194 | positioning: 'bottom-center', 195 | stopEvent: false, 196 | }); 197 | } 198 | 199 | function renderSVGFeatures(){ 200 | if (defaults.extractStylesKml) return; 201 | 202 | //wait till directive renders svg element 203 | setTimeout(function() { 204 | olMapFeatures() 205 | .forEach(function(item, i, arr){ 206 | var hidden = item.get('hidden'); 207 | if (!hidden) { 208 | var coordinates = item.getGeometry().getCoordinates(); 209 | var overlay = createSVGOverlay(coordinates, item); 210 | map.addOverlay(overlay); 211 | } 212 | }); 213 | }, 0); 214 | } 215 | 216 | function popupSetup() { 217 | var element = angular.element('#popup'); 218 | 219 | // Add popup showing the position the user clicked 220 | popup = new ol.Overlay({ 221 | element: element, 222 | stopEvent: true, 223 | offset: defaults.popupOffset 224 | }); 225 | map.addOverlay(popup); 226 | 227 | var displayPopup = function(evt){ 228 | var element = popup.getElement(); 229 | var coordinate = evt.coordinate; 230 | var hdms = ol.coordinate.toStringHDMS(ol.proj.transform( 231 | coordinate, 'EPSG:3857', 'EPSG:4326')); 232 | 233 | $(element).popover('destroy'); 234 | popup.setPosition(coordinate); 235 | // the keys are quoted to prevent renaming in ADVANCED mode. 236 | $(element).popover({ 237 | 'placement': 'top', 238 | 'animation': false, 239 | 'html': true, 240 | 'content': '

The location you clicked was:

' + hdms + '' 241 | }); 242 | $(element).popover('show'); 243 | } 244 | 245 | // display popup on click 246 | map.on('click', function(evt) { 247 | if (defaults.extractStylesKml) { 248 | // Regular rendered feature find on click coordinates 249 | var feature = map.forEachFeatureAtPixel(evt.pixel, 250 | function(feature, layer) { 251 | return feature; 252 | }); 253 | } else { 254 | // SVG marker. Search at element attributes 255 | if (!feature && evt.originalEvent.target && evt.originalEvent.target.nodeName == "path") { 256 | var target = evt.originalEvent.target; 257 | var featureId = unescape(target.getAttribute('feature')); 258 | feature = selectFeature(featureId, false); 259 | }; 260 | }; 261 | 262 | //trigger onFeatureSelected event 263 | selectedFeature = feature; 264 | onFeatureSelected(feature); 265 | 266 | $(element).popover('destroy'); 267 | //show popup for feature or hide any previous one 268 | if (feature) { 269 | 270 | setTimeout(function(){ 271 | var coord = feature.getGeometry().getCoordinates(); 272 | popup.setPosition(coord); 273 | var title = feature.get('name'); 274 | $(element).popover({ 275 | 'title': title, 276 | 'placement': 'top', 277 | 'animation': false, 278 | 'html': true 279 | //'content': feature.get('description') 280 | }); 281 | $(element).popover('show'); 282 | }, 1000); 283 | } 284 | 285 | if (feature) { 286 | panToFeature(feature, map.getView().getZoom()); 287 | } 288 | }); 289 | 290 | } 291 | 292 | function panToFeature(feature, zoom) { 293 | var lonLat = feature.getGeometry().getCoordinates() 294 | 295 | var olPixel = map.getPixelFromCoordinate(lonLat); 296 | olPixel[1] -= 40; 297 | lonLat = map.getCoordinateFromPixel(olPixel); 298 | 299 | if (map.getView().getZoom() < zoom) 300 | map.getView().setZoom(zoom); 301 | 302 | var animation = ol.animation.pan({ 303 | duration: 1000, 304 | easing: eval(ol.easing.inAndOut), 305 | source: map.getView().getCenter() 306 | }); 307 | 308 | // Add animation to the render pipeline 309 | map.beforeRender(animation); 310 | // Change center location 311 | map.getView().setCenter(lonLat); 312 | }; 313 | 314 | function init(config){ 315 | var config = angular.extend(defaults, config); 316 | 317 | createMyZoomToExtentControl(); 318 | 319 | // map initialisation 320 | map = new ol.Map({ 321 | target: 'map', 322 | layers: [ 323 | new ol.layer.Tile({ 324 | source: new ol.source.MapQuest({layer: 'osm'}) 325 | }) 326 | ], 327 | view: new ol.View({ 328 | center: ol.proj.transform(config.startLocation, 'EPSG:4326', 'EPSG:3857'), 329 | zoom: config.zoom 330 | }), 331 | controls: ol.control.defaults().extend([ 332 | new myZoomToExtentControl({tipLabel: "Fit to extent"}), 333 | new ol.control.ScaleLine() 334 | ]) 335 | }); 336 | 337 | popupSetup(); 338 | loadKML(); 339 | zoomToExtent(); 340 | } 341 | 342 | function createMyZoomToExtentControl(){ 343 | /** 344 | * @constructor 345 | * @extends {ol.control.Control} 346 | * @param {Object} opt_options - Control options. 347 | */ 348 | myZoomToExtentControl = function (opt_options) { 349 | 350 | var options = opt_options || {}; 351 | 352 | var button = document.createElement('button'); 353 | button.id = 'zoom-to-extent'; 354 | button.setAttribute("title","Zoom to Extent"); 355 | 356 | var span = document.createElement('span'); 357 | 358 | //span.setAttribute("class", "glyphicon glyphicon-record"); 359 | span.innerHTML = 'E'; 360 | button.appendChild(span); 361 | 362 | var this_ = this; 363 | var handler = function(e) { 364 | e.preventDefault(); //cancel click event 365 | zoomToExtent(); 366 | document.getElementById("zoom-to-extent").disabled = true; 367 | setTimeout(function() { 368 | document.getElementById("zoom-to-extent").disabled = false; 369 | }, 1); 370 | }; 371 | 372 | button.addEventListener('click', handler, true); 373 | button.addEventListener('touchstart', handler, true); 374 | 375 | var element = document.createElement('div'); 376 | element.className = 'zoom-to-extent ol-zoom-extent ol-unselectable ol-control'; 377 | element.appendChild(button); 378 | 379 | ol.control.Control.call(this, { 380 | element: element, 381 | target: options.target 382 | }); 383 | 384 | }; 385 | ol.inherits(myZoomToExtentControl, ol.control.ZoomToExtent); 386 | } 387 | 388 | function zoomToExtent() { 389 | var bounds = ol.extent.createEmpty(); 390 | 391 | olMapFeatures() 392 | .forEach(function(item, i, arr){ 393 | var ext = ol.extent.createEmpty(); 394 | ext = item.getGeometry().getExtent(); 395 | bounds = ol.extent.extend(bounds, ext); 396 | }); 397 | 398 | if (bounds) { 399 | // increase bounds using a tenth of the 400 | // maximum distance between coordinates 401 | var incX = Math.abs(bounds[2] - bounds[0]); 402 | var incY = Math.abs(bounds[3] - bounds[1]); 403 | var buffer = (incX>incY)? incX: incY; 404 | var bounds10 = ol.extent.createEmpty(); 405 | ol.extent.buffer(bounds, buffer/5, bounds10); 406 | 407 | var animation = ol.animation.pan({ 408 | easing: eval(ol.easing.inAndOut), 409 | source: map.getView().getCenter() 410 | }); 411 | map.beforeRender(animation); 412 | 413 | map.getView().fitExtent(bounds10, map.getSize()); 414 | } 415 | }; 416 | 417 | function loadKML(){ 418 | var kml = 'Venue URL
Phone: 07984 693440
]]>
#style1-0.078374147415161,51.526985282303,0
Venue URL
]]>
#style1-0.085487365722656,51.522703131223,0
Venue URL
]]>
#style1-0.073642730712891,51.50764732314,0
]]>#style1-0.087708234786987,51.525032831563,0Venue URL
Phone: 020 7248 0199
]]>
#style1-0.10269641876221,51.518998061413,0
Venue URL
Phone: 020 7148 6720
]]>
#style1-0.13124592579464,51.507792367419,0
'; 419 | var kmlSource = new ol.source.KML({ 420 | projection: 'EPSG:3857', 421 | text: kml, 422 | extractStyles: defaults.extractStylesKml 423 | }); 424 | 425 | var vectorLayer = new ol.layer.Vector({ 426 | source: kmlSource, 427 | style: kmlStyle 428 | }); 429 | 430 | function kmlStyle(feature, resolution){ 431 | // use default styles if using kml icons 432 | if (!defaults.extractStylesKml) return []; 433 | 434 | return [new ol.style.Style({ 435 | image: new ol.style.Circle({ 436 | radius: 5, 437 | fill: new ol.style.Fill({ 438 | color: 'rgba(123, 152, 188, 0)' 439 | }), 440 | stroke: new ol.style.Stroke({ 441 | color: 'rgba(123, 152, 188, 0)', 442 | width: 1 443 | }) 444 | }) 445 | })]; 446 | } 447 | 448 | // Add vectory layer to map 449 | map.addLayer(vectorLayer); 450 | 451 | //render custom markers 452 | renderSVGFeatures(); 453 | } 454 | } 455 | 456 | 457 | })(); -------------------------------------------------------------------------------- /app/multipleFilter.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Multiple terms search filter 6 | */ 7 | angular 8 | .module('app') 9 | .filter('multiple', filter); 10 | 11 | /** 12 | * Creates a filter that takes into account multiple search terms 13 | * 14 | * @param {$rootScope} $rootScope 15 | * @returns {Function} multiple filter 16 | * 17 | * Credits [multiple filter]{@link http://stackoverflow.com/questions/23504757/angular-js-filter-by-logical-and-using-multiple-terms} 18 | */ 19 | filter.$inject = [ 20 | '$rootScope' 21 | ]; 22 | 23 | function filter($rootScope) { 24 | return multiple; 25 | 26 | /** 27 | * Multiple filter 28 | * @param {String} items - filter expression 29 | * @param {String} searchTerms - search 30 | */ 31 | function multiple(items, searchTerms) { 32 | // return all items if searchTerms is empty 33 | if (!searchTerms) { 34 | triggerHideFeatures([], $rootScope); 35 | return items; 36 | } 37 | 38 | var terms = searchTerms.split(' '), 39 | matchingItems = [], 40 | passTest; 41 | 42 | items.forEach(function(item){ 43 | passTest = true; 44 | terms.forEach(function(term){ 45 | // we check the default KML properties 46 | passTest = passTest && ( 47 | (item.name.toLowerCase().indexOf(term.toLowerCase()) > -1) || 48 | (item.description.toLowerCase().indexOf(term.toLowerCase()) > -1) 49 | ); 50 | }); 51 | // Add item to return array only if passTest is true, 52 | // all search terms were found in item 53 | if (passTest) { matchingItems.push(item); } 54 | }); 55 | 56 | triggerHideFeatures(matchingItems, $rootScope); 57 | 58 | return matchingItems; 59 | } 60 | 61 | /** 62 | * Notifies the application with the matching features 63 | * @param {Array} matchingItems - filtered features 64 | * @param {Object} $rootScope - $rootScope 65 | */ 66 | function triggerHideFeatures(matchingItems, $rootScope){ 67 | var featuresArray = matchingItems.map(function(feature){ 68 | return feature.name; 69 | }) 70 | $rootScope.$broadcast("global.hide-features", featuresArray); 71 | } 72 | } 73 | 74 | })(); -------------------------------------------------------------------------------- /assets/css/style.css: -------------------------------------------------------------------------------- 1 | /*----------------------------------------*/ 2 | /* map styles 3 | */ 4 | html, body, #map { 5 | padding: 0; 6 | margin: 0; 7 | font-family: "Open Sans", Helvetica, Arial; 8 | } 9 | 10 | #map { 11 | width: 100%; 12 | height: 200px; 13 | } 14 | 15 | #map .popover { 16 | width: 200px; 17 | z-index: 9999999; 18 | } 19 | 20 | /* custom svg marker styles */ 21 | #map svg .icon { 22 | cursor: pointer; 23 | } 24 | 25 | #map svg .icon.selected { 26 | fill: #dd1c77; 27 | } 28 | 29 | /* custom zoom to extent control */ 30 | #map .ol-control, .ol-scale-line { 31 | z-index: 9999999; 32 | } 33 | 34 | 35 | /*----------------------------------------*/ 36 | /* tabs 37 | */ 38 | 39 | /* remove bootstrap rounded tab corners */ 40 | .tabset .nav-tabs > li > a { 41 | -webkit-border-radius: 0; 42 | -moz-border-radius: 0; 43 | border-radius: 0; 44 | } 45 | 46 | .tabset .details-tab .highlighted { 47 | background: yellow; 48 | } 49 | 50 | .tabset .search-tab, .tabset .details-tab { 51 | margin: 15px; 52 | } 53 | 54 | /* search tab */ 55 | .tabset .search-tab ul { 56 | list-style-type: none; 57 | padding: 0px; 58 | } 59 | 60 | .tabset .search-tab .input-group .input-group-addon { 61 | padding-bottom: 4px; 62 | } 63 | 64 | .tabset .list { 65 | margin-top: 2px; 66 | max-height: 150px; 67 | width: 100%; 68 | overflow-y: auto; 69 | background-color: #fff; 70 | border: 1px solid #eee; 71 | } 72 | 73 | .tabset .feature-result { 74 | padding: 10px; 75 | cursor: pointer; 76 | } 77 | 78 | .tabset .feature-result:hover { 79 | background-color: #dd1c77; 80 | border: 1px solid #ddd; 81 | color: #fff; 82 | } 83 | 84 | /* cancel search link */ 85 | .tabset .vcenter { 86 | padding: 10px 15px; 87 | cursor: pointer; 88 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | OpenLayers 3 + Angular 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
39 |
40 | 41 |
Cancel Search
42 | 43 |
44 |
45 | 46 | 47 |
48 | {{filtered.length}} Result/s 49 |
50 |
51 | 52 |
53 |
    54 |
  • 55 |
    56 | {{f.name}} 57 |
    58 |
  • 59 |
60 |
61 |
62 |
63 | 64 |
65 |
66 |

67 |

68 |
69 |
70 |

No Details available.

71 |

Select a marker on the map to see the details.

72 |
73 |
74 |
75 |
76 | 77 |
78 |
79 | 80 | 81 |
82 | 83 | 84 |
85 | 86 |
87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /slides/ol3-angular.md: -------------------------------------------------------------------------------- 1 | class: center, middle 2 | 3 | # Angular — Integration with OpenLayers 3 4 | 5 | 6 | [Gerard Sans](https://twitter.com/gerardsans) 7 | 8 | --- 9 | 10 | # Agenda 11 | 12 | ###1. Who Am I? 13 | ###2. Demo 14 | ###3. Integration 15 | ###4. Extras 16 | ###5. Q&A 17 | 18 | --- 19 | 20 | # 1. Who Am I? 21 | 22 | .section[ 23 | 24 | - **Gerard Sans**, Senior JavaScript Developer 25 | - CS Engineer (5 years degree) 26 | - Former **C# .NET Developer**, doing mainly Web Development (overall +10 years) 27 | - Worked for consultancies, end-clients and startups (Spain, Germany, UK and Brazil) 28 | - Just recently switched to **full-time JavaScript/AngularJS Developer** (1 year) 29 | - Planning to get the [Google Developer Expert badge for AngularJS](https://developers.google.com/experts/become-an-expert) in 2015. 30 | 31 | - **AngularJS Community** 32 | - Started the [AngularJS Meetup Waterloo](https://www.facebook.com/angularjswaterloo) last August to help Josh and Ed. 33 | - New format. Hacking sessions (**looking for sponsors**) 34 | - Idea to create the first **Angular Festival** (Panel, Submissions, Teams, Categories). [Google doc](https://docs.google.com/document/d/1kcbcCOiLmcKuvyoocYqTStsC5_kMzeq4IKUlfqHPuW0/edit?usp=sharing) 35 | - Help me make it happen! =) 36 | - Started blogging about JS/AngularJS at [Coderwall](https://coderwall.com/p/u/gsans) and later on [Medium](https://medium.com/@gerard.sans). 37 | 38 | ] 39 | 40 | --- 41 | 42 | # 2. Demo 43 | 44 | - Anybody with experience with OpenLayers 3? Maps? 45 | - How about your experience with Angular? new, 3m, 6m, +1y? 46 | 47 | ##[Demo](http://plnkr.co/edit/DHRLO6T89Wn92Dt1KNZT?p=preview) 48 | 49 | --- 50 | 51 | # 3. Integration (1/2) 52 | 53 | - **Architecture** 54 | - First Level: **Model** Markers/Coordinates + **View** (map, search/details tabs) + **Controller** 55 | - **View** uses *directives*, *expressions* and *filters* 56 | - Second Level: **ol3 library**, **Map Service**, **Filters** (multiple, highlight) 57 | - Communication is done using **events** (*event handlers*) 58 | 59 | - **Map to Angular (View)** 60 | - via `$timeout(function(){...})` 61 | 62 | - **Angular (View) to Map** 63 | - via message bus `$broadcast` and `$on` 64 | - via `ng-click` 65 | 66 | --- 67 | 68 | # 3. Integration (2/2) 69 | 70 | - **Angular Providers Overview** 71 | - ngMdIcons (Angular Material Icons), ui.bootstrap, ngSanitize 72 | 73 | - **Angular Directives Overview** 74 | - ng-app, ng-controller, ng-show, ng-click, ng-model, ng-model-options, ng-repeat 75 | - ng-md-icon (ngMdIcons) 76 | - ng-bind-html ($sce, Strict Contextual Escaping, privileged context) 77 | 78 | --- 79 | 80 | # 4. Extras 81 | 82 | - Extending default Angular single search term to accept multiple terms. 83 | - Custom SVG markers, with custom styles and adding a drop-shadow effect. 84 | - [Angular Material Icons with custom fill-color and size](http://klarsys.github.io/angular-material-icons/). 85 | - Includes optional [SVG Morpheus](http://alexk111.github.io/SVG-Morpheus/). 86 | - Custom OpenLayers Button Control, zoom to extent. 87 | - Using OpenLayers smooth animations. 88 | - Styling: using [UI Bootstrap](http://angular-ui.github.io/bootstrap/) and Open Sans Google font. 89 | 90 | - Links: 91 | - Swiss Confederation geo.admin.ch (OpenLayers 3 + Angular 1.3.x). [live](http://map.geo.admin.ch/) | [github](https://github.com/geoadmin/mf-geoadmin3) 92 | 93 | --- 94 | 95 | # 5. Q&A 96 | 97 | - Check out this [post](https://medium.com/angularjs-meetup-south-london/angular-integration-with-openlayers-3-5a6e8d29e635) for more in-depth details. 98 | 99 | - You can contact me at `gerard.sans@gmail.com` if you want to collaborate or have any questions. 100 | 101 | # Thank you! 102 | 103 | 132 | -------------------------------------------------------------------------------- /vendor/angular-sanitize.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.3.13 3 | (c) 2010-2014 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(n,h,p){'use strict';function E(a){var d=[];s(d,h.noop).chars(a);return d.join("")}function g(a){var d={};a=a.split(",");var c;for(c=0;c=c;e--)d.end&&d.end(f[e]);f.length=c}}"string"!==typeof a&&(a=null===a||"undefined"===typeof a?"":""+a);var b,k,f=[],m=a,l;for(f.last=function(){return f[f.length-1]};a;){l="";k=!0;if(f.last()&&x[f.last()])a=a.replace(new RegExp("([\\W\\w]*)<\\s*\\/\\s*"+f.last()+"[^>]*>","i"),function(a,b){b=b.replace(H,"$1").replace(I,"$1");d.chars&&d.chars(r(b));return""}),e("",f.last());else{if(0===a.indexOf("\x3c!--"))b=a.indexOf("--",4),0<=b&&a.lastIndexOf("--\x3e",b)===b&&(d.comment&& 8 | d.comment(a.substring(4,b)),a=a.substring(b+3),k=!1);else if(y.test(a)){if(b=a.match(y))a=a.replace(b[0],""),k=!1}else if(J.test(a)){if(b=a.match(z))a=a.substring(b[0].length),b[0].replace(z,e),k=!1}else K.test(a)&&((b=a.match(A))?(b[4]&&(a=a.substring(b[0].length),b[0].replace(A,c)),k=!1):(l+="<",a=a.substring(1)));k&&(b=a.indexOf("<"),l+=0>b?a:a.substring(0,b),a=0>b?"":a.substring(b),d.chars&&d.chars(r(l)))}if(a==m)throw L("badparse",a);m=a}e()}function r(a){if(!a)return"";var d=M.exec(a);a=d[1]; 9 | var c=d[3];if(d=d[2])q.innerHTML=d.replace(//g,">")}function s(a,d){var c=!1,e=h.bind(a,a.push);return{start:function(a,k,f){a=h.lowercase(a);!c&&x[a]&&(c=a);c||!0!==C[a]||(e("<"),e(a), 10 | h.forEach(k,function(c,f){var k=h.lowercase(f),g="img"===a&&"src"===k||"background"===k;!0!==P[k]||!0===D[k]&&!d(c,g)||(e(" "),e(f),e('="'),e(B(c)),e('"'))}),e(f?"/>":">"))},end:function(a){a=h.lowercase(a);c||!0!==C[a]||(e(""));a==c&&(c=!1)},chars:function(a){c||e(B(a))}}}var L=h.$$minErr("$sanitize"),A=/^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/,z=/^<\/\s*([\w:-]+)[^>]*>/,G=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g, 11 | K=/^]*?)>/i,I=/"\u201d\u2019]/,c=/^mailto:/;return function(e,b){function k(a){a&&g.push(E(a))} 15 | function f(a,c){g.push("');k(c);g.push("")}if(!e)return e;for(var m,l=e,g=[],n,p;m=l.match(d);)n=m[0],m[2]||m[4]||(n=(m[3]?"http://":"mailto:")+n),p=m.index,k(l.substr(0,p)),f(n,m[0].replace(c,"")),l=l.substring(p+m[0].length);k(l);return a(g.join(""))}}])})(window,window.angular); 16 | //# sourceMappingURL=angular-sanitize.min.js.map 17 | -------------------------------------------------------------------------------- /vendor/angular-sanitize.min.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version":3, 3 | "file":"angular-sanitize.min.js", 4 | "lineCount":15, 5 | "mappings":"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", 6 | "sources":["angular-sanitize.js"], 7 | "names":["window","angular","undefined","sanitizeText","chars","buf","htmlSanitizeWriter","writer","noop","join","makeMap","str","obj","items","split","i","length","htmlParser","html","handler","parseStartTag","tag","tagName","rest","unary","lowercase","blockElements","stack","last","inlineElements","parseEndTag","optionalEndTagElements","voidElements","push","attrs","replace","ATTR_REGEXP","match","name","doubleQuotedValue","singleQuotedValue","unquotedValue","decodeEntities","start","pos","end","index","text","stack.last","specialElements","RegExp","all","COMMENT_REGEXP","CDATA_REGEXP","indexOf","lastIndexOf","comment","substring","DOCTYPE_REGEXP","test","BEGING_END_TAGE_REGEXP","END_TAG_REGEXP","BEGIN_TAG_REGEXP","START_TAG_REGEXP","$sanitizeMinErr","value","parts","spaceRe","exec","spaceBefore","spaceAfter","content","hiddenPre","innerHTML","textContent","innerText","encodeEntities","SURROGATE_PAIR_REGEXP","hi","charCodeAt","low","NON_ALPHANUMERIC_REGEXP","uriValidator","ignore","out","bind","validElements","forEach","key","lkey","isImage","validAttrs","uriAttrs","$$minErr","optionalEndTagBlockElements","optionalEndTagInlineElements","extend","svgElements","htmlAttrs","svgAttrs","document","createElement","module","provider","$SanitizeProvider","$get","$$sanitizeUri","uri","filter","$sanitize","LINKY_URL_REGEXP","MAILTO_REGEXP","target","addText","addLink","url","isDefined","raw","substr"] 8 | } 9 | -------------------------------------------------------------------------------- /vendor/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.2 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.2",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.2",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active"));a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.2",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&"show"==b&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a(this.options.trigger).filter('[href="#'+b.id+'"], [data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.2",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0,trigger:'[data-toggle="collapse"]'},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":a.extend({},e.data(),{trigger:this});c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=c(d),f={relatedTarget:this};e.hasClass("open")&&(e.trigger(b=a.Event("hide.bs.dropdown",f)),b.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f)))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.2",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('