-
Notifications
You must be signed in to change notification settings - Fork 1
Design doc
- can-type
- DefineClass seal
- DefineClass defaults
- DefineClass PropDefinitions
- DefineClass PropDefinition
- Other Types
The following example shows most of the possibilities:
import {DefineClass, type} from "can/ecosystem";
MyExampleType extends DefineClass {
static define = {
}
get property(){}
set property(){}
method(){}
}
Currently, we are going to release the following packages with associated exports and named can/ecosystem
exports:
-
can-define-class
exports theDefineClass
constructor.import {DefineClass} from "can/ecosystem"
-
can-define-array
exports theDefineArray
constructor.import {DefineArray} from "can/ecosystem"
-
can-stache-define-element
exports theStacheDefineElement
constructor.import {StacheDefineElement} from "can/ecosystem"
-
can-type
exports an object of properties and methods.import {type} from "can/ecosystem"
Issues:
- Should define-class have a single export
- DefineArray package name
- Does DefineArray inherit from Array? If it does, there will be bugs due check to Array.isArray assuming non observable.
- DefineArray overwrites array methods so events will fire. This is why an Array type is needed.
- A proxy version of DefineArray is very useful / important. However, is this incompatible with
class
?
- Is there a "version locking problem" with having can-define-class and can-define-list? cc @kevin
This is an improved can-data-types
. 3.0 had a can-types
.
Examples:
age: types.maybe(Number), //-> {[can.new](), schema: {type:"or", values: [Number, null]}}
age: types.convert(Number), //-> {[can.new]()}
ALTERNATES:
types.coerce(Number)
age: types.maybeConvert(Number),
ALTERNATES:
age: types.loose(Number)
age: types.check(Number)
ALTERNATES:
age: types.strict(Number)
age: types.any
- not "*"
Issues:
- [Should this be kept in can-data-types]
- Naming of the type functions
This converts a type Type
to allow null and undefined.
Params:
-
type
- A constructor function orTypeObject
Returns:
- A
TypeObject
This will return a type that will throw if can.new
is called with value that is not already the right
type.
canReflect.new( type.check(String), 1 ) //-> throws
A type that allows anything to be set.
canReflect.new( type.any, 1 ) //-> 1
canReflect.new( type.any, {} ) //-> {}
Schema:
class Extended extends DefineClass {
static seal = Boolean;
static defaults = Primitive |
PrimitiveFunction |
ConstructorFunction |
TypeObject |
PropDefinition;
static define = PropDefinitions;
get PROP_NAME(){}
set PROP_NAME(){}
METHOD_NAME(){}
}
Create an instance of a defined class.
Issues:
-
What should happen with properties that were not pre-defined? For example:
new ExtendedDefineClass({propNotDefined: "value"})
can-define
does not complain.Solution: set seal to false if you want to enable this.
Creates an instance of a define class.
Example:
const obj = new DefineClass({ foo: "bar" })
obj.foo //-> "bar"
Issues:
-
Should this be sealed or unsealed?
-
DefineMap
is unsealed.
-
Controls if the object is sealed or not.
Issues:
This would describe the default property definition object. This would be used for all properties for the object. The possible values would match the key values of a PropDefinitions object.
Issues:
-
static can.defineInstanceProperty
- socan-connect
can add _saving -
listenTo
/stopListening
-
get(prop)
andset(prop, value)
Issues:
-
get()
andset()
should not work with a sealed instance. But if we add a proxy, what's the point of these? - How will StacheDefineElement be able to do
this.listenTo("click", function(){})
Schema:
class Extended extends DefineClass {
static define = PropDefinitions {
[key: String]:
Primitive |
PrimitiveFunction |
ConstructorFunction |
TypeObject |
PropDefinition |
}
}
Example:
class Extended extends DefineClass {
static define = {
age: 36, // Primitive
date: Number // PrimitiveFunction
date: Date // Constructor function
name: type.convert(String) // TypeObject
completed: {default: false} // PropDefinition
}
}
Depending on the key's value in a prop definition, the following will happen:
-
Primitive
- Uses the primitive as the default value. This also sets thetype
to match the type of the primitive value.Issues:
-
PrimitiveFunction
- Sets the type to theConstructorFunction
value. This will be have "strict" typing. Primitives likeNumber
will use atypeof
check. -
ConstructorFunction
- Sets the type to theConstructorFunction
value. This will be have "strict" typing. Non primitives will use aninstanceof
check. -
TypeObject
- This will be the type, set as-is. -
PropDefinition
- If an object is passed, it will be treated as aPropDefinition
object.
Issues:
- What if the
PropDefinition
object is empty? This was a shorthand for theany
type incan-define
. - What if a function is specified? Could this be treated as a default value too? This would be nice for views:
static define = { renderer: stache("{{foo}}") }
Schema:
{
type:
TypeObject |
PrimitiveFunction |
ConstructorFunction |
FunctionFunction //?
default:
Primitive |
Function |
Object
get default(){}
get(){}
set( [newVal] [,lastSet] ){}
async( [resolve] ){}
value( {resolve, listenTo, stopListening, lastSet}){},
required: Boolean=false,
enumerable: Boolean,
serialize(value):Any
}
Example:
{
type: type.convert(String) // TypeObject
type: Date // ConstructorFunction
type: Number // PrimitiveFunction
type: Function // FunctionFunction ??
default: 1 // Primitive
default(){ return {}; } // Function
default: {} // Object
get default(){ return {}; }
get(){
return this.first + " " + this.last;
}
set(){
this.sideEffect = 1;
}
set(value){
return value;
}
set(value, currentValue){
return currentValue;
}
async(){
return Todo.getList();
}
async(resolve){
Todo.getList().then(resolve);
}
value( {resolve, listenTo, stopListening, lastSet}){
resolve(5);
}
required: true,
enumerable: false
serialize: value => ""+value
}
Does the following based on the value:
-
Primitive
- Uses that primitive value as a default value. -
Function
- Calls that function so thatthis
is the instance. The return value of that function will be used as the initial value of the property. -
Object
- Warn user to useget default(){}
.
Issues:
- Allow providing a primitive as the default value
- Support a
resolve
- It is hard to have default functions. If
type: Function
do we take a function if provided? - As an alternative to warning when an
Object
is passed, we could either clone it orObject.create()
it.
Issues:
Issues:
A value be passed on initialization. Undefined counts as being passed: new Extended({prop: undefined})
.
Issues:
- Warn if
true
when there's a default property?
If the property should be enumerable. Non-enumerable properties are not serialized.
How to serialize the value.
The following are some important methods:
canReflect.convert()
canReflect.new()
canReflect.isConstructorLike()
-
canReflect.isMember()
... does this exist?
-
can.new(value)
- Throw if not allowed, otherwise converts to the type. -
can.isMember(value)
- Return true if a member of Type
Any constructor function, including Builtins that pass canReflect.isConstructorLike()
Examples:
class Todo {
method(){}
}
Date
https://github.com/canjs/can-define-class/issues/45
HungryDefineArrayOrObject {
}
HugryList extends DefineArray {
define {
"#": {
type: type.maybeConvert([Hungry|DefineList]) // does not work
}
}
}
Hungry extends DefineClass {
define {
"*": {
type: type.maybeConvert([Hungry|DefineList]) // does not work
}
}
}
Todo extends DefineClass {
static define = {
meta: Hungry
}
}
var todo = new Todo();
todo.meta = {
foo: {bar: {}}
}
todo.meta.foo.bar.zed = "ted"
class Type extends DefineClass {
static define = { prop: Number }
}
const instance = new Type();
instance.prop = "1"; //-> throws `"1" is not a Number`
instance.prop = null //-> throws `null is not a Number`