Mixing dynamic fields in static parts

Topics: Troubleshooting
Feb 17, 2011 at 10:21 AM

I've a part with a EditorTemplate, the partdriver returns a shape for that specific part.
The EditorTemplate has a fieldset with div classes foreach static property in the model.

Since orchardfields can't be staticly added i've a issue.

Dynamicly added fields like this

            ContentDefinitionManager.AlterPartDefinition("MyPart", cfg => cfg
                    .WithField("MyDateTime",
                        field => field.OfType("DateTimeField")
                        .WithSetting("DateTimeFieldSettings.Display", "DateOnly"))
                );

i can't put them in the middle of the other fields.
While i know everything staticly i don't care about it being dynamic.

What are my options?

1) find a way to declare my dynamic field in my static editortemplate and don't let it render together with the part anymore
2) make everything dynamic and use the placement.info to order my fields

I'm a bit frustrated here, i don't need to use them dynamic in my "generated" views..

Coordinator
Feb 17, 2011 at 6:18 PM

I don't understand why you couldn't use placement.info.

Feb 17, 2011 at 6:33 PM

I could use placement.info, but in our situation it might be a bit too dynamic
We've many requirements, some of them being:

- add customer defined businessrules to fields with dependency's between them;
- fields as dropdown filled by services, radiobuttons, etc..
- easy useable properties in the viewmodel for view 'combined fields'
- many displaytypes for a contentpart;
- the end customer can request us to modify the generated cshtml by us or by himself even using webmatrix or so.

It's so much more "logical" when there is a static model, static viewmodel and so.

Colleagues in my team don't believe everything can be dynamic, even if i believe it perhaps could be.
Today my colleagues we're in doubt about the whole contentfield idea.

There must be a way to show the field shape using @Model.MyField ?
And don't render it always along with the part

Coordinator
Feb 17, 2011 at 7:50 PM

I don't know what you mean by "a bit too dynamic". Placement.info has been designed for that. You can use static view models if you want to. You can use parts rather than fields if that works better. You can override the rendering of content items and replace the dynamic rendering with something very static.

Feb 17, 2011 at 8:27 PM

Can u please place an example of a static viewmodel with a contentfield, because in total the team did spend around 20+ hours just finding out how to use a contentfield in a static viewmodel or even in a static viewtemplate.


And i would use parts in parts but that isn't possible, something u said earlier, so we went the field way. 
Not only that.. fields do look good having all kind of fields, beside Text and DateTime a dropdown, numeric, radiobutton, licenseplate, bankaccountnr, areacode, stuff like that.
Maybe we're just not enough into shapes yet, it's something orchard basic, strongly themeable etc..

PS: Something else, is it possible to access a ContentPart field without the myPart.Field.FindFirst(x => x.Name == "MyField") ?
But just dynamic myPart; myPart.MyField 

I did some stuff with IDynamicMetaObjectProvider before, could be useable.
http://forums.lhotka.net/forums/p/9649/45487.aspx#45487

Coordinator
Feb 17, 2011 at 8:35 PM
Edited Feb 17, 2011 at 8:35 PM

You can look at any admin screen for examples of static view models. You can either copy the field values over to a property on your view model when building it from your controller, or you can copy the field or the content item. If you stick the content item object on the view model, you can then access any part or field statically or dynamically from the view.

The ugly Lambda stuff to get to the field value is unfortunately the only way in 1.0, but we are adding a much much simpler syntax in 1.1: you'll be able to go myPart.MyField directly.

Feb 18, 2011 at 5:26 PM

Getting the field value isn't an issue,i want to display the shape that the field driver returns.. which can be a jQuery UI control.
Our views are using a static viewmodel belonging to the part..

What would the viewcode look like displaying the field's shape?
I tried many

  @Display(Model.MyProperty) ofcourse just shows the object type name.
  @Display.MyProperty want's it to be a shape and not a field?
  @Display.MyProperty(..) is for a ShapeProvider with [Shape] above.

I could just go for the IShapeProvider  way and don't use fields.

PS: I don't see the UserControl<T> being used anywhere.. 

Coordinator
Feb 18, 2011 at 9:04 PM

I still don't see why you would want to do it this way rather than through placement, especially if you also insist on using the shape.

Anyway, what happens if you call Display with the field (not the value)?

Feb 19, 2011 at 2:43 PM

just simple partial rendering by using the field directly so it's driver returns a shape.
Btw a colleguage of my created a datatype template so whenever EditorFor() is used then datatype template is used. Using a UIHint attribute etc.

We loose field functionality that way and we'll have to do things another way. Maybe in the future i can get some time to investigate further, but i know once a certain path has been taken reversing it won't be that easy.
Maybe i can alter the datatype templates in the future once i figure out how to use field 'partial views' in code.

Anyway using placement would require all display's and editors to be added using the PartDefinition being flexible.

I was just hoping u or someone from the team could show me an example of how to render a fieldshape.

@Display(Model.MyProperty) it shows the object type name, where Model.MyProperty is of type DateTimeField. 
@Display.MyProperty needed the shape, so when i create the viewmodel in the partdriver i should make it aware of the field shape and use combine functionality.
@Display.MyProperty(..) is the way i see as an workaround

Oh well for now the datatype templates if u or any the team won't / can't / don't kick me a bit into the right direction (not placement.info :)

Coordinator
Feb 22, 2011 at 12:49 AM

From a shape table provider, you can alter the placement function and replace it with your own code (don't forget to chain in a call to the existing one though). That is a way you can dynamically affect placement without necessarily having to know all the shapes in advance.

Feb 22, 2011 at 7:16 AM

That ounds like it would come in handy for dynamic placement if all shapes (parts and fields) are be dynamic..

Maybe u misunderstood my question?
I've a static part/viewtemplate where editors are declared. I want to use a field in the middle of the static editors, in others words.. i want to render a field shape staticly.

    <div class="editor-label">
        @Html.LabelFor(m => m.Bouwjaar)
    </div>
    <div class="editor-field">
        @Html.TextBoxFor(m => m.Bouwjaar)
    </div>
    <div class="editor-label">
        @Html.LabelFor(m => m.MerkId)
    </div>
    @Display(MyField)     <---- here i want the shape of my field

<div class="editor-field"> @Html.DropDownListFor(m => m.MerkId, Model.Merkenlijst.Select(s => new SelectListItem { Selected = s.Id == Model.MerkId, Text = s.Omschrijving, Value = s.Id.ToString() }), Model.IsMerkEnabled ? null : new { disabled = "disabled" }) </div> <div class="editor-label"> @Html.LabelFor(m => m.ModelId) </div>
.....
Coordinator
Feb 22, 2011 at 7:48 AM

But a field is not a shape, so either you let the framework do its job (call the driver that will build a shape out of the field, which you can then place using the placement file or a placement function), or you handle the displaying of the contents of the field yourself, manually. But you can't have it both ways. If you go for the first choice, I was giving you a way you can get placement to work even on unknown stuff. There are also all kinds of crazy stuff you can do by manipulating the shape from a handler or shape table provider. I still don't quite understand the reluctance to use placement.

Feb 22, 2011 at 8:55 PM

I'm not gonna spend any more time on this for a while, maybe with shape debuggers from 1.1 i get more into shapes.

Coordinator
Feb 22, 2011 at 9:33 PM

Hopefully, yes. We are also adding new dynamic behavior to parts and items that will enable you to access fields much more easily.

Feb 28, 2011 at 12:05 PM

Oh.... using placement isn't enough, not for our Editors..
I'm missing an EditorType for Editors.

In our pageflow (Straight-through processing) module we're re-using a part with a differend viewmodel and editor-viewtemplate depending on the currentstep in the flow. In this editor-viewtemplate i was thinking to dynamicly render the fields. It isn't even needed to have a viewmodel and editortemplate as the Controller action can build the editor.

Except that the BuildEditorContent doesn't has a support an EditorType.
We've several EditorTemplates for a specific part, within the same module.

For Display templates i can use a displayType foreach step.. and configure the placement foreah step that way.

Why... isn't it easy to call the fielddriver to render a shape and use it directly in the cshtml

Feb 28, 2011 at 12:10 PM

Or is it possible using alternates / differentiators ?

Coordinator
Feb 28, 2011 at 7:09 PM

Ah, now I understand. Sorry if I was a little slow.

Well, actually you *could* get a dependency on a specific driver and call display on it to get a shape, that you could then put into a call to Display.

But a better way to achieve your scenario might be to handle things in a handler or shape table provider, depending on the details of your scenario. I'm finishing work on a module that might give you a couple of hints on how to provide dynamic placement for example.

Coordinator
Feb 28, 2011 at 7:14 PM

Calling BuildDisplay on the content manager, passing in your field, is a more generic way to get a shape out of anything that implements IContent by the way, than getting to the specific driver...

Mar 1, 2011 at 9:46 AM

Is it an idea to add a DisplayType to BuildEditor ?

Coordinator
Mar 1, 2011 at 8:56 PM

Sure, if you think that would be a satisfactory solution for your scenario.

Mar 2, 2011 at 9:26 AM

I ment a change in the Orchard Framework.

public interface IContentManager : IDependency {
    ...
    dynamic BuildDisplay(IContent content, string displayType = "");
    dynamic BuildEditor(IContent content, string displayType = "");
    dynamic UpdateEditor(IContent content, IUpdateModel updater);
}

Not sure if UpdateEditor would need that additional parameter then
And ofcourse other classes:

    public class BuildEditorContext : BuildShapeContext {
        public BuildEditorContext(IShape model, IContent content, string displayType, IShapeFactory shapeFactory)
            : base(model, content, shapeFactory) {
            DisplayType = displayType;
        }

        public string DisplayType { get; private set; }
    }

So all the ContentPartDriver and ContentFieldDriver can get the Editor DisplayType.

public abstract class ContentPartDriver<TContent> : IContentPartDriver where TContent : ContentPart, new() {
    ...
    DriverResult IContentPartDriver.BuildEditor(BuildEditorContext context) {
            var part = context.ContentItem.As<TContent>();
            return part == null ? null : Editor(part, context.DisplayType, context.New);
        }
...

This might be an breaking change??

Coordinator
Mar 2, 2011 at 6:10 PM

Ah, sorry I misunderstood. Please file a bug for this.

Mar 7, 2011 at 11:19 AM
Edited Mar 7, 2011 at 11:26 AM

bug filed a few days ago.. :)


Because of the bug i'm not able to use the placement then.
So back to my wish, in fact partialrendering a field editortemplate within a part editortemplate.

I got it almost working using a own ShapeTableProvider with a shape that just writes to the TextWriter.

For testing... atm only for a DateTimeField
Something like this, searching the driver could be done better:

        public void Discover(ShapeTableBuilder builder) 
        {
            builder.Describe("EditorField")
                .OnCreating(creating =>
                {
                    creating.Behaviors.Add(new ZoneHoldingBehavior(() => creating.New.Create()));
                });
        }

        [Shape]
        public void EditorField(dynamic Display, dynamic Shape, ContentPart part, DateTimeField.Fields.DateTimeField field, TextWriter Output)
        {
            var driver = _drivers.First(x => x.GetFieldInfo()
                .First(y => y.FieldTypeName == "DateTimeField").FieldTypeName == "DateTimeField");

            BuildEditorContext context = new BuildEditorContext(Shape, part, _orchardServices.New);
            context.FindPlacement = (partShapeType, differentiator, defaultLocation) =>
            {
                return "FieldZone";
            };           

            var result = driver.BuildEditorShape(context);
  
            if (result != null)
               result.Apply(context);

            var fieldZone = Shape.Zones["FieldZone"];
            foreach (var item in fieldZone)
            {
                Output.Write(Display(item));
            }
....

Just a little issue with TryUpdateModel from within the Field driver, the part driver includes a Prefix in the call Html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(Prefix). The fielddriver doesn't know about this and only uses it's own Prefix.

I'll have to search for a good solution to make TryUpdateModel work, and let the field work dynamicly and staticly

Is there a way to get the TemplateInfo in the HTTP POST request?

Mar 8, 2011 at 3:46 PM

hackish, omg! i hate to hack but sometimes there is a need for it..
A ContentZone containing EditorTemplates for parts and within that those EditorTemplates there are Field EditorTemplate generated.

To fool the EditorTemplate -> RenderInternal -> DeterminePrefix i just clear the HtmlFieldPrefix... (the real hack)

            ...
var oldPrefix = Html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix; try { Html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = string.Empty; var fieldZone = Shape.Zones["FieldZone"]; foreach (var item in fieldZone) { Output.Write(Display(item)); } } finally { Html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = oldPrefix; }