I have written an Orchard Module and would like an item to appear in a Navigation list when the module is Enabled. Ideally, I would like to be able to remove the item when the Module is disabled.
Where should I hook into to for when the module is enabled and disabled?
How do I programmatically add a menu item to an already existing Navigation?
You can implement the IMenuProvider interface for this. An example implementation might look something like this:
namespace Orchard.Bar {
public class SuperMenuProvider : IMenuProvider {
private readonly IOrchardServices _orchardServices;
public SuperMenuProvider(IOrchardServices orchardServices) {
_orchardServices = orchardServices;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
public void GetMenu(IContent menu, NavigationBuilder builder) {
string position = "10";
builder.Add(T("Foo"), position, item => item.Url("http://foo.com").AddClass("someClass"));
builder.Add(T("Bar"), position + ".1", item => item.Action("Index", "Foo", new { area = "Orchard.Bar" }));
if (_orchardServices.Authorizer.Authorize(Orchard.Security.StandardPermissions.AccessAdminPanel)) {
builder.Add(T("Secure FooBar"), position + ".2", item => item.Action("Index", "Secure", new { area = "Orchard.Bar" }));
}
}
}
}
This will appear on all menus on the front end. You may want to put in the name of the menu you are targeting if you know for sure that is what it is called (default in Orchard is "Main Menu", people don't generally change it to be honest). This could be a little brittle, so you may want it customizable, either with a site setting or you could create a part that you attach to the menu content type that lets the admin specify whether to show your menu items on the said menu.
An alternative approach would be to hook into the modules enable event using IFeatureEventHandler and using the content manager to create menu items with urls and adding them to a specified Menu. I don't really recommend this approach; you lose control of the menu items (e.g. to update a url), they can be removed from the menu accidentally, you have to know the name of the Menu you are adding them to, you are more limited (cant do permissions checks etc.).
I assume you are talking about showing up on the front end. If you talking about the admin menu then check out pretty much any module for a file generally called AdminMenu.cs, plenty of examples :)
The question doesn't specify what the module does so I guess we're to assume that it creates a content type. In that case you have (at least) two options:
In the Content Type's Content Definition go to Add Parts and add the Menu part. This will allow you to add a content item to a menu from the item's content editor.
From the Navigation menu choose the appropriate Menu and select add a Content Menu Item. Note that the content type must be set as "listable" in Content Definition in order for the items to be listed as a choice.
Disabling the module should remove the item from the navigation in either case.
Related
I'm trying to create a new widget called "Image Summary Section". I'm at the very beginning stages and I'm just trying to get the widget to appear in the list of widgets when adding widgets to the page. Instead, I just get existing widgets that I didn't create:
You can see that I've created a class that implements IWidgetProperties and that I've called RegisterWidget for it. I've also created _ImageSummarySection.cshtml (though, I wouldn't expect that to be necessary just for the widget to appear in the widget selection dialog).
The top solution is for the MVC website, and the bottom solution is for the Kentico CMS. Both are running, and the browser shown is the Kentico CMS (I'm trying to add my new widget in this screenshot, but it's not in the list of widgets).
Any idea of what I'm doing wrong? How can I get my widget to appear in the list of widgets?
Additional information:
I've looked at various links, but here's one I was looking through: https://docs.kentico.com/k12sp/developing-websites/page-builder-development/developing-widgets-in-mvc/defining-widget-properties-in-mvc
I'm on the latest Kentico version, which I think is 12.0.77.
.
.
.
.
.
.
EDIT:
I just watched this video, hoping it would provide insight: https://www.youtube.com/watch?v=ljQO9on5lLM
It was more basic than I anticipated, but I did notice these two frames:
Note that it shows six available widgets to select from.
And then there was this frame:
It shows only two available widgets.
From that, I infer that sections may have some feature that allows developers to constrain which widgets are allowed in them. Is there perhaps something I need to do in order to allow my widgets to appear as options in the default section (the one shown below)?
.
.
.
.
.
.
EDIT #2:
I researched widget constraints a bit and found this: https://docs.kentico.com/k12/developing-websites/page-builder-development/creating-pages-with-editable-areas-in-mvc
Specifically the section titled "Limiting widgets allowed in an editable area", which says the following:
Since my view is not passing a parameter with a whitelist of widgets, all widgets should (in theory) be allowed:
#* Index.cshtml *#
#using Kentico.PageBuilder.Web.Mvc
#using Kentico.Web.Mvc
<h1>Rhythm Agency</h1>
#Html.Kentico().EditableArea("main")
So there goes that theory. I'm still at a loss as to why my new widget isn't appearing as an option when adding new widgets to the page.
For the controller and widget to be recognized you need to put your controller in the '/Controllers' folder. I have my widget controllers located in the '/Controllers/Widgets' folder.
I had issues which included not having added the suffix 'Controller' in the class name and issues with the widget controller not being in the '/Controllers' folder.
Also you aren't working in an seperate project? Because this would need you to use the following in the 'AssemblyInfo.cs'
using CMS;
[assembly: AssemblyDiscoverable]
And make sure you have enabled the page builder feature in your kentico project. For example:
protected void Application_Start()
{
...
// Gets the ApplicationBuilder instance
// Allows you to enable and configure Kentico MVC features
ApplicationBuilder builder = ApplicationBuilder.Current;
// Enables the preview feature
builder.UsePreview();
// Enables the page builder feature
builder.UsePageBuilder();
...
}
You're almost there. You need to create another class and register your widgets in the App_Start folder. Check out the documentation here on that. It's the section on widget registration. Be sure to enable Page builder as well.
*** Updated ***
Based on your update and not being able to see the image well on my mobile device, I was able to see you're defining/registering your widget in your Properties model. This needs to be done in the Controller. See the example below.
\Models\Widgets\JobListingWidgetProperties.cs
namespace NameSpace.Models.Widgets.JobListingWidget
{
public class JobListingWidgetProperties : IWidgetProperties
{
// property definitions here
}
}
\Models\Widgets\JobListingModelView.cs
namespace NameSpace.Models.Widgets.JobListingWidget
{
public class JobListingWidgetViewModel
{
// properties here
}
}
\Controllers\Widgets\JobListingWidgetController.cs
[assembly: RegisterWidget("NameSpace.Widgets.JobListingWidget", typeof(JobListingWidgetController), "Job Listing Widget", Description = "Displays a listing of jobs for a given path", IconClass = "icon-heartshake")]
namespace NameSpace.Controllers.Widgets
{
public class JobListingWidgetController : WidgetController<JobListingWidgetProperties>
{
public ActionResult Index()
{
// code here
}
}
}
I'm using Acumatica 2020R1 and I'm adding side panels. Is there anyway, for example on the PO form (PO301000), that I could link a side panel to update when clicking on the Document Details?
I actually discovered a way to do this on Acumatica 2020R1. Here's how I did it:
Edit: I did this for the SOOrderEntry screen, creating a side panel that looks at the SOOrder but uses parameters from the SOLine. This method will work for any two tables with a master-detail relationship by replacing SOOrder & SOLine accordingly.
Part A:
Create virtual fields on the SOOrder DAC that correlate to the SOLine Parameters you need
Update these as lines are selecting/updated using event handlers
pass these fields as the params to your side panel actions as you normally would
While It seems like this alone would work, the side panel actually doesn’t refresh when a new SOLine is selected. We’re going to have to get creative to resolve this issue.
Part B:
Create an additional virtual field on the SOOrder DAC to keep track of which side panel was last opened (I will explain how to track this later). I use a string field to do so. This is especially important if you have more than one side panel on your screen.
Next you want to define a standard graph action that will update this field. Make it Visible = false, since you don’t actually want this button to show. We will call if from behind the scenes later. This is the method I use:
public PXAction<SOOrder> SidePanelCallback;
[PXButton]
[PXUIField(DisplayName = "SidePanelCallback", Visible = false)]
public virtual IEnumerable sidePanelCallback(PXAdapter adapter)
{
SOOrder doc = Base.Document.Current;
if (doc == null) return adapter.Get();
var docExt = doc.GetExtension<SOOrderExt>();
docExt.UsrSidePanelDetailParamsRefresh = adapter.CommandArguments;
return adapter.Get();
}
*Note: docExt.UsrSidePanelDetailParamRefresh is the variable I for the previous step
Now you need a way to trigger your code when your side panel actions are called. To do this we’ll use the following JavaScript
function commandResult(ds, context) {
var ds = px_alls['ds'];
switch (context.command)
{
case ('JustAction'):
ds.executeCallback('GraphAction', 'parameter value');
break;
}
}
You want to replace JustAction with the internal name of your side panel action. it should look like this: NavigateToLayer$DB000028
where DB000028 is the target ScreenID for your sidepanel. If you want to make sure your actions follow the same naming convention, you can check it by adding breakpoints and checking with the debugger.
You should replace GraphAction with the name of the graph action you created in the previous step. In our case SidePanelCallback
Then finally for the parameter value pass whatever you’d like to use to identify the opened side panel. This will be stored in UsrSidePanelDetailParamRefresh, or your equivalent.
You can create a case statement for each side panel you want this functionality for, so you only have to do all this once per graph!
Then finally, attach your script to the client event ‘commandPerformed’ on your data source.
Next we will define another graph action to do the actual refresh of the side panel. Mine looks like this:
public PXAction<SOOrder> SidePanelRefresh;
[PXButton]
[PXUIField(DisplayName = "SidePanelRefresh", Visible = false)]
public virtual IEnumerable sidePanelRefresh(PXAdapter adapter)
{
SOOrder doc = Base.Document.Current;
if (doc == null) return adapter.Get();
var docExt = doc.GetExtension<SOOrderExt>();
Dictionary<string, string> parameters = new Dictionary<string, string>();
string[] args = adapter.CommandArguments.Split('*');
switch (docExt.UsrSidePanelDetailParamsRefresh)
{
case ("Component Inventory"):
parameters.Add("Kit", args[0]);
parameters.Add("SiteID", args[1]);
throw new PXRedirectToGIRequiredException("Component Inventory", parameters) { Mode = PXBaseRedirectException.WindowMode.Layer };
}
return adapter.Get();
}
Note that for my use case the parameters I am passing cannot contain the character ‘*’, so I use it as a delimiter
And again, you can define as many case statements as you need for each of your dynamic side panels.
And now finally we need javascript again to call this action. Using event handlers are too slow for this type of refresh, they will always pass the old SOLine parameters. If you know a way to avoid javascript here, I’d love to hear it, as doing callbacks like this does have a heavy overhead. Because of this, be careful where you attach this script to, I actually attach it to the grid’s RowSelectorClick client event rather than AfterRowChange for this reason, and train my users to select rows in this way (by clicking the arrow to the left of each row) so we don’t impact the rest of the company for the minority that use this feature. I’d recommend this if you're working on a high traffic screen such as SO301000.
Anyway, here’s the javascript:
function Side_Panel_Refresh() {
px_alls["ds"].executeCallback("SidePanelRefresh", this.func.caller.arguments[2].row.getCell("InventoryID").getValue() + "*" + this.func.caller.arguments[2].row.getCell("SiteID").getValue());
}
SidePanelRefresh: Name of our graph action from the previous step.
InventoryID & SiteID are my parameters.
And again, using ‘*’ as a delimiter.
Anyway, I hope this helps anyone looking to make dynamic side panels. If anyone has improvements on this method I’d love to hear constructive feedback.
Happy development!
It is a new feature. Side panel were restricted to generic inquiry. In version 2020 you still need a generic inquiry to configure the side panel but they can be viewed in data entry forms too.
The feature is implemented with wizards so it's pretty limited in what it can do. Currently you are limited to the choices available in navigation parameter. It seems like it's not possible to add details entity there. Maybe there's a way but I'm not aware of it.
Check documentation reference link for User Interface: Side Panels for Data Entry Forms (content copied below):
https://help-2020r1.acumatica.com/Help?ScreenId=ShowWiki&pageid=a11aa71b-5f18-4c8f-9c69-350f2c7a89f4
Adding a Side Panel to the Data Entry Form
A customizer can add multiple side panel navigation paths to a data entry form.
To add a side panel, the customizer navigates to the Customization Projects (SM204505) form and creates a new project.
In the Customization Project Editor, the customizer selects the screen for which the side panel is planned to be added.
On the Screens > Form ID > Actions, the customizer adds an action of the Navigation:
Side panel type (1), specifies general settings (2), adds destination screen (3), and specifies parameters (4), if needed (for instance, in the screenshot below, Business Account was selected as a parameter so that the side panel displayed the cases of the specific customer selected on the data entry form).
Example of a side panel on the Customers form
To apply the changes, the customizer must publish the created customization project.
We are currently using the Acumatica Mobile to process Bin Transfers. We are also looking at the Scandit app to be able to scan the from and to bin location labels in the warehouse. So far with testing We can scan the locations, but only in the search window. We would like to be able to scan/enter the locations on the main screen without going to the search window. It seems the selector forces you to go to the search window. Is there a way around this in Acumatica Mobile.
This is a fairly old question with a relatively new solution. I am using 2020R1 where we now can leverage the built-in scanning capability in the Mobile App.
Your field must be defined in the ASPX as a textedit. Then simply add the field to the mobile app screen (if not already there) and decorate it with special = BarCodeScan as shown in the example below.
add screen ZZ301000 {
add container "ScanContainer" {
add field "MyBarcode"
{
special = BarCodeScan
}
add containerAction "Insert" {
icon = "system://Plus"
behavior = Create
}
add recordAction "Save" {
behavior = Save
}
add recordAction "Cancel" {
behavior = Cancel
}
add recordAction "Insert" {
behavior = Create
}
}
}
The result will be similar to the image below:
By clicking on the barcode icon, the built-in barcode reader will open to utilize the camera to scan the barcode.
You can try to set ForceType to "String" for the Location field to replace selector with a text edit. This will allow to type values directly on the form, but you will loose all selector functionality, like searching for records. Another option is to set the PickerType property to Searchable. For more information about the Field tag attributes, please refer to Acumatica Product Documentation
In orchard, I've added a boolean field called "IsDone" to the built in Content Menu Item content part via that Admin interface. I've then picked an item in Navigation and set the option to "yes" for the corresponding field i added.
In my custom theme, I've copied over MenuItem.cshtml.
How would I get the value of my custom "IsDone" field here?
I've tried something like
dynamic item = Model.ContentItem;
var myValue = item.MenuItem.IsDone.Value;
but I'm pretty sure my syntax is incorrect (because i get null binding errors at runtime).
thanks in advance!
First i suggest you use the shape alternate MenuItemLink-ContentMenuItem.cshtml instead of MenuItem.cshtml to target the content menu item directly.
Secondly, the field is attached to the ContentPart of the menu item. The following code retrieves the boolean field from this content part:
#using Orchard.ContentManagement;
#using System.Linq;
#{
Orchard.ContentManagement.ContentItem lContentItem = Model.Content.ContentItem;
var lBooleanField = lContentItem
.Parts
.Where(p => p.PartDefinition.Name == "ContentMenuItem") // *1
.SelectMany(p => p.Fields.Where(f => f.Name == "IsDone"))
.FirstOrDefault() as Orchard.Fields.Fields.BooleanField;
if (lBooleanField != null)
{
bool? v = lBooleanField.Value;
if (v.HasValue)
{
if (v.Value)
{
#("done")
}
else
{
#("not done")
}
}
else
{
#("not done")
}
}
}
*1
Sadly you cannot simply write lContentItem.As<Orchard.ContentManagement.ContentPart>() here as the first part in the part list is derived from this type, thus you would receive the wrong part.
While #ViRuSTriNiTy's answer is probably correct, it doesn't take advantage of the power of the dynamic objects that Orchard provides.
This is working for me but is a much shorter version:
#Model.Text
#{
bool? IsDone = Model.Content.ContentMenuItem.IsDone.Value;
var IsItDoneThough = (IsDone.HasValue ? IsDone.Value : false);
}
<p>Is it done? #IsItDoneThough</p>
You can see that in the first line I pull in the IsDone field using the dynamic nature of the Model.
For some reason (I'm sure there is a good one somewhere) the BooleanField uses a bool? as its backing value. This means that if you create the new menu item and just leave the checkbox blank it will be null when you query it. After you have saved it as checked it will be true and then if you go back and uncheck it then it will have the value false.
The second line that I've provided IsItDoneThough checks if it has a value yet. If it does then it uses that, otherwise it assumes it to be false.
Shape Alternate
#ViRuSTriNiTy's other advice, to change it to use the MenuItemLink-ContentMenuItem.cshtml instead of MenuItem.cshtml is also important.
The field doesn't exist on other menu items so it will crash if you try to access it. Just rename the .cshtml file to fix this.
Dynamic Model
Just to wrap this up with a little bit of insight as to how I got there (I'm still learning this as well) the way I figured it out is as follows:
.Content is a way of casting the current content item to dynamic, so you can use the dynamic advantages with the rest of line;
When you add the field in the admin panel it looks like it should be right there on the ContentItem, however it actually creates an invisible ContentPart to contain them and calls it whatever the ContentItem's type is.
So if you had added this field to a Page content type you would have used Model.Content.Page.IsDone.Value. If you had made a new content type called banana it would be Model.Content.Banana.IsDone.Value, etc.
Once you are inside the "invisible" part which holds the fields you can finally get at IsDone. This won't give you the actual value yet though. Each Field has its own properties which you can look up in the source code. the IsDone is actually a BooleanField and it exposes its data via the Value property.
Try doing a solution-wide search for : ContentField to see the classes for each of the fields you have available.
Hopefully this will have explained things clearly but I have actually written about using fields in a blog post and as part of my getting started with modules course over on the official docs (its way down in part 3 if you're curious).
Using built-in features instead of IsDone
This seems like a strange approach to do it this way. If you have a Content Item like a Page then you can just use the "Show on a menu" setting on the page.
Go to admin > content > open the page > down near the bottom you will find "Show on a menu":
This will automatically put it into your navigation and then you can move it around to where you want:
After it "IsDone" you can just go back and untick the "Show on a menu" option.
Setting up the alternative .cshtml
To clarify your comments about how to use the alternative, you need to
Copy the file you have at Orchard.Core/Shapes/Views/MenuItem.cshtml over to your theme's view folder so its /Views/MenuItem.cshtml
Rename the copy in your theme to MenuItem-ContentMenuItem.cshtml
Delete probably everything in it and paste in my sample at the start of this post. You don't want most of the original MenuItem.cshtml code in there as it is doing some special tricks to change itself into a different shape which isn't what you want.
Reset your original Orchard.Core/Shapes/Views/MenuItem.cshtml back to the factory default, grab it from the official Orchard repository
Understanding the view names
From your comments you asked about creating more specific views (known as alternates). You can use something call the Shape Tracer to view these. The name of them follows a certain pattern which makes them more and more specific.
You can learn about the alternates on the official docs site:
Accessing and Rendering Shapes
Alternates
To figure out what shape is being used and what alternates are available you can use the shape tracing module which is documented here:
Getting Started with Shape Tracing
I'm trying to modify the EditorPart controller for my web part. Basically what I want is to have my custom controls inside a box like the standard properties that can toggle between visible and hidden.
I've been googling for a while, but I cannot seem to find an answer.
Just to clarify: I know I can use the Category property to accomplish this when adding web part properties directly to the web part, but I've extended the EditorPart controller and so I don't think I can simply add [Category("Feed settings")] to the TextBox and LiteralControls I'm creating (correct?).
What you'd need for a standard property is to mark it with the Category attribute:
[Category("My Category")]
public string FeedQuery { get; set; }
(You'll need to add the System.ComponentModel namespace to your class file).
For editor parts it is not so simple. It appears that you can't add them to the standard categories. It is possible to style the editor part to resemble the OOB panels as shown here