I have a Shell application using FlyoutItem with multiple ShellContents using the same ContentPage and ViewModel, as the only difference is the loaded content, layout and api calls are the same. That's why I need to pass some parameter to it when loading the Page/ViewModel.
The only ways I can find to do this are:
override OnNavigated in the AppShell and OnNavigatedTo in the ContentPage to get the breadcrumb and call the method of my ViewModel passing the parameter to it, isn't there some more elegant way?
protected override async void OnNavigatedTo(NavigatedToEventArgs args)
{
string route = Shell.Current.CurrentItem.CurrentItem.CurrentItem.Route;
await Vm.SearchQueryAsync(route);
base.OnNavigatedTo(args);
}
In this case, the breadcrumb matches the parameter I want to pass.
Related
I'm not sure if I'm thinking about this in the correct way. I have a list of objects, and I would like the user to be able to edit and view the properties of a specified object. My initial thought is great, I'll pop up a dialog that has textboxes and let the user edit to their heart's content until they press either Ok or Cancel.
I'm on UWP, and using Prism for all of my MVVM needs. It's taken me a while, but I understand creating Views and their associated ViewModels, commands, etc. So far, I think I've done a good job keeping the view logic and business logic separated.
I've searched, but haven't found how to show a dialog in a way that follows MVVM principles. The two things that seem to be coming up the most are to use Interaction requests (which don't appear to exist using Prism on UWP), and creating a custom Content Dialog and showing it by calling ShowAsync in a method in the parent view's associated ViewModel (which seems to be counter to MVVM principles).
So, how do I either show a dialog that is defined using XAML and has an associated ViewModel (preferable since it is similar to what I'm familiar with), or another way I can tackle this problem?
Using MVVM the proper place for opening a dialog is in the ViewModel.
Usually I do something like this in your scenario:
Create an interface for showing dialogs:
public interface IWindowService
{
void OpenViewModelInWindow(ViewModelBase vm, string title, bool resizeable = true);
void CloseViewModelInWindow(ViewModelBase vm);
}
Implement this interface in UI layer:
public class WindowService : IWindowService
{
private List<Window> _windows = new List<Window>();
public void OpenViewModelInWindow(ViewModelBase vm, string title, bool resizeable = true)
{
var window = new Window
{
Title = title,
Content = vm,
Owner = Application.Current.MainWindow,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
ShowInTaskbar = false,
SizeToContent = SizeToContent.WidthAndHeight,
ResizeMode = resizeable ? ResizeMode.CanResize : ResizeMode.NoResize
};
_windows.Add(window);
window.ShowDialog();
}
public void CloseViewModelInWindow(ViewModelBase vm)
{
_windows.Single(w => w.Content == vm).Close();
}
}
In your App.xaml you need to define DataTemplates so that when you set the Content property of the window the corresponding View created in the window.
<DataTemplate DataType="{x:Type viewModel:AViewModel}">
<views:AUserControl />
</DataTemplate>
Then you can use the IWindowService from the ViewModel, you should inject it by constructor injection.
This way you are not referencing framework specific classes directly from the ViewModel. ViewModel has a reference only to an IWindowService. This has a benefit also when you want ot write unit test for a viewmodel. You can mock this service so that when the unit test is running it should not open a dialog.
Just wondering if anyone knows a good/simple approach using Durandal to disposing of or re-initializing a viewmodel once it becomes invalid?
I have a registration form that I could 're-initialize' manually after a user has completed the form and registered successfully, but I'd prefer to just dispose of it so that Durandal creates a new registraion view/view model when that particular route is accessed again.
If your viewmodel module returns a function rather than an object, it will create a new one each time rather than reusing the 'singleton' object. See the Module Values section of Creating a Module.
Updated link for the Durandal Module constructor function information: Module Values
You can split the difference:
var cache;
var ctor = function () {
if (cache) return cache;
// init logic
cache = this;
}
Just replace the if(cache) check with whatever "do I need a new thing or not" logic you like.
If you're using routing, simply redirect the user to an instance-based module (one that returns a constructor function). The user will most likely click or touch a button that signifies that he is done with the registration form. That would be the redirect action.
If you're using composition, you would still create an instance-based module. Then, you would use dynamic composition to swap it in once the user signified he was done with the registration form.
Dynamic composition is where the view and/or model attributes on a Durandal composition are, themselves, observables, referencing something like the following in the viewModel:
this.currentView = ko.observable('');
this.currentModel = ko.observable('');
Then, in your HTML:
<div>
<div data-bind="compose: {view: currentView(), model: currentModel())"></div>
</div>
When the user clicks "Done", or something to that effect, functions on your viewModel might look something like:
ctor.prototype.done = function () {
this.setCurrentView('viewmodels/registrationForm.html');
this.setCurrentModel('viewmodels/registrationForm.js');
}
ctor.prototype.setCurrentView = function (view) {
this.currentView(view);
}
ctor.prototype.setCurrentModel = function (model) {
this.currentModel(model);
}
Either one of the approaches above will create the registrationForm only when it's needed.
With Durandal 2.0, you can use the deactivate callback within the composition lifecycle. Here is some documentation http://durandaljs.com/documentation/Hooking-Lifecycle-Callbacks
I need to switch the layout based on a user value that is stored in the database. I would like to set this using a plugin (tried PreDispatch hook). However, it looks like I can't access the models there as yet. At what point can I access db values and set the layout? I prefer to do this globally rather than set for each controller. Ideas appreciated.
For such purposes better to use controller plugin
class Core_Controller_Plugin_LayoutManager extends Zend_Controller_Plugin_Abstract
{
public function routeStartup (Zend_Controller_Request_Abstract $request)
{
// Get your layout name here
$this->_layout = Zend_Layout::getMvcInstance()
->setLayoutPath(YOUR_PATH_HERE)
->setLayout(YOUR_LAYOT_NAME_HERE);
}
}
Don't forget to add in in config:
resources.frontController.plugins.templatemanager = Core_Controller_Plugin_LayoutManager
I have an MVC2 ASP.Net 4 app that has a log on page which uses the layout from the app's master page. The 'default' log on page is typical:
using (Html.BeginForm("LogIn", "Home", FormMethod.Post, new { id = "LogIn" }))
{ ....form stuff...}
Now I have to show a log on page with a totally different layout but I want it to do the same thing(s) as the 'default' log on view, i.e. call the same controller action and use the same web model.
They want the users to go to www.mydomain.com/alternateLogOn.aspx
So I'm doing this in my global.asax:
protected void Application_BeginRequest(object sender, EventArgs arg)
{ if (Request.Url.PathAndQuery.ToLower() == "/AlternateLogOn.aspx")
{ Context.RewritePath("/Views/Home/AlternateLogOn.aspx");
}
}
This gets me the page I want to show w/o the master page layout (not including MasterPageFile=):
<%# Page Language="C#" AutoEventWireup="true" Inherits="System.Web.Mvc.ViewPage<MySite.Web.Models.AccountLogIn>" %>
The page displays fine w/o the using (Html.BeginForm), but when I use it I get the Object Reference exception.
Here's the stack trace:
Stack Trace:
[NullReferenceException: Object reference not set to an instance of an object.]
System.Web.Mvc.Html.FormExtensions.BeginForm(HtmlHelper htmlHelper, String actionName, String controllerName, RouteValueDictionary routeValues, FormMethod method, IDictionary`2 htmlAttributes) +42
System.Web.Mvc.Html.FormExtensions.BeginForm(HtmlHelper htmlHelper, String actionName, String controllerName, FormMethod method, Object htmlAttributes) +214
ASP.views_home_alternatelogon_aspx.__Render__control1(HtmlTextWriter __w, Control parameterContainer) in d:\Visual Studio 2010\MySite\MySite.Web\Views\Home\AlternateLogon.aspx:32
System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +130
System.Web.Mvc.ViewPage.Render(HtmlTextWriter writer) +84
System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +5273
I tried adding <%# Import namespace="System.Web.Mvc.Html" %> but it does not help
Thanks...
It is probably easiest to put the logon form into a partial view. Then, create two distinct pages, each of which calls a different master page/layout. Within each of those pages, call Html.RenderPartial("LogonForm").
(URL rewriting is generally unnecessary in MVC. Use routes instead.)
One of two things has happened here: either Html.RouteCollection is null, or Html.ViewContext is null.
I'm not 100% certain here, but it looks like the ASP.NET is handling this request, not MVC, hence the failure to populate those values. This would make sense since you are redirecting your user directly to an aspx page.
If all you want to do is change the layout of the page, implement it as it's own action/view and use this overload for View() which takes a master page as an argument.
I'd like to have my controller - i.e. Page_IndexController - extend a base controller.
For example;
class Page_IndexController extends Content_IndexController {
}
However, it seems the autoloader doesn't pick up the fact it's a controller class at any point - I get the error Fatal error: Class 'Content_IndexController' not found
First question: How do I fix this?
I can temporarily fix this by require_once'ing the generic 'content' controller, but this is hardly ideal.
The next issue is that if my Page controller has it's own view script for an action, it works no problem.
But if I'm extending a controller, and I call for example 'listAction' on the Page controller, but this action is implemented in Content_IndexController, it still looks for the list view script in the page controllers "scripts" directory.
Second question: How do I configure my controller to use its parents view script if it doesn't have its own?
If your application can find Page_IndexController you probably have a Page module. If you are not using modules in your application you have to name your controllers PageController and ContentController, not Page_IndexController, ... So the solution is to register "Content_" namespace with the autoloader.
As for the second question. You can extend the provided ViewRenderer controller action helper and override methods for finding the view script so they can look in other places if needed. You just have to pass your viewrenderer to the front controller. For passing your own ViewRenderer to the controller take a look at Advanced Usage Examples.
The auto loader can't find your controller because you haven't told it where to search. The Content_IndexController isn't in your "library" folder (I assume its inside of the Content module)
What I would suggest is creating a My_Controller_IndexBase class in your library folder that both Content_IndexController and Page_IndexController inherit.
Did a little more research on the topic of the view script. You could change up the view's script paths during init() somewhere. I'm pretty sure this would probably need to be done in a ViewRenderer - but might also work inside the controller's init/action code.
$this->view->setScriptPath(
array(
realpath(APPLICATION_PATH+'/../path/to/other/module/views'),
) + $this->view->getScriptPath());
Script paths are processed Last In First Out according to the Zend_View_Abstract
For the second question:
If you don't want to write your own ViewRenderer, you can use $this->renderScript('parent/index.phtml') to render a specific view script. You could call that in your child controllers instead of letting the views be rendered for you automatically, or if your child controllers rely on the parent controller to do the rendering of the script you can just place that in your parent controllers.
I do that mode.
Frist I register a new name who a plugin in my index.php into the public folder:
/public/index.php
$autoloader = Zend_Loader_Autoloader::getInstance();
$autoloader->registerNamespace('modelo_');
Secound I create a new folder to put my controller
/library/modelo/
Third I create my controller model and put it into the folder created and rename it.
class Modelo_ModeloController extends Zend_Controller_Action
{
protected $_db = null;
protected $_menu = null;
protected $_util = null;
protected $_path = null;
... actions to my model
public function inicial(){}
}
and I extend this class im my application
class Sispromo_PedidoController extends Modelo_ModeloController
{
public function init(){}
....
}