Skip to content

Commit

Permalink
Prefixed the API of the framework.
Browse files Browse the repository at this point in the history
Impacted content is really the implementation, including:

- its code
- its documentation
- its tests

Here are the element which have been prefixed:

- `attributes` property of components' controllers
- `on<Attribute>Change` change handler of components' controllers
- the `elements` property of components definitions, to define what kind of content they can expect
- the associated `content` attribute on components instances referring to the instances of these elements
- `event` object available in environment of event handler expressions
- `scope` object available in environment of templates

The playground is part of the documentation and has been updated accordingly.

That includes:

- the playground runner
- the samples
	- code
	- documentation

Closes #320
  • Loading branch information
ymeine committed Oct 14, 2014
1 parent 075038e commit fbcc305
Show file tree
Hide file tree
Showing 54 changed files with 194 additions and 194 deletions.
32 changes: 16 additions & 16 deletions docs/api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ It will forward all the given parameters, and add its own metadata with the foll

```cs
<template example()>
{log scope}
{log $scope}
</template>
```

Expand Down Expand Up @@ -249,15 +249,15 @@ The above means mainly two important things:
* parameters of the template (equivalent of element attributes) are __passed by name__, not by position
* it not only instantiates the template but also renders it automatically in a DOM element inserted exactly where the statement is used

There is also an additional subtlety regarding the passing of the parameters. As said, they are passed by name, so if you use `<tplref arg1="..." />` for a template defined like this `<template(whatever, arg1)>`, `arg1` will be properly passed, wherever it is defined in the parameters list. However the actual subtlety resides in the __first__ parameter of the function: if it doesn't match any attribute name, it is not left `undefined` as one could think. Instead, it refers to an object built from the attribute/value pairs. In our little example, `whatever` would refer to an object like this: `{arg1: "..."}`. This is implicitely due to the internal way hashspace is managing components instantiation (components are discussed later in this documentation).
There is also an additional subtlety regarding the passing of the parameters. As said, they are passed by name, so if you use `<tplref arg1="..." />` for a template defined like this `<template(whatever, arg1)>`, `arg1` will be properly passed, wherever it is defined in the parameters list. However the actual subtlety resides in the __first__ parameter of the function: if it doesn't match any attribute name, it is not left `undefined` as one could think. Instead, it refers to an object built from the attribute/value pairs. In our little example, `whatever` would refer to an object like this: `{arg1: "..."}`. This is implicitly due to the internal way hashspace is managing components instantiation (components are discussed later in this documentation).

__Reference__:

Note also that the statement is not expecting a simple template name, it takes a reference to it. The syntax limits it to be a simple reference access (which means that it can't be returned by a function call, that the ternary operator can't be used, etc.)

__Using a template as a container__:

Using a template as a container with child elements is only useful if you have to pass `template` attributes' values to the subtemplate. It is not oftenly used, and you might prefer implement here a [component](#interfaces) instead.
Using a template as a container with child elements is only useful if you have to pass `template` attributes' values to the subtemplate. It is not often used, and you might prefer implement here a [component](#interfaces) instead.

---

Expand Down Expand Up @@ -364,17 +364,17 @@ In order to avoid side effects, you must pay attention in modifying and returnin

## Special HTML element attributes

#### Event handlers ` onclick="{a.b.doThis(event, mydata)}"`
#### Event handlers ` onclick="{a.b.doThis($event, mydata)}"`

Event handler attributes of HTML elements accept function expressions.

This means that instead of giving a piece of JavaScript code to be executed in the global environment, you can use the standard call mechanisms, in the environment of the current module (file).

Event handlers have a particularity though: in your function call, you can pass the `event` object to your handler function. This `event` object is implicitly available in the context of your expression, but it is not automatically passed, so you need to explicitly pass it if you want to have it available in your function.
Event handlers have a particularity though: in your function call, you can pass the `$event` object to your handler function. This `$event` object is implicitly available in the context of your expression, but it is not automatically passed, so you need to explicitly pass it if you want to have it available in your function.

```cs
<template example()>
<span onclick="{handler(event)}">Click me</span>
<span onclick="{handler($event)}">Click me</span>
</template>

function handler(event) {
Expand Down Expand Up @@ -494,7 +494,7 @@ A the end of the life cycle, when the component is not needed anymore, the `$dip

Defines the attributes of the controller, also considered as the attributes of the component.

In the _klass_ definition of the controller, a specific property called `attributes` is used to define the attributes of the controller/component. It allows you to define the public API of the controller itself.
In the _klass_ definition of the controller, a specific property called `$attributes` is used to define the attributes of the controller/component. It allows you to define the public API of the controller itself.

It expects a map of attribute names (the keys) with their definitions (the values).

Expand All @@ -516,7 +516,7 @@ Hashspace natively supports different types of attributes:
The basic types attributes, to be declared when defining your component class:

```json
attributes: {
$attributes: {
"count": { type: "int", defaultValue: 0 },
"price": { type: "float", defaultValue: 19.90 },
"active": { type: "boolean", defaultValue: true },
Expand All @@ -535,7 +535,7 @@ Hashspace natively supports different types of attributes:
We also support `callback` attributes. They are especially useful to be associated with you r component external events based API.

```json
attributes: {
$attributes: {
onclick: { type: "callback" },
onselect: { type: "callback" }
}
Expand Down Expand Up @@ -571,7 +571,7 @@ Hashspace natively supports different types of attributes:

```javascript
var MyCpt = klass({
attributes: {
$attributes: {
header: { type: "template" },
body: { type: "template", defaultcontent: true }
}
Expand Down Expand Up @@ -634,7 +634,7 @@ _Binding_ means linking two references, so that they point to the same value whe
__Example:__

```json
attributes: {
$attributes: {
attr: {type: "string", defaultValue: "", binding: "2-way"}
}
```
Expand All @@ -655,7 +655,7 @@ attributes: {
Elements are here to address this lack. They are somehow smarter `template` attributes. With them, you have the ability to use them as collections (or iterative elements).

```json
elements: {
$elements: {
option: { type: "template" }
}
```
Expand Down Expand Up @@ -754,25 +754,25 @@ var Controller = klass({

---

##### on&lt;Attribute&gt;Change(newValue, oldValue)
##### $on&lt;Attribute&gt;Change(newValue, oldValue)

User methods whose names follow a specific pattern, used to react to change events triggered by attributes.

When a property's value changes, __and if the property is bound in at least one way__, the engine automatically calls - if defined - a method of the controller whose name is built from the name of the property:

* the name of the property is taken and its first letter is upper cased, the rest is left untouched
* the function must then be named like this: `"on" + transformedName + "Change"`
* the function must then be named like this: `"$on" + transformedName + "Change"`

Now, let's talk quickly about what is a property change. A property's value is said to have changed when the reference associated to the property has changed, nothing more. That means that all inplace transformations are not taken into account, since the property will still refer to the same object.

__Important note__: any property change occurring in a change handler doesn't trigger any change event. That means that no change handler for any property will be called. This is done to prevent a possible automatic infinite recursive call to the current change handler (either directly or by some sorts of side-effects).

```javascript
var Controller = klass({
attributes: {
$attributes: {
text: {type: "string", binding: "1-way"}
},
onTextChange: function(newValue, oldValue) {
$onTextChange: function(newValue, oldValue) {
// ...
},
});
Expand Down
18 changes: 9 additions & 9 deletions docs/playground/layout.hsp
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ var plunkerExport = require("./plunker.js");
var Class = require("hsp/klass");

var DescriptionCtrl = Class({
attributes: {
$attributes: {
sample: { type: "object", binding: "1-way" }
},
$init: function() {
this.onSampleChange();
this.$onSampleChange();
},
$refresh: function() {
if (!this.sample) return;
Expand All @@ -22,7 +22,7 @@ var DescriptionCtrl = Class({
}
},

onSampleChange: function() {
$onSampleChange: function() {
if (!this.sample) return;
var parts = this.sample.description.split("[#output]");
this.before = parts[0];
Expand All @@ -48,19 +48,19 @@ var DescriptionCtrl = Class({
<export template mainLayout(data, playground)>
<#sampleList data="{data}" playground="{playground}"/>

<div class="hsp-sample {{'hsp-sample-full': data.navCollapsed}}" onclick="{hideNavHover(event, data)}">
<div class="hsp-sample {{'hsp-sample-full': data.navCollapsed}}" onclick="{hideNavHover($event, data)}">
<!--div class="actions">
<a href="" title="Toggle code panel"><span class="icon icon-code icon-active"></span></a>
<a href="" title="Toggle description panel"><span class="icon icon-preview icon-active"></span></a>
</div-->
<h4 class="title">{data.sampleTitle}</h4>

<div class="editor" style="width: {data.splitterPos}">
<button class="plunker" title="Export this code to Plunker" onclick="{plunkerExport(event, playground)}">Edit in Plunker</button>
<button class="plunker" title="Export this code to Plunker" onclick="{plunkerExport($event, playground)}">Edit in Plunker</button>
<div id="editor"></div>
</div>

<#splitter type="horizontal" size="3" onrelease="{splitterReleased(event.position, data, playground)}"/>
<#splitter type="horizontal" size="3" onrelease="{splitterReleased($event.position, data, playground)}"/>

<div class="description" style="left: {data.splitterPos}">
<div>
Expand Down Expand Up @@ -107,12 +107,12 @@ function hideNavHover(event, data) {

<template sampleList(data, playground)>
<div class="samples-list {{'samples-list-collapsed': data.navCollapsed}}"
onclick="{hideNavHover(event, data)}">
onclick="{hideNavHover($event, data)}">

<a href="" class="collapse" title="{data.navCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}" onclick="{collapseNav(event, data, playground)}"><span class="icon"></span></a>
<a href="" class="collapse" title="{data.navCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}" onclick="{collapseNav($event, data, playground)}"><span class="icon"></span></a>

{if data.navCollapsed}
<a href="" class="showlist action" title="See samples list" onclick="{showListHover(event, data)}"><span class="icon"></span></a>
<a href="" class="showlist action" title="See samples list" onclick="{showListHover($event, data)}"><span class="icon"></span></a>
<!--a href="" class="action" title="Toggle code panel"><span class="icon icon-code icon-active"></span></a>
<a href="" class="action" title="Toggle description panel"><span class="icon icon-preview icon-active"></span></a-->
{else}
Expand Down
4 changes: 2 additions & 2 deletions docs/playground/splitter.hsp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
var Class = require("hsp/klass");

var SplitterCtrl = Class({
attributes: {
$attributes: {
"type": { type: "string", defaultValue: "horizontal" },
"size": { type: "int", defaultValue: 3 },
"onrelease": { type: "callback" }
Expand Down Expand Up @@ -75,7 +75,7 @@ var SplitterCtrl = Class({
</script>

<template splitter using controller:SplitterCtrl>
<div class="splitter" onmousedown="{controller.onMouseDown(event)}"></div>
<div class="splitter" onmousedown="{controller.onMouseDown($event)}"></div>
<div class="splitter-proxy {{'splitter-proxy-hidden': !controller.active}}"></div>
</template>

Expand Down
6 changes: 3 additions & 3 deletions docs/samples/clock/clock.hsp
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,21 @@ var CITIES={
}

var ClockController=klass({
attributes:{
$attributes:{
"city":{type:"string",binding:"1-way",defaultValue:"PAR"}
},
$init:function() {
this.minuteMarkers=[]; // list of minute markers
for (var i=0;60>i;i++) {
this.minuteMarkers[i]={major:(i%5===0)};
}
this.onCityChange();
this.$onCityChange();
this._iid=setInterval(this.tick.bind(this),100);
},
$dispose:function() {
clearInterval(this._iid);
},
onCityChange:function() {
$onCityChange:function() {
// dynamic city change: check city validity and refresh the display
if (!CITIES[this.city]) {
// unsupported city
Expand Down
4 changes: 2 additions & 2 deletions docs/samples/component1/description.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ The differences with the non-componentized timer sample are that:
- the *timer* template doesn't use function arguments, but declares its controller through the **using [name]:[controller]** syntax
- the controller instance (in this case *Timer*) is automatically created when the component is used - this is why the two *timer* instances show different values
- **the controller *init* function is automatically called once all attributes have been created and initialized** with the values provided by the component host. The *init* method allows then to create and initialize internal properties that will be exposed to the template only. It is important to note that attributes will be automatically created and don't need to be created in the controller's constructor (empty in this example).
- the controller public attributes (here *initvalue*) have to be declared through the an **attributes** collection attached to the controller prototype
- when an attribute is set by an external object (e.g. the host or the component template), the **on[AttributeName]Change** method is called if the attribute binding is declared as *1-way* or *2-way*. This allows synchronization of internal properties bound to public attributes - but we don't need this possibility in this example.
- the controller public attributes (here *initvalue*) have to be declared through the **$attributes** collection attached to the controller prototype
- when an attribute is set by an external object (e.g. the host or the component template), the **$on[AttributeName]Change** method is called if the attribute binding is declared as *1-way* or *2-way*. This allows synchronization of internal properties bound to public attributes - but we don't need this possibility in this example.
2 changes: 1 addition & 1 deletion docs/samples/component1/timer.hsp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ var klass=require("hsp/klass");

// klass is a utility to create JS objects with constructors & prototypes
var Timer=klass({
attributes: {
$attributes: {
initvalue:{type:"int",defaultValue:0,binding:"none"}
},
$init:function() {
Expand Down
2 changes: 1 addition & 1 deletion docs/samples/component2/description.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ This component exposes 4 public attributes and 2 internal values (*internalValue
- the *value* attribute should be propagated to the field only if valid
- the *isValid* property has to change depending on the *internalValue* and on the attribute values, etc.

To manage these internal constraints the controller can implement onXxxChange() methods that will be automatically called when properties or bound attributes are updated
To manage these internal constraints the controller can implement $onXxxChange() methods that will be automatically called when properties or bound attributes are updated

It is important to note that **change handlers are not called when changes are performed by the controller itself** - i.e. **if a controller property is changed in a change handler, the change handler of the corresponding property will not be called**. This allows avoiding infinite loops and strange side effects.
10 changes: 5 additions & 5 deletions docs/samples/component2/nbrfield.hsp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ var klass=require("hsp/klass");

// Component controller
var NbrField = klass({
attributes:{
$attributes:{
"value":{type:"float",defaultValue:0,binding:"2-way"},
"defaultvalue":{type:"float",defaultValue:0},
"min":{type:"float",defaultValue:-Number.MAX_VALUE,binding:"1-way"},
Expand All @@ -23,22 +23,22 @@ var NbrField = klass({
* attribute change handlers - notify the controller that an external object
* (template or host) updated the value attribute
*/
onValueChange:function(newValue,oldValue) {
$onValueChange:function(newValue,oldValue) {
var n=getNumber(newValue);
this.internalValue = n!==null? n : 0;
this.checkValidity();
},
onMinChange:function(newValue,oldValue) {
$onMinChange:function(newValue,oldValue) {
this.checkValidity();
},
onMaxChange:function(newValue,oldValue) {
$onMaxChange:function(newValue,oldValue) {
this.checkValidity();
},
/**
* property change handler - notify the controller that the template
* changed an internal property
*/
onInternalValueChange:function(newValue,oldValue) {
$onInternalValueChange:function(newValue,oldValue) {
// validate and expose as attribute if ok
this.value = this.checkValidity()? parseInt(this.internalValue,10) : this.defaultvalue;
},
Expand Down
2 changes: 1 addition & 1 deletion docs/samples/component3/description.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This example shows how to develop a simple pagination component based on [bootst

As you can see, supporting custom events in a component simply requires two lines of code:

- in the component *attributes* section, the developer needs to declare the callback attribute - such as *onpageselect* in this example. Note that the event name should be lower case to follow the HTML conventions.
- in the component *$attributes* section, the developer needs to declare the callback attribute - such as *onpageselect* in this example. Note that the event name should be lower case to follow the HTML conventions.
- in the method where the event needs to be raised, you simply need to call the event callback and pass the event object properties as argument - c.f. *this.onpageselect({pageNumber:this.activepage})* in this example

[bootstrap]: http://getbootstrap.com/
8 changes: 4 additions & 4 deletions docs/samples/component3/pagination.hsp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ function rebuildInternalModel(ctl) {
}

var Pagination=klass({
attributes: {
$attributes: {
"collectionsize":{type:"int", defaultValue:0, binding:"1-way"},
"pagesize":{type:"int", defaultValue:10, binding:"1-way"},
"activepage":{type:"int", defaultValue:0, binding:"2-way"},
Expand All @@ -36,10 +36,10 @@ var Pagination=klass({
this.onpageselect({pageNumber:this.activepage});
}
},
onCollectionsizeChange: function() {
$onCollectionsizeChange: function() {
rebuildInternalModel(this);
},
onPagesizeChange: function() {
$onPagesizeChange: function() {
rebuildInternalModel(this);
}
});
Expand Down Expand Up @@ -69,7 +69,7 @@ var Pagination=klass({
Last page selection <i> - from event</i>: <span class="textValue">{model.lastSelectedPage}</span>
</div>
<#pagination activepage="{model.active}" collectionsize="{model.collectionSize}"
pagesize="{model.pageSize}" onpageselect="{updateSelection(event.pageNumber)}"/>
pagesize="{model.pageSize}" onpageselect="{updateSelection($event.pageNumber)}"/>

</template>

Expand Down
Loading

0 comments on commit fbcc305

Please sign in to comment.