From 895baeaf89574ee5da2bc257f9971e8df768fdc3 Mon Sep 17 00:00:00 2001 From: Alessandro Mazzoli Date: Thu, 31 Aug 2023 18:11:57 +0200 Subject: [PATCH 01/17] Added base idea for improved mvc --- src/main/scala/scatan/mvc/Example.scala | 89 +++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 src/main/scala/scatan/mvc/Example.scala diff --git a/src/main/scala/scatan/mvc/Example.scala b/src/main/scala/scatan/mvc/Example.scala new file mode 100644 index 00000000..095cbadc --- /dev/null +++ b/src/main/scala/scatan/mvc/Example.scala @@ -0,0 +1,89 @@ +package scatan.mvc + +trait State + +object ModelModule: + trait Model[S <: State]: + def state: S + trait Provider[S <: State]: + def model: Model[S] + +object ViewModule: + trait Requirements[C <: ControllerModule.Controller[?]] extends ControllerModule.Provider[C] + trait View[C <: ControllerModule.Controller[?]](requirements: Requirements[C]): + def controller: C = requirements.controller + def render(): Unit + trait Provider[V <: View[?]]: + def view: V + +object ControllerModule: + trait Requirements[V <: ViewModule.View[?]] extends ModelModule.Provider[?] with ViewModule.Provider[V] + trait Controller[V <: ViewModule.View[?]](requirements: Requirements[V]): + def model: ModelModule.Model[?] = requirements.model + def view: V = requirements.view + trait Provider[C <: Controller[?]]: + def controller: C + +trait MVCProvider[S <: State, C <: ControllerModule.Controller[?], V <: ViewModule.View[?]] + +class ModelImpl[S <: State](val state: S) extends ModelModule.Model[S] +case class MyState(value: Int) extends State + +trait Application[S <: State, C <: ControllerModule.Controller[?], V <: ViewModule.View[?]]( + val state: S, + viewBuilder: ViewModule.Requirements[C] => V, + controllerBuilder: ControllerModule.Requirements[V] => C +) extends ModelModule.Provider[S] + with ViewModule.Requirements[C] + with ControllerModule.Requirements[V]: + override def model: ModelModule.Model[S] = new ModelImpl(state) + override def view: V = viewBuilder(this) + override def controller: C = controllerBuilder(this) + + def render(): Unit = + view.render() + +class HomeView(requirements: ViewModule.Requirements[HomeController]) + extends ViewModule.View[HomeController](requirements): + override def render(): Unit = + println("HomeView.render") + controller.home() + +class HomeController(requirements: ControllerModule.Requirements[HomeView]) + extends ControllerModule.Controller[HomeView](requirements): + def home(): Unit = + println("HomeController.home") + println(model.state) + +class HomeApplication(state: MyState) + extends Application[MyState, HomeController, HomeView]( + state, + new HomeView(_), + new HomeController(_) + ) + +class GameView(requirements: ViewModule.Requirements[GameController]) + extends ViewModule.View[GameController](requirements): + override def render(): Unit = + println("GameView.render") + controller.game() + +class GameController(requirements: ControllerModule.Requirements[GameView]) + extends ControllerModule.Controller[GameView](requirements): + def game(): Unit = + println("GameController.game") + println(model.state) + +class GameApplication(state: MyState) + extends Application[MyState, GameController, GameView]( + state, + new GameView(_), + new GameController(_) + ) + +@main def run(): Unit = + val state: MyState = MyState(1) + var app: Application[?, ?, ?] = HomeApplication(state) + app.render() + app = GameApplication(state.copy(value = 2)) + app.render() From 650edf51c12bfb2f8f896fe6f529716f6801ae70 Mon Sep 17 00:00:00 2001 From: Alessandro Mazzoli Date: Thu, 31 Aug 2023 23:11:07 +0200 Subject: [PATCH 02/17] Refactor --- src/main/scala/scatan/mvc/Example.scala | 190 ++++++++++++++---------- 1 file changed, 109 insertions(+), 81 deletions(-) diff --git a/src/main/scala/scatan/mvc/Example.scala b/src/main/scala/scatan/mvc/Example.scala index 095cbadc..dafa5bd8 100644 --- a/src/main/scala/scatan/mvc/Example.scala +++ b/src/main/scala/scatan/mvc/Example.scala @@ -2,88 +2,116 @@ package scatan.mvc trait State -object ModelModule: - trait Model[S <: State]: - def state: S - trait Provider[S <: State]: - def model: Model[S] - -object ViewModule: - trait Requirements[C <: ControllerModule.Controller[?]] extends ControllerModule.Provider[C] - trait View[C <: ControllerModule.Controller[?]](requirements: Requirements[C]): - def controller: C = requirements.controller - def render(): Unit - trait Provider[V <: View[?]]: - def view: V - -object ControllerModule: - trait Requirements[V <: ViewModule.View[?]] extends ModelModule.Provider[?] with ViewModule.Provider[V] - trait Controller[V <: ViewModule.View[?]](requirements: Requirements[V]): - def model: ModelModule.Model[?] = requirements.model - def view: V = requirements.view - trait Provider[C <: Controller[?]]: - def controller: C - -trait MVCProvider[S <: State, C <: ControllerModule.Controller[?], V <: ViewModule.View[?]] - -class ModelImpl[S <: State](val state: S) extends ModelModule.Model[S] +package mvc: + object Model: + class Interface[S <: State](val state: S) + trait Provider[S <: State]: + def model: Interface[S] + def apply[S <: State](state: S): Interface[S] = Interface(state) + + object View: + trait Requirements[C <: Controller.Interface[?]] extends Controller.Provider[C] + trait Interface[C <: Controller.Interface[?]](requirements: Requirements[C]): + def controller: C = requirements.controller + def show(): Unit + def hide(): Unit + trait Provider[V <: Interface[?]]: + def view: V + def apply[V <: Interface[?]](_view: V): Provider[V] = new Provider[V]: + override def view: V = _view + + object Controller: + trait Requirements[V <: View.Interface[?]] extends Model.Provider[?] with View.Provider[V] + trait Interface[V <: View.Interface[?]](requirements: Requirements[V]): + def model: Model.Interface[?] = requirements.model + def view: V = requirements.view + trait Provider[C <: Interface[?]]: + def controller: C + def apply[C <: Interface[?]](_controller: C): Provider[C] = new Provider[C]: + override def controller: C = _controller + + trait ApplicationPage[S <: State, C <: Controller.Interface[?], V <: View.Interface[?]]( + val model: Model.Interface[S], + viewFactory: View.Requirements[C] => V, + controllerFactory: Controller.Requirements[V] => C + ) extends Model.Provider[S] + with View.Requirements[C] + with Controller.Requirements[V]: + override def view: V = viewFactory(this) + override def controller: C = controllerFactory(this) + + trait Application[S <: State, Route](val model: Model.Interface[S]): + val pages: Map[Route, ApplicationPage[S, ?, ?]] + + trait MementoApplication[Route] extends Application[?, Route]: + private var routes: Seq[Route] = Seq.empty + def show(route: Route): Unit = + routes.lastOption.foreach(pages(_).view.hide()) + routes = routes :+ route + pages(route).view.show() + def back(): Unit = + routes.lastOption.foreach(pages(_).view.hide()) + routes = routes.dropRight(1) + pages(routes.last).view.show() + +// +// Example +// + +import mvc.* case class MyState(value: Int) extends State -trait Application[S <: State, C <: ControllerModule.Controller[?], V <: ViewModule.View[?]]( - val state: S, - viewBuilder: ViewModule.Requirements[C] => V, - controllerBuilder: ControllerModule.Requirements[V] => C -) extends ModelModule.Provider[S] - with ViewModule.Requirements[C] - with ControllerModule.Requirements[V]: - override def model: ModelModule.Model[S] = new ModelImpl(state) - override def view: V = viewBuilder(this) - override def controller: C = controllerBuilder(this) - - def render(): Unit = - view.render() - -class HomeView(requirements: ViewModule.Requirements[HomeController]) - extends ViewModule.View[HomeController](requirements): - override def render(): Unit = - println("HomeView.render") - controller.home() - -class HomeController(requirements: ControllerModule.Requirements[HomeView]) - extends ControllerModule.Controller[HomeView](requirements): - def home(): Unit = - println("HomeController.home") - println(model.state) - -class HomeApplication(state: MyState) - extends Application[MyState, HomeController, HomeView]( - state, - new HomeView(_), - new HomeController(_) - ) - -class GameView(requirements: ViewModule.Requirements[GameController]) - extends ViewModule.View[GameController](requirements): - override def render(): Unit = - println("GameView.render") - controller.game() - -class GameController(requirements: ControllerModule.Requirements[GameView]) - extends ControllerModule.Controller[GameView](requirements): - def game(): Unit = - println("GameController.game") - println(model.state) - -class GameApplication(state: MyState) - extends Application[MyState, GameController, GameView]( - state, - new GameView(_), - new GameController(_) - ) +object HomePage: + + def apply(model: Model.Interface[MyState]): ApplicationPage[MyState, HomeController, HomeView] = + new ApplicationPage[MyState, HomeController, HomeView](model, new HomeView(_), new HomeController(_)) {} + + class HomeView(requirements: View.Requirements[HomeController]) extends View.Interface[HomeController](requirements): + override def show(): Unit = + println("HomeView.show") + renderHome() + override def hide(): Unit = + println("HomeView.hide") + def renderHome(): Unit = + println("HomeView.renderHome") + controller.controlHome() + + class HomeController(requirements: Controller.Requirements[HomeView]) + extends Controller.Interface[HomeView](requirements): + def controlHome(): Unit = + println("HomeController.controlHome") + +object AboutPage: + + def apply(model: Model.Interface[MyState]): ApplicationPage[MyState, AboutController, AboutView] = + new ApplicationPage[MyState, AboutController, AboutView](model, new AboutView(_), new AboutController(_)) {} + + class AboutView(requirements: View.Requirements[AboutController]) + extends View.Interface[AboutController](requirements): + override def show(): Unit = + println("AboutView.show") + renderAbout() + override def hide(): Unit = + println("AboutView.hide") + def renderAbout(): Unit = + println("AboutView.renderAbout") + controller.controlAbout() + + class AboutController(requirements: Controller.Requirements[AboutView]) + extends Controller.Interface[AboutView](requirements): + def controlAbout(): Unit = + println("AboutController.controlAbout") + +enum Page: + case Home, About +object MyApplication extends Application[MyState, Page](Model(MyState(1))) with MementoApplication[Page]: + override val pages: Map[Page, ApplicationPage[MyState, ?, ?]] = Map( + Page.Home -> HomePage(this.model), + Page.About -> AboutPage(this.model) + ) @main def run(): Unit = - val state: MyState = MyState(1) - var app: Application[?, ?, ?] = HomeApplication(state) - app.render() - app = GameApplication(state.copy(value = 2)) - app.render() + val app = MyApplication + app.show(Page.Home) + app.show(Page.About) + app.back() From a6fea68b3f9a66fd85d00ee252a523ab1cbaf150 Mon Sep 17 00:00:00 2001 From: Alessandro Mazzoli Date: Sat, 2 Sep 2023 15:09:32 +0200 Subject: [PATCH 03/17] Improved mvc architecture --- src/main/scala/scatan/mvc/Example.scala | 133 +++++------------- .../scala/scatan/mvc/lib/Application.scala | 10 ++ .../scatan/mvc/lib/ApplicationPage.scala | 35 +++++ .../scala/scatan/mvc/lib/Controller.scala | 30 ++++ .../scatan/mvc/lib/MementoApplication.scala | 12 ++ src/main/scala/scatan/mvc/lib/Model.scala | 10 ++ src/main/scala/scatan/mvc/lib/View.scala | 39 +++++ 7 files changed, 175 insertions(+), 94 deletions(-) create mode 100644 src/main/scala/scatan/mvc/lib/Application.scala create mode 100644 src/main/scala/scatan/mvc/lib/ApplicationPage.scala create mode 100644 src/main/scala/scatan/mvc/lib/Controller.scala create mode 100644 src/main/scala/scatan/mvc/lib/MementoApplication.scala create mode 100644 src/main/scala/scatan/mvc/lib/Model.scala create mode 100644 src/main/scala/scatan/mvc/lib/View.scala diff --git a/src/main/scala/scatan/mvc/Example.scala b/src/main/scala/scatan/mvc/Example.scala index dafa5bd8..b4e4b1b9 100644 --- a/src/main/scala/scatan/mvc/Example.scala +++ b/src/main/scala/scatan/mvc/Example.scala @@ -1,113 +1,58 @@ package scatan.mvc -trait State +import scatan.mvc.lib.* -package mvc: - object Model: - class Interface[S <: State](val state: S) - trait Provider[S <: State]: - def model: Interface[S] - def apply[S <: State](state: S): Interface[S] = Interface(state) +case class MyState(value: Int) extends Model.State - object View: - trait Requirements[C <: Controller.Interface[?]] extends Controller.Provider[C] - trait Interface[C <: Controller.Interface[?]](requirements: Requirements[C]): - def controller: C = requirements.controller - def show(): Unit - def hide(): Unit - trait Provider[V <: Interface[?]]: - def view: V - def apply[V <: Interface[?]](_view: V): Provider[V] = new Provider[V]: - override def view: V = _view +class HomeView(requirements: View.Requirements[HomeController]) extends View.Interface[HomeController](requirements): + override def show(): Unit = + println("HomeView.show") + renderHome() - object Controller: - trait Requirements[V <: View.Interface[?]] extends Model.Provider[?] with View.Provider[V] - trait Interface[V <: View.Interface[?]](requirements: Requirements[V]): - def model: Model.Interface[?] = requirements.model - def view: V = requirements.view - trait Provider[C <: Interface[?]]: - def controller: C - def apply[C <: Interface[?]](_controller: C): Provider[C] = new Provider[C]: - override def controller: C = _controller + override def hide(): Unit = + println("HomeView.hide") - trait ApplicationPage[S <: State, C <: Controller.Interface[?], V <: View.Interface[?]]( - val model: Model.Interface[S], - viewFactory: View.Requirements[C] => V, - controllerFactory: Controller.Requirements[V] => C - ) extends Model.Provider[S] - with View.Requirements[C] - with Controller.Requirements[V]: - override def view: V = viewFactory(this) - override def controller: C = controllerFactory(this) + def renderHome(): Unit = + println("HomeView.renderHome") + controller.controlHome() - trait Application[S <: State, Route](val model: Model.Interface[S]): - val pages: Map[Route, ApplicationPage[S, ?, ?]] +class HomeController(requirements: Controller.Requirements[HomeView]) + extends Controller.Interface[HomeView](requirements): + def controlHome(): Unit = + println("HomeController.controlHome") - trait MementoApplication[Route] extends Application[?, Route]: - private var routes: Seq[Route] = Seq.empty - def show(route: Route): Unit = - routes.lastOption.foreach(pages(_).view.hide()) - routes = routes :+ route - pages(route).view.show() - def back(): Unit = - routes.lastOption.foreach(pages(_).view.hide()) - routes = routes.dropRight(1) - pages(routes.last).view.show() +class AboutView(requirements: View.Requirements[AboutController]) extends View.Interface[AboutController](requirements): + override def show(): Unit = + println("AboutView.show") + renderAbout() -// -// Example -// + override def hide(): Unit = + println("AboutView.hide") -import mvc.* -case class MyState(value: Int) extends State + def renderAbout(): Unit = + println("AboutView.renderAbout") + controller.controlAbout() -object HomePage: - - def apply(model: Model.Interface[MyState]): ApplicationPage[MyState, HomeController, HomeView] = - new ApplicationPage[MyState, HomeController, HomeView](model, new HomeView(_), new HomeController(_)) {} - - class HomeView(requirements: View.Requirements[HomeController]) extends View.Interface[HomeController](requirements): - override def show(): Unit = - println("HomeView.show") - renderHome() - override def hide(): Unit = - println("HomeView.hide") - def renderHome(): Unit = - println("HomeView.renderHome") - controller.controlHome() - - class HomeController(requirements: Controller.Requirements[HomeView]) - extends Controller.Interface[HomeView](requirements): - def controlHome(): Unit = - println("HomeController.controlHome") - -object AboutPage: - - def apply(model: Model.Interface[MyState]): ApplicationPage[MyState, AboutController, AboutView] = - new ApplicationPage[MyState, AboutController, AboutView](model, new AboutView(_), new AboutController(_)) {} - - class AboutView(requirements: View.Requirements[AboutController]) - extends View.Interface[AboutController](requirements): - override def show(): Unit = - println("AboutView.show") - renderAbout() - override def hide(): Unit = - println("AboutView.hide") - def renderAbout(): Unit = - println("AboutView.renderAbout") - controller.controlAbout() - - class AboutController(requirements: Controller.Requirements[AboutView]) - extends Controller.Interface[AboutView](requirements): - def controlAbout(): Unit = - println("AboutController.controlAbout") +class AboutController(requirements: Controller.Requirements[AboutView]) + extends Controller.Interface[AboutView](requirements): + def controlAbout(): Unit = + println("AboutController.controlAbout") enum Page: case Home, About + object MyApplication extends Application[MyState, Page](Model(MyState(1))) with MementoApplication[Page]: override val pages: Map[Page, ApplicationPage[MyState, ?, ?]] = Map( - Page.Home -> HomePage(this.model), - Page.About -> AboutPage(this.model) + Page.Home -> ApplicationPage( + model = this.model, + viewFactory = (requirements: View.Requirements[HomeController]) => new HomeView(requirements), + controllerFactory = (requirements: Controller.Requirements[HomeView]) => new HomeController(requirements) + ), + Page.About -> ApplicationPage( + model = this.model, + viewFactory = (requirements: View.Requirements[AboutController]) => new AboutView(requirements), + controllerFactory = (requirements: Controller.Requirements[AboutView]) => new AboutController(requirements) + ) ) @main def run(): Unit = diff --git a/src/main/scala/scatan/mvc/lib/Application.scala b/src/main/scala/scatan/mvc/lib/Application.scala new file mode 100644 index 00000000..a38e6baf --- /dev/null +++ b/src/main/scala/scatan/mvc/lib/Application.scala @@ -0,0 +1,10 @@ +package scatan.mvc.lib + +/** An application is a collection of pages that share a model. + * @tparam S + * The state type of the model. + * @tparam Route + * The type of the route. + */ +trait Application[S <: Model.State, Route](val model: Model.Interface[S]): + val pages: Map[Route, ApplicationPage[S, ?, ?]] diff --git a/src/main/scala/scatan/mvc/lib/ApplicationPage.scala b/src/main/scala/scatan/mvc/lib/ApplicationPage.scala new file mode 100644 index 00000000..6e45692c --- /dev/null +++ b/src/main/scala/scatan/mvc/lib/ApplicationPage.scala @@ -0,0 +1,35 @@ +package scatan.mvc.lib + +/** A page of an application. It is a combination of a model, a view and a controller. + * @tparam S + * The type of the state of the model. + * @tparam C + * The type of the controller. + * @tparam V + * The type of the view. + * @param model + * The model. + * @param viewFactory + * A factory for the view. + * @param controllerFactory + * A factory for the controller. + */ +trait ApplicationPage[S <: Model.State, C <: Controller.Interface[V], V <: View.Interface[C]]( + val model: Model.Interface[S], + viewFactory: View.Factory[C, V], + controllerFactory: Controller.Factory[V, C] +) extends Model.Provider[S] + with View.Requirements[C] + with Controller.Requirements[V]: + override def view: V = viewFactory(this) + override def controller: C = controllerFactory(this) + +object ApplicationPage: + type Factory[S <: Model.State, C <: Controller.Interface[V], V <: View.Interface[C]] = + Model.Interface[S] => ApplicationPage[S, ?, ?] + def apply[S <: Model.State, C <: Controller.Interface[V], V <: View.Interface[C]]( + model: Model.Interface[S], + viewFactory: View.Factory[C, V], + controllerFactory: Controller.Factory[V, C] + ): ApplicationPage[S, C, V] = + new ApplicationPage[S, C, V](model, viewFactory, controllerFactory) {} diff --git a/src/main/scala/scatan/mvc/lib/Controller.scala b/src/main/scala/scatan/mvc/lib/Controller.scala new file mode 100644 index 00000000..59b1420a --- /dev/null +++ b/src/main/scala/scatan/mvc/lib/Controller.scala @@ -0,0 +1,30 @@ +package scatan.mvc.lib + +/** The Controller object. + */ +object Controller: + type Factory[V <: View.Interface[C], C <: Controller.Interface[V]] = Controller.Requirements[V] => C + + /** The requirements for a Controller. + * @tparam V + * The type of the View. + */ + trait Requirements[V <: View.Interface[?]] extends Model.Provider[?] with View.Provider[V] + + /** The interface for a Controller. + * @tparam V + * The type of the View. + * @param requirements + * The requirements for the Controller. + */ + trait Interface[V <: View.Interface[?]](requirements: Requirements[V]): + def model: Model.Interface[?] = requirements.model + + def view: V = requirements.view + + /** The provider for a Controller. + * @tparam C + * The type of the Controller. + */ + trait Provider[C <: Interface[?]]: + def controller: C diff --git a/src/main/scala/scatan/mvc/lib/MementoApplication.scala b/src/main/scala/scatan/mvc/lib/MementoApplication.scala new file mode 100644 index 00000000..84d98c1b --- /dev/null +++ b/src/main/scala/scatan/mvc/lib/MementoApplication.scala @@ -0,0 +1,12 @@ +package scatan.mvc.lib + +trait MementoApplication[Route] extends Application[?, Route]: + private var routes: Seq[Route] = Seq.empty + def show(route: Route): Unit = + routes.lastOption.foreach(pages(_).view.hide()) + routes = routes :+ route + pages(route).view.show() + def back(): Unit = + routes.lastOption.foreach(pages(_).view.hide()) + routes = routes.dropRight(1) + pages(routes.last).view.show() diff --git a/src/main/scala/scatan/mvc/lib/Model.scala b/src/main/scala/scatan/mvc/lib/Model.scala new file mode 100644 index 00000000..6222916f --- /dev/null +++ b/src/main/scala/scatan/mvc/lib/Model.scala @@ -0,0 +1,10 @@ +package scatan.mvc.lib + +/** The Model object. It encapsulate the base model traits and the apply method to create a State-based model. + */ +object Model: + trait State + class Interface[S <: State](val state: S) + trait Provider[S <: State]: + def model: Interface[S] + def apply[S <: State](state: S): Interface[S] = Interface(state) diff --git a/src/main/scala/scatan/mvc/lib/View.scala b/src/main/scala/scatan/mvc/lib/View.scala new file mode 100644 index 00000000..992f0b14 --- /dev/null +++ b/src/main/scala/scatan/mvc/lib/View.scala @@ -0,0 +1,39 @@ +package scatan.mvc.lib + +/** The View object. + */ +object View: + /** The Factory for a View. + * @tparam C + * The type of the Controller. + * @tparam V + * The type of the View. + */ + type Factory[C <: Controller.Interface[V], V <: Interface[C]] = Requirements[C] => V + + /** The Requirements for a View. + * + * @tparam C + * The type of the Controller. + */ + trait Requirements[C <: Controller.Interface[?]] extends Controller.Provider[C] + + /** The Interface for a View. + * + * @tparam C + * The type of the Controller. + * @param requirements + * The Requirements for the View. + */ + trait Interface[C <: Controller.Interface[?]](requirements: Requirements[C]): + def controller: C = requirements.controller + def show(): Unit + def hide(): Unit + + /** The Provider for a View. + * + * @tparam V + * The type of the View. + */ + trait Provider[V <: Interface[?]]: + def view: V From 944439001b2d3d4123221821a0f51b967b8ab602 Mon Sep 17 00:00:00 2001 From: Alessandro Mazzoli Date: Sat, 2 Sep 2023 16:21:04 +0200 Subject: [PATCH 04/17] test(ModelTest.scala): Added tests to the Model object --- src/test/scala/scatan/mvc/lib/ModelTest.scala | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/test/scala/scatan/mvc/lib/ModelTest.scala diff --git a/src/test/scala/scatan/mvc/lib/ModelTest.scala b/src/test/scala/scatan/mvc/lib/ModelTest.scala new file mode 100644 index 00000000..8296b73f --- /dev/null +++ b/src/test/scala/scatan/mvc/lib/ModelTest.scala @@ -0,0 +1,23 @@ +package scatan.mvc.lib + +import org.scalatest.flatspec.AnyFlatSpec + +class ModelTest extends AnyFlatSpec: + + "The Model Object" should "contains the State trait" in { + val temp: Model.State = new Model.State {} + } + + it should "contains the Interface class" in { + val state: Model.State = new Model.State {} + val temp: Model.Interface[Model.State] = new Model.Interface(state) + } + + it should "contains the Provider trait" in { + val temp: Model.Provider[Model.State] = null + } + + it should "be creatable with a State" in { + val state: Model.State = new Model.State {} + val model: Model.Interface[Model.State] = Model(state) + } From c4789163bed4f1f26b1bd06d9c5a10ff0512b504 Mon Sep 17 00:00:00 2001 From: Alessandro Mazzoli Date: Sat, 2 Sep 2023 16:43:46 +0200 Subject: [PATCH 05/17] test(ModelTest.scala): Improved test --- src/test/scala/scatan/mvc/lib/ModelTest.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/test/scala/scatan/mvc/lib/ModelTest.scala b/src/test/scala/scatan/mvc/lib/ModelTest.scala index 8296b73f..f125640b 100644 --- a/src/test/scala/scatan/mvc/lib/ModelTest.scala +++ b/src/test/scala/scatan/mvc/lib/ModelTest.scala @@ -1,6 +1,7 @@ package scatan.mvc.lib import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers.* class ModelTest extends AnyFlatSpec: @@ -20,4 +21,6 @@ class ModelTest extends AnyFlatSpec: it should "be creatable with a State" in { val state: Model.State = new Model.State {} val model: Model.Interface[Model.State] = Model(state) + model should not be null + model.getClass should be(classOf[Model.Interface[Model.State]]) } From 8c2392231059ec0adb0de0f66c6ffc7ee4058e4e Mon Sep 17 00:00:00 2001 From: Alessandro Mazzoli Date: Sun, 3 Sep 2023 10:18:38 +0200 Subject: [PATCH 06/17] wip: refactoring mvc lib structure --- src/main/scala/scatan/mvc/Example.scala | 41 +++++++++++-------- .../scala/scatan/mvc/lib/Application.scala | 24 ++++++++++- .../scatan/mvc/lib/ApplicationPage.scala | 12 +++--- .../scatan/mvc/lib/MementoApplication.scala | 12 ------ src/main/scala/scatan/mvc/lib/Navigable.scala | 23 +++++++++++ .../scala/scatan/mvc/lib/PageFactory.scala | 6 +++ src/test/scala/scatan/mvc/lib/ModelTest.scala | 3 +- 7 files changed, 82 insertions(+), 39 deletions(-) delete mode 100644 src/main/scala/scatan/mvc/lib/MementoApplication.scala create mode 100644 src/main/scala/scatan/mvc/lib/Navigable.scala create mode 100644 src/main/scala/scatan/mvc/lib/PageFactory.scala diff --git a/src/main/scala/scatan/mvc/Example.scala b/src/main/scala/scatan/mvc/Example.scala index b4e4b1b9..a8771a9e 100644 --- a/src/main/scala/scatan/mvc/Example.scala +++ b/src/main/scala/scatan/mvc/Example.scala @@ -38,25 +38,30 @@ class AboutController(requirements: Controller.Requirements[AboutView]) def controlAbout(): Unit = println("AboutController.controlAbout") -enum Page: - case Home, About - -object MyApplication extends Application[MyState, Page](Model(MyState(1))) with MementoApplication[Page]: - override val pages: Map[Page, ApplicationPage[MyState, ?, ?]] = Map( - Page.Home -> ApplicationPage( - model = this.model, - viewFactory = (requirements: View.Requirements[HomeController]) => new HomeView(requirements), - controllerFactory = (requirements: Controller.Requirements[HomeView]) => new HomeController(requirements) - ), - Page.About -> ApplicationPage( - model = this.model, - viewFactory = (requirements: View.Requirements[AboutController]) => new AboutView(requirements), - controllerFactory = (requirements: Controller.Requirements[AboutView]) => new AboutController(requirements) - ) - ) +enum Pages(val factory: PageFactory[?, ?]): + def toMapEntry: (Pages, PageFactory[?, ?]) = this -> factory + case Home + extends Pages( + PageFactory( + viewFactory = new HomeView(_), + controllerFactory = new HomeController(_) + ) + ) + case About + extends Pages( + PageFactory( + viewFactory = new AboutView(_), + controllerFactory = new AboutController(_) + ) + ) + +val MyApplication = NavigableApplication[MyState, Pages]( + initialState = MyState(0), + pagesFactories = Pages.values.map(_.toMapEntry).toMap +) @main def run(): Unit = val app = MyApplication - app.show(Page.Home) - app.show(Page.About) + app.show(Pages.Home) + app.show(Pages.About) app.back() diff --git a/src/main/scala/scatan/mvc/lib/Application.scala b/src/main/scala/scatan/mvc/lib/Application.scala index a38e6baf..b0296be1 100644 --- a/src/main/scala/scatan/mvc/lib/Application.scala +++ b/src/main/scala/scatan/mvc/lib/Application.scala @@ -6,5 +6,27 @@ package scatan.mvc.lib * @tparam Route * The type of the route. */ -trait Application[S <: Model.State, Route](val model: Model.Interface[S]): +trait Application[S <: Model.State, Route]: + val model: Model.Interface[S] val pages: Map[Route, ApplicationPage[S, ?, ?]] +object Application: + /** Create an application from a model and a list of pages. + * @param model + * The model. + * @param pagesFactories + * The pages. + * @tparam S + * The state type of the model. + * @tparam Route + * The type of the route. + * @return + * The application. + */ + def apply[S <: Model.State, Route]( + initialState: S, + pagesFactories: Map[Route, PageFactory[?, ?]] + ): Application[S, Route] = + new Application[S, Route]: + override val model: Model.Interface[S] = Model(initialState) + override val pages: Map[Route, ApplicationPage[S, ?, ?]] = + pagesFactories.map((k, v) => (k, ApplicationPage(model, v))) diff --git a/src/main/scala/scatan/mvc/lib/ApplicationPage.scala b/src/main/scala/scatan/mvc/lib/ApplicationPage.scala index 6e45692c..5fb9a638 100644 --- a/src/main/scala/scatan/mvc/lib/ApplicationPage.scala +++ b/src/main/scala/scatan/mvc/lib/ApplicationPage.scala @@ -16,20 +16,18 @@ package scatan.mvc.lib */ trait ApplicationPage[S <: Model.State, C <: Controller.Interface[V], V <: View.Interface[C]]( val model: Model.Interface[S], - viewFactory: View.Factory[C, V], - controllerFactory: Controller.Factory[V, C] + val pageFactory: PageFactory[C, V] ) extends Model.Provider[S] with View.Requirements[C] with Controller.Requirements[V]: - override def view: V = viewFactory(this) - override def controller: C = controllerFactory(this) + override def view: V = pageFactory.viewFactory(this) + override def controller: C = pageFactory.controllerFactory(this) object ApplicationPage: type Factory[S <: Model.State, C <: Controller.Interface[V], V <: View.Interface[C]] = Model.Interface[S] => ApplicationPage[S, ?, ?] def apply[S <: Model.State, C <: Controller.Interface[V], V <: View.Interface[C]]( model: Model.Interface[S], - viewFactory: View.Factory[C, V], - controllerFactory: Controller.Factory[V, C] + pageFactory: PageFactory[C, V] ): ApplicationPage[S, C, V] = - new ApplicationPage[S, C, V](model, viewFactory, controllerFactory) {} + new ApplicationPage[S, C, V](model, pageFactory) {} diff --git a/src/main/scala/scatan/mvc/lib/MementoApplication.scala b/src/main/scala/scatan/mvc/lib/MementoApplication.scala deleted file mode 100644 index 84d98c1b..00000000 --- a/src/main/scala/scatan/mvc/lib/MementoApplication.scala +++ /dev/null @@ -1,12 +0,0 @@ -package scatan.mvc.lib - -trait MementoApplication[Route] extends Application[?, Route]: - private var routes: Seq[Route] = Seq.empty - def show(route: Route): Unit = - routes.lastOption.foreach(pages(_).view.hide()) - routes = routes :+ route - pages(route).view.show() - def back(): Unit = - routes.lastOption.foreach(pages(_).view.hide()) - routes = routes.dropRight(1) - pages(routes.last).view.show() diff --git a/src/main/scala/scatan/mvc/lib/Navigable.scala b/src/main/scala/scatan/mvc/lib/Navigable.scala new file mode 100644 index 00000000..50b1c809 --- /dev/null +++ b/src/main/scala/scatan/mvc/lib/Navigable.scala @@ -0,0 +1,23 @@ +package scatan.mvc.lib + +trait Navigable[Route] extends Application[?, Route]: + private var pagesHistory: Seq[Route] = Seq.empty + def show(route: Route): Unit = + pagesHistory.lastOption.foreach(this.pages(_).view.hide()) + pagesHistory = pagesHistory :+ route + pages(route).view.show() + def back(): Unit = + pagesHistory.lastOption.foreach(pages(_).view.hide()) + pagesHistory = pagesHistory.dropRight(1) + pagesHistory.lastOption.foreach(pages(_).view.show()) + +object NavigableApplication: + def apply[S <: Model.State, Route]( + initialState: S, + pagesFactories: Map[Route, PageFactory[?, ?]] + ): Application[S, Route] with Navigable[Route] = + new Application[S, Route] with Navigable[Route]: + override val model: Model.Interface[S] = Model(initialState) + override val pages: Map[Route, ApplicationPage[S, ?, ?]] = pagesFactories.map { (route, pageFactory) => + route -> ApplicationPage(this.model, pageFactory) + } diff --git a/src/main/scala/scatan/mvc/lib/PageFactory.scala b/src/main/scala/scatan/mvc/lib/PageFactory.scala new file mode 100644 index 00000000..98da32e8 --- /dev/null +++ b/src/main/scala/scatan/mvc/lib/PageFactory.scala @@ -0,0 +1,6 @@ +package scatan.mvc.lib + +case class PageFactory[C <: Controller.Interface[V], V <: View.Interface[C]]( + viewFactory: View.Factory[C, V], + controllerFactory: Controller.Factory[V, C] +) diff --git a/src/test/scala/scatan/mvc/lib/ModelTest.scala b/src/test/scala/scatan/mvc/lib/ModelTest.scala index f125640b..131382f1 100644 --- a/src/test/scala/scatan/mvc/lib/ModelTest.scala +++ b/src/test/scala/scatan/mvc/lib/ModelTest.scala @@ -15,7 +15,8 @@ class ModelTest extends AnyFlatSpec: } it should "contains the Provider trait" in { - val temp: Model.Provider[Model.State] = null + val temp: Model.Provider[Model.State] = new Model.Provider[Model.State]: + override def model: Model.Interface[Model.State] = Model(new Model.State {}) } it should "be creatable with a State" in { From b792b37ffd34048fb77da8bc3fcea107fab5c98f Mon Sep 17 00:00:00 2001 From: Alessandro Mazzoli Date: Sun, 3 Sep 2023 10:27:38 +0200 Subject: [PATCH 07/17] chore: added given conversion from Pages enum to pagesFactories for building application --- src/main/scala/scatan/mvc/Example.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/scala/scatan/mvc/Example.scala b/src/main/scala/scatan/mvc/Example.scala index a8771a9e..31dc80bf 100644 --- a/src/main/scala/scatan/mvc/Example.scala +++ b/src/main/scala/scatan/mvc/Example.scala @@ -55,9 +55,13 @@ enum Pages(val factory: PageFactory[?, ?]): ) ) +object Pages: + // Implicit conversion from Pages enum to Map[Pages, PageFactory[?, ?]] + given Conversion[Pages.type, Map[Pages, PageFactory[?, ?]]] = _.values.map(_.toMapEntry).toMap + val MyApplication = NavigableApplication[MyState, Pages]( initialState = MyState(0), - pagesFactories = Pages.values.map(_.toMapEntry).toMap + pagesFactories = Pages ) @main def run(): Unit = From 533bba17e4fa5eec5443ab144843da227dc9f433 Mon Sep 17 00:00:00 2001 From: Alessandro Mazzoli Date: Sun, 3 Sep 2023 10:43:33 +0200 Subject: [PATCH 08/17] fix: changed scalatest version to 3.3.0-SNAP4 to avoid an error --- build.sbt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index 3b92366b..36801fb5 100644 --- a/build.sbt +++ b/build.sbt @@ -5,9 +5,9 @@ lazy val scatan = (project in file(".")) .settings( name := "Scatan", libraryDependencies ++= Seq( - "org.scalatest" %% "scalatest" % "3.2.16" % "test", + "org.scalatest" %% "scalatest" % "3.3.0-SNAP4" % Test, "org.scala-js" %%% "scalajs-dom" % "2.2.0", "com.raquo" %%% "laminar" % "16.0.0" - ) - , scalaJSUseMainModuleInitializer := true + ), + scalaJSUseMainModuleInitializer := true ) From 5b5466119903336993dd7b418ce298f14b12664f Mon Sep 17 00:00:00 2001 From: Alessandro Mazzoli Date: Mon, 4 Sep 2023 17:18:19 +0200 Subject: [PATCH 09/17] wip: refactoring mvc lib structure --- build.sbt | 1 + src/main/scala/scatan/mvc/Example.scala | 12 +++---- .../scala/scatan/mvc/lib/Application.scala | 10 +++--- .../scatan/mvc/lib/ApplicationPage.scala | 18 +++++------ .../scala/scatan/mvc/lib/Controller.scala | 29 +++++++++-------- src/main/scala/scatan/mvc/lib/Model.scala | 7 ++-- src/main/scala/scatan/mvc/lib/Navigable.scala | 2 +- .../scala/scatan/mvc/lib/PageFactory.scala | 2 +- src/main/scala/scatan/mvc/lib/View.scala | 32 ++++++++++--------- src/test/scala/scatan/TestMain.scala | 11 ------- src/test/scala/scatan/mvc/lib/ModelTest.scala | 15 +++------ 11 files changed, 62 insertions(+), 77 deletions(-) delete mode 100644 src/test/scala/scatan/TestMain.scala diff --git a/build.sbt b/build.sbt index 36801fb5..9dbb2f90 100644 --- a/build.sbt +++ b/build.sbt @@ -4,6 +4,7 @@ lazy val scatan = (project in file(".")) .enablePlugins(ScalaJSPlugin) .settings( name := "Scatan", + scalaVersion := "3.3.0", libraryDependencies ++= Seq( "org.scalatest" %% "scalatest" % "3.3.0-SNAP4" % Test, "org.scala-js" %%% "scalajs-dom" % "2.2.0", diff --git a/src/main/scala/scatan/mvc/Example.scala b/src/main/scala/scatan/mvc/Example.scala index 31dc80bf..d271e86e 100644 --- a/src/main/scala/scatan/mvc/Example.scala +++ b/src/main/scala/scatan/mvc/Example.scala @@ -4,7 +4,7 @@ import scatan.mvc.lib.* case class MyState(value: Int) extends Model.State -class HomeView(requirements: View.Requirements[HomeController]) extends View.Interface[HomeController](requirements): +class HomeView(requirements: View.Requirements[HomeController]) extends View[HomeController](requirements): override def show(): Unit = println("HomeView.show") renderHome() @@ -16,12 +16,11 @@ class HomeView(requirements: View.Requirements[HomeController]) extends View.Int println("HomeView.renderHome") controller.controlHome() -class HomeController(requirements: Controller.Requirements[HomeView]) - extends Controller.Interface[HomeView](requirements): +class HomeController(requirements: Controller.Requirements[HomeView]) extends Controller[HomeView](requirements): def controlHome(): Unit = println("HomeController.controlHome") -class AboutView(requirements: View.Requirements[AboutController]) extends View.Interface[AboutController](requirements): +class AboutView(requirements: View.Requirements[AboutController]) extends View[AboutController](requirements): override def show(): Unit = println("AboutView.show") renderAbout() @@ -33,13 +32,12 @@ class AboutView(requirements: View.Requirements[AboutController]) extends View.I println("AboutView.renderAbout") controller.controlAbout() -class AboutController(requirements: Controller.Requirements[AboutView]) - extends Controller.Interface[AboutView](requirements): +class AboutController(requirements: Controller.Requirements[AboutView]) extends Controller[AboutView](requirements): def controlAbout(): Unit = println("AboutController.controlAbout") enum Pages(val factory: PageFactory[?, ?]): - def toMapEntry: (Pages, PageFactory[?, ?]) = this -> factory + private def toMapEntry: (Pages, PageFactory[?, ?]) = this -> factory case Home extends Pages( PageFactory( diff --git a/src/main/scala/scatan/mvc/lib/Application.scala b/src/main/scala/scatan/mvc/lib/Application.scala index b0296be1..c24e358e 100644 --- a/src/main/scala/scatan/mvc/lib/Application.scala +++ b/src/main/scala/scatan/mvc/lib/Application.scala @@ -7,12 +7,12 @@ package scatan.mvc.lib * The type of the route. */ trait Application[S <: Model.State, Route]: - val model: Model.Interface[S] + val model: Model[S] val pages: Map[Route, ApplicationPage[S, ?, ?]] object Application: /** Create an application from a model and a list of pages. - * @param model - * The model. + * @param initialState + * The initial state of the model. * @param pagesFactories * The pages. * @tparam S @@ -27,6 +27,6 @@ object Application: pagesFactories: Map[Route, PageFactory[?, ?]] ): Application[S, Route] = new Application[S, Route]: - override val model: Model.Interface[S] = Model(initialState) + override val model: Model[S] = Model(initialState) override val pages: Map[Route, ApplicationPage[S, ?, ?]] = - pagesFactories.map((k, v) => (k, ApplicationPage(model, v))) + pagesFactories.map((route, pageFactory) => (route, ApplicationPage(model, pageFactory))) diff --git a/src/main/scala/scatan/mvc/lib/ApplicationPage.scala b/src/main/scala/scatan/mvc/lib/ApplicationPage.scala index 5fb9a638..7ac4e4a4 100644 --- a/src/main/scala/scatan/mvc/lib/ApplicationPage.scala +++ b/src/main/scala/scatan/mvc/lib/ApplicationPage.scala @@ -9,13 +9,11 @@ package scatan.mvc.lib * The type of the view. * @param model * The model. - * @param viewFactory - * A factory for the view. - * @param controllerFactory - * A factory for the controller. + * @param pageFactory + * The page factory. */ -trait ApplicationPage[S <: Model.State, C <: Controller.Interface[V], V <: View.Interface[C]]( - val model: Model.Interface[S], +trait ApplicationPage[S <: Model.State, C <: Controller[V], V <: View[C]]( + val model: Model[S], val pageFactory: PageFactory[C, V] ) extends Model.Provider[S] with View.Requirements[C] @@ -24,10 +22,10 @@ trait ApplicationPage[S <: Model.State, C <: Controller.Interface[V], V <: View. override def controller: C = pageFactory.controllerFactory(this) object ApplicationPage: - type Factory[S <: Model.State, C <: Controller.Interface[V], V <: View.Interface[C]] = - Model.Interface[S] => ApplicationPage[S, ?, ?] - def apply[S <: Model.State, C <: Controller.Interface[V], V <: View.Interface[C]]( - model: Model.Interface[S], + type Factory[S <: Model.State, C <: Controller[V], V <: View[C]] = + Model[S] => ApplicationPage[S, ?, ?] + def apply[S <: Model.State, C <: Controller[V], V <: View[C]]( + model: Model[S], pageFactory: PageFactory[C, V] ): ApplicationPage[S, C, V] = new ApplicationPage[S, C, V](model, pageFactory) {} diff --git a/src/main/scala/scatan/mvc/lib/Controller.scala b/src/main/scala/scatan/mvc/lib/Controller.scala index 59b1420a..7e361c75 100644 --- a/src/main/scala/scatan/mvc/lib/Controller.scala +++ b/src/main/scala/scatan/mvc/lib/Controller.scala @@ -1,30 +1,31 @@ package scatan.mvc.lib +/** The interface for a Controller. + * + * @tparam V + * The type of the View. + * @param requirements + * The requirements for the Controller. + */ +trait Controller[V <: View[?]](requirements: Controller.Requirements[V]): + def model: Model[?] = requirements.model + + def view: V = requirements.view + /** The Controller object. */ object Controller: - type Factory[V <: View.Interface[C], C <: Controller.Interface[V]] = Controller.Requirements[V] => C + type Factory[V <: View[C], C <: Controller[V]] = Requirements[V] => C /** The requirements for a Controller. * @tparam V * The type of the View. */ - trait Requirements[V <: View.Interface[?]] extends Model.Provider[?] with View.Provider[V] - - /** The interface for a Controller. - * @tparam V - * The type of the View. - * @param requirements - * The requirements for the Controller. - */ - trait Interface[V <: View.Interface[?]](requirements: Requirements[V]): - def model: Model.Interface[?] = requirements.model - - def view: V = requirements.view + trait Requirements[V <: View[?]] extends Model.Provider[?] with View.Provider[V] /** The provider for a Controller. * @tparam C * The type of the Controller. */ - trait Provider[C <: Interface[?]]: + trait Provider[C <: Controller[?]]: def controller: C diff --git a/src/main/scala/scatan/mvc/lib/Model.scala b/src/main/scala/scatan/mvc/lib/Model.scala index 6222916f..8cb4683e 100644 --- a/src/main/scala/scatan/mvc/lib/Model.scala +++ b/src/main/scala/scatan/mvc/lib/Model.scala @@ -1,10 +1,11 @@ package scatan.mvc.lib +class Model[S <: Model.State](val state: S) + /** The Model object. It encapsulate the base model traits and the apply method to create a State-based model. */ object Model: trait State - class Interface[S <: State](val state: S) trait Provider[S <: State]: - def model: Interface[S] - def apply[S <: State](state: S): Interface[S] = Interface(state) + def model: Model[S] + def apply[S <: State](state: S): Model[S] = new Model(state) diff --git a/src/main/scala/scatan/mvc/lib/Navigable.scala b/src/main/scala/scatan/mvc/lib/Navigable.scala index 50b1c809..f526e38b 100644 --- a/src/main/scala/scatan/mvc/lib/Navigable.scala +++ b/src/main/scala/scatan/mvc/lib/Navigable.scala @@ -17,7 +17,7 @@ object NavigableApplication: pagesFactories: Map[Route, PageFactory[?, ?]] ): Application[S, Route] with Navigable[Route] = new Application[S, Route] with Navigable[Route]: - override val model: Model.Interface[S] = Model(initialState) + override val model: Model[S] = Model(initialState) override val pages: Map[Route, ApplicationPage[S, ?, ?]] = pagesFactories.map { (route, pageFactory) => route -> ApplicationPage(this.model, pageFactory) } diff --git a/src/main/scala/scatan/mvc/lib/PageFactory.scala b/src/main/scala/scatan/mvc/lib/PageFactory.scala index 98da32e8..87749370 100644 --- a/src/main/scala/scatan/mvc/lib/PageFactory.scala +++ b/src/main/scala/scatan/mvc/lib/PageFactory.scala @@ -1,6 +1,6 @@ package scatan.mvc.lib -case class PageFactory[C <: Controller.Interface[V], V <: View.Interface[C]]( +case class PageFactory[C <: Controller[V], V <: View[C]]( viewFactory: View.Factory[C, V], controllerFactory: Controller.Factory[V, C] ) diff --git a/src/main/scala/scatan/mvc/lib/View.scala b/src/main/scala/scatan/mvc/lib/View.scala index 992f0b14..a24dc15f 100644 --- a/src/main/scala/scatan/mvc/lib/View.scala +++ b/src/main/scala/scatan/mvc/lib/View.scala @@ -1,5 +1,19 @@ package scatan.mvc.lib +/** The Interface for a View. + * + * @tparam C + * The type of the Controller. + * @param requirements + * The Requirements for the View. + */ +trait View[C <: Controller[?]](requirements: View.Requirements[C]): + def controller: C = requirements.controller + + def show(): Unit + + def hide(): Unit + /** The View object. */ object View: @@ -9,31 +23,19 @@ object View: * @tparam V * The type of the View. */ - type Factory[C <: Controller.Interface[V], V <: Interface[C]] = Requirements[C] => V + type Factory[C <: Controller[V], V <: View[C]] = Requirements[C] => V /** The Requirements for a View. * * @tparam C * The type of the Controller. */ - trait Requirements[C <: Controller.Interface[?]] extends Controller.Provider[C] - - /** The Interface for a View. - * - * @tparam C - * The type of the Controller. - * @param requirements - * The Requirements for the View. - */ - trait Interface[C <: Controller.Interface[?]](requirements: Requirements[C]): - def controller: C = requirements.controller - def show(): Unit - def hide(): Unit + trait Requirements[C <: Controller[?]] extends Controller.Provider[C] /** The Provider for a View. * * @tparam V * The type of the View. */ - trait Provider[V <: Interface[?]]: + trait Provider[V <: View[?]]: def view: V diff --git a/src/test/scala/scatan/TestMain.scala b/src/test/scala/scatan/TestMain.scala deleted file mode 100644 index fae0a02e..00000000 --- a/src/test/scala/scatan/TestMain.scala +++ /dev/null @@ -1,11 +0,0 @@ -package scatan - -import org.scalatest.flatspec.AnyFlatSpec - -class TestMain extends AnyFlatSpec: - - "A dummy test that" should "pass" in - assert(1 == 1) - - "A dummy test that" should "not pass" in - assert(1 == 2) diff --git a/src/test/scala/scatan/mvc/lib/ModelTest.scala b/src/test/scala/scatan/mvc/lib/ModelTest.scala index 131382f1..1a7f4f03 100644 --- a/src/test/scala/scatan/mvc/lib/ModelTest.scala +++ b/src/test/scala/scatan/mvc/lib/ModelTest.scala @@ -9,19 +9,14 @@ class ModelTest extends AnyFlatSpec: val temp: Model.State = new Model.State {} } - it should "contains the Interface class" in { + it should "be creatable with a State" in { val state: Model.State = new Model.State {} - val temp: Model.Interface[Model.State] = new Model.Interface(state) + val model: Model[Model.State] = Model(state) + model should not be null + model.getClass should be(classOf[Model[Model.State]]) } it should "contains the Provider trait" in { val temp: Model.Provider[Model.State] = new Model.Provider[Model.State]: - override def model: Model.Interface[Model.State] = Model(new Model.State {}) - } - - it should "be creatable with a State" in { - val state: Model.State = new Model.State {} - val model: Model.Interface[Model.State] = Model(state) - model should not be null - model.getClass should be(classOf[Model.Interface[Model.State]]) + override def model: Model[Model.State] = Model(new Model.State {}) } From 25e65625a68e48b7fb46bad7a5969cb3e1d91470 Mon Sep 17 00:00:00 2001 From: Alessandro Mazzoli Date: Mon, 4 Sep 2023 18:50:46 +0200 Subject: [PATCH 10/17] wip: refactoring mvc lib structure --- src/main/scala/scatan/mvc/Example.scala | 16 ++++++++----- .../scala/scatan/mvc/lib/Controller.scala | 8 +++---- src/main/scala/scatan/mvc/lib/Model.scala | 6 ++--- .../scala/scatan/mvc/lib/PageFactory.scala | 6 ----- src/main/scala/scatan/mvc/lib/View.scala | 6 ++--- .../lib/{ => application}/Application.scala | 8 +++++-- .../{ => application}/ApplicationPage.scala | 23 +++++++++++-------- .../mvc/lib/{ => application}/Navigable.scala | 7 ++++-- .../scatan/mvc/lib/page/PageFactory.scala | 8 +++++++ src/test/scala/scatan/mvc/lib/ModelTest.scala | 3 ++- 10 files changed, 53 insertions(+), 38 deletions(-) delete mode 100644 src/main/scala/scatan/mvc/lib/PageFactory.scala rename src/main/scala/scatan/mvc/lib/{ => application}/Application.scala (85%) rename src/main/scala/scatan/mvc/lib/{ => application}/ApplicationPage.scala (54%) rename src/main/scala/scatan/mvc/lib/{ => application}/Navigable.scala (85%) create mode 100644 src/main/scala/scatan/mvc/lib/page/PageFactory.scala diff --git a/src/main/scala/scatan/mvc/Example.scala b/src/main/scala/scatan/mvc/Example.scala index d271e86e..aa6c1713 100644 --- a/src/main/scala/scatan/mvc/Example.scala +++ b/src/main/scala/scatan/mvc/Example.scala @@ -1,6 +1,8 @@ package scatan.mvc -import scatan.mvc.lib.* +import scatan.mvc.lib.{page, *} +import scatan.mvc.lib.application.NavigableApplication +import scatan.mvc.lib.page.PageFactory case class MyState(value: Int) extends Model.State @@ -16,7 +18,8 @@ class HomeView(requirements: View.Requirements[HomeController]) extends View[Hom println("HomeView.renderHome") controller.controlHome() -class HomeController(requirements: Controller.Requirements[HomeView]) extends Controller[HomeView](requirements): +class HomeController(requirements: Controller.Requirements[HomeView, MyState]) + extends Controller[HomeView, MyState](requirements): def controlHome(): Unit = println("HomeController.controlHome") @@ -32,12 +35,13 @@ class AboutView(requirements: View.Requirements[AboutController]) extends View[A println("AboutView.renderAbout") controller.controlAbout() -class AboutController(requirements: Controller.Requirements[AboutView]) extends Controller[AboutView](requirements): +class AboutController(requirements: Controller.Requirements[AboutView, MyState]) + extends Controller[AboutView, MyState](requirements): def controlAbout(): Unit = println("AboutController.controlAbout") -enum Pages(val factory: PageFactory[?, ?]): - private def toMapEntry: (Pages, PageFactory[?, ?]) = this -> factory +enum Pages(val factory: PageFactory[?, ?, MyState]): + private def toMapEntry: (Pages, PageFactory[?, ?, MyState]) = this -> factory case Home extends Pages( PageFactory( @@ -55,7 +59,7 @@ enum Pages(val factory: PageFactory[?, ?]): object Pages: // Implicit conversion from Pages enum to Map[Pages, PageFactory[?, ?]] - given Conversion[Pages.type, Map[Pages, PageFactory[?, ?]]] = _.values.map(_.toMapEntry).toMap + given Conversion[Pages.type, Map[Pages, PageFactory[?, ?, MyState]]] = _.values.map(_.toMapEntry).toMap val MyApplication = NavigableApplication[MyState, Pages]( initialState = MyState(0), diff --git a/src/main/scala/scatan/mvc/lib/Controller.scala b/src/main/scala/scatan/mvc/lib/Controller.scala index 7e361c75..e8eb4bf1 100644 --- a/src/main/scala/scatan/mvc/lib/Controller.scala +++ b/src/main/scala/scatan/mvc/lib/Controller.scala @@ -7,7 +7,7 @@ package scatan.mvc.lib * @param requirements * The requirements for the Controller. */ -trait Controller[V <: View[?]](requirements: Controller.Requirements[V]): +trait Controller[V <: View[?], S <: Model.State](requirements: Controller.Requirements[V, S]): def model: Model[?] = requirements.model def view: V = requirements.view @@ -15,17 +15,17 @@ trait Controller[V <: View[?]](requirements: Controller.Requirements[V]): /** The Controller object. */ object Controller: - type Factory[V <: View[C], C <: Controller[V]] = Requirements[V] => C + type Factory[V <: View[C], C <: Controller[V, S], S <: Model.State] = Requirements[V, S] => C /** The requirements for a Controller. * @tparam V * The type of the View. */ - trait Requirements[V <: View[?]] extends Model.Provider[?] with View.Provider[V] + trait Requirements[V <: View[?], S <: Model.State] extends Model.Provider[S] with View.Provider[V] /** The provider for a Controller. * @tparam C * The type of the Controller. */ - trait Provider[C <: Controller[?]]: + trait Provider[C <: Controller[?, ?]]: def controller: C diff --git a/src/main/scala/scatan/mvc/lib/Model.scala b/src/main/scala/scatan/mvc/lib/Model.scala index 8cb4683e..8f794aa5 100644 --- a/src/main/scala/scatan/mvc/lib/Model.scala +++ b/src/main/scala/scatan/mvc/lib/Model.scala @@ -1,11 +1,9 @@ package scatan.mvc.lib -class Model[S <: Model.State](val state: S) +trait Model[S <: Model.State](val state: S) -/** The Model object. It encapsulate the base model traits and the apply method to create a State-based model. - */ object Model: trait State trait Provider[S <: State]: def model: Model[S] - def apply[S <: State](state: S): Model[S] = new Model(state) + def apply[S <: State](state: S): Model[S] = new Model(state) {} diff --git a/src/main/scala/scatan/mvc/lib/PageFactory.scala b/src/main/scala/scatan/mvc/lib/PageFactory.scala deleted file mode 100644 index 87749370..00000000 --- a/src/main/scala/scatan/mvc/lib/PageFactory.scala +++ /dev/null @@ -1,6 +0,0 @@ -package scatan.mvc.lib - -case class PageFactory[C <: Controller[V], V <: View[C]]( - viewFactory: View.Factory[C, V], - controllerFactory: Controller.Factory[V, C] -) diff --git a/src/main/scala/scatan/mvc/lib/View.scala b/src/main/scala/scatan/mvc/lib/View.scala index a24dc15f..8594ec68 100644 --- a/src/main/scala/scatan/mvc/lib/View.scala +++ b/src/main/scala/scatan/mvc/lib/View.scala @@ -7,7 +7,7 @@ package scatan.mvc.lib * @param requirements * The Requirements for the View. */ -trait View[C <: Controller[?]](requirements: View.Requirements[C]): +trait View[C <: Controller[?, ?]](requirements: View.Requirements[C]): def controller: C = requirements.controller def show(): Unit @@ -23,14 +23,14 @@ object View: * @tparam V * The type of the View. */ - type Factory[C <: Controller[V], V <: View[C]] = Requirements[C] => V + type Factory[C <: Controller[V, ?], V <: View[C]] = Requirements[C] => V /** The Requirements for a View. * * @tparam C * The type of the Controller. */ - trait Requirements[C <: Controller[?]] extends Controller.Provider[C] + trait Requirements[C <: Controller[?, ?]] extends Controller.Provider[C] /** The Provider for a View. * diff --git a/src/main/scala/scatan/mvc/lib/Application.scala b/src/main/scala/scatan/mvc/lib/application/Application.scala similarity index 85% rename from src/main/scala/scatan/mvc/lib/Application.scala rename to src/main/scala/scatan/mvc/lib/application/Application.scala index c24e358e..5be0ec95 100644 --- a/src/main/scala/scatan/mvc/lib/Application.scala +++ b/src/main/scala/scatan/mvc/lib/application/Application.scala @@ -1,6 +1,10 @@ -package scatan.mvc.lib +package scatan.mvc.lib.application + +import scatan.mvc.lib.Model +import scatan.mvc.lib.page.PageFactory /** An application is a collection of pages that share a model. + * * @tparam S * The state type of the model. * @tparam Route @@ -24,7 +28,7 @@ object Application: */ def apply[S <: Model.State, Route]( initialState: S, - pagesFactories: Map[Route, PageFactory[?, ?]] + pagesFactories: Map[Route, PageFactory[?, ?, S]] ): Application[S, Route] = new Application[S, Route]: override val model: Model[S] = Model(initialState) diff --git a/src/main/scala/scatan/mvc/lib/ApplicationPage.scala b/src/main/scala/scatan/mvc/lib/application/ApplicationPage.scala similarity index 54% rename from src/main/scala/scatan/mvc/lib/ApplicationPage.scala rename to src/main/scala/scatan/mvc/lib/application/ApplicationPage.scala index 7ac4e4a4..ec6ac2f9 100644 --- a/src/main/scala/scatan/mvc/lib/ApplicationPage.scala +++ b/src/main/scala/scatan/mvc/lib/application/ApplicationPage.scala @@ -1,6 +1,10 @@ -package scatan.mvc.lib +package scatan.mvc.lib.application + +import scatan.mvc.lib.* +import scatan.mvc.lib.page.PageFactory /** A page of an application. It is a combination of a model, a view and a controller. + * * @tparam S * The type of the state of the model. * @tparam C @@ -12,20 +16,19 @@ package scatan.mvc.lib * @param pageFactory * The page factory. */ -trait ApplicationPage[S <: Model.State, C <: Controller[V], V <: View[C]]( +trait ApplicationPage[S <: Model.State, C <: Controller[V, S], V <: View[C]]( val model: Model[S], - val pageFactory: PageFactory[C, V] -) extends Model.Provider[S] - with View.Requirements[C] - with Controller.Requirements[V]: + val pageFactory: PageFactory[C, V, S] +) extends View.Requirements[C] + with Controller.Requirements[V, S]: override def view: V = pageFactory.viewFactory(this) override def controller: C = pageFactory.controllerFactory(this) object ApplicationPage: - type Factory[S <: Model.State, C <: Controller[V], V <: View[C]] = - Model[S] => ApplicationPage[S, ?, ?] - def apply[S <: Model.State, C <: Controller[V], V <: View[C]]( + type Factory[S <: Model.State, C <: Controller[V, S], V <: View[C]] = + Model[S] => ApplicationPage[S, C, V] + def apply[S <: Model.State, C <: Controller[V, S], V <: View[C]]( model: Model[S], - pageFactory: PageFactory[C, V] + pageFactory: PageFactory[C, V, S] ): ApplicationPage[S, C, V] = new ApplicationPage[S, C, V](model, pageFactory) {} diff --git a/src/main/scala/scatan/mvc/lib/Navigable.scala b/src/main/scala/scatan/mvc/lib/application/Navigable.scala similarity index 85% rename from src/main/scala/scatan/mvc/lib/Navigable.scala rename to src/main/scala/scatan/mvc/lib/application/Navigable.scala index f526e38b..0df926cb 100644 --- a/src/main/scala/scatan/mvc/lib/Navigable.scala +++ b/src/main/scala/scatan/mvc/lib/application/Navigable.scala @@ -1,4 +1,7 @@ -package scatan.mvc.lib +package scatan.mvc.lib.application + +import scatan.mvc.lib.* +import scatan.mvc.lib.page.PageFactory trait Navigable[Route] extends Application[?, Route]: private var pagesHistory: Seq[Route] = Seq.empty @@ -14,7 +17,7 @@ trait Navigable[Route] extends Application[?, Route]: object NavigableApplication: def apply[S <: Model.State, Route]( initialState: S, - pagesFactories: Map[Route, PageFactory[?, ?]] + pagesFactories: Map[Route, PageFactory[?, ?, S]] ): Application[S, Route] with Navigable[Route] = new Application[S, Route] with Navigable[Route]: override val model: Model[S] = Model(initialState) diff --git a/src/main/scala/scatan/mvc/lib/page/PageFactory.scala b/src/main/scala/scatan/mvc/lib/page/PageFactory.scala new file mode 100644 index 00000000..0bed6bba --- /dev/null +++ b/src/main/scala/scatan/mvc/lib/page/PageFactory.scala @@ -0,0 +1,8 @@ +package scatan.mvc.lib.page + +import scatan.mvc.lib.{Controller, Model, View} + +case class PageFactory[C <: Controller[V, S], V <: View[C], S <: Model.State]( + viewFactory: View.Factory[C, V], + controllerFactory: Controller.Factory[V, C, S] +) diff --git a/src/test/scala/scatan/mvc/lib/ModelTest.scala b/src/test/scala/scatan/mvc/lib/ModelTest.scala index 1a7f4f03..990d61b5 100644 --- a/src/test/scala/scatan/mvc/lib/ModelTest.scala +++ b/src/test/scala/scatan/mvc/lib/ModelTest.scala @@ -13,7 +13,8 @@ class ModelTest extends AnyFlatSpec: val state: Model.State = new Model.State {} val model: Model[Model.State] = Model(state) model should not be null - model.getClass should be(classOf[Model[Model.State]]) + model.state should be(state) + model.isInstanceOf[Model[?]] should be(true) } it should "contains the Provider trait" in { From 2a5812f0e88664df75c44d0c5cd112c88b35ebed Mon Sep 17 00:00:00 2001 From: Alessandro Mazzoli Date: Mon, 4 Sep 2023 18:57:35 +0200 Subject: [PATCH 11/17] wip: refactoring mvc lib structure --- src/main/scala/scatan/mvc/lib/application/Application.scala | 3 ++- src/main/scala/scatan/mvc/lib/application/Navigable.scala | 2 +- .../scatan/mvc/lib/{application => page}/ApplicationPage.scala | 3 +-- 3 files changed, 4 insertions(+), 4 deletions(-) rename src/main/scala/scatan/mvc/lib/{application => page}/ApplicationPage.scala (93%) diff --git a/src/main/scala/scatan/mvc/lib/application/Application.scala b/src/main/scala/scatan/mvc/lib/application/Application.scala index 5be0ec95..783b157b 100644 --- a/src/main/scala/scatan/mvc/lib/application/Application.scala +++ b/src/main/scala/scatan/mvc/lib/application/Application.scala @@ -1,7 +1,7 @@ package scatan.mvc.lib.application import scatan.mvc.lib.Model -import scatan.mvc.lib.page.PageFactory +import scatan.mvc.lib.page.{ApplicationPage, PageFactory} /** An application is a collection of pages that share a model. * @@ -13,6 +13,7 @@ import scatan.mvc.lib.page.PageFactory trait Application[S <: Model.State, Route]: val model: Model[S] val pages: Map[Route, ApplicationPage[S, ?, ?]] + object Application: /** Create an application from a model and a list of pages. * @param initialState diff --git a/src/main/scala/scatan/mvc/lib/application/Navigable.scala b/src/main/scala/scatan/mvc/lib/application/Navigable.scala index 0df926cb..0c9155e0 100644 --- a/src/main/scala/scatan/mvc/lib/application/Navigable.scala +++ b/src/main/scala/scatan/mvc/lib/application/Navigable.scala @@ -1,7 +1,7 @@ package scatan.mvc.lib.application import scatan.mvc.lib.* -import scatan.mvc.lib.page.PageFactory +import scatan.mvc.lib.page.{ApplicationPage, PageFactory} trait Navigable[Route] extends Application[?, Route]: private var pagesHistory: Seq[Route] = Seq.empty diff --git a/src/main/scala/scatan/mvc/lib/application/ApplicationPage.scala b/src/main/scala/scatan/mvc/lib/page/ApplicationPage.scala similarity index 93% rename from src/main/scala/scatan/mvc/lib/application/ApplicationPage.scala rename to src/main/scala/scatan/mvc/lib/page/ApplicationPage.scala index ec6ac2f9..84123974 100644 --- a/src/main/scala/scatan/mvc/lib/application/ApplicationPage.scala +++ b/src/main/scala/scatan/mvc/lib/page/ApplicationPage.scala @@ -1,7 +1,6 @@ -package scatan.mvc.lib.application +package scatan.mvc.lib.page import scatan.mvc.lib.* -import scatan.mvc.lib.page.PageFactory /** A page of an application. It is a combination of a model, a view and a controller. * From 8e8dae4e3aac825963fddf481d45701c4cacdee4 Mon Sep 17 00:00:00 2001 From: Alessandro Mazzoli Date: Tue, 5 Sep 2023 11:21:34 +0200 Subject: [PATCH 12/17] First Scalajs App --- src/main/scala/scatan/Main.scala | 74 ++++++++++++++----- src/main/scala/scatan/mvc/Example.scala | 73 ------------------ .../scala/scatan/mvc/lib/Controller.scala | 3 +- src/main/scala/scatan/mvc/lib/Model.scala | 2 +- .../mvc/lib/NavigableApplicationManager.scala | 16 ++++ .../scala/scatan/mvc/lib/ScalajsView.scala | 16 ++++ src/main/scala/scatan/mvc/lib/View.scala | 2 - .../mvc/lib/application/Navigable.scala | 6 +- .../scatan/mvc/lib/page/ApplicationPage.scala | 8 +- 9 files changed, 100 insertions(+), 100 deletions(-) delete mode 100644 src/main/scala/scatan/mvc/Example.scala create mode 100644 src/main/scala/scatan/mvc/lib/NavigableApplicationManager.scala create mode 100644 src/main/scala/scatan/mvc/lib/ScalajsView.scala diff --git a/src/main/scala/scatan/Main.scala b/src/main/scala/scatan/Main.scala index 5062156d..d4697c03 100644 --- a/src/main/scala/scatan/Main.scala +++ b/src/main/scala/scatan/Main.scala @@ -1,23 +1,63 @@ package scatan import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom +import scatan.mvc.lib.application.NavigableApplication +import scatan.mvc.lib.page.PageFactory +import scatan.mvc.lib.{Controller, Model, NavigableApplicationManager, ScalajsView, View} -def appElement(): Element = - div( - h1("Hello Laminar!"), +import scala.util.Random + +// Model +case class CounterAppState(counter: Int) extends Model.State + +// View +class HomeView(requirements: View.Requirements[HomeController], container: String) + extends ScalajsView[HomeController](requirements, container): + + private val reactiveCounter = Var(initial = 0) + + def onCounterUpdate(): Unit = + println("Counter updated: " + controller.counter) + reactiveCounter.set(controller.counter) + println("Reactive counter updated: " + reactiveCounter.now()) + + override def element: Element = div( - className := "card", + h1("Hello, world!"), + p( + child.text <-- reactiveCounter.signal.map(_.toString) + ), button( - className := "button", - "Click me!", - tpe := "button" + "Increment", + onClick --> (_ => controller.increment()) + ) + ) + +// Controller +class HomeController(requirements: Controller.Requirements[HomeView, CounterAppState]) + extends Controller[HomeView, CounterAppState](requirements): + + def counter: Int = model.state.counter + + def increment(): Unit = + println("Incrementing counter in controller") + model.state = model.state.copy(counter = model.state.counter + 1) + view.onCounterUpdate() + +// Route +enum Pages(val pageFactory: PageFactory[?, ?, CounterAppState]): + case Home + extends Pages( + PageFactory( + viewFactory = new HomeView(_, "root"), + controllerFactory = new HomeController(_) + ) ) - ), - p(className := "read-the-docs", "Click on the Vite logo to learn more") - ) -@main -def main(): Unit = - val containerNode = dom.document.querySelector("#root") - - // this is how you render the rootElement in the browser - render(containerNode, appElement()) + +// Application +val CounterApplication: NavigableApplication[CounterAppState, Pages] = NavigableApplication[CounterAppState, Pages]( + initialState = CounterAppState(0), + pagesFactories = Pages.values.map(p => p -> p.pageFactory).toMap +) + +@main def main(): Unit = + NavigableApplicationManager.startApplication(CounterApplication, Pages.Home) diff --git a/src/main/scala/scatan/mvc/Example.scala b/src/main/scala/scatan/mvc/Example.scala deleted file mode 100644 index aa6c1713..00000000 --- a/src/main/scala/scatan/mvc/Example.scala +++ /dev/null @@ -1,73 +0,0 @@ -package scatan.mvc - -import scatan.mvc.lib.{page, *} -import scatan.mvc.lib.application.NavigableApplication -import scatan.mvc.lib.page.PageFactory - -case class MyState(value: Int) extends Model.State - -class HomeView(requirements: View.Requirements[HomeController]) extends View[HomeController](requirements): - override def show(): Unit = - println("HomeView.show") - renderHome() - - override def hide(): Unit = - println("HomeView.hide") - - def renderHome(): Unit = - println("HomeView.renderHome") - controller.controlHome() - -class HomeController(requirements: Controller.Requirements[HomeView, MyState]) - extends Controller[HomeView, MyState](requirements): - def controlHome(): Unit = - println("HomeController.controlHome") - -class AboutView(requirements: View.Requirements[AboutController]) extends View[AboutController](requirements): - override def show(): Unit = - println("AboutView.show") - renderAbout() - - override def hide(): Unit = - println("AboutView.hide") - - def renderAbout(): Unit = - println("AboutView.renderAbout") - controller.controlAbout() - -class AboutController(requirements: Controller.Requirements[AboutView, MyState]) - extends Controller[AboutView, MyState](requirements): - def controlAbout(): Unit = - println("AboutController.controlAbout") - -enum Pages(val factory: PageFactory[?, ?, MyState]): - private def toMapEntry: (Pages, PageFactory[?, ?, MyState]) = this -> factory - case Home - extends Pages( - PageFactory( - viewFactory = new HomeView(_), - controllerFactory = new HomeController(_) - ) - ) - case About - extends Pages( - PageFactory( - viewFactory = new AboutView(_), - controllerFactory = new AboutController(_) - ) - ) - -object Pages: - // Implicit conversion from Pages enum to Map[Pages, PageFactory[?, ?]] - given Conversion[Pages.type, Map[Pages, PageFactory[?, ?, MyState]]] = _.values.map(_.toMapEntry).toMap - -val MyApplication = NavigableApplication[MyState, Pages]( - initialState = MyState(0), - pagesFactories = Pages -) - -@main def run(): Unit = - val app = MyApplication - app.show(Pages.Home) - app.show(Pages.About) - app.back() diff --git a/src/main/scala/scatan/mvc/lib/Controller.scala b/src/main/scala/scatan/mvc/lib/Controller.scala index e8eb4bf1..c725c9cf 100644 --- a/src/main/scala/scatan/mvc/lib/Controller.scala +++ b/src/main/scala/scatan/mvc/lib/Controller.scala @@ -8,8 +8,7 @@ package scatan.mvc.lib * The requirements for the Controller. */ trait Controller[V <: View[?], S <: Model.State](requirements: Controller.Requirements[V, S]): - def model: Model[?] = requirements.model - + def model: Model[S] = requirements.model def view: V = requirements.view /** The Controller object. diff --git a/src/main/scala/scatan/mvc/lib/Model.scala b/src/main/scala/scatan/mvc/lib/Model.scala index 8f794aa5..be2b525f 100644 --- a/src/main/scala/scatan/mvc/lib/Model.scala +++ b/src/main/scala/scatan/mvc/lib/Model.scala @@ -1,6 +1,6 @@ package scatan.mvc.lib -trait Model[S <: Model.State](val state: S) +trait Model[S <: Model.State](var state: S) object Model: trait State diff --git a/src/main/scala/scatan/mvc/lib/NavigableApplicationManager.scala b/src/main/scala/scatan/mvc/lib/NavigableApplicationManager.scala new file mode 100644 index 00000000..b478c67a --- /dev/null +++ b/src/main/scala/scatan/mvc/lib/NavigableApplicationManager.scala @@ -0,0 +1,16 @@ +package scatan.mvc.lib + +import scatan.mvc.lib.application.{Application, NavigableApplication} + +object NavigableApplicationManager: + private var _application: Option[NavigableApplication[?, ?]] = None + + def startApplication[Route](application: NavigableApplication[?, Route], initialRoute: Route): Unit = + _application = Some(application) + application.show(initialRoute) + + def navigateTo[Route](route: Route): Unit = + _application.foreach(_.asInstanceOf[NavigableApplication[?, Route]].show(route)) + + def navigateBack(): Unit = + _application.foreach(_.back()) diff --git a/src/main/scala/scatan/mvc/lib/ScalajsView.scala b/src/main/scala/scatan/mvc/lib/ScalajsView.scala new file mode 100644 index 00000000..c07cc51d --- /dev/null +++ b/src/main/scala/scatan/mvc/lib/ScalajsView.scala @@ -0,0 +1,16 @@ +package scatan.mvc.lib + +import org.scalajs.dom +import com.raquo.laminar.api.L.* + +abstract class ScalajsView[C <: Controller[?, ?]](requirements: View.Requirements[C], val container: String) + extends View[C](requirements): + def element: Element + + override def show(): Unit = + val containerElement = dom.document.getElementById(container) + render(containerElement, element) + + override def hide(): Unit = + val containerElement = dom.document.getElementById(container) + render(containerElement, div()) diff --git a/src/main/scala/scatan/mvc/lib/View.scala b/src/main/scala/scatan/mvc/lib/View.scala index 8594ec68..73220fd7 100644 --- a/src/main/scala/scatan/mvc/lib/View.scala +++ b/src/main/scala/scatan/mvc/lib/View.scala @@ -9,9 +9,7 @@ package scatan.mvc.lib */ trait View[C <: Controller[?, ?]](requirements: View.Requirements[C]): def controller: C = requirements.controller - def show(): Unit - def hide(): Unit /** The View object. diff --git a/src/main/scala/scatan/mvc/lib/application/Navigable.scala b/src/main/scala/scatan/mvc/lib/application/Navigable.scala index 0c9155e0..e4104630 100644 --- a/src/main/scala/scatan/mvc/lib/application/Navigable.scala +++ b/src/main/scala/scatan/mvc/lib/application/Navigable.scala @@ -14,12 +14,14 @@ trait Navigable[Route] extends Application[?, Route]: pagesHistory = pagesHistory.dropRight(1) pagesHistory.lastOption.foreach(pages(_).view.show()) +trait NavigableApplication[S <: Model.State, Route] extends Application[S, Route] with Navigable[Route] + object NavigableApplication: def apply[S <: Model.State, Route]( initialState: S, pagesFactories: Map[Route, PageFactory[?, ?, S]] - ): Application[S, Route] with Navigable[Route] = - new Application[S, Route] with Navigable[Route]: + ): NavigableApplication[S, Route] = + new NavigableApplication[S, Route]: override val model: Model[S] = Model(initialState) override val pages: Map[Route, ApplicationPage[S, ?, ?]] = pagesFactories.map { (route, pageFactory) => route -> ApplicationPage(this.model, pageFactory) diff --git a/src/main/scala/scatan/mvc/lib/page/ApplicationPage.scala b/src/main/scala/scatan/mvc/lib/page/ApplicationPage.scala index 84123974..15f17d3e 100644 --- a/src/main/scala/scatan/mvc/lib/page/ApplicationPage.scala +++ b/src/main/scala/scatan/mvc/lib/page/ApplicationPage.scala @@ -16,12 +16,14 @@ import scatan.mvc.lib.* * The page factory. */ trait ApplicationPage[S <: Model.State, C <: Controller[V, S], V <: View[C]]( - val model: Model[S], + override val model: Model[S], val pageFactory: PageFactory[C, V, S] ) extends View.Requirements[C] with Controller.Requirements[V, S]: - override def view: V = pageFactory.viewFactory(this) - override def controller: C = pageFactory.controllerFactory(this) + private lazy val _controller: C = pageFactory.controllerFactory(this) + private lazy val _view: V = pageFactory.viewFactory(this) + override def controller: C = _controller + override def view: V = _view object ApplicationPage: type Factory[S <: Model.State, C <: Controller[V, S], V <: View[C]] = From 8509a6d5f658018f347aff36d74c2ddd72f42f64 Mon Sep 17 00:00:00 2001 From: Alessandro Mazzoli Date: Tue, 5 Sep 2023 14:55:53 +0200 Subject: [PATCH 13/17] Refactored to Mixin --- src/main/scala/scatan/Main.scala | 48 +++---------------- .../example/controller/HomeController.scala | 6 +++ .../controller/HomeControllerImpl.scala | 14 ++++++ .../example/model/CounterAppState.scala | 5 ++ .../scala/scatan/example/view/HomeView.scala | 6 +++ .../scatan/example/view/ScalaJsHomeView.scala | 15 ++++++ .../scala/scatan/mvc/lib/Controller.scala | 30 ++++-------- src/main/scala/scatan/mvc/lib/Model.scala | 7 ++- .../lib/{ScalajsView.scala => ScalaJS.scala} | 3 +- src/main/scala/scatan/mvc/lib/View.scala | 35 +++----------- .../scatan/mvc/lib/page/ApplicationPage.scala | 10 ++-- .../scatan/mvc/lib/page/PageFactory.scala | 2 +- 12 files changed, 81 insertions(+), 100 deletions(-) create mode 100644 src/main/scala/scatan/example/controller/HomeController.scala create mode 100644 src/main/scala/scatan/example/controller/HomeControllerImpl.scala create mode 100644 src/main/scala/scatan/example/model/CounterAppState.scala create mode 100644 src/main/scala/scatan/example/view/HomeView.scala create mode 100644 src/main/scala/scatan/example/view/ScalaJsHomeView.scala rename src/main/scala/scatan/mvc/lib/{ScalajsView.scala => ScalaJS.scala} (72%) diff --git a/src/main/scala/scatan/Main.scala b/src/main/scala/scatan/Main.scala index d4697c03..8ec8a487 100644 --- a/src/main/scala/scatan/Main.scala +++ b/src/main/scala/scatan/Main.scala @@ -1,55 +1,21 @@ package scatan import com.raquo.laminar.api.L.{*, given} +import scatan.example.controller.{HomeController, HomeControllerImpl} +import scatan.example.model.CounterAppState +import scatan.example.view.{HomeView, ScalaJsHomeView} import scatan.mvc.lib.application.NavigableApplication import scatan.mvc.lib.page.PageFactory -import scatan.mvc.lib.{Controller, Model, NavigableApplicationManager, ScalajsView, View} +import scatan.mvc.lib.{Controller, Model, NavigableApplicationManager, ScalaJS} import scala.util.Random -// Model -case class CounterAppState(counter: Int) extends Model.State - -// View -class HomeView(requirements: View.Requirements[HomeController], container: String) - extends ScalajsView[HomeController](requirements, container): - - private val reactiveCounter = Var(initial = 0) - - def onCounterUpdate(): Unit = - println("Counter updated: " + controller.counter) - reactiveCounter.set(controller.counter) - println("Reactive counter updated: " + reactiveCounter.now()) - - override def element: Element = - div( - h1("Hello, world!"), - p( - child.text <-- reactiveCounter.signal.map(_.toString) - ), - button( - "Increment", - onClick --> (_ => controller.increment()) - ) - ) - -// Controller -class HomeController(requirements: Controller.Requirements[HomeView, CounterAppState]) - extends Controller[HomeView, CounterAppState](requirements): - - def counter: Int = model.state.counter - - def increment(): Unit = - println("Incrementing counter in controller") - model.state = model.state.copy(counter = model.state.counter + 1) - view.onCounterUpdate() - // Route enum Pages(val pageFactory: PageFactory[?, ?, CounterAppState]): case Home extends Pages( - PageFactory( - viewFactory = new HomeView(_, "root"), - controllerFactory = new HomeController(_) + PageFactory[HomeController, HomeView, CounterAppState]( + viewFactory = new ScalaJsHomeView(_, "root"), + controllerFactory = new HomeControllerImpl(_) ) ) diff --git a/src/main/scala/scatan/example/controller/HomeController.scala b/src/main/scala/scatan/example/controller/HomeController.scala new file mode 100644 index 00000000..dbf1681f --- /dev/null +++ b/src/main/scala/scatan/example/controller/HomeController.scala @@ -0,0 +1,6 @@ +package scatan.example.controller + +import scatan.mvc.lib.Controller + +trait HomeController extends Controller: + def increment(): Unit diff --git a/src/main/scala/scatan/example/controller/HomeControllerImpl.scala b/src/main/scala/scatan/example/controller/HomeControllerImpl.scala new file mode 100644 index 00000000..a56151e6 --- /dev/null +++ b/src/main/scala/scatan/example/controller/HomeControllerImpl.scala @@ -0,0 +1,14 @@ +package scatan.example.controller + +import scatan.example.model.CounterAppState +import scatan.example.view +import scatan.example.view.HomeView +import scatan.mvc.lib.{Controller, Model, ScalaJS, View} + +class HomeControllerImpl(requirements: Controller.Requirements[HomeView, CounterAppState]) + extends HomeController + with Controller.Dependencies(requirements): + override def increment(): Unit = + this.model.update { m => + m.copy(count = m.count + 1) + } diff --git a/src/main/scala/scatan/example/model/CounterAppState.scala b/src/main/scala/scatan/example/model/CounterAppState.scala new file mode 100644 index 00000000..ddd29a53 --- /dev/null +++ b/src/main/scala/scatan/example/model/CounterAppState.scala @@ -0,0 +1,5 @@ +package scatan.example.model + +import scatan.mvc.lib.Model + +case class CounterAppState(count: Int) extends Model.State diff --git a/src/main/scala/scatan/example/view/HomeView.scala b/src/main/scala/scatan/example/view/HomeView.scala new file mode 100644 index 00000000..4da69d55 --- /dev/null +++ b/src/main/scala/scatan/example/view/HomeView.scala @@ -0,0 +1,6 @@ +package scatan.example.view + +import scatan.mvc.lib.{ScalaJS, View} + +trait HomeView extends View: + def onCounterUpdated(counter: Int): Unit diff --git a/src/main/scala/scatan/example/view/ScalaJsHomeView.scala b/src/main/scala/scatan/example/view/ScalaJsHomeView.scala new file mode 100644 index 00000000..b3c4c8a7 --- /dev/null +++ b/src/main/scala/scatan/example/view/ScalaJsHomeView.scala @@ -0,0 +1,15 @@ +package scatan.example.view + +import com.raquo.laminar.api.L +import scatan.example.controller.HomeController +import scatan.mvc.lib.{ScalaJS, View} + +class ScalaJsHomeView(requirements: View.Requirements[HomeController], container: String) + extends HomeView + with View.Dependencies(requirements) + with ScalaJS(container): + + override def element: L.Element = + ??? + override def onCounterUpdated(counter: Int): Unit = + ??? diff --git a/src/main/scala/scatan/mvc/lib/Controller.scala b/src/main/scala/scatan/mvc/lib/Controller.scala index c725c9cf..68942e52 100644 --- a/src/main/scala/scatan/mvc/lib/Controller.scala +++ b/src/main/scala/scatan/mvc/lib/Controller.scala @@ -1,30 +1,18 @@ package scatan.mvc.lib -/** The interface for a Controller. - * - * @tparam V - * The type of the View. - * @param requirements - * The requirements for the Controller. - */ -trait Controller[V <: View[?], S <: Model.State](requirements: Controller.Requirements[V, S]): - def model: Model[S] = requirements.model - def view: V = requirements.view +import scatan.mvc.lib + +trait Controller /** The Controller object. */ object Controller: - type Factory[V <: View[C], C <: Controller[V, S], S <: Model.State] = Requirements[V, S] => C + type Factory[V <: View, C <: Controller, S <: Model.State] = Requirements[V, S] => C + trait Requirements[V <: View, S <: Model.State] extends Model.Provider[S] with View.Provider[V] - /** The requirements for a Controller. - * @tparam V - * The type of the View. - */ - trait Requirements[V <: View[?], S <: Model.State] extends Model.Provider[S] with View.Provider[V] + trait Dependencies[V <: View, S <: Model.State](requirements: Requirements[V, S]) extends Controller: + protected def view: V = requirements.view + protected def model: Model[S] = requirements.model - /** The provider for a Controller. - * @tparam C - * The type of the Controller. - */ - trait Provider[C <: Controller[?, ?]]: + trait Provider[C <: Controller]: def controller: C diff --git a/src/main/scala/scatan/mvc/lib/Model.scala b/src/main/scala/scatan/mvc/lib/Model.scala index be2b525f..664d9acf 100644 --- a/src/main/scala/scatan/mvc/lib/Model.scala +++ b/src/main/scala/scatan/mvc/lib/Model.scala @@ -1,9 +1,12 @@ package scatan.mvc.lib -trait Model[S <: Model.State](var state: S) +trait Model[S <: Model.State](private var _state: S): + def state: S = _state + def update(f: S => S): Unit = _state = f(_state) object Model: trait State + def apply[S <: State](state: S): Model[S] = new Model(state) {} + trait Provider[S <: State]: def model: Model[S] - def apply[S <: State](state: S): Model[S] = new Model(state) {} diff --git a/src/main/scala/scatan/mvc/lib/ScalajsView.scala b/src/main/scala/scatan/mvc/lib/ScalaJS.scala similarity index 72% rename from src/main/scala/scatan/mvc/lib/ScalajsView.scala rename to src/main/scala/scatan/mvc/lib/ScalaJS.scala index c07cc51d..4fbe9821 100644 --- a/src/main/scala/scatan/mvc/lib/ScalajsView.scala +++ b/src/main/scala/scatan/mvc/lib/ScalaJS.scala @@ -3,8 +3,7 @@ package scatan.mvc.lib import org.scalajs.dom import com.raquo.laminar.api.L.* -abstract class ScalajsView[C <: Controller[?, ?]](requirements: View.Requirements[C], val container: String) - extends View[C](requirements): +trait ScalaJS(val container: String) extends View: def element: Element override def show(): Unit = diff --git a/src/main/scala/scatan/mvc/lib/View.scala b/src/main/scala/scatan/mvc/lib/View.scala index 73220fd7..4e2e80f0 100644 --- a/src/main/scala/scatan/mvc/lib/View.scala +++ b/src/main/scala/scatan/mvc/lib/View.scala @@ -1,39 +1,18 @@ package scatan.mvc.lib -/** The Interface for a View. - * - * @tparam C - * The type of the Controller. - * @param requirements - * The Requirements for the View. - */ -trait View[C <: Controller[?, ?]](requirements: View.Requirements[C]): - def controller: C = requirements.controller +trait View: def show(): Unit def hide(): Unit /** The View object. */ object View: - /** The Factory for a View. - * @tparam C - * The type of the Controller. - * @tparam V - * The type of the View. - */ - type Factory[C <: Controller[V, ?], V <: View[C]] = Requirements[C] => V + type Factory[C <: Controller, V <: View] = Requirements[C] => V + + trait Requirements[C <: Controller] extends Controller.Provider[C] - /** The Requirements for a View. - * - * @tparam C - * The type of the Controller. - */ - trait Requirements[C <: Controller[?, ?]] extends Controller.Provider[C] + trait Dependencies[C <: Controller](requirements: Requirements[C]) extends View: + protected def controller: C = requirements.controller - /** The Provider for a View. - * - * @tparam V - * The type of the View. - */ - trait Provider[V <: View[?]]: + trait Provider[V <: View]: def view: V diff --git a/src/main/scala/scatan/mvc/lib/page/ApplicationPage.scala b/src/main/scala/scatan/mvc/lib/page/ApplicationPage.scala index 15f17d3e..41748193 100644 --- a/src/main/scala/scatan/mvc/lib/page/ApplicationPage.scala +++ b/src/main/scala/scatan/mvc/lib/page/ApplicationPage.scala @@ -15,20 +15,20 @@ import scatan.mvc.lib.* * @param pageFactory * The page factory. */ -trait ApplicationPage[S <: Model.State, C <: Controller[V, S], V <: View[C]]( +trait ApplicationPage[S <: Model.State, C <: Controller, V <: View]( override val model: Model[S], val pageFactory: PageFactory[C, V, S] -) extends View.Requirements[C] - with Controller.Requirements[V, S]: +) extends View.Requirements[C], + Controller.Requirements[V, S]: private lazy val _controller: C = pageFactory.controllerFactory(this) private lazy val _view: V = pageFactory.viewFactory(this) override def controller: C = _controller override def view: V = _view object ApplicationPage: - type Factory[S <: Model.State, C <: Controller[V, S], V <: View[C]] = + type Factory[S <: Model.State, C <: Controller, V <: View] = Model[S] => ApplicationPage[S, C, V] - def apply[S <: Model.State, C <: Controller[V, S], V <: View[C]]( + def apply[S <: Model.State, C <: Controller, V <: View]( model: Model[S], pageFactory: PageFactory[C, V, S] ): ApplicationPage[S, C, V] = diff --git a/src/main/scala/scatan/mvc/lib/page/PageFactory.scala b/src/main/scala/scatan/mvc/lib/page/PageFactory.scala index 0bed6bba..4728bbbb 100644 --- a/src/main/scala/scatan/mvc/lib/page/PageFactory.scala +++ b/src/main/scala/scatan/mvc/lib/page/PageFactory.scala @@ -2,7 +2,7 @@ package scatan.mvc.lib.page import scatan.mvc.lib.{Controller, Model, View} -case class PageFactory[C <: Controller[V, S], V <: View[C], S <: Model.State]( +case class PageFactory[C <: Controller, V <: View, S <: Model.State]( viewFactory: View.Factory[C, V], controllerFactory: Controller.Factory[V, C, S] ) From 37d4cadb27ae38e155d8478921bd2c2d29f58a00 Mon Sep 17 00:00:00 2001 From: Alessandro Mazzoli Date: Tue, 5 Sep 2023 15:16:31 +0200 Subject: [PATCH 14/17] feat: completed first counter app using MixinMVC --- .../example/controller/HomeController.scala | 1 + .../controller/HomeControllerImpl.scala | 3 +++ .../scatan/example/view/ScalaJsHomeView.scala | 19 +++++++++++++++---- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/main/scala/scatan/example/controller/HomeController.scala b/src/main/scala/scatan/example/controller/HomeController.scala index dbf1681f..2a33b31c 100644 --- a/src/main/scala/scatan/example/controller/HomeController.scala +++ b/src/main/scala/scatan/example/controller/HomeController.scala @@ -3,4 +3,5 @@ package scatan.example.controller import scatan.mvc.lib.Controller trait HomeController extends Controller: + def counter: Int def increment(): Unit diff --git a/src/main/scala/scatan/example/controller/HomeControllerImpl.scala b/src/main/scala/scatan/example/controller/HomeControllerImpl.scala index a56151e6..104463aa 100644 --- a/src/main/scala/scatan/example/controller/HomeControllerImpl.scala +++ b/src/main/scala/scatan/example/controller/HomeControllerImpl.scala @@ -8,7 +8,10 @@ import scatan.mvc.lib.{Controller, Model, ScalaJS, View} class HomeControllerImpl(requirements: Controller.Requirements[HomeView, CounterAppState]) extends HomeController with Controller.Dependencies(requirements): + + override def counter: Int = this.model.state.count override def increment(): Unit = this.model.update { m => m.copy(count = m.count + 1) } + this.view.onCounterUpdated(this.model.state.count) diff --git a/src/main/scala/scatan/example/view/ScalaJsHomeView.scala b/src/main/scala/scatan/example/view/ScalaJsHomeView.scala index b3c4c8a7..04abdbf4 100644 --- a/src/main/scala/scatan/example/view/ScalaJsHomeView.scala +++ b/src/main/scala/scatan/example/view/ScalaJsHomeView.scala @@ -1,6 +1,6 @@ package scatan.example.view -import com.raquo.laminar.api.L +import com.raquo.laminar.api.L.* import scatan.example.controller.HomeController import scatan.mvc.lib.{ScalaJS, View} @@ -9,7 +9,18 @@ class ScalaJsHomeView(requirements: View.Requirements[HomeController], container with View.Dependencies(requirements) with ScalaJS(container): - override def element: L.Element = - ??? + private val reactiveCounter = Var(this.controller.counter) + + override def element: Element = + div( + h1("Scala.js Home"), + p("This is a Scala.js view"), + p("The counter is: ", child.text <-- reactiveCounter.signal), + button( + "Increment", + onClick --> (_ => controller.increment()) + ) + ) + override def onCounterUpdated(counter: Int): Unit = - ??? + reactiveCounter.set(counter) From c8a6f70c4f03f080c82ed2a46393ad46bbb081ae Mon Sep 17 00:00:00 2001 From: Alessandro Mazzoli Date: Wed, 6 Sep 2023 09:54:30 +0200 Subject: [PATCH 15/17] wip: refactoring mvc lib structure --- src/main/scala/scatan/Main.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/scala/scatan/Main.scala b/src/main/scala/scatan/Main.scala index 8ec8a487..9554bba6 100644 --- a/src/main/scala/scatan/Main.scala +++ b/src/main/scala/scatan/Main.scala @@ -25,5 +25,7 @@ val CounterApplication: NavigableApplication[CounterAppState, Pages] = Navigable pagesFactories = Pages.values.map(p => p -> p.pageFactory).toMap ) +/* @main def main(): Unit = NavigableApplicationManager.startApplication(CounterApplication, Pages.Home) +*/ \ No newline at end of file From b22f1cf4929cc5b02788cf67cd3ae757f884fd8c Mon Sep 17 00:00:00 2001 From: Alessandro Mazzoli Date: Wed, 6 Sep 2023 12:25:45 +0200 Subject: [PATCH 16/17] feat: completed first version of mvc structure with demo application --- src/main/scala/scatan/Main.scala | 16 +++++++++---- .../example/controller/AboutController.scala | 15 ++++++++++++ .../controller/HomeControllerImpl.scala | 2 +- .../scala/scatan/example/view/AboutView.scala | 24 +++++++++++++++++++ .../scala/scatan/example/view/HomeView.scala | 2 +- .../scatan/example/view/ScalaJsHomeView.scala | 11 ++++++--- .../lib/{ScalaJS.scala => ScalaJSView.scala} | 4 +++- .../scatan/mvc/lib/page/ApplicationPage.scala | 3 +-- 8 files changed, 64 insertions(+), 13 deletions(-) create mode 100644 src/main/scala/scatan/example/controller/AboutController.scala create mode 100644 src/main/scala/scatan/example/view/AboutView.scala rename src/main/scala/scatan/mvc/lib/{ScalaJS.scala => ScalaJSView.scala} (70%) diff --git a/src/main/scala/scatan/Main.scala b/src/main/scala/scatan/Main.scala index 9554bba6..2e845c47 100644 --- a/src/main/scala/scatan/Main.scala +++ b/src/main/scala/scatan/Main.scala @@ -1,11 +1,11 @@ package scatan import com.raquo.laminar.api.L.{*, given} -import scatan.example.controller.{HomeController, HomeControllerImpl} +import scatan.example.controller.{AboutController, AboutControllerImpl, HomeController, HomeControllerImpl} import scatan.example.model.CounterAppState -import scatan.example.view.{HomeView, ScalaJsHomeView} +import scatan.example.view.{AboutView, HomeView, ScalaJSAboutView, ScalaJsHomeView} import scatan.mvc.lib.application.NavigableApplication import scatan.mvc.lib.page.PageFactory -import scatan.mvc.lib.{Controller, Model, NavigableApplicationManager, ScalaJS} +import scatan.mvc.lib.{Controller, Model, NavigableApplicationManager, ScalaJSView} import scala.util.Random @@ -18,6 +18,13 @@ enum Pages(val pageFactory: PageFactory[?, ?, CounterAppState]): controllerFactory = new HomeControllerImpl(_) ) ) + case About + extends Pages( + PageFactory[AboutController, AboutView, CounterAppState]( + viewFactory = new ScalaJSAboutView(_, "root"), + controllerFactory = new AboutControllerImpl(_) + ) + ) // Application val CounterApplication: NavigableApplication[CounterAppState, Pages] = NavigableApplication[CounterAppState, Pages]( @@ -25,7 +32,6 @@ val CounterApplication: NavigableApplication[CounterAppState, Pages] = Navigable pagesFactories = Pages.values.map(p => p -> p.pageFactory).toMap ) -/* + @main def main(): Unit = NavigableApplicationManager.startApplication(CounterApplication, Pages.Home) -*/ \ No newline at end of file diff --git a/src/main/scala/scatan/example/controller/AboutController.scala b/src/main/scala/scatan/example/controller/AboutController.scala new file mode 100644 index 00000000..8455c1d6 --- /dev/null +++ b/src/main/scala/scatan/example/controller/AboutController.scala @@ -0,0 +1,15 @@ +package scatan.example.controller + +import scatan.example.model.CounterAppState +import scatan.example.view.AboutView +import scatan.mvc.lib.Controller + +trait AboutController extends Controller: + def about(): Unit + + +class AboutControllerImpl(requirements: Controller.Requirements[AboutView, CounterAppState]) + extends AboutController + with Controller.Dependencies(requirements): + override def about(): Unit = + println("About") diff --git a/src/main/scala/scatan/example/controller/HomeControllerImpl.scala b/src/main/scala/scatan/example/controller/HomeControllerImpl.scala index 104463aa..24c71f99 100644 --- a/src/main/scala/scatan/example/controller/HomeControllerImpl.scala +++ b/src/main/scala/scatan/example/controller/HomeControllerImpl.scala @@ -3,7 +3,7 @@ package scatan.example.controller import scatan.example.model.CounterAppState import scatan.example.view import scatan.example.view.HomeView -import scatan.mvc.lib.{Controller, Model, ScalaJS, View} +import scatan.mvc.lib.{Controller, Model, ScalaJSView, View} class HomeControllerImpl(requirements: Controller.Requirements[HomeView, CounterAppState]) extends HomeController diff --git a/src/main/scala/scatan/example/view/AboutView.scala b/src/main/scala/scatan/example/view/AboutView.scala new file mode 100644 index 00000000..ce37fd69 --- /dev/null +++ b/src/main/scala/scatan/example/view/AboutView.scala @@ -0,0 +1,24 @@ +package scatan.example.view + +import com.raquo.laminar.api.L.* +import scatan.example.controller.AboutController +import scatan.mvc.lib.{NavigableApplicationManager, ScalaJSView, View} + +trait AboutView extends View: + def about(): Unit + +class ScalaJSAboutView(requirements: View.Requirements[AboutController], container: String) + extends AboutView + with View.Dependencies[AboutController](requirements) + with ScalaJSView(container): + + override def about(): Unit = ??? + + override def element: Element = div( + h1("About"), + p("This is a ScalaJS view"), + button( + "Back", + onClick --> (_ => NavigableApplicationManager.navigateBack()) + ) + ) diff --git a/src/main/scala/scatan/example/view/HomeView.scala b/src/main/scala/scatan/example/view/HomeView.scala index 4da69d55..f37dfd77 100644 --- a/src/main/scala/scatan/example/view/HomeView.scala +++ b/src/main/scala/scatan/example/view/HomeView.scala @@ -1,6 +1,6 @@ package scatan.example.view -import scatan.mvc.lib.{ScalaJS, View} +import scatan.mvc.lib.{ScalaJSView, View} trait HomeView extends View: def onCounterUpdated(counter: Int): Unit diff --git a/src/main/scala/scatan/example/view/ScalaJsHomeView.scala b/src/main/scala/scatan/example/view/ScalaJsHomeView.scala index 04abdbf4..4b5f8039 100644 --- a/src/main/scala/scatan/example/view/ScalaJsHomeView.scala +++ b/src/main/scala/scatan/example/view/ScalaJsHomeView.scala @@ -1,13 +1,14 @@ package scatan.example.view import com.raquo.laminar.api.L.* +import scatan.Pages import scatan.example.controller.HomeController -import scatan.mvc.lib.{ScalaJS, View} +import scatan.mvc.lib.{NavigableApplicationManager, ScalaJSView, View} class ScalaJsHomeView(requirements: View.Requirements[HomeController], container: String) extends HomeView with View.Dependencies(requirements) - with ScalaJS(container): + with ScalaJSView(container): private val reactiveCounter = Var(this.controller.counter) @@ -19,7 +20,11 @@ class ScalaJsHomeView(requirements: View.Requirements[HomeController], container button( "Increment", onClick --> (_ => controller.increment()) - ) + ), + button( + "Switch to About Page", + onClick --> (_ => NavigableApplicationManager.navigateTo(Pages.About)) + ) ) override def onCounterUpdated(counter: Int): Unit = diff --git a/src/main/scala/scatan/mvc/lib/ScalaJS.scala b/src/main/scala/scatan/mvc/lib/ScalaJSView.scala similarity index 70% rename from src/main/scala/scatan/mvc/lib/ScalaJS.scala rename to src/main/scala/scatan/mvc/lib/ScalaJSView.scala index 4fbe9821..ea3ed61e 100644 --- a/src/main/scala/scatan/mvc/lib/ScalaJS.scala +++ b/src/main/scala/scatan/mvc/lib/ScalaJSView.scala @@ -3,13 +3,15 @@ package scatan.mvc.lib import org.scalajs.dom import com.raquo.laminar.api.L.* -trait ScalaJS(val container: String) extends View: +trait ScalaJSView(val container: String) extends View: def element: Element override def show(): Unit = val containerElement = dom.document.getElementById(container) + containerElement.children.foreach(_.remove()) render(containerElement, element) override def hide(): Unit = val containerElement = dom.document.getElementById(container) + containerElement.children.foreach(_.remove()) render(containerElement, div()) diff --git a/src/main/scala/scatan/mvc/lib/page/ApplicationPage.scala b/src/main/scala/scatan/mvc/lib/page/ApplicationPage.scala index 41748193..4d0d2073 100644 --- a/src/main/scala/scatan/mvc/lib/page/ApplicationPage.scala +++ b/src/main/scala/scatan/mvc/lib/page/ApplicationPage.scala @@ -18,8 +18,7 @@ import scatan.mvc.lib.* trait ApplicationPage[S <: Model.State, C <: Controller, V <: View]( override val model: Model[S], val pageFactory: PageFactory[C, V, S] -) extends View.Requirements[C], - Controller.Requirements[V, S]: +) extends View.Requirements[C] with Controller.Requirements[V, S]: private lazy val _controller: C = pageFactory.controllerFactory(this) private lazy val _view: V = pageFactory.viewFactory(this) override def controller: C = _controller From fce19aac46e14ca59baeab9029e60e99e71f2c8d Mon Sep 17 00:00:00 2001 From: Alessandro Mazzoli Date: Wed, 6 Sep 2023 12:38:02 +0200 Subject: [PATCH 17/17] fix: formatted --- src/main/scala/scatan/Main.scala | 11 +++++------ .../example/controller/AboutController.scala | 3 +-- src/main/scala/scatan/example/view/AboutView.scala | 6 +++--- .../scatan/example/view/ScalaJsHomeView.scala | 14 +++++++------- .../scatan/mvc/lib/page/ApplicationPage.scala | 3 ++- 5 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/main/scala/scatan/Main.scala b/src/main/scala/scatan/Main.scala index 2e845c47..0586576d 100644 --- a/src/main/scala/scatan/Main.scala +++ b/src/main/scala/scatan/Main.scala @@ -19,12 +19,12 @@ enum Pages(val pageFactory: PageFactory[?, ?, CounterAppState]): ) ) case About - extends Pages( - PageFactory[AboutController, AboutView, CounterAppState]( - viewFactory = new ScalaJSAboutView(_, "root"), - controllerFactory = new AboutControllerImpl(_) + extends Pages( + PageFactory[AboutController, AboutView, CounterAppState]( + viewFactory = new ScalaJSAboutView(_, "root"), + controllerFactory = new AboutControllerImpl(_) + ) ) - ) // Application val CounterApplication: NavigableApplication[CounterAppState, Pages] = NavigableApplication[CounterAppState, Pages]( @@ -32,6 +32,5 @@ val CounterApplication: NavigableApplication[CounterAppState, Pages] = Navigable pagesFactories = Pages.values.map(p => p -> p.pageFactory).toMap ) - @main def main(): Unit = NavigableApplicationManager.startApplication(CounterApplication, Pages.Home) diff --git a/src/main/scala/scatan/example/controller/AboutController.scala b/src/main/scala/scatan/example/controller/AboutController.scala index 8455c1d6..3571770f 100644 --- a/src/main/scala/scatan/example/controller/AboutController.scala +++ b/src/main/scala/scatan/example/controller/AboutController.scala @@ -7,9 +7,8 @@ import scatan.mvc.lib.Controller trait AboutController extends Controller: def about(): Unit - class AboutControllerImpl(requirements: Controller.Requirements[AboutView, CounterAppState]) - extends AboutController + extends AboutController with Controller.Dependencies(requirements): override def about(): Unit = println("About") diff --git a/src/main/scala/scatan/example/view/AboutView.scala b/src/main/scala/scatan/example/view/AboutView.scala index ce37fd69..5c57bba3 100644 --- a/src/main/scala/scatan/example/view/AboutView.scala +++ b/src/main/scala/scatan/example/view/AboutView.scala @@ -8,9 +8,9 @@ trait AboutView extends View: def about(): Unit class ScalaJSAboutView(requirements: View.Requirements[AboutController], container: String) - extends AboutView - with View.Dependencies[AboutController](requirements) - with ScalaJSView(container): + extends AboutView + with View.Dependencies[AboutController](requirements) + with ScalaJSView(container): override def about(): Unit = ??? diff --git a/src/main/scala/scatan/example/view/ScalaJsHomeView.scala b/src/main/scala/scatan/example/view/ScalaJsHomeView.scala index 4b5f8039..8fc8e3da 100644 --- a/src/main/scala/scatan/example/view/ScalaJsHomeView.scala +++ b/src/main/scala/scatan/example/view/ScalaJsHomeView.scala @@ -14,13 +14,13 @@ class ScalaJsHomeView(requirements: View.Requirements[HomeController], container override def element: Element = div( - h1("Scala.js Home"), - p("This is a Scala.js view"), - p("The counter is: ", child.text <-- reactiveCounter.signal), - button( - "Increment", - onClick --> (_ => controller.increment()) - ), + h1("Scala.js Home"), + p("This is a Scala.js view"), + p("The counter is: ", child.text <-- reactiveCounter.signal), + button( + "Increment", + onClick --> (_ => controller.increment()) + ), button( "Switch to About Page", onClick --> (_ => NavigableApplicationManager.navigateTo(Pages.About)) diff --git a/src/main/scala/scatan/mvc/lib/page/ApplicationPage.scala b/src/main/scala/scatan/mvc/lib/page/ApplicationPage.scala index 4d0d2073..87385d4d 100644 --- a/src/main/scala/scatan/mvc/lib/page/ApplicationPage.scala +++ b/src/main/scala/scatan/mvc/lib/page/ApplicationPage.scala @@ -18,7 +18,8 @@ import scatan.mvc.lib.* trait ApplicationPage[S <: Model.State, C <: Controller, V <: View]( override val model: Model[S], val pageFactory: PageFactory[C, V, S] -) extends View.Requirements[C] with Controller.Requirements[V, S]: +) extends View.Requirements[C] + with Controller.Requirements[V, S]: private lazy val _controller: C = pageFactory.controllerFactory(this) private lazy val _view: V = pageFactory.viewFactory(this) override def controller: C = _controller