Components
Components are Askew’s most important structure.
They are defined in .askew
files, and each component defined there will generate a struct
type in the generated Go code which implements askew.Component
.
Name and Visibility
A component is defined with a <a:component>
element at top level:
<a:component name="MyComponent">
<p>Hello, World!</p>
</a:component>
It must have a name
attribute, whose value must be a valid Go identifier.
This value will be the name of the generated struct
type.
Lowercase names will make the component only be usable within the current package.
The name must not collide with any other names, regardless of whether they are defined via Askew or in your Go code.
By default, Askew will generate two additional types for a component with name <name>
:
<name>List
and Optional<name>
.
These are used when embedding the component with the list
or optional
attributes, they are not standalone components.
Their names also must not collide with any other names.
You can disable the generation of these additional types by supplying a parameter usage
:
<a:component name="MyComponent" usage="list">
<p>Hello, World!</p>
</a:component>
In this case, the types MyComponent
and MyComponentList
will be created, but not OptionalMyComponent
.
You can leave usage
empty to only generate the main type.
In the value, you can either give list
or optional
or both, separated by a space character.
Be aware that if you restrict usage, you cannot embed this component elsewhere with a list or optional embed (depending on what you allow). This option should generally be avoided unless you absolutely need to disable the additional types due to name clashes.
Parameters and Construction
A component can have parameters.
Parameters are values given when creating a component instance and can be used to initialize the component’s HTML.
Parameters are given via the attribute params and have the same syntax as Go func
params:
<a:component name="foo" params="name, dayDescr string">
<p>Hello, <a:text expr="name"></a:text>!</p>
<p>Have a <a:text expr="dayDescr"><a:text> day.</p>
</a:component>
In this example, <a:text>
is used to insert the value of the given Go expression as text when initializing the component.
For every component, a method askewInit
will be created.
It will take the parameters defined in params
(or none, if it wasn’t given) and return nothing.
This is considered an internal function that must be called from the component’s init or new func.
Askew expects every component to have both an init and a new func.
The init func is always a method with the name Init
and must not return anything.
The new func must be a standalone function with the name new<c-name>
for private and New<c-name>
for public components and must return a pointer to the component struct.
<c-name>
stands for the capitalized component’s name, i.e. the first letter is converted into uppercase.
Both the new and the init funcs must call askewInit
.
The init func is called by Askew for direct <a:embed>
s.
The new func is called by Askew for <a:construct>
s inside list or optional embeds.
You can either let Askew autogenerate the funcs or write them yourself.
Autogenerating them will have them take the same parameters as askewInit
.
An autogenerated new func will create a component with the new
keyword and call askewInit
on it.
An autogenerated init func will call askewInit
on the subject.
You tell Askew to autogenerate the new and init func by giving the valueless attribute gen-new-init
on the component:
<a:component name="foo" params="name string" gen-new-init>
<p>Hello, <a:text expr="name"></a:text>!</p>
</a:component>
<!-- the following initialization functions will be generated:
func newFoo(name string) {
ret := new(foo)
ret.askewInit(name)
}
func (o *foo) init(name string) {
o.askewInit(name)
}
func (o *foo) askewInit(name string) {…} // always generated
-->
<a:component name="bar">
<!-- Renders the paragraph with "Hello, World!" -->
<a:embed name="f" type="foo" args="`World`"></a:embed>
</a:component>
If you need to do additonal initialization or calculate the arguments for askewInit
internally, instead of autogenerating the functions, you can write them yourself.
You are free to choose different parameters for your functions, as long as you can supply proper arguments to askewInit
, which you must call in those functions.
If Askew calls your funcs via <a:embed>
or <a:construct>
, the args
given there need to fit the parameters of your init / new funcs.
The following example manually defines new/init funcs for the component greeter
and calls them in bar
:
<!-- html code -->
<a:component name="greeter" params="name string">
<p>Hello, <a:text expr="name"></a:text>!</p>
</a:component>
<a:component name="bar" gen-new-init>
<!-- calls user-defined Init() and results in "Hello, Dr. Evil!" -->
<a:embed type="greeter" args="true, `Evil`"></a:embed>
</a:component>
// go code
func newGreeter(isDr bool, name string) *greeter {
ret := new(greeter)
ret.Init(isDr, name)
return ret
}
func (o *greeter) Init(isDr bool, name string) {
if isDr {
o.askewInit("Dr. " + name)
} else {
o.askewInit(name)
}
}
Mind that in generated code, the subject of methods is always named o. While Go does not forbid you to name it differently in your methods, GopherJS seems to be confused when you use a different name, so you should always name the subject o.
Sometimes, you need to keep the value of parameters around for the lifetime of the object.
There is a shorthand notation to do this: Simply put var
in front of the parameter name.
The semantic of this is that a field with that name will be generated in the component’s struct
type and the value of the parameter will be assigned to it in askewInit
.
The following example stores the parameter id
inside the generated MyButton
struct:
<a:component name="MyButton" params="var id string" gen-new-init>
<button>Click me!</button>
</a:component>
Assignments
When instantiating a component, you’d usually want to do something with the values of the parameters.
We have already seen <a:text>
, which is a special case for creating text nodes.
<a:text>
takes a single attribute, expr
, and is replaced by the value of the given Go expression on instantiation.
For setting property values in the DOM, you use a:assign
, which is an Askew-specific attribute.
You can put it on any HTML element inside a component.
It takes a value with the following syntax:
<assignments> ::= <bval> "=" <expr> ("," <bval> "=" <expr>)*
<bval>
is a bound value.
Bound values are Askew’s primary interface between DOM values and Go code.
The are described in detail in the next chapter.
<expr>
is a Go expression that can reference the component’s parameters.
You can give multiple assignments by separating them with commas.
Let’s look at how to use a:assign
to assign the default value of a text input:
<a:component name="MyInput" params="defaultValue string">
<input type="text" a:assign="prop(value) = defaultValue">
</a:component>
Here, we bind the DOM property value of the current element, and then we assign the parameter value of defaultValue to it.
Bindings
While assignments allow you to set bound values at instantiation, bindings allow you access to a bound value for the whole lifetime of a component instance.
You define bindings with Askew’s a:bindings
attribute.
They have the following syntax:
<target> ::= <name> | "(" <name> <type> ")"
<binding> ::= <bval> ":" <target> [ "=" <expr> ]
<bindings> ::= <binding> ( "," <binding> )*
You specify a <bval>
to define the DOM value you want to bind.
Then you specify either a simple name or a name/type tuple.
The name will become a field of the generated struct
.
With the type, you specify the Go type of the value you want to set/retrieve from this binding.
If you don’t specify the type, Askew will guess it, but it is pretty dumb and mostly ends up with string
.
Optionally, you can define an initial value with <expr>
that will be assigned at instantiation just like with a:assign
.
We will now take the previous example with the input value but instead of using a:assign
, we will use a:bindings
as we later might want to query what the user has actually entered:
<a:component name="MyInput">
<input type="text" a:bindings="prop(value):value">
</a:component>
To see how accessing the binding works, we’ll write a constructor that sets the initial value:
func NewMyInput(defaultValue string) *MyInput {
ret := new(MyInput)
ret.Init(defaultValue)
return ret
}
func (o *MyInput) Init(defaultValue string) {
o.askewInit()
o.value.Set(defaultValue)
}
A binding will always have a Get
and a Set
method.
Get
will return the value with the given or guessed type, Set
will take the new value of that type as argument.
Since JavaScript is dynamically typed, there is no single true type for some bindings.
For example, a prop(zIndex)
may be bound as int
which will work as long as the DOM property is actually an integer. However, the DOM allows you to set it to auto
, which cannot be mapped to an int
.
It is your responsibility to select the appropriate type so that every value that can ever occur in your app can be handled.
Askew will panic if the current bound value cannot be mapped to the target Go type.
The names you give to your bindings must be unique in the component.
You can give multiple bindings in a:bindings
by separating them with a comma.
In your code, you can only use the bindings after you called askewInit
.
Captures
Askew allows you to define handlers that will be called when certain DOM events occur.
A component can declare its handlers with an element <a:handlers>
.
Its text content has the same syntax as the content of a Go interface
.
You must define the handlers you declare on the component in your Go code as Askew does not know what the handlers should do and therefore will not generate code for them.
You declare the handlers in the component so that Askew knows how many parameters they have an what the type of each parameter is.
Besides <a:handlers>
, a component may also define handlers with <a:controller>
, which uses the same syntax.
Such handlers are not part of the component.
Instead, if <a:controller>
exists, a controller interface for the component will be generated that contains the function declarations in <a:controller>
.
The generated struct
will have a field named Controller
of that interface type.
Whenever a handler of the controller should be called, the generated code will check whether the Controller
field is currently nil
and if not, call the method on it.
This way, a component can emit events to someone else.
After you defined your handlers, you need to define which events are to be handled by those handlers.
For this, you specify that certain events ought to be captured.
You do that with a:capture
on the element that issues the event.
The syntax is as follows:
<mapping> ::= <name> "=" <bval>
<mappings> ::= "(" [ <mapping> ( "," mapping )* ] ")"
<tags> ::= "{" [ <tag> ( "," tag )* ] "}"
<capture> ::= <eventid> ":" <handler> [ <mappings> ] [ <tags> ]
<captures> ::= <capture> ( "," <capture> )*
A capture begins with the <eventid>
, i.e. the DOM name of the event you want to capture.
Then after the colon, you need to specify a <handler>
that should be called when the event is captured.
This must be the name of a handler you declared with <a:handlers>
or <a:controller>
.
With <mappings>
, you can bind values to the parameters of the handler.
The <name>
must be a parameter of the handler.
If you do not supply a binding for a parameter, Askew will try to fetch it from the item’s dataset
.
<tags>
specify the behavior of the capture.
There is currently only one tag available, preventDefault
.
It takes an optional parameter, which can be:
preventDefault(true)
(the default if given without parameter)preventDefault(false)
preventDefault(ask)
true
means that the event’s default action should be prevented.
false
means it shouldn’t.
ask
means that the event’s default action should be prevented if and only if the handler returns false
.
This requires the handler to return a bool
(otherwise it shouldn’t return anything).
If the preventDefault
tag is not given but the handler returns bool
, the capture behaves like preventDefault(ask)
.
The following example defines a handler that will be called when a form is submitted:
<a:component name="MyForm">
<a:handlers>
submit(name string)
</a:handlers>
<form a:capture="submit:submit(name=form(name)) {preventDefault}">
<input type="text" name="name">
<button type="submit">Submit</button>
</form>
</a:component>
Data
You may need your component to contain additional data.
This can be done with the <a:data>
element, which uses the following syntax:
<field> ::= <name> ( "," <name> )* <type> [ "=" <expr> ]
<fields> ::= <field> ( "\n" <field>)*
Each name given will become a field of the component’s generated struct
type.
The fields will have the given Go type.
If <expr>
is given, each field will be initialized with the given expression in askewInit
.
You can reference the component’s parameters in those expressions.
The following example declares a field s
of type string
and a field i
of type int
:
<a:component name="MyComponent">
<a:data>
s string
i int = 42
</a:data>
</a:component>
i
will be initialized with 42
in askewInit
.