Can anybody explain what are the difference between Event Bus and Router in SAPUI5?
Please help me in which scenario we have to use Router or Event Bus.
Let me try to explain you in a shopping scenario.
Suppose you are browsing the "MyshopWebsite" shopping website. Let's think it is developed using SAP UI5 library.
Major Functionlity :
Infinite scrolling on Home page ( Loading,say, n items first and then 10 more items as soon as you hit bottom.
Click on product and you can see its details in separate page.
Suggestions Shown on side panel based on your selections.
Scenario 1: Now, on home page you are scrolling. As soon as you hit bottom, new products are loaded. Note :
Here, we need to show more information on same page. We are not navigating or changing the browser HASH.
Router is used to navigate to a different page than the current one and pass required data. So, I will not use it here. I want to be on same page and not change browser URL and its current hash.
So, how do I show more products after reaching the end of page ? Check the browser end of Page ( through JS) and load more. Many ways are there.
So, let's think there is Messenger, X, whose job is to tell the world : Hey, he reached end of page !! He reached End of Page. We need more Products!!!
And there is a listener, L, who keeps listening to our Messenger, X. When he hears him, he sends him new data.
So, lets convert it to technical term.
function initMethod() {
var eventBus = sap.ui.getCore().getEventBus();
eventBus.subscribe("homePage", "reachedEndOfPage", handleEndOfPage, this); // Listener L
}
function reachedEndOfPage() {
var eventBus = sap.ui.getCore().getEventBus();
eventBus.publish("homePage", "reachedEndOfPage"); // Messenger X
}
function handleEndOfPage() {
// Ok.. lets send more data
}
Scenario 2 : Clicked on a Product. Simple, move him to a new page. Show the information of the page. Change Broswer URL and hash so it can be bookmarked.
What to choose: Router.
Our SAPUI5 router is based on Crossroads JS. They say Crossroads is :
It is a powerful and flexible routing system. If used properly it can reduce code complexity by decoupling objects and also by abstracting navigation paths and server requests.
Router used for Navigation.
But can event bus be used here for Navigation. How ? In press event of Product, Publish event (saying 'Product Clicked'), then create a handler who will subscribe for 'Product Clicked' event and then Fire App.navTo or Router.navTo.
So, difference is Event bus creates listeners for ANY TYPE OF EVENT which you can fire manually.
Where as Router is used to NAVIGATION and is ONLY LISTENING TO BROWSER HASH changes.
Scenario 3 : Now, I hope it gets clear. I'm NOT NAVIGATING but I want to listen/react to every time user clicks a Product.
So, I will create a Messenger, 'Click', who will shout(Publish) when I click on any Product. And a Suggestion Listener, say Listener 'Suggest', who will be listen/react to Published events by our Messenger :'Click'.
I hope it helps you. Let me know if you still have confusion. Will try to add more info as needed.
With the router you can navigate between different targets. One target consists of one or two views.
Say you have a list in view A and a form containing the details of a list item in view B. When pressing on a list item in view A you can navigate to your detail view B using the router.
With the event bus you can communicate between different active controllers.
Say you have a split app with a master and a detail view. You can fire events in your master view and listen to these events in your detail view (and vice versa)
Related
I am developing a warehouse management application using Maui.
At the moment, I am using the GoToAsync method to navigate between pages, however, I have encountered a problem.
The structure of my pages:
Main menu with several buttons, select the first one:
Location ->
search location ->(after scanning the code)
3.List of products on the location ->(after selecting the product)
4.List with operations to perform (each one displays a different page) -> select the first one
5.Move the product to another location
etc.
Depending on the button selected in point 4, a different number of pages can be put on the stack, for example, after selecting the operation in point 5, 3 or 4 or 5 more pages can be put on the stack.
This is how I register my pages:
Routing.RegisterRoute(nameof(MenuPage), typeof(MenuPage)); Routing.RegisterRoute(nameof(ProductSearchPage), typeof(ProductSearchPage)); Routing.RegisterRoute(nameof(ProductListPage), typeof(ProductListPage)); Routing.RegisterRoute(nameof(ProductDetailsPage), typeof(ProductDetailsPage));
So as you can see I don't create a hierarchy of Uri addresses e.g. Page1/Page2/Page3 because, pages like the code finder, or the product list can be accessed from different places in the program.
What I'm trying to do is go back to one of the pages set aside on the stack. In the example given above, I would like each operation selected in item 4 to, in effect, backtrack me to the list of products from level 3. I am unable to determine this using GoToAsync("../../../...") because I do not know how many pages have been set aside.
I would also like to mention that a page like a search engine or a list sends a message about the selected code or item has a list, and this is handled on one of the pages on the stack ( that is why I do not know how many pages I have to backtrack)
I tried GoToAsync(Page3) however it takes me to a new instance of that page, and additionally puts another page back on the stack.
Any ideas how I could approach such an issue?
Is Shell Uri navigation the right mechanism for this type of project?
enter image description here
I want to know how to provide the numbering for each tab, so that author can provide the number as per their requirement.
This is the strangest requirement I ever heard of. You can sure do that, but it didn't make much sense of doing it system wide as one author can feel the dialog one way, other in completely different. The only reasonable solution is to use javascript to reorder the tabs in the way an author want and than save the settings for this specific component in his user profile. You can start implementing it by creating a clientlib with the category cq.authoring.dialog. In your JS you have to listen to specific dialog loading event as shown below. I think this should be enough and it's a good starting point.
// necessary as no granite or coral ui event is triggered, when the dialog is opened
// in a fullscreen mode the dialog is opened under specific url for serving devices with low screen resolution
if (location.href.match(/mnt\/override/)) {
$(window).on('load', function(e) {
setTimeout(doSomething, 100);
});
} else {
$(document).on('dialog-ready', function(e) {
Coral.commons.ready(function(){
setTimeout(doSomething, 100);
});
});
}
You can use granite:rel to define specific identifiers in the dialog definition and use save them later in the user settings. You can define drag & drop events using the tab selector [role="tab"].
This is not trivially possible. Decide upfront about the order when building the component, provide meaningful labels and go with that. Touch UI does not provide the feature you need.
We are working on tracking a site which has components built using Shadow DOM concepts, when we are creating a rule in launch to add tagging to these components it’s not working.
Can you guide us with best practice on tagging components in Shadow DOM?
I found unanswered questions about google analytics Google analytics inside shadow DOM doesn't work is this true for adobe analytics also?
Best Practice
Firstly, the spirit of using Shadow DOM concepts is to provide scope/closure for web components, so that people can't just go poking at them and messing them up. In principle, it is similar to having a local scoped variable inside a function that a higher scope can't touch. In practice, it is possible to get around this "wall" and have your way with it, but it breaks the "spirit" of shadow DOM, which IMO is bad practice.
So, if I were to advise some best practice about any of this, my first advice is to as much as possible, respect the spirit of web components that utilize shadow DOM, and treat them like the black box they strive to be. Meaning, you should go to the web developers in charge of the web component and ask them to provide an interface for you to use.
For example, Adobe Launch has the ability to listen for custom events broadcast to the (light) DOM, so the site developers can add to their web component, create a custom event and broadcast it on click of the button.
Note: Launch's custom event listener will only listen for custom event broadcasts starting at document.body, not document, so make sure to create and broadcast custom events on document.body or deeper.
"But the devs won't do anything so I have to take matters into my own hands..."
Sadly, this is a reality more often than not, so you gotta do what you gotta do. If this is the case, well, Launch does not currently have any native features to really make life easier for you in this regard (for the "core" part of the below stuff, anyways), and as of this post, AFAIK there are no public extensions that offer anything for this, either. But that doesn't mean you're SoL.
But I want to state that I'm not sure I would be quick to call the rest of this answer "Best Practice" so much as "It's 'a' solution..". Mostly because this largely involves just dumping a lot of pure javascript into a custom code box and calling it a day, which is more of a "catch-all, last resort" solution.
Meanwhile, in general, it's best practice to avoid using custom code boxes when it comes to tag managers unless you have to. The whole point of tag managers is to abstract away the code.
I think the TL;DR here is basically me reiterating this something that should ideally be put on the site devs' plate to do. But if you still really need to do it all in Launch because ReasonsTM, keep on reading.
'A' Solution...
Note: This is a really basic example with a simple open-mode shadow DOM scenario - in reality your scenario is almost certainly a lot more complex. I expect you to know what you're doing with javascript if you're diving into this!
Let's say you have the following on the page. Simple example of a custom html element with a button added to its shadow DOM.
<script>
class MyComponent extends HTMLElement {
constructor() {
super();
this._shadowRoot = this.attachShadow({
mode: 'open'
});
var button = document.createElement('button');
button.id = 'myButton';
button.value = 'my button value';
button.innerText = 'My Button';
this._shadowRoot.appendChild(button);
}
}
customElements.define('my-component', MyComponent);
</script>
<my-component id='myComponentContainer'></my-component>
Let's say you want to trigger a rule when a visitor clicks on the button.
Quick Solution Example
At this point I should probably say that you can get away with doing a Launch click event rule with query selector my-component#myComponentContainer with a custom code condition along the lines of:
return event.nativeEvent.path[0].matches('button#myButton');
Something like this should work for this scenario because there are a lot of stars aligned here:
The shadow dom is open mode, so no hacks to overwrite things
There are easily identifiable unique css selectors for both light and shadow DOM levels
You just want to listen for the click event, which bubbles up and
acts like a click happened on the shadow root's light DOM root.
In practice though, your requirements probably aren't going to be this easy. Maybe you need to attach some other event listener, such as a video play event. Unfortunately, there is no "one size fits all" solution at this point; it just depends on what your actual tracking requirements are.
But in general, the goal is pretty much the same as what you would have asked the devs to do: create and broadcast a custom (light) DOM event within the context of the shadow DOM.
Better Solution Example
Using the same component example and requirement as above, you could for example create a rule to trigger on DOM Ready. Name it something like "My Component Tracking - Core" or whatever. No conditions, unless you want to do something like check if the web component's root light DOM element exists or whatever.
Overall, this is the core code for attaching the event listener to the button and dispatching a custom event for Launch to listen for. Note, this code is based on our example component and tracking requirements above. It is unique to this example. You will need to write similar code based on your own setup.
Add a custom js container with something along the lines of this:
// get the root (light dom) element of the component
var rootElement = document.querySelector('#myComponentContainer');
if (rootElement && rootElement.shadowRoot) {
// get a reference to the component's shadow dom
var rootElementDOM = rootElement.shadowRoot;
// try and look for the button
var elem = rootElementDOM.querySelector('button#myButton');
if (elem) {
// add a click event listener to the button
elem.addEventListener('click', function(e) {
// optional payload of data to send to the custom event, e.g. the button's value
var data = {
value: e.target.value
};
// create a custom event 'MyButtonClick' to broadcast
var ev = new CustomEvent('MyButtonClick', {
detail: data
});
// broadcast the event (remember, natively, Launch can only listen for custom events starting on document.body, not document!
document.body.dispatchEvent(ev);
}, false);
}
}
From here, you can create a new rule that listens for the custom event broadcast.
Custom Event Rule Example
Rule name: My Button clicks
Events
Extension: Core
Event Type: Custom Event
Name: MyButtonClick
Custom Event Type: MyButtonClick
Elements matching the CSS selector: body
Conditions
*None for this scenario*
From here, you can set whatever Actions you want (set Adobe Analytics variables, send beacon, etc.).
Note:
In this example, I sent a data payload to the custom event. You can reference the payload in any custom (javascript) code box with event.detail, e.g. event.detail.value. You can also reference them in Launch fields with the % syntax, e.g. %event.detail.value%.
Modals and Notifications are components that are appended to the body. So they work little different than normal components. In my App, I can think of two ways of implementing them and I am not sure which one is better.
No stores
In this approach, I create a NotificationHelper class which has a create method. Inside that, I create a new container node, append it to the body and then call React.render(, container);
So any component can call NotificationHelper.create() and it will create a notification. Notification component that manages it's lifecycle and closes when the timer expires or somebody clicks on the close button.
The problem is often times, I need to show notification on the page in response to XHR response (success or failure), so in my actionCreator, I will have code like this
APIManager.post(url, postData).then(function(response) {
NotificationHelper.create(<SuccessNotification />)
});
I don't know if it's correct to call something like this from action creator that renders a new component.
With stores
Another approach is to create a NotificationStore and on emitChange render the notification component.
The code will look something like this
In my App.js, the code will be
<body>
<Header />
<Fooder />
<NotificationContainer />
</body>
And then in NotificationContainer, I will do something like
onChange: function() {
this.setState({customNotification: NotificationStore.get()});
},
render: function() {
<Notification>
{this.state.customNotification}
</Notification>
}
And finally, the action creator will look something like
Dispatcher.dispatch({
actionType: 'notification',
component: <MyComponent/>
});
The problem with this approach is the additional overhead of stores. Store is not doing any meaningful thing here, it's only there just to follow the flux. From action creator, we are passing data to the store, and the component is again taking the same data from the store and rendering it. So we finish the flux cycle without really getting anything out of it.
Also, I now need to initialize NotificationContainer at the start of my app, even though I don't have any notifications at this point.
I don't really see how your problems are problems. It does exactly what it's supposed to do, and if you need to build on it later, you can easily do so. Notifications and other no-true-component-owner features are one of the best reasons to use flux in my opinion (90% of the time I don't recommend flux).
With flux the notification action creator would be responsible for creating a remove notification action after a set period of time. You can also have an x button on the notification, which when clicked creates that action, and these go to the store which removes the item if it exists, and any/all views dependant on this store update. When I say any/all I mean that the notifications component may be hidden, or there may be an alternate way to view notifications on one page of the app, or there may be a simple counter with the number of notifications anywhere in the app.
Side note: don't pass around elements in a way that they could be rendered more than once. Pass {component: SuccessNotification, props: props} instead if you need to specify the component ahead of time.
I follow the answer of FakeRainBrigand.
Sorry the self promotion here but I created a Notification component that you can use with Flux. Here you can see a issue that shows a example of usage with Alt, but the principles are the same. You add the component to a top level element on your HTML and subscribe that component to a Notification store. On your notification action creator, you can add notification with some properties like: level, position, auto-dismissible, action, etc.
Here is the component demo.
I tried the example which is showing how to get data from history to re-generate UI; The thing I see mostly in all "history usage" examples are related to UI re-generation only so it is none-static way...
But what about "each UI state may have its unique url something like JSF does with flows"? For example I have app url like a
http://localhost:8080/myapp/MyApp.html
the app default UI contains main menu which is helping to navigate through my test catalog; I tried to make possible keep the UI dynamics in history by building url in this way
http://localhost:8080/myapp/MyApp.html#menu_testcategory_page1
but when I click internet browser "refresh" button the url keeps the same as http://localhost:8080/myapp/MyApp.html#menu_testcategory_page1 but the UI comes back to its default state :(
So my question is
is there an optimal way in pure gwt to stay in the same UI state even after browser's refresh button is clicked (I mean the unload/load window events occur)?
thanks
P.S. gwt 2.3
You should implement Activities and Places pattern: http://www.gwtproject.org/doc/latest/DevGuideMvpActivitiesAndPlaces.html
I am using it for 3 years, and it works very well.
Note, however, that when you reload a page, you lose all of your state, data, etc. If you need to preserve some of it, you can use a combination of a Place (#page1) and a token that tells the corresponding Activity the state of the View representing this Place, i.e. (#page1:item=5).
You probably just forgot to call
History.fireCurrentHistoryState();
from your entry point.