Kentico 9: Auto Add Binding in Custom Module - kentico

I have created a custom module (actually I have created a handful in recent years, and this same obstacle frustrates me every time) following the Kentico documentation:
https://docs.kentico.com/display/K9/Creating+custom+modules
The problem I end up with every time, is in developing the User Interface for Parent/Child classes. I create a Vertical Tab node, and beneath it I add an edit tab and a Binding tab for the child class. This all works, and I can add and remove bindings at will, but what I can't do is ADD a new child class and bind it.
Using the Standard Edit Binding template, I am able to bind EXISTING Job Titles to the selected Category, but I cannot CREATE a new one from that page:
To solve this, I created a custom Edit Binding template, and added a New Child Class Header Action that points to a New / Edit Object child:
Which gives me a button that I can use to add a new child class (Job Title):
This approach works per se, in that I can click the New Job Title button and create a new item on the subsequent page:
But no binding is created to link the child object (Job Title) to the selected parent object (Category), An even bigger problem is that once I click Save, I am presented with the following:
The new object DOES SAVE, but the post-save navigation is somehow failing. The event log offers little in the way of diagnostics:
So I thought to create a completely custom interface to accomplish my needs here, according to the Kentico documentation:
https://docs.kentico.com/display/K9/Manually+creating+the+interface+for+custom+modules
So I change the Element Content of the New Job Title page to a custom page that I created to post a DataForm for the new object:
Taking care to assign the proper Object Types on the Properties Tab:
The intent was to programmatically create the binding upon save and also handle the correct navigation to avoid the ambiguous parameter error above, but when this page loads, the UIContext.ObjectID and UIContext.ParentObjectID are both 0:
So I cannot create the binding programmatically. I was able however to solve the error that I received by manually assigning the redirect. The experience is still lacking even with this hack, since it returns to the listing page, but the user still has to click "Add Items" to assign the binding after successfully creating it with the custom page I built.
This cannot be the proper way to do this, so any help with getting me on the right track would be greatly appreciated.

In order for the EditedObject to have a value you have to either decorate the page with the EditedObjectAtribute e.g. like this:
[EditedObject("<custom.objecttype>", "<objectid>", ...)]
or set the object yourself:
int objectId = QueryHelper.GetInteger("objectid", 0);
EditedObject = SomeInfoProvider.GetSomeInfo(objectId);
In your case, I'd recommend exploring what query parameters are available on the page and using them to fetch appropriate object(s). Also, make sure "JobCategoryId" is passed to the "New Job Title" dialog so that you can create the binding.
Btw - kudos for well asked question!

Related

How do I add a Lead Source option

I'm trying to add options to the Source dropdown on the Leads screen. The field uses the CRMSourcesAttribute class to define the existing list. After simply creating a new attribute class to add my own items, I extend the CRLead.Source CacheAttached event to use my new attribute class instead. The result is that there is no change - the new dropdown items are not shown. After doing this, if I inspect the field and select the Drop Down Values button, I do actually see the new options in the Drop Down Values popup window. Any ideas on what could be preventing the new options from displaying in the dropdown itself?
Here's how I configure it in my LeadMaint graph extension:
[PXMergeAttributes(Method = MergeMethod.Append)]
[PXRemoveBaseAttribute(typeof(CRMSourcesAttribute))]
[CRMSourcesExt] // list with old + new options
protected virtual void _(Events.CacheAttached<CRLead.source> e) { }
(v20R2)
Another option is to use code and create a new attribute class with new options, then override the DAC field to use it instead. However, as far as I know, you still have to go through the steps of activating the new options in the workflow customizations UI for each of the fields you're overriding. But then you can refer to the new options in code without having to search the list's AllowedValues at runtime.
With help from a colleague, I was able to get it working, and “without code” through screen workflow customization. I removed my code, then customized it with a new custom Workflow copied from the default. Not sure yet of the ramifications of this, like if we can access the new options in other real code or not, but I see how to create these options now. It has to also be done for both Lead and Opportunity in this scenario. It’s a bit tedious and ethereal (good word) though. I can see this easily causing problems and unexpected results for us we’ll want to watch out for.
Update 5/28/21: I added the options in the Workflow customizations for the field, then copied the default workflow to a new workflow, activated it, and activated the new options for each state/transition. I don't like it, but Acumatica tells me "that's just the way it is now". Note: you'll want to do this for other places referencing CRMSourcesAttribute as well, such as Opportunity.

OrchardCMS: How to access Content Menu Item boolean field in cshtml view

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

Proper Kentico object for non-user-editable block of static html/javascript

The Situation: I've got a mid-sized chunk of html/javascript that contains an authentication script/input (it's a text input, radio control, and a combo box and a few buttons). What it is is less important than the concept that it's a mass of static client side code that the marketing department can pretty easily accidentally the whole thing.
The Desire: I want the users to be able to add it as a whole to a page, but not be able to modify it. When something needs to change, I want to change it in one place and have it be changed on all the pages.
What I've Tried: Widget with a default text. It works, but feels wrong. Users can edit it, and if they do when I fix it one place it doesn't propagate to all the instances. I'm a bit of a Kentico noob, but it seems like there should be a better way to do this.
Also note: I'm using portal engine if that makes a difference.
A widget is the proper usage. What you make your widget inherit from is the key in this case. I'd suggest creating a new widget based on a static HTML webpart. This way you can set the static HTML markup and hide the property from the content editor on the front end. You can do this by going to the Properties tab of the widget and setting the visibility of the field on the form. Don't delete the field, just hide it. It should be a checkbox that says hide on public form or editing form.
** Edit **
As I read through my answer and comments, I realized I meant to say clone the static HTML webpart and set its default text to your javascript. Then create a widget based on that cloned webpart. The text will reside in the web part and will allow you to update it in one place later, if needed.
I will not do it this way because you will be not able to make changes in the future. You can better create a new webpart this can be an empty webpart and then create a custom layout. In this layout you can put you're code. In this way you can always change you're code in the future and then it will be changed on all the places where the widget is placed.
I'd use a new widget based on the Static HTML webpart (make the field read only or hide it as Brenden mentioned), but store the data in a new custom setting.
no coding needed (only a macro to read the custom setting)
able to edit the script on the fly on any instance in the settings module. If you have multiple of these settings you won't need to go through all kinds of widgets to adjust their default setting but find them on a central place.
Cheers!
David
In this case I think it makes sense to create a custom web part to store all your code in it and use it that way. If you want to achieve it without creating a custom web part, you have to store the code in some non-web part and not widget specific object. I like the suggestion of creating a custom setting. You can then access this custom setting via a macro. This macro can be used as a default property of a newly created web part (inherited e.g. from the static text web part, you'd use the text property). You may as well create a widget out of it. Another approach is to use Kentico localization keys as a workaround. you can create a key in the Localization application and access it again, via a macro, e.g. {?customkey.myhtml?}. The approach with a custom setting however sounds cleaner to me.
This syntax should be working to access a custom setting value via macro:
{%Settings.CustomSettings.xxx%}
{%Settings.CustomSettings["xxx"]%}
{%Settings.CustomSettings.GetValue("xxx")%}

Command Not Handled Exception

I'm new to Domino Designer. I'm trying to modify an existing application which is running on a remote server. I create a blank xPage and view in browser and it works. I add a label and view in browser and it works. I add a combo box and setup a data source for my xPage. The data source is in the current application and I'm using a view. I setup my combo to point to that data source and specify BindTo value. I view it in browser and I get Error 500 "HTTP Web Server: Command Not Handled Exception" I can open the view that I've used and see all the data in the view. I can even add, edit, and delete from the view.
Ideas... Thnxs in advance.
Additional Info. I've added a "View" from "Container Controls" and used the same data source and the same view and it also works in the browser.
The error being printed is:
javax.faces.el.PropertyNotFoundException: Error getting property 'CompanyName' from bean of type lotus.domino.local.View
com.sun.faces.el.PropertyResolverImpl.getValue(PropertyResolverImpl.java:119)
com.ibm.xsp.el.PropertyResolverImpl.getValue(PropertyResolverImpl.java:144)
com.sun.faces.el.impl.ArraySuffix.evaluate(ArraySuffix.java:182)
com.sun.faces.el.impl.ComplexValue.evaluate(ComplexValue.java:163)
com.sun.faces.el.impl.ExpressionEvaluatorImpl.evaluate(ExpressionEvaluatorImpl.j‌​ava:257)
com.sun.faces.el.ValueBindingImpl.getValue(ValueBindingImpl.java:150) ....
This was already answered in the comments, but I want to write a better answer.
The problem in this case is that a view data source is being used for editing a document. Directly, this is not a possibility, though there are a few great ways to get around this. The first way, and my favorite, is to use an inline form. I think the Extension Library Demo database offers a few great examples of how to go about doing this. One way is to use a tooltip dialog. Another way is an inline form. A repeater is used and for every row in the view, a hidden panel can be made which contains the document data source and all field which are required to be changeable. By clicking on a button or link, the panel is made visible. This is my favorite variant because it is simple, quick, and generally looks great.
Another way to go about this is to have a view data source only, but use scripts to retrieve the document and set the values in the background and then save and release. It all depends on what you are trying to achieve.
My main recommendation is to look at the ExtLib demo and take a look at what they are doing there.

Getting NSOutlineView to scroll to new entry

First I apologize for the length but I want to ensure I get enough information out. I have an interesting problem that really shouldn't be difficult. I know that if you want to have the NSOutlineView scroll to and want display a specific row you simply use this pattern:
NSInteger row = [self.myOutlineView rowForItem:myItem];
[self.myOutlineView scrollRowToVisible:row];
I am not using a NSTreeController in favor of using delegation.
I am using an adapter pattern class to act as my data. The adapter wraps a number of different types. It is a simple structure:
[adapter children] ----- *[adapter parent]
So the adapter takes care of dealing with child count, can add, can remove, can expand, etc. and also acts like a data transport object.
So with that all out of the way here is the crux of the problem. When I add a new item to the NSOutlineView I start by adding a new child adapter to the adapter that is selected in the outline view. Then I reload the item with the children using:
[self.myOutlineView reloadItem:selectedAdapter reloadChildren:YES];
I then display the view for this object and attempt to synchronize the NSOutlineView so the selected row relates to the newly displayed view. So in a simple way the steps are:
Initially load the NSOutlineView with data utilizing the NSOutlineDataSource protocol
User selects an item (adapter) that can add child items
User clicks "Add" button
A new empty represented object is created
The new represented object is wrapped in an adapter
The newly created adapter is added to the children of the selected adapter
A new view is created using the new represented object and displayed
The selected (parent) node and its children is reloaded in the NSOutlineView
The newly created row in the NSOutlineView is scrolled to view which synchronizes the view and the selection of the NSOutlineView
So here is the problem. The NSOutlineView will not find the newly added item if you use the rowForItem method. Yet after I reload I can expand the parent node and select the newly added item and everything works fine. In an attempt to fix the problem I have programmatically expanded the parent node (exposing the new item) then attempt to scroll to the new item and it still cannot find the row. The rowForItem returns -1.
Have I missed something when adding a new item that I am not updating something in the NSOutlineView? I've tried many different things without success and any help will be greatly appreciated.
Thanks in advance,
Rob
Solved my own problem...finally.
Since I was adding my first child to that node and it was changing from non-expandable to expandable it needed to be expanded first, then scroll to view, then select the item. No more problem.

Resources