Comments (4)
writing
...
looks much nicer than writing...
I think that's a deal breaker. Having to write object attributes satisfies XAttributes {...}
for tags w/attributes is way too verbose and detracts from the content.
Normally in Ceylon, I try to use formal
attribute in base classes, which among other benefits reduces the size of the extends clause. But I think one reason this is not done in ceylon.html
is that attributes must be generically enumerated during serialization, so they are stored in a Sequential
in the base class. Perhaps I missed it, but I don't see the enumeration concern being addressed by the Attribute
s classes.
A few other notes:
- For API evolution, I think you need to use
abstract class
es. I don't think the current compiler supports introducing new defaultedinterface
members without recompiling satisfying classes. - Remember, interfaces attributes must use
=>
- I'm not sure how efficient it would be to declare new classes for each use of a tag (OTOH I guess the compiler declares so many classes anyway for lazy iterables)
- Perhaps you could do something like
object extends Div { id="1"; ... }
w/o separate attribute classes, but that only helps a little with the verbosity, and enumerability of attributes must still be addressed. And, I guess, tags would need no-arg constructors.
from ceylon-sdk.
@jvasileff, to address a specifically your “serialization of attributes” concern:
I have started implementing my idea, and the way I’ve dealt with this is by:
- making element classes final,
- making the
Element
class sealed, - adding a
serializedAttributes
attribute to them, - adding unshared
serializeXxxAttribute
functions that can serialize different kinds of attributes (e.g.Boolean
,Sequence
,AttributeValueProvider
), - for
XAttributes
s that have subtypes, adding aserializeXAttributes
to allow the associatedX
class to serialize them, - for
X
classes whoseXAttributes
s don’t have subtypes, merely usesuper.serializeAttributes
.
Since I probably didn’t do a good job explaining, here is how I have the Element.ceylon
unit:
"Represents base class for all HTML elements."
shared sealed abstract class Element(tagName, attributes, children)
extends Node() {
"The name of the tag for this element."
shared String tagName;
"The attributes associated with this element."
shared restricted Attributes attributes;
"The children of this element."
shared {Content<Node>*} children;
shared restricted default{<String->String>*} serializedAttributes => serializeAttributes(attributes);
}
{<String->String>*} serializeAttributes(Attributes attributes) => {
"id"->attributes.id,
"class"->serializeSequentialAttribute(attributes.classes),
"accesskey"->serializeSequentialCharacterAttribute(attributes.accessKey),
"contenteditable"->serializeInheritableBooleanAttribute(attributes.contentEditable),
"contextmenu"->attributes.contextMenu,
"dir"->serializeOptionalAttribute(attributes.dir),
"draggable"->serializeInheritableBooleanAttribute(attributes.draggable),
"dropzone"->serializeOptionalAttribute(attributes.dropZone),
"hidden"->serializeBooleanAttribute(attributes.hidden),
"lang"->attributes.lang,
"spellcheck"->serializeInheritableBooleanAttribute(attributes.spellcheck, "yes", "no"),
"style"->serializeNonemptyAttribute(attributes.style),
"tabindex"->serializeIntegerAttribute(attributes.tabIndex),
"title"->attributes.title,
"translate"->serializeInheritableBooleanAttribute(attributes.translate, "yes", "no"),
*attributes.more
}.map(coalesceItem).coalesced;
Where Attributes
is (sans the documentation):
shared interface Attributes {
shared default String? id => null;
shared default [String*] classes => [];
shared default [Character*] accessKey => [];
shared default Boolean? contentEditable => null;
shared default String? contextMenu => null;
shared default Direction? dir => null;
shared default Boolean? draggable => null;
shared default DropZone? dropZone => null;
shared default Boolean hidden => false;
shared default String? lang => null;
shared default Boolean? spellcheck => null;
shared default String style => "";
shared default Integer? tabIndex => null;
shared default String? title => null;
shared default Boolean? translate => null;
shared default {<String->String>*} more => [];
}
An element that doesn’t define unique attribute doesn’t need to worry about refining attributes
, as it’s the case of Abbr
:
tagged("flow", "phrasing")
shared final class Abbr(attributes = object satisfies Attributes {}, children = [])
extends Element("abbr", attributes, children)
satisfies FlowCategory & PhrasingCategory {
"The attributes associated with this element."
Attributes attributes;
"The children of this element."
{Content<Node>*} children;
}
An element that has unique attributes like Audio
needs to refine attributes
:
tagged("flow", "phrasing", "embedded", "interactive")
shared final class Audio(attributes = object satisfies AudioAttributes {}, children = [])
extends Element("audio", attributes, children)
satisfies FlowCategory & PhrasingCategory & EmbeddedCategory & InteractiveCategory {
"The attributes associated with this element."
AudioAttributes attributes;
"The children of this element."
{Content<Source|Track|FlowCategory>*} children;
shared restricted actual {<String->String>*} serializedAttributes => {
"autoplay"->serializeBooleanAttribute(attributes.autoplay),
"controls"->serializeBooleanAttribute(attributes.controls),
"loop"-> serializeBooleanAttribute(attributes.loop),
"muted"->serializeBooleanAttribute(attributes.muted),
"preload"->serializeOptionalAttribute(attributes.preload),
"src"->attributes.src,
*super.serializedAttributes
}.map(coalesceItem).coalesced;
}
shared interface AudioAttributes
satisfies Attributes {
shared default Boolean autoplay => false;
shared default Boolean controls => false;
shared default Boolean loop => false;
shared default Boolean muted => false;
shared default Preload? preload => null;
shared default String? src => null;
}
If there is a hierarchy between two XAttributes
interfaces, such as the case of AAttributes
and AreaAttributes
, then the serialization of AAttributes
cannot be done inside A
, because Area
doesn’t inherit from A
, and so it can’t use super.serializedAttributes
to refer to A
.
So, we need to separate it into its own declaration:
tagged("flow", "phrasing", "interactive")
shared final class A(attributes = object satisfies AAttributes {}, children = [])
extends Element("a", attributes, children)
satisfies FlowCategory & PhrasingCategory & InteractiveCategory {
"The attributes associated with this element."
AAttributes attributes;
"The children of this element."
{Content<FlowCategory>*} children;
shared restricted actual {<String->String>*} serializedAttributes => serializeAAttributes(attributes);
}
shared interface AAttributes
satisfies BaseAttributes {
shared default String|Boolean download => false;
shared default String? hreflang => null;
shared default [String*] rel => [];
shared default MimeType? type => null;
}
{<String->String>*} serializeAAttributes(AAttributes attributes) => {
"download"->(switch(attribute = attributes.download) case(is String) attribute case(is Boolean) serializeBooleanAttribute(attribute)),
"hreflang"->attributes.hreflang,
"rel"->serializeSequentialAttribute(attributes.rel),
"type"->serializeOptionalAttribute(attributes.type),
*serializeAttributes(attributes)
}.map(coalesceItem).coalesced;
Then, we can use it from Area
:
tagged("flow", "phrasing")
shared final class Area(attributes = object satisfies AreaAttributes {}, children = [])
extends Element("area", attributes, children)
satisfies FlowCategory & PhrasingCategory {
"The attributes associated with this element."
AreaAttributes attributes;
"The children of this element."
{Content<PhrasingCategory>*} children;
shared restricted actual {<String->String>*} serializedAttributes => {
"alt"->attributes.alt,
"coords"->serializeSequentialIntegerAttribute(attributes.coords),
"media"->attributes.media,
"shape"->serializeOptionalAttribute(attributes.shape),
*serializeAAttributes(attributes)
}.map(coalesceItem).coalesced;
}
shared interface AreaAttributes
satisfies AAttributes {
shared default String alt => "";
shared default [Integer*] coords => [];
shared default String? media => null;
shared default Shape? shape => null;
}
By the way, the definition of serializeXxxAttribute
should be clear. Here is an example to demonstrate:
String? serializeSequentialAttribute([String*] attribute) {
if(nonempty attribute) {
return " ".join(attribute);
}
else {
return null;
}
}
coalesceItem
(probably a misnomer), is defined as:
<Key->Item&Object>? coalesceItem<out Key, out Item>(Key->Item element)
given Key satisfies Object {
if(exists item = element.item) {
return element.key->item;
}
else {
return null;
}
}
It’s also interesting to note that, since streams are lazy, I don’t need to worry about having an Attribute
alias, since people who want their attributes to be only evaluated at template rendering time can write the attributes with =>
instead of with =
:
value div = Div
{
object attributes
satisfies Attributes
{
classes => potentiallyHeavyComputation();
// or even:
// shared actual late [String*] classes = potentiallyHeavyComputation();
}
};
In case you are wondering why I have put serializedAttributes
in X
instead of XAttributes
, it’s because I don’t think it’s a good idea that you can end up with href="https://whatever/"
in, say, a Div
.
For API evolution, I think you need to use abstract classes. I don't think the current compiler supports introducing new defaulted interface members without recompiling satisfying classes.
That’s a bummer, considering that one might want to instantiate, say, an A
and an Input
with the same global attributes set.
Perhaps you could do something like […]
That’s an interesting idea, but I feel like it’s overall worse because it requires verbose boilerplate for every element, and not only elements with attributes. Additionally, I’d say that an element’s tag is more important semantically than its attributes, so having noise in there makes the templates harder to read than in the attributes.
from ceylon-sdk.
So, this is library developer ease of use versus end-user ease of use question.
The way I see this. ceylon.html
biggest strength so far is that it is a fairly close to 1:1 parity between HTML format and Ceylon declarative code style.
@Zambonifofex, I see your pain and I understand it, but forcing library users to write their declarative HTML like this is unacceptable:
Div {
object attributes satisfies Attributes {
classes = ["form-container", "foo-bar"];
lang = "en";
},
Form {
object attributes satisfies Attributes {
classes = ["my-form"];
id = "main-form";
},
P {
Label{ "Login", Input {}}
},
P {
Label {
"Password",
Input {
object attributes satisfies InputAttributes {
type = password;
}
}
}
},
P {
Button { "Submit" }
}
}
}```
When currently we can do this (I hope I got it right):
```ceylon
Div {
classes = ["form-container", "foo-bar"];
lang = "en";
Form {
classes = ["my-form"];
id = "main-form";
P {
Label{ "Login", Input() }
},
P {
Label{ "Password", Input(type = password) }
},
P {
Button { "Submit" }
}
}
}
}```
from ceylon-sdk.
So, something occurred to me. @gavinking’s “inline object satisfies
clause inferrence” idea (eclipse-archived/ceylon#5739) could help us out here.
You’ll see me arguing against the feature there, but it seems it would actually be more generally useful than what I could first see. (It wouldn’t be only useful for this particular idea.)
Then, you could shorten it down to:
value section = Section
{
object attributes {id = "how-it-works"; classes = ["foo-bar"];}
H1 {"How it works"},
P {"..."}
};
Or even:
value section = Section
{
object {id = "how-it-works"; classes = ["foo-bar"];};
H1 {"How it works"},
P {"..."}
};
Of course, ideally {}
wouldn’t have been taken by streams, so that the object
keyword could have been omitted (just like function
can be omitted in lambdas), but that’s just not how the language has evolved to be.
from ceylon-sdk.
Related Issues (20)
- http.server.Request.read() and "unterminated" strings HOT 1
- Ceylon logging should support loggers with class name HOT 1
- JavaList should throw IOOBE for bad indexes HOT 2
- Regex stickiness
- ceylon.test::parameters is unintuitive when test is wrapped in a class HOT 1
- ceylon.test::parameters is unintuitive when test is wrapped in a class
- make JavaMap, JavaSet, JavaList serializable
- JDK 9 adds SSLEngineResult.HandshakeStatus.NEED_UNWRAP_AGAIN
- Loading files from the classpath throws FileSystemNotFoundException
- Bad class file error using ceylon.file 1.3.4-SNAPSHOT
- ceylon.time.iso8601::parseDayOfMonth and parseDate fail for leap day HOT 5
- ceylon.time.Period compare and equals incompatible HOT 2
- ceylon.time.Period should be `Invertible` HOT 4
- Decimal == AnythingElse causes assertion failure HOT 3
- use 'new' enum style instead of old 'object' style HOT 3
- ceylon.time.Duration should satisfy Summable, Invertible, and Comparable
- assertThateException -> ExceptionAssert can't handle member exception classes
- promlem with parsing february 29 in leap year HOT 2
- ceylon test -F fails with AssertionError
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from ceylon-sdk.