diff --git a/src/Container.js b/src/Container.js index 0abc69ab..6e7bb039 100644 --- a/src/Container.js +++ b/src/Container.js @@ -40,8 +40,6 @@ */ add: function(child) { var go = Kinetic.Global, children = this.children; - - child._id = Kinetic.Global.idCounter++; child.index = children.length; child.parent = this; children.push(child); diff --git a/src/Layer.js b/src/Layer.js index f19b710a..fa179c20 100644 --- a/src/Layer.js +++ b/src/Layer.js @@ -7,7 +7,7 @@ * @param {Object} config * @param {Boolean} [config.clearBeforeDraw] set this property to false if you don't want * to clear the canvas before each layer draw. The default value is true. - * + * * @param {Number} [config.x] * @param {Number} [config.y] * @param {Number} [config.width] @@ -101,7 +101,7 @@ } // otherwise get data url of the currently drawn layer else { - return this.getCanvas().toDataURL(mimeType, quality); + return this.getCanvas().toDataURL(mimeType, quality); } }, /** @@ -222,17 +222,11 @@ * remove layer from stage */ remove: function() { - var stage = this.getStage(); + var stage = this.getStage(), canvas = this.canvas, element = canvas.element; Kinetic.Node.prototype.remove.call(this); - /* - * remove canvas DOM from the document if - * it exists - */ - try { - stage.content.removeChild(this.canvas.element); - } - catch(e) { - Kinetic.Global.warn('unable to remove layer scene canvas element from the document'); + + if(stage && canvas && Kinetic.Type._isInDocument(element)) { + stage.content.removeChild(element); } } }; diff --git a/src/Node.js b/src/Node.js index e09e885a..d6ce32f1 100644 --- a/src/Node.js +++ b/src/Node.js @@ -30,6 +30,8 @@ Kinetic.Node.prototype = { _nodeInit: function(config) { + this._id = Kinetic.Global.idCounter++; + this.defaultNodeAttrs = { visible: true, listening: true, @@ -147,38 +149,44 @@ } }, /** - * remove child from container + * remove child from container, but don't destroy it * @name remove * @methodOf Kinetic.Node.prototype */ remove: function() { var parent = this.getParent(); - if(parent && this.index !== undefined && parent.children[this.index]._id == this._id) { - var stage = parent.getStage(); - /* - * remove event listeners and references to the node - * from the ids and names hashes - */ - if(stage) { - stage._removeId(this.getId()); - stage._removeName(this.getName(), this._id); - } - - Kinetic.Global._removeTempNode(this); + if(parent && parent.children) { parent.children.splice(this.index, 1); parent._setChildrenIndices(); + } + delete this.parent; + }, + /** + * remove and destroy node + * @name destroy + * @methodOf Kinetic.Node.prototype + */ + destroy: function() { + // destroy children + while(this.children && this.children.length > 0) { + this.children[0].destroy(); + } - // remove from DD - var dd = Kinetic.DD; - if(dd && dd.node && dd.node._id === this._id) { - delete Kinetic.DD.node; - } + var parent = this.getParent(), stage = this.getStage(), dd = Kinetic.DD; + this.remove(); - // remove children - while(this.children && this.children.length > 0) { - this.children[0].remove(); - } - delete this.parent; + // remove ids and names hashes + if(stage) { + stage._removeId(this.getId()); + stage._removeName(this.getName(), this._id); + } + + // remove from temp nodes + Kinetic.Global._removeTempNode(this); + + // remove from DD + if(dd && dd.node && dd.node._id === this._id) { + delete Kinetic.DD.node; } }, /** diff --git a/src/Stage.js b/src/Stage.js index 6f5987b8..b0cef568 100644 --- a/src/Stage.js +++ b/src/Stage.js @@ -108,6 +108,17 @@ layers[n].clear(); } }, + /** + * remove stage + */ + remove: function() { + var content = this.content; + Kinetic.Node.prototype.remove.call(this); + + if(content && Kinetic.Type._isInDocument(content)) { + this.attrs.container.removeChild(content); + } + }, /** * reset stage to default state * @name reset @@ -154,12 +165,12 @@ return this; }, /** - * get stage DOM node which is a div element - * with the class name "kineticjs-content" - * @name getDOM + * get stage content div element which has the + * the class name "kineticjs-content" + * @name getContent * @methodOf Kinetic.Stage.prototype */ - getDOM: function() { + getContent: function() { return this.content; }, /** diff --git a/src/util/Type.js b/src/util/Type.js index fe956eee..347d5c9f 100644 --- a/src/util/Type.js +++ b/src/util/Type.js @@ -35,6 +35,14 @@ } return names.length > 0; }, + _isInDocument: function(el) { + while( el = el.parentNode) { + if(el == document) { + return true; + } + } + return false; + }, /* * The argument can be: * - an integer (will be applied to both x and y) diff --git a/tests/js/unit/containerTests.js b/tests/js/unit/containerTests.js index f19e4f1d..6e78fa64 100644 --- a/tests/js/unit/containerTests.js +++ b/tests/js/unit/containerTests.js @@ -90,60 +90,6 @@ Test.Modules.CONTAINER = { test(node.nodeType === 'Layer', 'node type should be Layer'); }, - 'remove shape by id or name': function(containerId) { - var stage = new Kinetic.Stage({ - container: containerId, - width: 578, - height: 200 - }); - var layer = new Kinetic.Layer(); - var circle = new Kinetic.Circle({ - x: stage.getWidth() / 2, - y: stage.getHeight() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - id: 'myCircle' - }); - - var rect = new Kinetic.Rect({ - x: 300, - y: 100, - width: 100, - height: 50, - fill: 'purple', - stroke: 'black', - strokeWidth: 4, - name: 'myRect' - }); - - var circleColorKey = circle.colorKey; - var rectColorKey = rect.colorKey; - - layer.add(circle); - layer.add(rect); - stage.add(layer); - - test(stage.ids.myCircle._id === circle._id, 'circle not in ids hash'); - test(stage.names.myRect[0]._id === rect._id, 'rect not in names hash'); - test(Kinetic.Global.shapes[circleColorKey]._id === circle._id, 'circle color key should be in shapes hash'); - test(Kinetic.Global.shapes[rectColorKey]._id === rect._id, 'rect color key should be in shapes hash'); - - circle.remove(); - - test(stage.ids.myCircle === undefined, 'circle still in hash'); - test(stage.names.myRect[0]._id === rect._id, 'rect not in names hash'); - test(Kinetic.Global.shapes[circleColorKey] === undefined, 'circle color key should not be in shapes hash'); - test(Kinetic.Global.shapes[rectColorKey]._id === rect._id, 'rect color key should be in shapes hash'); - - rect.remove(); - - test(stage.ids.myCircle === undefined, 'circle still in hash'); - test(stage.names.myRect === undefined, 'rect still in hash'); - test(Kinetic.Global.shapes[circleColorKey] === undefined, 'circle color key should not be in shapes hash'); - test(Kinetic.Global.shapes[rectColorKey] === undefined, 'rect color key should not be in shapes hash'); - }, 'set x on an array of nodes': function(containerId) { var stage = new Kinetic.Stage({ container: containerId, diff --git a/tests/js/unit/nodeTests.js b/tests/js/unit/nodeTests.js index 8bad5555..a1a16e98 100644 --- a/tests/js/unit/nodeTests.js +++ b/tests/js/unit/nodeTests.js @@ -421,8 +421,8 @@ Test.Modules.NODE = { rect.setSize(210); rect.setShadowOffset({ - x: 20 - }); + x: 20 + }); test(widthChanged === 1, 'width change event was not fired correctly'); test(shadowChanged === 1, 'shadow change event not fired correctly'); @@ -1098,23 +1098,23 @@ Test.Modules.NODE = { test(rect.getShadowBlur() === 12, 'shadow blur should still be 12'); rect.setShadowOffset({ - x: 3, - y: 4 + x: 3, + y: 4 }); test(rect.getShadowOffset().x === 3, 'shadow offset x should be 3'); test(rect.getShadowOffset().y === 4, 'shadow offset y should be 4'); // test partial setting rect.setShadowOffset({ - x: 5 + x: 5 }); test(rect.getShadowOffset().x === 5, 'shadow offset x should be 5'); test(rect.getShadowOffset().y === 4, 'shadow offset y should be 4'); // test partial setting rect.setShadowOffset({ - y: 6 - }); + y: 6 + }); test(rect.getShadowOffset().x === 5, 'shadow offset x should be 5'); test(rect.getShadowOffset().y === 6, 'shadow offset y should be 6'); @@ -2197,13 +2197,42 @@ Test.Modules.NODE = { circle.remove(); test(layer.children.length === 0, 'layer should have 0 children'); - //test(layer.getChild('myCircle') === undefined, 'shape should be null'); layer.draw(); test(circle.getParent() === undefined, 'circle parent should be undefined'); }, - 'remove shape without adding its parent to stage': function(containerId) { + 'destroy shape': function(containerId) { + var stage = new Kinetic.Stage({ + container: containerId, + width: 578, + height: 200 + }); + var layer = new Kinetic.Layer(); + var circle = new Kinetic.Circle({ + x: stage.getWidth() / 2, + y: stage.getHeight() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle' + }); + + layer.add(circle); + stage.add(layer); + + test(layer.children.length === 1, 'layer should have 1 children'); + + circle.destroy(); + + test(layer.children.length === 0, 'layer should have 0 children'); + + layer.draw(); + + test(circle.getParent() === undefined, 'circle parent should be undefined'); + }, + 'destroy shape without adding its parent to stage': function(containerId) { var stage = new Kinetic.Stage({ container: containerId, width: 578, @@ -2232,12 +2261,12 @@ Test.Modules.NODE = { test(go.tempNodes[circle._id].attrs.id === 'myCircle', 'circle should be in temp nodes'); - circle.remove(); + circle.destroy(); test(go.tempNodes[circle._id] === undefined, 'circle shouldn\'t be in the temp nodes hash'); }, - 'remove layer with shape': function(containerId) { + 'destroy layer with shape': function(containerId) { var stage = new Kinetic.Stage({ container: containerId, width: 578, @@ -2263,7 +2292,7 @@ Test.Modules.NODE = { test(stage.get('.myLayer')[0] !== undefined, 'layer should exist'); test(stage.get('.myCircle')[0] !== undefined, 'circle should exist'); - layer.remove(); + layer.destroy(); test(stage.children.length === 0, 'stage should have 0 children'); test(stage.get('.myLayer')[0] === undefined, 'layer should not exist'); @@ -2271,7 +2300,75 @@ Test.Modules.NODE = { stage.draw(); }, - 'remove layer with no shapes': function(containerId) { + 'destroy stage with layer and shape': function(containerId) { + var stage = new Kinetic.Stage({ + container: containerId, + width: 578, + height: 200 + }); + var layer = new Kinetic.Layer({ + name: 'myLayer' + }); + var circle = new Kinetic.Circle({ + x: stage.getWidth() / 2, + y: stage.getHeight() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle' + }); + + layer.add(circle); + stage.add(layer); + + stage.destroy(); + + test(layer.getParent() === undefined, 'layer parent should be undefined'); + test(circle.getParent() === undefined, 'circle parent should be undefined'); + test(stage.children.length === 0, 'stage children length should be 0'); + test(layer.children.length === 0, 'layer children length should be 0'); + }, + 'destroy group with shape': function(containerId) { + var stage = new Kinetic.Stage({ + container: containerId, + width: 578, + height: 200 + }); + var layer = new Kinetic.Layer({ + name: 'myLayer' + }); + var group = new Kinetic.Group({ + name: 'myGroup' + }); + + var circle = new Kinetic.Circle({ + x: stage.getWidth() / 2, + y: stage.getHeight() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle' + }); + + group.add(circle); + layer.add(group); + stage.add(layer); + + test(layer.getChildren().length === 1, 'layer should have 1 children'); + test(stage.get('.myGroup')[0] !== undefined, 'group should exist'); + test(stage.get('.myCircle')[0] !== undefined, 'circle should exist'); + + group.destroy(); + + test(layer.children.length === 0, 'layer should have 0 children'); + test(stage.get('.myGroup')[0] === undefined, 'group should not exist'); + test(stage.get('.myCircle')[0] === undefined, 'circle should not exist'); + + stage.draw(); + }, + 'destroy layer with no shapes': function(containerId) { var stage = new Kinetic.Stage({ container: containerId, width: 578, @@ -2279,11 +2376,11 @@ Test.Modules.NODE = { }); var layer = new Kinetic.Layer(); stage.add(layer); - layer.remove(); + layer.destroy(); test(stage.children.length === 0, 'stage should have 0 children'); }, - 'remove shape multiple times': function(containerId) { + 'destroy shape multiple times': function(containerId) { var stage = new Kinetic.Stage({ container: containerId, width: 578, @@ -2312,8 +2409,8 @@ Test.Modules.NODE = { test(layer.getChildren().length === 2, 'layer should have two children'); - shape1.remove(); - shape1.remove(); + shape1.destroy(); + shape1.destroy(); test(layer.getChildren().length === 1, 'layer should have two children'); @@ -2359,5 +2456,59 @@ Test.Modules.NODE = { stage.draw(); + }, + 'destroy shape by id or name': function(containerId) { + var stage = new Kinetic.Stage({ + container: containerId, + width: 578, + height: 200 + }); + var layer = new Kinetic.Layer(); + var circle = new Kinetic.Circle({ + x: stage.getWidth() / 2, + y: stage.getHeight() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + id: 'myCircle' + }); + + var rect = new Kinetic.Rect({ + x: 300, + y: 100, + width: 100, + height: 50, + fill: 'purple', + stroke: 'black', + strokeWidth: 4, + name: 'myRect' + }); + + var circleColorKey = circle.colorKey; + var rectColorKey = rect.colorKey; + + layer.add(circle); + layer.add(rect); + stage.add(layer); + + test(stage.ids.myCircle._id === circle._id, 'circle not in ids hash'); + test(stage.names.myRect[0]._id === rect._id, 'rect not in names hash'); + test(Kinetic.Global.shapes[circleColorKey]._id === circle._id, 'circle color key should be in shapes hash'); + test(Kinetic.Global.shapes[rectColorKey]._id === rect._id, 'rect color key should be in shapes hash'); + + circle.destroy(); + + test(stage.ids.myCircle === undefined, 'circle still in hash'); + test(stage.names.myRect[0]._id === rect._id, 'rect not in names hash'); + test(Kinetic.Global.shapes[circleColorKey] === undefined, 'circle color key should not be in shapes hash'); + test(Kinetic.Global.shapes[rectColorKey]._id === rect._id, 'rect color key should be in shapes hash'); + + rect.destroy(); + + test(stage.ids.myCircle === undefined, 'circle still in hash'); + test(stage.names.myRect === undefined, 'rect still in hash'); + test(Kinetic.Global.shapes[circleColorKey] === undefined, 'circle color key should not be in shapes hash'); + test(Kinetic.Global.shapes[rectColorKey] === undefined, 'rect color key should not be in shapes hash'); } }; diff --git a/tests/js/unit/stageTests.js b/tests/js/unit/stageTests.js index 7970bbae..e20421ab 100644 --- a/tests/js/unit/stageTests.js +++ b/tests/js/unit/stageTests.js @@ -62,8 +62,8 @@ Test.Modules.STAGE = { test(stage.getSize().width === 333, 'stage width should be 333'); test(stage.getSize().height === 155, 'stage height should be 155'); - test(stage.getDOM().style.width === '333px', 'content width should be 333'); - test(stage.getDOM().style.height === '155px', 'content height should be 155px'); + test(stage.getContent().style.width === '333px', 'content width should be 333'); + test(stage.getContent().style.height === '155px', 'content height should be 155px'); test(layer.getCanvas().element.width === 333, 'layer canvas element width should be 333'); test(layer.getCanvas().element.height === 155, 'layer canvas element width should be 155'); }, @@ -105,7 +105,7 @@ Test.Modules.STAGE = { height: 200 }); - test(stage.getDOM().className === 'kineticjs-content', 'stage DOM class name is wrong'); + test(stage.getContent().className === 'kineticjs-content', 'stage DOM class name is wrong'); }, 'test getIntersections': function(containerId) { var stage = new Kinetic.Stage({