Valentjs
Valentjs provide easier way to register framework components (directives / routing / controllers) with features. Registered components could be transalted into different frameworks but right now only AngularJS available.
Valentjs - just the wrapper for frameworks and could be used together with default frameworks approach.
- Valentjs
- Valentjs + AngularJS
- AngularJS bootstrap
- Configuration
- Angular configuration (config/run)
- Controllers
- Route
- Directive
- Defined structures
- Serializers
- Url
- Url Manager
- Services
- Decorators
- Base Components
- Contributing
- TODO
TOC was generated using doctoc.
Valentjs + AngularJS
Valentjs provide methods to register and validate:
- directvies
- controllers
- routing
and features form the box:
- Writing code using ES6
- Easier application configuration
- URL manager
- Configured URL serializer/unserializer
- Custom serializers
- Multiple routing for one controller
- Enchanted tcomb structures
- Interfaces / pipes for directives
- Debounced safe digest
- No access to $scope
Simple examples (valentjs / angularjs)
AngularJS bootstrap
valent - same as angular - variable at the global namespace.
; let framework = 'your-application-name' /** * if dependencies are defined - angular module * will be created automatically * * Otherwise - you should register angular module * manually */ dependencies: 'ngRoute' ; valent;
Configuration
valent.config
could be used as key/value storage. Keys with dots will be translated into nested objects.
valentconfig;valentconfig; valentconfig; // {otherwise: '/home', 'html5': true}valentconfig // '/home'
And there are number of shortctus
valentconfigroute; valentconfigroute; valentconfigroute; valentconfigexception;
List of config shorctus
Routing
- route.otherwise(url) - setup url for redirect if route does not exist
- route.onChangeStart(callback) - add hook for event $routeChangeStart
- route.onChangeError(callback) - add hook for event $routeChangeError
- route.addResolver(key, resolver) - add global resolver that will be applied for each route
- resolver(name, params) - same as local resolver. Takes 2 arguments - route name, route params.
- route.enableHistoryApi() - enable html5 routing. By default - html5 routing is enabled
- route.disableHistoryApi() - disable html5 routing
- routing.requireBase(isBaseRequired) - is base tag requierd for application
Exceptions
- exception.handler(handler) - setup exception handler that will available for framework's context and window.on('error')
Angular configuration (config/run)
; let framework = 'your-application-name' dependencies: 'ngRoute' ; // app - angular modulelet app = framework; // same as with native angularapp; // same as with native angularapp;
Or you can run config/run methods directly to angular.module
; let framework = 'your-application-name' dependencies: 'ngRoute' ; angular ;
Controllers
Simple configuration
// ... valent; // or with already attached routevalent;
valent.controller
takes 3 arguments
- controller name
- controller class
- options
Controller class
{ // ... } { // ... }
Constructor could take up to 3 arguments:
resolvers
- if there any local or global resolverurl
- if three is attached routelogger
- configured logger. Always add colored controller's name to logs
destructor
method is called when controller's $scope is destroyed ($destroy event).
Controller options
controller.option.as
An identifier name for a reference to the controller. By default - controller.
{{ controller.greeting }}
If as defined:
as: '_'
Template should be
{{ _.greeting }}
controller.option.url
Route url proxy
controller.option.params
Route url proxy
controller.option.resolve
Route resolve proxy
controller.option.struct
Route struct proxy
controller.option.template
Route template proxy
controller.option.templateUrl
Route templateUrl proxy
Controller.render()
If there is static function render() at controller's class - it's result will be used as template.
{ // ... } static { return '<div>Yo!</div>' }
Route
valent;
valent.controller
takes 3 arguments
- controller name
- url
- options
url
url for controller. Could be a string:
url: '/home'
or array of strings (means that controller will be available by different routes)
url: '/home' '/my/home'
Route options
route.option.params
Any params that will be passed to angular route config. Also route params are available in resolvers (local and global) as second argument.
valent;
route.option.resolve
Local resolvers. Function that will be executed before controller's constructor. Support promises. Resolved results will be passed to controller's constructor. Local resolvers will be executed after global resolvers. Here is Angular documentation about route resolvers. Each resolver take 2 arguments:
name
- resolving route nameparams
- resolving route params
resolve: { return Users; }
route.option.template
template: '<div>Yo!</div>'
route.option.templateUrl
templateUrl: '/templates/home.html'
route.option.struct
Structure for url.
; // ... valent;
Directive
// ... valent;
Directive Controller class
Full list of auto called methods. They are NOT required.
{ } { } { } { } static { return {}; } static { return '<div>....</div>'; }
Constructor arguments are depends on options. By default constructor takes 2 arguments
- directive params
- logger - configured logger. Always add colored controller's name to logs
if interfaces
or optionals (options)
are defined - they will passed before.
TODO: Find better naming for this features. High prio :)
-
destructor
method is called when controller's $scope is destroyed ($destroy event). -
link(element, compileResult)
method - same as default angular's link function but do not take $scope.static compile()
result will be passed as second argument. -
require(controllers)
method - takes all required controllers. Returned content will be passed as second argument tolink()
method. -
static render()
- result of this method could be used as directive's template. -
static compile(element)
method could be used for template compilation. Same as default angular's compile. Very useful if directive's templates are used as configuration. In this way - directive's template should NOT be defined.
For example in this case "Applications" will be used for multi-select label.
Applications
For example in this case - cell templates are defined as a content of grid
directive. In static compile(element)
method this could be parsed and passed to directive controller.
{{ cell.value | date:"MM/dd/yyyy" }}
Directive options
directive.option.as
Same as valent.controller as option
directive.option.template
Same as valent.controller template option but not proxy no route.
directive.option.templateUrl
Same as valent.controller templateUrl option but not proxy no route.
directive.option.restrict
Same as angular directive's options restrict.
Recomment to use only
- A - only matches attribute name
- E - only matches element name
directive.option.require
Uses for directive communications.
require: 'ngModel' '^^plFilterBar'More details at official angular dochttps://docs.angularjs.org/api/ng/service/$compile#-require-).
Relate controllers will be passed to require(controllers)
method.
{ } { thisngModel = controllersngModel; thisplFilterBar = controllersplFilterBar; }
directive.option.params
Same as angular directive's options scope.
directive.option.interfaces
// app-connector.js host = valentconfig; { //... } { } // ...
// server-status-component.js; { connector; } static { return '<div>{{ _.status }}</div>' } valent;
// home-screen.js; connector = 9001; { thisconnector; } static { return `<server-status connector="controller.connector"></server-status>`; } valent;
So we defined for directive <server-status>
interface connector with specific class - AppConnector. So already created instance of this class should be passed to directive as attribute. If not - Exception. If wrong class - Exception.
Bonuses:
- Have control over same object in directive and in parent controller (no matter if it is screen's controller or another directive's controller). Could be useful to create relation between app components.
- Interfaces are passed as first arguments to directitve's constructor
- Inside directive you are sure that instance (or its parents) has correct class (type).
- Useful for complex directives that works with charts, grids, forms etc.
directive.option.options (rename)
valent;
Same as interfaces but options are NOT required. If option's attribute is defined at directive - option instance will be passed to directive's constructor.
{ // }
If option's attribute is NOT defined - null will be passed to directive's constructor.
{ ; }
directive.option.pipes
If defined and not passed to directive - will be created automatically. Available throw DirectiveParams.get()
.
// toggler.js isVisible = false; { if !thisisVisible thisisVisible = true; this; } { if thisisVisible thisisVisible = false; this; }
// drop-down-component.js; { thistoggler = params; } static { return ` <button ng-click="controller.toggler.open()"> Open </button> <button ng-click="controller.toggler.close()"> Close </button> <div ng-if="controller.toggler.isVisible"> ... </div>`; } valent;
// home-screen.js; toggler = ; { thistoggler; thistoggler; } valent;
Both cases will work. If you not pass toggler from parent controller - it will be created automatically (only if params.get('toggler')
was called). For first <drop-down>
component - initial state is controller.toggler.isVisible == false
. For second - controller.toggler.isVisible == true
. And HomeController listens to open
event.
- In HomeController we have control over directive.
- Toggler instance incapsulate all logic - open/close methods, events etc.
- Toggler Could be used multiple times.
- Do not need to implement open/close logic inside directitve
Directive Params
{ thisname = params; params; } static { return '<div>{{ controller.name }} </div>' } valent;
Available methods:
- get(key) - available only we defined params (component options.params). Returns passed value.
- watch(key, callback) -available only we defined params (component options.params). Create watcher for this key. Callback takes actual param's value.
- parse(key) - available for all passed attributes. Uses
$scope.$parse(attr)
to get value. Do not create extra watchers. Useful for large applications.
If try to get()
or watch()
for keys that are not defined at component's params or not equal component's name (for params with restrict 'A') - will be Exception.
parse()
example:
{ thisname = params; } static { return '<div>{{ controller.name }} </div>' } valent;
Defined structures
;
Use tcomb and define structures. Exported structures.
const ListNum = ;const ListInt = ;const ListStr = ;const ListBool = ;const ListDat = ; const MaybeNum = ;const MaybeInt = ;const MaybeStr = ;const MaybeBool = ;const MaybeDat = ; const MaybeListNum = ;const MaybeListInt = ;const MaybeListStr = ;const MaybeListBool = ;const MaybeListDat = ; // [[1,..n],[1..m]...k]const MatrixNum = ;// [[1, undefined, ..n],[1 undefined,..m]...k]const MatrixMaybeNum = ; // [['a',..n],['a'..m]...k]const MatrixStr = ;// [['a', undefined, .n],['a' undefined,..m]...k]const MatrixMaybeStr = ; const MatrixDate = ;const MatrixMaybeDate = ; const MatrixBool = ;const MatrixMaybeBool = ;
Serializers
There 3 serializers
- base serializer
- rename serializer
- url serializer - extends rename serializer with already added rules for all struc that defined in primitives (valent/utils/primitives)
;;;
Base serializer
Constructor takes 1 argument - params structure. Does NOT contain any encode/decode rules.
- encode(decoded)
- decode(encoded)
Struct example
; let struct = id: primitivesNum tags: primitivesMaybeListStr;
Rename serializer
Extends custom serializer but support attributes renaming encode and returns the, original names during decode. Does NOT contain any encode/decode rules. Constructor takes 1 argument - params structure.
Struct example
; let struct = // "id" key will encoded into "i" id: 'i' primitivesNum tags: primitivesMaybeListStr;
Custom serializer
Extends custom serializers from base or rename serializer and add rule for encode/decode.
;; let User = tcomb; { super user: User ; this; } /** * We should override encode/decode methods * because by default encode method takes object * same as struct that is defined in constructor */ { return super; } { let decoded = super; return decodeduser; } let serializer = ; let user = id: 1 name: 'Lorem'; // encodelet encoded = serializer; ; // decodelet decoded = serializer;;
Url serializer
Extends rename serializer and contain rules for encode/decode. Does not contains URL instance. Rules will work only if struct attributes are references to primitives.
NOTE: Serializers contains WeakMap of encode/decode rules. And keys - are objects from "primitives.js" module. Thats you need to make references to primitives. Primitives works with tcomb - so it also works as type validator.
TODO: map strings to primitive's objects?
{
id: 'number',
tags: 'maybe.listOfStrings'
}
Constructor takes 2 arguments
- struct (same as at rename serializer)
- encode/decode options
Default options:
list_delimiter: '~' matrix_delimiter: '!' date_format: 'YYYYMMDD'
Example:
;; let serializer = id: 'i' primitivesNum tags: primitivesMaybeListStr ; let origin = id: 1 tags: 'a' 'b' 'c'; let encoded = serializer;let decoded = serializer; ;
Encode rules:
Primitive | Origin | Encoded |
---|---|---|
primitives.Num, primitives.MaybeNum | 1 | 1 |
primitives.Str, primitives.MaybeStr | 'a' | a |
primitives.Bool, primitives.MaybeBool | true | 1 |
primitives.Bool, primitives.MaybeBool | false | 0 |
primitives.Dat, primitives.MaybeDat | new Date() | 2015112 |
primitives.ListNum, primitives.MaybeListNum | [1, 2, 3] | 1~2~3 |
primitives.ListMaybeNum | [1, null, 2, 3] | 1~~2,3 |
primitives.ListStr, primitives.MaybeListStr | ['a', 'b', 'c'] | a~b~c |
primitives.ListMaybeStr | ['a', null, 'b', 'c'] | a~~b~c |
primitives.ListBool, primitives.MaybeListBool | [true, false] | 1~0 |
primitives.ListDat, primitives.MaybeListDat | [new Date(), new Date()] | 2015112~2015112 |
primitives.MatrixNum, primitives.MaybeMatrixNum | [[1,2],[3,4]] | 1~2!3~4 |
primitives.MatrixMaybeNum | [[1,2,null,3],[4,null,5]] | 1~2 |
primitives.MatrixStr, primitives.MaybeMatrixStr | [['a','b'],['c','4']] | a~b!c~d |
primitives.MatrixBool, primitives.MaybeMatrixBool | [[true, false],[false, true]] | 1~0!0~1 |
Url
If route is defined for controller url instance will be passed to controller's constructor. Also url instance could be created manually.
;; let url = '/store/:id/:tags' id: primitivesNum tags: primitivesMaybeListStr period: primitivesMaybeListDat search: 'q' primitivesMaybeListStr;
Uses URL serializer. Constructor takes 2 arguments:
- pattern - url pattern with placeholders.
- struct - url params structure
;; let url = '/store/:id/:tags' id: primitivesNum tags: primitivesMaybeListStr period: primitivesMaybeListDat search: 'q' primitivesMaybeStr; let route = url; ; // And if current url is // '/store/1/yellow-large?q=Hello?period=20151110-20151120' let params = url;;
Structures with Maybe prefix - means that this parameters are not required. If passed parameters have wrong type - will be exception. Parameters that are not described as placeholder at url pattern - will be added as GET parameter.
Provide helpful methods to work with url. Available methods:
-
go(params, options)
- replace current url with generating according to passed params. Works in angular context - all route events will be fired. options - event options that will be available at url watchers. -
stringify(params)
- return url according to passed params -
redirect(params)
- same as go() but with page reloading -
parse()
- parse current url and return decoded params -
watch(callback)
- listen url changes ($scope event $routeUpdate) and execute callback. Callback arguments - params, diff, options.- params - current url params.
- diff - difference between previous url update.
- options - event options that were passed to go() method
-
isEmpty()
- return true if there are no params in current url -
link(key, fn)
- describe setter for url param. -
linkTo(store)
- automatically link all structure params to store object -
apply()
- execute all added link() functions
Url link and apply example. If url is changed (no matter how - back/forward browser buttons, url.go(params) method, page reload etc.) - each link function will be executed and take current value of binded param.
; filters = {}; { /** * url params "search", "tags" * will be linked this this.filters object */ url; // add link for "id" param urllink'id' { thisid = id; }; urllink'search' { thisfilterssearch = search; }; url; } valent;
Url Manager
let homeUrl = valenturl;homeUrl; valenturl;
- valent.url.get(key) returns url instance for passed key.
- valent.url.set(key, instance) set url instance for key.
For controller with attached url valent.url.set(...)
will be called automatically.
Services
Digest
; { ; }
Already debounce digest (trailing = true, timeout = 50). Configurable. Global configuration:
valentconfig;
Local configuration:
;let configured = digest;
Injector
;
AngularJS $injector service. Only method get() is available and only after application bootstrap (after angular run phase).
; { let $parse = Injector; }
Watcher
;
Service is using to create watchers. watchGroup, watchCollection and deep watch - are not available.
NOTE: We highly recommend NOT to use watchers. No matter how watchers are created using this service or native $scope methods.
; title = 'Hello World!'; { let watcher = this; watcher; }
new Watcher(context)
- takes one argument - controller's context. Return watcher instance that setuped for controller's $scope.
new Watcher()
- Return watcher instance that setuped for $rootScope.
Events
;
Provide access to $scope events. Constructor takes one argument - controller's context.
; { let events = this; events; events; events; }
Decorators
From PR#10.
TODO: implement and add docs :)
; @;@; // ...
Base Components
; ;
TODO: implement and add docs :)
Contributing
TODO: add docs :)
TODO
- Use tcomb for controller/route/component validation!
- Boilerplate
- Examples
- valentjs vs angularjs. Configuration diffs
- implement TODO application using valent
- Fix old and add new test
- redevelop angular-url. Kick extra dependencies (url-pattern)
- rename directive options - interfaces / optionals / pipes
- rename DirecitveParams into ComponentParams
- replace pathes -
/valent/..
. into/valentjs/....
- redevelop exception system. Right now RuntimeException and RegisterException are not comfortable for debugging.
- add more useful primitives
- do not register component's interfaces / options / pipes as angular directive option - scope. This is extra watchers. Use
DirectiveParams.parse()
method to get them.