From 5e869c96a57dbeeb069476d42ecf90d2fbaa4ee7 Mon Sep 17 00:00:00 2001 From: nekotoro Date: Mon, 17 Nov 2025 14:29:12 +0100 Subject: [PATCH] first implementation of Cairngorm --- assets/data/riders.json | 213 +++++++++++++++++++++++++++++ project.xml | 3 + src/LPTCManager2026.hx | 30 +++- src/business/LoadRidersDelegate.hx | 24 ++++ src/business/Services.hx | 15 ++ src/command/LoadRidersCommand.hx | 54 ++++++++ src/control/AppController.hx | 13 ++ src/control/LoadRidersEvent.hx | 9 ++ src/model/AppModelLocator.hx | 76 ++++++++++ src/vo/Rider.hx | 64 +++++++++ 10 files changed, 498 insertions(+), 3 deletions(-) create mode 100644 assets/data/riders.json create mode 100644 src/business/LoadRidersDelegate.hx create mode 100644 src/business/Services.hx create mode 100644 src/command/LoadRidersCommand.hx create mode 100644 src/control/AppController.hx create mode 100644 src/control/LoadRidersEvent.hx create mode 100644 src/model/AppModelLocator.hx create mode 100644 src/vo/Rider.hx diff --git a/assets/data/riders.json b/assets/data/riders.json new file mode 100644 index 0000000..0c122c1 --- /dev/null +++ b/assets/data/riders.json @@ -0,0 +1,213 @@ +[ + { + "id": 1, + "name": "Maitray", + "firstName": "Camille", + "age": "11", + "lastLessonDate": "1757548800000", + "level": "1", + "credit": "10", + "address": "8 rue de Chez Douteau, 17600 Corme-Écluse", + "notes": "🐤", + "ffeLicence": "true", + "ffeLicenceValidityYear": "2025", + "ffeLicenceNumber": "6178760C", + "legalGuardianName": "Maitray", + "legalGuardianFirstName": "Vincent", + "legalGuardianRole": "père", + "legalGuardianPhoneNumber": "0640180230", + "legalGuardianEmail": "vincent.maitray@gmail.com" + }, + + { + "id": 0, + "name": "Durand", + "firstName": "Lucas", + "age": 12, + "lastLessonDate": 1711574400, + "level": 1, + "credit": 8, + "address": "14 rue des Acacias, 75017 Paris", + "notes": "Bon progrès, travaille la posture.", + "ffeLicence": true, + "ffeLicenceValidityYear": 2025, + "ffeLicenceNumber": "C1234567", + "legalGuardianName": "Durand", + "legalGuardianFirstName": "Sophie", + "legalGuardianRole": "mère", + "legalGuardianPhoneNumber": "01 45 67 89 10", + "legalGuardianEmail": "sophie.durand@example.com" + }, + { + "id": 1, + "name": "Martin", + "firstName": "Emma", + "age": 9, + "lastLessonDate": 1704067200, + "level": 0, + "credit": 3, + "address": "7 avenue Victor Hugo, 33000 Bordeaux", + "notes": "Très attentive, timide en groupe.", + "ffeLicence": false, + "ffeLicenceValidityYear": 2026, + "ffeLicenceNumber": "C7654321", + "legalGuardianName": "Lemoine", + "legalGuardianFirstName": "Claire", + "legalGuardianRole": "mère", + "legalGuardianPhoneNumber": "05 56 12 34 56", + "legalGuardianEmail": "claire.lemoine@example.net" + }, + { + "id": 2, + "name": "Bernard", + "firstName": "Tom", + "age": 15, + "lastLessonDate": 1716844800, + "level": 2, + "credit": -2, + "address": "23 boulevard Gambetta, 59000 Lille", + "notes": "Doit rattraper les cours manqués.", + "ffeLicence": true, + "ffeLicenceValidityYear": 2026, + "ffeLicenceNumber": "C9876543", + "legalGuardianName": "Bernard", + "legalGuardianFirstName": "Marc", + "legalGuardianRole": "père", + "legalGuardianPhoneNumber": "03 20 33 44 55", + "legalGuardianEmail": "marc.bernard@mail-example.fr" + }, + { + "id": 3, + "name": "Petit", + "firstName": "Léa", + "age": 7, + "lastLessonDate": 1709548800, + "level": 0, + "credit": 12, + "address": "5 impasse des Lilas, 87000 Limoges", + "notes": "Très sociable, aime les activités créatives.", + "ffeLicence": false, + "ffeLicenceValidityYear": 2025, + "ffeLicenceNumber": "C2345678", + "legalGuardianName": "Moreau", + "legalGuardianFirstName": "Anne", + "legalGuardianRole": "grand-mère", + "legalGuardianPhoneNumber": "05 55 12 23 34", + "legalGuardianEmail": "anne.moreau@example.org" + }, + { + "id": 4, + "name": "Roux", + "firstName": "Noah", + "age": 11, + "lastLessonDate": 1712150400, + "level": 1, + "credit": 0, + "address": "98 route de Grenoble, 73000 Chambéry", + "notes": "Besoin d'encouragement pour prendre confiance.", + "ffeLicence": true, + "ffeLicenceValidityYear": 2025, + "ffeLicenceNumber": "C3456789", + "legalGuardianName": "Lefèvre", + "legalGuardianFirstName": "Isabelle", + "legalGuardianRole": "mère", + "legalGuardianPhoneNumber": "04 79 11 22 33", + "legalGuardianEmail": "isabelle.lefevre@example.com" + }, + + { + "id": 5, + "name": "Morel", + "firstName": "Mathis", + "age": 13, + "lastLessonDate": 1710326400, + "level": 2, + "credit": 5, + "address": "12 rue Jean Jaurès, 44000 Nantes", + "notes": "Technique en amélioration.", + "ffeLicence": true, + "ffeLicenceValidityYear": 2026, + "ffeLicenceNumber": "C4567890", + "legalGuardianName": "Dubois", + "legalGuardianFirstName": "Caroline", + "legalGuardianRole": "mère", + "legalGuardianPhoneNumber": "02 40 12 34 56", + "legalGuardianEmail": "caroline.dubois@example.com" + }, + { + "id": 6, + "name": "Garcia", + "firstName": "Chloé", + "age": 8, + "lastLessonDate": 1706784000, + "level": 0, + "credit": 10, + "address": "3 place Sainte-Catherine, 33000 Bordeaux", + "notes": "Très impliquée et souriante.", + "ffeLicence": false, + "ffeLicenceValidityYear": 2025, + "ffeLicenceNumber": "C1122334", + "legalGuardianName": "Garcia", + "legalGuardianFirstName": "Ismaël", + "legalGuardianRole": "père", + "legalGuardianPhoneNumber": "05 56 78 90 12", + "legalGuardianEmail": "ismael.garcia@mail-example.fr" + }, + { + "id": 7, + "name": "Fischer", + "firstName": "Hugo", + "age": 16, + "lastLessonDate": 1714512000, + "level": 2, + "credit": -5, + "address": "45 avenue Foch, 06000 Nice", + "notes": "Doit respecter les règles du groupe.", + "ffeLicence": true, + "ffeLicenceValidityYear": 2026, + "ffeLicenceNumber": "C9988776", + "legalGuardianName": "Fischer", + "legalGuardianFirstName": "Monique", + "legalGuardianRole": "grand-mère", + "legalGuardianPhoneNumber": "04 93 12 34 56", + "legalGuardianEmail": "monique.fischer@example.net" + }, + { + "id": 8, + "name": "Lefort", + "firstName": "Maya", + "age": 10, + "lastLessonDate": 1709452400, + "level": 1, + "credit": 7, + "address": "28 rue Carnot, 21000 Dijon", + "notes": "Bonne écoute, progresser en endurance.", + "ffeLicence": true, + "ffeLicenceValidityYear": 2025, + "ffeLicenceNumber": "C5566778", + "legalGuardianName": "Lefort", + "legalGuardianFirstName": "Paul", + "legalGuardianRole": "père", + "legalGuardianPhoneNumber": "03 80 12 34 56", + "legalGuardianEmail": "paul.lefort@example.org" + }, + { + "id": 9, + "name": "Dupuy", + "firstName": "Sacha", + "age": 6, + "lastLessonDate": 1705132800, + "level": 0, + "credit": 15, + "address": "6 rue des Charmilles, 67000 Strasbourg", + "notes": "Très créatif, participe volontiers.", + "ffeLicence": false, + "ffeLicenceValidityYear": 2026, + "ffeLicenceNumber": "C3344556", + "legalGuardianName": "Martin", + "legalGuardianFirstName": "Élodie", + "legalGuardianRole": "mère", + "legalGuardianPhoneNumber": "03 88 12 34 56", + "legalGuardianEmail": "elodie.martin@example.com" + } +] diff --git a/project.xml b/project.xml index 7e6402e..38b937f 100644 --- a/project.xml +++ b/project.xml @@ -28,4 +28,7 @@ + + + \ No newline at end of file diff --git a/src/LPTCManager2026.hx b/src/LPTCManager2026.hx index 7cc3b62..22adb62 100644 --- a/src/LPTCManager2026.hx +++ b/src/LPTCManager2026.hx @@ -1,3 +1,9 @@ +import control.AppController; +import business.Services; +import model.AppModelLocator; +import com.adobe.cairngorm.control.CairngormEventDispatcher; +import control.LoadRidersEvent; +import feathers.events.FeathersEvent; import feathers.layout.VerticalAlign; import components.NekoRectangle; import feathers.controls.Application; @@ -12,12 +18,19 @@ import openfl.Assets; import openfl.text.Font; class LPTCManager2026 extends Application { - private var mainPanel:Panel; + + private var model = AppModelLocator.getInstance(); + private var services = new Services(); + private var appController = new AppController(); + //private var mainPanel:Panel; // private var nav:StackNavigator; public function new() { super(); + + addEventListener(FeathersEvent.CREATION_COMPLETE, onCreationComplete); + } override private function initialize():Void { @@ -26,7 +39,7 @@ class LPTCManager2026 extends Application { stage.displayState = NORMAL; stage.scaleMode = NO_SCALE; - mainPanel = new Panel(); + /*mainPanel = new Panel(); mainPanel.autoSizeMode = STAGE; mainPanel.backgroundSkin = new NekoRectangle(Constants.MAIN_COLOR3); @@ -70,10 +83,21 @@ class LPTCManager2026 extends Application { mainPanel.footer = footer; - addChild(mainPanel); + addChild(mainPanel);*/ // nav = new StackNavigator(); trace(this, "--> initialize()"); } + + private function loadRiders():Void { + trace(this + " --> loadRiders()"); + var cgEvent:LoadRidersEvent = new LoadRidersEvent(); + CairngormEventDispatcher.getInstance().dispatchEvent(cgEvent); + } + + private function onCreationComplete(event:FeathersEvent):Void { + trace(this + " --> onCreationComplete()"); + loadRiders(); + } } diff --git a/src/business/LoadRidersDelegate.hx b/src/business/LoadRidersDelegate.hx new file mode 100644 index 0000000..a178ab2 --- /dev/null +++ b/src/business/LoadRidersDelegate.hx @@ -0,0 +1,24 @@ +package business; + +import com.adobe.cairngorm.business.ServiceLocator; +import feathers.rpc.IResponder; +import feathers.rpc.http.HTTPService; + +class LoadRidersDelegate { + private var command:IResponder; + private var service:HTTPService; + + public function new(command:IResponder) { + // constructor will store a reference to the service we're going to call + service = ServiceLocator.getInstance().getHTTPService('loadRidersService'); + // and store a reference to the command that created this delegate + this.command = command; + } + + public function loadRidersService():Void { + // call the service + var token = service.send(); + // notify this command when the service call completes + token.addResponder(command); + } +} diff --git a/src/business/Services.hx b/src/business/Services.hx new file mode 100644 index 0000000..9052e62 --- /dev/null +++ b/src/business/Services.hx @@ -0,0 +1,15 @@ +package business; + +import com.adobe.cairngorm.business.ServiceLocator; +import feathers.rpc.http.HTTPService; + +class Services extends ServiceLocator { + public var loadRidersService:HTTPService; + + public function new() { + super(); + loadRidersService = new HTTPService(); + loadRidersService.url = "data/riders.json"; + loadRidersService.resultFormat = HTTPService.RESULT_FORMAT_JSON; + } +} diff --git a/src/command/LoadRidersCommand.hx b/src/command/LoadRidersCommand.hx new file mode 100644 index 0000000..e980816 --- /dev/null +++ b/src/command/LoadRidersCommand.hx @@ -0,0 +1,54 @@ +package command; + +import openfl.Vector; +import vo.Rider; +import feathers.data.ArrayCollection; +import openfl.Lib; +import haxe.DynamicAccess; +import business.LoadRidersDelegate; +import com.adobe.cairngorm.commands.ICommand; +import com.adobe.cairngorm.control.CairngormEvent; +import feathers.rpc.IResponder; +import feathers.rpc.events.ResultEvent; +import haxe.Json; +import model.AppModelLocator; + +class LoadRidersCommand implements ICommand implements IResponder { + private var model = AppModelLocator.getInstance(); + + public function execute(cgEvent:CairngormEvent):Void { + // create a worker who will go get some data and pass it a reference to this command so the delegate knows where to return the data + var delegate = new LoadRidersDelegate(this); + // make the delegate do some work + delegate.loadRidersService(); + trace(this + "execute()"); + } + + // this is called when the delegate receives a result from the service + public function result(rpcEvent:Dynamic):Void { + + // populate the riders DP in the model locator with the JSON results from the service call + var riders:Array = cast(rpcEvent, ResultEvent).result; + model.ridersListDP = new ArrayCollection(riders); + + trace("ridersListDP.length --> " + model.ridersListDP.length); + + /*var data:DynamicAccess = Json.parse(cast(rpcEvent, ResultEvent).result); + */ + + /*var data:DynamicAccess = Json.parse(e.target.data); + for (key => value in data){ + ConfigValues.data[key] = value; + } */ + + + + } + + // this is called when the delegate receives a fault from the service + public function fault(rpcEvent:Dynamic):Void { + // store an error message in the model locator + // labels, alerts, etc can bind to this to notify the user of errors + model.errorStatus = "Fault occured in LoadEmployeesCommand."; + } +} diff --git a/src/control/AppController.hx b/src/control/AppController.hx new file mode 100644 index 0000000..db84798 --- /dev/null +++ b/src/control/AppController.hx @@ -0,0 +1,13 @@ +package control; + +import command.LoadRidersCommand; +import com.adobe.cairngorm.control.FrontController; + +class AppController extends FrontController { + public static final LOAD_RIDERS_EVENT = "loadRidersEvent"; + + public function new() { + super(); + addCommand(AppController.LOAD_RIDERS_EVENT, LoadRidersCommand); + } +} diff --git a/src/control/LoadRidersEvent.hx b/src/control/LoadRidersEvent.hx new file mode 100644 index 0000000..945d223 --- /dev/null +++ b/src/control/LoadRidersEvent.hx @@ -0,0 +1,9 @@ +package control; + +import com.adobe.cairngorm.control.CairngormEvent; + +class LoadRidersEvent extends CairngormEvent { + public function new() { + super(AppController.LOAD_RIDERS_EVENT); + } +} diff --git a/src/model/AppModelLocator.hx b/src/model/AppModelLocator.hx new file mode 100644 index 0000000..d569bd2 --- /dev/null +++ b/src/model/AppModelLocator.hx @@ -0,0 +1,76 @@ +package model; + +import vo.Rider; +import feathers.data.ArrayCollection; +import openfl.errors.Error; +import openfl.events.Event; +import com.adobe.cairngorm.model.IModelLocator; +import openfl.events.EventDispatcher; + +class AppModelLocator extends EventDispatcher implements IModelLocator { + + // events constants + public static final VIEWING_CHANGE = "viewingChange"; + public static final RIDERS_LIST_DP_CHANGE = "ridersListDPChange"; + + + // this instance stores a static reference to itself + private static var model:AppModelLocator; + + // available values for the main viewstack defined as constants to help uncover errors at compile time instead of run time + public static final ADMIN_LOGIN = 0; + public static final RIDERS_LIST = 1; + public static final RIDER_DETAIL = 2; + + // viewstack starts out on the admin login screen + public var viewing(default, set):Int = ADMIN_LOGIN; + + private function set_viewing(value:Int):Int { + viewing = value; + dispatchEvent(new Event(VIEWING_CHANGE)); + return viewing; + } + + // rider object contains uid/passwd + // its value gets set at login and cleared at logout but nothing binds to it or uses it + // retained since it was used in the original Adobe CafeTownsend example app + /*public var user(default, set):User; + + private function set_user(value:User):User { + user = value; + dispatchEvent(new Event(USER_CHANGE)); + return user; + }*/ + + // contains the main riders list which is populated on startup + // mx:application's creationComplete event is mutated into a cairngorm event + // that calls the httpservice for the data + public var ridersListDP(default, set):ArrayCollection; + + private function set_ridersListDP(value:ArrayCollection):ArrayCollection { + ridersListDP = value; + dispatchEvent(new Event(RIDERS_LIST_DP_CHANGE)); + return ridersListDP; + } + + // variable to store error messages from the httpservice + // nothinng currently binds to it, but an Alert or the login box could to show startup errors + public var errorStatus:String; + + + // singleton: constructor only allows one model locator + public function new() { + super(); + if (AppModelLocator.model != null) { + throw new Error("Only one ModelLocator instance should be instantiated"); + } + } + + // singleton: always returns the one existing static instance to itself + public static function getInstance():AppModelLocator { + if (model == null) + model = new AppModelLocator(); + return model; + } + +} \ No newline at end of file diff --git a/src/vo/Rider.hx b/src/vo/Rider.hx new file mode 100644 index 0000000..8cd3485 --- /dev/null +++ b/src/vo/Rider.hx @@ -0,0 +1,64 @@ +package vo; + +class Rider { + private static var currentIndex = 1000; + + public var id:Int; + public var name:String; + public var firstName:String; + public var age:Int; + public var lastLessonDate:Int; + public var level:Int; + public var credit:Int; + public var address:String; + public var notes:String; + public var ffeLicence:Bool; + public var ffeLicenceValidityYear:Int; + public var ffeLicenceNumber:String; + public var legalGuardianName:String; + public var legalGuardianFirstName:String; + public var legalGuardianRole:String; + public var legalGuardianPhoneNumber:Int; + public var legalGuardianEmail:String; + + + public function new(pId:Int = 0, + pName:String = "", + pFirstName:String = "", + pAge:Int = 0, + pLastLessonDate:Int, + pLevel:Int = 0, + pCredit:Int = 0, + pAddress:String = "", + pNotes:String = "", + pffeLicence:Bool = false, + pffeLicenceValidityYear:Int = 0, + pffeLicenceNumber:String = "", + pLegalGuardianName:String = "", + pLegalGuardianFirstName:String = "", + pLegalGuardianRole:String = "", + pLegalGuardianPhoneNumber:Int = 0, + pLegalGuardianEmail:String = "") { + + id = (pId == 0) ? currentIndex++ : pId; + name = pName; + firstName = pFirstName; + age = pAge; + lastLessonDate = pLastLessonDate; + level = pLevel; + credit = pCredit; + address = pAddress; + notes = pNotes; + ffeLicence = pffeLicence; + ffeLicenceValidityYear = pffeLicenceValidityYear; + ffeLicenceNumber = pffeLicenceNumber; + legalGuardianName = pLegalGuardianName; + legalGuardianFirstName = pLegalGuardianFirstName; + legalGuardianRole = pLegalGuardianRole; + legalGuardianPhoneNumber = pLegalGuardianPhoneNumber; + legalGuardianEmail = pLegalGuardianEmail; + + //startdate = (startdate == null) ? Date.now() : startdate; + + } +} \ No newline at end of file