Dailycode.info

Short solution for short problems

Custom sharepoint list item action

I thought this was a often used functionality of WSS, but when I started googling I guess I was wrong.

I needed to add some custom action to the list items in my document list. Sharepoint made this very easy to do. This article describes it pretty good: http://msdn.microsoft.com/en-us/library/ms473643.aspx.

I followed the example, only using this custom action: UserInterfaceCustomActions.ECBItemToolbar. This is the one that is shown in the action toolbar of an item. Here is the result:

Suggest modification on a document                                       Copy a document to a public library or resend an instruction on a document.

       

 As I was following this example I noticed that when I tried to install the feature there was an error in the XML:

Feature definition with Id 2aeaccad-cf77-4d38-86a2-4fca58bbc0cc failed validation, file 'UICustomActions.xml', line 8, character 5:
The 'Type' attribute is not declared.

This is how it looked like at the Microsoft site:

<!-- Per Item Dropdown (ECB)-->  
<CustomAction
      Id="UserInterfaceCustomActions.ECBItemToolbar"
     RegistrationType="List"
     RegistrationId="101"
     Type="ECBItem"
      Location="EditControlBlock"
     Sequence="106"
     Title="Copy to public">
     <UrlAction Url="/_layouts/CustomActions.aspx?ECBItem"/>  

</CustomAction>


This is how it should look like:

<!-- Per Item Dropdown (ECB)-->
 <CustomAction Id="UserInterfaceCustomActions.ECBItemToolbar"
 RegistrationType="List"
 RegistrationId="101"
 Location="EditControlBlock"
 Sequence="106"
 Title="MY ECB ITEM">
  <UrlAction Url="/_layouts/CustomActionsHello.aspx?ECBItem"/>

</CustomAction>


The Type attribute is not allowed. Get rid of the "Type" attribute and it will work, strange that Microsoft has this in their example. When you want to deploy the item to all type of list on the website, remove the RegistrationId attribute. For a custom list type the registrationId = "100".  RegistrationId="101" is for document libraries.

You can find a list of all attributes for the custom action element here: http://msdn.microsoft.com/en-us/library/ms460194.aspx.

If you like to add an image to your action, use the ImageUrl attribute:   

Rights="ViewListItems"
ImageUrl="/_layouts/images/CHNGCOL.GIF"
Title="Suggest modification">

      

After I installed and activated the feature, I needed to restart IIS and it worked. Then I made some changes to the XML and wanted to update the feature. Here is the command to do that:

stsadm -o installfeature -filename UserInterfaceCustomActions\feature.xml -force 

 

Followed by an iisreset everytime you made changes.

So it's very easy to do this. You can use a webpart page in combination with custom webparts to handle the action. When you create new features, do not forget to generate a unique guid for each new feature. Its easy to copy and paste an existing feature and then change the custom actions, but if you forget to change the feature id, then you will get a warning message when you try to install or activate the feature. It could be something like: "feature is already installed" or "feature is already activated". When you use the -force attribute, you will overwrite the old feature with the same id. So be cautious that you provide each feature with an unique Id!

When you would like to install different features for different sites, create as many directories in the Features dirrectory as you like. I used UserInterfaceCustomActions as directory like the example, but this is not a mandatory name, when you start developing, choose the names of these directories wisely, so you can distinguish the features. E.g. MyCompanyTaskManagement\Feature.xml and MyCompanyFinance\Features.xml. This way we instantly know what the features are for. (As described in the paragraph above, provide each feature with an unique feature id)

As I was deploying the first features to test sites, I noticed that I only could deploy to the entire site collection. If I tried to deploy to a subsite I got this error: The specified feature applies to the entire site collection, but the specified URL refers to a particular sub site.  To apply this feature to the entire site collection, use the root URL ...  At first I couldn't find the problem, because I was trusting on the example on the Microsoft site. But for the second time I noticed there was an error in their example. In the feature.xml they put "Site" as scope:

<?xml version="1.0" encoding="utf-8"?>
 <Feature Id="6FA98D7A-D802-4f30-8473-0F9B00DD8F7C"
    Title="UI Custom Actions"
    Description="Custom action on list items."
    Version="1.0.0.0"
    Scope="Site"
    xmlns="http://schemas.microsoft.com/sharepoint/">
  <ElementManifests>
    <ElementManifest Location="CustomActions.xml" />
  </ElementManifests>

</Feature>

 

Then they suggest this for the activation of the feature:

stsadm -o activatefeature -filename UserInterfaceCustomActions\feature.xml -url http://Server/Site/Subsite

 

Which is not correct, because if they use Site as scope, it is not permitted to activate it to a subsite, but it is required to activate it on site collection level. So the solution to this problem is: Use scope="Web" in the feature.xml. Then it is allowed to activate it for a subsite!

You can find the scopes and more on the feature.xml file here.

You can find more on the activatefeature here. You can deactivate a feature using the deactivatefeature:

C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\BIN>stsadm -o deactivatefeature -filename UserInterfaceCustomActions\feature.xml -url http://Server/Site/Subsite

When you need security on your feature, its really easy. Just add the Rights to the custom action XML. The list of all the rights you can use can be found here.I used the AddListItems to make sure only users with sufficient rights could view the feature. Here's the example of the XML:

<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <CustomAction
    Id="UserInterfaceCustomActions.ECBItemToolbar"
    RegistrationType="List"
    RegistrationId="100"
    ImageUrl = "/_layouts/images/WRI16.GIF"
    Location="EditControlBlock"
    Sequence="106"
    Title="Add progress">
    <UrlAction Url="serverurl/TM/default.aspx?qID={ItemId}"/>
  </CustomAction>
  <CustomAction
    Id="UserInterfaceCustomActions.ECBItemToolbar"
    RegistrationType="List"
    RegistrationId="100"
    ImageUrl = "/_layouts/images/EML16.GIF"
    Location="EditControlBlock"
    Sequence="106"
    Rights="AddListItems"
    Title="Send alert">
    <UrlAction Url="serverurl/TM/default.aspx?qMailID={ItemId}"/>
  </CustomAction>
</Elements>
 

As I was developing further, I had 2 different lists on the subsite. Now they both had this functionality available. But I needed to provide the listID as parameter, so I would know which list the items comes from. This I managed by adding an extra querystring to the URLAction. Now it looks like this:

    <UrlAction Url="serverurl/TM/default.aspx?qMailID={ItemId}&amp;qlistID={ListId}"/>

Be sure to check the casing in the code, the first time I wrote {ListID} with capital D, it wouldn't work. Again thanks to Microsoft example. They really have to check their code before posting it public. the & sign has to be replaced by &amp;  

To know more about the custom list item action properties, you can continu to read this blog, or go to the Microsoft site: http://msdn.microsoft.com/en-us/library/ms465980.aspx

Here's a quick list of all Rights you can use for a feature:

AddAndCustomizePages
AddDelPrivateWebParts
AddListItems
ApplyStyleSheets
ApplyThemeAndBorder
ApproveItems
BrowseDirectories
BrowseUserInfo
CancelCheckout
CreateAlerts
CreateGroups
CreateSSCSite
DeleteListItems
DeleteVersions
EditListItems
EditMyUserInfo
EmptyMask
EnumeratePermissions
FullMask
ManageAlerts
ManageLists
ManagePermissions
ManagePersonalViews
ManageSubwebs
ManageWeb
Open
OpenItems
UpdatePersonalWebParts
UseClientIntegration
UseRemoteAPIs
ViewFormPages
ViewListItems
ViewPages
ViewUsageData
ViewVersions

If you want to have several right, seperate them by a comma Eg.:

Rights="AddListItems,ViewPages"

 

Here is a quick list of all locations that can be used:

Location

Custom Action Group ID Group Description
DisplayFormToolbar N/A Location corresponds to the display form toolbar of lists.
EditControlBlock N/A Corresponds to the per-item edit control block (ECB) menu.
EditFormToolbar N/A Location corresponds to the edit form toolbar of lists.
Microsoft.SharePoint.Administration.ApplicationCreated Links Application Created page.
Microsoft.SharePoint.Administration.ApplicationManagement ApplicationSecurity Application Security section on Central Administration Application Management page.
Microsoft.SharePoint.Administration.ApplicationManagement ExternalService ·                       External Service Connections section on Central Administration Application Management page.
Microsoft.SharePoint.Administration.ApplicationManagement SiteManagement ·                       SharePoint Site Management section on Central Administration Application Management page.
Microsoft.SharePoint.Administration.ApplicationManagement WebApplicationConfiguration ·                       SharePoint Web Application Management section on Central Administration Application Management page.
Microsoft.SharePoint.Administration.ApplicationManagement WorkflowManagement Workflow Management section on Central Administration Application Management page.
Microsoft.SharePoint.Administration.Operations BackupRestore Backup and Restore section on Central Administration Operations page.
Microsoft.SharePoint.Administration.Operations DataConfiguration Data Configuration section on Central Administration Operations page.
Microsoft.SharePoint.Administration.Operations GlobalConfiguration ·                       Global Configuration section on Central Administration Operations page.
Microsoft.SharePoint.Administration.Operations LoggingAndReporting Logging and Reporting section on Central Administration Operations page.
Microsoft.SharePoint.Administration.Operations Security Security Configuration section on Central Administration Operations page.
Microsoft.SharePoint.Administration.Operations Topology Topology and Services section on Central Administration Operations page.
Microsoft.SharePoint.Administration.Operations Upgrade Central Administration Operations page.
Microsoft.SharePoint.ContentTypeSettings Fields ·                       Columns section on Site Collection Content Type page.
Microsoft.SharePoint.ContentTypeSettings General ·                       Settings section on Site Collection Content Type page.
Microsoft.SharePoint.ContentTypeTemplateSettings Fields ·                       Columns section on List Content Type page.
Microsoft.SharePoint.ContentTypeTemplateSettings General Settings section on List Content Type page.
Microsoft.SharePoint.Create WebPages Web Pages section on Create page.
Microsoft.SharePoint.GroupsPage NewMenu New menu on site collection People and Groups page.
Microsoft.SharePoint.GroupsPage SettingsMenu Settings menu on site collection People and Groups page.
Microsoft.SharePoint.ListEdit Communications Communications section on Customize page for list or document library.
Microsoft.SharePoint.ListEdit GeneralSettings General Settings section on Customize page for list.
Microsoft.SharePoint.ListEdit Permissions Permissions and Management section on Customize page for list or document library.
Microsoft.SharePoint.ListEdit.DocumentLibrary GeneralSettings General Settings section on Customize page for document library.
Microsoft.SharePoint.PeoplePage ActionsMenu Actions menu on site collection People and Groups page.
Microsoft.SharePoint.PeoplePage NewMenu New menu on site collection People and Groups page.
Microsoft.SharePoint.PeoplePage SettingsMenu Settings menu on site collection People and Groups page.
Microsoft.SharePoint.SiteSettings Customization ·                       Look and Feel section on Site Settings page.
Microsoft.SharePoint.SiteSettings Galleries ·                       Galleries section on Site Settings page.
Microsoft.SharePoint.SiteSettings SiteAdministration ·                       Site Administration section on Site Settings page.
Microsoft.SharePoint.SiteSettings SiteCollectionAdmin ·                       Site Collection Administration section on Site Settings page.
Microsoft.SharePoint.SiteSettings UsersAndPermissions ·                       Users and Permissions section on Site Settings page.
Microsoft.SharePoint.StandardMenu ActionsMenu Actions menu in list and document library views.
Microsoft.SharePoint.StandardMenu ActionsMenuForSurvey Site Actions menu for surveys.
Microsoft.SharePoint.StandardMenu NewMenu New menu in list and document library views.
Microsoft.SharePoint.StandardMenu SettingsMenu Settings menu in list and document library views.
Microsoft.SharePoint.StandardMenu SettingsMenuForSurvey Site Settings links for surveys.
Microsoft.SharePoint.StandardMenu SiteActions Site Actions menu.
Microsoft.SharePoint.StandardMenu UploadMenu Upload menu in document library views.
Microsoft.SharePoint.User ActionsMenu Actions menu on Web site Permissions page.
Microsoft.SharePoint.User NewMenu New menu on Web site Permissions page.
Microsoft.SharePoint.User SettingsMenu Settings menu on Web site Permissions page.
Microsoft.SharePoint.Workflows LeftNavBarLinks Left navigational area on pages for managing workflow.
NewFormToolbar N/A Location corresponds to the new form toolbar of lists.
ViewToolbar N/A Location corresponds to the toolbar in list views.

 

A list for the SharePoint actions with their sequences: http://www.johnholliday.net/resources/customactions.html

 

 

 

 


ASP .Net Calendar: Set selected date by default on today

As the title tells you, I was trying to set the default selected day of a asp .Net calendar to today. It looked like this:

Calendar1.SelectedDate = DateTime.Now;

But when the page loaded, the selected date was not shown, it was set, but not shown. So I found this solution: You have to use DateTime.Today instead of DateTime.Now.

Calendar1.SelectedDate = DateTime.Today;

In the comments, DevToolShed explained the following about this:

The reason this works with Today instead of Now is because of the Time part of the DateTime. DateTime.Now will return the current date and the current time. DateTime.Today returns the current date and "12:00:00 AM" as the time. To show a date selected on the calendar, the time must be exactly: 12:00:00 AM. Any other time will correctly be put into the property but will not show. 

That's all folks.


Using client side validation in .Net

In this next example I will briefly explain the use of validators in ASP.Net

When we create web applications, there is always a need to perform client-site validation on user input. .Net really facilitates this with the use of validators. I will show the use of a regularexpression validator.

I have a form that needs some numeric input. It has to be a positive number. When the input is wrong, I don't want my button event to fire. Since I'm using AJAX no postback will be fire anyway, but still the button event is fired. So this is how to correctly handle a situation like this:

First place the input and button on the screen, next to the input you can place the regularexpression validator.

The ASP code looks like this:

<tr>
                        <td>
                            <asp:Label ID="Label5" runat="server" Text="Lot number"></asp:Label>
                        </td>
                        <td>
                            <asp:TextBox ID="txtLotNumber" runat="server" ValidationGroup="Numeric">0
			    </asp:TextBox>    
                        </td>
                        <td>
                            <asp:RegularExpressionValidator ID="RegularExpressionValidator3" 
				runat="server" 
                                ControlToValidate="txtLotNumber" 
                                ErrorMessage="Please provide a positive number" 
				ValidationExpression="^\d+$" 
                                ValidationGroup="Numeric"></asp:RegularExpressionValidator>
                        </td>
                    </tr>
                    <tr>
                        <td><asp:Button ID="btnSave" Text="Save" runat="server" 
				OnClick="btnSave_Click" 
                                ValidationGroup="Numeric" Width="100px" /></td>
                    </tr>

The validationexpression is: ^\d+$ More on these expressions you can find here. As you can see I called the validationgroup numeric. This is because we only have numeric validation on this button event. If you have several validations that need to be performed with this button event, then you'll need a more general validation group name, keep in mind that you can only use 1 validationgroup for the button. When the button is in the same validationgroup as the regularexpressionvalidator, the button event will only be fired when the validations are valid. You can use several validationgroups on one page, in case you have more then 1 event that needs validation. No postback or event are triggered making the user expirience on your site better. No time wasted on refreshing screens or manually perfomring validations. No problems with control states.  

After the regular expression was checked, I needed to check on required fields. This is not possible using regular expression validators, so I had to add a required field validator. Using the designer of VS 2008, I added this validator. Then when I tried out the validators, it appeared that the second validator had a space between the control to validate. I googled and found out that you need to set the Display to dynamic. In the following example I changed the first 2 controls, the last I didn't change, you can see the result at the picture below the code:

<tr>
    <td>
        <asp:Label ID="Label3" runat="server" Text="Amount"></asp:Label>
    </td>
    <td>
        <asp:TextBox ID="txtAmount" runat="server" ValidationGroup="SaveArrival"></asp:TextBox>    
    </td>
    <td>
        <asp:RegularExpressionValidator ID="RegularExpressionValidator2" runat="server" 
            ControlToValidate="txtAmount" ErrorMessage="Please provide a positive number" 
            ValidationExpression="^\d+$" ValidationGroup="SaveArrival" 
            Display="Dynamic"></asp:RegularExpressionValidator>
        <asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" 
            ControlToValidate="txtAmount" ErrorMessage="Amount is required!" 
            ValidationGroup="SaveArrival" Display="Dynamic"></asp:RequiredFieldValidator>
    </td>
</tr>
<tr>
    <td>
        <asp:Label ID="Label4" runat="server" Text="K number"></asp:Label>
    </td>
    <td>
        <asp:TextBox ID="txtKNumber" runat="server" ValidationGroup="SaveArrival">0</asp:TextBox>    
    </td>
    <td>
        <asp:RegularExpressionValidator ID="RegularExpressionValidator1" runat="server" 
            ControlToValidate="txtKNumber" ErrorMessage="Please provide a positive number" 
            ValidationExpression="^\d+$" ValidationGroup="SaveArrival" 
            Display="Dynamic"></asp:RegularExpressionValidator>
        <asp:RequiredFieldValidator ID="RequiredFieldValidator2" runat="server" 
            ControlToValidate="txtKNumber" ErrorMessage="K number is required!" 
            ValidationGroup="SaveArrival" Display="Dynamic"></asp:RequiredFieldValidator>
    </td>
</tr>
<tr>
    <td>
        <asp:Label ID="Label5" runat="server" Text="Lot number"></asp:Label>
    </td>
    <td>
        <asp:TextBox ID="txtLotNumber" runat="server" ValidationGroup="SaveArrival">0</asp:TextBox>    
    </td>
    <td>
        <asp:RegularExpressionValidator ID="RegularExpressionValidator3" runat="server" 
            ControlToValidate="txtLotNumber" 
            ErrorMessage="Please provide a positive number" ValidationExpression="^\d+$" 
            ValidationGroup="SaveArrival"></asp:RegularExpressionValidator>
        <asp:RequiredFieldValidator ID="RequiredFieldValidator3" runat="server" 
            ControlToValidate="txtLotNumber" ErrorMessage="Lot number is required!" 
            ValidationGroup="SaveArrival"></asp:RequiredFieldValidator>
    </td>
</tr>
 

 

As you can see I used SaveArrival as ValidationGroup, because I now have more then 1 validation type.


Outer join in Oracle SQL

I'm by origin a MS SQL  writer. This is the SQL I grew up with, although at school I learned Oracle, at my first job it was MS SQL and since then 90 %  of the SQL I use was MQ SQL. But at my current employer, it the other way around. It's 90% Oracle. Working more then 1 year with Oracle, I'm becoming more and more aware of the differences between MS SQL en Oracle SQL. I'm will discuss one of these differences.

When you need information from 2 tables, in MS SQL we use the Join clause, inner or outer. When it's possible that the child table has no related data for the record of the master table in the query, and still you want to see the record from the master table, you will need to use outer join. Now I tried this in Oracle SQL, there was no syntax error, but also no data. I could figure out what I was doing wrong.

This syntax returned nothing:

SELECT a.GUID, a.ROLE_NAME, b.APP_NAME, b.guid as APPGUID 
FROM CIG_MGMT.ROLE a
LEFT OUTER JOIN CIG_MGMT.ROLE_APPS b ON b.ROLE_GUID = a.GUID 
WHERE a.IS_VALID = 1 AND b.IS_VALID = 1 
ORDER BY a.ROLE_NAME,b.APP_NAME

So I started looking for an answer. I came up with this site: http://www.adp-gmbh.ch/ora/sql/outer_join.html

I changed the code into this and it worked!

SELECT a.GUID, a.ROLE_NAME, b.APP_NAME, b.guid as APPGUID 
FROM CIG_MGMT.ROLE a, CIG_MGMT.ROLE_APPS b 
WHERE b.ROLE_GUID(+) = a.GUID and 
a.IS_VALID = 1 AND b.IS_VALID(+) = 1 
ORDER BY a.ROLE_NAME,b.APP_NAME
 

Just use the (+) syntax for every field of the (child) table in the where clause. 

A solution was found, but not a reason why the left outer join wouldn't work and the (+) worked fine. The first reason could be that I was using Oracle 8i wich only supports the (+). But this is not the case. We are using 9i and the compiler doesn't complain to the syntax.

So I'm still looking for an explanation, because I have very little time, I cannot investigate this and I will just implement the (+) solution. I hope somebody who knows the answer takes the time to post it here... Or maybe in the future when I will have some more time, I will remember this and look for an answer. But this first 2 years will be hectic ;-)


Serial communication in VB6

A friend just asked me how to use serial communication in VB6 or VBA.

So here's just a quick article on how to do this:

If you can't find the MSComm control, just add it this way:

First right click on the toolbar on the right side of the screen in VB:

Then click on: 'components'.

The following screen will appear where you can look for Microsoft Comm Control.

Now you can add this control to your forms.

Here is some code on how to start communication and how to process the recieved data:

startComPort(com_Scale, 1, "9600,e,7,2"))

 

This function starts the communication with the serial device: 

Private Function startComPort(com_obj As MSComm, i_Port As Integer, s_settings As String) As Boolean
  On Error GoTo startComPortERROR
  
  com_obj.Settings = s_settings
  com_obj.RThreshold = 1
  com_obj.SThreshold = 0
  com_obj.InputMode = comInputModeText
  com_obj.Handshaking = comNone
  com_obj.CommPort = i_Port
  com_obj.PortOpen = True
  
  startComPort = True
  
  Exit Function
startComPortERROR:
  startComPort = False
  DBG_ReportError Err.Number, Err.Description, "Weighstation.cls,startComPort", "", True
  Exit Function
End Function

Then when the serial device gets input this function is fired:

Private Sub com_Scale_OnComm()
  On Error GoTo com_OnCommERROR
Dim Text As String
  
  If com_Scale.PortOpen = True Then
                Text = com_Scale.Input
  End Select
  
...
  Exit Sub
com_OnCommERROR:
  If (Err.Number = 8021) And (com_Scale.PortOpen = True) Then
    com_Scale.PortOpen = False
  End If
  DBG_ReportError Err.Number, Err.Description, "Weighstation.cls,com_OnComm", "", True
  Exit Sub

 

Special greeting to Gustavo, a brother and colleague in Brazil...

Drag and drop in Windows.Net

I was looking for some drag and drop functionality. Between all the posts I managed to gather some usefull information. Here's the result:

The goal was to drag an image onto another image. Then I save the coordinates in the database. I have an image containing the ground plans of our company. On the left I have a list of several type of machinery. They would have to drag a machine onto the map and save the location. I'm just going to use a simple example below to illustrate the drag and drop in Windows.Net.

We'll use 3 events of the panel (or picturebox): mousedown, mousemove and mouseup. When the user presses the mouse down on the image, we set the isDragging flag to true. Then the MouseMove event kicks in. The location of the panel (or picturebox) is changed to the new coordinates. When we release the mousebutton, we set the isDragging to false. That all there is. In the MouseUp event I can save the coordinates to the database.

	private void panel3_MouseDown(object sender, MouseEventArgs e)
        {
            isDragging = true;
 
            currentX = e.X;
            currentY = e.Y;
        }
 
        private void panel3_MouseMove(object sender, MouseEventArgs e)
        {
            if (isDragging)
            {
                panel3.Top = panel3.Top + (e.Y - currentY);
                panel3.Left = panel3.Left + (e.X - currentX);
            }
 
        }
 
        private void panel3_MouseUp(object sender, MouseEventArgs e)
        {
            isDragging = false;
        }

 

I used the example located here.