# geometry editor

This commit is contained in:
FDD 2018-02-17 23:26:45 +08:00
parent 42bd28beed
commit 5d997d15b7
17 changed files with 1340 additions and 88 deletions

View File

@ -16,6 +16,10 @@ module.exports = {
],
// add your custom rules here
'rules': {
// allow semi
'semi': 0,
// allow global require
'global-require': 0,
// allow paren-less arrow functions
'arrow-parens': 0,
// allow async-await

View File

@ -7,11 +7,15 @@ const nodeResolve = require('rollup-plugin-node-resolve');
const replace = require('rollup-plugin-replace');
const eslint = require('rollup-plugin-eslint');
const friendlyFormatter = require("eslint-friendly-formatter");
const _package = require('../package.json')
const eslintConfig = require('../.eslintrc')
const year = new Date().getFullYear();
const banner = `/*!\n * ${_package.name} v${_package.version}\n * LICENSE : ${_package.license}\n * (c) 2017-${year} ${_package.homepage}\n */`;
const _package = require('../package.json');
const eslintConfig = require('../.eslintrc');
const time = new Date();
const year = time.getFullYear();
const banner = `/*!\n * author: ${_package.author}
* ${_package.name} v${_package.version}
* build-time: ${year}-${time.getMonth()}-${time.getDay()} ${time.getHours()}:${time.getMinutes()}
* LICENSE: ${_package.license}
* (c) 2017-${year} ${_package.homepage}\n */`;
const resolve = _path => path.resolve(__dirname, '../', _path)
const genConfig = (opts) => {
@ -19,7 +23,13 @@ const genConfig = (opts) => {
input: {
input: resolve('src/index.js'),
plugins: [
eslint((eslintConfig => eslintConfig.formatter = friendlyFormatter)(eslintConfig)),
eslint(Object.assign({}, eslintConfig, {
formatter: friendlyFormatter,
exclude: [
resolve('package.json'),
resolve('node_modules/**')
]
})),
babel({
exclude: 'node_modules/**' // only transpile our source code
}),

View File

@ -22,6 +22,8 @@
<!--<script src="https://cdn.jsdelivr.net/npm/maptalks/dist/maptalks.js"></script>-->
<script src="../dist/maptalks.plot.js"></script>
<script type="text/javascript">
var id = MaptalksPlot.utils.uuid();
MaptalksPlot.utils.addClass(document.getElementById('map'), id);
var map = new maptalks.Map('map', {
center: [108.93, 34.27],
zoom: 5,
@ -37,7 +39,7 @@
var drawTool = new MaptalksPlot.PlotDraw({
mode: 'Curve'
}).addTo(map).disable();
var editor = new MaptalksPlot.PlotEditor();
var editor = new MaptalksPlot.PlotEditor().addTo(map);
editor.on('editStart', function (event) {
console.log(event);
});

View File

@ -31,7 +31,8 @@
"karma.tdd": "karma start build/karma.tdd.config.js"
},
"dependencies": {
"maptalks": "^0.37.0-alpha.1"
"autosize": "^4.0.0",
"maptalks": "^0.38.2"
},
"devDependencies": {
"babel-core": "^6.26.0",
@ -68,17 +69,17 @@
"karma-safari-launcher": "^1.0.0",
"karma-sinon": "^1.0.5",
"mocha": "^3.5.0",
"rollup": "^0.50.0",
"rollup-plugin-babel": "^3.0.2",
"rollup-plugin-buble": "^0.16.0",
"rollup-plugin-commonjs": "^8.2.6",
"rollup": "^0.56.1",
"rollup-plugin-babel": "^3.0.3",
"rollup-plugin-buble": "^0.19.2",
"rollup-plugin-commonjs": "^8.3.0",
"rollup-plugin-eslint": "^4.0.0",
"rollup-plugin-local-resolve": "^1.0.7",
"rollup-plugin-node-resolve": "^3.0.0",
"rollup-plugin-node-resolve": "^3.0.3",
"rollup-plugin-replace": "^2.0.0",
"rollup-watch": "^4.3.1",
"sinon": "^3.2.1",
"uglify-js": "^3.1.2"
"uglify-js": "^3.3.11"
},
"engines": {
"node": ">= 4.0.0",

View File

@ -8,8 +8,7 @@ export const ZERO_TOLERANCE = 0.0001
export const TWO_PI = Math.PI * 2
export const BASE_LAYERNAME = 'maptalks-plot-vector-layer' // 矢量图层名,(唯一标识)
export const BASE_HELP_CONTROL_POINT_ID = 'plot-helper-control-point-div' // 控制点要素的基类id
export const BASE_HELP_HIDDEN = 'plot-helper-hidden-div' // 父类隐藏容器
export const INTERNAL_LAYER_PREFIX = '_maptalks__internal_layer_'
export const EDITOR_LAYERNAME = 'maptalks-plot-editor-vector-layer'
export const DEF_TEXT_STYEL = { // 默认文本框样式
backgroundColor: '#fff',
borderRadius: '2px',

View File

@ -6,7 +6,7 @@
import * as maptalks from 'maptalks'
import {BASE_LAYERNAME} from '../Constants'
import RegisterModes from '../geometry'
import {merge} from '../utils/utils'
import {merge, stopPropagation} from '../utils'
const _options = {
'symbol': {
@ -22,14 +22,6 @@ const _options = {
'ignoreMouseleave': true
}
const registeredMode = {}
const stopPropagation = function (e) {
if (e.stopPropagation) {
e.stopPropagation()
} else {
e.cancelBubble = true
}
return this
}
class PlotDraw extends maptalks.MapTool {
constructor (options = {}) {
@ -336,8 +328,8 @@ class PlotDraw extends maptalks.MapTool {
/**
* Set draw tool's symbol
* @param {Object} symbol - symbol set
* @returns {DrawTool} this
* @param symbol
* @returns {PlotDraw}
*/
setSymbol (symbol) {
if (!symbol) {

View File

@ -1,12 +1,43 @@
import * as maptalks from 'maptalks'
import { isNumber } from '../utils'
import {BASE_HELP_CONTROL_POINT_ID, EDITOR_LAYERNAME} from '../Constants'
const Class = maptalks.Class
const EventAble = maptalks.Eventable
const key = '_plot_editor';
class PlotEditor extends EventAble(Class) {
constructor (geometry, opts) {
super(opts)
/**
* geometry
*/
this._geometry = geometry
/**
* map pan
* @type {boolean}
*/
this.mapDragPan = true
/**
* 存放控制点
* @type {Array}
*/
this.controlPoints = []
/**
* is dragging
* @type {boolean}
*/
this.isDragging = false
/**
* 创建图层名称
* @type {string}
*/
this.layerName = ((opts && opts['layerName']) ? opts['layerName'] : EDITOR_LAYERNAME)
}
/**
@ -14,18 +45,280 @@ class PlotEditor extends EventAble(Class) {
* @param plot
*/
activate (plot) {
this.deactivate()
this._geometry = plot
console.log(plot, this)
this.initControlPoints()
this.fire('editStart', {
geometry: this._geometry
})
return this
}
/**
* 取消激活工具
*/
deactivate () {
console.log(this)
this.removeControlPoints()
delete this._geometry
this.controlPoints = []
}
initControlPoints () {
this.controlPoints = []
let controlPoints = PlotEditor.getControlPoints(this._geometry)
if (controlPoints && Array.isArray(controlPoints) && controlPoints.length > 0) {
if (controlPoints.length > this.options['limitControlPoints'] &&
this.options.hasOwnProperty('limitControlPoints') &&
this.options['limitControlPoints'] > 2) {
// TODO 这里其中 `limitControlPoints` 必须包含起止点,所以这里处理了一下
const _n = Math.floor(controlPoints.length / (this.options['limitControlPoints'] - 2)) || 1
for (let i = 0; i < this.options['limitControlPoints'] - 2; i++) {
const _index = (i + 1) * _n - 1
this._addControlPoint(controlPoints, _index)
}
this._addControlPoint(controlPoints, 0)
this._addControlPoint(controlPoints, controlPoints.length - 1)
} else {
for (let i = 0; i < controlPoints.length; i++) {
this._addControlPoint(controlPoints, i)
}
}
}
}
/**
* remove control points
*/
removeControlPoints () {
const _layer = this._getDrawLayer(this.layerName)
for (let i = 0; i < this.controlPoints.length; i++) {
if (this.controlPoints[i]) {
_layer.removeGeometry(this.controlPoints[i])
}
}
}
/**
* 添加控制点
* @param points
* @param _index
* @private
*/
_addControlPoint (points, _index) {
const id = BASE_HELP_CONTROL_POINT_ID + '-' + _index
const _marker = new maptalks.Marker(
points[_index],
{
id: id,
draggable: true,
symbol: {
'markerType': 'ellipse',
'markerFill': '#ffffff',
'markerFillOpacity': 1,
'markerLineColor': '#000',
'markerLineWidth': 1,
'markerLineOpacity': 1,
'markerLineDasharray': [],
'markerWidth': 10,
'markerHeight': 10,
'markerDx': 0,
'markerDy': 0,
'markerOpacity': 0.6
},
properties: {
index: _index
}
}
)
if (this.getMap()) {
_marker.addTo(this._getDrawLayer(this.layerName))
_marker.on('dragstart', this._handleDragStart, this)
_marker.on('dragging', this._handleDragging, this)
_marker.on('dragend', this._handleDragEnd, this)
}
this.controlPoints.push(_marker)
}
_handleDragStart (event) {
this.isDragging = true
}
_handleDragging (event) {
if (this.isDragging && event.target) {
const _index = event.target.getProperties() && event.target.getProperties()['index']
const sourcePoints = this._geometry.getPoints()
if (isNumber(_index) && sourcePoints.length > _index) {
sourcePoints[_index] = event.coordinate
this._geometry.setPoints(sourcePoints)
}
}
}
_handleDragEnd (event) {
this.isDragging = false
}
/**
* 创建矢量图层
* @param layerName
* @returns {*}
* @private
*/
_getDrawLayer (layerName) {
const _map = this.getMap()
if (!_map) return
let drawToolLayer = _map.getLayer(layerName)
if (!drawToolLayer) {
drawToolLayer = new maptalks.VectorLayer(layerName, {
'enableSimplify': false
})
_map.addLayer(drawToolLayer)
}
return drawToolLayer
}
/**
* add plot editor to map
* @param map
* @returns {PlotEditor}
*/
addTo (map) {
if (!map) {
return this;
}
this._map = map;
if (map[key]) {
map[key].disable();
}
if (this.onAdd) {
this.onAdd();
}
this.enable();
map[key] = this;
this.fire('add');
return this;
}
/**
* Gets the map it added to.
* @return {Map} map
*/
getMap () {
return this._map;
}
/**
* enable plot editor
* @returns {PlotEditor}
*/
enable () {
const map = this._map;
if (!map || this._enabled) {
return this;
}
this._enabled = true;
if (this.onEnable) {
this.onEnable();
}
this.fire('enable');
return this;
}
/**
* disable plot editor
* @returns {PlotEditor}
*/
disable () {
if (!this._enabled || !this._map) {
return this;
}
this._enabled = false;
if (this.onDisable) {
this.onDisable();
}
this.fire('disable');
return this;
}
/**
* check is enable
* @returns {boolean}
*/
isEnabled () {
if (!this._enabled) {
return false;
}
return true;
}
/**
* remove from map
* @returns {PlotEditor}
*/
remove () {
if (!this._map) {
return this;
}
this.disable();
if (this._map) {
delete this._map[key];
delete this._map;
}
this.fire('remove');
return this;
}
/**
* 激活地图的拖拽平移
*/
enableMapDragPan () {
const _map = this.getMap()
if (!_map) return
_map.config({
'draggable': this.mapDragPan
})
}
/**
* 禁止地图的拖拽平移
*/
disableMapDragPan () {
const _map = this.getMap()
if (!_map) return
this.mapDragPan = _map.options['draggable']
_map.config({
'draggable': false
})
}
/**
* 获取要素的控制点
* @returns {Array}
*/
static getControlPoints (plot) {
let points = []
if (plot) {
points = plot.getPoints()
}
return points
}
/**
* 根据控制点个数生成控制点
* @param points
* @param limit
* @returns {Array}
*/
static getLimitControlPoints (points, limit) {
const _coordinates = []
if (points && points.length > 0) {
const _n = Math.floor(points.length / limit) || 1
for (let i = 0; i < limit; i++) {
_coordinates.push(points[(i + 1) * _n - 1])
}
}
return _coordinates
}
}

View File

@ -1,4 +1,4 @@
const TextArea = 'TextArea' // 文本标绘(特殊)
const TEXTAREA = 'TextArea' // 文本标绘(特殊)
const ARC = 'Arc'
const CURVE = 'Curve'
const GATHERING_PLACE = 'GatheringPlace'
@ -26,7 +26,7 @@ const RECTFLAG = 'RectFlag'
const TRIANGLEFLAG = 'TriangleFlag'
const CURVEFLAG = 'CurveFlag'
export {
TextArea,
TEXTAREA,
ARC,
CURVE,
GATHERING_PLACE,

View File

@ -0,0 +1,449 @@
import * as maptalks from 'maptalks'
import autosize from 'autosize'
import {DEF_TEXT_STYEL} from '../../Constants'
import { merge, on, off, hasClass, setStyle, getStyle } from '../../utils'
const _options = {
'autoPan': true,
'autoCloseOn': null,
'autoOpenOn': 'click',
'width': 300,
'minHeight': 120,
'custom': false,
'title': null,
'content': null
}
class PlotTextBox extends maptalks.UIComponent {
constructor (options = {}) {
const $options = merge(_options, options)
super()
this.options = $options
/**
* 地图交互
* @type {undefined}
*/
this.mapDragPan = undefined
/**
* is click
* @type {boolean}
* @private
*/
this.isClick_ = false
/**
* 是否处于拖拽状态
* @type {boolean}
* @private
*/
this.dragging_ = false
/**
* 当前气泡是否获取焦点
* @type {boolean}
* @private
*/
this.isFocus_ = false
/**
* 当前配置信息
* @type {{}}
* @private
*/
this.options_ = options
/**
* 当前气泡位置
* @type {Array}
* @private
*/
this._position = (options['position'] && options['position'].length > 0) ? options['position'] : []
/**
* 防抖延时
* @type {null}
* @private
*/
this.handleTimer_ = null
/**
* 每次鼠标按下的位置
* @type {Array}
* @private
*/
this.currentPixel_ = []
/**
* 创建text content
*/
this.createTextContent(options)
}
/**
* 创建文本框父容器
* @param options
*/
createTextContent (options) {
const _className = options.className || 'maptalks-plot-text-editor'
const content = document.createElement('textarea')
content.className = _className
content.style.width = options['width'] + 'px'
content.style.height = options['height'] + 'px'
content.style.minHeight = options['minHeight'] + 'px'
content.setAttribute('id', options['id'])
content.setAttribute('autofocus', true)
on(content, 'focus', this.handleFocus_, this)
on(content, 'blur', this.handleBlur_, this)
on(content, 'click', this.handleClick_, this)
on(content, 'mousedown', this.handleDragStart_, this)
on(window, 'mouseup', this.handleDragEnd_, this)
this.set('isPlotText', true)
this.setElement(content)
this.createCloseButton(options)
this.createResizeButton(options)
this.setPosition(this._position)
this.fire('textBoxDrawEnd', {
overlay: this,
element: content,
uuid: options['id']
})
}
/**
* 获取文本框
* @returns {string}
* @private
*/
getTextAreaFromContent_ () {
let _node = ''
const childrens_ = Array.prototype.slice.call((this.element && this.element.children), 0)
if (childrens_.length > 0) {
childrens_.every(ele => {
if (ele.nodeType === 1 && ele.nodeName.toLowerCase() === 'textarea') {
_node = ele
return false
} else {
return true
}
})
}
return _node
}
/**
* 创建关闭按钮
* @param options
*/
createCloseButton (options) {
const _closeSpan = document.createElement('span')
_closeSpan.className = 'maptalks-plot-text-editor-close'
_closeSpan.setAttribute('data-id', options['id'])
off(_closeSpan, 'click', this.closeCurrentPlotText, this)
on(_closeSpan, 'click', this.closeCurrentPlotText, this)
this.element.appendChild(_closeSpan)
}
/**
* 创建文本框大小调整按钮
* @param options
*/
createResizeButton (options) {
const _resizeSpan = document.createElement('span')
_resizeSpan.className = 'maptalks-plot-text-editor-resize'
_resizeSpan.setAttribute('data-id', options['id'])
off(_resizeSpan, 'mousedown', this.handleResizeMouseDown_, this)
off(_resizeSpan, 'mousemove', this.handleResizeMouseMove_, this)
on(_resizeSpan, 'mousedown', this.handleResizeMouseDown_, this)
on(_resizeSpan, 'mousemove', this.handleResizeMouseMove_, this)
this.element.appendChild(_resizeSpan)
}
/**
* 调整大小
* @param event
* @private
*/
resizeButtonMoveHandler_ (event) {
const pixel_ = event.pixel
const element_ = this.getTextAreaFromContent_()
if (pixel_.length < 1 || this.currentPixel_.length < 1 || !element_) return
const _offset = [pixel_[0] - this.currentPixel_[0], pixel_[1] - this.currentPixel_[1]]
const _size = [element_.offsetWidth, element_.offsetHeight]
const _width = _size[0] + _offset[0] * 2
const _height = _size[1] + _offset[1] * 2
setStyle(element_, 'width', _width + 'px')
setStyle(element_, 'height', _height + 'px')
this.currentPixel_ = pixel_
this.getMap().render()
}
/**
* 处理移动事件
* @param event
* @private
*/
handleResizeMouseMove_ (event) {
event.stopImmediatePropagation()
}
/**
* 处理鼠标按下事件
* @param event
* @private
*/
handleResizeMouseDown_ (event) {
if (!this.getMap()) return
this.currentPixel_ = [event.x, event.y]
this.getMap().on('pointermove', this.resizeButtonMoveHandler_, this)
on(this.getMap().getViewport(), 'mouseup', this.handleResizeMouseUp_, this)
}
/**
* 处理鼠标抬起事件移除所有事件监听
* @param event
* @private
*/
handleResizeMouseUp_ (event) {
if (!this.getMap()) return
this.getMap().un('pointermove', this.resizeButtonMoveHandler_, this)
off(this.getMap().getViewport(), 'mouseup', this.handleResizeMouseUp_, this)
this.currentPixel_ = []
}
/**
* 处理关闭事件
* @param event
*/
closeCurrentPlotText (event) {
if (!this.getMap()) return
if (event && hasClass(event.target, 'maptalks-plot-text-editor-close')) {
let _id = event.target.getAttribute('data-id')
if (_id) {
const _overlay = this.getMap().getOverlayById(_id)
if (_overlay) {
this.getMap().removeOverlay(_overlay)
}
}
}
}
/**
* 处理获取焦点事件
* @private
*/
handleFocus_ () {
this.isFocus_ = true
if (this.getMap()) {
this.getMap().set('activeTextArea', this)
this.getMap().dispatchEvent('activeTextArea')
}
}
/**
* 处理失去焦点事件
* @private
*/
handleBlur_ () {
this.isFocus_ = false
if (this.getMap()) {
this.getMap().set('activeTextArea', null)
this.getMap().set('disActiveTextArea', this)
this.getMap().dispatchEvent('disActiveTextArea')
}
}
/**
* 处理拖拽开始
* @private
*/
handleDragStart_ (event) {
if (!this.getMap()) return
if (!this.dragging_ && this.isMoveModel() && this.isFocus_) {
this.handleTimer_ = window.setTimeout(() => {
window.clearTimeout(this.handleTimer_)
this.handleTimer_ = null
if (!this.isClick_) {
this.dragging_ = true
this.disableMapDragPan()
this.preCursor_ = this.element.style.cursor
on(this.getMap().getViewport(), 'mousemove', this.handleDragDrag_, this)
on(this.element, 'mouseup', this.handleDragEnd_, this)
}
}, 300)
}
}
/**
* 处理拖拽
* @param event
* @private
*/
handleDragDrag_ (event) {
if (this.dragging_) {
this.element.style.cursor = 'move'
this._position = this.getMap().getCoordinateFromPixel([event.clientX, event.clientY])
this.setPosition(this._position)
}
}
/**
* 处理拖拽
* @private
*/
handleDragEnd_ (event) {
this.isClick_ = false
window.clearTimeout(this.handleTimer_)
this.handleTimer_ = null
if (this.dragging_ && this.isFocus_) {
this.dragging_ = false
this.enableMapDragPan()
this.element.style.cursor = this.preCursor_
off(this.getMap().getViewport(), 'mousemove', this.handleDragDrag_, this)
off(this.element, 'mouseup', this.handleDragEnd_, this)
}
}
/**
* 处理点击事件
* @param event
* @private
*/
handleClick_ (event) {
if (event.target === this.element) {
this.isClick_ = true
} else {
this.isClick_ = false
}
}
/**
* 是否处于选择模式
* @returns {boolean}
*/
isMoveModel () {
const range = window.getSelection().getRangeAt(0)
return range.collapsed
}
/**
* 设置样式
* @param style
*/
setStyle (style = {}) {
const _element = this.getTextAreaFromContent_()
if (_element) {
for (let key in style) {
if (style[key]) {
setStyle(_element, key, style[key])
}
}
}
}
/**
* 获取当前样式
* @returns {CSSStyleDeclaration}
*/
getStyle () {
const _style = {}
const _element = this.getTextAreaFromContent_()
if (_element) {
for (let key in DEF_TEXT_STYEL) {
_style[key] = getStyle(_element, key)
}
}
return _style
}
/**
* set value
* @param value
*/
setValue (value) {
const _element = this.getTextAreaFromContent_()
if (_element) {
_element.value = value
if (value) {
autosize.update(_element)
}
this.getMap().render()
}
}
/**
* get value
* @returns {*}
*/
getValue () {
const _element = this.getTextAreaFromContent_()
if (_element) {
return _element.value
} else {
return ''
}
}
/**
* 获取宽度
* @returns {number}
*/
getWidth () {
const element_ = this.getTextAreaFromContent_()
if (element_ && element_.offsetWidth) {
return element_.offsetWidth
} else {
return 0
}
}
/**
* 获取高度
* @returns {number}
*/
getHeight () {
const element_ = this.getTextAreaFromContent_()
if (element_ && element_.offsetHeight) {
return element_.offsetHeight
} else {
return 0
}
}
/**
* 激活地图的拖拽平移
*/
enableMapDragPan () {
const _map = this.getMap()
if (!_map) return
_map.config({
'draggable': this.mapDragPan
})
}
/**
* 禁止地图的拖拽平移
*/
disableMapDragPan () {
const _map = this.getMap()
if (!_map) return
this.mapDragPan = _map.options['draggable']
_map.config({
'draggable': false
})
}
/**
* set map
* @param map
*/
setMap (map) {
maptalks.UIComponent.prototype.addTo.call(this, map)
if (map && map instanceof maptalks.Map) {
this.setStyle(merge(DEF_TEXT_STYEL, this.options_['style']))
this.setValue(this.options_['value'])
}
}
}
export default PlotTextBox

View File

@ -32,6 +32,8 @@ import FreePolygon from './Polygon/FreePolygon'
import RectAngle from './Polygon/RectAngle'
import GatheringPlace from './Polygon/GatheringPlace'
// import TextArea from './Text/PlotTextBox'
import * as PlotTypes from '../core/PlotTypes'
const Coordinate = maptalks.Coordinate
const RegisterModes = {}
@ -387,5 +389,19 @@ RegisterModes[PlotTypes.ELLIPSE] = {
return geometry
}
}
// RegisterModes[PlotTypes.TEXTAREA] = {
// 'freehand': false,
// 'limitClickCount': 2,
// 'action': ['click', 'mousemove', 'click'],
// 'create': function (path) {
// return new RectAngle(path)
// },
// 'update': function (path, geometry) {
// geometry.setPoints(path)
// },
// 'generate': function (geometry) {
// return new TextArea()
// }
// }
export default RegisterModes

View File

@ -1,8 +1,10 @@
import * as utils from './utils'
import * as PlotTypes from './core/PlotTypes'
import PlotDraw from './core/PlotDraw'
import PlotEditor from './core/PlotEditor'
export {
utils,
PlotDraw,
PlotEditor,
PlotTypes

194
src/utils/dom.js Normal file
View File

@ -0,0 +1,194 @@
import { trim, camelCase } from './utils'
/**
* create element
* @param tagName
* @param className
* @param container
* @param id
* @returns {HTMLElement}
*/
const create = function (tagName, className, container, id) {
const el = document.createElement(tagName)
if (id) el.id = id
if (className) addClass(el, className)
if (container) {
container.appendChild(el)
}
return el
}
/**
* get element
* @param selector
* @returns {*}
*/
const getElement = function (selector) {
const dom = (function () {
let found
return (document && /^#([\w-]+)$/.test(selector))
? ((found = document.getElementById(RegExp.$1)) ? [found] : [])
: Array.prototype.slice.call(/^\.([\w-]+)$/.test(selector)
? document.getElementsByClassName(RegExp.$1)
: /^[\w-]+$/.test(selector) ? document.getElementsByTagName(selector)
: document.querySelectorAll(selector)
)
})()
return dom
}
/**
* remove current element
* @param node
*/
const remove = function (node) {
return node && node.parentNode ? node.parentNode.removeChild(node) : null
}
/**
* clear element child
* @param el
*/
const empty = function (el) {
while (el.firstChild) {
el.removeChild(el.firstChild)
}
}
/**
* create hidden element
* @param tagName
* @param parent
* @param id
* @returns {HTMLElement}
*/
const createHidden = function (tagName, parent, id) {
const element = document.createElement(tagName)
element.style.display = 'none'
if (id) {
element.id = id
}
if (parent) {
parent.appendChild(element)
}
return element
}
/**
* check element has class
* @param el
* @param cls
* @returns {boolean}
*/
const hasClass = (el, cls) => {
if (!el || !cls) return false
if (cls.indexOf(' ') !== -1) throw new Error('className should not contain space.')
if (el.classList) {
return el.classList.contains(cls)
} else {
return (' ' + el.className + ' ').indexOf(' ' + cls + ' ') > -1
}
}
/**
* add class for element
* @param el
* @param cls
*/
const addClass = (el, cls) => {
if (!el) return
let curClass = el.className
let classes = (cls || '').split(' ')
for (let i = 0, j = classes.length; i < j; i++) {
let clsName = classes[i]
if (!clsName) continue
if (el.classList) {
el.classList.add(clsName)
} else if (!hasClass(el, clsName)) {
curClass += ' ' + clsName
}
}
if (!el.classList) {
el.className = curClass
}
}
/**
* remove element class
* @param el
* @param cls
*/
const removeClass = (el, cls) => {
if (!el || !cls) return
const classes = cls.split(' ')
let curClass = ' ' + el.className + ' '
for (let i = 0, j = classes.length; i < j; i++) {
let clsName = classes[i]
if (!clsName) continue
if (el.classList) {
el.classList.remove(clsName)
} else if (hasClass(el, clsName)) {
curClass = curClass.replace(' ' + clsName + ' ', ' ')
}
}
if (!el.classList) {
el.className = trim(curClass)
}
}
/**
* get current element style
* @param element
* @param styleName
* @returns {*}
*/
const getStyle = (element, styleName) => {
if (!element || !styleName) return null
styleName = camelCase(styleName)
if (styleName === 'float') {
styleName = 'cssFloat'
}
try {
const computed = document.defaultView.getComputedStyle(element, '')
return element.style[styleName] || computed ? computed[styleName] : null
} catch (e) {
return element.style[styleName]
}
}
/**
* set dom style
* @param element
* @param styleName
* @param value
*/
const setStyle = (element, styleName, value) => {
if (!element || !styleName) return
if (typeof styleName === 'object') {
for (let prop in styleName) {
if (styleName.hasOwnProperty(prop)) {
setStyle(element, prop, styleName[prop])
}
}
} else {
styleName = camelCase(styleName)
if (styleName === 'opacity') {
element.style.filter = isNaN(value) ? '' : 'alpha(opacity=' + value * 100 + ')'
} else {
element.style[styleName] = value
}
}
}
export {
create,
getElement,
remove,
empty,
createHidden,
hasClass,
addClass,
removeClass,
getStyle,
setStyle
}

128
src/utils/events.js Normal file
View File

@ -0,0 +1,128 @@
import { stamp } from './utils'
/**
* 获取事件唯一标识
* @param type
* @param fn
* @param context
* @returns {string}
*/
const getDomEventKey = (type, fn, context) => {
return '_dom_event_' + type + '_' + stamp(fn) + (context ? '_' + stamp(context) : '')
}
/**
* 对DOM对象添加事件监听
* @param element
* @param type
* @param fn
* @param context
* @param isOnce
* @returns {*}
*/
const addListener = function (element, type, fn, context, isOnce) {
let eventKey = getDomEventKey(type, fn, context)
let handler = element[eventKey]
if (handler) {
if (!isOnce) {
handler.callOnce = false
}
return this
}
handler = function (e) {
return fn.call(context || element, e)
}
if ('addEventListener' in element) {
element.addEventListener(type, handler, false)
} else if ('attachEvent' in element) {
element.attachEvent('on' + type, handler)
}
element[eventKey] = handler
return this
}
const on = addListener
/**
* 移除DOM对象监听事件
* @param element
* @param type
* @param fn
* @param context
* @returns {removeListener}
*/
const removeListener = function (element, type, fn, context) {
let eventKey = getDomEventKey(type, fn, context)
let handler = element[eventKey]
if (!handler) {
return this
}
if ('removeEventListener' in element) {
element.removeEventListener(type, handler, false)
} else if ('detachEvent' in element) {
element.detachEvent('on' + type, handler)
}
element[eventKey] = null
return this
}
const off = removeListener
/**
* attach events once
* @param element
* @param type
* @param fn
* @param context
* @returns {*}
*/
const once = function (element, type, fn, context) {
return addListener(element, type, fn, context, true)
}
/**
* Prevent default behavior of the browser.
* @param event
* @returns {preventDefault}
*/
const preventDefault = function (event) {
if (event.preventDefault) {
event.preventDefault()
} else {
event.returnValue = false
}
return this
}
/**
* Stop browser event propagation
* @param event
* @returns {stopPropagation}
*/
const stopPropagation = function (event) {
if (event.stopPropagation) {
event.stopPropagation()
} else {
event.cancelBubble = true
}
return this
}
/**
* Prevents other listeners of the same event from being called.
* @param event
*/
const stopImmediatePropagation = function (event) {
event.stopImmediatePropagation()
}
export {
on,
once,
addListener,
off,
removeListener,
preventDefault,
stopPropagation,
stopImmediatePropagation
}

4
src/utils/index.js Normal file
View File

@ -0,0 +1,4 @@
export * from './utils'
export * from './dom'
export * from './events'
export { default as uuid } from './uuid'

View File

@ -1,14 +1,72 @@
import uuid from './uuid'
/* eslint no-useless-escape: "off" */
const SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g
const MOZ_HACK_REGEXP = /^moz([A-Z])/
const trim = function (string) {
return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, '')
}
/**
* stamp string
* @param obj
* @returns {*}
*/
const stamp = function (obj) {
let key = '_event_id_'
obj[key] = obj[key] || (uuid())
return obj[key]
}
/**
* check case
* @param name
* @returns {string}
*/
const camelCase = function (name) {
return name.replace(SPECIAL_CHARS_REGEXP, function (_, separator, letter, offset) {
return offset ? letter.toUpperCase() : letter
}).replace(MOZ_HACK_REGEXP, 'Moz$1')
}
/**
* 判断是否为对象
* @param value
* @returns {boolean}
*/
export const isObject = value => {
const isObject = value => {
const type = typeof value
return value !== null && (type === 'object' || type === 'function')
}
export const merge = (a, b) => {
/**
* 判断是否为合法字符串
* @param value
* @returns {boolean}
*/
const isString = (value) => {
if (value == null) {
return false
}
return typeof value === 'string' || (value.constructor !== null && value.constructor === String)
}
/**
* check is number
* @param val
* @returns {boolean}
*/
const isNumber = (val) => {
return typeof val === 'number' && !isNaN(val);
}
/**
* merge
* @param a
* @param b
* @returns {*}
*/
const merge = (a, b) => {
for (const key in b) {
if (isObject(b[key]) && isObject(a[key])) {
merge(a[key], b[key])
@ -18,3 +76,13 @@ export const merge = (a, b) => {
}
return a
}
export {
merge,
trim,
stamp,
isString,
isObject,
camelCase,
isNumber
}

67
src/utils/uuid.js Normal file
View File

@ -0,0 +1,67 @@
const byteToHex = []
const rnds = new Array(16)
for (let i = 0; i < 256; ++i) {
byteToHex[i] = (i + 0x100).toString(16).substr(1)
}
/**
* form uuid
* Convert array of 16 byte values to UUID string format of the form:
* XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
* @param buf
* @param offset
* @returns {string}
*/
const bytesToUuid = (buf, offset) => {
let i = offset || 0
const bth = byteToHex
return bth[buf[i++]] + bth[buf[i++]] +
bth[buf[i++]] + bth[buf[i++]] + '-' +
bth[buf[i++]] + bth[buf[i++]] + '-' +
bth[buf[i++]] + bth[buf[i++]] + '-' +
bth[buf[i++]] + bth[buf[i++]] + '-' +
bth[buf[i++]] + bth[buf[i++]] +
bth[buf[i++]] + bth[buf[i++]] +
bth[buf[i++]] + bth[buf[i++]]
}
/**
* math rng
* @returns {any[]}
*/
const mathRNG = () => {
for (let i = 0, r; i < 16; i++) {
if ((i & 0x03) === 0) r = Math.random() * 0x100000000
rnds[i] = r >>> ((i & 0x03) << 3) & 0xff
}
return rnds
}
/**
* get uuid
* @param options
* @param buf
* @param offset
* @returns {*|string}
*/
const uuid = (options, buf, offset) => {
/* eslint-disable */
const i = buf && offset || 0
if (typeof (options) === 'string') {
buf = options === 'binary' ? new Array(16) : null
options = null
}
options = options || {}
const rnds = options.random || (options.rng || mathRNG)()
rnds[6] = (rnds[6] & 0x0f) | 0x40
rnds[8] = (rnds[8] & 0x3f) | 0x80
// Copy bytes to buffer, if provided
if (buf) {
for (let ii = 0; ii < 16; ++ii) {
buf[i + ii] = rnds[ii]
}
}
return buf || bytesToUuid(rnds)
}
export default uuid

133
yarn.lock
View File

@ -17,22 +17,32 @@ accepts@1.3.3:
mime-types "~2.1.11"
negotiator "0.6.1"
acorn-jsx@^3.0.0, acorn-jsx@^3.0.1:
acorn-dynamic-import@^3.0.0:
version "3.0.0"
resolved "http://registry.npm.taobao.org/acorn-dynamic-import/download/acorn-dynamic-import-3.0.0.tgz#901ceee4c7faaef7e07ad2a47e890675da50a278"
dependencies:
acorn "^5.0.0"
acorn-jsx@^3.0.0:
version "3.0.1"
resolved "http://registry.npm.taobao.org/acorn-jsx/download/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b"
dependencies:
acorn "^3.0.4"
acorn-object-spread@^1.0.0:
version "1.0.0"
resolved "http://registry.npm.taobao.org/acorn-object-spread/download/acorn-object-spread-1.0.0.tgz#48ead0f4a8eb16995a17a0db9ffc6acaada4ba68"
acorn-jsx@^4.1.1:
version "4.1.1"
resolved "http://registry.npm.taobao.org/acorn-jsx/download/acorn-jsx-4.1.1.tgz#e8e41e48ea2fe0c896740610ab6a4ffd8add225e"
dependencies:
acorn "^3.1.0"
acorn "^5.0.3"
acorn@^3.0.4, acorn@^3.1.0, acorn@^3.3.0:
acorn@^3.0.4:
version "3.3.0"
resolved "http://registry.npm.taobao.org/acorn/download/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
acorn@^5.0.0, acorn@^5.0.3, acorn@^5.4.1:
version "5.4.1"
resolved "http://registry.npm.taobao.org/acorn/download/acorn-5.4.1.tgz#fdc58d9d17f4a4e98d102ded826a9b9759125102"
acorn@^5.2.1:
version "5.3.0"
resolved "http://registry.npm.taobao.org/acorn/download/acorn-5.3.0.tgz#7446d39459c54fb49a80e6ee6478149b940ec822"
@ -89,7 +99,7 @@ ansi-styles@^2.2.1:
version "2.2.1"
resolved "http://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
ansi-styles@^3.1.0:
ansi-styles@^3.1.0, ansi-styles@^3.2.0:
version "3.2.0"
resolved "http://registry.npm.taobao.org/ansi-styles/download/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88"
dependencies:
@ -218,6 +228,10 @@ atob@^2.0.0:
version "2.0.3"
resolved "http://registry.npm.taobao.org/atob/download/atob-2.0.3.tgz#19c7a760473774468f20b2d2d03372ad7d4cbf5d"
autosize@^4.0.0:
version "4.0.0"
resolved "http://registry.npm.taobao.org/autosize/download/autosize-4.0.0.tgz#7a0599b1ba84d73bd7589b0d9da3870152c69237"
aws-sign2@~0.6.0:
version "0.6.0"
resolved "http://registry.npm.taobao.org/aws-sign2/download/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f"
@ -942,28 +956,22 @@ braces@^2.3.0:
split-string "^3.0.2"
to-regex "^3.0.1"
browser-resolve@^1.11.0:
version "1.11.2"
resolved "http://registry.npm.taobao.org/browser-resolve/download/browser-resolve-1.11.2.tgz#8ff09b0a2c421718a1051c260b32e48f442938ce"
dependencies:
resolve "1.1.7"
browser-stdout@1.3.0:
version "1.3.0"
resolved "http://registry.npm.taobao.org/browser-stdout/download/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f"
buble@^0.16.0:
version "0.16.0"
resolved "http://registry.npm.taobao.org/buble/download/buble-0.16.0.tgz#1773e7b5a383f5c722af6b1b16b2ba49cb866a98"
buble@^0.19.2:
version "0.19.3"
resolved "http://registry.npm.taobao.org/buble/download/buble-0.19.3.tgz#01e9412062cff1da6f20342b6ecd72e7bf699d02"
dependencies:
acorn "^3.3.0"
acorn-jsx "^3.0.1"
acorn-object-spread "^1.0.0"
chalk "^1.1.3"
magic-string "^0.14.0"
acorn "^5.4.1"
acorn-dynamic-import "^3.0.0"
acorn-jsx "^4.1.1"
chalk "^2.3.1"
magic-string "^0.22.4"
minimist "^1.2.0"
os-homedir "^1.0.1"
vlq "^0.2.2"
vlq "^1.0.0"
build@^0.1.4:
version "0.1.4"
@ -1060,6 +1068,14 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0:
escape-string-regexp "^1.0.5"
supports-color "^4.0.0"
chalk@^2.3.1:
version "2.3.1"
resolved "http://registry.npm.taobao.org/chalk/download/chalk-2.3.1.tgz#523fe2678aec7b04e8041909292fe8b17059b796"
dependencies:
ansi-styles "^3.2.0"
escape-string-regexp "^1.0.5"
supports-color "^5.2.0"
chardet@^0.4.0:
version "0.4.2"
resolved "http://registry.npm.taobao.org/chardet/download/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2"
@ -1191,9 +1207,9 @@ commander@2.9.0:
dependencies:
graceful-readlink ">= 1.0.0"
commander@~2.12.1:
version "2.12.2"
resolved "http://registry.npm.taobao.org/commander/download/commander-2.12.2.tgz#0f5946c427ed9ec0d91a46bb9def53e54650e555"
commander@~2.14.1:
version "2.14.1"
resolved "http://registry.npm.taobao.org/commander/download/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa"
commondir@^1.0.1:
version "1.0.1"
@ -2170,6 +2186,10 @@ has-flag@^2.0.0:
version "2.0.0"
resolved "http://registry.npm.taobao.org/has-flag/download/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51"
has-flag@^3.0.0:
version "3.0.0"
resolved "http://registry.npm.taobao.org/has-flag/download/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
has-unicode@^2.0.0:
version "2.0.1"
resolved "http://registry.npm.taobao.org/has-unicode/download/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
@ -3009,12 +3029,6 @@ lru-cache@^4.0.1:
pseudomap "^1.0.2"
yallist "^2.1.2"
magic-string@^0.14.0:
version "0.14.0"
resolved "http://registry.npm.taobao.org/magic-string/download/magic-string-0.14.0.tgz#57224aef1701caeed273b17a39a956e72b172462"
dependencies:
vlq "^0.2.1"
magic-string@^0.22.4:
version "0.22.4"
resolved "http://registry.npm.taobao.org/magic-string/download/magic-string-0.22.4.tgz#31039b4e40366395618c1d6cf8193c53917475ff"
@ -3047,9 +3061,9 @@ map-visit@^1.0.0:
dependencies:
object-visit "^1.0.0"
maptalks@^0.37.0-alpha.1:
version "0.37.0-alpha.1"
resolved "http://registry.npm.taobao.org/maptalks/download/maptalks-0.37.0-alpha.1.tgz#e13f58e037101d184ae1f600cd3eac7ca580b508"
maptalks@^0.38.2:
version "0.38.2"
resolved "http://registry.npm.taobao.org/maptalks/download/maptalks-0.38.2.tgz#8aff032d57227560a7e22af502495751c39791da"
dependencies:
simplify-js "^1.2.1"
zousan "^2.3.3"
@ -3868,7 +3882,7 @@ resolve-url@^0.2.1:
version "0.2.1"
resolved "http://registry.npm.taobao.org/resolve-url/download/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
resolve@1.1.7, resolve@1.1.x:
resolve@1.1.x:
version "1.1.7"
resolved "http://registry.npm.taobao.org/resolve/download/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
@ -3897,22 +3911,22 @@ rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.6.0, rimraf@^2.6.1:
dependencies:
glob "^7.0.5"
rollup-plugin-babel@^3.0.2:
rollup-plugin-babel@^3.0.3:
version "3.0.3"
resolved "http://registry.npm.taobao.org/rollup-plugin-babel/download/rollup-plugin-babel-3.0.3.tgz#63adedc863130327512a4a9006efc2241c5b7c15"
dependencies:
rollup-pluginutils "^1.5.0"
rollup-plugin-buble@^0.16.0:
version "0.16.0"
resolved "http://registry.npm.taobao.org/rollup-plugin-buble/download/rollup-plugin-buble-0.16.0.tgz#3c66e2f4703527f5d27a4054f97d5eef463c5bb8"
rollup-plugin-buble@^0.19.2:
version "0.19.2"
resolved "http://registry.npm.taobao.org/rollup-plugin-buble/download/rollup-plugin-buble-0.19.2.tgz#c0590c7d3d475b5ed59f129764ec93710cc6e8dd"
dependencies:
buble "^0.16.0"
buble "^0.19.2"
rollup-pluginutils "^2.0.1"
rollup-plugin-commonjs@^8.2.6:
version "8.2.6"
resolved "http://registry.npm.taobao.org/rollup-plugin-commonjs/download/rollup-plugin-commonjs-8.2.6.tgz#27e5b9069ff94005bb01e01bb46a1e4873784677"
rollup-plugin-commonjs@^8.3.0:
version "8.3.0"
resolved "http://registry.npm.taobao.org/rollup-plugin-commonjs/download/rollup-plugin-commonjs-8.3.0.tgz#91b4ba18f340951e39ed7b1901f377a80ab3f9c3"
dependencies:
acorn "^5.2.1"
estree-walker "^0.5.0"
@ -3931,11 +3945,10 @@ rollup-plugin-local-resolve@^1.0.7:
version "1.0.7"
resolved "http://registry.npm.taobao.org/rollup-plugin-local-resolve/download/rollup-plugin-local-resolve-1.0.7.tgz#c486701716c15add2127565c2eaa101123320887"
rollup-plugin-node-resolve@^3.0.0:
version "3.0.0"
resolved "http://registry.npm.taobao.org/rollup-plugin-node-resolve/download/rollup-plugin-node-resolve-3.0.0.tgz#8b897c4c3030d5001277b0514b25d2ca09683ee0"
rollup-plugin-node-resolve@^3.0.3:
version "3.0.3"
resolved "http://registry.npm.taobao.org/rollup-plugin-node-resolve/download/rollup-plugin-node-resolve-3.0.3.tgz#8f57b253edd00e5b0ad0aed7b7e9cf5982e98fa4"
dependencies:
browser-resolve "^1.11.0"
builtin-modules "^1.1.0"
is-module "^1.0.0"
resolve "^1.1.6"
@ -3970,9 +3983,9 @@ rollup-watch@^4.3.1:
require-relative "0.8.7"
rollup-pluginutils "^2.0.1"
rollup@^0.50.0:
version "0.50.1"
resolved "http://registry.npm.taobao.org/rollup/download/rollup-0.50.1.tgz#e4dafcbf8d2bb0d9f5589d0cc6f64d76b8815730"
rollup@^0.56.1:
version "0.56.1"
resolved "http://registry.npm.taobao.org/rollup/download/rollup-0.56.1.tgz#87dd6295103da48c2376872836273785ffe0def3"
run-async@^2.2.0:
version "2.3.0"
@ -4353,6 +4366,12 @@ supports-color@^4.0.0:
dependencies:
has-flag "^2.0.0"
supports-color@^5.2.0:
version "5.2.0"
resolved "http://registry.npm.taobao.org/supports-color/download/supports-color-5.2.0.tgz#b0d5333b1184dd3666cbe5aa0b45c5ac7ac17a4a"
dependencies:
has-flag "^3.0.0"
table@^4.0.1:
version "4.0.2"
resolved "http://registry.npm.taobao.org/table/download/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36"
@ -4518,11 +4537,11 @@ uglify-js@^2.6:
optionalDependencies:
uglify-to-browserify "~1.0.0"
uglify-js@^3.1.2:
version "3.3.5"
resolved "http://registry.npm.taobao.org/uglify-js/download/uglify-js-3.3.5.tgz#4c4143dfe08e8825746675cc49a6874a933b543e"
uglify-js@^3.3.11:
version "3.3.11"
resolved "http://registry.npm.taobao.org/uglify-js/download/uglify-js-3.3.11.tgz#e9d058b20715138bb4e8e5cae2ea581686bdaae3"
dependencies:
commander "~2.12.1"
commander "~2.14.1"
source-map "~0.6.1"
uglify-to-browserify@~1.0.0:
@ -4607,10 +4626,14 @@ verror@1.10.0:
core-util-is "1.0.2"
extsprintf "^1.2.0"
vlq@^0.2.1, vlq@^0.2.2:
vlq@^0.2.1:
version "0.2.3"
resolved "http://registry.npm.taobao.org/vlq/download/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26"
vlq@^1.0.0:
version "1.0.0"
resolved "http://registry.npm.taobao.org/vlq/download/vlq-1.0.0.tgz#8101be90843422954c2b13eb27f2f3122bdcc806"
void-elements@^2.0.0:
version "2.0.1"
resolved "http://registry.npm.taobao.org/void-elements/download/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"