(function() { // CONSTANTS var ABSOLUTE_OPACITY = 'absoluteOpacity', ABSOLUTE_TRANSFORM = 'absoluteTransform', ADD = 'add', B = 'b', BEFORE = 'before', BLACK = 'black', CHANGE = 'Change', CHILDREN = 'children', DEG = 'Deg', DOT = '.', EMPTY_STRING = '', G = 'g', GET = 'get', HASH = '#', ID = 'id', KINETIC = 'kinetic', LISTENING = 'listening', MOUSEENTER = 'mouseenter', MOUSELEAVE = 'mouseleave', NAME = 'name', OFF = 'off', ON = 'on', PRIVATE_GET = '_get', R = 'r', RGB = 'RGB', SET = 'set', SHAPE = 'Shape', SPACE = ' ', STAGE = 'stage', TRANSFORM = 'transform', UPPER_B = 'B', UPPER_G = 'G', UPPER_HEIGHT = 'Height', UPPER_R = 'R', UPPER_STAGE = 'Stage', UPPER_WIDTH = 'Width', UPPER_X = 'X', UPPER_Y = 'Y', VISIBLE = 'visible', X = 'x', Y = 'y', transformChangeStr = [ 'xChange.kinetic', 'yChange.kinetic', 'scaleXChange.kinetic', 'scaleYChange.kinetic', 'skewXChange.kinetic', 'skewYChange.kinetic', 'rotationChange.kinetic', 'offsetXChange.kinetic', 'offsetYChange.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.setAttrs(config); // event bindings for cache handling this.on(transformChangeStr, function() { this._clearCache(TRANSFORM); that._clearSelfAndChildrenCache(ABSOLUTE_TRANSFORM); }); this.on('visibleChange.kinetic', function() { that._clearSelfAndChildrenCache(VISIBLE); }); this.on('listeningChange.kinetic', function() { that._clearSelfAndChildrenCache(LISTENING); }); this.on('opacityChange.kinetic', function() { that._clearSelfAndChildrenCache(ABSOLUTE_OPACITY); }); }, /** * clear cached variables * @method * @memberof Kinetic.Node.prototype * @example * node.clearCache(); */ clearCache: function() { this.cache = {}; }, _clearCache: function(attr){ delete this.cache[attr]; }, _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]; }, _clearSelfAndChildrenCache: function(attr) { var that = this; this._clearCache(attr); if (this.children) { this.getChildren().each(function(node) { node._clearSelfAndChildrenCache(attr); }); } }, /** * 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 * @example * // add click listener
* node.on('click', function() {
* console.log('you clicked me!');
* });

* * // get the target node
* node.on('click', function(evt) {
* console.log(evt.targetNode);
* });

* * // stop event propagation
* node.on('click', function(evt) {
* evt.cancelBubble = true;
* });

* * // bind multiple listeners
* node.on('click touchstart', function() {
* console.log('you clicked/touched me!');
* });

* * // namespace listener
* node.on('click.foo', function() {
* console.log('you clicked/touched me!');
* }); */ 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 }); } 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' * @example * // remove listener
* node.off('click');

* * // remove multiple listeners
* node.off('click touchstart');

* * // remove listener by name
* node.off('click.foo'); */ off: function(evtStr) { var events = evtStr.split(SPACE), len = events.length, n, i, 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; }, /** * remove self from parent, but don't destroy * @method * @memberof Kinetic.Node.prototype * @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 // treversal must be cleared when removing a node this._clearSelfAndChildrenCache(STAGE); this._clearSelfAndChildrenCache(ABSOLUTE_TRANSFORM); this._clearSelfAndChildrenCache(VISIBLE); this._clearSelfAndChildrenCache(LISTENING); this._clearSelfAndChildrenCache(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 * @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 * @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; }, /** * set attr * @method * @memberof Kinetic.Node.prototype * @param {String} attr * #param {*} val * @example * node.setAttr('x', 5); */ setAttr: function() { var args = Array.prototype.slice.call(arguments), attr = args[0], method = SET + Kinetic.Util._capitalize(attr), func = this[method]; args.shift(); if(Kinetic.Util._isFunction(func)) { func.apply(this, args); } // otherwise set directly else { this.attrs[attr] = args[0]; } return this; }, /** * get attrs object literal * @method * @memberof Kinetic.Node.prototype */ 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 * @example * node.setAttrs({
* x: 5,
* fill: 'red'
* });
*/ 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. The node is listening only * if it's listening and all of its ancestors are listening. If an ancestor * is listening, this means that the node is also listening * @method * @memberof Kinetic.Node.prototype */ isListening: function() { return this._getCache(LISTENING, this._isListening); }, _isListening: function() { var listening = this.getListening(), parent = this.getParent(); if(listening && parent && !parent.isListening()) { return false; } return listening; }, /** * determine if node is visible or not. Node is visible only * if it's visible and all of its ancestors are visible. If an ancestor * is invisible, this means that the node is also invisible * @method * @memberof Kinetic.Node.prototype */ isVisible: function() { return this._getCache(VISIBLE, this._isVisible); }, _isVisible: function() { var visible = this.getVisible(), parent = this.getParent(); if(visible && parent && !parent.isVisible()) { return false; } return visible; }, /** * show node * @method * @memberof Kinetic.Node.prototype */ show: function() { this.setVisible(true); return this; }, /** * hide node. Hidden nodes are no longer detectable * @method * @memberof Kinetic.Node.prototype */ 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 */ getZIndex: function() { return this.index || 0; }, /** * get absolute z-index which takes into account sibling * and ancestor indices * @method * @memberof Kinetic.Node.prototype */ getAbsoluteZIndex: function() { var level = this.getLevel(), 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].getLevel() <= level) { addChildren(nodes); } } if(that.nodeType !== UPPER_STAGE) { addChildren(that.getStage().getChildren()); } return index; }, /** * get node level in node tree. Returns an integer.

* e.g. Stage level will always be 0. Layers will always be 1. Groups and Shapes will always * be >= 2 * @method * @memberof Kinetic.Node.prototype */ getLevel: function() { var level = 0, parent = this.parent; while(parent) { level++; parent = parent.parent; } return level; }, /** * set node position relative to parent * @method * @memberof Kinetic.Node.prototype * @param {Object} pos * @param {Number} pos.x * @param {Nubmer} pos.y * @example * // set x and
* node.setPosition({
* x: 5
* y: 10 * }); */ setPosition: function(pos) { this.setX(pos.x); this.setY(pos.y); return this; }, /** * get node position relative to parent * @method * @memberof Kinetic.Node.prototype */ 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 */ getAbsolutePosition: function() { var absoluteMatrix = this.getAbsoluteTransform().getMatrix(), absoluteTransform = new Kinetic.Transform(), o = this.getOffset(); // clone the matrix array absoluteTransform.m = absoluteMatrix.slice(); absoluteTransform.translate(o.x, o.y); return absoluteTransform.getTranslation(); }, /** * set absolute position * @method * @memberof Kinetic.Node.prototype * @param {Object} pos * @param {Number} pos.x * @param {Number} pos.y */ setAbsolutePosition: function(pos) { var trans = this._clearTransform(), it; // don't clear translation this.attrs.x = trans.x; this.attrs.y = trans.y; delete trans.x; delete trans.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(pos.x, pos.y); this._setTransform(trans); return this; }, _setTransform: function(trans) { var key; for(key in trans) { this.attrs[key] = trans[key]; } this._clearCache(TRANSFORM); this._clearSelfAndChildrenCache(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._clearSelfAndChildrenCache(ABSOLUTE_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 * @example * // move node in x direction by 1px and y direction by 2px
* node.move({
* x: 1,
* y: 2)
* }); */ 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, y); return this; }, _eachAncestorReverse: function(func, includeSelf) { var family = [], parent = this.getParent(), len, n; // build family by traversing ancestors if(includeSelf) { family.unshift(this); } while(parent) { family.unshift(parent); parent = parent.parent; } len = family.length; for(n = 0; n < len; n++) { func(family[n]); } }, /** * rotate node by an amount in radians relative to its current rotation * @method * @memberof Kinetic.Node.prototype * @param {Number} theta */ rotate: function(theta) { this.setRotation(this.getRotation() + theta); return this; }, /** * rotate node by an amount in degrees relative to its current rotation * @method * @memberof Kinetic.Node.prototype * @param {Number} deg */ rotateDeg: function(deg) { this.setRotation(this.getRotation() + Kinetic.Util._degToRad(deg)); return this; }, /** * move node to the top of its siblings * @method * @memberof Kinetic.Node.prototype */ moveToTop: function() { 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 */ moveUp: function() { 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 */ moveDown: function() { 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 */ moveToBottom: function() { 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 */ setZIndex: function(zIndex) { 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 */ 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 * @example * // move node from current layer into layer2
* 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 */ 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[GET + Kinetic.Util._capitalize(key)]; defaultValue = getter ? getter.call({attrs: {}}) : null; 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 */ toJSON: function() { return JSON.stringify(this.toObject()); }, /** * get parent container * @method * @memberof Kinetic.Node.prototype */ getParent: function() { return this.parent; }, /** * get layer ancestor * @method * @memberof Kinetic.Node.prototype */ getLayer: function() { var parent = this.getParent(); return parent ? parent.getLayer() : null; }, /** * get stage ancestor * @method * @memberof Kinetic.Node.prototype */ 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. * @example * // manually fire click event
* node.fire('click');

* * // fire custom event
* node.fire('foo');

* * // fire custom event with custom event object
* node.fire('foo', {
* bar: 10
* });

* * // fire click event that bubbles
* 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 */ getAbsoluteTransform: function() { return this._getCache(ABSOLUTE_TRANSFORM, this._getAbsoluteTransform); }, _getAbsoluteTransform: function() { // absolute transform var am = new Kinetic.Transform(), m; this._eachAncestorReverse(function(node) { m = node.getTransform(); am.multiply(m); }, true); return am; }, _getTransform: function() { var m = new Kinetic.Transform(), x = this.getX(), y = this.getY(), rotation = 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 * @example * // simple clone
* var clone = node.clone();

* * // clone a node and override the x position
* var clone = rect.clone({
* x: 5
* }); */ clone: function(obj) { // instantiate new node var className = this.getClassName(), node = new Kinetic[className](this.attrs), key, allListeners, len, n, listener; // 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); } } } // apply attr overrides node.setAttrs(obj); 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 {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 */ 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({
* callback: function(img) {
* // do stuff with img
* }
* }); */ toImage: function(config) { Kinetic.Util._getImage(this.toDataURL(config), function(img) { config.callback(img); }); }, /** * set size * @method * @memberof Kinetic.Node.prototype * @param {Object} size * @param {Number} width * @param {Number} height */ setSize: function(size) { this.setWidth(size.width); this.setHeight(size.height); return this; }, /** * get size * @method * @memberof Kinetic.Node.prototype */ getSize: function() { return { width: this.getWidth(), height: this.getHeight() }; }, /** * get width * @method * @memberof Kinetic.Node.prototype */ getWidth: function() { return this.attrs.width || 0; }, /** * get height * @method * @memberof Kinetic.Node.prototype */ 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 */ getClassName: function() { return this.className || this.nodeType; }, /** * get the node type, which may return Stage, Layer, Group, or Node * @method * @memberof Kinetic.Node.prototype */ getType: function() { return this.nodeType; }, _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--; } } }, _fireBeforeChangeEvent: function(attr, oldVal, newVal) { this._fire(BEFORE + Kinetic.Util._capitalize(attr) + CHANGE, { oldVal: oldVal, newVal: newVal }); }, _fireChangeEvent: function(attr, oldVal, newVal) { this._fire(attr + CHANGE, { oldVal: oldVal, newVal: newVal }); }, /** * set id * @method * @memberof Kinetic.Node.prototype * @param {String} id */ setId: function(id) { var oldId = this.getId(); Kinetic._removeId(oldId); Kinetic._addId(this, id); this._setAttr(ID, id); return this; }, /** * set name * @method * @memberof Kinetic.Node.prototype * @param {String} name */ setName: function(name) { var oldName = this.getName(); Kinetic._removeName(oldName, this._id); Kinetic._addName(this, name); this._setAttr(NAME, name); return this; }, _setAttr: function(key, val) { var oldVal; if(val !== undefined) { oldVal = this.attrs[key]; this._fireBeforeChangeEvent(key, oldVal, val); 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) { this.attrs[key] = []; } this._fireBeforeChangeEvent(key, oldVal, val); this.attrs[key][component] = val; this._fireChangeEvent(key, oldVal, val); } }, _fireAndBubble: function(eventType, evt, compareShape) { var okayToRun = true; if(evt && this.nodeType === SHAPE) { evt.targetNode = 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; 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 redra * @method * @memberof Kinetic.Node.prototype * the scene renderer */ draw: function() { this.drawScene(); this.drawHit(); return this; }, shouldDrawHit: function() { return this.isListening() && this.isVisible() && !Kinetic.isDragging(); }, isDraggable: function() { return false; }, /** * get transform of the node * @method * @memberof Kinetic.Node.prototype */ getTransform: function() { //return this._getTransform(); return this._getCache(TRANSFORM, this._getTransform); } }); /** * 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.addGetterSetter(Kinetic.Node, 'x', 0); /** * set x position * @name setX * @method * @memberof Kinetic.Node.prototype * @param {Number} x */ /** * get x position * @name getX * @method * @memberof Kinetic.Node.prototype */ Kinetic.Factory.addGetterSetter(Kinetic.Node, 'y', 0); /** * set y position * @name setY * @method * @memberof Kinetic.Node.prototype * @param {Number} y */ /** * get y position * @name getY * @method * @memberof Kinetic.Node.prototype */ Kinetic.Factory.addGetterSetter(Kinetic.Node, 'opacity', 1); /** * 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 setOpacity * @method * @memberof Kinetic.Node.prototype * @param {Object} opacity */ /** * get opacity. * @name getOpacity * @method * @memberof Kinetic.Node.prototype */ Kinetic.Factory.addGetter(Kinetic.Node, 'name'); /** * get name * @name getName * @method * @memberof Kinetic.Node.prototype */ Kinetic.Factory.addGetter(Kinetic.Node, 'id'); /** * get id * @name getId * @method * @memberof Kinetic.Node.prototype */ Kinetic.Factory.addRotationGetterSetter(Kinetic.Node, 'rotation', 0); /** * set rotation in radians * @name setRotation * @method * @memberof Kinetic.Node.prototype * @param {Number} theta */ /** * set rotation in degrees * @name setRotationDeg * @method * @memberof Kinetic.Node.prototype * @param {Number} deg */ /** * get rotation in degrees * @name getRotationDeg * @method * @memberof Kinetic.Node.prototype */ /** * get rotation in radians * @name getRotation * @method * @memberof Kinetic.Node.prototype */ Kinetic.Factory.addGetterSetter(Kinetic.Node, 'scale', {x: 1, y: 1}); /** * set scale * @name setScale * @param {Object} scale * @param {Number} scale.x * @param {Number} scale.y * @method * @memberof Kinetic.Node.prototype * @example * // set x and y
* shape.setScale({
* x: 20
* y: 10
* }); */ /** * get scale * @name getScale * @method * @memberof Kinetic.Node.prototype */ Kinetic.Factory.addComponentGetterSetter(Kinetic.Node, 'scale', 'x', 1); /** * set scale x * @name setScaleX * @param {Number} x * @method * @memberof Kinetic.Node.prototype */ /** * get scale x * @name getScaleX * @method * @memberof Kinetic.Node.prototype */ Kinetic.Factory.addComponentGetterSetter(Kinetic.Node, 'scale', 'y', 1); /** * set scale y * @name setScaleY * @param {Number} y * @method * @memberof Kinetic.Node.prototype */ /** * get scale y * @name getScaleY * @method * @memberof Kinetic.Node.prototype */ Kinetic.Factory.addPointGetterSetter(Kinetic.Node, 'skew', 0); /** * set skew * @name setSkew * @param {Number} x * @param {Number} y * @method * @memberof Kinetic.Node.prototype * @example * // set x and y
* shape.setSkew(20, 40);

* * // set x only
* shape.setSkew({
* x: 20
* });

* * // set x and y using an array
* shape.setSkew([20, 40]);

* * // set x and y to the same value
* shape.setSkew(5); */ /** * set skew x * @name setSkewX * @param {Number} x * @method * @memberof Kinetic.Node.prototype */ /** * set skew y * @name setSkewY * @param {Number} y * @method * @memberof Kinetic.Node.prototype */ /** * get skew * @name getSkew * @method * @memberof Kinetic.Node.prototype */ /** * get skew x * @name getSkewX * @method * @memberof Kinetic.Node.prototype */ /** * get skew y * @name getSkewY * @method * @memberof Kinetic.Node.prototype */ Kinetic.Factory.addGetterSetter(Kinetic.Node, 'offset', {x: 0, y: 0}); /** * set offset. A node's offset defines the position and rotation point * @name setOffset * @method * @memberof Kinetic.Node.prototype * @param {Number} x * @param {Number} y * @example * // set x and y
* shape.setOffset({
* x: 20
* y: 10
* });

*/ /** * get offset * @name getOffset * @method * @memberof Kinetic.Node.prototype * @returns {Object} */ Kinetic.Factory.addComponentGetterSetter(Kinetic.Node, 'offset', 'x', 0); /** * set offset x * @name setOffsetX * @method * @memberof Kinetic.Node.prototype * @param {Number} x */ /** * get offset x * @name getOffsetX * @method * @memberof Kinetic.Node.prototype * @returns {Number} */ Kinetic.Factory.addComponentGetterSetter(Kinetic.Node, 'offset', 'y', 0); /** * set offset y * @name setOffsetY * @method * @memberof Kinetic.Node.prototype * @param {Number} y */ /** * get offset y * @name getOffsetY * @method * @memberof Kinetic.Node.prototype * @returns {Number} */ Kinetic.Factory.addSetter(Kinetic.Node, 'width'); /** * set width * @name setWidth * @method * @memberof Kinetic.Node.prototype * @param {Number} width */ Kinetic.Factory.addSetter(Kinetic.Node, 'height'); /** * set height * @name setHeight * @method * @memberof Kinetic.Node.prototype * @param {Number} height */ Kinetic.Factory.addGetterSetter(Kinetic.Node, 'listening', true); /** * listen or don't listen to events * @name setListening * @method * @memberof Kinetic.Node.prototype * @param {Boolean} listening */ /** * determine if node is listening or not. Node can be listening even if its ancestors * are not listening * @method * @memberof Kinetic.Node.prototype */ Kinetic.Factory.addGetterSetter(Kinetic.Node, 'visible', true); /** * set visible * @name setVisible * @method * @memberof Kinetic.Node.prototype * @param {Boolean} visible */ /** * get visible property for the node. If you need to determine if the node is actually visible, * use the isVisible() method because it takes ancestors into account * @name getVisible * @method * @memberof Kinetic.Node.prototype */ Kinetic.Collection.mapMethods([ 'on', 'off', 'remove', 'destroy', 'show', 'hide', 'move', 'rotate', 'moveToTop', 'moveUp', 'moveDown', 'moveToBottom', 'moveTo', 'fire', 'draw' ]); })();