ILearnable .Net

August 23, 2012

Brilliant Chunking of IEnumerables

Filed under: Uncategorized — andreakn @ 08:58

I recently needed to chunk a *huge* IEnumerable into manageable chunks (SqlServer only allows _so_ many parameters in a single query (update x where y in *huge ienumerable*) )

So I came across this little gem on StackOverflow:

    public static class IEnumerableExtension
        public static IEnumerable<IEnumerable> Chunks(this IEnumerable source, int chunkSize)
            var chunk = new List(chunkSize);
            foreach (var x in source)
                if (chunk.Count < chunkSize)
                yield return chunk;
                chunk = new List(chunkSize);
            if (chunk.Any())
                yield return chunk;

August 16, 2012

ASPNET_COMPILER: “Circular file references are not allowed” or “how bad neighbors can ruin the neighborhood”

Filed under: Uncategorized — andreakn @ 07:36

Yesterday I came across a cute little bug in our codebase ( which had me totally stumped for a bit.

After a massive change in an underlying API which introduced breakage left and right I had finally managed to get the codebase to compile again and was about to investigate whether changes were needed in the code-front files (.ascx / .aspx / .master). In order to do this somewhat efficiently I ran the aspnet_compiler.exe on the codebase to see what it would tell me:

C:\Windows\Microsoft.NET\Framework\v4.0.30319\aspnet_compiler -p “E:\*path_to_solution*\*project_directory*\” -v “E:\temp\compilationoutput”

It barfed on me exclaiming “Circular file references are not allowed”. Well isn’t that just nice? I’m fairly certain that I don’t have any “circular file reference”s in the codebase so just what does this mean? After googling a bit I tried running a modified command:

C:\Windows\Microsoft.NET\Framework\v4.0.30319\aspnet_compiler -p “E:\*path_to_solution*\*project_directory*\” -v “E:\temp\compilationoutput” -fixednames e:\temp\aspnetout

which ran without any such errors.


after thinking for a bit I realized what the problem was: Asp.Net compiles all ascx-controls *in the same folder* into *one* dll. So if you have two folders A and B, each with two controls (A1, A2 and B1,B2), while it is obviously not good to have A1 reference B1 and B1 reference A1 back, it is not so obviously similarly no good having A1 reference B1 and also B2 reference A2. Even though none of the files have a circular references, because of the way they are packaged into dlls the resulting dlls *do* have circular references.

There are two ways to fix this:

1) compile each control into its own dll. That way they are not affected by their “neighbors'” behaviors at all (this is done by the aspnet_compiler when you specify “-fixednames”, it is also achieved in the running code by setting batch=”false” in the compilation section of web.config)

2) compile *all* controls into a single dll (however, afaik there is no way to specify this, neither using aspnet_compiler or using web.config)


In the end I tracked down which control was being a lousy neighbor to the other controls in the same folder and moved it to a new neighborhood


September 2, 2011

The specified service has been marked for deletion

Filed under: Uncategorized — andreakn @ 11:50
After stopping a windows service (.Net 3.5) through the GUI, I uninstalled it using installutil.exe (commandline), then I tried to install the same service using a newer version of the framework (.Net 4.0). No such luck.
“The specified service has been marked for deletion”
Turns out that the GUI still holds a reference to the service even though it’s technically uninstalled, so I needed to shut down the windows service GUI window, then I could reinstall the service.

August 3, 2011

Bug in PageTypeBuilder 1.3.0 and 1.3.1

Filed under: Uncategorized — andreakn @ 09:19

Quite a while back I convinced Joel to include a feature in PTB 1.3 which enabled putting PageTypeProperty attributes on interface declarations, which has made my life using PTB a heckuva lot easier,

Instead of having to use inheritance to get properties onto pages I now mostly use interfaces, especially for “side-features” like stuff that is supposed to go into the sidebar of a site it doesn’t always make sense to specify this using inheritance, sooner or later you end up with a page which needs the side-feature, but have nothing else in common with the other pages, and inheritance trees based on side-feature is an antipattern only until you add side-feature number two, then it becomes impossible.

Now for years I have preferred to use PTB *only* for defining the pages at startup-time, and not for extracting the data from them during run-time. (I have found that the convenience of specifying prop names as strings to EPiServer:Property far outweighs the potential for harm during refactoring) And if this is all you are doing then both 1.3.0 and 1.3.1 will work fine for you.

It wasn’t until some collegues of mine started using the new interface features that I became aware that I had introduced a bug into PTB: Whenever you declare page type properties using interfaces you cannot get those properties back using the same interface. As I said I never used PTB for this purpose, that’s my excuse and I’m sticking to it.

10 minutes after being made aware of this defect I had created a fix for it and shipped it to Joel.

That’s where it stopped. Joel never did release a fixed version of PTB 1.3.0, and when 1.3.1 came out it had the same bug in it.

I asked Joel why during NDC2011 and he claimed that he had found some problem with the fix but couldn’t remember what that was exactly.

This means that whenever I start up a new project I always have to use my special-build PTB and not the official one.

Anyhoo, I thought more people might be interested in the fix, so here goes, all you need to change the implementation of one method in PageTypeBuilder/Reflection/MethodInfoExtensions.cs:

public static bool IsGetterOrSetterForPropertyWithAttribute(this MethodInfo propertyGetterOrSetter, Type attributeType)
            if (!propertyGetterOrSetter.IsGetterOrSetter())
                return false;

            string propertyName = propertyGetterOrSetter.GetPropertyName();
            PropertyInfo property = propertyGetterOrSetter.DeclaringType.GetAllValidPageTypePropertiesFromClassAndImplementedInterfaces().FirstOrDefault(p=>p.Name.Equals(propertyName));

            if (property == null)
                return false;

            return property.HasAttribute(attributeType);

May 22, 2011

Introducing EPiLang

Filed under: Uncategorized — andreakn @ 18:28

A while back I wrote about how to get strongly typed globalization handling for EPiServer CMS.

In the time since I have been playing around with ways to empower administrators of EPiServer sites to change the globalization texts themselves without having to involve developers directly. I don’t have know how many times the customer has come to me and requested that this string should be that and required me to circumvent our standard deployment routine so that “we can get the new string out immediately”

What I have come up with is basically a framework that allows each text used to be persistently overridden in runtime by an administrator. Combined with the T4-goodness from my previous post I believe I have made something that could turn out to be useful for some other sites. I Have decided to call it EPiLang (yes, I am inspired by the awesome EPiImage framework)

Under normal conditions you can access globalized strings in three different ways in EPiServer:

  1. <%= LanguageManager.Instance.Translate(“/xpath/to/string”) %>
  2. <EPiServer:Translate runat=”server”  Text=”/xpath/to/string” />
  3. <%$ Resources: EPiServer,>
Each of these methods require you to spell the path to the text correctly and also requires a fair bit of looking up the xml file(s) going “where did I put that string….” .
With EPiLang you can access strings this way
  • <%= LanguageService.Translate(“/xpath/to/string”) %>
  • <EPiLang:Translate runat=”server”  Text=”/xpath/to/string” />
  • <%$ Resources: EPiServer,>
  • <%= %>
The first three are as you can see remarkably like the originals, but the last one is the one you’ll find yourself using over and over again since it is strongly typed and gives you compiler support and intellisense.
No matter which of these you use, after adding the webcontrol <EPiLang:Editor runat=”server” /> to the very end of your master page, any administrator surfing the site will get a new item on his/her EPiServer context menu:
When they click that item a list of all overrideable texts used for that page rendering will be shown in the lower right hand corner of the page:
If the administrator clicks on one of the texts then an editor is shown in a lightbox:
Now the administrator may input a new string into the textbox and click save
on refreshing the page the globalized text will now be changed:
What happened here was that every time EPiLang is asked to fetch a string, it registers it on the HttpContext.Current.Request.Items array, so that when the editor is rendered it can produce a list of all the relevant strings.  The mechanism used for localizing this string here (this is taken from the AlloyTech sample provided by EPiServer) is like this:
EPiServer has defined its own resourcefactory which enables this syntax, however EPiLang substitutes this factory with  its own during installation and so is able to override which string is actually displayed.
There is a way to enable administrators to just click on the text inline instead of finding it in the list on the lower right hand side, we can exchange the code above with a strongly typed editable string:
which in turn light up the string when an admin enables editing of globalized strings through the context menu
The editable text is in fact a <span> tag with some attributes set which enables the editing
Overridden strings are not saved back to the xml file, as the next deploy would probably undo what the admin just did. Instead they are saved into EPiServers Dynamic Data store, along with the original string. That way if the string in the xml file changes for any reason then that value is used (until it is overridden at a later stage). Using this setup ensures that the latest write wins.
If you want to play around with EPiLang you are more than welcome to do so. It is CMS6 only and you will find it on nuget under the name Forse.EPiLang
When you install it it modifies your web.config slightly, it:
  • Adds Forse.EPiLang to pages/namespaces
  • Adds Forse.EPiLang.Controls to pages/controls
  • Adds a httphandler to handle the overriding commands (from the lightbox)
  • Exchanges the resourceProviderFactoryType with a new one.
on uninstall all these changes are reverted.
You will also get a readme file under the App_Readme folder. It contains the instructions on what to do to get up and running.
If this is useful for anyone I’ll put it up on EPiCode. Any feedback is much appreciated

May 2, 2011

EPiServer CMS 5 / PageTypeBuilder 1.1 / UnmappablePropertyTypeException

Filed under: Uncategorized — andreakn @ 09:18

I never work on EPiServer projects without PageTypeBuilder anymore, it’s such a great tool, however version 1.1 (the one to use for EPiServer CMS 5) has a really annoying “bug/feature” in it:

If you add a new custom property type and make use of it all in one go then PTB will *sometimes* throw an UnmappablePropertyTypeException. The reason is that EPiServer does not yet know of a given property, (the reflection magic which makes EPi startup times such a nightmare has not yet been executed on the assembly containing the prop and thus registered the prop to the EPi DB)

(this is not a problem on later PTB versions, but they don’t support EPiServer CMS 5)

the reason this only crops up *sometimes* is that the order in which EPi CMS 5 does its thing is important. If it finds the DLL with the prop first then you are ok, if it finds the PTB page type definitions first then you get the exception. This wouldn’t be such a huge problem if EPi continued wth dll registration after PTB throws up, so that the property in question would get into the DB and thus be ready for the next time PTB starts up, but then I wouldn’t be writing this blogpost if it did.

Even if you are really careful with always adding the custom property first, starting the site, adding the use of the property to a PTB page type, starting the site again, you are vulnerable when deploying to a new system (test / stage / prod) or when rolling back to a previous version of the EPiServer DB.

PageTypeBuilder comes with a funnily named config setting you can enable: “disablePageTypeUpdation”, but setting this to true does not stop the exception from cropping up, as the thing you are disabling is the “updation” of already known page types, it still goes through and adds new pagetypes, and more importantly: it validates the entire PTB setup, which fails as there is an unknown property type.

So I made my own version of PTB 1.1 which actually lets you disable it completely so that it doesn’t throw a spanner in the works when registering new custom properties, the new property is called disablePageTypeBuilder, and its implementation is thusly:


        [ConfigurationProperty("disablePageTypeBuilder", IsRequired = false)]
        public virtual bool DisablePageTypeBuilder
                return (bool)this["disablePageTypeBuilder"];

\Initializer.cs :

  public static void Start()
            lock (_lockObject)
                if (_started)

                PageTypeBuilderConfiguration configuration = PageTypeBuilderConfiguration.GetConfiguration();
                if (!configuration.DisablePageTypeBuilder)
                    PageTypeSynchronizer synchronizer = new PageTypeSynchronizer(new PageTypeDefinitionLocator(),


                    DataFactory.Instance.LoadedPage += DataFactory_LoadedPage;
                    DataFactory.Instance.LoadedChildren += DataFactory_LoadedChildren;
                    DataFactory.Instance.LoadedDefaultPageData += DataFactory_LoadedPage;

                _started = true;


    public class UnmappablePropertyTypeException : PageTypeBuilderException
        private const string NewMappingsHint =
You may try to disable PageTypeBuilder using the pagetypebuilder config section
( <section name=""pageTypeBuilder"" type=""PageTypeBuilder.Configuration.PageTypeBuilderConfiguration, PageTypeBuilder""/> )
with attribute <pageTypeBuilder disablePageTypeBuilder=""True"" />
and run the site once without PTB to register any new custom properties first,
then reactivate PTB to make use of new properties";

        public UnmappablePropertyTypeException()

        public UnmappablePropertyTypeException(string message) : base(message + NewMappingsHint)


        public UnmappablePropertyTypeException(string message, Exception innerException)
            : base(message + NewMappingsHint, innerException)

This means that when you get the exception you can just turn off PTB, restart, then turn on PTB and restart and you’re set

You disable PageTypeBuilder like this in web.config:

    <section name="pageTypeBuilder" type="PageTypeBuilder.Configuration.PageTypeBuilderConfiguration, PageTypeBuilder"/>
    <pageTypeBuilder disablePageTypeBuilder="true" />

If you’re too lazy to compile it yourself you can download this file:, and change from .doc to .zip, (thanks a lot wordpress….) unzip it and use it instead of the regular PTB1.1

December 2, 2010

Getting EPiServer CMS6 to run under Asp.Net 4.0

Filed under: Uncategorized — andreakn @ 13:33

There’s a couple of gotchas for getting EPiServer to run under .Net 4.0

New default ClientID generation scheme in .net 4 disrupts EPiServer’s javascripts

If you find youself getting the following javascript exception when creating pages or expanding nodes in the edit tree in episerver:

error: the target Fullregion$explorertreeview$treeview for the callback could not be found or dit not implement icallbackeventhandler

The solution is to add the following attribute to web.config: under configuration > system.web > pages :  clientIDMode=”AutoID”

You can scope this to only be applied on the util and UI locations if you don’t want the old school ID generation scheme applied globally


Validation is more strict in .net 4.0

When editing pages and submitting rich text content containing tags you might get complaining to you that you need to turn off eventvalidation, but in addition you also need to instruct 4.0 to use <httpRuntime requestValidationMode=”2.0″ /> under configuration > system.web

November 29, 2010

Get C# 3.5 to work in a T4 template on VS2008

Filed under: Uncategorized — andreakn @ 08:15

In order to be able to use C# 3.5 in a .tt file on VS2008 you need to hack the devenv.exe.config file located at C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE, adding the following to the /configuration/runtime/assemblyBinding node

<!-- START T4 Hack -->
  <assemblyIdentity name="Microsoft.VisualStudio.TextTemplating" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" /> 
  <bindingRedirect oldVersion="" newVersion="" /> 
  <assemblyIdentity name="Microsoft.VisualStudio.TextTemplating.VSHost" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" /> 
  <bindingRedirect oldVersion="" newVersion="" /> 
<!-- END T4 Hack -->

You also need to explicitly declare that you want to use C# 3.5 in the top of the .tt file like this:

<#@ template debug="true" language="C#v3.5" #>

November 22, 2010

WebActivator / PreApplicationStartMethod

Filed under: Uncategorized — andreakn @ 07:51

Yesterday I was totally taken aback by the tiny but excellent webactivator project. I totally had a near-magic experience, that’s when I’m almost starting to suspect something works because of tiny magic elves within the .Net runtime “just know what to do” and even though the code doesn’t seem like it’s doing the right things it’s still working.

Let me explain: WebActivator is something you will probably see more and more of the more you start to use modules from NuGet. It is a neat little dll which allows you to specify anywhere in your code that a certain method is to be called on application startup. See this example from Ninject, this class gets added to your solution when installing the ninject package with nuget:

[assembly: WebActivator.PreApplicationStartMethod(typeof(SinsenWeb.Web.AppStart_NinjectMVC3), "Start")]
namespace YourNameSpace {
    public static class AppStart_NinjectMVC3 {
        public static void RegisterServices(IKernel kernel) {

        public static void Start() {
            // Create Ninject DI Kernel
            IKernel kernel = new StandardKernel();

            // Register services with our Ninject DI Container

            // Tell ASP.NET MVC 3 to use our Ninject DI Container
            DependencyResolver.SetResolver(new NinjectServiceLocator(kernel));

Ignore the ninject specific stuff. The goodness that webactivator brings to the table is the first line where a usage of the webactivator attribute will result in a certain method will be called on startup.

Interested in knowing just *how* WebActivator manages to do this I looked up the sourcecode. Latest version of  which is available here

as it only consists of two classes, the attribute which you can include (like ninject did) and the implementation of the logic I thought it would be easy to get a handle on this.

I’ll include the two sources here so you can see if you’re smarter than me in figuring out how this works.

using System;
using System.Reflection;

namespace WebActivator {
    // This attribute is similar to its System.Web namesake, except that
    // it can be used multiple times on an assembly.
    [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
    public sealed class PreApplicationStartMethodAttribute : Attribute {
        private Type _type;
        private string _methodName;

        public PreApplicationStartMethodAttribute(Type type, string methodName) {
            _type = type;
            _methodName = methodName;

        public Type Type {
            get {
                return _type;

        public string MethodName {
            get {
                return _methodName;

        public void InvokeMethod() {
            // Get the method
            MethodInfo method = Type.GetMethod(
                BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);

            if (method == null) {
                throw new ArgumentException(
                    String.Format("The type {0} doesn't have a static method named {1}",
                        Type, MethodName));

            // Invoke it
            method.Invoke(null, null);


using System;
using System.IO;
using System.Reflection;
using System.Web;

namespace WebActivator {
    public class PreApplicationStartCode {
        public static void Start() {
            // Go through all the bin assemblies
            foreach (var assemblyFile in Directory.GetFiles(HttpRuntime.BinDirectory, "*.dll")) {
                var assembly = Assembly.LoadFrom(assemblyFile);

                // Go through all the PreApplicationStartMethodAttribute attributes
                // Note that this is *our* attribute, not the System.Web namesake
                foreach (PreApplicationStartMethodAttribute preStartAttrib in assembly.GetCustomAttributes(
                    inherit: false)) {

                    // Invoke the method that the attribute points to

Hmm, so basically, we declare an attribute and we have a static method which when run will track down all instances of this attribute in all dlls in the bin folder and fire the method as specified in each of those instances.

But *who* calls the static method?

I had a look in the Properties\AssemblyInfo.cs file which looks like this:

using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Web;

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("WebActivator")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Microsoft")]
[assembly: AssemblyProduct("WebActivator")]
[assembly: AssemblyCopyright("Copyright © Microsoft 2010")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components.  If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("3bc078bd-ade4-4271-964f-1d041508c419")]

// Version information for an assembly consists of the following four values:
//      Major Version
//      Minor Version
//      Build Number
//      Revision
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("")]
[assembly: AssemblyFileVersion("")]

[assembly: PreApplicationStartMethod(typeof(WebActivator.PreApplicationStartCode), "Start")]

and in the bottom of that file I finally started to believe that magic little elves live within the jit-compiler and make everything fine and dandy even though it seems like the feature is using itself to start itself.

Maybe there really *are* turtles all the way down?

Have you spotted how it works yet?

I’ll give you some whitespace to think of the solution before I blurt it out and ruin your fun
































WebActivator.PreApplicationStartMethod != System.Web.PreApplicationStartMethod

WebActivator doesn’t use itself but rather a feature of the Asp.Net 4.0 runtime to start itself off. The whole motivation for WebActivator is that the feature built into Asp.Net 4.0 has a design flaw in that it only allows a single instance of the attribute within each dll, making it hard to use for NuGet scenarios where you would want to use multiple different packages each with their own startup logic independent of eachother, you would have to manually merge the logic into one place if you wanted to use the Asp.Net 4.0 implementation.

However, since WebActivator.dll is a tiny separate dll it can make use of the feature to kickstart the other dlls.

I think that I would prefer if the AssemblyInfo.cs file was a little more specific in its usage of the feature, seeing that it’s basically reimplementing it itself. Instead of relying on a using statement for System.Web, I would prefer if the attribute was declared as

[assembly: System.Web.PreApplicationStartMethod(typeof(WebActivator.PreApplicationStartCode), "Start")]


October 12, 2010

How to make your world better one browser extension at a time

Filed under: Uncategorized — andreakn @ 21:22

As a consultant I have been exposed to a lot of different timesheet applications. Everything from a simple excel sheet to fairly advanced web sites into which you enter the amount of time spent on various (hopefully) project-related activities. The fidelity with which the hours are to be registered also varies wildly. Generally being a bit of a slob I prefer when the input process is simple and the fidelity is almost nonexisten. This frees me up to deliver quality work instead of painstakingly documenting *what* work I did *when* and for *how long*.

In my simplistic world-view the hourly report forms you have to fill out are a necessary evil, but they’re still evil 🙂

So, when I got subcontracted through a firm with this input process: (snip: )
First I threw a fit and then I started to think: “is there any way for me to automate this”.

as it turns out, yes there is. I won’t bore you with all the juicy details on how to create a chrome plugin that modifies a webpage on your behalf,
but in essence all you do is to have a manifest file declare on which urls this plugin should kick in, and what javascript files it should inject at the end of the loading process for that page.

For me in this case the file (which must be called manifest.json by the way) looks like this:

  "name" : "ElanIt Timereg decrapifier",
  "version" : "0.1",
  "description" : "Makes timereg useable",

  "content_scripts": [
      "matches": ["*"],
      "js": ["jquery.js", "timeregmod.js"]

which as you can see loads a bundled version of a minified jquery 1.4.2 and also a custom js file which contains the logic I needed to inject onto the page.
here is the timeregmod.js file:

$.fn.getNonColSpanIndex = function () {
    if (!$(this).is('td') && !$(this).is('th'))
        return -1;

    var allCells = this.parent('tr').children();
    var normalIndex = allCells.index(this);
    var nonColSpanIndex = 0;

        function (i, item) {
            if (i == normalIndex)
                return false;

            var colspan = $(this).attr('colspan');
            colspan = colspan ? parseInt(colspan) : 1;
            nonColSpanIndex += colspan;

    return nonColSpanIndex;

$(document).ready(function () {
    findAllWorkDays($('.ITMTimesheetDetail')).not(':last').each(function () {
        var tr = $(this);
        tr.append($('<td></td>').append($('<input type="button"></input>)').val('>>').css('font-size','9px').click(function () {
            var input = $(this);
            var thisRow = input.parents('tr').first();

            var nextRow = findNextWorkDay(thisRow);

            $('input[type=text]', thisRow).each(function () {
                var textbox = $(this);
                var index = textbox.parent().getNonColSpanIndex();

                $('td:eq(' + index + ') input', nextRow).val(textbox.val());
    $('.ITMTimesheetDetail tr:eq(2)').each(function () {
        var tr = $(this);
        tr.append($('<td></td>').append($('<input type="button"></input>)').val('Kopier til alle').css('font-size', '9px').click(function () {
            $('input[type=text]', tr).each(function () {
                var textbox = $(this);
                var value = textbox.val();
                var index = textbox.parent().getNonColSpanIndex();
                var workdays = findAllWorkDays('.ITMTimesheetDetail');
                $('td:eq(' + index + ') input', workdays).val(value);

function findAllWorkDays(table) {
    return $('tr:gt(1)', table).filter(function (index) {
        var tr = $(this);
        return isWorkDayRow(tr);

function isWorkDayRow(tr){
    if(tr.children().first().hasClass('ITMTimesheetDetailDayOfMonthHoliday') || tr.children().first().hasClass('ITMTimesheetDetailDayOfMonthSaturday')){
        return false;
    return true;

function findNextWorkDay(currentRow) {
   var nextRow =;

   while (!isWorkDayRow(nextRow)) {
       nextRow =;
   return nextRow;

I won’t pretend that I’m overly happy with the javascript, it should be shorter and better, but it gets the job done, so I’m stopping working on it.

The timesheet application (which is called Manpower Timereg apparently) now looks like this for me (when browsing using google chrome with the plugin loaded):

(yes, the buttons on the right hand side was added by this plugin).

SO: if you have to deal with this (dare I say somewhat challenging) system and would like to improve your own experience, feel free to take the sourcecode in this blogpost and create your own chrome plugin. You probably want to change the url in manifest.json (unless you happen to be subcontracting for ElanIt in norway)

creating a chrome plugin based on this is as simple as:
– create a new folder and call it MySpecialPlugin or something like that
– download the latest version of jquery into that folder and rename the file to jquery.js
– put the two files as described in this blogpost into the folder.
– in chrome: navigate to the url chrome://extensions and click the “load unpacked extension”
– select the folder you created a few bullets ago

and voila: Bob’s your uncle

« Previous PageNext Page »

Blog at