Wednesday, June 27, 2012

Google Top Bar Rearrange UserScript for GreaseMonkey

I ran across a script on UserScripts.org called Google topbar. It doesn’t appear to be maintained any longer and doesn’t worked in FireFox 13 (not sure if it didn't work before, but that is what I tested on). So I decided that I would use it as a base for an updated script to make it display the links I most commonly use. 

// ==UserScript==
// @name          Google Top Bar Rearrange
// @namespace     com.blogspot.intellectualponderings.GoogleToolBarRearrange
// @description   Custom Google Links and Order, based on http://userscripts.org/scripts/show/18128
// @include       http://*.google.*/*
// @include       https://*.google.*/*
// ==/UserScript==

(function(undefined) {
  var shared = {
    more: ['Maps', 'Images', 'Play', 'YouTube', 'News', 'Shopping'],
    top: ['Blogger', 'Calendar', 'Reader'],
    topBarList: "//ol[@class='gbtc']",
    topBarNode: "//li[@class='gbt'][a[span[text()='{{0}}']]]",
    moreList: "//ol[@id='gbmm']",
    moreNode: "//li[@class='gbmtc'][a[text()='{{0}}']|text()='{{0}}']",
    nodeLink: "//a"
  };
  
  var node = function(path, text, context) {
    path = (text) ? path.replace(/\{\{0\}\}/g, text) : path;
    context = context || document;
    return document.evaluate(path, context, null, XPathResult.ANY_TYPE,null).iterateNext();
  },
  toMoreMenu = function(text) {
    var tag = node(shared.topBarNode, text);
    if (tag) {
      var more = node(shared.moreList);
      if (more) {
          more.appendChild(tag);
          tag.className = 'gbmtc';
      }
    }
  },
  toTopMenu = function(text) {
    var more = node(shared.moreList),
        tag = node(shared.moreNode, text, more),
        alink, moreli;
    if (tag) {
      var topBar = node(shared.topBarList);
      if (topBar) {
        moreli = topBar.lastChild;
        topBar.appendChild(tag);
        tag.className = "gbt";
        alink = node(shared.nodeLink);
        alink.className = "gbzt";
        if (moreli) {
          topBar.removeChild(moreli);
          topBar.appendChild(moreli);
        }
      }
    }
  };

  if (window.top == window.self) {
      var i;
      for (i = 0; i < shared.more.length; i++) {
        toMoreMenu(shared.more[i]);
      }
      for (i = 0; i < shared.top.length; i++) {
        toTopMenu(shared.top[i]);
      }
  }
})();

And then I decided to change it so that it would use jQuery and be a bit more complete. In fact, I didn't keep much.

// ==UserScript==
// @name          Google Top Bar Rearrange
// @namespace     com.blogspot.intellectualponderings.GoogleToolBarRearrange
// @description   Custom Google Links and Order, based on http://userscripts.org/scripts/show/18128
// @include       http://*.google.*/*
// @include       https://*.google.*/*
// @grant         GM_getValue
// @grant         GM_setValue
// @require       https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js
// ==/UserScript==


(function(undefined) {
  var context = $("#gbz ol.gbtc"),
      more = ['Maps', 'Images', 'Play', 'YouTube', 'News', 'Shopping'],
      top = ['Blogger', 'Calendar', 'Reader'];
      
  var addItem = function(url, text) {
    var item = $("<li />").addClass("gbt").append(
                  $("<a />").attr("href", url).addClass("gbzt").attr("target", "_blank").append(
                    $("<span/>").addClass("gbtb2")
                  ).append(
                      $("<span />").addClass("gbts").html(text)
                  )
                );
        $(context).find("li.gbt:last-child").before(item);
    },
    removeItem = function(text) {
    var link = $(context).find("li.gbt span:contains('" + text + "')").parent(),
        href = link.attr("href");
        link.remove();
        return href;
    },
    moveItemToMore = function(text) {
    if ($(context).find("ol.gbmcc li.gbmtc a:contains('" + text + "')").size() == 0) {
      var url = removeItem(text);
      addMoreItem(url, text);
    }
    },
    addMoreItem = function(url, text) {
    var item = $("<li />").addClass("gbmtc").append(
                  $("<a />").attr("href", url).addClass("gbmt").attr("target", "_blank").html(text));
        $(context).find("ol.gbmcc li.gbmtc:last-child").prev().before(item);
    },    
    removeMoreItem = function(text) {
    var link = $(context).find("ol.gbmcc li.gbmtc a:contains('" + text + "')"),
        href = link.attr("href");
        link.remove();
        return href;
    },
    moveItemToTop = function(text) {    
    if ($(context).find("a span.gbts:contains('" + text + "')").size() == 0) {
      var url = removeMoreItem(text);
      addItem(url, text);
    }
    };
    
  if (window.top == window.self) {
      var i;
      for (i = 0; i < more.length; i++) {
        moveItemToMore(more[i]);
      }
      for (i = 0; i < top.length; i++) {
        moveItemToTop(top[i]);
      }
  }
})();

I am much happier with this one.  Using the DOM XML functions seems so pre-jQuery.

UPDATE 2012-08-29: GreaseMonkey updated and made Google Reader not display feed items. I added some @grant lines in the header and everything works again. My discussion on Google Groups is located here.

// @grant         GM_getValue
// @grant         GM_setValue

Wednesday, June 20, 2012

MSBuild Task Build Projects With Different Properties

I built an MSBuild Script a couple months ago and stumbled across a couple interesting features that MSBuild gives you a way to control a project file's build in a fine tuned way. In my build scenario, I have 3 SharePoint 2010 projects and a WCF service to build (preferably without a lot of source). Since I want to generate the SharePoint solutions and have separate have the WCF service output placed in a different place, normally the path of least resistance would be to have two MSBuild tasks, one for the SharePoint projects and one for the WCF service. This works, but there is some refactoring that can be accomplished. Thus, the following.

Suppose a script reads in several properties we want to use on the various projects. In this example we will hardcode them in a PropertyGroup node as follows:
<PropertyGroup Label="Build options" Condition="'$(GenerateSerializationAssemblies)' == ''">
    <GenerateSerializationAssemblies>Auto</GenerateSerializationAssemblies><!-- Auto|On|Off -->
    <Configuration>Release</Configuration>
</PropertyGroup>
Now we define the ItemGroup with all of our projects. The important part each node is that we define the Targets and Properties nodes that we would otherwise define in the MSBuild task node. This example shows that we set the same Configuration setting for all projects and only set the GenerateSerializationAssemblies on the DemoWeb project and then set it to off for the other projects.
<ItemGroup>
<ProjectsToBuild Include="$(SourceFolder)\$(DemoWeb)\$(DemoWeb).csproj">
    <Targets>Package</Targets>
    <Properties>BasePackagePath=.\$(WSPOutputFolder)\;IsPackaging=True;Configuration=$(Configuration);GenerateSerializationAssemblies=$(GenerateSerializationAssemblies)</Properties>
</ProjectsToBuild>
<ProjectsToBuild Include="$(SourceFolder)\$(DemoConfiguration)\$(DemoConfiguration).csproj">
    <Targets>Package</Targets>
    <Properties>BasePackagePath=.\$(WSPOutputFolder)\;IsPackaging=True;Configuration=$(Configuration);GenerateSerializationAssemblies=Off</Properties>
</ProjectsToBuild>
<ProjectsToBuild Include="$(SourceFolder)\$(DemoBusinessData)\$(DemoBusinessData).csproj">
    <Targets>Package</Targets>
    <Properties>BasePackagePath=.\$(WSPOutputFolder)\;IsPackaging=True;Configuration=$(Configuration);GenerateSerializationAssemblies=Off</Properties>
</ProjectsToBuild>
<ProjectsToBuild Include="$(WCFSourceFolder)\$(DemoSvc)\$(DemoSvc).csproj">
    <Targets>ResolveReferences;_CopyWebApplication</Targets>
    <Properties>WebProjectOutputDir=$(WCFOutputFolder)\;OutDir=$(WCFOutputFolder)\bin\;Configuration=$(Configuration)</Properties>
</ProjectsToBuild>
</ItemGroup>
Now the projects and settings are set as needed. The compilation is done as normal, just with some of the nodes refactored. This is great, one MSBuild task, and finer grain control over each project.
<Target Name="Compile" DependsOnTargets="Clean">
    <!-- ... Other tasks ... -->
    
    <MSBuild Projects="@(ProjectsToBuild)">
        <Output ItemName="BuildOutput" TaskParameter="TargetOutputs"/>
    </MSBuild>
    <Message Text="!%0D%0A@(BuildOutput)" />

    <!-- ... Other tasks ... -->
</Target>
As it turns out, it appears that the properties/targets from the current project are applied onto the result of the other project's properties/targets (respectively applied onto the previous.

I have not found this documented previously, but it works for me. I hope it will help someone else.

Wednesday, June 6, 2012

Show and Hide VirtualBox Menu Bar and Status Bar

I use VirtualBox to host virtual machines on my laptop. I am not a fan of clutter, especially when things needlessly take up space. The menu and status bar on the virtual machine window fall into the category of unneeded clutter most of the time. After a considerable amount of digging, I found the VBoxManage command and from there built my commands from there.

There is a downside to using this, the changes are applied the next time VirtualBox starts. Which means you have to completely close VirtualBox and then start it again to toggle it. When you are finished with the bars, you have to close VirtualBox again to toggle it back. I find that I need to use it once every couple months, so the downside is not really an issue.

Here are the contents of batch files to toggle the menu bar and status bar.
C:
cd "\Program Files\Oracle\VirtualBox"
VBoxManage setextradata global GUI/Customizations noMenuBar,noStatusBar
As always, it is good to know how to undo what just occurred.
C:
cd "\Program Files\Oracle\VirtualBox"
VBoxManage setextradata global GUI/Customizations MenuBar,StatusBar