Closed CrispinF closed 6 years ago
This is another excellent request and is a problem I've had many discussions about and actually already put a lot of thought into an implementation design that I will elaborate on here to see if you think it meets your needs.
This is another thing I would like to implement but it is non-trivial and will take some time investment. I would probably eventually get to it on my own time but it would be ideal if we could work out some financial sponsorship for the development effort to make it a priority and deliver it in the near future.
This is something I think we only need for Pages, not Blog posts.
We want to keep the existing simple way to just directly create and edit content that we already have but allow for more helpful ways to edit content so that it can be formatted any way we want it without the user having to do work to achieve the layout. So the idea would start with introducing a new icon next to the current icons for new page and edit page, the new icon would be for "New Page From Template", and clicking that the user would be presented with a list of available templates with names and descriptions and would be able to click one of those to get started. It would possible via configuration to plugin additional custom templates that would be available in the list, and the custom templates could be globally available or tenant specific. The template would provide a custom editing experience with any number of custom fields and html editors for as many different parts as needed so that it is clear to the user where to put things and the user doesn't have to worry about the final layout which the template will take care of.
A custom template would consist of:
The Page class and table would get some additional fields initially populated based on the template to capture the following:
For editing we will obtain the following information by looking up the template with the template key
The presence of data in the above fields is how the code would know how to orchestrate the editing and rendering process. If not populated then it is just a normal content page. The edit link will just link to the normal edit page but will be redirected to a new controller action for editing templated pages if the page has a template specified.
At edit time if those fields have data then the model would be deserialized and the template edit razor view would be used to edit the model. Upon save the serialized model would be updated and possibly the render view could be used to pre-render the content into the existing main content field. This needs a little investigation because we currently do this for email with razor but that uses a layout file and renders a full html document whereas what we would want here is to just render an html fragment with a partial view. We can propbably manage this by using an empty layout file with page template render views. Alternatively we can just render the page on each request by deserializing the model and passing it to the render view partial.
There would be standard models and interfaces for defining the template itself and these would be defined and plugged in via appsettings.json and deserialized from there to produce the list of templates that the user can choose from. Something along the lines of this json:
"ContentTemplateConfig": {
"Templates": [
{
"TenantId": "*",
"UniqueKey": "someuniquevalue",
"Title": "Some title",
"Description": "Some description to help the user pick the template",
"ModelType": "some fully qualified model type",
"EditView": "The name of the edit razor view file",
"RenderView": "The name of the render razor view file"
"EditScripts" : [
{
"Url" : "/somefile.js",
"Environment" : "any",
"Sort" : "1"
}
],
"EditCss" : [
{
"Url" : "/somefile.css",
"Environment" : "any",
"Sort" : "1"
}
]
},
{
"TenantId": "*",
"UniqueKey": "someotheruniquevalue",
"Title": "Some other title",
"Description": "Some other description to help the user pick the template",
"ModelType": "some fully qualified model type",
"EditView": "The name of the edit razor view file",
"RenderView": "The name of the render razor view file"
}
]
}
The * would be used for TenantId to make a template globally available, and site id could be used to make it only available for a given site.
I will try to implement something re-useable as a helper to be able to derive the model from posted form fields. We may need to make it possible for a given template to plugin its own implementation to bind the model in case the default implementation doesn't meet the specific needs of the given template.
I think this would make a pretty open ended solution allowing developers to use arbitrary models and views that they create themselves.
I will implement and include a simple template that could be used for a Staff profile page with the scenario of one page per profile. The template will ensure that each page is formatted consistently.
I will try to implement a more complex template sample for managing a small list of Staff Profiles on a single page. This one will likely require some custom javascript and css, and some hidden field conventions for finding new items added to the list on postback. Possibly it will require implementing a template specific plugin to create the model from the posted form elements, but I will try to implement a generic solution first.
Estimating is hard, I'm not good at it, things often take less or more time than expected, more often they take more time than I originally guess. Sometimes things go smoothly and progress rapidly, other times unanticipated challenges emerge.
My ballpark estimate for the main system is 2-4 weeks of effort. The more complex template example with custom javascript to manage a list could take 2-5 additional days after implementing the main system, but would be a valuable addition for other developers to model their own templates on.
There will be some challenges with handling the save of custom models, as it posts back to a controller action that doesn't really know about the specific model. So there would have to be some logic to get the model from the Request Form variables. and server side validation would be a little tricky as well but can be done once we get the model from form variables. The model would be like any typical view model in that required fields should be decorated with data annotation attributes that can be used for both client side and server side validation.
I think to solve that the page needs to be initialized and saved with an empty model upon selection of the template, so page title will need to be provided at the same time as template selection. That way all future saves are edits and we can figure out the expected model based on just looking up the page and getting the information about the model type in order to know what we are looking for in the form variables that are posted.
@joeaudette interesting - will take a little time to absorb and reflect.
Example: how about staff pages. widely applicable with some simple properties: firstname, lastname, phone, mob, email, profileURL (?), image, department (optional), position, and perhaps some free html (biog?). This one highlights a question though... would your model allow for multiple instances of a template on a page. Staff pages might easily house multiple "cards" each using the template.
Another template might be an image carousel, with image & caption (repeated).
@CrispinF content templates are not something I envision for full blown features like staff directory or image gallery. Something like that could be developed like spa apps and plugged into a page as js and css.
You could in theory have a model with a list and upon post back update the whole model in one go. But processing changes to items in a list would be quite challenging. Probably would have to have additional interfaces defined to attach to a template to allow plugging in custom server side code to process the form variables. I would not want promise I could write some magic code that can handle any case like that, but could develop an interface approach that allows developers to plugin their own logic to "try" that kind of approach.
A more reasonable use case would be if you had a page per staff member and you wanted them to all have the same layout, a template could solve that. So the staff directory would be a series of individual pages hanging of a page that lists the child pages and maybe had a group photo on the index page.
or a template for a Product model would allow using SimpleContent pages for product details, with one product per page for example. It isn't going to magically support a complicated product catalog feature,
To clarify I'm not saying it would be impossible to manage small lists as part of the object model, just tricky, I'd certainly be willing to try to make an example that does that like a staff directory, but I think that approach would not hold up for very large lists.
I also think the templates need to support adding scripts and css defined in the template model in appsettings to the edit page. That way the editing for a given template could be enhanced with client side logic, maybe to setup hidden fields for any changed or new list items and to append html for them to display items added to the list when editing but before postback. I've updated my design post above to include edit scripts and css.
Many thanks to exeGesIS Spatial Data Management for agreeing to sponsor this improvement! Work to implement this will begin soon.
Wanted to give a status update on this as work is nearing completion.
The templating system and the content history/versioning work are both completed and currently I'm working on actual templates. I will say that the views for the templating system and history etc are implemented for both bootstrap 4 and bootstrap 3, however I'm not planning on implementing the templates themselves in bootstrap 3. For those using bootstrap 3 who may want to use the developed templates it will be possible to reference the bootstrap 4 templates nuget and override the views locally to adapt them to bootstrap 3 and if there is community interest in providing those back they could be bundled and shipped later.
That brings me to another point to mention. Originally the plan was for templates to be plugged in from config, and that is still true, but additionally the design allows for packaging templates in separate nuget packages as well and plugging them in with one line of code in startup. I've come up with 4 compelling templates so far that I will bundle in a single nuget package. And the sourceDev.WebApp has a local template for Staff Members as example code and that one is plugged in via appsettings.
I have solved the "list" problem and come up with a general pattern for handling lists. Basically initial list is passed to the edit templates as json that has been urlencoded and passed via a hidden field. Then using a convention with another hidden field named [PropertyName]Json where property name is the name of the List of T property on the view model. Javascript is used in the edit templates to manage the list and keep the PropertyJson field up to date with the current state of the list. On postback reflection is used to detect generic List of T properties on the template model and then we look for a posted field PropertyNameJson, and if it exists we de-urlencode it and deserialize the list from the json and assign it back to the model. It may sound complicated but it works very smoothly. I'm using knockoutjs to help manage the lists in the templates I've built so far. Their "observables" made it easy to manage the list client side and always keep the hidden field for the list in sync.
Hoping to make a google map template with content above and below as well.
If no templates exist ie none configured and none added via startup, then the new page/new post icons just work as they always have. If templates are plugged in then the user is shown the list of templates and can either choose to create content with a template, or using the link for a standard content editor. The template list page is paginated and if there is more than one page it also surfaces a search box and templates can be searched on title words. All the configured or plugged in templates are aggregated into a common list for paging and searching.
Since the templates came out very slick and friendly to use, I went ahead and implemented templating also in the blog. Individual templates can specify whether they support Pages, Blog, or * which means both so that it is possible to make templates for pages that may not be suitable for blog posts. But the gallery template for example is nice for both.
I have made the serialization pluggable and but I have only implemented a json serializer. Please let me know if you think an xml serializer is needed, I think that was mainly a search concern for a separate issue.
I have implemented a form parser and a model validator that works pretty well with the given conventions, but it is also possible to plugin a custom model form parser and/or validator per template so if someone wants to do something I've not accounted for it is possible.
I can hardly wait to ship this, I'm pretty excited how it has turned out!
one more thing on my remaining to do list is I think we need something to detect unsaved changes in templates and prompt the user if they try to navigate away from the page without saving. For example at the moment a user could create a gallery, all the images are uploaded as soon as they are dropped or selected from the device but if they navigate away then the list is not saved. I have an idea for it in the list js but probably need to research it a it and see if I can find a generic solution. It is less of an issue in the wysiwyg editor because that saves work in browser storage and can restore it.
This work is now in the master branch for anyone interested in testing
I'm very near to shipping, only remaining issue is the gallery layout is all messed up in IE 11. At first I was getting some script errors in IE 11 but that turned out to be bugs in bootstrap 4 js, fixed by upgrading to 4.1.2. The css issue in the gallery with IE 11 is that columns seem to be much taller than they should be. Bootstrap columns are supposed to wrap to a new line after every 12 columns and that works in other browsers and kind of happens in IE except that then the row height gets way tall and apparently the column heights get very tall pushing the next row of columns way down the page. When using cards, even the card heights get too tall in IE 11. The carousel layouts and other template features all seem ok and no script issues found.
I've manage to add an IE class to the body when the browser is IE, so tomorrow I will see if I can solve it with some IE specific css overrides using that class. After that I think I am ready to ship nugets. I'm bumping the major version to 4.0 since there are major new features and changes.
This work is now completed, nugets ave been published, and the project template has been updated to include the new major version.
Closing this issue, please open new issues for any bug reports or feedback.
It would be great to have a way of templating content, so that for any new page the web editor enters information into some controls on a form, and the entered values (which might include images) are rendered as html when not editing. The required ingredients for this are (as it seems to me!):
It would be most powerful if data entered through content templates became queryable "data" rather than just shaped html.