Tuesday, September 24, 2013

Trello Board Bing Wallpaper GreaseMonkey UserScript

I occasionally use Trello for maintaining tasks for projects. It helps me stay on task and estimate completion times better. The interface makes managing/creating/completing tasks very easy. It is a breath of fresh air for task management.

I thought it would be cool to see a different background everyday. So decided to use the Bing wallpaper rss feed to get me an amazing image every day.

The GreaseMonkey script below works in Firefox. I have tested it in Chrome (via TamperMonkey) and there are a couple issues. I will try to resolve them and update it here.

// ==UserScript==
// @name        Trello Board Canvas Bing Image
// @namespace   net.intellectualponderings.TrelloBoardCanvas
// @require     http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_log
// @grant       GM_xmlhttpRequest
// @grant       unsafeWindow
// @include     https://trello.com/b/*
// @version     1
// ==/UserScript==

GM_xmlhttpRequest({
    url: "http://feeds.feedburner.com/bingimages",
    dataType: "xml",
    //data: "",
    //type: "GET",
    method: "GET",
    //success: function (data){
    onload: function (responseObject){
        var data = responseObject.responseText;
        GM_log(data);
        var enclosures = $(data).find("enclosure"), // /rss/channel/item/enclosure[url]
            imgurl = $(enclosures[0]).attr('url'); //alert(imgurl);
        $(".board-canvas").css({ 
            'background': 'url(' + imgurl.replace("http://", 'https://') + ') no-repeat center center', 
            'background-size': '100%;'});
    },
    //Error: function (XMLHttpRequest, textStatus, errorThrown) {
    onerror: function () {
      // Same code as in my question...
    }
});

Thursday, September 19, 2013

Image Information For Filtered Files in Directory

Just a quick script that lists information about the PNG and JPG images in a directory. I wrapped the important parts in a Start/Stop Transcript so that all output is logged to a file. I needed to get a feel for the image sizes and resolutions uploaded by users in our production environment in order to make recommendations for updates to the system.

add-type -AssemblyName System.Drawing;
try { Start-Transcript ".\Get-ImageInformation-log.txt" -ErrorAction SilentlyContinue; } catch {}
gci "C:\Demo\Images\*" -Include @("*.png","*.jpg") | % {
    $img = New-Object System.Drawing.Bitmap $_.FullName;
    $obj = New-Object PSObject;
        Add-Member -InputObject $obj -MemberType NoteProperty -Name Name -Value $_.Name;
        Add-Member -InputObject $obj -MemberType NoteProperty -Name Length -Value $_.Length;
        Add-Member -InputObject $obj -MemberType NoteProperty -Name Height -Value $img.Height;
        Add-Member -InputObject $obj -MemberType NoteProperty -Name Width -Value $img.Width;
        Add-Member -InputObject $obj -MemberType NoteProperty -Name HRes -Value $img.HorizontalResolution;
        Add-Member -InputObject $obj -MemberType NoteProperty -Name VRes -Value $img.VerticalResolution;
    [Void]$img.Dispose();
    $obj;
} | ft -AutoSize Name, @{n='Length';e={"{0:N2} KB" -f ($_.Length / 1Kb)};align='right'}, Height, Width, HRes, VRes;
try { Stop-Transcript -ErrorAction SilentlyContinue; } catch {}

Monday, September 16, 2013

Adding Bundles to ASP.NET 4.0 Web Applications

I recently encountered an opportunity while updating (rewriting the HTML) the UI on an existing application. Having worked on a a couple .Net 4.5/MVC 4 applications previously and learning the awesomeness of the .NET bundling functionality, I wanted to see if I could bring that functionality back into a .Net 4.0 Web Forms application. I hadn't heard of anyone doing it, but doesn't mean it can't happen.

I ran across a couple sites which lead me in the correct direction. They both target .Net 4.5. The Bundles and Minify CSS and Javascript in your ASP.Net Web Form Web Site post pointed me to the Microsoft ASP.NET Web Optimization Framework NuGet package. It didn't say that it required the .Net 4.5 framework so I added it to my project and it worked without issue.

using System.Web.Optimization;
public namespace SomeNS {
public class BundleConfig
{
    public static void RegisterBundles(BundleCollection bundles)
    {
        bundles.Add(new StyleBundle("~/bundles/style.css").Include(
            "~/Content/bootstrap-cust.min.css",
            "~/Content/styles.css"));

        bundles.Add(new StyleBundle("~/bundles/kui.css").Include(
            "~/Content/styles/kendo.common.css",
            "~/Content/styles/kendo.default.css"));

        bundles.Add(new ScriptBundle("~/bundles/kui.js").Include(
                "~/Scripts/kendo.core.js",
                "~/Scripts/kendo.calendar.js",
                "~/Scripts/kendo.popup.js",
                "~/Scripts/kendo.datepicker.js",
                "~/Scripts/kendo.validator.js"));

        bundles.Add(new ScriptBundle("~/bundles/Common.js").Include(
            "~/Scripts/jquery-1.9.1.js",
            "~/Scripts/Common.js"));

        bundles.Add(new ScriptBundle("~/bundles/UserCommon.js").Include(
            "~/Secure/User/Scripts/Script.js",
            "~/Secure/User/Scripts/CustomValidation.js"));

        bundles.Add(new ScriptBundle("~/bundles/SysAdminCommon.js").Include(
            "~/Scripts/jquery-ui-1.8.17.custom.min.js"));

        bundles.Add(new ScriptBundle("~/bundles/bootstrap.js").Include(
            "~/Scripts/bootstrap-cust.js"));

        BundleTable.EnableOptimizations = true;
    }
}
}

With the NuGet package added to the project all of the functionality surrounding the CDN functionality is available.

Next add the following line to the Application_Start function in the Global.asax.

BundleConfig.RegisterBundles(BundleTable.Bundles);

The post says to use the following block to get the scripts to render. This doesn't work because Scripts/Styles do are not available.

<asp:PlaceHolder runat="server">
        <%: Scripts.Render("~/Scripts/jquery") %>
        <%: Styles.Render("~/Styles/css") %>
</asp:PlaceHolder>

This is not a show stopper; the functions merely generate standard HTML elements. The style bundles can be added to the application pages like the example below.

<link rel="stylesheet" href="/bundles/style.css" type="text/css" />

Similarly, the script bundles can be added to the application pages like the example below.

<script src="/bundles/bootstrap.js"></script>

Using Firebug/developer tools/fiddler, it is easy to see that the files are bundled and minified.

Tuesday, September 10, 2013

Branching Current Changes Into New TFS Branch

Every once in a while I find myself in the middle of a change and think that the changes I am making need to be pushed into a new branch. Being in an evironment using TFS as the source control system, that kind of flexibility is not something I generally think of in the scope of TFS.

From a Stack Overflow question, I was able to get a start. The most important line was the TFPT line. It appeared the task at hand was achievable.

1. You need to include the new branch in your workspace in order to see any changes (otherwise you'll only see the checked-in versions). From Source Control Explorer, select Workspace->Workspaces... from the toolbar.
2. Select Edit for your workspace and add a mapping to your new branch, e.g. Active|$/Root/MyProject-Branch|<my Local TFS Storage>\MyProject-Branch
3 .Run the command tfpt unshelve <shelveset> /migrate /source:$/Root/MyProject /target:$/Root/MyProject-Branch. It should create a new shelveset with mappings changed to your new branch.
4 .Try unshelving the new, migrated shelveset onto your new branch.

This wasn't very clear, so I continued my search. I found Brian Franklin's post was far more detailed and gave a better picture of what needed to happen.

1. Shelve the pending changes.
2. Create a new Dev branch with the latest changes and perform a Get on the branch.
3. Start Visual Studio Command Prompt.
4. Set the path to the folder the new branch is mapped to.
5. Execute tfpt unshelve command supplying the following arguments:
1. Shelveset name
2. Source server path
3. Target server path
4. Migrate
5. No Backup

In the process of going through his steps, which was easy enough, I ran into a peculiar error.

TFPT.EXE unshelve bundles /migrate /source:"$/<TFS project path>/branches/Feature1Rewrite" /target:"$/<TFS project path>/branches/Feature1RewriteExploritory"
An item with the same key has already been added.

Googling yielded no fruitful paths forward. And after some thought, it occurred to me that I might need to check in my branch before migrating my shelf to the new target. After checking in my branch and retrying, the did not occur.

It may be pertinent to note that when I create branches in this environment, I receive an error saying that I don't have permission to create a branch. However, the branch is created and everything seems to work.

TL;DR:

Make sure you check in your branch before running the command.

Wednesday, September 4, 2013

Handling Twitter Bootstrap Modals and UpdatePanels with PostBacks

In the process of updating a web application from an extremely rigid design to a responsive design using the Twitter Bootstrap framework. The goal is to keep the changes to the application to the HTML layout as possible. There are some situations where server side code can be removed or updated for display purposes.

This works great until you have situations which are more complicated like Bootstrap modal dialogs which need to display after a UpdatePanels and UpdatePanels which need to update in modal dialogs.

Searching the internet did not provide very fruitful results. Some involved registering script blocks, but I was not able to get them to work. This was not my preferred implementation because it deviated from my initial goal, but I was looking for solutions.

I remembered that I could execute code on an UpdatePanel PostBack and that led me to the following solution. I think there should be a better way to handle it, but it will do for a first pass. In my situation, I had a master page with a script tag using the PageRequestManager like the example below. I added the highlighted lines to call a function (RequestCallback) if it had been declared.

var prm = Sys.WebForms.PageRequestManager.getInstance();

prm.add_initializeRequest(initializeRequest);
prm.add_endRequest(endRequest);

function initializeRequest(sender, args) {
    if (prm.get_isInAsyncPostBack()) {
        args.set_cancel(true);
    }
}

function endRequest(sender, args) {
    ToggleProgressMsg('Please Wait While Your Content Loads...');
    if (RequestCallback) {
        RequestCallback(sender, args);
    }
}

Now we can declare the RequestCallback function on any page to execute when a call back occurs. This leaves us in an situation where we need to know what panel was updating or what control initiated the request. I found the following variable referenced in several places on the web.

sender._postBackSettings.panelID

I found that in several situations, this variable is not reliable. I found that the following variable was always what I expected.

sender._postBackSettings.asyncTarget

In a situation where a grid needs to populate fields on a modal dialog and display the dialog. The grid and modal dialogs were already wrapped in an UpdatePanel so I can leverage the RequestCallback function to clean up the backdrop (the modal closes, but leaves the backdrop) and then open or reopen the dialog based on the control that executed the request.

function RequestCallback(sender, args) {
    var editLink = "lnkEditCar",
        altAddr = "lnkbtnAlternativeAddress",
        trigger = sender ? sender._postBackSettings.asyncTarget : "";

    console.log("RequestCallback", sender, args, trigger, "END");
    
    // Clean up the backdrop
    $('body').removeClass('modal-open');
    $('.modal-backdrop').remove();
    
    // Handle Alternate Address request callback
    if (trigger.indexOf(altAddr, trigger.length - altAddr.length) !== -1) {
        console.log('Reopening Dialog', altAddr);
        $('#pManageCar').modal();
    }
    // Handle Edit Card request callback
    if ($('span[id$="tabpCars_tab"]').hasClass("ajax__tab_active") && trigger.indexOf(editLink , trigger.length - editLink .length) !== -1) {
        console.log("tabpCars_tab");
        $('#pManageCars').modal('show');
    } else if ($('span[id$="tabpTrucks_tab"]').hasClass("ajax__tab_active") && trigger.indexOf(up, trigger.length - up.length) !== -1) {
        console.log("tabpTrucks_tab");
        $('#pManageTrucks').modal('show');
    }
}

The result is that the modal behaves pretty much as expected. I am sure I will revisit this code, but that is how refactoring works.