This morning I was trying to use Appcelerator’s Styled Label Module in an Alloy project and discovered how Alloy’s namespace feature can be used to create custom Alloy view components.
Update: As of Alloy 1.2 you would use <MyView module="my/factory" />
which would result in require('my/factory').createMyView()
.
The iOS example included in the project shows the view component should be used like this:
1 2 3 4 5 6 |
var StyledLabel = require('ti.styledlabel'); var label = StyledLabel.createLabel({ height: Ti.UI.SIZE || 'auto', top: 5, right: 5, bottom: 5, left: 5, html: '<center>Loading, please wait.</center>' }); |
So how do we do this in Alloy?
Well, by default Alloy takes a view component’s tag name and prepends Ti.UI
plus .create
, so that Label
becomes Ti.UI.createLabel
. Luckily, the Styled Label fits the second convention, but instead of Ti.UI
, we need StyledLabel
.
Alloy Namespaces
This is where Alloy’s namespaces or the ns
attribute comes in. Meant to be used for views like Ti.Map.View
it let’s you specify what needs to be used instead of the default Ti.UI
, where most of Titanium’s builtin view components can be found.
So let’s define our view code:
1 2 3 |
<Alloy> <Label ns="StyledLabel" html="<center>Loading, please wait.</center>" /> </Alloy> |
And of course in our controller we need to require the module:
1 |
var StyledLabel = require('ti.styledlabel'); |
And there you are, it looks like we’re done:
1 2 3 4 5 |
$.__views.__alloyId1 = StyledLabel.createLabel({ ns: "StyledLabel", html: "<center>Loading, please wait.</center>", id: "__alloyId1" }); |
.. but we’re not! Try to run this code and you’ll get our beloved red screen telling you Can't find variable: StyledLabel at..
.
Understanding Alloy
But didn’t we defined this variable in our controller? Yes we did, but if you open the Alloy compiled controller you will see that all the generated view code is placed before the original controller code. So at the time our Styled Label is created, there is no StyledLabel
variable, hence the error.
I’m sure some day we will be able to specify parts of our controller code that needs to come before the view code, but right now we can’t.
Working around
Of course StyledLabel
was just a arbitrary name we picked. What we really need is a reference to the ti.styledlabel
module. Knowing now how Alloy used the ns
attribute, the solution is actually pretty simple:
1 2 3 |
<Alloy> <Label ns="require('ti.styledlabel')" html="<center>Loading, please wait.</center>" /> </Alloy> |
which translates properly and works as:
1 2 3 4 5 |
$.__views.__alloyId1 = require('ti.styledlabel').createLabel({ ns: "StyledLabel", html: "<center>Loading, please wait.</center>", id: "__alloyId1" }); |
As an alternative you could define StyledLabel in alloy.js
, which will get inserted in the generated app.js
. This pollutes the global scope, but now you can just use StyledLabel
as namespace.
1 |
var StyledLabel = require('ti.styledlabel'); |
Custom view components
The require
construct can be used to require native as well as CommonJS modules. This opens up possibilities for easily adding custom view components. Imagine having the following loremipsum.js
CommonJS module:
1 2 3 4 5 6 7 8 9 10 11 |
var loremIpsum = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'; exports.createLoremIpsum = function(args) { if (args.chars) { args.text = loremIpsum.substring(0, args.chars).replace(/\w+$/, ''); delete args.chars; } else { args.text = loremIpsum; } return Ti.UI.createLabel(args); }; |
Now this can be used in an Alloy view like this:
1 2 3 |
<Alloy> <LoremIpsum ns="require('loremipsum')" chars="100" /> </Alloy> |
You’ll end up with a custom Alloy view component that shows a variable amount of Lorem Ipsum in a label. Of course this could also be a view composed out of several children, with event listeners and whatever functionality you can think of. Think of it like
or
but then for stand-alone CommonJS modules.
You can even add child elements like this:
1 2 3 4 5 6 |
<Alloy> <SomeView id="someView" ns="require('custom')"> <View id="standardView" /> <AnotherView ns="require('custom')" /> </SomeView> </Alloy> |
This will generate code calling someView.add
to pass the child views, something that isn’t (yet) supported for Widgets.
Pretty awesome hey?!