views:

57

answers:

1

A non-developer wants the ability to modify scripts that users read on several screens.

For example, the first script may read, "Thanks for calling, how may I help you?" and contain some input elements. After clicking a "Next" button on this screen, they are presented with another form with another script with more input elements.

How can they "insert" a screen in between (or after or before) the two screens mentioned above with a lightweight XML-style language read from an external file? This file would contain simple, non-developer readable tags like <text>blah blah blah</text>, <button>Next</button> surrounded by <screen></screen> that I could translate into hide-able and show-able <div> tags. It should also be cache-able so all screens can load hidden when the user logs in or some earlier time in the application process.

All I've come up with so far is jQuery in the button.onclick's to hide the current div and show the next. I'm unsure how to use MVC to shove all this data down at the client at some prior time. Cookies are too small for caching big chunks of text, I think. Should I be looking into frames ::shudder::?

Basically, this .NET MVC app needs to dynamically cache views based on an external source to create a "dynamic" workflow of screens able to be modified by non-developers. It is ok to force the users to log out and back in to re-cache these screens.

Please let me know if this is unclear, I'm happy to elaborate.

+1  A: 

Here's what you could try:

Start by designing each page you would like to have with its input fields. Then you could define the ViewModels, the Controllers and the corresponding Views. It might look something like this (oversimplified):

ViewModels:

/// <summary>
/// Represents a button with its text and 
/// the controller name it will redirect to
/// </summary>
public class Button
{
    public string Text { get; set; }
    public string Controller { get; set; }
}

/// <summary>
/// Represents the page with a header and a list of buttons
/// </summary>
public class Page
{
    public string Header { get; set; }
    public IEnumerable<Button> Buttons { get; set; }
}

/// <summary>
/// Each view model will have page metadata
/// </summary>
public abstract class BaseViewModel
{
    public Page Page { get; set; }
}

public class Page1ViewModel : BaseViewModel
{
    // Put any properties specific to this page
    // which will be used for the input fields
}

public class Page2ViewModel : BaseViewModel
{
    // Put any properties specific to this page
    // which will be used for the input fields
}

...

Then create a repository which will parse the XML (the implementation is left for brevity):

public interface IPagesRepository
{
    Page ReadPage(string pageName);
}

Then here's how a page controller might look like:

public class Page1Controller : Controller
{
    private readonly IPagesRepository _repository;
    // TODO: to avoid repeating this ctor you could have 
    // a base repository controller which others derive from
    public Page1Controller(IPagesRepository repository)
    {
        _repository = repository;
    }

    public ActionResult Index()
    {
        var model = new Page1ViewModel();
        model.Page = _repository.ReadPage("page1");
        //model.Page = new Page
        //{
        //    Header = "Thanks for calling, how may I help you?",
        //    Buttons = new[] 
        //    {
        //        new Button { Text = "Next", Controller = "Page2" },
        //        new Button { Text = "Address", Controller = "Page3" },
        //    }
        //};
        return View(model);
    }

    [HttpPost]
    public ActionResult Index(Page1ViewModel model, string redirectTo)
    {
        if (!ModelState.IsValid)
        {
            return View(model);
        }
        return Redirect(redirectTo);
    }
}

And the last part is the corresponding view:

<script type="text/javascript">
$(function () {
    // when a navigation link is clicked set the redirectTo 
    // hidden field to the value of the controller we
    // want to redirect to and submit the form
    $('.nav a').click(function () {
        $('form :hidden[name=redirectTo]').val(this.href);
        $('form').submit();
        return false;
    });
});
</script>

<h2><%: Model.Page.Header %></h2>

<%: Html.ValidationSummary() %>

<% using (Html.BeginForm()) { %>
    <%: Html.Hidden("redirectTo") %>
    <!-- TODO: Put here the specific input fields for this page -->
<% } %>

<div class="nav">
    <%: Html.EditorFor(x => x.Page.Buttons) %>
</div>

And the Button.ascx Editor Template which will render the navigation links:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<SomeNs.Models.Button>" %>
<!-- Here we assume Index action is setup by default in the routes -->
<%: Html.ActionLink(Model.Text, "index", Model.Controller) %>
Darin Dimitrov
Wow! I appreciate you taking the time to piece that idea together. I'll try to look at that tonight and let you know what I can make from it. At first glance, it looks fantastic. I continued scribbling and came up with a possible alternative: big ViewModel constructs a bunch of divs (which I initially hide), but I feel like that might be a lot of jQuery
David
I apologize, but I now realize my specification may not be complete--Can I validate each ViewModel via AJAX instead of regular post? The point of my "big ViewModel" was to bring all the screens (divs) down at once preferably right after login and save a few requests by validating via AJAX. Is this possible? The big ViewModel wouldn't be validated until the final submit, and then some kind of response could indicate which div to render visible depending on which screen (div) had a validation problem.
David