Showing posts with label Web. Show all posts
Showing posts with label Web. Show all posts

Monday, May 28, 2018

One month later and TSB online banking is still a disaster

One month ago TSB tried to migrate their customers to a new online banking platform. Things didn’t go particularly well and although the media have lost interest in the problems, as a TSB customer and IT geek I’m still interested in what’s happening and thought I’d touch on a few of the issues.

The most glaring issue is that my mortgage account still doesn’t appear in my list of accounts. I assume I still have a mortgage since the payments are still being taken from my current account but there has been no sighting of it since the ‘upgrade’.

A few days ago, I tried to login, only to see an error message, <div>Sorry, we are currently experiencing technical issues. Please try again later.</div>. Not being able to login isn’t great but not totally unexpected given the recent troubles. Displaying HTML tags in your error messages is a fairly minor issue but suggests a lack of attention to detail that can lead to more serious issues down the road.

Now lets go under the hood by firing up Dev Tools in Chrome. The first error I saw was complaining about Symantec certificates which Chrome will stop supporting in a future release. Let’s hope someone at TSB renews that fairly soon before things break properly.

But the next set of errors are slightly more concerning, WebSocket connection to 'wss://127.0.0.1:3389/' failed, something on the web page is trying to connect to a server on my local machine! I can only assume this is some kind of debugging code but should definitely not be getting pushed out to their production system.

Next we have a failure to load a locale file for US English, which is odd since I’m set up for UK English. Again, nothing serious but shows a lack of attention to detail.

Finally, let’s run the page through Lighthouse, the Chrome web page checker. The performance score is a mighty 18 out of 100. This seems mostly due to Javascript and CSS that isn’t bundled together and isn’t being loaded using async or defer, meaning everything has to load before the page can be displayed.

And that’s what I found spending a few minutes on the TSB website, who knows what could be found if I was an evil hacker wanting to steal some money

Thursday, October 02, 2014

Unable to get property 'setState' of undefined or null reference in ckeditor.js

I’ve been working on the latest and greatest Business Optix product which dynamically creates lots and lots of HTML controls, some of which are CKEditor controls. Whilst testing the app and quickly navigating around it, I started getting an error “Unable to get property 'setState' of undefined or null reference” coming from deep in the bowels of CKEditor. Google astonishingly turned up nothing, but I remembered I’ve had trouble in the past with CKEditor when I didn’t explicitly destroy the editors. The fix was simple, like this

for (name in CKEDITOR.instances) {
  CKEDITOR.instances[name].destroy();
}

Monday, May 19, 2014

Downloading Javascript generated data

I have a number of web pages that generate some data in text areas using Javascript. The only way users could download this data was to copy and paste the contents of these text areas, but I wanted to add a download button to simplify the process. The problem is that this simply isn’t possible in Javascript. The only client-side solutions I’ve seen either require Flash or are not supported in all browsers.

So I came up with a slightly hacky and inefficient solution. The basic idea is to post the data to the server and get the server to return it to the client as a download. The HTML looks like this

      <form action="download.php" method="post">
        <div>
          <input type="hidden" name="fileName" value="locations.csv" />
          <input type="submit" value="Download" />
        </div>
        <textarea id="geocodedPostcodes" style="width:100%;" rows="20" name="data"></textarea>
      </form>

All that is needed is a hidden field that tells the server-side script what the download file name should be and a text area with a name of “data”.

The server-side script is pretty simple, it looks like this

<?php
  header('Content-Disposition: attachment; filename="' . $_POST["fileName"] . '"');

  // add data
  print($_POST["data"]);
?>

All it does is get the requested file name and echo back the data.

It’s seems a bit crazy (and a waste of bandwidth) that this seems to be the only way to achieve a seemingly simple task, but that looks to be the case. I’d be happy to be proved wrong.

Thursday, March 27, 2014

Bluffers Guide to responsive design

A while back I spent a bit of time making doogal.co.uk more mobile friendly. I’d put it off for a long time primarily because I thought it would be really hard. But actually it turned out to be not too tricky. So here is my not so comprehensive guide to making your site mobile friendly with responsive design

The first thing to do is decide at what screen size your design will change from the normal design to the mobile design. My decision was that tablets should see the standard design but mobile phones should see the mobile design. So the media query I use for all my mobile CSS is

@media handheld, only screen and (max-width: 600px), only screen and (max-device-width: 600px) 

Better menus - My standard menus were tricky to use on a small screen, but fortunately they were built using styled unordered lists i.e. <ul></ul>, so I was able to use the Mobilemenu JavaScript library. This converts the list into a dropdown <select> which is much more useable on mobile devices.

Hide stuff – display:none is your friend. Most websites have bits on the page that may be useful but aren’t entirely essential. On a big screen we can get away with that, but on mobile devices it’s necessary to concentrate on the essential information. So hide anything that isn’t needed. I have several tables with many columns, but each row links through to more information, so I hid several of the columns since the tables didn’t render very well. The best approach to this appears to be CSS like the following

.postcodeTable  td + td { display: none;} 

No more table layouts – We’ve been told for years not to use tables for layout of our websites. But I’ve certainly been guilty of it, since it’s always easier to build a multi-column layout using a table. But now is when the chickens come home to roost. You may want two columns on the desktop, but on a mobile device, you’ll probably want to have the two columns stacked on top of each other, so those tables will need to be converted to div’s which float on the desktop and don’t on mobile devices.

Server side control – The solutions so far have all been client-side. I think this is generally the easiest way to deal with the issue, but there is certainly a good argument for saying content shouldn’t be getting pushed to the client if the client will never actually display it. If you’re using PHP on the back-end, Mobile Detect Library can be used to tailor your HTML before it leaves the server. One place where you may need to do this is adverts. Google’s T&Cs say you can’t hide adverts, so using display:none for them is probably a bad idea.

Saturday, June 15, 2013

Website ideas

I often have ideas for websites, but due to a lack of time I never actually get round to doing anything about them. They may well be terrible ideas, or may already exist in some form already, and almost all of them are guaranteed to not make any money, but I present them here in case anyone wants to pick up the baton and run with them.

Random acts of kindness

Say you have some spare cash and you want to give it to a good cause, but you don’t have a good idea about who to give it to. Wouldn’t it be great if there was a website where you could post a message saying ‘I have some money, who wants it?’ and from the other side people could post messages asking for money, help or whatever? When you find someone you want to help, you can contact each other and arrange to hand over the cash (although there needs to be some kind of protection here against receiving shedloads of begging emails).

Domain reminder

So you’re interested in buying that doogal.com domain (oh, that’s just me is it?) but somebody has bought it already and is sitting on it, only being prepared to sell it for a ludicrous 2800 euros? If you’re willing to wait, wouldn’t it be great if there was a website that would send you reminders when the domain name was about to expire? And whenever the status of the domain changed in any other way?

Foreign exchange market

The difference between the official exchange rates and the price you’ll pay to exchange money yourself can be huge. But whenever I convert 100 pounds to euros, there’ll be thousands of other people who want to take their euros and convert them into pounds, so how about a website to match the buyers and sellers? I’ve not really thought through how the exchange takes place, but that’s just a minor detail…

Re-open nominations

This is more than just a website, this is a political movement. Back in the day when I was at university, any election would include RON, re-open nominations. So if you weren’t happy with any of the candidates, you could vote for RON and if enough people voted for him then the election would be rerun. Currently I’m pretty disappointed with all three of our major parties, since there seems to be very little difference between them in terms of policy and they all seem to be led by people who have no experience of the real world. But there is no way to register the fact that I still believe in the democratic process but don’t want to vote for any of the options available to me. So for my take on this idea in real elections, the RON Party would stand in every election, promising to immediately stand down if elected. Along with that, it would lobby the government to include RON as an option in all elections.

Fat cyclists

I enjoy cycling, but have no desire to wear lycra. There are lots of clubs for cyclists, but they are generally full of extremely fit people who cycle 50 miles a day. So how about a web community for people who like to cycle but aren’t necessarily as fit as they could be and don’t take it too seriously so they can get together and have a slow ride together?

Friday, November 23, 2012

Endomondo discovers how to get my money

When I logged into Endomondo the other day I discovered that in the process of redesigning their website, they had also decided to hide some of the useful stats from us users who weren’t paying them any money.

I could have started shouting and screaming at this point, since they’d had the temerity to take away my free toys. But frankly $20 a year for a very useful service is a price worth paying if it helps them to keep the lights on.

Of course this approach only works for sites that offer a valuable service, it probably wouldn’t work for quite a few sites (I’m looking at you Facebook!)

Tuesday, July 17, 2012

Lets build a socialist network

It was five years ago. We’d driven down to Spain to see my dad and as we sat in his living room I was amazed to see my teenage nephew and his mate typing ferociously on their laptops as they chatted away on the social network du jour (I’m guessing Facebook but I might be wrong). I remember thinking at the time what would happen if we could harness all that energy into doing something productive.

What I hadn’t realised is that Facebook had already figured out how to harness that energy. Most websites (ignoring the ones that sell stuff) draw viewers in by producing interesting content and make their money through advertising on the site. Facebook works in the same way, but it doesn’t produce its own content, it gets its users to do that. In fact it’s a brilliant system since the content that is produced helps to decide what adverts should get shown, but I can’t help feeling there’s a better way. If I write some content, I want to have the chance to earn some income from it. I know from my experiences here that the chances of me making a reasonable amount of cash are pretty low, but I still think the system where the advertising network takes a cut and the content producer takes a cut is a fairer approach.

I think there are two approaches possible. The first is to build a competitor to Facebook, where the users take a cut of profits. I think this could have a few problems, how do you decide who gets what?, how do you stop fake users signing up just for a cut?, how do you stop click through fraud to increase earnings?

So here’s another option, we develop open protocols that allow different sites to hook up together and provide similar functionality to Facebook (and Twitter and Google+ and LinkedIn). There’s not a lot to it as far as I can see, the basic actions are add a friend, follow someone (that’s pretty much RSS), like something, send a message to someone and publish a message (that’s essentially a blog post). None of it is rocket science. And then we all have a choice. A geek like me could mash all my social networking stuff together on my own site and have the chance to earn some money. Ordinary users could continue to use the services they want, new services would crop up that aggregated all your social networks together. Everyone wins…

Thursday, May 24, 2012

I don’t rate Rated People (or maybe I do)

You’ve probably seen the adverts where Phil Spencer, the man whose remarkable negotiating skills can sometimes get £5,000 knocked off a £500,000 house purchase, tells us about this remarkable website which can connect us punters with local tradesmen. To me this seems like a good idea for a website and I have some work that needs doing, so thought I’d give it a spin.

So I filled in my details, entered the job details and waited for a response. And then, nothing. OK, not entirely true, I received a couple of text messages and emails. A couple of days passed and I received an email telling me that I hadn’t had a response and maybe I should expand on my job description to get a response. So off I went and updated the description and hit submit and then I was presented with a 404 error (for the non technical, this means the website is broken). So I tried again and got the same result (definition of insanity - doing the same thing over and over again and expecting different results). And I gave up at that point.

OK, slightly annoying, but no worries, I’ll drop them an email and let them know and I’ll update the description later when it’s fixed. So I go to their contact page. I can ring them, but not at this time or I can send them a tweet or write on their Facebook page or add a comment to their blog. No feedback form, no email address. I didn’t particularly want to berate them on a public forum, maybe the problem is something specific to me, so none of those options appealed. Yes, yes, I see the irony, I am now berating them quite publically (hello my many reader), but I’m annoyed now.

So, so far, Rated People is something of a failure for me. I think if you’re going to be spending a bunch of money on TV advertising, quite a bit of effort should also be spent on the product…

Update – A funny thing happened today, I got a call from Rated People, specifically due to this blog post. I’ve ranted about a few companies on here and never got any response from the company involved, so it was a pleasant surprise to get some feedback. I should also say that I did eventually manage to update my job description and also got a response from an interested builder, who is repairing my pointing as I type. So it would appear my initial review of Rated People may have been overly negative. And what’s more, Phil Spencer is no longer doing the voice over on their ads…

Saturday, April 14, 2012

How to increase your AdSense revenue

My proper website has been around for about 12 years. For most of that time it had very few visitors and the money I made from advertising was minimal. Then a couple of years ago advertising revenue started going up. It’s now plateaued but I earn a nice wedge of cash ever month, not enough to give up the day job but enough to add a decent amount to my income. So what’s my secret and how can you do the same thing?

Firstly, you can optimise the placement, colours and number of AdSense units. Now this may well improve your revenue but the problem is it is almost impossible to figure out if revenue increased due to changes to your ads or just due to random fluctuations. Take a look at my daily income over the past month.

image

There’s a lot of volatility in those figures. Admittedly you can slice the data in different ways which can produce better results but even with no changes to my ad setup, the daily numbers are all pretty volatile. And if you aren’t making a lot of cash from your ads, your figures are likely even more volatile. So I figure if you want to optimise you ad setup, then you need to be looking at weekly or monthly numbers, so the feedback loop on changes is going to be pretty slow. To be frank the limit of my optimisation of ads is to increase the number of ad units to three (the maximum allowed and also probably the maximum number that wouldn’t be too annoying to a user of you site).

So what did cause the increase in my income? Simple really, an increasing number of visitors. My graph of monthly visitors and my graph of monthly AdSense revenue are almost identical. So unfortunately the answer to the question of how to increase AdSense revenue is another question, how to increase the number of visitors to your site.

And the answer to that question is actually pretty straightforward I reckon. The first part is fully within your control - Produce a lot of content of a reasonably quality. i.e Content that search engines will consider to be of value and hence index. The second part isn’t completely within your control, gain some inbound links from reputable sites. I actually think this will naturally follow from producing content that people appreciate.

In some ways I can’t help thinking this is the business model that Facebook has adopted, get a shedload of content (although in their case its helpfully created by their own users) and shove ads on it.

Saturday, April 07, 2012

Creepy ads

I’m used to visiting a website and their adverts then following me round the web afterwards. It’s happened with Dell and some property website. They even seem to make an effort to show me relevant stuff (laptops I was looking at or properties in the area I looked at). Then after a few days they disappear. I find it a bit creepy and it’s a bit of an eye opener to realise what the ad companies know about me (or about the cookie that’s sat on my machine).

But there’s an advert that’s been following me around for months now, for LeanKit Enterprise Kanban. I’m pretty sure this ad can’t be getting shown to everybody on the web, since this is a pretty niche product, so I can only assume it’s appearing because I visited their site ages ago. This has gone beyond creepy, this is just plain weird. Is showing me the same ad for months on end actually effective? Are the advertisers paying a premium for it?

Friday, April 06, 2012

Cross browser selectSingleNode for XML in Javascript

Internet Explorer has a non-standard method available in XML documents returned from AJAX calls called selectSingleNode. Pass in some XPath and it returns the first node that matches the XPath. Other browsers have ways of doing the same thing, but they are more long winded. So in short, I like the IE implementation and since I don’t fiddle with XML in JavaScript that often I often forget that selectSingleNode is not supported on all browsers (and if you’re wondering why I use IE as my primary browser, it’s because it’s still, just about, the most popular browser out there).

So here’s a cross browser version of selectSingleNode (not my own work, copy and pasted from somewhere I can’t remember on the web)

    function SelectSingleNode(xmlDoc, elementPath) {
      if (xmlDoc.evaluate) {
        var nodes = xmlDoc.evaluate(elementPath, xmlDoc, null, XPathResult.ANY_TYPE, null);
        var results = nodes.iterateNext();
        return results;
      }
      else
        return xmlDoc.selectSingleNode(elementPath); 
    }

Update – This no longer works in IE10, since selectSingleNode has been removed from the XML document returned from AJAX calls. This can be worked around by setting the response type of the XmlHttpRequest, like so

xhr.responseType =  'msxml-document';

More info

Update – There’s a more fully featured version of this now available

Saturday, January 21, 2012

Do it yourself inbound link alerts

Embedded Analytics provide a nice service that will email you whenever somebody clicks on a new link to your site. I’ve been signed up for a while and it’s interesting to see who’s linked to my site. But I received an email last week informing me that my site had so many inbound links that I would have to start paying for the service. To be fair the amount they were going to charge me wasn’t a lot, but I couldn’t really justify spending money on something that is essentially just a way to waste a bit of time for me. And I also figured I could probably do the same thing myself through the Google Analytics API, since this is what Embedded Analytics uses.

I’m assuming that Embedded Analytics uses the source for visitors to your site to spot new links. There is a downside to this since it won’t spot links that have been added but have not been clicked on, but generally these won’t be that interesting, since they presumably are links on low traffic sites.

So to implement this requires a few steps. Pull out the data from Google Analytics and store this data somewhere (DB, XML file, whatever). Then next time we pull the data out of Google, check for new URLs in the returned data and send a notification of these new URLs. Embedded Analytics also goes a step further and validates that the links are valid and that the pages containing them are available from the web. I was only really interested in the first part of this solution so have written a piece of code to pull out the URLs using the Google Data API for .NET. The rest of the work is left as an exercise for the reader!

using System;
using Google.GData.Analytics;

namespace GoogleAnalytics
{
  class Program
  {
    static void Main(string[] args)
    {
      AccountQuery feedQuery = new AccountQuery();
      AnalyticsService service = new AnalyticsService("DoogalAnalytics");
      service.setUserCredentials("email", "password");

      DataQuery pageViewQuery = new DataQuery("https://www.google.com/analytics/feeds/data");
      pageViewQuery.Ids = "ga:202885";
      pageViewQuery.Metrics = "ga:visits";
      pageViewQuery.Dimensions = "ga:source,ga:referralPath";
      pageViewQuery.Sort = "ga:source,ga:referralPath";
      pageViewQuery.GAStartDate = DateTime.Now.AddMonths(-1).ToString("yyyy-MM-dd");
      pageViewQuery.GAEndDate = DateTime.Now.ToString("yyyy-MM-dd");

      DataFeed feed = service.Query(pageViewQuery);
      for (int i = 0; i < feed.Entries.Count; i++)
      {
        DataEntry pvEntry = (DataEntry)feed.Entries[i];
        string host = pvEntry.Dimensions[0].Value;
        string path = pvEntry.Dimensions[1].Value;
        Console.WriteLine("http://" + host + path);
      }

      Console.ReadLine();
    }
  }
}

Friday, January 06, 2012

Another take on peer to peer lending

It seems a little strange to me that at a time when banks are meant to be desperately trying to increase their balance sheets, they are offering such meagre interest rates. I dunnow but maybe offer some decent rates and people will shove their money in your bank? But it seems they have decided it’s better to offer crap rates and hope we are too stupid to realise we can get better returns elsewhere.

So for a few years I’ve been stashing cash in Zopa and getting a return that manages to beat inflation by lending money directly to people, with the caveat that there is a higher level of risk than having money in the bank.

But I’m obviously not the only person to choose this option and rates have been falling of late. And although I’m happy to finance personal loans, i did have a hankering to lend money to businesses, especially since this seems to be something else banks are failing to do properly*

Which is where Funding Circle comes in. This is peer to peer lending but the other party is a business. Interest rates are currently some what better than Zopa. Time will tell how bad the default rates are but I’m going to drip feed some money in there and see how it pans out.

*I always thought banking was pretty straightforward, take in money from deposits, then lend it out to other people and skim a bit of profit from the transaction. Simple and a bit boring. Maybe they should have stuck to this slightly dull job, rather than inventing, buying and selling insane derivatives that nobody understands and nobody can quantify the associated risk. Get back to doing what you’re meant to do and people might get off your back a bit…

Friday, December 30, 2011

Why all the free ads for Facebook?

A while back I picked up a copy of the Evening Standard and was surprised to see Facebook and Twitter logos at the top of every page, suggesting readers follow ES on these two websites. And this is just an extreme example of what I’m seeing more and more. Ads on the telly and in newspapers no longer show the URL of the company’s website, but the URL of their Facebook page instead.

But the thing that confuses me is why big companies would choose Facebook as their main point of contact with their customers? Sure, social networks are the big thing at the moment and getting users to follow you or like your product might have some benefit, but there seem to be a number of downsides.

First, who owns all the data being collected about your customers? My guess is Facebook. Can you extract that data if Facebook decide they don’t want you anymore or you decide to move? I guess it’s probably possible via the Facebook API but it seems somewhat risky. And even if you do extract it, Facebook will no doubt keep hold of it as well.

Then there’s the question of ads. The company pages I’ve seen on Facebook seem to have the same ads as any other page. I’ve found no indication that companies get any of the income from those ads, so why drive traffic to Facebook so they can make money from your brand? And what if Facebook decide to show ads for one of your competitors?

Frankly it all seems a bit odd. Big companies have big IT departments and generally have their own websites, fully under their control. It is pretty simple to add some Facebook widgets to your own site and get integration that way which seems a more sane approach if you want to get hooked into Facebook.

For a one man band kind of company, I can see the sense in putting you web presence on Facebook, it’s a lot simpler and cheaper than building your own website, but for multi-nationals my prediction for 2012 is that this is something they do much less of.

Sunday, May 30, 2010

Free XML sitemap generator with unlimited pages

I’ve been happily using this free XML sitemap generator for a while, but as my sites have got bigger I keep hitting the 500 page limit. Since I’m too tight to actually pay for this kind of service I’ve been searching around for a while for another free alternative. I was even thinking of writing my own, but before I was forced down that road, I found this alternative generator. I’m not too keen on Java applets generally, but this one seems to work very well. If it fails to download any pages, you can go back and retry downloading the failed pages. It also gives details of how long pages take to download, so it can also be a useful performance checker of a website.

Even so, I’m a little ambivalent towards sitemaps, especially if they are generated using one of these tools that just crawl your website to find all the pages. What information does this provide to search engines that they can’t find for themselves by crawling the site themselves? And If I do want to provide any more useful information, I have to edit the thing by hand, which I really can’t be bothered doing.

Friday, April 23, 2010

JavaScript not executing in Internet Explorer 8

I’ve been caught out by this on numerous occasions and still haven’t learnt my lesson. So in an attempt to remember in the future and perhaps help somebody else out, here’s the situation. Take the following script declaration

<script type="text/javascript" src="Scripts/jquery-1.4.1.min.js" />

This may look valid and probably should be, but Internet Explorer thinks the script tag hasn’t been closed, which means any script blocks after it will be ignored. The declaration needs to be

<script type="text/javascript" src="Scripts/jquery-1.4.1.min.js"></script>

Saturday, March 27, 2010

jQuery zoom addin with image map support

In the past I fiddled around with the moozoom plugin to add support for image maps and on a new project I needed the same thing. But this new project uses a lot of jQuery stuff and jQuery and mootools don’t play together particularly well. Although there are a lot of image zooming plugins available for jQuery, none of them did exactly what I needed. I just wanted image zooming, panning and support for image maps. Anyway, this is what I came up with. This is my first adventure in the world of jQuery plugins, so it may not be a perfect implementation but it works for me…

Usage is pretty simple

$('.selector').zoomable();

where .selector is the selector for your image. View an example here (that page will also be the place I put all new versions of this code). You can now also programmatically zoom in and out using $('.selector').zoomable('zoomIn'); and $('.selector').zoomable('zoomOut'); which you can hook up to buttons for users without a mouse wheel

Here’s the code (requires jQuery and jQuery UI)

(function ($) {
  $.fn.zoomable = function (method) {
 
    return this.each(function (index, value) {
      // restore data, if there is any for this element
      var zoomData;
      if ($(this).data('zoomData') == null) {
        zoomData = {
          busy: false,
          x_fact: 1.2,
          currentZoom: 1,
          originalMap: null,
          currentX: 0,
          currentY: 0
        };
        $(this).data('zoomData', zoomData);
      }
      else
        zoomData = $(this).data('zoomData');
      
      var init = function() {
        if (value.useMap != "") {
          var tempOriginalMap = document.getElementById(value.useMap.substring(1));
          zoomData.originalMap = tempOriginalMap.cloneNode(true);
          // for IE6, we need to manually copy the areas' coords
          for (var i = 0; i < zoomData.originalMap.areas.length; i++)
            zoomData.originalMap.areas[i].coords = tempOriginalMap.areas[i].coords;
        }

        $(value).css('position', 'relative').css('left', '0').css('top', 0).css('margin', '0');

        $(value).draggable();

        // jquery mousewheel not working in FireFox for some reason
        if ($.browser.mozilla) {
          value.addEventListener('DOMMouseScroll', function (e) {
            e.preventDefault();
            zoomMouse(-e.detail);
          }, false);
          if (value.useMap != "") {
            $(value.useMap)[0].addEventListener('DOMMouseScroll', function (e) {
              e.preventDefault();
              zoomMouse(-e.detail);
            }, false);
          }
        }
        else {
          $(value).bind('mousewheel', function (e) {
            e.preventDefault();
            zoomMouse(e.wheelDelta);
          });
          if (value.useMap != "") {
            $(value.useMap).bind('mousewheel', function (e) {
              e.preventDefault();
              zoomMouse(e.wheelDelta);
            });
          }
        }

        $(value).bind('mousemove', function (e) {
          zoomData.currentX = e.pageX;
          zoomData.currentY = e.pageY;
        });
      };

      var left = function() {
        return parseInt($(value).css('left'));
      };
      
      var top = function() {
        return parseInt($(value).css('top'));
      }
      
      var zoomIn = function() {
        // zoom as if mouse is in centre of image
        var parent = $(value).parent()[0];
        zoom(zoomData.x_fact, left()+parent.offsetLeft+(value.width/2), top()+parent.offsetTop+(value.height/2));
      };
      
      var zoomOut = function() {
        // zoom as if mouse is in centre of image
        var yi = parseInt($(value).css('top'));
        var parent = $(value).parent()[0];
        zoom(1 / zoomData.x_fact, left()+parent.offsetLeft+(value.width/2), top()+parent.offsetTop+(value.height/2));
      };
      
      var zoomMouse = function (delta) {

        // zoom out ---------------
        if (delta < 0) {
          zoom(1 / zoomData.x_fact, zoomData.currentX, zoomData.currentY);
        }

        // zoom in -----------
        else if (delta > 0) {
          zoom(zoomData.x_fact, zoomData.currentX, zoomData.currentY);
        }
      };

      var zoomMap = function () {
        // resize image map
        var map = document.getElementById(value.useMap.substring(1));
        if (map != null) {
          for (var i = 0; i < map.areas.length; i++) {
            var area = map.areas[i];
            var originalArea = zoomData.originalMap.areas[i];
            var coords = originalArea.coords.split(',');
            for (var j = 0; j < coords.length; j++) {
              coords[j] = Math.round(coords[j] * zoomData.currentZoom);
            }
            var coordsString = "";
            for (var k = 0; k < coords.length; k++) {
              if (k > 0)
                coordsString += ",";
              coordsString += coords[k];
            }
            area.coords = coordsString;
          }
        }
      };

      var zoom = function (fact, mouseX, mouseY) {
        if (!zoomData.busy) {
          zoomData.busy = true;

          var xi = left();
          var yi = top();

          var new_h = (value.height * fact);
          var new_w = (value.width * fact);
          zoomData.currentZoom = zoomData.currentZoom * fact;

          // calculate new X and y based on mouse position
          var parent = $(value).parent()[0];
          mouseX = mouseX - parent.offsetLeft
          var newImageX = (mouseX - xi) * fact;
          xi = mouseX - newImageX;

          mouseY = mouseY - parent.offsetTop
          var newImageY = (mouseY - yi) * fact;
          yi = mouseY - newImageY;

          $(value).animate({
            left: xi,
            top: yi,
            height: new_h,
            width: new_w
          }, 100, function () {
            zoomData.busy = false;
          });

          zoomMap();
        }
      };
      
      if (method == "zoomIn")
        zoomIn();
      else if (method == "zoomOut")
        zoomOut();
      else
        init();
    });
  };
})(jQuery);

Tuesday, March 23, 2010

jqGrid hints and tips

I’ve been spending a lot of time with jqGrid recently. It’s a marvellous piece of work but the documentation doesn’t always match up to the quality of the code. I’m not complaining, I realise it’s free, it’s flipping great and as a developer myself I know documentation is always the last thing I want to tackle. But here are a few things I’ve discovered along the way where it wasn’t immediately apparent what was causing the problem.

Multiple rows being selected – This can be caused by a couple of things. The first thing to check is that the row IDs being used are sensible. My problem was caused by having some rows with the same ID. Another time the problem cropped up when I used email addresses as my row IDs. It seems the grid has problems with certain characters being used in IDs. The @ symbol did it for me but there are others, like spaces, that can cause problems.

Arbitrary XML – The docs say that jqGrid can accept any arbitrary XML document, so long as you provide a mapping to describe how to map it to the standard XML format. This is almost true, but falls over if your XML document contains attributes that you want to pull into the grid. Attributes aren’t supported at all. I managed to get round this in a couple of steps. First I had to hack jqGrid so the loadComplete event was triggered before the grid was actually populated, rather than after. Then I could fiddle with my returned XML before jqGrid tried to read it. Then I wrote some code to convert attributes to elements with he same name (this could probably be improved with some jQuery magic but it did the job for me) and called it for the relevant attributes.

function setNodeText(newElem, text) {
  if (text != null) {
    try {
      newElem.textContent = text;
    }
    catch (e) {
      newElem.text = text;
    }
  }
}

function convertAttributeToElement(xml, node, attributeName) {
  var name = node.getAttribute(attributeName);
  addChildNode(xml, node, attributeName, name);
}

function addChildNode(xml, parent, nodeName, value) {
  var newElem = xml.createElement(nodeName);
  setNodeText(newElem, value);
  parent.appendChild(newElem);
}

Word wrap – I wish this was on by default, since it is now the first thing I do when I’m setting up a grid for the first time. Just modify the ui.jqgrid.css file so the definition for .ui-jqgrid tr.jqgrow td includes the following white-space: normal;

Sunday, October 11, 2009

Zopa – 18 months on

About 18 months ago, I started to siphon some of my savings into Zopa in an attempt to get a slightly better return on my money. So I thought now would be a good time to look back on how well it has gone. First up, it certainly provides better returns than having your money in a bank account, with interest rates as low as they are. On the downside, this increased return does mean increased risk, the old risk premium again. Those loans can sometimes turn bad and when they do, all the outstanding debt is written off. Well I guess some of it may be returned, but I’ve not had any bad debt returned yet.

But the main downside is that the money is tied up for a long time, and by my calculations the return from part paying off my mortgage is still better than the returns on Zopa. And although doing that means that the money is gone forever, in the future I’ll probably be more likely to do that than put more money into Zopa.

Thursday, June 04, 2009

IIS Search Engine Optimization Toolkit

I love Google Webmaster Tools since it tells me things I’m doing wrong on the websites I maintain (in fact I love any tools that tell me what I’m doing wrong, FxCop, the HTML editor in Visual Studio, compilers… I’m sure a psychiatrist would draw some scary conclusions from this admission). But the problem with the Google webmaster Tools is that they aren’t very responsive. If I fix an issue, I have to wait for the Google bot to crawl that page again before I know it’s been fixed.

So I was pleased to be directed to the Search Engine Optimization Toolkit by ScottGu’s post. It’s only in beta but it’s already a powerful tool. It will tell you about lots of potential problems on your site, such as missing alt tags on images, missing description meta tags, broken links, no h1 tag etc… I’ve spent the day trying it out with the Process Mapping site and have cleaned up a lot of potential problems, that no other tools had told me about. Only time will tell if this improves our ranking a lot, but I’m guessing even in the worst case, it won’t cause our ranking to drop, since all the suggestions seem perfectly sensible.

It’s simple to install, requires nothing special on the website to get it working and provides instant feedback, so I’m sold on it. The only possible downside is that it will only install on IIS 7 I guess (although that doesn’t mean your website needs to be running IIS 7, just the machine running the toolkit).