瀏覽代碼

Implemented Model.extend().

fredck 10 年之前
父節點
當前提交
68243560cc

+ 5 - 0
packages/ckeditor5-utils/src/mvc/model.js

@@ -42,6 +42,11 @@ CKEDITOR.define( [ 'emitter', 'utils' ], function( EmitterMixin, utils ) {
 		}
 	}
 
+	/**
+	 * @inheritdoc utils#extend
+	 */
+	Model.extend = utils.extendMixin;
+
 	utils.extend( Model.prototype, EmitterMixin, {
 		/**
 		 * Creates and sets the value of a model property of this object. This property will be part of the model state

+ 48 - 2
packages/ckeditor5-utils/src/utils.js

@@ -13,7 +13,7 @@
  */
 
 CKEDITOR.define( function() {
-	return {
+	var utils = {
 		/**
 		 * Extends one JavaScript object with the properties defined in one or more objects. Existing properties are
 		 * overridden.
@@ -23,7 +23,7 @@ CKEDITOR.define( function() {
 		 * @returns {Object} The `target` object.
 		 */
 		extend: function( target, source ) {
-			if ( !this.isObject( source ) ) {
+			if ( !this.isObject( source ) && !this.isFunction( source ) ) {
 				return target;
 			}
 
@@ -67,6 +67,36 @@ CKEDITOR.define( function() {
 		},
 
 		/**
+		 * A mixin function to be used to implement the static `extend()` method in classes. It allows for easy creation
+		 * of subclasses.
+		 *
+		 * @param {Object} [proto] Extensions to be added to the subclass prototype.
+		 * @param {Object} [statics] Additional static properties to be added to the subclass constructor.
+		 * @returns {Object} When executed as a static method of a class, it returns the new subclass constructor.
+		 */
+		extendMixin: function( proto, statics ) {
+			var that = this;
+			var child = ( proto && proto.hasOwnProperty( 'constructor' ) ) ?
+					proto.constructor :
+					function() {
+						that.apply( this, arguments );
+					};
+
+			// Copy the statics.
+			utils.extend( child, this, statics );
+
+			// Use the same prototype.
+			child.prototype = Object.create( this.prototype );
+
+			// Add the new prototype stuff.
+			if ( proto ) {
+				utils.extend( child.prototype, proto );
+			}
+
+			return child;
+		},
+
+		/**
 		 * Creates a spy function (ala Sinon.js) that can be used to inspect call to it.
 		 *
 		 * The following are the present features:
@@ -97,4 +127,20 @@ CKEDITOR.define( function() {
 			};
 		} )()
 	};
+
+	return utils;
 } );
+
+/**
+ * Creates a subclass constructor based on this class.
+ *
+ * @member utils
+ * @method extend
+ * @abstract
+ * @static
+ * @inheritable
+ *
+ * @param {Object} [proto] Extensions to be added to the subclass prototype.
+ * @param {Object} [statics] Extension to be added as static members of the subclass constructor.
+ * @returns {Object} The subclass constructor.
+ */

+ 18 - 3
packages/ckeditor5-utils/tests/mvc/model/model.js

@@ -9,13 +9,15 @@
 
 var modules = bender.amd.require( 'mvc/model', 'eventinfo' );
 
-var car;
+var Car, car;
 
 describe( 'Model', function() {
 	beforeEach( 'Create a test model instance', function() {
 		var Model = modules[ 'mvc/model' ];
 
-		car = new Model( {
+		Car = Model.extend();
+
+		car = new Car( {
 			color: 'red',
 			year: 2015
 		} );
@@ -45,7 +47,7 @@ describe( 'Model', function() {
 
 	//////////
 
-	describe( 'set()', function() {
+	describe( 'set', function() {
 		it( 'should work when passing an object', function() {
 			car.set( {
 				color: 'blue',	// Override
@@ -111,4 +113,17 @@ describe( 'Model', function() {
 			sinon.assert.calledWithExactly( spyWheels, sinon.match.instanceOf( EventInfo ), car, 4, sinon.match.typeOf( 'undefined' ) );
 		} );
 	} );
+
+	describe( 'extend', function() {
+		it( 'should create new Model based classes', function() {
+			var Model = modules[ 'mvc/model' ];
+
+			var Truck = Car.extend();
+
+			var truck = new Truck();
+
+			expect( truck ).to.be.an.instanceof( Car );
+			expect( truck ).to.be.an.instanceof( Model );
+		} );
+	} );
 } );

+ 39 - 0
packages/ckeditor5-utils/tests/utils/utils.js

@@ -9,6 +9,45 @@
 
 var modules = bender.amd.require( 'utils' );
 
+describe( 'extendMixin', function() {
+	it( 'should extend classes', function() {
+		var utils = modules.utils;
+
+		function Car( name ) {
+			this.name = name;
+		}
+
+		Car.prototype.addGas = function() {};
+
+		Car.extend = utils.extendMixin;
+
+		var Truck = Car.extend( {
+			loadContainers: function() {}
+		} );
+
+		var volvoTruck = new Truck( 'Volvo' );
+
+		expect( volvoTruck ).to.be.an.instanceof( Truck );
+		expect( volvoTruck ).to.be.an.instanceof( Car );
+		expect( volvoTruck ).to.have.property( 'name' ).to.equals( 'Volvo' );
+		expect( volvoTruck ).to.have.property( 'addGas' ).to.be.a( 'function' );
+		expect( volvoTruck ).to.have.property( 'loadContainers' ).to.be.a( 'function' );
+
+		var Spacecraft = Truck.extend( {
+			jumpToHyperspace: function() {}
+		} );
+
+		var falcon = new Spacecraft( 'Millennium Falcon' );
+		expect( falcon ).to.be.an.instanceof( Spacecraft );
+		expect( falcon ).to.be.an.instanceof( Truck );
+		expect( falcon ).to.be.an.instanceof( Car );
+		expect( falcon ).to.have.property( 'name' ).to.equals( 'Millennium Falcon' );
+		expect( falcon ).to.have.property( 'addGas' ).to.be.a( 'function' );
+		expect( falcon ).to.have.property( 'loadContainers' ).to.be.a( 'function' );
+		expect( falcon ).to.have.property( 'jumpToHyperspace' ).to.be.a( 'function' );
+	} );
+} );
+
 describe( 'spy', function() {
 	it( 'should register calls', function() {
 		var utils = modules.utils;