I am new to Orchard. So please forgive me if there is anything looking silly!
I want to create a custom widget for my Orchard website to encourage visitors to sign up for my Newsletter service. I have seen there is an option of using HTML widget but I want to create a new widget type like "Newsletter" which I shall use conditionally at AsideFirst block.
Is this possible to do? I only want to grab visitor's Name and Email address, and the form submission will be done using an action controller.
Do I have to create this widget through by-hand coding in VS? In fact I want to this way, not through the Orchard admin console.
Seeking for help. Any suggestion please?
Edit:
I have managed to create the widget following Sipke Schoorstra's suggestion. The area where I want to display the widget is now showing along with the the title I set from admin at the time of adding it to a zone. But the content (form elements) I created in the view is not displaying.
The View: (Views/NewsLetterSignupPart/NewsletterSignup.cshtml)
#model Emfluence.Intrust.Models.NewsLetterSignupPart
#{
ViewBag.Title = "Newsletter Signup";
}
#using (Html.BeginForm("NewsletterSignup", "NewsLetter", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="row-fluid">
<div class="span6">
<label>Name</label>
<input type="text" name="txtNewsletterUserName" required maxlength="50" style="width: 95%" />
<label>Email</label>
<input name="txtNewsletterUserEmail" type="email" required maxlength="85" style="width: 95%" />
<button class="btn pull-right">Submit</button>
</div>
</div>
}
Migration.cs
public int UpdateFrom15()
{
ContentDefinitionManager.AlterTypeDefinition(
"NewsletterWidget", cfg => cfg
.WithPart("NewsLetterSignupPart")
.WithPart("CommonPart")
.WithPart("WidgetPart")
.WithSetting("Stereotype", "Widget")
);
return 16;
}
NewsLetterSignupPart.cs
public class NewsLetterSignupPart : ContentPart<NewsletterSignupRecord>
{
[Required]
public string Name
{
get { return Record.Name; }
set { Record.Name = value; }
}
[Required]
public string Email
{
get { return Record.Email; }
set { Record.Email = value; }
}
}
And NewsletterSignupRecord.cs
public class NewsletterSignupRecord : ContentPartRecord
{
public virtual string Name { get; set; }
public virtual string Email { get; set; }
}
Where I am doing wrong?
The Custom Forms module is great if you don't want or need to code something yourself. In case you do want to handle form submissions yourself without using Custom Forms, this is what you could do:
Create a custom module
Create a migrations class that defines a new widget content type (see the docs for details on how to do this. Note: you don't need to create a custom part. You don't even need to create a migrations file to create a content type - you could do it using a recipe file. The nice thing about a migration though is that it will execute automatically when your module's feature is enabled).
Create a view specific for content items of your widget type (e.g. Widget-Newsletter.cshtml).
Inside of this view, write markup that includes a form element and input elements. Have this form post back to your controller.
Create your controller.
In the /admin interface, click Modules, on the Features` tab search for Custom Forms and click Enable. This will add a new Forms admin link on the left.
Next, create a custom content type (under Content Definition) called Newsletter, and add two fields (of type Text Field) called Name and E-mail.
Finally, click Forms and add a new Custom Form. Give it a title: this will be the default URL to access e.g. "Newsletter Form" will have a URL of /newsletter-form by Orchard defaults. Under Content Type select your newly created content type, Newsletter, from the dropdown. Customize anything else you want on this page, and click Publish Now
If you want to make this a widget, edit the content type and add the Widget Part. Create a layer with the rules you need and you can add the "Newsletter" widget to any zone you need on that layer.
Related
I am registering a custom kentico widget.
When it was created, I could not locate it in the collection of other widgets on the page tab in the admin website of the CMS solution.
I also looked for my widget in the CMS_Widget Table with no luck. Is there another table which might house custom widgets?
My Controller
using System.Web.Mvc;
using Website.Contracts;
using Company.Views.FeaturedProduct;
[assembly: RegisterWidget(OptInOptOutWidgetController.Identifier, typeof(OptInOptOutWidgetController), "Opt In Opt Out List", Description = "Lists all products that are opted in", IconClass = "icon-bullseye")]
namespace Website.Controllers.Widgets
{
public class OptInOptOutWidgetController : WidgetController
{
public const string Identifier = "Website.Controllers.Widgets.OptInOptOutWidgetController";
public IApplyLoanService ApplyLoanService { get; }
public OptInOptOutWidgetController( IApplyLoanService _ApplyLoanService)
{
ApplyLoanService = _ApplyLoanService;
}
public ActionResult Index()
{
List<StateInfo> stateInfoList = ApplyLoanService.GetStateDetails();
List<SelectListItem> statesList = new List<SelectListItem>();
OptInOptOutWidgetViewModel viewModel = new OptInOptOutWidgetViewModel();
viewModel.StateList = stateInfoList.Where(x => x.StateName != null).Select(x => new OptInOptOutStateViewModel
{
StateName = x.StateName
}).ToList();
ViewBag.States = viewModel.StateList;
return PartialView("Widgets/OptInOptOut/_OptInOptOutProductList", viewModel);
}
}
}
My View
#using Company.Models.Widgets
#model Company.Models.Widgets.OptInOptOut.OptInOptOutWidgetViewModel
#if (Context.Kentico().PageBuilder().EditMode && Context.Kentico().PageBuilder().Initialized())
{
#Html.Kentico().PageBuilderScripts()
}
<div>
#if (Model.StateList != null)
{
<p>Select State</p>
<select>
#Html.DropDownList("States", new SelectList(Model.StateList), "")
</select>
}
else
{
<p>There are no states</p>
}
</div>
I referenced this post:
Unable to Create New MVC Widget in Kentico 12
I started making updates the code base but still nothing:
The AssemblyDiscoverable was already where it was supposed to be
We have other widgets published to the CMS but there is no clear path on how to reproduce. Are there any suggestions?
The CMS_Widget table is used for Portal Engine Widgets, which are very different from MVC Page Builder Widgets.
Portal Engine Widgets are used to create the Administration UI which is still built on ASP.NET Web Forms technology.
MVC Page Builder Widgets are not used anywhere in the Administration UI, only on the Live Site.
There is no database table that records all Widgets (or any Page Builder components) defined in the Live Site. Instead, these are all discovered at runtime by the Xperience framework based on your [assembly: RegisterX(...)] component registration attributes.
Your Widget will appear in the Page Builder UI in a dialog that opens when you click the + button in a Widget Zone.
If your Widget is registered in the MVC Live Site application, you do not need the AssemblyDiscoverable attribute - that is only required when your Xperience components are in separate class libraries. AssemblyDiscoverable tells Xperience to scan the class library assembly for components - without it, scanning that assembly is skipped.
This is the markup for Content.ThumbnailSummary.cshtml, a custom DisplayType I use to render ContentItems as clickable thumbnails with their contents absolutely positioned over them.
#using Orchard.Utility.Extensions;
#{
var contentTypeClassName = ((string)Model.ContentItem.ContentType).HtmlClassify();
}
<a class="content-item #contentTypeClassName thumbnail-summary">
#Display(Model.Header)
<div class="thumbnail-summary-inner">
#Display(Model.Content)
</div>
#Display(Model.Footer)
</a>
The problem is that out of the box most Parts and Fields get rendered as links or paragraphs containing links, and nested <a> tags mess up DOM rendering pretty badly in most browsers. A ThumbnailSummary should never contain any links.
I could create alternates for every field and part, or I could remove everything by default in placement and only add rules for specific cases as I need them. But that would be pretty tedious and defeats a lot of the benefits of placement, so I was hoping I could somehow strip or replace all <a> tags in code only for shapes with this DisplayType.
I've been looking in this direction but I'm not sure if it's viable:
public class Shapes : IShapeTableProvider
{
public void Discover(ShapeTableBuilder builder)
{
builder.Describe("Content")
.OnDisplaying(displaying =>
{
if (displaying.ShapeMetadata.DisplayType == "ThumbnailSummary")
{
// Do something here???
}
});
}
}
You are almost right, instead of a provider add a class that inherits from Orchard.DisplayManagement.Implementation.ShapeDisplayEvents or implement IShapeDisplayEvents yourself.
I've done this myself to remove certain functionality from admin area that cannot be disabled via feature or permission.
The code should look like this
public class MyShapeDisplayEvents : Orchard.DisplayManagement.Implementation.ShapeDisplayEvents
{
public override void Displayed(Orchard.DisplayManagement.Implementation.ShapeDisplayedContext context)
{
if (context.Shape is Orchard.DisplayManagement.Shapes.Shape)
{
Orchard.DisplayManagement.Shapes.Shape lShape = (Orchard.DisplayManagement.Shapes.Shape)context.Shape;
if (lShape.Metadata.Type == "Layout")
{
string lChildContent = context.ChildContent.ToHtmlString();
// do something with the content like removing tags
context.ChildContent = new System.Web.HtmlString(lChildContent);
}
...
I'm learning Shopware and I got to something I can't figure out how to solve.
I'm writing a test plugin that adds an attribute to the customer. I've added the correspondent field to the Registration form and it saves its value to the db automatically, like I read somewhere in the docs.
Now I wanted to let the attribute be editable in the account profile page, after the password field. I managed to put the input there, and even show the value from the db. But when I change the value and save, the value its not updated. I don't know if it is just a matter of getting the field name right, or do I need to override something else. Or is it just not possible? Any help on how to achieve this would be greatly appreciated.
Relevant code below:
plugin bootstrap
public function install(InstallContext $context)
{
$service = $this->container->get('shopware_attribute.crud_service');
$service->update('s_user_attributes', 'test_field', 'string');
$metaDataCache = Shopware()->Models()->getConfiguration()->getMetadataCacheImpl();
$metaDataCache->deleteAll();
Shopware()->Models()->generateAttributeModels(['s_user_attributes']);
return true;
}
register/personal_fieldset.tpl
{extends file="parent:frontend/register/personal_fieldset.tpl"}
{block name='frontend_register_personal_fieldset_password_description'}
{$smarty.block.parent}
<div class="register--test-field">
<input autocomplete="section-personal test-field"
name="register[personal][attribute][testField]"
type="text"
placeholder="Test Field"
id="testfield"
value="{$form_data.attribute.testField|escape}"
class="register--field{if $errorFlags.testField} has--error{/if}"
/>
</div>
{/block}
account/profile.tpl
{extends file="parent:frontend/account/profile.tpl"}
{block name='frontend_account_profile_profile_required_info'}
<div class="profile--test-field">
<input autocomplete="section-personal test-field"
name="profile[attribute][testfield]"
type="text"
placeholder="Test Field"
id="testfield"
value="{$sUserData.additional.user.test_field|escape}"
class="profile--field{if $errorFlags.testField} has--error{/if}"
/>
</div>
{$smarty.block.parent}
{/block}
The form type that it's used on registration isn't the same you have on profile.
If you check \Shopware\Bundle\AccountBundle\Form\Account\PersonalFormType::buildForm, you can see
$builder->add('attribute', AttributeFormType::class, [
'data_class' => CustomerAttribute::class
]);
That means the attributes are included on form and they will be persisted. That's why you can save the value on registration form.
On profile you have \Shopware\Bundle\AccountBundle\Form\Account\ProfileUpdateFormType. And here the attribute isn't added to form builder.
How to extend the ProfileUpdateFormType?
Subscribe Shopware_Form_Builder on Bootstrap (or on a specific Subscriber class)
$this->subscribeEvent('Shopware_Form_Builder', 'onFormBuild');
Create the method onFormBuild to add your logic
public function onFormBuild(\Enlight_Event_EventArgs $event) {
if ($event->getReference() !== \Shopware\Bundle\AccountBundle\Form\Account\ProfileUpdateFormType::class) {
return;
}
$builder = $event->getBuilder();
$builder->add('attribute', AttributeFormType::class, [
'data_class' => CustomerAttribute::class
]);
}
With this approach all attributes are available on your profile form.
Other possibility you have is using the 'additional' property instead of 'attribute' and then subscribe a controller event or hook a controller action to handle your custom data.
I'd like to extend the users content definition to include a short bio and picture that can be viewed on every blog post of an existing blog. I'm unsure of what the best method to do this is.
I have tried extending the User content type with those fields, but I can't seem to see them in the Model using the shape tracing tool on the front end.
Is there a way to pass through fields on the User shape in a blog post? If so, what is the best way to do it?
I also have done this a lot, and always include some custom functionality to achieve this.
There is a way to do this OOTB, but it's not the best IMO. You always have the 'Owner' property on the CommonPart of any content item, so in your blogpost view you can do this:
#{
var owner = Model.ContentItem.CommonPart.Owner;
}
<!-- This automatically builds anything that is attached to the user, except for what's in the UserPart (email, username, ..) -->
<h4>#owner.UserName</h4>
#Display(BuildDisplay((IUser) owner))
<!-- Or, with specific properties: -->
<h1>#T("Author:")</h1>
<h4>#owner.UserName</h4>
<label>#T("Biography")</label>
<p>
#Html.Raw(owner.BodyPart.Text)
</p>
<!-- <owner content item>.<Part with the image field>.<Name of the image field>.FirstMediaUrl (assuming you use MediaLibraryPickerField) -->
<img src="#owner.User.Image.FirstMediaUrl" />
What I often do though is creating a custom driver for this, so you can make use of placement.info and follow the orchard's best practices:
CommonPartDriver:
public class CommonPartDriver : ContentPartDriver<CommonPart> {
protected override DriverResult Display(CommonPart part, string displayType, dynamic shapeHelper) {
return ContentShape("Parts_Common_Owner", () => {
if (part.Owner == null)
return null;
var ownerShape = _contentManager.BuildDisplay(part.Owner);
return shapeHelper.Parts_Common_Owner(Owner: part.Owner, OwnerShape: ownerShape);
});
}
}
Views/Parts.Common.Owner.cshtml:
<h1>#T("Author")</h1>
<h3>#Model.Owner.UserName</h3>
#Display(Model.OwnerShape)
Placement.info:
<Placement>
<!-- Place in aside second zone -->
<Place Parts_Common_Owner="/AsideSecond:before" />
</Placement>
IMHO the best way to have a simple extension on an Orchard user, is to create a ContentPart, e.g. "UserExtensions", and attach it to the Orchard user.
This UserExtensions part can then hold your fields, etc.
This way, your extensions are clearly separated from the core user.
To access this part and its fields in the front-end, just add an alternate for the particular view you want to override.
Is there a way to pass through fields on the User shape in a blog post?
Do you want to display a nice picture / vita / whatever of the blog posts author? If so:
This could be your Content-BlogPost.Detail.cshtml - Alternate
#using Orchard.Blogs.Models
#using Orchard.MediaLibrary.Fields
#using Orchard.Users.Models
#using Orchard.Utility.Extensions
#{
// Standard Orchard stuff here...
if ( Model.Title != null )
{
Layout.Title = Model.Title;
}
Model.Classes.Add("content-item");
var contentTypeClassName = ( (string)Model.ContentItem.ContentType ).HtmlClassify();
Model.Classes.Add(contentTypeClassName);
var tag = Tag(Model, "article");
// And here we go:
// Get the blogPost
var blogPostPart = (BlogPostPart)Model.ContentItem.BlogPostPart;
// Either access the creator directly
var blogPostAuthor = blogPostPart.Creator;
// Or go this way
var blogPostAuthorAsUserPart = ( (dynamic)blogPostPart.ContentItem ).UserPart as UserPart;
// Access your UserExtensions part
var userExtensions = ( (dynamic)blogPostAuthor.ContentItem ).UserExtensions;
// profit
var profilePicture = (MediaLibraryPickerField)userExtensions.ProfilePicture;
}
#tag.StartElement
<header>
#Display(Model.Header)
#if ( Model.Meta != null )
{
<div class="metadata">
#Display(Model.Meta)
</div>
}
<div class="author">
<img src="#profilePicture.FirstMediaUrl"/>
</div>
</header>
#Display(Model.Content)
#if ( Model.Footer != null )
{
<footer>
#Display(Model.Footer)
</footer>
}
#tag.EndElement
Hope this helps, here's the proof:
UPDATE: I've changed the original question drastically based on Bertrand's suggestions and my own findings. Now it provides an incomplete solution in its text instead of my own blind meanderings and commentary on Orchard, which were completely WRONG!
I need to display a menu using images instead of text, one standard, and another for when hovered/selected. The requirements for the site states that the end-user should be able to manage the menu item images. The standard navigation module now provides an HTML menu item, which is not what the end user wants. The customer wants a very simple, intuitive interface for configuring the sites many menus, and all menus are image-based.
Based on Bertrand's advice, and after realizing that Content Menu Item IS A CONTENT TYPE, I've created a new Content Part in the Admin Interface (not by code, I only want to write code for parts and content types when ultimately needed... I really want to see how far I can go with Orchard just by using the admin interface and templating/CSSing).
So, I've created a Menu Image Part, with two Content Picker fields added to it: Image and Hover Image. Then I've added this part to the Content Menu Item in the Manage Content Items admin interface.
Since I didn't write a Driver for it, the Model passed to the menu item template does not have an easily accessible property like #Model.Href... I've overriden the MenuItemLink-ContentMenuItem.cshtml with the following code so far:
#using Orchard.Core.Common.Models
#using Orchard.ContentManagement
#{
var contentManager = WorkContext.Resolve<IContentManager>();
var itemId = Model.Content.ContentItem.ContentMenuItemPart.Id;
ContentItem contentItem = contentManager.Get(itemId);
ContentField temp = null;
var menuImagePart = contentItem.Parts.FirstOrDefault(p => p.PartDefinition.Name == "MenuImagePart");
if (menuImagePart != null)
{
temp = menuImagePart.Fields.First();
}
}
<span>#temp</span>
#Model.Text
This yields the expected title for the Menu in a link, with a span before it with the following text:
Orchard.Fields.Fields.MediaPickerField
So all the above code (get the current content manager and the id of the ContentItem representing the ContentMenuItemPart, then use the content manager to get ContentItem itself, then linqing over its Parts to find the MenuImagePart (I can't use Get to get it because it requires a type and the MenuImagePart is not a type, it was created in the admin interface), then finally getting the first field for debugging purposes (this should be the Image field of the MenuImagePart I've created...)... all the above code actually got me to the Media Picker Field on my Meny Image Part...
What I'm not being able to do, and what makes me certainly a lot obtuse and stupid, is to find a way to read the MediaPickerField URL property! I've tried casting it to MediaPickerField, but I can't access its namespace from inside my template code above. I don't even know which reference to add to my theme to be able to add the following directive to it:
#using Orchard.Fields.Fields
I've finally succeeded in this task (thanks to Bertrand's direction).
UPDATE: And thanks again to Bertrand I've polished the solution which was running in circles, querying content items from the content manager when they were already available on the Model... now I'm leveraging the dynamic nature of content item, etc. And I'm finally satisfied with this solution.
It was necessary to create a new Content Part called Menu Image, then add this to the Content Type named Content Item Menu, and finally overriding the Content Item Menu template. This last part was the really tricky one. If it was not for Bertrand's directions the code bellow would have been smelly and daunting. The template ended up as follow:
#using Orchard.Utility.Extensions;
#using System.Dynamic
#{
/* Getting the menu content item
***************************************************************/
var menu = Model.Content.ContentItem;
/* Creating a unique CSS class name based on the menu item
***************************************************************/
// !!! for some reason the following code throws: 'string' does not contain a definition for 'HtmlClassify'
//string test = menu.ContentType.HtmlClassify();
string cssPrefix = Orchard.Utility.Extensions.StringExtensions.HtmlClassify(menu.ContentType);
var uniqueCSSClassName = cssPrefix + '-' + Model.Menu.MenuName;
/* Adds the normal and hovered styles to the html if any
***************************************************************/
if (menu.MenuImagePart != null)
{
if (!string.IsNullOrWhiteSpace(menu.MenuImagePart.Image.Url))
{
using(Script.Head()){
<style>
.#uniqueCSSClassName {
background-image: url('#Href(menu.MenuImagePart.Image.Url)');
width: #{#menu.MenuImagePart.Image.Width}px;
height: #{#menu.MenuImagePart.Image.Height}px;
display: block;
}
</style>
}
}
if (!string.IsNullOrWhiteSpace(menu.MenuImagePart.HoverImage.Url))
{
using(Script.Head()){
<style>
.#uniqueCSSClassName:hover {
background-image: url('#Href(menu.MenuImagePart.HoverImage.Url)');
width: #{#menu.MenuImagePart.HoverImage.Width}px;
height: #{#menu.MenuImagePart.HoverImage.Height}px;
}
</style>
}
}
}
}
<a class="#uniqueCSSClassName" href="#Model.Href">#Model.Text</a>
The only thing that I didn't understand is why I can't use HtmlClassify as an extension method with menu.ContentItem.HtmlClassify() and have to resort to calling the method as a standard static method (see the line with the comment `// !!! for some reason the following code throws´...)
Thanks again Bertrand!