Listen to Router events using ChaplinJS's mediator - chaplinjs

I currently trying out ChaplinJS for a new project and I'm running into a problem.
I want to have a controller (for my navigation) that's listening to the changes of the router. I want to have callbacks for events like changeURL or route. I'm not sure how Chaplin's mediator works but I thought the router is throwing events using the mediator which I should be able to catch. I tried this:
mediator = require 'mediator'
Controller = require 'controllers/base/controller'
Menu = require 'models/menu'
MenuView = require 'views/menu-view'
module.exports = class MenuController extends Controller
listen:
'router:changeURL mediator': #test
initialize: ->
super
#menu = new Menu()
#view = new MenuView model: #menu
mediator.subscribe 'router:changeURL', #test
test: ->
console.log 'testlisten'

You should use beforeAction method to accomplish this.
The method receives all arguments actions receive. http://docs.chaplinjs.org/chaplin.controller.html
There is no listen in controllers.

Related

Kotlin Multiplatform Compose ~ How to use events to control views

I have a Kotlin Multiplatform project where I want to use Events to control views.
The basic idea is this:
Buttons & Co fire an Event when clicked
These events get caught and handled by the responsible program components, which will in turn fire other events
Eventually, some kind of ViewEvent is fired, which is subscribed to by the ViewController
The ViewController then tells the program what should be drawn on the screen
In theory, that sounds like it should work. In practice, what happens is that while it gets to the point where the ViewController receives the event and reacts accordingly, the actual views are unaffected.
My ViewController looks like this:
import androidx.compose.runtime.Composable
import com.tri_tail.ceal_chronicler.events.OpenCharacterSelectionViewEvent
import com.tri_tail.ceal_chronicler.ui.main_view.MainView
import com.tri_tail.ceal_chronicler.ui.main_view.MainViewState
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
class ViewController {
private var mainViewState = MainViewState.TITLE
init {
val eventBus = EventBus.getDefault()
eventBus.register(this)
}
#Composable
fun draw() {
MainView(mainViewState)
}
#Subscribe
fun onOpenCharacterSelectionViewEvent(event: OpenCharacterSelectionViewEvent) {
mainViewState = MainViewState.CHARACTER
}
}
I debugged that, and was able to see that the mainViewState changes, as expected. However, the draw() function is never called again, and so the changed mainViewState never arrives in the MainView.
I've already tried making mainViewState a mutableStateOf(mainViewState), but that didn't change anything.
Furthermore, I can't just call draw() inside the onOpenCharacterSelectionViewEvent, because it is not #Composable, and adding that annotation to the method causes the build to fail.
At this point, I am not even sure whether what I am trying to do here can work this way. Can someone please help me out here?
I have also published a version of the code with the current non-working solution here: https://github.com/KiraResari/ceal-chronicler/tree/event-system
For my KMM project i use open source viewModel for KMM.
This one https://github.com/adeo-opensource/kviewmodel--mpp
I suggest transfer your MVC architecture to MVI and use this KMM viewModel to control your state as in usual android app.
Okay, so after worrying at this for several days, I have now come up with a solution that works.
Basically, the reason why it doesn't work as I tried it is that the frontend lives in its own little world, and it is very difficult for something from outside that world to affect it.
However, it can be done using delegates. Basically, what I called the ViewController in above is more of a MainViewModel, and it needs to look like this:
import com.tri_tail.ceal_chronicler.events.OpenCharacterSelectionViewEvent
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
class MainViewModel {
var state = MainViewState.TITLE
var updateState: ((MainViewState) -> Unit) = { }
set(value) {
field = value
updateState(state)
}
init {
val eventBus = EventBus.getDefault()
eventBus.register(this)
}
#Subscribe
fun onOpenCharacterSelectionViewEvent(event: OpenCharacterSelectionViewEvent) {
state = MainViewState.CHARACTER
updateState(state)
}
}
The other part of the magic happens in the MainView, where the state needs to be a remember with a mutableStateOf(..., policy = neverEqualPolicy()), and the delegate needs to be set like this:
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.*
import com.tri_tail.ceal_chronicler.models.main_view.MainViewModel
import com.tri_tail.ceal_chronicler.models.main_view.MainViewState
import com.tri_tail.ceal_chronicler.theme.AppTheme
import com.tri_tail.ceal_chronicler.ui.TitleScreen
import com.tri_tail.ceal_chronicler.ui.characters.DisplayCharacterSelector
#Composable
fun MainView(model: MainViewModel = MainViewModel()) {
var state by remember {
mutableStateOf(
model.state,
policy = neverEqualPolicy()
)
}
model.updateState = {
state = it
}
AppTheme {
when (state) {
MainViewState.TITLE -> TitleScreen()
MainViewState.CHARACTER -> DisplayCharacterSelector()
}
}
}
And that's all there is to it! Works like a charm, no extra libraries required.

Events provider is deprecating. Using Redux or Observables for state in ionic apps

I've been using events in my ionic application, where i subscribe in one page, and publish the event in the other page. Now I see a warning that Events are going to be changed with Observables and Redux state and effect.
I was using Events mainly to call for component function changes outside it, so I had a components for example:
Component1.ts
this.events.subscribe('event:addValue1', (data: any) => {
this.valueName = 'VALUE1';
});
this.events.subscribe('event:addValue2', (data: any) => {
this.valueName = 'VALUE2';
});
and than outside this component I was calling the publish methods from any page, like:
Page1.ts
this.events.publish('event:addValue1');
Page2.ts
this.events.publish('event:addValue2');
By this i was able to change the data (this.valueName) outside the Component1.ts from any other page, simply by publishing the desired event.
I know that this might not sound or be right approach, but It was the only way I was doing changes to my Component1.ts outside it from any page.
I have now changed this and just put separate functions and than i access them via ViewChild component name like
#ViewChild('component') component: any;
....
this.component.functionAddValue1().
and additionally I send additional params via Angular NavigationExtras if i need to calculate and call some function from the Component1.ts, lets say if I navigate to some route.
Before this I was just calling the events.publish and I was able to make the changes to the Component1.ts on the fly.
Create event service.
In the EventService.ts:
export class EventService {
private dataObserved = new BehaviorSubject<any>('');
currentEvent = this.dataObserved.asObservable();
constructo(){}
publish(param):void {
this.dataObserved.next(param);
}
}
For publishing the event from example page1:
constructor(public eventService:EventService){}
updatePost(value){
this.eventService.publish({name:'post:updated',params:value});
}
In page 2:
constructor(public eventService:EventService){
eventService.currentEvent.subscribe(value=>{
if(value.name=='post:updated'){
//get value.name
}else if(value.name=='another:event'){
//get value or update view or trigger function or method...
}
// here you can get the value or do whatever you want
});
}

How to set ID of a XML-View in a component environment?

I want to access a view's controller from a custom module with some utility functions. Basically you can do this that way:
var oController = sap.ui.getCore().byId("__xmlview1").getController();
The problem is that the above coding will not work in a real environment because __xmlview1is dynamically created by the framework. So I tried to find a possibility to set the ID of the view during instantiation. The problem is - I could not find one:
Trying to give the view an ID in the view.xml file does not work:
<mvc:View
controllerName="dividendgrowthtools.view.dividendcompare"
id="testID"
xmlns="sap.m"
...
Trying to set the ID in the router configuration of the component does not work either:
...
name: "Dividend Compare",
viewId: "test",
pattern: "Dividend-Compare",
target: "dividendcompare"
...
The problem is that I do not have direct control over the instantiation of the XML view - the component respectively the router does it.
So, is there a solution for that problem? Or at least a save way to get the view ID by providing the name of the view?
You should have a look at the SAPUI5 EventBus.
I am pretty sure, you want to let the controller to do something with the dividentcompare view. With the SAPUI5 Eventbus, you can publish actions from one controller to another witout braking MVC patterns.
In your dividendcompare.controller.js:
onInit : function() {
var oEventBus = sap.ui.getCore().getEventBus();
oEventBus.subscribe("MyChannel", "doStuff", this.handleDoStuff, this);
[...]
},
handleDoStuff : function (oEvent) {
var oView = this.getView();
[...]
}
Now, in your anothercontroller.controller.js:
onTriggerDividendStuff : function (oEvent){
var oEventBus = sap.ui.getCore().getEventBus();
oEventBus.publish("MyChannel", "doStuff", { [optional Params] });
}
You are now able to get the view from the dividentcontroller in every case from every other controller of your app. You dont access the view directly, this would brake MVC patterns, but can pass options to its controller and do the handling there.

Basic CoffeeScript not firing events when run?

I'm having real problems writing a simple Backbone.js app using CoffeeScript and Zepto.js
This is the simplest Backbone view yet the events don't fire. I get no errors in the console either? Where am I going wrong?
#Main view
class AppView extends Backbone.View
constructor: ->
#el = $("#books")
#template = _.template("<div>New Item <a href='' id='addNew'> add new item</a></div>")
events: {
"click" : "createNew"
}
render: =>
#el.html(#template())
createNew : ->
console.log "new"
#Onload
$(document).ready ->
view = new AppView
view.render()
I've been following the only example I can find of CoffeeScript & Backbone together https://github.com/bnolan/Backbone-Mobile/blob/master/application.coffee
However if I add super into my view code above I get an undefined error, his code does not.
The class Backbone.View has its own constructor that does plenty of work, and you are overriding it and not calling super. Bad.
Instead, Backbone.View provides you the ability to define your own constructor-type function called initialize. Perform all your setup there. Backbone.View#constructor will call initialize.
#Main view
class AppView extends Backbone.View
initialize: ->
#el = $("#books")
#template = _.template(
"<div>New Item <a href='' id='addNew'> add new item</a></div>"
)
I had a similar problem (events not firing) and found that the problem was due to not setting #el. I set that:
#el: $("#content")
and it worked.

Widget notifying other widget(s)

How should widgets in GWT inform other widgets to refresh themselfs or perform some other action.
Should I use sinkEvent / onBrowserEvent?
And if so is there a way to create custom Events?
I have solved this problem using the Observer Pattern and a central Controller. The central controller is the only class that has knowledge of all widgets in the application and determines the way they fit together. If someone changes something on widget A, widget A fires an event. In the eventhandler you call the central controller through the 'notifyObservers()' call, which informes the central controller (and optionally others, but for simplicity I'm not going into that) that a certain action (passing a 'MyEvent' enum instance) has occurred.
This way, application flow logic is contained in a single central class and widgets don't need a spaghetti of references to eachother.
It's a very open ended question - for example, you could create your own static event Handler class which widgets subscribe themselves to. e.g:
Class newMessageHandler {
void update(Widget caller, Widget subscriber) {
...
}
}
customEventHandler.addEventType("New Message", newMessageHandler);
Widget w;
customEventHandler.subscribe(w, "New Message");
...
Widget caller;
// Fire "New Message" event for all widgets which have
// subscribed
customEventHandler.fireEvent(caller, "New Message");
Where customEventHandler keeps track of all widgets subscribing to each named event, and calls the update method on the named class, which could then call any additional methods you want. You might want to call unsubscribe in the destructor - but you could make it as fancy as you want.
So here is my (sample) implementation,
first let's create a new event:
import java.util.EventObject;
import com.google.gwt.user.client.ui.Widget;
public class NotificationEvent extends EventObject {
public NotificationEvent(String data) {
super(data);
}
}
Then we create an event handler interface:
import com.google.gwt.user.client.EventListener;
public interface NotificationHandler extends EventListener {
void onNotification(NotificationEvent event);
}
If we now have a widget implementing the NotificationHanlder, we can
trigger the event by calling:
((NotificationHandler)widget).onNotification(event);