-
Notifications
You must be signed in to change notification settings - Fork 507
Basics of Plugin Develop
This doc is about plugin basics, introduction of class methods and a simple plugin example.
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.
Before begin to develop plugin, we assume you are familiar with:
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.
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)
}
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}
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 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
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.
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, …);
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.