Sunday, August 25, 2013

URL Encode in JS URL decode in PHP

Recently I had a task where I needed to find a Javascript function that would mimic the PHP urlencode function.

The first thing I tried was encodeURIComponent(). This turns spaces into %20, single-quotes into %27 or something like that. So I did a search and found these out-of-the-box options for URL encoding in Javascript:

  • escape() will not encode: @*/+
  • encodeURI() will not encode: ~!@#$&*()=:/,;?+'
  • encodeURIComponent() will not encode: ~!*()'
Encoded test string: "Jimmy's Coffee & Bean Emporium":

In Javascript using encodeURIComponent()
Jimmy's%20Coffee%20%26%20Bean%20Emporium
In Javascript using escape()
Jimmy%27s%20Coffee%20%26%20Bean%20Emporiu
In Javascript using encodeURI()
Jimmy's%20Coffee%20&%20Bean%20Emporium
In PHP using urlencode()
Jimmy%27s+Coffee+%26+Bean+Emporium

None of the encoded strings are the same.

The solution:

I created a new Javascript function called php_urlencode() to encode the same way PHP does.

function php_urlencode(str) {
    return escape(str).replace(/\+/g,'%2B').replace(/%20/g, '+').replace(/\*/g, '%2A').replace(/\//g, '%2F').replace(/@/g, '%40');
}
This function uses the escape function first and then uses the "replace()" regular expression function to replace a few strings with something PHP would use instead. It uses the "/g" global replace property to replace all instances of the string instead of just the first one.

  • + becomes %2B (done first to encode valid "plus" symbols)
  • %20 becomes + (done after the first to turn spaces into the "plus" symbol)
  • * becomes %2A
  • / becomes %2F
  • @ becomes %40

This function returns:
Jimmy%27s+Coffee+%26+Bean+Emporium
In PHP using urlencode()
Jimmy%27s+Coffee+%26+Bean+Emporium

It matches! Very nice.

Thursday, June 27, 2013

VBA: Set cursor to end of MS Access field

This came about because I have a form that periodically saves the form record using the OnTimer event. When a save is initiated, the Microsoft Access default behavior is to place the cursor at the front of the current field.

This default behavior would interrupt the user if they are typing, and it would require them to scroll down if the text is much larger than the scroll box. Either way, it is an annoyance.

 Access has an option to change the default behavior and these settings apply to all of the databases opened by that users' instance of Access.

  •  File - Options - Client Settings - "Behavior Entering Field" (Access 2010)
  •  Office Button - Access Options - Advanced - "Behavior Entering Field" (Access 2007) 

The options are:
  • Select Entire Field 
  • Go to Start of Field 
  • Go to End of Field
Changing the defaults for everything may be a solution for you, but it is not for me. I simply want to change certain fields on certain forms so that when a user is typing and the OnTimer event is triggered, their cursor is not pushed back to the top of the field causing them to enter text in the wrong place.

There is one thing do to to duplicate the behavior for an individual field. Set the "SelStart" property to the end of the field. I do this in the OnEnter event so that clicking, tabbing, or other means of entering the field will automatically place the cursor at the end of the field value. Of course you have to do this for each field that needs it, but the benefit is that it allows you to pick and choose which fields it applies to.

Private Sub  txtFieldName_Enter()
'SelStart = end of the field. Add the ("" &) to ensure it works with a null field value.
me.txtFieldName.SelStart = Len("" & me.txtFieldName)
End Sub 

This solves the default behavior so that the need to scroll to end is removed. The second part of the fix is to ensure that when saving the form record, the cursor is still in current field and appears at the end.

This requires a global variable within the module to keep track of which field is current.
Dim strCurrField as String

And the Timer event:

Private Sub Form_Timer()
If Me.Dirty then
DoCmd.RunCommand acCmdSaveRecord 'save
Me.Controls(strCurrField).SetFocus ' cannot use the SelStart property without focus
Me.Controls(strCurrField).SelStart = Len("" & Me.Controls(strCurrField).Value)
End if
End Sub

So now when the form record is saved while a user is typing, the field blinks and the cursor continues at the end, which is the most likely place it belongs.

This does not help if the user is typing in the middle of a block of text, but in my instance, this is not a common scenario.

Tuesday, April 2, 2013

DSNless Access Table with Primary Key Index

I was recently working on a project where I used MS Access 2007 to build a multi-user application with a SQL backend. The nature of the application required the linked SQL tables to be DSNless.

I attach my DSNless tables in code, which offers a few benefits. By having the objects to attach listed in an a local Access table, I can attach and delete the linked tables on demand without the user knowing, and I can easily port this application to a new SQL database without much work. More useful is the ability to ensure that each time the application is opened, the table schemas are refreshed and changes made to SQL tables and views are reflected in Access.

My list of attachable objects looks like this:
SourceObjectLocalObjectServerDatabaseKeyField
tableXYdbo_tableXYSVRNameDBName(null)
viewXYdbo_viewXYSVRNameDBNameviewkeyid

I use a function that I found online and have since adapted to do the extra stuff I needed.

Function AttachDSNLessTable(stLocalTableName As String, stRemoteTableName As String, stServer As String, stDatabase As String, Optional stUsername As String, Optional stPassword As String)
    On Error GoTo AttachDSNLessTable_Err
    Dim td As TableDef
    Dim idx As Index
    Dim stConnect As String
  
    For Each td In CurrentDb.TableDefs
        If td.Name = stLocalTableName Then
            CurrentDb.TableDefs.Delete stLocalTableName
        End If
    Next
    
    If Len(stUsername) = 0 Then
        '//Use trusted authentication if stUsername is not supplied.
        stConnect = "ODBC;DRIVER=SQL Server;SERVER=" & stServer & ";DATABASE=" & stDatabase & ";Trusted_Connection=Yes"
    Else
        '//WARNING: This will save the username and the password with the linked table information.
        stConnect = "ODBC;DRIVER=SQL Server;SERVER=" & stServer & ";DATABASE=" & stDatabase & ";UID=" & stUsername & ";PWD=" & stPassword
    End If
    Set td = CurrentDb.CreateTableDef(stLocalTableName, dbAttachSavePWD, stRemoteTableName, stConnect)
    CurrentDb.TableDefs.Append td
  
    AttachDSNLessTable = True
    Exit Function

AttachDSNLessTable_Err:
    AttachDSNLessTable = False
    MsgBox "AttachDSNLessTable encountered an unexpected error: " & Err.Description

End Function

The problem:
In Access when you link a table manually (with a DSN) you will be prompted to select the primary key field if Access cannot get the information from the source object. If there is a key field assigned in SQL, that information will carry over to Access. This is not the case for SQL views.

Why you need an index in Access:
You cannot update records in a linked SQL view/table without a primary key index setup in Access. Your Acess statusbar will say' Recordset not updateable.' This will be likely to happen if you are converting an Access application to a SQL backend.

Solution - creating an index in code:
Attaching SQL objects in code will not present a primary key selection prompt, so you'll need a way to create that index in code.

There are a lot of posts online about using the DAO object model to create and attach an index kind of like this:

Set tdf = currentDB.CreateTableDef("tablename") 'new table def
currentDB.TableDefs.Append tdf 'append to the db
Set idx = tdf.CreateIndex("PrimaryKey") 'new index
With idx
     .Fields.Append .CreateField("ID")
     .Primary = True
End With
tdf.Indexes.Append idx 'append index

This never worked for me. Tons of people swear that it works, but I could never get it to work. *Notice that the tableDef has to be created and attached to the currentDB before creating the index.

What works for me:
I added an optional variable to the function called "keyField" that brings in that value from my table, which is only populated for objects that do not already. have a primary key in SQL.

Add this to the function definition: Optional stKeyfield As String

Function AttachDSNLessTable(stLocalTableName As String, stRemoteTableName As String, stServer As String, stDatabase As String, Optional stUsername As String, Optional stPassword As String, Optional stKeyfield As String)

Access will automatically setup an index for objects with primary keys, so in my list of objects to attach (a table), I only assign a keyfield to items that are SQL views because trying to create an index on a table will report that an index already exists.

In the function I add this code after appending the tableDef to the currentDB:

If Len(stKeyfield) > 0 Then
        CurrentDb.Execute "CREATE UNIQUE INDEX 'PrimaryKey' ON " & stLocalTableName & " (" & stKeyfield & ") WITH PRIMARY"
    End If

This works fine for me. I get to pre-define which field will be my primary key and every time that object is attached, it also adds the index. The SQL view is updateable in SQL, and with that index it is also updateable in Access.

Final function:

Function AttachDSNLessTable(stLocalTableName As String, stRemoteTableName As String, stServer As String, stDatabase As String, Optional stUsername As String, Optional stPassword As String, Optional stKeyfield As String)
    On Error GoTo AttachDSNLessTable_Err
    Dim td As TableDef
    Dim idx As Index
    Dim stConnect As String
   
    For Each td In CurrentDb.TableDefs
        If td.Name = stLocalTableName Then
            CurrentDb.TableDefs.Delete stLocalTableName
        End If
    Next
     
    If Len(stUsername) = 0 Then
        '//Use trusted authentication if stUsername is not supplied.
        stConnect = "ODBC;DRIVER=SQL Server;SERVER=" & stServer & ";DATABASE=" & stDatabase & ";Trusted_Connection=Yes"
    Else
        '//WARNING: This will save the username and the password with the linked table information.
        stConnect = "ODBC;DRIVER=SQL Server;SERVER=" & stServer & ";DATABASE=" & stDatabase & ";UID=" & stUsername & ";PWD=" & stPassword
    End If
    Set td = CurrentDb.CreateTableDef(stLocalTableName, dbAttachSavePWD, stRemoteTableName, stConnect)
    CurrentDb.TableDefs.Append td
   
    If Len(stKeyfield) > 0 Then
        CurrentDb.Execute "CREATE UNIQUE INDEX 'PrimaryKey' ON " & stLocalTableName & " (" & stKeyfield & ") WITH PRIMARY"
    End If

   
    AttachDSNLessTable = True
    Exit Function

AttachDSNLessTable_Err:
    AttachDSNLessTable = False
    MsgBox "AttachDSNLessTable encountered an unexpected error: " & Err.Description

End Function

Friday, February 15, 2013

CAML, IntApp, SharePoint


I just got done doing a bunch of work in IntApp Integration Builder where I had to do things like reading/updating SharePoint lists and libraries.

This post/blog is never going to be highly detailed in any one area. I have no interest in explaining CAML, XML, IntApp, web services, logic, etc. There are plenty of resources online to find out how to write a CAML query. The reason I write this stuff down at all is because I have spent a lot of time looking for solutions that have been solved by others... but they don't usually share the information. Lots of questions with few answers. The goal here is to share the answers that I've found so someone else doesn't have to go through the try/fail/try again steps to get the answer.


If you use IntApp, they have a lot of great resources. You'll need to sign up on their customer community portal, but you can then find templates for solutions. This is the barebones "here is how we did this" kind of template that lets you skip past the first 20% of the setup.

So here are the basics:

SharePoint Web Services:
Every MS SharePoint site has a set of web services.
You can access them via URL by adding "_vti_bin/lists.asmx" to the end of your site URL like this:
http://yoursite/_vti_bin/lists.asmx
You'll need to us WSDL format (web services description language) so you add "?wsdl" to the end of the URL.
http://yoursite/_vti_bin/lists.asmx?wsdl
"Lists.asmx" is one of many web services. Lists.asmx gives you access to the lists in your SharePoint site. A library is a sub-type of list, so from now on, I'll just call it a list.

On to the CAML:
(Collaborative Application Markup Language)


You'll need a SOAP (Simple Object Access Protocol) wrapper around your query.
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
     <soap12:Body>

          Your web service call goes here
     </soap12:Body>
</soap12:Envelope>
 You'll also need the web service call. Each web service has multiple functions. I'll use "GetListItems" as the example. The name should be self-explanatory... it "Get"s a "List"s "Items". At least one piece of information you need... "listName" is the GUID of  the list or library that you wish to query.

 <GetListItems xmlns="http://schemas.microsoft.com/sharepoint/soap/">
     <listName>_YOURLISTGUID_</listName>
     <viewName>_YOURVIEWGUID_</viewName>
     Your CAML query goes here (or somewhere in the "GetListItems" tag.) The order shouldn't matter)
     <ViewFields>
          <FieldRef Name="ID" />
          <FieldRef Name="Title" />
          <FieldRef Name="yourfield" />
          ...etc
     </ViewFields>
     <rowLimit>0</rowLimit>
</GetListItems>

Quirk #1:
When querying a SharePoint web service, the "ViewFields" don't matter. You need to have the tag there, and it can be empty "<ViewFields></ViewFields>", but don't bother listing the fields you want to have returned in the dataset. The fields that are returned are defined in the view.
The "viewName" GUID is optional. If you leave it out, the list's "default" view will be used and you will only be able to access the fields that are returned in that default view. So if you've added a custom column into your list and it's not in the default view, you will need to add it there or create another view containing the fields you need and then use "viewName" to tell the web service which view to return.

** How you find your SharePoint list or view GUID? **

OK, So new we just need a query. There a lot of good resources online to figure out how to write a CAML query.

Quirk #2:
Normally your CAML query is wrapped in this:
<Query>...</Query>
With these web services you'll need to put an extra <query> tag (lower-case "q") wrapping the whole thing, like this:
<query><Query>...</Query></query>
If you don't do this, your query will be ignored, the web service will be called, and your dataset will be all values from the view sorted in the default sort order for the view that you've chosen (or the default view if you've not set a viewName GUID). If you've got data returned and the filter or sort doesn't work, this is probably why.


By the way the "<rowLimit>" tag works just fine. Use "0" to return all, or another number to limit the dataset to that number of records.


Here are some MORE TIPS for working with IntApp Relationship Builder and SharePoint web services.

SharePoint Web Service Dataset Basics

More useful information:
The "GetListItems" function of the http://yoursite/_vti_bin/lists.asmx?wsdl SharePoint web service returns XML and you need to have this path to access the returned data:
soap:Envelope/soap:Body/GetListItemsResponse/GetListItemsResult/listitems/rs:data/z:row
The nodes involved (and therefore the path) will differ depending on which web service and function you are calling.

If you work with XML you'll understand nodes and how to decipher the path.
Logically, XML is simply nested data. the first node is soap:Envelope. Inside that is soap:Body, inside that is GetListItemsResponse, and so on.

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <soap:Body>
        <GetListItemsResponse xmlns="http://schemas.microsoft.com/sharepoint/soap/">
            <GetListItemsResult>
                <listitems xmlns:s='uuid:yourGUID' xmlns:dt='uuid:yourGUID' xmlns:rs='urn:schemas-microsoft-com:rowset' xmlns:z='#RowsetSchema'>
                    <rs:data ItemCount="X">
                        <z:row ows_FieldName1='FieldValue' ows_FieldName2='FieldValue'/>
                        <z:row... etc>
                    <rs:data>
                <listitems>
            </GetListItemsResult>
        </GetListItemsResponse>
    </soap:Body>
</soap:Envelope>
Tips:
  • SharePoint prepends "ows_" onto the field names.  i.e. "Title" becomes "ows_Title".
  • Any space in the field name is replaced with "_x0020_". i.e. "My Field" becomes "My_x0020_Field".
  • Calculated fields return "string;#" before the value. i.e. a value of "Jimmy" becomes "string;#Jimmy" and you'll have to strip that or at least compensate for it before you use the data.

Here is a good post on SharePoint field naming conventions over at Web BorG. 

SharePoint GUIDs


A GUID is a Globally Unique Identifier.
Here is how you find your SharePoint list or view GUID:
The easiest way is to go to the list's default view (SP2010) and hover over the "Add Document"/Add New Item" link on the bottom of the page. In the browser status bar you'll see the URL for that link. That URL contains the bare GUID for the list.

http://yoursite/_layouts/Upload.aspx?List={EFG33460-E1F4-4E78-A2F8-CDEG5BE0420B}&RootFolder=

Your GUID is in curly brackets: {EFG33460-E1F4-4E78-A2F8-CDEG5BE0420B}

If you don't have that link to click on, you can go to the list settings, copy the URL and see the GUID in its HTML encoded form in the "List" querystring variable (List=listGUID):
http://yoursite/_layouts/listedit.aspx?List=%7BEFG33460%2DE1F4%2D4E78%2DA2F8%2DCDEG5BE0420B%7D
It takes 5 seconds to manually transform this to a valid GUID. %7B = "{", %7D = "}", and %2D = "-".

List=%7BEFG33460%2DE1F4%2D4E78%2DA2F8%2DCDEG5BE0420B%7D
becomes
List={EFG33460-E1F4-4E78-A2F8-CDEG5BE0420B}

You can get your "view" GUID this way:
Go to the list settings, scroll all the way down to the views, and get the URL from your view (right-click "copy shortcut" or just click on it and copy the page URL). The view GUID is stored in its HTML encoded form in the "View" querystring variable (View=viewGUID). You can then manually decode that using the same logic as described above.
http://yoursite/_layouts/ViewEdit.aspx?List=%7B1637E544%2D5183%2D417B%2DB9DC%2DCD0E2E4EDEE1%7D&View=%7B7E510143%2DD7EA%2D4CFD%2D9A4D%2D65FF5AE909CC%7D&Source=

So.... This
View=%7B7E510143%2DD7EA%2D4CFD%2D9A4D%2D65FF5AE909CC%7D
becomes
View={7E510143-D7EA-4CFD-9A4D-65FF5AE909CC}

Thursday, February 14, 2013

Surface Update 2/12/2013

  • This release will address several 'Limited' Wi-Fi connections issues.
  • Contains driver updates improving performance with Windows, Volume and Power buttons
  • Source: MS Community

    That's pretty limited information. I've not had any problem with the wifi, nor lag of the buttons. Lucky me?

    VMWare View on Surface RT

    Around the first of February, 2013, we finally got a Surface RT app for VMWare View. For me, this turns the device into a work capable device. The app isn't perfect, no PCoIP support, but it's better than nothing.

    Citrix did a good job releasing the Citrix Receiver app last year. Kudos to them.

    Wednesday, January 9, 2013

    Windows RT Surface update 1/8/13

    It's always nice to have a Windows Update... Odd I know, but it's exciting to find out what has been fixed or added.

    The update today (Jan 8, 2013) involved security updates mostly, but also included some firmware updates for stability.

    First off, the Windows RT Flash Player is not updated by Adobe. The Flash player is version 10 and updated by Microsoft. The downside is that Flash player on Windows RT is always behind and may not work on some sites. Although it is rare, I have run into this already. The upside is that I don't get an almost daily update from Adobe.

    But in today's update, the Flash player was updated for IE10. This was done to fix a security hole. Benefit for me. I am seeing some improvement on sites that have required a higher version of flash.
    http://technet.microsoft.com/en-us/security/advisory/2755801

    The security stuff includes update for the .NET Framework, SSL vulnerability, elevation of privilege vulnerability, MSXML core services,
    http://support.microsoft.com/kb/2750149 (non-security)


    http://support.microsoft.com/kb/2785220
    http://support.microsoft.com/kb/2778930
    http://support.microsoft.com/kb/2757638

    Nothing exotic, nothing exciting.