Skip to content

Basics of Plugin Develop

Fu Zhen edited this page Oct 21, 2017 · 2 revisions

This doc is about plugin basics, introduction of class methods and a simple plugin example.

Index

  1. Introduction
  2. Basics
  3. The first plugin

Introduction

Any feature that maptalks.js doesn't have can be implemented as a plugin.

The benefits of plugins:

  • Easy to reuse
  • Decouple from your business modules
  • Easy to test, confidence to deliver

Some cases that plugins can be used to:

  • Add new method to existing classes

e.g. a plugin to define a isects method on maptalks.Polygon to detect if it is self-intersection.

  • Create new layer type

e.g. create a maptalks.HeatLayer as a subclass of maptalks.Layer to draw heatmaps on a map.

  • Create new renderer for an existing layer
  • Create new geometry type
  • Create new map tool, control or ui component
  • Create useful tools

e.g. a player plugin for GPS routes playback.

Plugins are published here. Welcome to create yours, and share with us.

Basics

Before begin to develop plugin, we assume you are familiar with:

Object Oriented

maptalks is an object oriented javascript library, organized with classes.

If you are not familiar with object oriented javascript, you can read this article to have a basic overview before you start.

maptalks.Class

Inspired by Leaflet, we created maptalks.Class containing common class utility methods. You can use it as your root class (not mandatory).

Class utility methods defined in maptalks.Class (source) is as below:

class Class {

    // options parameter will bne merged with class's default options
    constructor(options) { }

    // set options to the instance, and merge with class's default options
    setOptions(options) { }

    // Get or update instance's options
    // * If key is undefined, returns instance's options, e.g.
    //      const options = map.config();
    // * if key is present, update instance's options
    //    * key and value, e.g. map.config('draggable', false);
    //    * key-value object, e.g. 
    //     map.config({
    //       'draggable' : false,
    //       'doubleClickZoom' : false
    //     });
    config(key) { }

    // Callback when options is updated by config
    onConfig(key) { }

    // Add a init hook function, called when an instance is created
    static addInitHook(fn, ...args) { }
   
    // Extend the class, and add new methods on class's prototype
    static include(...sources)

    // Define class's default options
    // If default options exists, merge it.
    static mergeOptions(options)
}

options

options is a special property that unlike other objects that you pass to extend will be merged with the parent one instead of overriding it completely, which makes managing configuration of objects and default values convenient.

The usages:

  • Define default options of class:
class MyClass extends maptalks.Class { }
// Define default options
MyClass.mergeOptions({
    option1: 'foo',
    option2: 'bar'
});

class ChildClass extends MyClass { }
ChildClass.mergeOptions({
    option1: 'blah',
    option3: true
});

const a = new ChildClass();
a.options.option1; // 'blah', override parent's option1
a.options.option2; // 'bar', from parent's option2
a.options.option3; // true
  • You can also pass options as a constructor parameter, it will be merged with map's default options, e.g.
class MyClass extends maptalks.Class {
    constructor(options) {
        super(options);
    }
};
MyClass.mergeOptions({
    foo: 'bar',
    bla: 5
});

const a = new MyClass({bla: 10});

a.options; // {foo: 'bar', bla: 10}
  • After instance created, use config method to update options, e.g.
/*
class MyClass extends maptalks.Class {
    constructor(options) {
        super(options);
    }
};
MyClass.mergeOptions({
    foo: 'bar',
    bla: 5
});
*/
const a = new MyClass({bla: 10});

a.config({
    bla : 20
});

a.config('foo', 'barar');

a.options; // {foo: 'barar', bla: 20}

Inheritance

We use standard ES6 grammar to inherit a parent class, e.g.

class Child extends maptalks.Class {
    constructor(name, options) {
        super(options);
        this.name = name;
    }
}

Mixin

Mixin is a way to implement multiple inheritance, we use MDN recommended way to implement mixin.

const calculatorMixin = Base => class extends Base {
  calc() { }
};

const randomizerMixin = Base => class extends Base {
  randomize() { }
};

class Foo { }
class Bar extends calculatorMixin(randomizerMixin(Foo)) { }

Mixin exmples in maptalks: Eventable and JSONAble

include method

include acts similar with Mixin, it can extend class's prototype with new methods, e.g.

class MyClass extends maptalks.Class {}
MyClass.include({
    fooFunc() {
        //...
    }
});

const a = new MyClass();
a.fooFunc();

Different from mixin, we usually use include to define methods only used in MyClass while using mixin for multiple inheritance.

addInitHook method

When developing plugin, you can use addInitHook to add hook functions that will be called when instance is being created, e.g.

MyClass.addInitHook(function () {
    this.foo = 'bar';
});

Here is another form of calling addInitHook, in its parmeters pass the name string of hook function (methodNameOfMyClass) and according parameters (arg1, arg2)

MyClass.include({
    methodNameOfMyClass() { }
});
MyClass.addInitHook('methodNameOfMyClass', arg1, arg2, );

The first plugin

Now, let's see a practical plugin example (github).

This plugin is used to decide whether a polygon has self-intersections (self-intersection is usually not allowed in many scenarios). The plugin adds a new instance method isects on Polygon class and MultiPolygon class.

isects is very simple: it doesn't have any parameter, and its return value is an array.

  • An array of intersection points if the geometry has self-intersections.
  • An empty array if not

The usage example:

const polygon = new maptalks.Polygon(...);
const isects = polygon.isects();
console.log(isects.length > 0 ? 'have self-intersection' : 'no self-intersection');

The plugin is implemented based on 2d-polygon-self-intersections. We use include method mentioned above to extend Polygon and MultiPolygon with isects:

import isect from '2d-polygon-self-intersections';
import * as maptalks from 'maptalks';

maptalks.Polygon.include({
    isects() {
        const coordinates = maptalks.Coordinate.toNumberArrays(this.getCoordinates());
        const sects = [];
        let r, ring;
        for (let i = 0, l = coordinates.length; i < l; i++) {
            ring = coordinates[i];
            if (ring.length > 0) {
                ring = ring.slice(0, ring.length - 1);
            }
            r = isect(ring);
            if (r.length > 0) {
                sects.push([i, r]);
            }
        }
        return sects;
    }
});

maptalks.MultiPolygon.include({
    isects() {
        const geometries = this.getGeometries();
        let r;
        const sects = [];
        for (let i = 0, l = geometries.length; i < l; i++) {
            r = geometries[i].isects();
            if (r.length > 0) {
                sects.push([i, r]);
            }
        }
        return sects;
    }
});

This is the end, please proceed to other docs about how to develop a plugin.