%PDF- %PDF-
Direktori : /home/jalalj2hb/www/ftm-admin/bower_components/kineticjs/src/ |
Current File : /home/jalalj2hb/www/ftm-admin/bower_components/kineticjs/src/Node.js |
(function() { // CONSTANTS var ABSOLUTE_OPACITY = 'absoluteOpacity', ABSOLUTE_TRANSFORM = 'absoluteTransform', BEFORE = 'before', CHANGE = 'Change', CHILDREN = 'children', DOT = '.', EMPTY_STRING = '', GET = 'get', ID = 'id', KINETIC = 'kinetic', LISTENING = 'listening', MOUSEENTER = 'mouseenter', MOUSELEAVE = 'mouseleave', NAME = 'name', SET = 'set', SHAPE = 'Shape', SPACE = ' ', STAGE = 'stage', TRANSFORM = 'transform', UPPER_STAGE = 'Stage', VISIBLE = 'visible', CLONE_BLACK_LIST = ['id'], TRANSFORM_CHANGE_STR = [ 'xChange.kinetic', 'yChange.kinetic', 'scaleXChange.kinetic', 'scaleYChange.kinetic', 'skewXChange.kinetic', 'skewYChange.kinetic', 'rotationChange.kinetic', 'offsetXChange.kinetic', 'offsetYChange.kinetic', 'transformsEnabledChange.kinetic' ].join(SPACE); Kinetic.Util.addMethods(Kinetic.Node, { _init: function(config) { var that = this; this._id = Kinetic.idCounter++; this.eventListeners = {}; this.attrs = {}; this._cache = {}; this._filterUpToDate = false; this.setAttrs(config); // event bindings for cache handling this.on(TRANSFORM_CHANGE_STR, function() { this._clearCache(TRANSFORM); that._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); }); this.on('visibleChange.kinetic', function() { that._clearSelfAndDescendantCache(VISIBLE); }); this.on('listeningChange.kinetic', function() { that._clearSelfAndDescendantCache(LISTENING); }); this.on('opacityChange.kinetic', function() { that._clearSelfAndDescendantCache(ABSOLUTE_OPACITY); }); }, _clearCache: function(attr){ if (attr) { delete this._cache[attr]; } else { this._cache = {}; } }, _getCache: function(attr, privateGetter){ var cache = this._cache[attr]; // if not cached, we need to set it using the private getter method. if (cache === undefined) { this._cache[attr] = privateGetter.call(this); } return this._cache[attr]; }, /* * when the logic for a cached result depends on ancestor propagation, use this * method to clear self and children cache */ _clearSelfAndDescendantCache: function(attr) { this._clearCache(attr); if (this.children) { this.getChildren().each(function(node) { node._clearSelfAndDescendantCache(attr); }); } }, /** * clear cached canvas * @method * @memberof Kinetic.Node.prototype * @returns {Kinetic.Node} * @example * node.clearCache(); */ clearCache: function() { delete this._cache.canvas; this._filterUpToDate = false; return this; }, /** * cache node to improve drawing performance, apply filters, or create more accurate * hit regions * @method * @memberof Kinetic.Node.prototype * @param {Object} config * @param {Number} [config.x] * @param {Number} [config.y] * @param {Number} [config.width] * @param {Number} [config.height] * @param {Boolean} [config.drawBorder] when set to true, a red border will be drawn around the cached * region for debugging purposes * @returns {Kinetic.Node} * @example * // cache a shape with the x,y position of the bounding box at the center and<br> * // the width and height of the bounding box equal to the width and height of<br> * // the shape obtained from shape.width() and shape.height()<br> * image.cache();<br><br> * * // cache a node and define the bounding box position and size<br> * node.cache({<br> * x: -30,<br> * y: -30,<br> * width: 100,<br> * height: 200<br> * });<br><br> * * // cache a node and draw a red border around the bounding box<br> * // for debugging purposes<br> * node.cache({<br> * x: -30,<br> * y: -30,<br> * width: 100,<br> * height: 200,<br> * drawBorder: true<br> * }); */ cache: function(config) { var conf = config || {}, x = conf.x || 0, y = conf.y || 0, width = conf.width || this.width(), height = conf.height || this.height(), drawBorder = conf.drawBorder || false, layer = this.getLayer(); if (width === 0 || height === 0) { Kinetic.Util.warn('Width or height of caching configuration equals 0. Cache is ignored.'); return; } var cachedSceneCanvas = new Kinetic.SceneCanvas({ pixelRatio: 1, width: width, height: height }), cachedFilterCanvas = new Kinetic.SceneCanvas({ pixelRatio: 1, width: width, height: height }), cachedHitCanvas = new Kinetic.HitCanvas({ width: width, height: height }), origTransEnabled = this.transformsEnabled(), origX = this.x(), origY = this.y(), sceneContext = cachedSceneCanvas.getContext(), hitContext = cachedHitCanvas.getContext(); this.clearCache(); sceneContext.save(); hitContext.save(); // this will draw a red border around the cached box for // debugging purposes if (drawBorder) { sceneContext.save(); sceneContext.beginPath(); sceneContext.rect(0, 0, width, height); sceneContext.closePath(); sceneContext.setAttr('strokeStyle', 'red'); sceneContext.setAttr('lineWidth', 5); sceneContext.stroke(); sceneContext.restore(); } sceneContext.translate(x * -1, y * -1); hitContext.translate(x * -1, y * -1); if (this.nodeType === 'Shape') { sceneContext.translate(this.x() * -1, this.y() * -1); hitContext.translate(this.x() * -1, this.y() * -1); } this.drawScene(cachedSceneCanvas, this); this.drawHit(cachedHitCanvas, this); sceneContext.restore(); hitContext.restore(); this._cache.canvas = { scene: cachedSceneCanvas, filter: cachedFilterCanvas, hit: cachedHitCanvas }; return this; }, _drawCachedSceneCanvas: function(context) { context.save(); this.getLayer()._applyTransform(this, context); context.drawImage(this._getCachedSceneCanvas()._canvas, 0, 0); context.restore(); }, _getCachedSceneCanvas: function() { var filters = this.filters(), cachedCanvas = this._cache.canvas, sceneCanvas = cachedCanvas.scene, filterCanvas = cachedCanvas.filter, filterContext = filterCanvas.getContext(), len, imageData, n, filter; if (filters) { if (!this._filterUpToDate) { try { len = filters.length; filterContext.clear(); // copy cached canvas onto filter context filterContext.drawImage(sceneCanvas._canvas, 0, 0); imageData = filterContext.getImageData(0, 0, filterCanvas.getWidth(), filterCanvas.getHeight()); // apply filters to filter context for (n=0; n<len; n++) { filter = filters[n]; filter.call(this, imageData); filterContext.putImageData(imageData, 0, 0); } } catch(e) { Kinetic.Util.warn('Unable to apply filter. ' + e.message); } this._filterUpToDate = true; } return filterCanvas; } else { return sceneCanvas; } }, _drawCachedHitCanvas: function(context) { var cachedCanvas = this._cache.canvas, hitCanvas = cachedCanvas.hit; context.save(); this.getLayer()._applyTransform(this, context); context.drawImage(hitCanvas._canvas, 0, 0); context.restore(); }, /** * bind events to the node. KineticJS supports mouseover, mousemove, * mouseout, mouseenter, mouseleave, mousedown, mouseup, click, dblclick, touchstart, touchmove, * touchend, tap, dbltap, dragstart, dragmove, and dragend events. The Kinetic Stage supports * contentMouseover, contentMousemove, contentMouseout, contentMousedown, contentMouseup, * contentClick, contentDblclick, contentTouchstart, contentTouchmove, contentTouchend, contentTap, * and contentDblTap. Pass in a string of events delimmited by a space to bind multiple events at once * such as 'mousedown mouseup mousemove'. Include a namespace to bind an * event by name such as 'click.foobar'. * @method * @memberof Kinetic.Node.prototype * @param {String} evtStr e.g. 'click', 'mousedown touchstart', 'mousedown.foo touchstart.foo' * @param {Function} handler The handler function is passed an event object * @returns {Kinetic.Node} * @example * // add click listener<br> * node.on('click', function() {<br> * console.log('you clicked me!');<br> * });<br><br> * * // get the target node<br> * node.on('click', function(evt) {<br> * console.log(evt.target);<br> * });<br><br> * * // stop event propagation<br> * node.on('click', function(evt) {<br> * evt.cancelBubble = true;<br> * });<br><br> * * // bind multiple listeners<br> * node.on('click touchstart', function() {<br> * console.log('you clicked/touched me!');<br> * });<br><br> * * // namespace listener<br> * node.on('click.foo', function() {<br> * console.log('you clicked/touched me!');<br> * });<br><br> * * // get the event type<br> * node.on('click tap', function(evt) {<br> * var eventType = evt.type;<br> * });<br><br> * * // get native event object<br> * node.on('click tap', function(evt) {<br> * var nativeEvent = evt.evt;<br> * });<br><br> * * // for change events, get the old and new val<br> * node.on('xChange', function(evt) {<br> * var oldVal = evt.oldVal;<br> * var newVal = evt.newVal;<br> * }); */ on: function(evtStr, handler) { var events = evtStr.split(SPACE), len = events.length, n, event, parts, baseEvent, name; /* * loop through types and attach event listeners to * each one. eg. 'click mouseover.namespace mouseout' * will create three event bindings */ for(n = 0; n < len; n++) { event = events[n]; parts = event.split(DOT); baseEvent = parts[0]; name = parts[1] || EMPTY_STRING; // create events array if it doesn't exist if(!this.eventListeners[baseEvent]) { this.eventListeners[baseEvent] = []; } this.eventListeners[baseEvent].push({ name: name, handler: handler }); // NOTE: this flag is set to true when any event handler is added, even non // mouse or touch gesture events. This improves performance for most // cases where users aren't using events, but is still very light weight. // To ensure perfect accuracy, devs can explicitly set listening to false. /* if (name !== KINETIC) { this._listeningEnabled = true; this._clearSelfAndAncestorCache(LISTENING_ENABLED); } */ } return this; }, /** * remove event bindings from the node. Pass in a string of * event types delimmited by a space to remove multiple event * bindings at once such as 'mousedown mouseup mousemove'. * include a namespace to remove an event binding by name * such as 'click.foobar'. If you only give a name like '.foobar', * all events in that namespace will be removed. * @method * @memberof Kinetic.Node.prototype * @param {String} evtStr e.g. 'click', 'mousedown touchstart', '.foobar' * @returns {Kinetic.Node} * @example * // remove listener<br> * node.off('click');<br><br> * * // remove multiple listeners<br> * node.off('click touchstart');<br><br> * * // remove listener by name<br> * node.off('click.foo'); */ off: function(evtStr) { var events = evtStr.split(SPACE), len = events.length, n, t, event, parts, baseEvent, name; for(n = 0; n < len; n++) { event = events[n]; parts = event.split(DOT); baseEvent = parts[0]; name = parts[1]; if(baseEvent) { if(this.eventListeners[baseEvent]) { this._off(baseEvent, name); } } else { for(t in this.eventListeners) { this._off(t, name); } } } return this; }, // some event aliases for third party integration like HammerJS dispatchEvent: function(evt) { var e = { target: this, type: evt.type, evt: evt }; this.fire(evt.type, e); }, addEventListener: function(type, handler) { // we to pass native event to handler this.on(type, function(evt){ handler.call(this, evt.evt); }); }, /** * remove self from parent, but don't destroy * @method * @memberof Kinetic.Node.prototype * @returns {Kinetic.Node} * @example * node.remove(); */ remove: function() { var parent = this.getParent(); if(parent && parent.children) { parent.children.splice(this.index, 1); parent._setChildrenIndices(); delete this.parent; } // every cached attr that is calculated via node tree // traversal must be cleared when removing a node this._clearSelfAndDescendantCache(STAGE); this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); this._clearSelfAndDescendantCache(VISIBLE); this._clearSelfAndDescendantCache(LISTENING); this._clearSelfAndDescendantCache(ABSOLUTE_OPACITY); return this; }, /** * remove and destroy self * @method * @memberof Kinetic.Node.prototype * @example * node.destroy(); */ destroy: function() { // remove from ids and names hashes Kinetic._removeId(this.getId()); Kinetic._removeName(this.getName(), this._id); this.remove(); }, /** * get attr * @method * @memberof Kinetic.Node.prototype * @param {String} attr * @returns {Integer|String|Object|Array} * @example * var x = node.getAttr('x'); */ getAttr: function(attr) { var method = GET + Kinetic.Util._capitalize(attr); if(Kinetic.Util._isFunction(this[method])) { return this[method](); } // otherwise get directly else { return this.attrs[attr]; } }, /** * get ancestors * @method * @memberof Kinetic.Node.prototype * @returns {Kinetic.Collection} * @example * shape.getAncestors().each(function(node) { * console.log(node.getId()); * }) */ getAncestors: function() { var parent = this.getParent(), ancestors = new Kinetic.Collection(); while (parent) { ancestors.push(parent); parent = parent.getParent(); } return ancestors; }, /** * get attrs object literal * @method * @memberof Kinetic.Node.prototype * @returns {Object} */ getAttrs: function() { return this.attrs || {}; }, /** * set multiple attrs at once using an object literal * @method * @memberof Kinetic.Node.prototype * @param {Object} config object containing key value pairs * @returns {Kinetic.Node} * @example * node.setAttrs({<br> * x: 5,<br> * fill: 'red'<br> * });<br> */ setAttrs: function(config) { var key, method; if(config) { for(key in config) { if (key === CHILDREN) { } else { method = SET + Kinetic.Util._capitalize(key); // use setter if available if(Kinetic.Util._isFunction(this[method])) { this[method](config[key]); } // otherwise set directly else { this._setAttr(key, config[key]); } } } } return this; }, /** * determine if node is listening for events by taking into account ancestors. * * Parent | Self | isListening * listening | listening | * ----------+-----------+------------ * T | T | T * T | F | F * F | T | T * F | F | F * ----------+-----------+------------ * T | I | T * F | I | F * I | I | T * * @method * @memberof Kinetic.Node.prototype * @returns {Boolean} */ isListening: function() { return this._getCache(LISTENING, this._isListening); }, _isListening: function() { var listening = this.getListening(), parent = this.getParent(); // the following conditions are a simplification of the truth table above. // please modify carefully if (listening === 'inherit') { if (parent) { return parent.isListening(); } else { return true; } } else { return listening; } }, /** * determine if node is visible by taking into account ancestors. * * Parent | Self | isVisible * visible | visible | * ----------+-----------+------------ * T | T | T * T | F | F * F | T | T * F | F | F * ----------+-----------+------------ * T | I | T * F | I | F * I | I | T * @method * @memberof Kinetic.Node.prototype * @returns {Boolean} */ isVisible: function() { return this._getCache(VISIBLE, this._isVisible); }, _isVisible: function() { var visible = this.getVisible(), parent = this.getParent(); // the following conditions are a simplification of the truth table above. // please modify carefully if (visible === 'inherit') { if (parent) { return parent.isVisible(); } else { return true; } } else { return visible; } }, /** * determine if listening is enabled by taking into account descendants. If self or any children * have _isListeningEnabled set to true, then self also has listening enabled. * @method * @memberof Kinetic.Node.prototype * @returns {Boolean} */ shouldDrawHit: function() { var layer = this.getLayer(); return layer && layer.hitGraphEnabled() && this.isListening() && this.isVisible() && !Kinetic.isDragging(); }, /** * show node * @method * @memberof Kinetic.Node.prototype * @returns {Kinetic.Node} */ show: function() { this.setVisible(true); return this; }, /** * hide node. Hidden nodes are no longer detectable * @method * @memberof Kinetic.Node.prototype * @returns {Kinetic.Node} */ hide: function() { this.setVisible(false); return this; }, /** * get zIndex relative to the node's siblings who share the same parent * @method * @memberof Kinetic.Node.prototype * @returns {Integer} */ getZIndex: function() { return this.index || 0; }, /** * get absolute z-index which takes into account sibling * and ancestor indices * @method * @memberof Kinetic.Node.prototype * @returns {Integer} */ getAbsoluteZIndex: function() { var depth = this.getDepth(), that = this, index = 0, nodes, len, n, child; function addChildren(children) { nodes = []; len = children.length; for(n = 0; n < len; n++) { child = children[n]; index++; if(child.nodeType !== SHAPE) { nodes = nodes.concat(child.getChildren().toArray()); } if(child._id === that._id) { n = len; } } if(nodes.length > 0 && nodes[0].getDepth() <= depth) { addChildren(nodes); } } if(that.nodeType !== UPPER_STAGE) { addChildren(that.getStage().getChildren()); } return index; }, /** * get node depth in node tree. Returns an integer.<br><br> * e.g. Stage depth will always be 0. Layers will always be 1. Groups and Shapes will always * be >= 2 * @method * @memberof Kinetic.Node.prototype * @returns {Integer} */ getDepth: function() { var depth = 0, parent = this.parent; while(parent) { depth++; parent = parent.parent; } return depth; }, setPosition: function(pos) { this.setX(pos.x); this.setY(pos.y); return this; }, getPosition: function() { return { x: this.getX(), y: this.getY() }; }, /** * get absolute position relative to the top left corner of the stage container div * @method * @memberof Kinetic.Node.prototype * @returns {Object} */ getAbsolutePosition: function() { var absoluteMatrix = this.getAbsoluteTransform().getMatrix(), absoluteTransform = new Kinetic.Transform(), offset = this.offset(); // clone the matrix array absoluteTransform.m = absoluteMatrix.slice(); absoluteTransform.translate(offset.x, offset.y); return absoluteTransform.getTranslation(); }, /** * set absolute position * @method * @memberof Kinetic.Node.prototype * @param {Object} pos * @param {Number} pos.x * @param {Number} pos.y * @returns {Kinetic.Node} */ setAbsolutePosition: function(pos) { var origTrans = this._clearTransform(), it; // don't clear translation this.attrs.x = origTrans.x; this.attrs.y = origTrans.y; delete origTrans.x; delete origTrans.y; // unravel transform it = this.getAbsoluteTransform(); it.invert(); it.translate(pos.x, pos.y); pos = { x: this.attrs.x + it.getTranslation().x, y: this.attrs.y + it.getTranslation().y }; this.setPosition({x:pos.x, y:pos.y}); this._setTransform(origTrans); return this; }, _setTransform: function(trans) { var key; for(key in trans) { this.attrs[key] = trans[key]; } this._clearCache(TRANSFORM); this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); }, _clearTransform: function() { var trans = { x: this.getX(), y: this.getY(), rotation: this.getRotation(), scaleX: this.getScaleX(), scaleY: this.getScaleY(), offsetX: this.getOffsetX(), offsetY: this.getOffsetY(), skewX: this.getSkewX(), skewY: this.getSkewY() }; this.attrs.x = 0; this.attrs.y = 0; this.attrs.rotation = 0; this.attrs.scaleX = 1; this.attrs.scaleY = 1; this.attrs.offsetX = 0; this.attrs.offsetY = 0; this.attrs.skewX = 0; this.attrs.skewY = 0; this._clearCache(TRANSFORM); this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); // return original transform return trans; }, /** * move node by an amount relative to its current position * @method * @memberof Kinetic.Node.prototype * @param {Object} change * @param {Number} change.x * @param {Number} change.y * @returns {Kinetic.Node} * @example * // move node in x direction by 1px and y direction by 2px<br> * node.move({<br> * x: 1,<br> * y: 2)<br> * }); */ move: function(change) { var changeX = change.x, changeY = change.y, x = this.getX(), y = this.getY(); if(changeX !== undefined) { x += changeX; } if(changeY !== undefined) { y += changeY; } this.setPosition({x:x, y:y}); return this; }, _eachAncestorReverse: function(func, top) { var family = [], parent = this.getParent(), len, n; // if top node is defined, and this node is top node, // there's no need to build a family tree. just execute // func with this because it will be the only node if (top && top._id === this._id) { func(this); return true; } family.unshift(this); while(parent && (!top || parent._id !== top._id)) { family.unshift(parent); parent = parent.parent; } len = family.length; for(n = 0; n < len; n++) { func(family[n]); } }, /** * rotate node by an amount in degrees relative to its current rotation * @method * @memberof Kinetic.Node.prototype * @param {Number} theta * @returns {Kinetic.Node} */ rotate: function(theta) { this.setRotation(this.getRotation() + theta); return this; }, /** * move node to the top of its siblings * @method * @memberof Kinetic.Node.prototype * @returns {Boolean} */ moveToTop: function() { if (!this.parent) { Kinetic.Util.warn('Node has no parent. moveToTop function is ignored.'); return; } var index = this.index; this.parent.children.splice(index, 1); this.parent.children.push(this); this.parent._setChildrenIndices(); return true; }, /** * move node up * @method * @memberof Kinetic.Node.prototype * @returns {Boolean} */ moveUp: function() { if (!this.parent) { Kinetic.Util.warn('Node has no parent. moveUp function is ignored.'); return; } var index = this.index, len = this.parent.getChildren().length; if(index < len - 1) { this.parent.children.splice(index, 1); this.parent.children.splice(index + 1, 0, this); this.parent._setChildrenIndices(); return true; } return false; }, /** * move node down * @method * @memberof Kinetic.Node.prototype * @returns {Boolean} */ moveDown: function() { if (!this.parent) { Kinetic.Util.warn('Node has no parent. moveDown function is ignored.'); return; } var index = this.index; if(index > 0) { this.parent.children.splice(index, 1); this.parent.children.splice(index - 1, 0, this); this.parent._setChildrenIndices(); return true; } return false; }, /** * move node to the bottom of its siblings * @method * @memberof Kinetic.Node.prototype * @returns {Boolean} */ moveToBottom: function() { if (!this.parent) { Kinetic.Util.warn('Node has no parent. moveToBottom function is ignored.'); return; } var index = this.index; if(index > 0) { this.parent.children.splice(index, 1); this.parent.children.unshift(this); this.parent._setChildrenIndices(); return true; } return false; }, /** * set zIndex relative to siblings * @method * @memberof Kinetic.Node.prototype * @param {Integer} zIndex * @returns {Kinetic.Node} */ setZIndex: function(zIndex) { if (!this.parent) { Kinetic.Util.warn('Node has no parent. zIndex parameter is ignored.'); return; } var index = this.index; this.parent.children.splice(index, 1); this.parent.children.splice(zIndex, 0, this); this.parent._setChildrenIndices(); return this; }, /** * get absolute opacity * @method * @memberof Kinetic.Node.prototype * @returns {Number} */ getAbsoluteOpacity: function() { return this._getCache(ABSOLUTE_OPACITY, this._getAbsoluteOpacity); }, _getAbsoluteOpacity: function() { var absOpacity = this.getOpacity(); if(this.getParent()) { absOpacity *= this.getParent().getAbsoluteOpacity(); } return absOpacity; }, /** * move node to another container * @method * @memberof Kinetic.Node.prototype * @param {Container} newContainer * @returns {Kinetic.Node} * @example * // move node from current layer into layer2<br> * node.moveTo(layer2); */ moveTo: function(newContainer) { Kinetic.Node.prototype.remove.call(this); newContainer.add(this); return this; }, /** * convert Node into an object for serialization. Returns an object. * @method * @memberof Kinetic.Node.prototype * @returns {Object} */ toObject: function() { var type = Kinetic.Util, obj = {}, attrs = this.getAttrs(), key, val, getter, defaultValue; obj.attrs = {}; // serialize only attributes that are not function, image, DOM, or objects with methods for(key in attrs) { val = attrs[key]; if (!type._isFunction(val) && !type._isElement(val) && !(type._isObject(val) && type._hasMethods(val))) { getter = this[key]; // remove attr value so that we can extract the default value from the getter delete attrs[key]; defaultValue = getter ? getter.call(this) : null; // restore attr value attrs[key] = val; if (defaultValue !== val) { obj.attrs[key] = val; } } } obj.className = this.getClassName(); return obj; }, /** * convert Node into a JSON string. Returns a JSON string. * @method * @memberof Kinetic.Node.prototype * @returns {String}} */ toJSON: function() { return JSON.stringify(this.toObject()); }, /** * get parent container * @method * @memberof Kinetic.Node.prototype * @returns {Kinetic.Node} */ getParent: function() { return this.parent; }, /** * get layer ancestor * @method * @memberof Kinetic.Node.prototype * @returns {Kinetic.Layer} */ getLayer: function() { var parent = this.getParent(); return parent ? parent.getLayer() : null; }, /** * get stage ancestor * @method * @memberof Kinetic.Node.prototype * @returns {Kinetic.Stage} */ getStage: function() { return this._getCache(STAGE, this._getStage); }, _getStage: function() { var parent = this.getParent(); if(parent) { return parent.getStage(); } else { return undefined; } }, /** * fire event * @method * @memberof Kinetic.Node.prototype * @param {String} eventType event type. can be a regular event, like click, mouseover, or mouseout, or it can be a custom event, like myCustomEvent * @param {EventObject} [evt] event object * @param {Boolean} [bubble] setting the value to false, or leaving it undefined, will result in the event * not bubbling. Setting the value to true will result in the event bubbling. * @returns {Kinetic.Node} * @example * // manually fire click event<br> * node.fire('click');<br><br> * * // fire custom event<br> * node.fire('foo');<br><br> * * // fire custom event with custom event object<br> * node.fire('foo', {<br> * bar: 10<br> * });<br><br> * * // fire click event that bubbles<br> * node.fire('click', null, true); */ fire: function(eventType, evt, bubble) { // bubble if (bubble) { this._fireAndBubble(eventType, evt || {}); } // no bubble else { this._fire(eventType, evt || {}); } return this; }, /** * get absolute transform of the node which takes into * account its ancestor transforms * @method * @memberof Kinetic.Node.prototype * @returns {Kinetic.Transform} */ getAbsoluteTransform: function(top) { // if using an argument, we can't cache the result. if (top) { return this._getAbsoluteTransform(top); } // if no argument, we can cache the result else { return this._getCache(ABSOLUTE_TRANSFORM, this._getAbsoluteTransform); } }, _getAbsoluteTransform: function(top) { var at = new Kinetic.Transform(), transformsEnabled, trans; // start with stage and traverse downwards to self this._eachAncestorReverse(function(node) { transformsEnabled = node.transformsEnabled(); trans = node.getTransform(); if (transformsEnabled === 'all') { at.multiply(trans); } else if (transformsEnabled === 'position') { at.translate(node.x(), node.y()); } }, top); return at; }, /** * get transform of the node * @method * @memberof Kinetic.Node.prototype * @returns {Kinetic.Transform} */ getTransform: function() { return this._getCache(TRANSFORM, this._getTransform); }, _getTransform: function() { var m = new Kinetic.Transform(), x = this.getX(), y = this.getY(), rotation = Kinetic.getAngle(this.getRotation()), scaleX = this.getScaleX(), scaleY = this.getScaleY(), skewX = this.getSkewX(), skewY = this.getSkewY(), offsetX = this.getOffsetX(), offsetY = this.getOffsetY(); if(x !== 0 || y !== 0) { m.translate(x, y); } if(rotation !== 0) { m.rotate(rotation); } if(skewX !== 0 || skewY !== 0) { m.skew(skewX, skewY); } if(scaleX !== 1 || scaleY !== 1) { m.scale(scaleX, scaleY); } if(offsetX !== 0 || offsetY !== 0) { m.translate(-1 * offsetX, -1 * offsetY); } return m; }, /** * clone node. Returns a new Node instance with identical attributes. You can also override * the node properties with an object literal, enabling you to use an existing node as a template * for another node * @method * @memberof Kinetic.Node.prototype * @param {Object} attrs override attrs * @returns {Kinetic.Node} * @example * // simple clone<br> * var clone = node.clone();<br><br> * * // clone a node and override the x position<br> * var clone = rect.clone({<br> * x: 5<br> * }); */ clone: function(obj) { // instantiate new node var className = this.getClassName(), attrs = Kinetic.Util.cloneObject(this.attrs), key, allListeners, len, n, listener; // filter black attrs for (var i in CLONE_BLACK_LIST) { var blockAttr = CLONE_BLACK_LIST[i]; delete attrs[blockAttr]; } // apply attr overrides for (key in obj) { attrs[key] = obj[key]; } var node = new Kinetic[className](attrs); // copy over listeners for(key in this.eventListeners) { allListeners = this.eventListeners[key]; len = allListeners.length; for(n = 0; n < len; n++) { listener = allListeners[n]; /* * don't include kinetic namespaced listeners because * these are generated by the constructors */ if(listener.name.indexOf(KINETIC) < 0) { // if listeners array doesn't exist, then create it if(!node.eventListeners[key]) { node.eventListeners[key] = []; } node.eventListeners[key].push(listener); } } } return node; }, /** * Creates a composite data URL. If MIME type is not * specified, then "image/png" will result. For "image/jpeg", specify a quality * level as quality (range 0.0 - 1.0) * @method * @memberof Kinetic.Node.prototype * @param {Object} config * @param {String} [config.mimeType] can be "image/png" or "image/jpeg". * "image/png" is the default * @param {Number} [config.x] x position of canvas section * @param {Number} [config.y] y position of canvas section * @param {Number} [config.width] width of canvas section * @param {Number} [config.height] height of canvas section * @param {Number} [config.quality] jpeg quality. If using an "image/jpeg" mimeType, * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 * is very high quality * @returns {String} */ toDataURL: function(config) { config = config || {}; var mimeType = config.mimeType || null, quality = config.quality || null, stage = this.getStage(), x = config.x || 0, y = config.y || 0, canvas = new Kinetic.SceneCanvas({ width: config.width || this.getWidth() || (stage ? stage.getWidth() : 0), height: config.height || this.getHeight() || (stage ? stage.getHeight() : 0), pixelRatio: 1 }), context = canvas.getContext(); context.save(); if(x || y) { context.translate(-1 * x, -1 * y); } this.drawScene(canvas); context.restore(); return canvas.toDataURL(mimeType, quality); }, /** * converts node into an image. Since the toImage * method is asynchronous, a callback is required. toImage is most commonly used * to cache complex drawings as an image so that they don't have to constantly be redrawn * @method * @memberof Kinetic.Node.prototype * @param {Object} config * @param {Function} config.callback function executed when the composite has completed * @param {String} [config.mimeType] can be "image/png" or "image/jpeg". * "image/png" is the default * @param {Number} [config.x] x position of canvas section * @param {Number} [config.y] y position of canvas section * @param {Number} [config.width] width of canvas section * @param {Number} [config.height] height of canvas section * @param {Number} [config.quality] jpeg quality. If using an "image/jpeg" mimeType, * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 * is very high quality * @example * var image = node.toImage({<br> * callback: function(img) {<br> * // do stuff with img<br> * }<br> * }); */ toImage: function(config) { Kinetic.Util._getImage(this.toDataURL(config), function(img) { config.callback(img); }); }, setSize: function(size) { this.setWidth(size.width); this.setHeight(size.height); return this; }, getSize: function() { return { width: this.getWidth(), height: this.getHeight() }; }, /** * get width * @method * @memberof Kinetic.Node.prototype * @returns {Integer} */ getWidth: function() { return this.attrs.width || 0; }, /** * get height * @method * @memberof Kinetic.Node.prototype * @returns {Integer} */ getHeight: function() { return this.attrs.height || 0; }, /** * get class name, which may return Stage, Layer, Group, or shape class names like Rect, Circle, Text, etc. * @method * @memberof Kinetic.Node.prototype * @returns {String} */ getClassName: function() { return this.className || this.nodeType; }, /** * get the node type, which may return Stage, Layer, Group, or Node * @method * @memberof Kinetic.Node.prototype * @returns {String} */ getType: function() { return this.nodeType; }, getDragDistance: function() { // compare with undefined because we need to track 0 value if (this.attrs.dragDistance !== undefined) { return this.attrs.dragDistance; } else if (this.parent) { return this.parent.getDragDistance(); } else { return Kinetic.dragDistance; } }, _get: function(selector) { return this.nodeType === selector ? [this] : []; }, _off: function(type, name) { var evtListeners = this.eventListeners[type], i, evtName; for(i = 0; i < evtListeners.length; i++) { evtName = evtListeners[i].name; // the following two conditions must be true in order to remove a handler: // 1) the current event name cannot be kinetic unless the event name is kinetic // this enables developers to force remove a kinetic specific listener for whatever reason // 2) an event name is not specified, or if one is specified, it matches the current event name if((evtName !== 'kinetic' || name === 'kinetic') && (!name || evtName === name)) { evtListeners.splice(i, 1); if(evtListeners.length === 0) { delete this.eventListeners[type]; break; } i--; } } }, _fireChangeEvent: function(attr, oldVal, newVal) { this._fire(attr + CHANGE, { oldVal: oldVal, newVal: newVal }); }, /** * set id * @method * @memberof Kinetic.Node.prototype * @param {String} id * @returns {Kinetic.Node} */ setId: function(id) { var oldId = this.getId(); Kinetic._removeId(oldId); Kinetic._addId(this, id); this._setAttr(ID, id); return this; }, setName: function(name) { var oldName = this.getName(); Kinetic._removeName(oldName, this._id); Kinetic._addName(this, name); this._setAttr(NAME, name); return this; }, /** * set attr * @method * @memberof Kinetic.Node.prototype * @param {String} attr * @param {*} val * @returns {Kinetic.Node} * @example * node.setAttr('x', 5); */ setAttr: function() { var args = Array.prototype.slice.call(arguments), attr = args[0], val = args[1], method = SET + Kinetic.Util._capitalize(attr), func = this[method]; if(Kinetic.Util._isFunction(func)) { func.call(this, val); } // otherwise set directly else { this._setAttr(attr, val); } return this; }, _setAttr: function(key, val) { var oldVal; if(val !== undefined) { oldVal = this.attrs[key]; this.attrs[key] = val; this._fireChangeEvent(key, oldVal, val); } }, _setComponentAttr: function(key, component, val) { var oldVal; if(val !== undefined) { oldVal = this.attrs[key]; if (!oldVal) { // set value to default value using getAttr this.attrs[key] = this.getAttr(key); } this.attrs[key][component] = val; this._fireChangeEvent(key, oldVal, val); } }, _fireAndBubble: function(eventType, evt, compareShape) { var okayToRun = true; if(evt && this.nodeType === SHAPE) { evt.target = this; } if(eventType === MOUSEENTER && compareShape && this._id === compareShape._id) { okayToRun = false; } else if(eventType === MOUSELEAVE && compareShape && this._id === compareShape._id) { okayToRun = false; } if(okayToRun) { this._fire(eventType, evt); // simulate event bubbling if(evt && !evt.cancelBubble && this.parent) { if(compareShape && compareShape.parent) { this._fireAndBubble.call(this.parent, eventType, evt, compareShape.parent); } else { this._fireAndBubble.call(this.parent, eventType, evt); } } } }, _fire: function(eventType, evt) { var events = this.eventListeners[eventType], i; evt.type = eventType; if (events) { for(i = 0; i < events.length; i++) { events[i].handler.call(this, evt); } } }, /** * draw both scene and hit graphs. If the node being drawn is the stage, all of the layers will be cleared and redrawn * @method * @memberof Kinetic.Node.prototype * @returns {Kinetic.Node} */ draw: function() { this.drawScene(); this.drawHit(); return this; } }); /** * create node with JSON string. De-serializtion does not generate custom * shape drawing functions, images, or event handlers (this would make the * serialized object huge). If your app uses custom shapes, images, and * event handlers (it probably does), then you need to select the appropriate * shapes after loading the stage and set these properties via on(), setDrawFunc(), * and setImage() methods * @method * @memberof Kinetic.Node * @param {String} JSON string * @param {DomElement} [container] optional container dom element used only if you're * creating a stage node */ Kinetic.Node.create = function(json, container) { return this._createNode(JSON.parse(json), container); }; Kinetic.Node._createNode = function(obj, container) { var className = Kinetic.Node.prototype.getClassName.call(obj), children = obj.children, no, len, n; // if container was passed in, add it to attrs if(container) { obj.attrs.container = container; } no = new Kinetic[className](obj.attrs); if(children) { len = children.length; for(n = 0; n < len; n++) { no.add(this._createNode(children[n])); } } return no; }; // =========================== add getters setters =========================== Kinetic.Factory.addOverloadedGetterSetter(Kinetic.Node, 'position'); /** * get/set node position relative to parent * @name position * @method * @memberof Kinetic.Node.prototype * @param {Object} pos * @param {Number} pos.x * @param {Nubmer} pos.y * @returns {Object} * @example * // get position<br> * var position = node.position();<br><br> * * // set position<br> * node.position({<br> * x: 5<br> * y: 10<br> * }); */ Kinetic.Factory.addGetterSetter(Kinetic.Node, 'x', 0); /** * get/set x position * @name x * @method * @memberof Kinetic.Node.prototype * @param {Number} x * @returns {Object} * @example * // get x<br> * var x = node.x();<br><br> * * // set x<br> * node.x(5); */ Kinetic.Factory.addGetterSetter(Kinetic.Node, 'y', 0); /** * get/set y position * @name y * @method * @memberof Kinetic.Node.prototype * @param {Number} y * @returns {Integer} * @example * // get y<br> * var y = node.y();<br><br> * * // set y<br> * node.y(5); */ Kinetic.Factory.addGetterSetter(Kinetic.Node, 'opacity', 1); /** * get/set opacity. Opacity values range from 0 to 1. * A node with an opacity of 0 is fully transparent, and a node * with an opacity of 1 is fully opaque * @name opacity * @method * @memberof Kinetic.Node.prototype * @param {Object} opacity * @returns {Number} * @example * // get opacity<br> * var opacity = node.opacity();<br><br> * * // set opacity<br> * node.opacity(0.5); */ Kinetic.Factory.addGetter(Kinetic.Node, 'name'); Kinetic.Factory.addOverloadedGetterSetter(Kinetic.Node, 'name'); /** * get/set name * @name name * @method * @memberof Kinetic.Node.prototype * @param {String} name * @returns {String} * @example * // get name<br> * var name = node.name();<br><br> * * // set name<br> * node.name('foo'); */ Kinetic.Factory.addGetter(Kinetic.Node, 'id'); Kinetic.Factory.addOverloadedGetterSetter(Kinetic.Node, 'id'); /** * get/set id * @name id * @method * @memberof Kinetic.Node.prototype * @param {String} id * @returns {String} * @example * // get id<br> * var name = node.id();<br><br> * * // set id<br> * node.id('foo'); */ Kinetic.Factory.addGetterSetter(Kinetic.Node, 'rotation', 0); /** * get/set rotation in degrees * @name rotation * @method * @memberof Kinetic.Node.prototype * @param {Number} rotation * @returns {Number} * @example * // get rotation in degrees<br> * var rotation = node.rotation();<br><br> * * // set rotation in degrees<br> * node.rotation(45); */ Kinetic.Factory.addComponentsGetterSetter(Kinetic.Node, 'scale', ['x', 'y']); /** * get/set scale * @name scale * @param {Object} scale * @param {Number} scale.x * @param {Number} scale.y * @method * @memberof Kinetic.Node.prototype * @returns {Object} * @example * // get scale<br> * var scale = node.scale();<br><br> * * // set scale <br> * shape.scale({<br> * x: 2<br> * y: 3<br> * }); */ Kinetic.Factory.addGetterSetter(Kinetic.Node, 'scaleX', 1); /** * get/set scale x * @name scaleX * @param {Number} x * @method * @memberof Kinetic.Node.prototype * @returns {Number} * @example * // get scale x<br> * var scaleX = node.scaleX();<br><br> * * // set scale x<br> * node.scaleX(2); */ Kinetic.Factory.addGetterSetter(Kinetic.Node, 'scaleY', 1); /** * get/set scale y * @name scaleY * @param {Number} y * @method * @memberof Kinetic.Node.prototype * @returns {Number} * @example * // get scale y<br> * var scaleY = node.scaleY();<br><br> * * // set scale y<br> * node.scaleY(2); */ Kinetic.Factory.addComponentsGetterSetter(Kinetic.Node, 'skew', ['x', 'y']); /** * get/set skew * @name skew * @param {Object} skew * @param {Number} skew.x * @param {Number} skew.y * @method * @memberof Kinetic.Node.prototype * @returns {Object} * @example * // get skew<br> * var skew = node.skew();<br><br> * * // set skew <br> * node.skew({<br> * x: 20<br> * y: 10 * }); */ Kinetic.Factory.addGetterSetter(Kinetic.Node, 'skewX', 0); /** * get/set skew x * @name skewX * @param {Number} x * @method * @memberof Kinetic.Node.prototype * @returns {Number} * @example * // get skew x<br> * var skewX = node.skewX();<br><br> * * // set skew x<br> * node.skewX(3); */ Kinetic.Factory.addGetterSetter(Kinetic.Node, 'skewY', 0); /** * get/set skew y * @name skewY * @param {Number} y * @method * @memberof Kinetic.Node.prototype * @returns {Number} * @example * // get skew y<br> * var skewY = node.skewY();<br><br> * * // set skew y<br> * node.skewY(3); */ Kinetic.Factory.addComponentsGetterSetter(Kinetic.Node, 'offset', ['x', 'y']); /** * get/set offset. Offsets the default position and rotation point * @method * @memberof Kinetic.Node.prototype * @param {Object} offset * @param {Number} offset.x * @param {Number} offset.y * @returns {Object} * @example * // get offset<br> * var offset = node.offset();<br><br> * * // set offset<br> * node.offset({<br> * x: 20<br> * y: 10<br> * }); */ Kinetic.Factory.addGetterSetter(Kinetic.Node, 'offsetX', 0); /** * get/set offset x * @name offsetX * @memberof Kinetic.Node.prototype * @param {Number} x * @returns {Number} * @example * // get offset x<br> * var offsetX = node.offsetX();<br><br> * * // set offset x<br> * node.offsetX(3); */ Kinetic.Factory.addGetterSetter(Kinetic.Node, 'offsetY', 0); /** * get/set drag distance * @name dragDistance * @memberof Kinetic.Node.prototype * @param {Number} distance * @returns {Number} * @example * // get drag distance<br> * var dragDistance = node.dragDistance();<br><br> * * // set distance<br> * // node starts dragging only if pointer moved more then 3 pixels<br> * node.dragDistance(3);<br> * // or set globally<br> * Kinetic.dragDistance = 3; */ Kinetic.Factory.addSetter(Kinetic.Node, 'dragDistance'); Kinetic.Factory.addOverloadedGetterSetter(Kinetic.Node, 'dragDistance'); /** * get/set offset y * @name offsetY * @method * @memberof Kinetic.Node.prototype * @param {Number} y * @returns {Number} * @example * // get offset y<br> * var offsetY = node.offsetY();<br><br> * * // set offset y<br> * node.offsetY(3); */ Kinetic.Factory.addSetter(Kinetic.Node, 'width', 0); Kinetic.Factory.addOverloadedGetterSetter(Kinetic.Node, 'width'); /** * get/set width * @name width * @method * @memberof Kinetic.Node.prototype * @param {Number} width * @returns {Number} * @example * // get width<br> * var width = node.width();<br><br> * * // set width<br> * node.width(100); */ Kinetic.Factory.addSetter(Kinetic.Node, 'height', 0); Kinetic.Factory.addOverloadedGetterSetter(Kinetic.Node, 'height'); /** * get/set height * @name height * @method * @memberof Kinetic.Node.prototype * @param {Number} height * @returns {Number} * @example * // get height<br> * var height = node.height();<br><br> * * // set height<br> * node.height(100); */ Kinetic.Factory.addGetterSetter(Kinetic.Node, 'listening', 'inherit'); /** * get/set listenig attr. If you need to determine if a node is listening or not * by taking into account its parents, use the isListening() method * @name listening * @method * @memberof Kinetic.Node.prototype * @param {Boolean|String} listening Can be "inherit", true, or false. The default is "inherit". * @returns {Boolean|String} * @example * // get listening attr<br> * var listening = node.listening();<br><br> * * // stop listening for events<br> * node.listening(false);<br><br> * * // listen for events<br> * node.listening(true);<br><br> * * // listen to events according to the parent<br> * node.listening('inherit'); */ Kinetic.Factory.addGetterSetter(Kinetic.Node, 'filters', undefined, function(val) {this._filterUpToDate = false;return val;}); /** * get/set filters. Filters are applied to cached canvases * @name filters * @method * @memberof Kinetic.Node.prototype * @param {Array} filters array of filters * @returns {Array} * @example * // get filters<br> * var filters = node.filters();<br><br> * * // set a single filter<br> * node.cache();<br> * node.filters([Kinetic.Filters.Blur]);<br><br> * * // set multiple filters<br> * node.cache();<br> * node.filters([<br> * Kinetic.Filters.Blur,<br> * Kinetic.Filters.Sepia,<br> * Kinetic.Filters.Invert<br> * ]); */ Kinetic.Factory.addGetterSetter(Kinetic.Node, 'visible', 'inherit'); /** * get/set visible attr. Can be "inherit", true, or false. The default is "inherit". * If you need to determine if a node is visible or not * by taking into account its parents, use the isVisible() method * @name visible * @method * @memberof Kinetic.Node.prototype * @param {Boolean|String} visible * @returns {Boolean|String} * @example * // get visible attr<br> * var visible = node.visible();<br><br> * * // make invisible<br> * node.visible(false);<br><br> * * // make visible<br> * node.visible(true);<br><br> * * // make visible according to the parent<br> * node.visible('inherit'); */ Kinetic.Factory.addGetterSetter(Kinetic.Node, 'transformsEnabled', 'all'); /** * get/set transforms that are enabled. Can be "all", "none", or "position". The default * is "all" * @name transformsEnabled * @method * @memberof Kinetic.Node.prototype * @param {String} enabled * @returns {String} * @example * // enable position transform only to improve draw performance<br> * node.transformsEnabled('position');<br><br> * * // enable all transforms<br> * node.transformsEnabled('all'); */ /** * get/set node size * @name size * @method * @memberof Kinetic.Node.prototype * @param {Object} size * @param {Number} size.width * @param {Number} size.height * @returns {Object} * @example * // get node size<br> * var size = node.size();<br> * var x = size.x;<br> * var y = size.y;<br><br> * * // set size<br> * node.size({<br> * width: 100,<br> * height: 200<br> * }); */ Kinetic.Factory.addOverloadedGetterSetter(Kinetic.Node, 'size'); Kinetic.Factory.backCompat(Kinetic.Node, { rotateDeg: 'rotate', setRotationDeg: 'setRotation', getRotationDeg: 'getRotation' }); Kinetic.Collection.mapMethods(Kinetic.Node); })();