Detect a change of iframe in page detail view in magnolia - magnolia

Is there a way to detect which node is being rendered when the user selects a link within the pages detail app and only the iframe changes? If just the the iframe is changing I need a mechanism that can call the parent page and send the path of the node that is being rendered in the iframe. We run Magnolia EE 5.6.11.
My issue is that I have a ValueChangeListener in a ComboBox that I use as a versionSelector in a PageBar extension in the pages detail app.
// Create a selection component;
private ComboBox versionSelector = new ComboBox();
private Listener listener;
private boolean isSettingValue;
public VersionSelectorViewImpl(){
construct();
}
private void construct() {
versionSelector.setVisible(false);
versionSelector.setSizeUndefined();
versionSelector.setImmediate(true);
versionSelector.setNullSelectionAllowed(false);
versionSelector.setTextInputAllowed(false);
//setup listener for the selected item
versionSelector.addValueChangeListener(new Property.ValueChangeListener() {
#Override
public void valueChange(Property.ValueChangeEvent event) {
if (listener != null) {
listener.versionSelected((Object) event.getProperty().getValue());
}
}
});
}
The implementation is similar to the LanguageSelector or VariantSelector. When someone activates a hyperlink in the page template the iframe changes and the valueChange method retrieves the wrong value (ie. the event is from the previous page).
When someone activates a link, the PagesEditorSubApp#updateNodePath calls the updateLocationDependentComponents which calls the PageBar.onLocationUpdate. This calls in our case the VersionSelector#setCurrentVersion method. At this point I would need to reload the page detail subapp so that the listener is correctly set to the new page. I tried using the pageEditorPresenter.refresh() method in the setCurrentVersion method but it didn't do it.

In Magnolia 5.x you need to fire ContentChangeEvent to trigger the refresh (and hope that subapp you want to refresh listens to it, which in case of page detail subapp shouldn't be a problem).
Or, since you mention LanguageSelector, you can indeed try to call PageEditorPresenter directly as it does from info.magnolia.pages.app.editor.pagebar.languageselector.LanguageSelector#languageSelected, tho unless you need to modify something in the presenter (eg. locale in the example above), it seems rather unnecessary coupling of the code.
Once you are migrating your code to Magnolia 6.x and new UI framework, you have more convenient way of triggering notification of datasource change which all the views consuming data from given source listen to and facilitating refresh that way (as is shown eg in PasteComponentAction here.

Related

Storing a view in Xamarin.Forms MvvmCross

I have an app with four main pages, switched through a tab bar (no "back" button).
One page has a lot of content (ScrollView) and takes quite a few seconds until it's rendered. I handle that by showing a "loading" overlay while the work is done. But for that specific page I'd like to keep the view alive, so that when the user switches to another page and comes back later, the page is ready without loading everything again.
I'm not sure how to do that in MvvmCross, though.
I did read the documentation and from what I understood the View Presenter would be the right way to do it, since the docs say:
"Another kind of presentation changes your app can request through
hints includes clearing / modifying the BackStack, changing a root
while maintaining the existent views, … possibilities are really
endless. Once again your app is king here!"
I guess I would need to create a custom MvxPresentationHint for that, but I don't quite get it :(
How or rather where would I access and store/load the View?
I'm generally still quite unfamiliar with MvvmCross (how it works under the hood) and especially customization of Mvx classes, even though I've been using it for a while.
Any explanation and preferably code examples beyond what's written in the documentation would be extremely appreciated!
It isn't meaningful to attempt to "store" a view in MVVM. The XF view is a representation of what will be created with native (e.g. "Android" or "iOS") widgets. Creating and measuring/laying out those native widgets is what is slow. MVVM View Presenter won't speed up that logic.
Instead of "store", you need "keep alive":
For a ContentPage called MyPage, when you create it, store it in a static variable. Then re-use that variable. If you never need more than one of these, you can store it in the class itself.
Modify the "code behind", MyPage.xaml.cs:
public partial class MyPage : ContentPage
{
// Singleton Pattern.
private static MyPage _it;
public static MyPage It {
get {
if (_it == null)
_it = new MyPage();
return _it;
}
}
// "private", because calling this directly defeats the purpose. Instead, use `MyPage.It`.
private MyPage()
{
InitializeComponent();
}
}
To create it, whereever you would put:
new MyPage()
instead put this:
MyPage.It
For instance, you might do PushAsync(MyPage.It);
This will always return the SAME INSTANCE of MyPage. So once it has been created, it keeps its state.
IMPORTANT: Note that the constructor is only called ONCE. Any code that needs to be done each time the page appears, put in override .. OnAppearing() method.
LIMITATION: Views "expect" to be part of the visual hierarchy when they are manipulated. If you attempt to alter the page or its view model while it is not on the screen, you may encounter problems. Those are beyond the scope of this answer - create a new StackOverflow question with the details of any problem you encounter.

OnNavigatedTo or Initialize too late for binding

I'm new to Prism with Xamarin.Forms. I've implemented the ViewModel approach using the Navigation and Commanding classes. It works just fine but there's just one thing I don't understand. With the AutowireViewModel set to true the ViewModelLocator automatically fills the BindingContext for me and that is sweet. The order of things is not what I expected. First the binding for the properties on the View fire and then the OnNavigatedTo is fired. This means that my init of the properties is already finished by the time I receive the parameters on the View. I can solve this by executing the RaisePropertyChanged. This causes the following:
I'm forced to write RaisePropertyChanged for every property on the ViewModel I want to see on the view with the new Data.
All bindings fire two times. For fast stuff that's not a problem but some are slower.
Data is refreshed after the View has become visible. Not disturbing but it would be nicer to show the finished View with it's data all at once.
All properties must be able to handle null references.
Is there a way to initialize the data in the ViewModel before the binding kicks in?
Actually Prism for Xamarin.Forms has long supported initializing your ViewModels prior to the View being pushed onto the NavigationStack. That said there is an order of operations that has to be taken into consideration. We cannot for instance perform a bunch of operations on a ViewModel and then attach it to a View.
The Order in which things are carried out are as follows:
The View is Created (Anything in the View's ctor is executed)
If you have specifically attached the ViewModelLocator.AutowireViewModel property this will resolve the ViewModel as part of the ctor
If you have not specifically opted out of the ViewModelLocator's Autowire, the Navigation Service will set it for you (after the ctor has completed)
The NavigationService will then call IAutoInitialize/IInitialize/InitializeAsync (for Prism 7.2+... INavigatingAware.OnNavigatingTo in older versions of Prism)
The NavigationService will then push the Page onto the Navigation Stack (note that this may or may not be visible to the user as additional pages may have to be added first when deep linking)
The NavigationService will then call OnNavigatedFrom / OnNavigatedTo (this is where people often report seeing a visible delay due to binding updates.
For an overwhelming number of scenarios if you have correctly initialized your ViewModel this process works exactly as you need. If you have some edge case where you absolutely have to ensure the ViewModel is initialized before it is set then you will need to handle this manually.
public partial class ViewA : ContentPage, IInitialize
{
private ViewAViewModel ViewModel { get; }
public ViewA(ViewAViewModel viewModel)
{
ViewModel = viewModel;
// Explicitly Opt Out of Autowiring
ViewModelLocator.SetAutowireViewModel(this, false);
InitializeComponent();
}
public void Initialize(INavigationParameters parameters)
{
ViewModel.Initialize(parameters);
BindingContext = ViewModel;
}
}

Tornadofx onFocus listener

Is there something like an onFocus() method, which I could override like the onDock() and onCreate() in the view class?
In the documentation there is only written about live reloading of views.
I tried combining that with, the onDock() and onCreate() method, but, even though it "works", it's not a very neat way of replicating onFocus behaviour.
Is there a simple way to have a "listener" method that's called when the view/fragment comes to front/on focus?
The View is a container, and not an UI element in the sense of the JavaFX context, so it cannot receive an onFocus callback. However, you can register one with the current window or even the root node of the View. If you're opening a window, you could register such an even with the currentWindow property. If you're not opening a window, you could register it with the root property of the View. Here is an example listening for a single focus change event from the currentWindow:
override fun onDock() {
currentStage?.focusedProperty()?.onChangeOnce {
}
}

Avoid back button using vaadin

I develop RIA application using Vaadin CDI addon.
About the app : it has two views(viewA and viewB) which maintained by Navigator.
User story is pretty simple:
User enters viewA
Perform some business stuff
Redirects to viewB
Using address bar to go some external website(ex. google.com)
Press back and it goes to he lastest page what he saw(viewB), instead of viewA
Any suggestions/tips-n-tricks how to avoid redirecting to viewB, but redirect to viewA?
Vaadin Forum quiet about this thing.
Thank you
I had same problem but resolved using following code:
getUI().getNavigator().addViewChangeListener(new ViewChangeListener() {
public boolean beforeViewChange(ViewChangeEvent event) {
return changeView;
}
#Override
public void afterViewChange(ViewChangeEvent event) {
// TODO Auto-generated method stub
}});
Set changeView = true only through proper navigation's (eg: On button clicks).
This how you can avoid navigation's using browser back button. In this case if user uses any of browser buttons the view does not changes and it remains on same page.
You could override View.enter(...) in your ViewB, and, according to your application state, update your view URI using Page.getCurrent().setUriFragment(recentUrl, false);

Dropthings and loading user control content

I started looking at the dropthings portal and I don't understand how the content within the widget gets loaded. My understanding is that the header is in an Updatepanel and the body of the widget is in an UpdatePanel with a ASP Panel in it. When the page loads, the ASP Panels of all the widgets load first before loading the User controls within the panels.
Can someone explain how this happens and point me to the code where this is setup?
Any help is appreciated
I'm using version 2.7.5 (released Jan 11), so this might be a bit newer.
Each widget is a user control, and is hosted in a WidgetContainer.ascx. The WidgetContainer provides all the common functions like the title renaming, positioning, expand/collapse, editing, etc. The source file is in the root "dropthings" folder. Looking at the Init method, we can see where the UserControl instantiated & added to the page:
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
var widget = LoadControl(this.WidgetInstance.Widget.Url); //Here's the magic
widget.ID = "Widget" + this.WidgetInstance.Id.ToString();
WidgetBodyPanel.Controls.Add(widget);
_WidgetRef = widget as IWidget;
if (_WidgetRef != null) _WidgetRef.Init(this);
}
So, first the WidgetContainer is added to the control tree for each Widget configured. Then as each container is initialized, it creates then adds the specific UserControl to the page. When
WidgetBodyPanel.Controls.Add(widget);
is called, it will initialize (OnInit) the specific widget. From there the contents of the widget are in it's own hands.

Resources