How do I show a list of SmartForms in Ektron - content-management-system

How do I show a list of items created using a SmartForm in Ektron Version: 9.00 SP3(Build 9.0.0.249)?
I created a SmartForm where users can put in details of job positions available. Now I want to display those positions in a list i.e. jobs available. Then when they click on a link it takes them to the details of that job.
I have zero experience with Ektron so I hope this isn't a silly question and I need stuff broken down to pretty basic levels.

EDIT: You can find this and more in-depth information on using strongly typed objects with Ektron Smart Forms at Thomas Higginbotham's blog.
POST:
The example I'm creating below is for output of images in a list, as though I were going to make a gallery. Keep in mind that there are other things I would do when actually creating a gallery, so this is by no means completed code in that regard. It is, however, a very good and simple illustration for how to retrieve and handle content from Ektron in the most .NET way possible (without a lot more separation of concerns).
First, I have some recommended reading for you. Ken McAndrew (long-time Ektron dev and now Sitecore dev) put together a nice set of posts about a class you can use to obtain Ektron's Smart Form content as .NET objects, making them much easier to work with. I recommend using Ken's version of the class (link within the blog post).
There's also a hangout featuring Bill Cava, original author of the method, Ken, and myself talking about this approach, which might help.
I looked and I think the original webinar has been taken down.
Regardless, once you have the Content Types code in place, it's relatively simple. You want to:
Create .NET models for your Smart Forms
Get the items
Map them to a ViewModel (optional)
Databind them to a ListView or Repeater control
Creating .NET Models for Smart Forms
To start this, I've created a rather simple Smart Form for a Press Photo. It contains three fields:
Name
Description (Caption)
Image (stored as a URL string for simplicity, not as an <img /> tag)
Next, click the "XSD" button in the SmartForm Data Design View (where you add/manage the fields) and copy all the XSD code. (You'll see the XSD button in the screenshot below, immediately above the modal.)
Save that to a file. I created a folder at C:\ContentTypes and added the file as PressPhoto.xsd there.
You'll want to run a file called XSD.exe (provided and documented by Microsoft as part of most Visual Studio installations). This will use the XSD and generate a C# class file for you - one that's friendly for deserializing the Smart Form XML into a .NET object.
Here's where to find it (as included with Visual Studio 2015):
However, running it from the command line is something I find painful since I always forget the rather lengthy path for it. So I created a Batch file to do most of the dirty work for me. I wrote it to accept three parameters, in order:
The path to the XSD file
The output path where I'd like to store the generated class file
The namespace I want for the generated class (tip: I always set the Namespace to include the name of the Smart Form - this prevents conflicts if you're generating classes for multiple Smart Forms)
Here's the code and a screenshot of the command to execute it.
#ECHO OFF
SET xsdExePath="C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\xsd.exe"
SET xsdFilePath="%~f1"
SET outFolderPath="%~f2"
SET ns=%3
%xsdExePath% /c %xsdFilePath% /o:%outFilePath% /l:CS /n:%ns%
This generates a file named PressPhoto.cs within my C:\ContentTypes\ directory.
Going into my Ektron project, I'm going to add Ken's SmartForm class (referenced in his blog above) and my new PressPhoto class within a directory in my App_Code folder. Like so (folder structure beneath App_Code/CSCode is up to you, but keep it organized for your own sanity):
Add a class for each Smart Form you want to pull via API (for me, that's all Smart Forms).
I know this is seemingly a lot of prep, and compared to some other, more modern systems, it is. But this will help you immensely once you're in the habit.
Get the Items (and Map to a ViewModel)
Now that you've basically created your own APIs for getting the items (the alternative is XML Transforms, btw), you're ready to use them.
I prefer to create my own "Manager" class as well as ViewModel. If you'd prefer a different approach, then this will at least give you the sample code to move in your own direction.
I'm putting comments about the code in-line so you can read them in context.
View Model - very basic
namespace MyProject.ViewModels
{
/// <summary>
/// Provides the fields necessary to display a PressPhoto Smart Form to the site.
/// </summary>
public class PressPhotoViewModel
{
public string Title { get; set; }
public string Description { get; set; }
public string ImageUrl { get; set; }
public string ContentUrl { get; set; }
public long ContentId { get; set; }
public PressPhotoViewModel()
{
}
}
}
And the "Manager" class so I put as little code into the code-behinds as possible. A nice, centralized location for all my PressPhoto logic.
using Ektron.Cms;
using Ektron.Cms.Content;
using Ektron.Cms.Common;
using Ektron.Cms.Framework.Custom;
using MyProject.SmartForms.PressPhoto;
using MyProject.ViewModels;
using System.Collections.Generic;
using System.Linq;
namespace MyProject.Managers
{
/// <summary>
/// Provides CRUD operations for managing PressPhoto objects within the CMS.
/// </summary>
public class PressPhotoManager
{
/*
* "root" is the default root element of the Smart Form XML.
*
* If you've changed this, your code will be different. Most people don't,
* so I'm going with the lowest-common-denominator example.
*
* I normally set the "root" to something else, and thus give it a more
* meaningful class name. I also tend to load this manager via something more
* similar to a singleton, but for simplicity's sake...
*/
private SmartFormManager<root> pressPhotoManager = new SmartFormManager<root>();
public PressPhotoManager()
{
// Nothing needs done here for this example.
}
/*
* Usually, I'm not interested in writing information back to the DB, so
* I'm only going to include samples for GetItem and GetList (by folder) here.
*/
/// <summary>
/// Retrieves a PressPhoto item by its Ektron Content ID
/// </summary>
/// <param name="ContentId">Ektron Smart Form Content Id</param>
/// <returns>Press Photo ViewModel for display</returns>
public PressPhotoViewModel GetItem(long ContentId)
{
/*
* Get items - this returns an object that is the amalgamation of the standard
* Ektron ContentData object and the deserialized Smart Form information.
*
* The format is:
* * systemObject.Content = standard ContentData fields
* * systemOjbect.SmartForm = Smart Form fields
*
* For some reason, the returned object in the custom class inherits from ContentData.
* This inheritance is probably not necessary and is also likely confusing. So only try
* to use the fields within the containers listed above.
*/
var systemObject = pressPhotoManager.GetItem(ContentId, false);
if (systemObject == null) return null;
/*
* I often want to map both Smart Form and ContentData fields to my output. This is where
* a ViewModel comes in handy - it normalizes the data to be rendered. So then I'm not
* dealing with object.Content.* and object.SmartForm.* during rendering.
*
* Another note: You might consider putting this mapping into a constructor in
* the ViewModel in order to avoid the repetition you'll find in the GetList method
* below.
*/
return new PressPhotoViewModel()
{
ContentId = systemObject.Content.Id,
ContentUrl = systemObject.Content.Quicklink,
Title = systemObject.SmartForm.Name,
Description = systemObject.SmartForm.Caption,
ImageUrl = systemObject.SmartForm.Photo
};
}
/// <summary>
/// Retrieves a list of PressPhoto by the containing Ektron Folder Id (non-recursive)
/// </summary>
/// <param name="FolderId">Ektron Folder Id</param>
/// <returns>Enumerable of Press Photo ViewModel for display</returns>
public IEnumerable<PressPhotoViewModel> GetList(long FolderId)
{
/*
* There are several "Criteria" objects. This is the simplist, but they also exist
* for retrieving items from a Collection, from Taxonomy, or given a certain
* Metadata property value.
*/
var criteria = new ContentCriteria();
// Filter tells the API which folder to retrieve content from.
criteria.AddFilter(ContentProperty.FolderId, CriteriaFilterOperator.EqualTo, FolderId);
// Don't check sub-folders.
criteria.FolderRecursive = false;
/*
* Retrieve only 12. The default is 50. Get in the habit of setting this so you
* don't grab 50 when you only need one.
*/
criteria.PagingInfo = new PagingInfo(12);
// Only return metadata when you need it, for performance. Default here is false.
criteria.ReturnMetadata = false;
// Ordering FTW! Hopefully self-explanatory.
criteria.OrderByField = ContentProperty.Title;
criteria.OrderByDirection = EkEnumeration.OrderByDirection.Ascending;
// Same as above...
var systemObjectList = pressPhotoManager.GetList(criteria);
if (systemObjectList == null || !systemObjectList.Any()) return null;
return systemObjectList.Select(p => new PressPhotoViewModel()
{
ContentId = p.Content.Id,
ContentUrl = p.Content.Quicklink,
Title = p.SmartForm.Name,
Description = p.SmartForm.Caption,
ImageUrl = p.SmartForm.Photo
});
}
}
}
Databinding to a List Control
For simplicity, I'm going to put this code into a .NET User Control.
Markup:
<%# Control Language="C#" AutoEventWireup="true" CodeFile="Gallery.ascx.cs" Inherits="Components_Controls_Gallery" %>
<asp:ListView ID="uxPhotoGallery" runat="server" ItemPlaceholderID="itemPlaceholder">
<LayoutTemplate>
<ul>
<asp:PlaceHolder ID="itemPlaceholder" runat="server" />
</ul>
</LayoutTemplate>
<ItemTemplate>
<li>
<%--
I'm mixing up two different ways of referencing the incoming data. One is by casting
the DataItem to the incoming type, which gives you intellisense access to the properties.
The other is more of a dictionary approach in which you have to type out the property name
as a string.
I really like the casting approach, but it's mega-wordy.
--%>
<a href="<%#((MyProject.ViewModels.PressPhotoViewModel)Container.DataItem).ImageUrl %>">
<img src="<%#((MyProject.ViewModels.PressPhotoViewModel)Container.DataItem).ImageUrl %>" alt="<%#Eval("Description") %>" />
<div><%#Eval("Description") %></div>
</a>
</li>
</ItemTemplate>
</asp:ListView>
Code:
using MyProject.Managers;
using System;
using System.Linq;
public partial class Components_Controls_Gallery : System.Web.UI.UserControl
{
protected void Page_Load(object sender, EventArgs e)
{
var pressPhotoManager = new PressPhotoManager();
// Whichever folder Id...
var photos = pressPhotoManager.GetList(75);
if (photos != null && photos.Any())
{
uxPhotoGallery.DataSource = photos;
uxPhotoGallery.DataBind();
}
}
}
And... that should do it. The best way I know how, in any case. There are certainly ways that require less code and prep, but this is by far my preferred as it keeps me working as much as possible with real .NET objects. Though you could use LinqToXML or other techniques, I prefer to not use them as it requires you to develop a lot of your own knowledge of the XML and never reads as well as this, imo.
Happy coding.

Related

Thymeleaf form with multiple objects of the same class

Simple problem but can't find a solution: I have a Thymeleaf form used to add a new object, say of a Book class. It works perfectly well and I only need that particular form for adding new objects, not editing the existing ones. The question is: how can I put several objects of the Book class in the same single form? So, purely for convenience, instead of filling form for a single book and clicking Send you can fill form for several books at once and only then click Send, have them all inserted into the database (in whatever order) and also have the option to fill the form partially (e.g. the form has room for 5 books but it will also accept 1, 2, 3 or 4 and you can leave the rest blank).
Edit: I've tried passing a list of object to the Thymeleaf template with the form bound to the whole list and iteration inside, but Thymeleaf throws BingingResultError upon rendering it.
You need to use a wrapper object to realize what you want.
Something like:
public class BooksCreationDto {
private List<Book> books;
// default and parameterized constructor
public void addBook(Book book) {
this.books.add(book);
}
// getter and setter
}
Then you need to pass this object as a model attribute in your controller:
BooksCreationDto booksForm = new BooksCreationDto();
model.addAttribute("form", booksForm);
bind fields using index property
th:field="*{books[__${itemStat.index}__].title}"
and get back the result with
#ModelAttribute BooksCreationDto form
in your controller.
For a complete and detailled explaination visit: https://www.baeldung.com/thymeleaf-list

Typo3 Extension PHP View

Using the infos in this link:
https://docs.typo3.org/typo3cms/ExtbaseFluidBook/8-Fluid/9-using-php-based-views.html
I try to create an action to output a JSON.
I have a normal controller with the list action:
public function listAction()
{
$storelocators = $this->storelocatorRepository->findAll();
$this->view->assign('storelocators', $storelocators);
}
And in ext/my_storelocator/Classes/View/Storelocator I have a class List.php:
<?
class Tx_MyStorelocator_View_Storelocator_List extends Tx_Extbase_MVC_View_AbstractView {
public function render() {
return 'Hello World';
}
}
All I get is:
Sorry, the requested view was not found.
The technical reason is: No template was found. View could not be resolved for action "list" in class "My\MyStorelocator\Controller\StorelocatorController".
So I guess there is something wrong with the paths. Or where is the Problem?
Edit: Extensioninfos
Vendor: My
key: my_storelocator
controller: NOT SURE (I created it with the extension_builder so I guess my controllers name is Storelocator)
action: list
From my understanding a classname like Tx_MyStorelocator_View_Storelocator_List should be correct. But its not working
You will need to create an empty file for the HTML view for your controller, e.g. Resources/Private/Template/Storelocator/List.html, even if you do not plan to use the HTML view or if you just return the content yourself (which is perfectly fine).
The reason for this is simply technical limitation.
First of all, TYPO3 now has a built-in JSON view, described thoroughly here: https://usetypo3.com/json-view.html. It lets you easily define which properties you'd like to render.
The error message means that your Controller is still pointing to the TemplateView - because thats the error the TemplateView throws if it can't find the defined template file.
You can specify which view to use to render within your controller. You can either set a default view via the $defaultViewObjectName property, like so:
/**
* #var string
*/
protected $defaultViewObjectName = '\TYPO3\CMS\Fluid\View\TemplateView';
You can also set it from within the Controller inside initialization actions like so:
public function initializeExportPDFAction(){
$this->defaultViewObjectName = 'Vendor\Extension\View\FileTransferView';
}
(I have, however, not yet found a way to define the template from within actions, any tips in the comments would be appreciated)
Your path syntax is probably out of date. Instead of writing a render() function in Classes/View/Storelocator/List.php, try writing a listAction() function in a Classes/Controller/StorelocatorController.php file. Extension Builder should have created this file for you, if you made an aggregate model with the usual "list, create, edit ..." and such actions.
Review A journey through the Blog Example and the following chapter, Creating a first extension, for tips.
Keep in mind that there is a mismatch between the documentation and the Extension Builder generated PHP code files. Developing TYPO3 Extensions with Extbase and Fluid has some parts up to date, and other parts still using old syntax.

October CMS - deferred bindings with images that aren't attachments

I'm using the October CMS and I'm having some trouble with deferred bindings.
I have two tables: products and product_images. I've split up my backend form into two tabs, one for product details and one for the product images:
I have my relationships set up correctly and use the following code (placed in a partial) to render the product images list:
<?= $this->relationRender('product_images'); ?>
The images tab looks like this:
The problem happens when I try to create a new image. When saving the image from the image modal, I get this exception:
I understand why there would be a constraint violation: The main record hasn't been saved yet, so there's no id for the image record to reference. In other words, the product image can't be associated with the product because the product doesn't exist yet.
The OctoberCMS documentation on deferred binding hints at a solution. But the documentation also states,
Deferred bindings are supported in the back-end Form behavior
automatically
Indeed, I haven't explicitly written any back-end form processing code. So even if I wanted to follow the instructions on deferred bindings, I wouldn't know where to put it. Any suggestions?
UPDATE:
In my config_relations.yaml file, I've set deferredBinding to true, but it made no difference:
product_images:
label: Image
deferredBinding: true
My products controller looks like:
class Products extends \Backend\Classes\Controller
{
public $implement = [
'Backend.Behaviors.FormController',
'Backend.Behaviors.ListController',
'Backend.Behaviors.RelationController'
];
public $formConfig = 'config_form.yaml';
public $listConfig = 'config_list.yaml';
public $relationConfig = 'config_relation.yaml';
public function __construct()
{
parent::__construct();
BackendMenu::setContext('MyPlugin.Products', 'products');
}
public function index()
{
$this->makeLists();
$this->makeView('index');
}
I do not have a product_images controller. I'm not sure why. Is that the issue?
My mistake was that I had put constraints on the product_id column in the product_images table:
$table->integer('product_id')->unsigned();
$table->foreign('product_id')->references('id')->on('me_myplugin_products');
Apparently I need to allow that column to be null. Changing it to this worked:
$table->integer('product_id')->nullable();

symfony2 forms custom fields

So, I have a form for editing blog articles.
Among other things I need to be able to edit article tags. They are stored as ArrayCollection inside my Blog entity. (ManyToMany cascade: persist,remove)
Now, Simfony handles this type of data with <select> tag and it works just fine for selecting, but I want to be able to remove and add tags too.
This is also possible and is very well explained in this Cookbook article: How to Embed a Collection of Forms
However, result of this tutorial is still not very elegant and I would love to have input box similar to StackOverflow tag box.
Since there are many already done solutions under free licences I decided to just use one of them, for example jQuery Tags Input.
Basically, all I need to do is run $('#tags_input_box').tagsInput() and it transforms it into SO-like tag box.
Now, I'm searching for the easiest way to bind some custom made input to my form and submit it back together with the rest of 'genuine' fields in a shape that will be understood by Symfony2.
Could anyone refer me to some document or give me some starting info where I should begin my research on this matter ?
It appears that plugin sends it in as a comma-separated string value.
Probably the easiest way would be to simply treat it as a single input in your form, and then split it up when you process the form.
// Entity to hold it in string form.
namespace Some\Entity;
class TagStringEntity {
protected $tagString;
// getTagString and setTagString
}
// Custom form type.
// Use this AbstractType in your form.
namespace Some\Form;
Symfony\Component\Form\AbstractType;
class TagType extends AbstractType {
public buildForm(FormBuilder $builder, array $options) {
$builder->add('tagString'); // will default to text field.
}
}
// In Controller
public function displayFormAction() {
// Join the tags into a single string.
$tagString = implode(',', $article->getTags()); // assuming it returns an array of strings.
$tagStringType = new TagStringType();
$tagStringType->setTagString($tagString);
// build form, etc...
}
public function checkFormAction() {
// ...
if ($form->isValid()) {
// Get the tag string, split it, and manually create your separated tag objects to store.
}
}
That's probably the cleanest and simplest way to do it using that jQuery plugin. Takes a bit of working around since you are turning multiple items into many and vice versa, but not too bad.

ASP MVC 2 Pattern for implementing CanExecute style commands

I have come from WPF (MVVM) background and trying to shift to MVC 2. Is there any pattern in MVC2 where you can use Commanding/Command buttons like <input> which you use to submit the form so that you can hide/disable when you try to Render the View.
In MVVM world, your commands could implement ICommand interface, and it had CanExecute method which was quite useful. I was wondering if there is anything similar in ASP MVC 2 ?
The only way I can think of, is to do it in the View, so that I can check the flag on ViewModel (CanSave) and depending on that show/hide the <input> tag.
Basically I want to have 2 version of the website running, one in Read-Only mode and the other Editing mode.
Let me know if you need any clarification.
ASP.NET MVC does not feature the notion of 'controls', as are found in classic ASP.NET and WPF. The foundational blocks of ASP.NET MVC are HTML elements, like <input>, <button> et cetera. Naturally, these don't offer the functionality you're looking for (i.e. Implementation of the ICommand Interface).
The scenario that you're looking at (i.e. two modes of your form) can be (and arguably should be) dealt with at the View level. You're already facing the right direction: have a 'CanSave' property on your Model, and use this in the View to determine what is generated.
Example:
<% if (Model.CanSave)
{ %>
<p>First Name: <%= Html.TextBox("firstname", Model.firstname) %> </p>
<% }
else
{ %>
<p>First Name: <%=Model.firstname %></p>
<% } %>
You'll probably want to check out the DisplayTemplates and EditorTemplates... very handy for this scenario. Brad Wilson does a good job here.
It will help you move to this:
<%= (Model.CanSave) ? Html.EditorFor(x => x.firstname) : Html.DisplayFor(x => x.firstname) %>
...which makes your View clean and nice.
If you can't get MVC to do this it's relatively worth it to hand-code something like this vb-style pseudocode. This involves...
Subclassing your controls.
Not as much of a pain as it sounds, but, it is a medium sized one. Therefore it is only appropriate for medium-sized to large apps. But worth it for them.
Interface BaseUIControl
Property Enabled as Boolean
Property Visible as Boolean
Property Name as String
Property EntireStateAsXML as string ' You can use this to do EVERYTHING!
Interface UserActionItem
Event Clicked(sender as UserActionItem ... don't pass anything from UI namespaces!)
Class MyButton (or link, etc.) Implement BaseUIControl, UserActionItem Inherits UI.Button
How does this help? You've basically replaced the missing functionality. Your Controller (or even application layer) can be aware of the UI components by interface only, so they won't have to see the UI types.
more...
You can leverage this philosophy to control everything. This has saved me thousands of hours of monkey code.
Interface TextControl
Property Value as text
Interface CheckControl
Property Checked as boolean
The above two are Pretty basic - you inherit MyCheckBox and MyTextBox from the UI versions and implement the appropriate.
Of course you could set up common code to loop thru all controls and auto-validate (or loop thru and get each one's XML to autobind the whole form).
Interface ValidationBase
Property Required as Boolean
If you have a text or numeric-only mask or restricitons built into 2 subclasses...
Interface ValidationNumeric
Property MinVal, MaxVal as double
Interface ValidationText
Property MinLen, MaxLen as double
No, it won't go to the database for you. But this sweeps a ton of crud under the rug.
You can even set these property values in the UI designer - yes, putting BL in bed with UI, BUT, if you only have one UI for the BL, actually works very well.
Now image a UI with a mix of things like listbox/multiselect, double-list picker controls, checked listbox, a groupbox of option buttons/checkboxes ...
Interface Selector
property Items as list (of string)
property SelectedItems as list (of string)
Use what works on the UI - your generic routines can care less what they look like!! The subclassed UI pieces will just implement them to set/get the right values.
In addition ... we added 'validationEquation', ActivatesEquation (gray/ungray), SetValueTriggerEquation (if true, set value to SetValueEquation, otherwise, leave alone), which allowed controls to be set to simple values from other items (basically getting the values from bound objects as if using reflection) via Pascal Gayane's Expression Evaluator (it reads .net types!)
You can also subclass the main form, have it recurse thru all it's subcontrols, put together the XML's for the whole screen, and serialize it like that. You can have your own classes implement these in the non-UI layers and use it to totally (de/)serialize the UI state, and use them to read the UI too, if they relate to a business object, to map to it.
It's unbelievable how much this simplifies a complex app. We have one with 1200+ data entry panels (... pages... ours is a thickclient app) that will fill out 250 different paper forms at 250K LOC. The form definitions contain the 'name' of each control and this is pulled from the XML generated from the screens. We probably saved 500K LOC as many of the screens have no code behind them or only trivial code; all the databinding, validation, etc. is handled by common routines that reference the interfaces.
Like I say, this only works for a big app. Spend at least 2-3 weeks developing 90% of the functionality, though; probably another month throughout the 2 years dev maturing it. I am guessing your apps is big if you're caring about ICommand and its conveniences. I would put the payback at 15-20 moderately complex pages.
If I'm understanding the question correctly, you could write a ControllerCommand class to encapsulate this. Something like this:
public class ControllerCommand
{
public string Action { get; set; }
public string Controller { get; set; }
public object RouteValues { get; set; }
public bool IsEnabled { get; set; }
}
Your Details viewmodel might use it like this:
public class DetailsModel
{
public guid Id { get; set;}
// some other viewmodel properties
public ControllerCommand Edit { get; set; }
}
You could write extension methods on HtmlHelper to replace the built-in ones:
public MvcHtmlString CommandLink(this HtmlHelper html, string linkText, ControllerCommand command, object htmlAttributes)
{
if (command.IsEnabled)
{
return html.ActionLink(linkText, command.Action, command.Controller, command.RouteValues, htmlAttributes);
}
else
{
return MvcHtmlString.Create(linkText);
// perhaps return <span class="disabled-command">linkText</span>
}
}
One of the ways I have found is to use Filter attributes which you can put in your Actions, but that only handles CanExecute on the server side.
For the GUI side, couldnt find better way than putting If statements to check if the user is Priviliged to run particular action (i.e. Edit/Delete buttons)