Reusing Views and Viewmodel with MEF & Silverlight - silverlight-4.0

Here is what I'd like to do :
I have an Silverlight application using navigation frame and MEF. (like this one : http://msdn.microsoft.com/en-us/magazine/gg535672.aspx)
This application consists of a set of buttons. Each button click load a view and its associated ViewModel.
Within theses views, I've a list with items and when I click on each items it refreshs a kind of sub-view in this view.
I'd like to create a navigation system : for example myapp.aspx#view1/2, where 2 is in fact the item clicked in the list. If I click on one of the button, it would load a default item and refresh all the view, but when I click on an item, I wouldn't like to refresh all the view but only certain part of the view (I do not want to create another instance of the view and viewmodel).
My problem is in fact that I would like to get the best pratice to get a reference to an existing view or viewmodel when i'm navigating to this page that has already been loaded (for example from myapp.aspx#view1/2 to myapp.aspx#view1/3)(I plan to do this into the BeginLoad of the ContentLoader class)
If I get the viewmodel, I can do that I want by changing for example the current itemId property which could refresh the view thanks to binding.
Thanks in davance if you have something to propose.

A common approach is to use some form of Messenger to do this type of operation. The item's click could trigger the sending of a message, with the Item attached. The ViewModel in question would be a subscriber, and edit its current settings (ie: it's ItemId, which would trigger the binding refresh).
The most common implementations are usually ones similar to the Messenger service in MVVM Light.
It's fairly easy to roll your own here, though, especially since you're already using MEF. Just create a service to handle the message passing, and import it into both endpoints.

Actually, I would have prefered to use an URI to navigate in my application when I click on an item, but if I use an URI, the entire view is reloading and not the specific part I'd like to.
With the messenger, I won't be able to use navigation with url within the view, I think ? Or else I didn't really figure out what you proposed to me.
The algorithm I would like to take is :
navigate("...asp#MyView1/1")
MyView1 is current view ?
yes then I'd like to get the viewmodel of the current view and change it the ItemId property with 1
no, then the view will be created
And I'd like to implement this algorithm there : (this is the place where the view is instancied for each navigation, in my CompositionNavigationContentLoader class)
public IAsyncResult BeginLoad(Uri targetUri, Uri currentUri, AsyncCallback userCallback, object asyncState)
{
// Convert to a dummy relative Uri so we can access the host.
var relativeUri = new Uri("http://" + targetUri.OriginalString, UriKind.Absolute);
// Get the factory for the ViewModel.
var viewModelMapping = ViewModelExports.FirstOrDefault(o => o.Metadata.Key.Equals(relativeUri.Host, StringComparison.OrdinalIgnoreCase));
if (viewModelMapping == null)
throw new InvalidOperationException(
String.Format("Unable to navigate to: {0}. Could not locate the ViewModel.", targetUri.OriginalString));
// Get the factory for the View.
var viewMapping = ViewExports.FirstOrDefault(o => o.Metadata.ViewModelContract == viewModelMapping.Metadata.ViewModelContract);
if (viewMapping == null)
throw new InvalidOperationException(
String.Format("Unable to navigate to: {0}. Could not locate the View.", targetUri.OriginalString));
// Resolve both the View and the ViewModel.
var viewFactory = viewMapping.CreateExport();
var view = viewFactory.Value as Control;
var viewModelFactory = viewModelMapping.CreateExport();
var viewModel = viewModelFactory.Value as IViewModel;
// Attach ViewModel to View.
view.DataContext = viewModel;
viewModel.OnLoaded();
Thanks.

Related

Implementing Detachable Panel in UWP App

I have an Image in a grid where I display some custom content by setting the Image's source to a WritableBitmap and updating the bitmap. What I want to do is to implement a "detach" button that will put my Image on a separate window allowing the user to move it to a different screen, resize it etc. independent of my main app window. If the new window is closed, I would like to bring it back to its original spot. While the Image is on the new window, I want to continuously update it with new content via updating source bitmap (as it would have been before it was detached).
I initially thought I would be able to create a new window and "move" my Image control there by first removing it from its original parent then adding it as a child to a layout in the new window. I used the code below:
CoreApplicationView^ newCoreView = CoreApplication::CreateNewView();
int mainViewId = Windows::UI::ViewManagement::ApplicationView::GetApplicationViewIdForWindow(
CoreApplication::MainView->CoreWindow);
uint indexOfObjectToDetach = -1;
bool found = originalGrid->Children->IndexOf(imageToMove, &indexOfObjectToDetach);
if(found)
{
myGrid->Children->RemoveAt(indexOfObjectToDetach);
}
DispatchedHandler^ dispatchHandler = ref new DispatchedHandler([this, mainViewId]()
{
newView_ = Windows::UI::ViewManagement::ApplicationView::GetForCurrentView();
Windows::UI::Xaml::Controls::StackPanel^ newWindowGrid = ref new Windows::UI::Xaml::Controls::StackPanel();
Window::Current->Content = newWindowGrid;
Window::Current->Activate();
newWindowGrid->Children->Append(imageToMove); // Add to new parent
});
create_task(newCoreView->Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, dispatchHandler)).then([this, mainViewId]()
{
auto a = newView_->Id;
create_task(ApplicationViewSwitcher::TryShowAsStandaloneAsync(a, ViewSizePreference::Default, mainViewId, ViewSizePreference::Default));
});
However in the line where I add the Image to its new parent, I get an Interface was marshalled for a different thread error. Upon more reading, this is due to the fact that each new window is in its own thread and I'm moving an object to another thread.
I am new to UWP and I am not sure how to approach implementing this UI behavior. How do I access/transfer my state in one view to another ?
The problem is indeed the fact that each application view in UWP has its own thread and its own UI dispatcher. When you create a control, it is tied to the UI thread it was created on, hence you cannot place it onto another application view.
The solution is to create the new Image next to the StackPanel within the new view's UI thread. I don't really use C++, but in C# I would implement it as follows:
await newCoreView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
StackPanel panel = new StackPanel();
Image image = new Image();
panel.Children.Add( panel );
image.Source = ...; //your source
Window.Current.Content = frame;
Window.Current.Activate();
newViewId = ApplicationView.GetForCurrentView().Id;
});
To further clarify - you can safely "transfer" normal data types into other view, the problem is mainly with the UI-tied types like controls, pages, etc.

Get SelectedItem in Dropdownlist without FormMethod.Post

I have a dropdownlist that is bound to a list in my model. Model.list_IDs
The current page is an "editing" page where a user would inherently change a property of the model by using the dropdownlist.
#Html.DropDownListFor(model => Model.ID, Model.list_IDs, new { style = "width:250px" })
The items within the dropdownlist are not intuitive, so I would like to provide a button, that when clicked, retrieves additional information via a stored procedure (called from the controller via a method called GetDetails)
The button is created through an action link, and I plan to display a partial view (like a focused pop up window) that shows the additional information once the button is clicked.
#Html.ActionLink(" ", "GetDetails", new { id = Model.ID.ToString() }, new { #class = "lnkMagnify16", title = "View Details" })
Obviously Model.ID.ToString() is incorrect, because it will only send the model's current ID, rather than the ID currently selected in the dropdownlist.
How can I change Model.ID.ToString() to represent the dropdownlist's current selected item?
I know there is a way to do this using FormMethod.Post (Get selected item in DropDownList ASP.NET MVC) but I do not want to reload the page with a submit button. I'd also like to avoid "third party" approaches like JavaScript if possible.
JavaScript is not a "third party" approach, and it's also the only way to accomplish what you want here. Whether by a standard form post or via AJAX (JavaScript), you must make a new request to the server to get the new information you want.
Now, since all you're specifically after is a way to dynamically update the the Model.ID value in the link, you can do that without AJAX or a form post, but you still must use JavaScript, since all dynamic behavior client-side originates from JavaScript. Basically, you'd need to bind to the change event of the dropdown and alter the href property of your link.
document.getElementById('ID').addEventListener('change', function () {
var selectedId = this.options[this.selectedIndex].value;
document.getElementById('AwesomeLink').href = // alter `href` with `selectedId`
});
However, that link is still going to change the whole view if the user clicks it. If you truly want the user to stay on the page, then you'll need to fetch the response of that link using AJAX and then either add it to the page somehow, whether that be directly or via a modal popup (which will required yet even further JavaScript to achieve). Also, it's not clear how the Model.ID value ends up in the URL, i.e. whether it's part of the path (/some/url/{id}/) or as a query string param (/some/url/?id={id}). If it's the former, I'd recommend switching to the latter, as it will make it much easier to build your URL client-side.
var xhr = new XMLHttpRequest();
xhr.addEventListener("load", function (response) {
// add response.responseText to the DOM
});
xhr.open("GET", "/some/url/?id=" + selectedId);
xhr.send();

Controllers sharing parts: How to include a controller's output from within another controller's view in Prestashop >= 1.5?

In the shop's customer section I would like to render the user account menu (which we can see by default when reaching /my-account URL) as a side column on some others controllers like the ones associated to "/my-adresses", "/identity" pages ..
I thought I would have to create another controller which purpose would be to gather menu infos and only render the menu <ul> list. Then I could override Controllers such as MyAccountController, IdentityController to include this former Ctrl and then render its content as part of the views of those two other controllers views.
So how one can load a specific controller from another in order to render shared views between pages ? Which is the right/clean way to do that ?
I heard about $this->getController() but I did not find any snippet or implementation of what I'd like to achieve. I new to Prestashop but even if the code seems clear, I don't get the point here.
Thank you !
After getting a bit deeper into sources, I ended up moving the menu (initially part of the MyAccountControllerCore alias my-account template) into a brand new Controller "MyAccountMenuController", in /override/controllers/front/.
<?php
// In /override/controllers/front/MyAccountMenuController.php
// The "exposer" controller
public function display()
{
// Do what ever you want to pass specific variables
if (! $this->template) {
throw new Exception(get_class($this) . '::display() : missing template.');
}
$this->context->smarty->display($this->template);
return true;
}
I am now able to import this menu by adding the following snippet inside the initContent() method of each controller that are part of the customer account section (this means that each of them must be overridden, for more info about overriding controllers, see documentation):
<?php
// In /override/controllers/front/MyAccountController.php
// The "consumer" controller
public function initContent() {
// ...
// Importing the customer area's menu
$menuController = $this->getController('MyAccountMenuController');
ob_start();
$menuController->run();
$this->context->smarty->assign('myAccountMenu', ob_get_clean());
}
I don't think the original purpose of getController method (located in ControllerCore class) was meant to include another controller output, at least in Prestashop 1.5. Still, to me this approach is far cleaner than duplicate views code.
If you have a better (cleaner) approach to implement such a mechanism, please let me know !
Any thoughts ?

Windows 8 app: How to pass a parameter on GoBack()?

There are a lot of samples showing how to pass a parameter on navigating to a page in Window 8 (WinRT). But I could not find any hint for passing parameters going back.
Situation: The user navigates to a details page of same data. The data is passed to the page by
Frame.Navigate(typeof(DedtailsPage), data);
How can I pass back the changed data on GoBack()?
Store the reference to data somewhere and retrieve it when you navigate back?
Also note that it's best not to pass objects other than simple strings or values between pages, since only simple types are supported for storing frame navigation state in case your app gets suspended.
I know, that this is a very bad idea, but usualy I use this:
To write:
Frame rootFrame = Window.Current.Content as Frame;
rootFrame.Tag = myObject1;
To read:
Frame rootFrame = Window.Current.Content as Frame;
var myObject2 = rootFrame.Tag;
I've made another choice to handle this.
Keep in mind that I'm a Xamarin developer (not Forms!) so i was looking for a solution which was similar for all platforms: iOS, Android and Windows.
I am a great fun of events, rather than passing objects or storing globals.
So the best choice to pass data from PageB (the child) to PageA (the parent) is to communicate via events.
Some notes: In iOS and Android, when you navigate from a "page" to "page" you can do this by passing an instance of the target object you want to navigate to. In iOS you create an instance of a custom UIViewController. In Android you create an instance of a custom Fragment. Doing this allow you to attach events handler to your instances.
An example:
var controller = new ExtrasViewController();
controller.ExtraSelected += ExtrasFragment_ExtraSelected;
controller.ExtrasCleared += ExtrasFragment_ExtrasCleared;
this.NavigationController.PushViewController(controller, false);
In WinRT Apps you are only allowed to pass "types" of target page to navigate to. So the only way to attach event handlers to your page instance is to use the OnNavigatedFrom method from the calling page. So suppose you are in PageA and want to attach some event handlers to your PageB, before it become active, simply write in your PageA "code behind":
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
var page = e.Content as ExtraBody;
if(page != null)
{
page.ExtraSelected += ExtrasFragment_ExtraSelected;
page.ExtrasCleared += ExtrasFragment_ExtrasCleared;
}
}

DojoX Mobile ListItem load HTML via AJAX and then remove from DOM

Let's say in a view I have a DojoX Mobile ListItem that is pulling an HTML view fragment into the DOM via AJAX and then transitioning to that view. Assume this is all working fine.
Now, I go back to the initial view that had that ListItem on it and click some other button that destroys that view node from the DOM. If I now click on that ListItem that previously loaded that view node into the DOM (which has now been removed), it will try to transition to a view that doesn't exist. It doesn't know that it has been removed.
Is there some type of way to tell a ListItem that it needs to fetch the HTML again because what was previously fetched no longer exists? I am not seeing anything about doing this in any documentation anywhere. I don't think a code sample is really necessary here, but I can provide a minimal one if necessary.
I went a different route and left the view exist in the DOM, and simply made a function that clears all sensitive data out of the view.
Okay, in this case, i guess you could hook the onShow function of your ListItem container(or any other onchange event). Create a listener for said handle to evaluate if your item needs reloading. Following is under the assumtion that it is the item.onclick contents showing - and not the label of your item which contains these informations
Or better yet, do all this during initialization so that your ListItem container will be an extended with custom onClick code.
Seems simple but may introduce some quirks, where/when/if you programatically change to this item, however here goes:
function checkItem() {
// figure out if DOM is present and if it should be
if( isLoggedIn() ) {
this.getChildren().forEach(function(listitem) {
if( dojo.query("#ID_TO_LOOK_FOR", listitem.domNode).length == 0 ) {
// this references the listItem, refresh contents.
// Note: this expects the listitem to be stateful, have no testing environment at time being but it should be
listitem.set("url", listitem.url);
}
});
}
}
Preferably, set this in your construct of the container for your ListItems
var listItemParent = new dojox.mobile.RoundRectList({
onShow : checkItem,
...
});
Or create listener
var listItemParent = dijit.byId('itemRegistryId');
// override onClick - calling inheritance chain once done
dojo.connect(listItemParent, "onClick", listItemParent, checkItem);