Pass string from View to shape in @Display() command?

Topics: Writing modules
Jul 27, 2012 at 6:35 AM

This is a REALLY odd scenario and therefore I have a REALLY odd question...something I wouldn't normally do, but this is a custom app.

In my View, I'm rendering shapes for my Cast with 

@Display(Model.Cast.ContentItems)

Is there a way to pass in a simple string to each shape from this line in the View?

The reason I'm asking, is that I'm rendering the Cast twice on the same page, on different tabs. I'm also using drag/drop functionality on both and therefore need each list of Cast to have unique Id's. That's why I can't add do it from the Driver.

The way I'm doing it right now, I'm actually creating the Cast list twice in my Controller and then calling 2 different shapes using the DisplayType in my Driver to determine which shape to render. I was hoping to be able to consolidate that and only create the Cast list once and use the same shape. To do that, I'd need to be able to pass something to the shape that I could use to alter the Id's of the objects that are going to be drag/drop and the only place I can figure out where I could do that from is the View.

If it's not possible or some horrendous amount of coding, it's not a big deal. I was just trying to simplify the coding a bit.

Thanks!

Coordinator
Jul 27, 2012 at 1:49 PM

HAving no idea what Cast and Cast.ContentItems are, it's hard to say, but if anything in there is a dynamic shape, it should be trivial to add some data to it.

Jul 27, 2012 at 7:32 PM

Sorry...I was trying not to inundate the post with a ton of code, but probably could have included a little to better explain this.

Basically, this application is a Reality TV Gaming App. User's are able to join Leagues with their friends and co-workers. For each Episode of a Season of a Show (let's take Survivor as an example), you slot Cast members into Categories. If your Picks are correct, you earn Points for each Category you slotted correctly. That's the basics...there's a lot more that goes into it such as an MVP bonus, Early Pick Bonus, Perfect Pick Bonus.

BTW...all of this is currently running in Orchard at http://myrealitypicks.com. In the current version, nothing uses Content Items...it's basically just an MVC app running inside Orchard. I'm redesigning it to use Content Items instead.

Okay...enough background

The part I'm working on right now is where the admin (not in the admin Dashboard though) is configuring the Episode. This entails selecting which Cast members are on which Teams for the Episode and also putting Cast members into the correct Result Category after the Episode airs. I'm doing this via drag/drop where you drag an icon of the Cast and drop it in the proper container for the Team and Category.

Here's what the CastPartRecord looks like

using Orchard.ContentManagement.Records;

namespace Raptor.MyRealityPicks.Models {
    public class CastPartRecord : ContentPartRecord {
        public virtual int Age { get; set; }
        public virtual string FirstName { get; set; }
        public virtual string LastName { get; set; }
        public virtual string NickName { get; set; }
        public virtual string Hometown { get; set; }
        public virtual string Profession { get; set; }
        public virtual string About { get; set; }
        public virtual string ThumbnailExtension { get; set; }
        public virtual string ImageExtension { get; set; }

        public virtual SeasonPartRecord Season { get; set; }
    }
}

It's being called in the EpisodeController twice right now to display a list of Cast members I can use on the Team configuration and Results tabs in the Episode Edit View

        public ActionResult Edit(int episodeId) {
            if (!_orchardServices.Authorizer.Authorize(Permissions.ManageApplication, T("Not Allowed to Edit Episodes")))
                return new HttpUnauthorizedResult();

            TempData["showTab"] = "tab2";

            var episode = _orchardServices.ContentManager.Get(episodeId, VersionOptions.Latest);
            if (episode == null)
                return HttpNotFound();

            dynamic episodeModel = _orchardServices.ContentManager.BuildEditor(episode);

            HttpContext.Items["episodeContext"] = episode.Id;

            var castteam = _castService.Get().Where(x => x.Season.ContentItemRecord == episode.As<EpisodePart>().Season.ContentItemRecord).Select(x => _orchardServices.ContentManager.BuildDisplay(x, "DragDropTeam"));

            var castteamList = Shape.List();
            castteamList.AddRange(castteam);

            dynamic castteamModel = Shape.ViewModel()
                .ContentItems(castteamList);

            var castresult = _castService.Get().Where(x => x.Season.ContentItemRecord == episode.As<EpisodePart>().Season.ContentItemRecord).Select(x => _orchardServices.ContentManager.BuildDisplay(x, "DragDropResult"));

            var castresultList = Shape.List();
            castresultList.AddRange(castresult);

            dynamic castresultModel = Shape.ViewModel()
                .ContentItems(castresultList);

            var teams = _teamService.Get().Where(x => x.Season.ContentItemRecord == episode.As<EpisodePart>().Season.ContentItemRecord).Select(x => _orchardServices.ContentManager.BuildDisplay(x, "DragDrop"));

            var teamList = Shape.List();
            teamList.AddRange(teams);

            dynamic teamModel = Shape.ViewModel()
                .ContentItems(teamList);

            var categories = _categoryService.Get().Where(x => x.Season.ContentItemRecord == episode.As<EpisodePart>().Season.ContentItemRecord).Select(x => _orchardServices.ContentManager.BuildDisplay(x, "DragDrop"));

            var categoryList = Shape.List();
            categoryList.AddRange(categories);

            dynamic categoryModel = Shape.ViewModel()
                .ContentItems(categoryList);

            dynamic viewModel = Shape.ViewModel()
                .Episode(episodeModel)
                .CastTeam(castteamModel)
                .CastResult(castresultModel)
                .Teams(teamModel)
                .Categories(categoryModel);

            return View((object)viewModel);
        }

See the castteamlist and castresult list. I'm using different DisplayTypes to render the shapes in the Driver

protected override DriverResult Display(CastPart part, string displayType, dynamic shapeHelper) {
            if (displayType == "DragDropTeam") {
                return ContentShape("Parts_Cast_DragDropTeam",
                    () => shapeHelper.Parts_Cast_DragDropTeam(ContentPart: part));
            }
            else if (displayType == "DragDropResult") {
                return ContentShape("Parts_Cast_DragDropResult",
                    () => shapeHelper.Parts_Cast_DragDropResult(ContentPart: part));
            }

            return ContentShape("Parts_Cast",
                () => shapeHelper.Parts_Cast(ContentPart: part));
        }

and then rendering in the View for the Episode Edit action

@using Raptor.MyRealityPicks.Models;
@{
    Script.Require("AnimateColors");
    Style.Require("MyRealityPicks");

    Model.CastTeam.ContentItems.Classes.Add("cast-items");
    Model.CastResult.ContentItems.Classes.Add("cast-items");
    Model.Teams.ContentItems.Classes.Add("team-drop");
    Model.Categories.ContentItems.Classes.Add("category-drop");
}
@using (Script.Foot()) {
    <script type="text/javascript">
        var team_drop = function (obj, newPick) {
            var current_drag = $("input#is_dragging").val();
            var current_drop = $(obj).attr("id");
            
            var episode_id = @ViewContext.RouteData.Values["episodeId"];
            var cast_id = current_drag.replace("castteamdrag-", "");
            var team_id = current_drop.replace("teamdrop-", "");

            $.ajax({
                type: "POST",
                url: "/TeamMember/Create",
                data: {
                    episodeId: episode_id,
                    teamId: team_id,
                    castId: cast_id,
                    __RequestVerificationToken: $("input[name=__RequestVerificationToken]").val()
                },
                success: function (result, status) {
                    if (result.message == "Success") {
                        $(obj).append(newPick);
                        $(obj).animate({ backgroundColor: "#008000" }, 10);
                        $(obj).animate({ backgroundColor: "#efefef" }, 1000);
                    }
                    else {
                        $(obj).animate({ backgroundColor: "#cc0a0c" }, 10);
                        $(obj).animate({ backgroundColor: "#efefef" }, 1000);
                    }
                },
                error: function (req, status, error) {
                    $(obj).animate({ backgroundColor: "#cc0a0c" }, 10);
                    $(obj).animate({ backgroundColor: "#efefef" }, 1000);
                }
            });
        };

        var category_drop = function (obj, newPick) {
            var current_drag = $("input#is_dragging").val();
            var current_drop = $(obj).attr("id");
            
            var episode_id = @ViewContext.RouteData.Values["episodeId"];
            var cast_id = current_drag.replace("castresultdrag-", "");
            var category_id = current_drop.replace("categorydrop-", "");

            $.ajax({
                type: "POST",
                url: "/Result/Create",
                data: {
                    episodeId: episode_id,
                    categoryId: category_id,
                    castId: cast_id,
                    __RequestVerificationToken: $("input[name=__RequestVerificationToken]").val()
                },
                success: function (result, status) {
                    if (result.message == "Success") {
                        $(obj).append(newPick);
                        $(obj).animate({ backgroundColor: "#008000" }, 10);
                        $(obj).animate({ backgroundColor: "#efefef" }, 1000);
                    }
                    else {
                        $(obj).animate({ backgroundColor: "#cc0a0c" }, 10);
                        $(obj).animate({ backgroundColor: "#efefef" }, 1000);
                    }
                },
                error: function (req, status, error) {
                    $(obj).animate({ backgroundColor: "#cc0a0c" }, 10);
                    $(obj).animate({ backgroundColor: "#efefef" }, 1000);
                }
            });
        };
    </script>
}
<ul class="nav nav-tabs">
    <li class="@(TempData["episodeTab"] == null || TempData["episodeTab"] == "tab1" ? "active" : "")"><a href="#tab1" data-toggle="tab">Episode Info</a></li>
    <li class="@(TempData["episodeTab"] == "tab2" ? "active" : "")"><a href="#tab2" data-toggle="tab">Cast</a></li>
    <li class="@(TempData["episodeTab"] == "tab3" ? "active" : "")"><a href="#tab3" data-toggle="tab">Results</a></li>
</ul>
<div class="tab-content" style="overflow: visible;">
    <div id="tab1" class="tab-pane @(TempData["episodeTab"] == null || TempData["episodeTab"] == "tab1" ? "active" : "")">
        @using (Html.BeginFormAntiForgeryPost()) {
            @Html.ValidationSummary()
            @Display(Model.Episode)
        }
    </div>
    <div id="tab2" class="tab-pane @(TempData["episodeTab"] == "tab2" ? "active" : "")">
        <div class="clearfix">
            <h3>
                Cast Members</h3>
            @Display(Model.CastTeam.ContentItems)
            @if (Model.CastTeam.ContentItems.Items.Count < 1) {
                <p>
                    @T("No Cast Members Found.")
                </p>
            }
        </div>
        <div class="clearfix" style="margin-top: 30px;">
            <h3>
                Teams</h3>
            @Display(Model.Teams.ContentItems)
            @if (Model.Teams.ContentItems.Items.Count < 1) {
                <p>
                    @T("No Teams Found.")
                </p>
            }
        </div>
    </div>
    <div id="tab3" class="tab-pane @(TempData["episodeTab"] == "tab3" ? "active" : "")">
        <div class="clearfix">
            <h3>
                Cast Members</h3>
            @Display(Model.CastResult.ContentItems)
            @if (Model.CastResult.ContentItems.Items.Count < 1) {
                <p>
                    @T("No Cast Members Found.")
                </p>
            }
        </div>
        <div class="clearfix" style="margin-top: 30px;">
            <h3>
                Categories</h3>
            @Display(Model.Categories.ContentItems)
            @if (Model.Categories.ContentItems.Items.Count < 1) {
                <p>
                    @T("No Categories Found.")
                </p>
            }
        </div>
    </div>
</div>
<input type="hidden" id="is_dragging" value="" />

You can see where I'm calling them via @Display(Model.CastTeam.ContentItems) and @Display(Model.CastResult.ContentItems)

Here's one of the shapes being called. The other is identical EXCEPT for the id that I'm assigning to the container

@using Raptor.MyRealityPicks.Models;
@{
    Script.Require("jQueryUI_Draggable");

    var castPart = (CastPart)Model.ContentPart;
}
@using (Script.Foot()) {
    <script type="text/javascript">
        $("#castteamdrag-@castPart.Id").draggable({
            opacity: 0.5,
            helper: "clone",
            revert: "invalid",
            start: function (event, ui) { $("input#is_dragging").val($(this).attr("id")); },
            stop: function (event, ui) { $("input#is_dragging").val(""); }
        });
    </script>
}
<div id="castteamdrag-@castPart.Id" class="draggable" style="text-align: center;">
    <a href="#" class="thumbnail">
        <img src="@castPart.ThumbnailUrl" alt="@castPart.NickName" />
    </a>
    <span style="font-size: 9px;">@castPart.NickName</span>
</div>

Instead of "castteamdrag", it's "castresultdrag" in the other shape. These need to be unique for the drag & drop to work on both tabs. If I render the same shape twice, only the first tab works because the second has duplicate id's.

That's why I was looking for a way to pass something into the shape to identify which part of the View is calling it. I can't figure out any other way higher in the code stream to be to identify this.

Again...it's working with rendering the different shapes that are basically the same thing, I was just hoping to figure out a way to render the same shape and dynamically change the Id depending on where it's called from.

If there isn't a way to do it, not a big deal...but if you have any ideas, I'd love to streamline the code.

As always...thanks so much for the assistance (and for reading this REALLY long post) =)

Coordinator
Jul 28, 2012 at 12:47 PM

From your action, you're building lists of shapes that you then add to list shapes. All those objects are dynamic shapes so it's really trivial to add properties to them. It's kinda the point of shapes really. The result of BuildDisplay is a content shape that will have zones, into which the shapes for your part will be added. The difficulty is that you're trying to get that data into those deeper part shapes. You can do this in a number of ways. For example, you could just add it to the outermost result of BuildDisplay and then override the Content and Zone templates so they relay the information down (unfortunately shapes don't know their own parent, something we may want to change in future versions, let me create a bug for it... there: http://orchard.codeplex.com/workitem/18875). Not a fun solution but should work. Another solution could be to invent your own convention to name properties that you add to Layout (because all templates have access to the Layout object). Or add a dictionary object to Layout. Makes sense?

Jul 30, 2012 at 9:10 PM

Thanks so much for the breakdown of how those shapes work...I think's it's definitely given me a way to only pull the data once and then pass something into the shape that I can use to dynamically assign that Id.