Wednesday, December 28, 2011

SharePoint 2010 Custom Application Page Affix Ribbon To Top Using CSS

Migrating existing applications into SharePoint can be difficult depending on the JavaScript functionality of the old code. Using the default SharePoint 2010 custom application page, the s4-workspace is a div that is re-sized and scrollable to allow the SharePoint ribbon to display. I don't know why Microsoft felt it necessary to do far more work than necessary to fix a div to the top of the window.

Below is the code I used to fix the scroll bars on the page. This makes the ribbon not fixed and will scroll out of view. This could be enough if you don't use the ribbon in your pages.
body {
    overflow: auto ! important;
}
body.v4master { 
    height:inherit; 
    width:inherit; 
    overflow:visible!important;
}

body #s4-workspace {
   overflow-y:auto !important;
   overflow-x:auto !important;
   height:auto !important;
}

If the ribbon absolutely must be at the top of the page. You can add this bit of code after the above code to properly affix the div to the top of the visible window. My tests showed that this worked for me in IE8, IE 9, and Firefox.

body #s4-ribbonrow {
    left: 0;
    position: fixed;
    top: 0;
    width: 100%;
    z-index: 101;
}
body #s4-workspace {
    padding-top: 44px;
}

That should be it. Not too hard.





Tuesday, December 27, 2011

SharePoint 2010 Custom Application Page jQuery Lightbox Plug-in Fix

I used a fantastic jQuery lightbox plug-in which can be found at http://leandrovieira.com/projects/jquery/lightbox/. There was almost no setup involved. That is, until it meets SharePoint 2010 custom application pages. Since the page doesn't use the body scroll bars and creates faux-scroll bars in the s4-workspace div, it is possible that the image is too large for the visible area and there are no functional scroll bars to view the rest of the picture. I should suffix my last statement with the fact that this probably would have happened with any lightbox plug-in; it just happened that this is the plug-in I used.

I was tasked with finding a fix and below is the result. I only had to add code to the beginning of two functions: _set_interface and _finish. The short of it is that I cache the important styles that I am going to change, then I modify the styles to enable the page scroll bars. When the lightbox is closed, the cached styles are restored.

var htmlbody = $("BODY"),
    bodyMaster = $("body.v4master"),
    bodyWorkspace = $("body #s4-workspace"),
    savedCSS = {
        BodyOverflow: htmlbody.css("overflow"),

        BodyMasterHeight: bodyMaster.css("height"),
        BodyMasterWidth: bodyMaster.css("width"),
        BodyMasterOverflow: bodyMaster.css("overflow"),

        BodyWorkspaceOverflowY: bodyWorkspace.css("overflow-y"),
        BodyWorkspaceOverflowX: bodyWorkspace.css("overflow-x"),
        BodyWorkspaceHeight: bodyWorkspace.css("height")
    };
    settings.SavedCSS = savedCSS;
            
htmlbody.css({ "overflow": "auto" });
bodyMaster.css({ "height": "inherit", "width": "inherit", "overflow": "visible" });
bodyWorkspace.css({ "overflow-y": "auto", "overflow-x": "auto", "height": "auto" });

var htmlbody = $("BODY"),
    bodyMaster = $("body.v4master"),
    bodyWorkspace = $("body #s4-workspace"),
    savedCSS = settings.SavedCSS;

htmlbody.css({ "overflow": savedCSS.BodyOverflow });
bodyMaster.css({ "height": savedCSS.BodyMasterHeight, "width": savedCSS.BodyMasterWidth, "overflow": savedCSS.BodyMasterOverflow });
bodyWorkspace.css({ "overflow-y": savedCSS.BodyWorkspaceOverflowY, "overflow-x": savedCSS.BodyWorkspaceOverflowX, "height": savedCSS.BodyWorkspaceHeight });

SharePoint 2010 Custom Application Page Scroll To Top On Postback

SharePoint 2010 is full of wonderful features that make developers' lives just a bit harder. I ran across an issue where validation was returning a message back to the screen, the page would display the page scrolled to the top and then immediately scroll down to the position to the location of the page prior to posting back. I have had previous run-ins with the s4-workspace, but nothing JavaScript related. I tried several avenues for solutions:

1. Setting the page directive attribute "MaintainScrollPosition" to be false
2. Registering a start up script: $(window).scrollTop(0)
3. Registering a start up script: $("#s4-workspace").scrollTop(0)
4. Attempted to register the the functions via a client script block to add a "pageLoaded" event

The short of it was that none of these worked. I decided to dive in to the HTML source and discovered a "_maintainWorkspaceScrollPosition" hidden field. This looked amazingly like the MaintainScrollPosition functionality, I thought I might be on the right path. The "Workspace" term jumped out at me since the SharePoint custom application page's content is in the s4-workspace; I started to get the feeling that this was a SharePoint feature. After searching all the files for the hidden field, I discovered it was only referenced in the SharePoint JavaScript files. Searching the internet did not yield any solutions on ways to disable the feature. So I generated a function that I would execute to scroll the page to the top.

function scrollToTop() {
    $(window).scrollTop(0);
    $("#s4-workspace").scrollTop(0);
    $("#_maintainWorkspaceScrollPosition").val(0);
}

The function above probably does more than required, but I don't control the Master Page and need to make sure the page can tolerate changes to Master Page style changes.

The server side needs to register a script to execute on the post back. This is a simple line that can be thrown about anywhere.

ScriptManager.RegisterStartupScript(this, this.Page.GetType(), "scrollToTop", "scrollToTop();", true)

Wednesday, September 7, 2011

WCF Service Call with X.509 Certificate Using Powershell

I ran into a situation where I needed to be able to query a WCF service that used an X.509 certificate. I searched the web and was left wanting. So, I turned to powershell. I had previously used powershell to call web services (mostly in Powershell v1), but never hit a WCF service. I found a useful post that took me a little farther, namely it got me to the point where I could script the proxy code generation and compiling the assembly.

I added code to make the binding use the certificate (highlighted line 9 below) and then specified the certificate to use in the client proxy object. My certificate is installed into the Personal store on the local machine (highlighted line 13 below).

Upon testing the code (sans line 10). I received an error "If this is a legitimate remote endpoint, you can fix the problem by explicitly specifying DNS identity '' as the Identity property of EndpointAddress when creating channel proxy." It was trying to match the partial domain name to the fully distinguished domain name. I found that I could fix this limitation by specifying the DNS entry that I was expecting on the endpoint (highlighted line 10).

$proxy = "http://ws.logos.domain:8181/LogosService.svc";
& 'C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\SvcUtil.exe' "$proxy?wsdl"
& 'C:\Windows\Microsoft.NET\Framework\v3.5\csc.exe'  /t:library LogosService.cs /r:"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\System.ServiceModel.dll"

[Reflection.Assembly]::LoadWithPartialName("System.ServiceModel");
[Reflection.Assembly]::LoadFrom("$pwd\LogosService.dll");

$wsHttpBinding = New-Object System.ServiceModel.WSHttpBinding;
$wsHttpBinding.Security.Message.ClientCredentialType = [System.ServiceModel.MessageCredentialType]::Certificate;
$endpoint = New-Object System.ServiceModel.EndpointAddress($memberProxy, [System.ServiceModel.EndpointIdentity]::CreateDNSIdentity("ws.Logos"))

$proxy = New-Object MemberServiceClient($wsHttpBinding, $endpoint)
$proxy.ClientCredentials.ClientCertificate.SetCertificate([System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine, [System.Security.Cryptography.X509Certificates.StoreName]::My, [System.Security.Cryptography.X509Certificates.X509FindType]::FindBySubjectName, "ws.Logos");
$request = New-Object LogosService.ServiceContracts.GetUserInfoRequest;
$request.RequestId = [Guid]::NewGuid();
$request.login = 'domain\test';

$response = $proxy.GetUserInfo($request);

Once I had the $response object, I could get at all the information I needed. Pretty useful.

Thursday, September 1, 2011

JavaScript Format File Size Truncate Decimals

I found a nice bit of JavaScript code which decides what size abbreviation to display for a given number of bytes. For my situation, it was a bit too limited so, I used the awesome bit of logarithmic logic and added the ability to handle null and truncate to a number of decimal places (defaulting to 2 decimals).

function formatSize(bytes, decimals) {
    if (!!!bytes) return 'n/a';
    var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'],
        i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))),
        decMult = Math.pow(10, decimals || 2);
    return (Math.round((bytes / Math.pow(1024, i)) * decMult)) / decMult + ' ' + sizes[i];
}

The bit of logarithmic logic may not be the obvious choice of most developers; I certainly haven't seem that logic anywhere else, but it works. After seeing this and peeking my interest, I wanted to understand how this path of logic was obvious to someone else.

Lets first consider what it would take to achieve the goal using a non-logarithmic solution. It could involve casting the number to a string to find it's length and then doing some calculation on that to produce the index that mapped to the abbreviation. You would have to account for possible fringe cases. Potentially this is easy enough.

For those that despise math, the following may sound similar to Vogon poetry. Plus, I am not a math teacher, so bear with me, I am attempting to think through it logically.

Logarithms are used in many every day situations. The two that come to mind first are decibels (sound volume) and the richter scale (earthquake); both of these use base 10. So each step higher is 10 times the previous value or we can call this a period. In short, it provides a way to turn an exponential equation/graph into a linear equation/graph. It is not important to understand the math, but notice the at 10 maps to 10 and 100 maps to 20.

From: http://www.ndt-ed.org/GeneralResources/decibel/decibel.htm, Note: log is in base 10.
Ratio between Measurement 1 and 2 Equation dB
10 dB = 10 log (10) 10 dB
100 dB = 10 log (100) 20 dB


In this situation we want to determine which period the largest number is in (so to speak). Abstractly, ",...,<3>,<2>,<1>,<0> bytes".

For Example:
25490 would fit in to <1> per the representation above.

Lets start to put the decibels/richter scale and this case together. Decibels uses a base(period) of 10. A base(period) of 10 does not translate usefully to bytes. However, a period of 1024 does mean something.

So we can use Log base 1024 of x bytes (lets write this as log1024(x)) to find out which period it falls in. Javascript has a function (log10) which will evaluate log base 10 of a value. However it doesn't allow you to specify a specific base. I will elide a lot of logic and say that Javascript has a function (log) which is the natural log of a value. See the links below for more information on natural log. To simplify things, we can solve any log base "b" of "a" using only natural logs (math shorthand: ln).

logb(a) = ln(a) / ln(b)
Thus,
log1024(a) = ln(a) / ln(1024)

So we can use Wolfram Alpha to do some examples.

ln(20480) / ln(1024) = 1.432...
http://www.wolframalpha.com/input/?i=ln%2820480
%29%2Fln%281024%29






ln(2048000) / ln(1024) = 2.096...
http://www.wolframalpha.com/input/?i=ln%282048000%29%2Fln%281024%29






If we take the mathematical floor of our natural log formula, we can map this to our list of abbreviations.
...,<3>,<2>,<1>,<0> bytes
[..., GB, MB, KB,bytes]

Mapping the results yields KB for Example 1 and MB for Example 2.

This is a pretty cool line of thought for solving a programming problem mathematically.

Additional information:
The original code that spurred my interest can be found here: http://codeaid.net/javascript/convert-size-in-bytes-to-human-readable-format-%28javascript%29

Wednesday, August 31, 2011

Javascript CAML Where clause OR Builder

It is pretty rare that I run into an occasion where I get to use recursive functions and it is always great. This case is pretty limited, but is perfectly suited for JavaScript. Essentially, I needed to be able to tell if a series of files existed in a SharePoint document library because of an old process where data in the database may contain file names which were not in the SharePoint document library. The root cause of this issue is in the file loading and the database loading processes, however changing these are out of scope.

If the list of possible file names is known we can build a query and have SharePoint return the files that exist. In this case I don't need a reusable function, so I decided that I would take the chance and use an area of JavaScript that I almost never get to utilize: self-referencing anonymous functions or recursive anonymous functions. Below is my implementation, it writes the result of the function to the FireBug/IE Developer Toolbar console.

NOTE: Below there is an issue with the syntax highlighter functionality. If you copy and paste the test below, you must change the "" + field + "" to '" + field + "' and the Type="Text" to Type='Text'
Hopefully there will be a fix to the rendering functionality, but until then, there we are.
files = ["file3.pdf", "file1.pdf", "file2.pdf"];
ors = (function buildOR(field, filelist) {
    var BuildEq = function (field, value) { return "" + value + ""; }
    return (filelist.length == 1) ? BuildEq(field, filelist.shift()) : "" + BuildEq(field, filelist.shift()) + buildOR(field, filelist) + "";
})("FileLeafRef", files)

console.log(ors);
//buildOR("FileLeafRef", files);
Running the example with line 8 uncommented, causes the browser to throw an error. This occurs because the "buildOR" function is an anonymous function. The name "buildOR" is only available inside the function.

Running this example produces the following output (I have formatted it for readability). Note: The FieldRef nodes are self closing, the syntax highlighter appears does not handle these correctly, so I have changed the self closing nodes to accommodate the limitation.

    
        
            
            file3.pdf
        
        
            
                
                file1.pdf
            
            
                
                file2.pdf