Table of Contents for
Mastering OpenLayers 3

Version ebook / Retour

Cover image for bash Cookbook, 2nd Edition Mastering OpenLayers 3 by Gábor Farkas Published by Packt Publishing, 2016
  1. Cover
  2. Table of Contents
  3. Mastering OpenLayers 3
  4. Mastering OpenLayers 3
  5. Credits
  6. About the Author
  7. About the Reviewer
  8. www.PacktPub.com
  9. Preface
  10. What you need for this book
  11. Who this book is for
  12. Conventions
  13. Reader feedback
  14. Customer support
  15. 1. Creating Simple Maps with OpenLayers 3
  16. Structure of OpenLayers 3
  17. Building the layout
  18. Using the API documentation
  19. Debugging the code
  20. Summary
  21. 2. Applying Custom Styles
  22. Customizing the default appearance
  23. Styling vector layers
  24. Customizing the appearance with JavaScript
  25. Creating a WebGIS client layout
  26. Summary
  27. 3. Working with Layers
  28. Building a layer tree
  29. Adding layers dynamically
  30. Adding vector layers with the File API
  31. Adding vector layers with a library
  32. Removing layers dynamically
  33. Changing layer attributes
  34. Changing the layer order with the Drag and Drop API
  35. Clearing the message bar
  36. Summary
  37. 4. Using Vector Data
  38. Accessing attributes
  39. Setting attributes
  40. Validating attributes
  41. Creating thematic layers
  42. Saving vector data
  43. Saving with WFS-T
  44. Modifying the geometry
  45. Summary
  46. 5. Creating Responsive Applications with Interactions and Controls
  47. Building the toolbar
  48. Mapping interactions to controls
  49. Building a set of feature selection controls
  50. Adding new vector layers
  51. Building a set of drawing tools
  52. Modifying and snapping to features
  53. Creating new interactions
  54. Building a measuring control
  55. Summary
  56. 6. Controlling the Map – View and Projection
  57. Customizing a view
  58. Constraining a view
  59. Creating a navigation history
  60. Working with extents
  61. Rotating a view
  62. Changing the map's projection
  63. Creating custom animations
  64. Summary
  65. 7. Mastering Renderers
  66. Using different renderers
  67. Creating a WebGL map
  68. Drawing lines and polygons with WebGL
  69. Blending layers
  70. Clipping layers
  71. Exporting a map
  72. Creating a raster calculator
  73. Creating a convolution matrix
  74. Clipping a layer with WebGL
  75. Summary
  76. 8. OpenLayers 3 for Mobile
  77. Responsive styling with CSS
  78. Generating geocaches
  79. Adding device-dependent controls
  80. Vectorizing the mobile version
  81. Making the mobile application interactive
  82. Summary
  83. 9. Tools of the Trade – Integrating Third-Party Applications
  84. Exporting a QGIS project
  85. Importing shapefiles
  86. Spatial analysis with Turf
  87. Spatial analysis with JSTS
  88. 3D rendering with Cesium
  89. Summary
  90. 10. Compiling Custom Builds with Closure
  91. Configuring Node JS
  92. Compiling OpenLayers 3
  93. Bundling an application with OpenLayers 3
  94. Extending OpenLayers 3
  95. Creating rich documentation with JSDoc
  96. Summary
  97. Index

Setting attributes

As you must have wondered, setting attributes requires much more consideration than simply mapping them to an element. In this example, called ch04_setattributes, we will create a form with the existing attributes. In the form, the user will be able to change the attribute values, add new ones to the feature, and remove them. We will extend the previous example to achieve our goal.

Styling the form

Our form can have multiple possible layouts. A row with attributes will surely contain the attribute name, the attribute value, and a remove button. However, depending on the attribute's state, the name can be a span or a text input element. Besides the rows, the form will have the add and save buttons. As we will create a new div element for every row, we can create some simple CSS logic for these different cases:

.popup span {
    display: inline-block;
    width: 5%;
}
.popup span:first-child {
    float: left;
    width: 40%;
}
.popup input[type=text] {
    width: 50%;
    box-sizing: border-box;
}
.popup input[type=text]:first-child {
    width: 35%;
    box-sizing: border-box;
}
.popup input[type=button], .popup input[type=submit] {
    width: 10%;
    float: right;
    […]
}
.popup .delete {
    background-image: url(../../res/button_delete.png);
}
.popup .save {
    background-image: url(../../res/button_save.png);
}

Tip

You can select elements in CSS based on their attributes. You have to provide the attribute name and the value in KVP format in brackets after the element definition.

Writing the code

Firstly, unlike the previous example, we create a form element. Basically, as we create the possibility to rewrite and append attributes, we take advantage of the form element's submitting capability. We also dedicate a function to build rows based on some arguments:

map.on('click', function (evt) {
    […]
    var attributeForm = document.createElement('form');
    […]
    var deletedAttributes = [];
    var firstFeature = true;
    function createRow (attributeName, attributeValue, isNew) {
        var rowElem = document.createElement('div');
        if (isNew) {
            var nameInput = document.createElement('input');
            nameInput.type = 'text';
            rowElem.appendChild(nameInput);
        }
        var attributeSpan = document.createElement('span');
        attributeSpan.textContent = isNew ? ': ' : attributeName + ': ';
        rowElem.appendChild(attributeSpan);
        var attributeInput = document.createElement('input');
        attributeInput.name = attributeName;
        attributeInput.type = 'text';
        attributeInput.value = attributeValue;
        rowElem.appendChild(attributeInput);
        var removeButton = document.createElement('input');
        removeButton.type = 'button';
        removeButton.className = 'delete';
        removeButton.addEventListener('click', function () {
            rowElem.parentNode.removeChild(rowElem);
            deletedAttributes.push(attributeName);
        });
        rowElem.appendChild(removeButton);
        return rowElem;
    }

We store the deleted attribute names, so we can remove them from the feature on form submission. We use a firstFeature Boolean variable for a trick to limit the queried features to the first one in the given pixel. The createRow function creates a row based on the attribute's name and value. If it has to create an empty row, it creates a text input for the attribute's name, too.

Next, we modify our forEachFeatureAtPixel method's callback function. Firstly, we create the rows based on the feature's attributes and append the add and save buttons to the end of the element:

    this.forEachFeatureAtPixel(pixel, function (feature, layer) {
        if (firstFeature) {
            var attributes = feature.getProperties();
            for (var i in attributes) {
                if (typeof attributes[i] !== 'object') {
                    attributeForm.appendChild(createRow(i, attributes[i]));
                }
            }
            if (attributeForm.children.length > 0) {
                var addAttribute = document.createElement('input');
                addAttribute.type = 'button';
                addAttribute.value = '+';
                addAttribute.addEventListener('click', function () {
                    attributeForm.insertBefore(createRow('', '', true), this);
                });
                attributeForm.appendChild(addAttribute);
                var saveAttributes = document.createElement('input');
                saveAttributes.type = 'submit';
                saveAttributes.value = '';
                saveAttributes.className = 'save';

If the user clicks on the add button, we can simply create a new row with two empty strings and insert the new row before the buttons. This way, the new attributes' elements will be nameless, which is required for the next part of our logic. As the final step, we add a submit event listener to the form and close our function:

                attributeForm.addEventListener('submit', function (evt) {
                    evt.preventDefault();
                    var attributeList = {};
                    var inputList = [].slice.call(this.querySelectorAll('input[type=text]'));
                    while (inputList.length > 0 && inputList[0].name !== '') {
                        attributeList[inputList[0].name] = inputList[0].value;
                        inputList.splice(0,1);
                    }
                    for (i = 0; i < inputList.length; i += 2) {
                        if (inputList[i].value !== '') {
                            attributeList[inputList[i].value] = inputList[i + 1].value;
                        }
                   }
                   feature.setProperties(attributeList);
                   for (i = 0; i < deletedAttributes.length; i += 1) {
                        feature.unset(deletedAttributes[i]);
                   }
                    map.getOverlays().clear();
                });
                attributeForm.appendChild(saveAttributes);
                this.addOverlay(new ol.Overlay({
                    element: attributeForm,
                    position: coord
                }));
                firstFeature = false;
            }
        }
    }

Tip

You can query any element's children with a crafted CSS selector and JavaScript's querySelector or querySelectorAll methods. The returned element(s) will be the first matching the selector or an array in order, respectively.

On form submission, we grab all the text inputs from the form. We convert them to a regular array and create an attribute object based on some regularities. As we get an ordered array, we first check all the named inputs. If there are no named inputs left, we clearly have only new attributes in the array that come in pairs: one for the name and one for the value. If we are at the end of the list, we can move on and remove the unwanted attributes with OpenLayers 3's unset method. Finally, we set the firstFeature variable to false, ensuring that the function won't check any further features.

If you save the example and load it up, you will see the new attribute management interface at work:

Writing the code

Feeling the power of modifying attributes as you like? This is one of the perks of object-oriented data management. As most of the ASCII formats used by web mapping libraries store their data as independent objects, and JavaScript is a purely object-oriented language, supporting this schema is a necessity in a WebGIS application.