The markdown parsing is broken/disabled for release notes. Sorry about that, I'm chasing the source of a crash that's been bringing this website down for the last couple of days.
๐ฅ `DOM.print` method
1 year ago
Normally you can't use `print` inside of the `@DOM` since it is a function builder which takes DOM elements, it is not regular function
```swift
@DOM override var body: DOM.Content {
// you can't use print statements here, it is not regular function
// if/else, if let, guard let statements are not like in regular functions
}
```
But now it is possible to `print` inside of the `@DOM` this way
```swift
let hello: String? = nil
@DOM override var body: DOM.Content {
if let hello = self.hello {
DOM.print("hello is not null: \(hello)")
} else {
DOM.print("hello is null")
}
}
```
๐ FetchAPI: fix `RequestOptions` (bonus: GraphQL example)
1 year ago
```swift
let options = RequestOptions()
options.method(.post)
options.header("Content-Type", "application/json")
struct ExecutionArgs: Encodable {
let query: String
let variables: [String: String]
}
do {
let jsonData = try JSONEncoder().encode(ExecutionArgs(query: """
query {
ships {
name
model
}
}
""", variables: ["name": "users"]))
if let jsonString = String(data: jsonData, encoding: .utf8) {
options.body(jsonString)
} else {
print("๐ Unable to encode body")
}
} catch {
print("๐ Something went wrong: \(error)")
}
Fetch("https://spacex-production.up.railway.app/", options) { result in
switch result {
case .failure:
break
case .success(let response):
guard response.ok else {
print("๐ Response status code is: \(response.status)")
return
}
struct Response: Decodable {
struct Data: Decodable {
struct Ship: Decodable {
let name: String
let model: String?
}
let ships: [Ship]
}
let data: Data
}
response.json(as: Response.self) { result in
switch result {
case .failure(let error):
print("๐ Unable to decode response: \(error)")
case .success(let response):
print("โ
Ships: \(response.data.ships.map { $0.name }.joined(separator: ", "))")
}
}
break
}
}
```
๐ฆ Improved nested routing
1 year ago
```swift
import Web
@main
class App: WebApp {
@AppBuilder override var app: Configuration {
Routes {
Page { IndexPage() }
Page("space") { SpacePage() }
Page("**") { NotFoundPage() }
}
}
}
class SpacePage: PageController {
// here we pass all fragment routes into the root router
class override var fragmentRoutes: [FragmentRoutes] { [fragment] }
// here we declare fragment with its relative routes
static var fragment = FragmentRoutes {
Page("earth") {
PageController { "earth" }.onDidLoad {
print("๐ earth loaded")
}
}
Page("moon") {
PageController { "moon" }.onDidLoad {
print("๐ moon loaded")
}
}
}
// you can declare multiple different fragment routes
@DOM override var body: DOM.Content {
H1("Space Page")
Button("Load Earth").display(.block).onClick {
self.changePath(to: "/space/earth")
}
Br()
Button("Load Moon").display(.block).onClick {
self.changePath(to: "/space/moon")
}
FragmentRouter(self, Self.fragment) // <== here we add fragment into the DOM
}
}
```
๐ฆ Nested routing, page controller lifecycle, and more
1 year ago
# FragmentRouter
We may not want to replace the entire content on the page for the next route, but only certain blocks.
This is where the new `FragmentRouter` comes in handy!
Let's consider that we have tabs on the `/user` page. Each tab is a subroute, and we want to react to changes in the subroute using the `FragmentRouter` without reloading use page even though url changes.
Declare the top-level route in the `App` class
```swift
Page("user") { UserPage() }
```
And declare `FragmentRouter` in the `UserPage` class
```swift
class UserPage: PageController {
@DOM override var body: DOM.Content {
// NavBar is from Materialize library :)
Navbar()
.item("Profile") { self.changePath(to: "/user/profile") }
.item("Friends") { self.changePath(to: "/user/friends") }
FragmentRouter(self)
.routes {
Page("profile") { UserProfilePage() }
Page("friends") { UserFriendsPage() }
}
}
}
```
In the example above `FragmentRouter` handles `/user/profile` and `/user/friends` subroutes and renders it under the `Navbar`, so page never reload the whole content but only specific fragments. There are also may be declared more than one fragment with the same or different subroutes and they all will just work together like a magic!
Btw `FragmentRouter` is a `Div` and you may configure it by calling
```swift
FragmentRouter(self)
.configure { div in
// do anything you want with the div
}
```
# Breaking changes
`ViewController` has been renamed into `PageController `, Xcode will propose to rename it automatically.
# PageController
`PageController` now have lifecycle methods: `willLoad`, `didLoad`, `willUnload`, `didUnload`.
```swift
override func willLoad(with req: PageRequest) {
super.willLoad(with: req)
}
override func didLoad(with req: PageRequest) {
super.didLoad(with: req)
// set page title and metaDescription
// also parse query and hash
}
override func willUnload() {
super.willUnload()
}
override func didUnload() {
super.didUnload()
}
```
Also you can declare same methods without overriding, e.g. when you declare little page without subclassing
```swift
PageController { page in
H1("Hello world")
P("Text under title")
Button("Click me") {
page.alert("Click!")
print("button clicked")
}
}
.backgroundcolor(.lightGrey)
.onWillLoad { page in }
.onDidLoad { page in }
.onWillUnload { page in }
.onDidUnload { page in }
```
### New convenience methods
`alert(message: String)` - direct JS alert method
`changePath(to: String)` - switching URL path
# More
`Id` and `Class` now can be initialized simply with string like this
```swift
Class("myClass")
Id("myId")
```
Tiny little change but may be very useful.
`App.current.window.document.querySelectorAll("your_query") ` now works!
# Tip
๐จPlease don't forget to update `Webber CLI` tool to version `1.6.1` or above!
๐ซถ `ForEach` for `DOM` and `CSS`
1 year ago
# DOM
## Static example
```swift
let names = ["Bob", "John", "Annie"]
ForEach(names) { name in
Div(name)
}
// or
ForEach(names) { index, name in
Div("\(index). \(name)")
}
```
## Dynamic example
```swift
@State var names = ["Bob", "John", "Annie"]
ForEach($names) { name in
Div(name)
}
// or with index
ForEach($names) { index, name in
Div("\(index). \(name)")
}
Button("Change 1").onClick {
self.names.append("George") // this will append new Div with name automatically
}
Button("Change 2").onClick {
self.names = ["Bob", "Peppa", "George"] // this will replace and update Divs with names automatically
}
```
It is also easy to use it with ranges
```swift
ForEach(1...20) { index in
Div()
}
```
And even simpler to place X-times same element on the screen
```swift
20.times {
Div().class(.shootingStar)
}
```
# CSS
Same as in examples above, but also `BuilderFunction ` is available
```swift
Stylesheet {
ForEach(1...20) { index in
CSSRule(Div.pointer.nthChild("\(index)"))
// set rule properties depending on index
}
20.times { index in
CSSRule(Div.pointer.nthChild("\(index)"))
// set rule properties depending on index
}
}
```
# BuilderFunction
You can use `BuilderFunction` in `ForEach` loops to calculate some value one time only like a `delay` value in the following example
```swift
ForEach(1...20) { index in
BuilderFunction(9999.asRandomMax()) { delay in
CSSRule(Pointer(".shooting_star").nthChild("\(index)"))
.custom("top", "calc(50% - (\(400.asRandomMax() - 200)px))")
.custom("left", "calc(50% - (\(300.asRandomMax() + 300)px))")
.animationDelay(delay.ms)
CSSRule(Pointer(".shooting_star").nthChild("\(index)").before)
.animationDelay(delay.ms)
CSSRule(Pointer(".shooting_star").nthChild("\(index)").after)
.animationDelay(delay.ms)
}
}
```
it can also take function as an argument
```swift
BuilderFunction({ return 1 + 1 }) { calculatedValue in
// CSS rule or DOM element
}
```
LivePreview, DOM, and CSS improvements
1 year ago
## ๐ฅ Improve `LivePreview` declaration
Old way
```swift
class Index_Preview: WebPreview {
override class var language: Language { .en }
override class var title: String { "Index page" }
override class var width: UInt { 600 }
override class var height: UInt { 480 }
@Preview override class var content: Preview.Content {
AppStyles.all
IndexPage()
}
}
```
New way
```swift
class Index_Preview: WebPreview {
@Preview override class var content: Preview.Content {
Language.en
Title("Index page")
Size(600, 480)
AppStyles.all
IndexPage()
}
}
```
## ๐ช DOM: make `attribute` method public
Now you can set custom attributes or not-supported attributes simply by calling
```swift
Div()
.attribute("my-custom-attribute", "myCustomValue")
```
## ๐จ Fix CSS properties
Stop color for gradients now can be set these ways
```swift
// convenient way
.red.stop(80) // red / 80%
// short way
.red/80 // red / 80%
```
`BackgroundClipType` got new `text` value
Fixed properties with browser prefixes, now they all work as expected
`BackgroundImageProperty` got dedicated initializer with `CSSFunction`
Fix uid generation, CSS `!important` modifier, multiple classes
1 year ago
## ๐ Fix uid generation
Excluded digits from the uid cause css doesn't allow ids which starts with digit.
## ๐ช `Class` stores multiple names
Now you can instantiate `Class` with multiple values like `.class("one", "two", "three")`
## ๐จ CSS: implement `!important` modifier
Yeah, that modifier is very important ๐
```swift
// simple values can just call `.important` in the end, e.g.:
.backgroundColor(.white.important)
.height(100.px.important)
.width(100.percent.important)
.display(.block.important)
// all complex calls now have `important: Bool` parameter, e.g.:
.border(width: .length(1.px), style: .solid, color: .white, important: true)
.backgroundColor(r: 255, g: 255, b: 255, a: 0.26, important: true)
.transition(.property(.backgroundColor), duration: .seconds(0.3), timingFunction: .easeIn, important: true)
```
๐ช Allow to put `Style` into `@DOM` block
1 year ago
```swift
@DOM override var body: DOM.Content {
Stylesheet {
Rule(Body.pointer)
.margin(all: 0.px)
.padding(all: 0.px)
MediaRule(.all.maxWidth(800.px)) {
Rule(Body.pointer)
.backgroundColor(0x9bc4e2)
}
MediaRule(.all.maxWidth(1200.px)) {
Rule(Body.pointer)
.backgroundColor(0xffd700)
}
}
// ...other elements...
}
```
๐ช Improve `@media` rule syntax
1 year ago
```swift
@main
public class App: WebApp {
@AppBuilder public override var body: AppBuilder.Content {
/// ...
MainStyle()
}
}
class MainStyle: Stylesheet {
@Rules override var rules: Rules.Content {
MediaRule(.screen.maxWidth(800.px)) {
Rule(Body.pointer)
.backgroundColor(.red)
}
MediaRule(.screen.maxWidth(1200.px)) {
Rule(Body.pointer)
.backgroundColor(.green)
}
MediaRule(.screen.aspectRatio(4/3)) {
Rule(Body.pointer)
.backgroundColor(.purple)
}
MediaRule(!.screen.aspectRatio(4/3)) {
Rule(Body.pointer)
.backgroundColor(.black)
}
}
}
```
which represents
```css
@media only screen and (max-width: 800px) {
background-color: red;
}
@media only screen and (max-width: 1200px) {
background-color: green;
}
@media only screen and (aspect-ratio: 4/3) {
background-color: purple;
}
@media not screen and (aspect-ratio: 4/3) {
background-color: black;
}
```
๐ช Implement simpler `@State` with `UnitValue`
1 year ago
Normal usage without `@State`
```swift
Div().height(100.px) // static value
```
Usage with `@State`
```swift
@State var height = 100.px // this way you even can change px to em on the fly
Div().height($height)
```
New option
```swift
@State var height: Double = 100 // this way you can change digit value only
Div().height($height.px)
```