diff --git a/src/api.js b/src/api.js new file mode 100644 index 0000000..928e860 --- /dev/null +++ b/src/api.js @@ -0,0 +1,165 @@ +/** + * Api attribute + * Set it like this so it will not be accessible from outside + * + * @type {String} + */ +var apiAttr = ('__api' + Math.random()).slice(0, 10) + +/** + * Api constructor + * + * @param {Object} svgPanZoomInstance SvgPanZoom object instace + * @param {String} pluginName Plugin name + */ +function PluginApi(svgPanZoomInstance, pluginName) { + this._name = pluginName + + // Lock reference to API + Object.defineProperty(this, apiAttr, { + enumerable: false + , configurable: false + , writable: false + , value: svgPanZoomInstance + }) +} + +// Events handling +// =============== + +PluginApi.prototype.on = function(name, fn, ctx) { + if (typeof ctx === 'undefined') ctx = this // Automatically inject plugin context + this[apiAttr].on(name, fn, ctx, this._name) + return this +} + +PluginApi.prototype.off = function(name, fn, ctx) { + this[apiAttr].off(name, fn, ctx, this._name) + return this +} + +PluginApi.prototype.trigger = function(name, data, originalEvent) { + return this[apiAttr].trigger(name, data, this._name, originalEvent) +} + +// Panning +// ======= + +PluginApi.prototype.pan = function(point) { + this[apiAttr].pan(point, this._name) + return this +} + +PluginApi.prototype.panBy = function(point) { + this[apiAttr].panBy(point, this._name) + return this +} + +// Not namespaced +PluginApi.prototype.getPan = function() { + return this[apiAttr].getPan() +} + +// Zooming +// ======= + +PluginApi.prototype.zoom = function(scale) { + this[apiAttr].pluginZoom(scale, true, this._name) + return this +} + +PluginApi.prototype.zoomBy = function(scale) { + this[apiAttr].pluginZoom(scale, false, this._name) + return this +} + +PluginApi.prototype.zoomAtPoint = function(scale, point) { + this[apiAttr].pluginZoomAtPoint(scale, point, true, this._name) + return this +} + +PluginApi.prototype.zoomAtPointBy = function(scale, point) { + this[apiAttr].pluginZoomAtPoint(scale, point, false, this._name) + return this +} + +// Not namespaced +PluginApi.prototype.getZoom = function() { + return this[apiAttr].getRelativeZoom() +} + +// Resetting +// ========= + +PluginApi.prototype.resetZoom = function() { + this[apiAttr].resetZoom(this._name) + return this +} + +PluginApi.prototype.resetPan = function() { + this[apiAttr].resetPan(this._name) + return this +} + +PluginApi.prototype.reset = function() { + this[apiAttr].reset() + return this +} + +// Size and Resize +// =============== + +// Not namespaced +PluginApi.prototype.updateBBox = function() { + this[apiAttr].updateBBox() + return this +} + +// Not namespaced +PluginApi.prototype.resize = function() { + this[apiAttr].resize() + return this +} + +// Not namespaced +PluginApi.prototype.getSizes = function() { + return { + width: this[apiAttr].width + , height: this[apiAttr].height + , realZoom: this[apiAttr].getZoom() + , viewBox: this[apiAttr].viewport.getViewBox() + } +} + +// Plugins +// ======= + +// Not namespaced +PluginApi.prototype.addPlugin = function(name) { + this[apiAttr].addPlugin(name) + return this +} + +// Not namespaced +PluginApi.prototype.removePlugin = function(name) { + this[apiAttr].removePlugin(name) + return this +} + +// Destroy +// ======= + +// Not namespaced +PluginApi.prototype.destroy = function() { + this[apiAttr].destroy() + return this +} + +// Export +// ====== + +module.exports = { + createApi: function(svgPanZoomInstance, pluginName) { + return new PluginApi(svgPanZoomInstance, pluginName) + } +} diff --git a/src/event.js b/src/event.js index 636b446..04cef01 100644 --- a/src/event.js +++ b/src/event.js @@ -16,6 +16,16 @@ var SvgPanZoomEvent = { */ , data: null + /** + * Event namespace + * Default is `__system` + * Public API uses `__user` + * Plugins use `pluginName` + * + * @type {String} + */ +, namespace: '__system' + /** * Event propagation allowance through middlewares * @@ -56,11 +66,12 @@ var SvgPanZoomEvent = { } module.exports = { - create: function(data, originalEvent) { + create: function(data, namespace, originalEvent) { var event = Object.create(SvgPanZoomEvent) // Add data attributes data != null && (event.data = data) + namespace != null && (event.namespace = namespace) originalEvent != null && (event.originalEvent = originalEvent) return event diff --git a/src/shadow-viewport.js b/src/shadow-viewport.js index 2f3972f..b3e2af9 100644 --- a/src/shadow-viewport.js +++ b/src/shadow-viewport.js @@ -205,10 +205,9 @@ ShadowViewport.prototype.getCTM = function() { ShadowViewport.prototype.setCTM = function(newCTM, namespace) { if (this.isZoomDifferent(newCTM) || this.isPanDifferent(newCTM)) { var panZoom = this.convertCTMToPanZoom(newCTM) - panZoom.namespace = namespace // Render only if event is not prevented - if (this.options.trigger('panzoom', panZoom)) { + if (this.options.trigger('panzoom', panZoom, namespace)) { // Copy panZoom values in case they were modified this.copyPanZoomToCTM(panZoom, newCTM) @@ -262,6 +261,7 @@ ShadowViewport.prototype.updateCTM = function() { , panZoom = this.convertCTMToPanZoom(CTM) // Render only if event is not prevented + // Has no namespace as it is unknown whos change triggered this render if (this.options.trigger('render', panZoom)) { // Copy panZoom values in case they were modified this.copyPanZoomToCTM(panZoom, CTM) diff --git a/src/svg-pan-zoom.js b/src/svg-pan-zoom.js index b156acf..de53081 100644 --- a/src/svg-pan-zoom.js +++ b/src/svg-pan-zoom.js @@ -2,6 +2,7 @@ var Utils = require('./utilities') , SvgUtils = require('./svg-utilities') , ShadowViewport = require('./shadow-viewport') , SvgPanZoomEvent = require('./event') + , Api = require('./api') var SvgPanZoom = function(svg, options) { this.init(svg, options) @@ -97,13 +98,13 @@ SvgPanZoom.prototype.off = function(name, fn, ctx, pluginName) { * @param {String} name Event name. Plugins should namespace their events as pluginName:eventName * @return {Boolean} True if default event action should be done */ -SvgPanZoom.prototype.trigger = function(name, data, originalEvent) { +SvgPanZoom.prototype.trigger = function(name, data, namespace, originalEvent) { var event // Only if there are listening events if (name in this.events && this.events[name].length) { // Create event - event = SvgPanZoomEvent.create(data, originalEvent) + event = SvgPanZoomEvent.create(data, namespace, originalEvent) for (var i = 0; i < this.events[name].length; i++) { @@ -144,7 +145,7 @@ SvgPanZoom.prototype.loadPlugins = function() { */ SvgPanZoom.prototype.addPlugin = function(name) { if (name in this.pluginsStore) { - var pluginApi = this.getPluginApi(name) + var pluginApi = Api.createApi(this, name) this.plugins.push({ name: name @@ -188,8 +189,9 @@ SvgPanZoom.prototype.removePlugin = function(name) { * @param {Float} zoomScale Number representing how much to zoom * @param {Boolean} zoomAbsolute Default false. If true, zoomScale is treated as an absolute value. * Otherwise, zoomScale is treated as a multiplied (e.g. 1.10 would zoom in 10%) + * @param {String} namespace Method namespace */ -SvgPanZoom.prototype.zoomAtPoint = function(zoomScale, point, zoomAbsolute) { +SvgPanZoom.prototype.zoomAtPoint = function(zoomScale, point, zoomAbsolute, namespace) { var originalState = this.viewport.getOriginalState() if (zoomAbsolute) { @@ -203,7 +205,7 @@ SvgPanZoom.prototype.zoomAtPoint = function(zoomScale, point, zoomAbsolute) { , newCTM = oldCTM.multiply(modifier) if (newCTM.a !== oldCTM.a) { - this.viewport.setCTM(newCTM) + this.viewport.setCTM(newCTM, namespace) } } @@ -212,9 +214,10 @@ SvgPanZoom.prototype.zoomAtPoint = function(zoomScale, point, zoomAbsolute) { * * @param {Float} scale * @param {Boolean} absolute Marks zoom scale as relative or absolute + * @param {String} namespace Method namespace */ -SvgPanZoom.prototype.zoom = function(scale, absolute) { - this.zoomAtPoint(scale, SvgUtils.getSvgCenterPoint(this.svg, this.width, this.height), absolute) +SvgPanZoom.prototype.zoom = function(scale, absolute, namespace) { + this.zoomAtPoint(scale, SvgUtils.getSvgCenterPoint(this.svg, this.width, this.height), absolute, namespace) } /** @@ -222,13 +225,14 @@ SvgPanZoom.prototype.zoom = function(scale, absolute) { * * @param {Float} scale * @param {Boolean} absolute Marks zoom scale as relative or absolute + * @param {string} namespace Method namespace */ -SvgPanZoom.prototype.publicZoom = function(scale, absolute) { +SvgPanZoom.prototype.pluginZoom = function(scale, absolute, namespace) { if (absolute) { scale = this.computeFromRelativeZoom(scale) } - this.zoom(scale, absolute) + this.zoom(scale, absolute, namespace) } /** @@ -237,8 +241,9 @@ SvgPanZoom.prototype.publicZoom = function(scale, absolute) { * @param {Float} scale * @param {SVGPoint|Object} point An object that has x and y attributes * @param {Boolean} absolute Marks zoom scale as relative or absolute + * @param {String} namespace Method namespace */ -SvgPanZoom.prototype.publicZoomAtPoint = function(scale, point, absolute) { +SvgPanZoom.prototype.pluginZoomAtPoint = function(scale, point, absolute, namespace) { if (absolute) { // Transform zoom into a relative value scale = this.computeFromRelativeZoom(scale) @@ -253,7 +258,7 @@ SvgPanZoom.prototype.publicZoomAtPoint = function(scale, point, absolute) { } } - this.zoomAtPoint(scale, point, absolute) + this.zoomAtPoint(scale, point, absolute, namespace) } /** @@ -287,25 +292,25 @@ SvgPanZoom.prototype.computeFromRelativeZoom = function(zoom) { /** * Set zoom to initial state */ -SvgPanZoom.prototype.resetZoom = function() { +SvgPanZoom.prototype.resetZoom = function(namespace) { var originalState = this.viewport.getOriginalState() - this.zoom(originalState.zoom, true); + this.zoom(originalState.zoom, true, namespace); } /** * Set pan to initial state */ -SvgPanZoom.prototype.resetPan = function() { - this.pan(this.viewport.getOriginalState()); +SvgPanZoom.prototype.resetPan = function(namespace) { + this.pan(this.viewport.getOriginalState(), namespace); } /** * Set pan and zoom to initial state */ -SvgPanZoom.prototype.reset = function() { - this.resetZoom() - this.resetPan() +SvgPanZoom.prototype.reset = function(namespace) { + this.resetZoom(namespace) + this.resetPan(namespace) } /** @@ -320,24 +325,26 @@ SvgPanZoom.prototype.updateBBox = function() { * Pan to a rendered position * * @param {Object} point {x: 0, y: 0} + * @param {String} namespace Method namespace */ -SvgPanZoom.prototype.pan = function(point) { +SvgPanZoom.prototype.pan = function(point, namespace) { var viewportCTM = this.viewport.getCTM() viewportCTM.e = point.x viewportCTM.f = point.y - this.viewport.setCTM(viewportCTM) + this.viewport.setCTM(viewportCTM, namespace) } /** * Relatively pan the graph by a specified rendered position vector * * @param {Object} point {x: 0, y: 0} + * @param {String} namespace Method namespace */ -SvgPanZoom.prototype.panBy = function(point) { +SvgPanZoom.prototype.panBy = function(point, namespace) { var viewportCTM = this.viewport.getCTM() viewportCTM.e += point.x viewportCTM.f += point.y - this.viewport.setCTM(viewportCTM) + this.viewport.setCTM(viewportCTM, namespace) } /** @@ -367,17 +374,13 @@ SvgPanZoom.prototype.resize = function() { SvgPanZoom.prototype.destroy = function() { var that = this + this.trigger('before:destroy', null) + // Remove all plugins while (this.plugins.length) { this.removePlugin(this.plugins[0].name) } - // Unbind eventListeners - for (var event in this.eventListeners) { - (this.options.eventsListenerElement || this.svg) - .removeEventListener(event, this.eventListeners[event], false) - } - // Reset zoom and pan this.reset() @@ -392,12 +395,9 @@ SvgPanZoom.prototype.destroy = function() { // Delete options and its contents delete this.options - // Destroy public instance and rewrite getPublicApi + // Destroy public instance and getPublicApi method delete this.publicApi - delete this.pi - this.getPublicApi = function(){ - return null - } + delete this.getPublicApi } /** @@ -406,78 +406,14 @@ SvgPanZoom.prototype.destroy = function() { * @return {Object} Public API object */ SvgPanZoom.prototype.getPublicApi = function() { - var that = this - - // Create cache + // Cache if (!this.publicApi) { - this.publicApi = this.pi = { - // Pan - pan: function(point) {that.pan(point); return that.pi} - , panBy: function(point) {that.panBy(point); return that.pi} - , getPan: function() {return that.getPan()} - // Zooming - , zoom: function(scale) {that.publicZoom(scale, true); return that.pi} - , zoomBy: function(scale) {that.publicZoom(scale, false); return that.pi} - , zoomAtPoint: function(scale, point) {that.publicZoomAtPoint(scale, point, true); return that.pi} - , zoomAtPointBy: function(scale, point) {that.publicZoomAtPoint(scale, point, false); return that.pi} - , getZoom: function() {return that.getRelativeZoom()} - // Reset - , resetZoom: function() {that.resetZoom(); return that.pi} - , resetPan: function() {that.resetPan(); return that.pi} - , reset: function() {that.reset(); return that.pi} - // Size and Resize - , updateBBox: function() {that.updateBBox(); return that.pi} - , resize: function() {that.resize(); return that.pi} - , getSizes: function() { - return { - width: that.width - , height: that.height - , realZoom: that.getZoom() - , viewBox: that.viewport.getViewBox() - } - } - // Events handling - , on: function(name, fn, ctx) { - if (typeof ctx === 'undefined') ctx = that.pi // Automatically inject public context - that.on(name, fn, ctx, '__user') - return that.pi - } - , off: function(name, fn, ctx) {that.off(name, fn, ctx, '__user'); return that.pi} - , trigger: function(name, data, oE) {return that.trigger(name, data, oE)} - // Plugins - , addPlugin: function(name) {that.addPlugin(name); return that.pi} - , removePlugin: function(name) {that.removePlugin(name); return that.pi} - // Destroy - , destroy: function() {that.destroy(); return that.pi} - } + this.publicApi = Api.createApi(this, '__user') } return this.publicApi } -/** - * Creates similar to public API but namespaced to given plugin - * - * @param {[type]} pluginName [description] - * @return {[type]} [description] - */ -SvgPanZoom.prototype.getPluginApi = function(pluginName) { - var publicApi = this.getPublicApi() - , that = this - - // Same API as for public use but with slight differences - var pluginApi = Object.create(publicApi) - - pluginApi.on = function(name, fn, ctx) { - if (typeof ctx === 'undefined') ctx = pluginApi // Automatically inject plugin context - that.on(name, fn, ctx, pluginName) - return pluginApi - } - pluginApi.off = function(name, fn, ctx) {that.off(name, fn, ctx, pluginName); return pluginApi} - - return pluginApi -} - /** * Keeps all plugins *