LavaBlast Software Blog

Help your franchise business get to the next level.
AddThis Feed Button

BlogEngine.NET 1.6 Breaking Old Links

clock February 12, 2010 10:17 by author EtienneT

UPDATE: BlogEngine 1.6.0.1 will fix this problem

Download BlogEngine 1.6 Binary Patch (drop in your bin folder)

We recently installed an open source error logging project called Elmah on our BlogEngine.NET because we noticed that comments were broken on our blog and some people informed us of other errors.

After installing Elmah, we found out that we had a lot of exceptions like this:

The file '/post/2008/01/Attach-to-Process-with-one-shortcut.aspx' does not exist.

After some investigation, we found out that a lot of the links leading to our blog were broken!  This is bad news because we care about our Google rankings!

I investigated the BlogEngine.NET code a little bit because we considered this a major problem and found out that BlogEngine.NET changed the link pattern from:

http://blog.lavablast.com/post/2008/02/I2c-for-one2c-welcome-our-new-revision-control-overlords!.aspx  (the old pattern)

to

http://blog.lavablast.com/post/2008/02/11/I2c-for-one2c-welcome-our-new-revision-control-overlords!.aspx  (the new pattern)

The day is now required in the link. Otherwise, external links are redirected to an error page.

After investigating the code, I found out that the method RewritePost in the class BlogEngine.Core.Web.HttpModules.UrlRewrite was changed in svn revision 29143 from:

Post post = Post.Posts.Find(delegate(Post p)
{
    if (date != DateTime.MinValue && (p.DateCreated.Year != date.Year || p.DateCreated.Month != date.Month))
    {
        if (p.DateCreated.Day != 1 && p.DateCreated.Day != date.Day)
            return false;
    }
 
    return slug.Equals(Utils.RemoveIllegalCharacters(p.Slug), StringComparison.OrdinalIgnoreCase);
});
 

to

Post post = Post.Posts.Find(delegate(Post p)
{
    if (date != DateTime.MinValue &&
        (p.DateCreated.Year != date.Year || p.DateCreated.Month != date.Month || p.DateCreated.Day != date.Day))
    {
        return false;
    }
    return slug.Equals(Utils.RemoveIllegalCharacters(p.Slug), StringComparison.OrdinalIgnoreCase);
});

 

Days are now mandatory in the URL rewrite to lead to a valid post.  Our old links that were using the yyyy/mm pattern are now returning and error page, and without installing Elmah or double-checking links, I would probably not have noticed. .

The RelativeLink property of the class Post was changed at svn revision 8906 to change the pattern from yyyy/mm to yyyy/mm/dd.  This in itself was not a problem if the old links continued to work.  But with this new change to the UrlRewrite class, the legacy format no longer works.

I changed the code manually to fix our problem, but this might affect other BlogEngine.NET users as well.  If you have a pretty old blog where people link to your old blog posts, then you probably are affected.

After some investigation, this bug was introduced while fixing Bug 9212.

I made a temporary patch for those of you who run BlogEngine.NET 1.6 and are experiencing the same issue.

I changed the code in UrlRewrite to search posts a second time but ignoring days in the date if it didn’t find the post in the first search.  Here is my code:

var predicate = new Func<bool, Predicate<Post>>(includeDays =>
{
    return new Predicate<Post>(
        p =>
        {
            if (date != DateTime.MinValue &&
                (p.DateCreated.Year != date.Year || p.DateCreated.Month != date.Month || (p.DateCreated.Day != date.Day && includeDays)))
                    {
                        return false;
                    }
                    return slug.Equals(Utils.RemoveIllegalCharacters(p.Slug), StringComparison.OrdinalIgnoreCase);
                }
        );
});
 
// Search all post and include days in the search
// A valid link would look like this
// http://blog.com/post/2010/02/10/slug.aspx
Post post = Post.Posts.Find(predicate(true));
 
// We can't find this post, try to find it but ignore days in the path
// http://blog.com/post/2010/02/slug.aspx
// Some older blogengine.net site used this link pattern
// We want to preserve links to those sites.
if (post == null)
    post = Post.Posts.Find(predicate(false));

 

BlogEngine.NET developers are aware of this bug, but in the meantime, you can just drop this new version of BlogEngine.Core.dll in your bin folder to temporary fix the problem if you are using version 1.6 of BlogEngine.NET.

I highly recommend installing Elmah on your BlogEngine.NET to discover errors like these!

Download BlogEngine.NET 1.6 Binary Patch (drop in your bin folder)

kick it on DotNetKicks.com

Shout it



ASP.NET Regenerate .designer.cs

clock February 8, 2010 11:58 by author EtienneT

In this simple post, I want to share a quick tip with Visual Studio.  You probably encountered this problem before: you are designing an ASPX or ASCX form in Visual Studio and then for some reason an element you just added in the ASPX/ASCX is not available in the code behind.

If you are lucky you know exactly what you changed in the page to cause this problem and you fix it.  However, if you’re opening a file that hasn’t been worked on in a long time (maybe someone else did the most recent modification), it becomes difficult to know what exactly cause the .designer.cs automatic generation fail. There are a few common scenarios for this such as invalid HTML, missing or duplicate declarations, or unresolved references. Finding the core issue by hand is difficult because the designer does not let you know what the specific issue is.

Small Tip

To regenerate the .designer.cs file for a ASPX/ASCX, first delete the .designer.cs file, then right click on the ASPX/ASCX and click “Convert to Web Application”.  This will probably give you an error message that will help you find the root of the problem.  I noticed that there’s a difference in the error message you might get here versus when the file is opened in Design mode in Visual Studio.

For example, we had a .ASCX file where the .designer.cs file was not being generated when we modified it.  We could not figure out what the error was until we discovered this tip because Visual Studio did not give us any feedback.  Once I deleted the .designer.cs and ran “Convert to Web Application” on the file, I was prompted with an error message that informed me that we had a <%@ Register /> declaration in our ASCX that was also declared in our web.config file (and this was creating a conflict even if they both pointed to the same location).  Visual Studio could benefit from better error reporting for this particular scenario.

kick it on DotNetKicks.com

Shout it



Simplified Chinese on Epson TM-T88IV Receipt Printer

clock January 15, 2010 14:27 by author JKealey

 

As you might know, our favourite teddy bear franchise is now opening stores in China. Since we’ve developed a fully integrated point of sale for the teddy bear industry, we’re helping them setup the first store over there. One of our challenges has been getting the receipt printer to recognize Simplified Chinese characters. We posted this issue on StackOverflow and contacted Epson support for the first time to try and resolve this issue but we ended up finding the solution ourselves. I’d like to share this solution with you (and some of the hoops we had to go through) as it might prove helpful to other point of sale developers out there.

 Chinese Teddy Bear Birth Certificate

For neophytes, using point of sale hardware is usually straightforward. We use the Microsoft Point of Sale SDK for .NET which is a .NET class library that interfaces with OPOS (OLE for POS). OPOS is the first widely-adopted POS device standard, allowing developers like us to write code that will work with hardware using a unified interface. I will spare you the details of how to get a handle on the printer (open it, claim it, enable it) and will focus on the actual printing portion.

string str = "this is a test";
PosPrinter printer = GetPrinter(); // open it, claim it, enable it. 
printer.PrintNormal(PrinterStation.Receipt, str);
ReleasePrinter(); // unclaim it. 
 

 

Easy enough?  This code worked for us for our stores in Québec (French), Spain (Spanish), and Denmark (Danish) and still worked for us in China when printing Latin characters, but all the Simplified Chinese characters appeared as question marks. 

Question Marks

The first thing to note is that you cannot use any plain old Epson TM-T88IV to print Chinese characters. You need the special multilingual version (which we have: TM-T88IVM). Second, you need to ensure that Epson OPOS sees it configured as the multilingual version, otherwise it won’t know it can print in simplified Chinese. In our tests, we were able to print to the printer via the sample application that comes with the Microsoft POS SDK.

Epson OPOS Configuration ms

Doing a bit of research, we found that we simply had to change the printer’s codepage (from 1252 to 936) for it to recognize the simplified Chinese characters. (Our CharacterSetList=255,437,850,852,858,860,863,865,866,936,998,999,1252 which implied that we could actually use this character set value. If we had not configured Epson OPOS to use the multilingual version, we would get an exception because 936 is not in the list.)

   1:  string str = "重新开始";
   2:  string str = "this is a test";
   3:  PosPrinter printer = GetPrinter(); // open it, claim it, enable it. 
   4:  printer.CharacterSet = 936;
   5:  printer.PrintNormal(PrinterStation.Receipt, str);
   6:  ReleasePrinter(); // unclaim it. 

 

However, this changed absolutely nothing. At this point, we contacted Epson support who could not help us. Our printer self tests showed the printer was capable of printing the characters, but we were still unable to print Chinese characters. Furthermore, the build-publish-test cycle was very slow because we did not have this printer on site (70 days to have it delivered from our regular supplier!) – we had to rely on our partner who was in China to help with the store setup. We tried dozens of things, but could not find the answer. We needed to have the printer on site – we had our partner ship one to us – it arrived three days later. We then decided to run another test:  printing the following website.

Some characters printed image

Interesting… some Chinese characters printed. But not where we were expecting them! I immediately realized it was encoding other (simpler) characters as Chinese characters. In this case, I took the first one above that was generated when the source string was ài. I did a few tests to confirm that àj, àk, àl were all printing different Chinese characters.

Having no knowledge of Chinese, finding the character’s unicode/decimal value in some character map was impossible for me. I had to reverse engineer the problem. 

  • à = 0xE0 in hex = 224 in decimal
  • i = 0x69 in hex = 105 in decimal
  • ài is therefore {224, 105} as confirmed by
byte[] source = Encoding.Unicode.GetBytes(text);

 

I looked up 0xE069 but found it was nothing. I then reloaded one of my old tests to convert this byte array to Code Page 936.

   1:  // simplified chinese
   2:  var encoding = Encoding.GetEncoding(936);
   3:   
   4:  // convert the text into a byte array
   5:  byte[] source = Encoding.Unicode.GetBytes(text);
   6:   
   7:  // convert that byte array to the new codepage. 
   8:  byte[] converted = Encoding.Convert(Encoding.Unicode, encoding, source);

 

Looking in the resulting byte array, I saw {145, 6}. However, converting this byte array back to a string and sending it to the printer did not work. It did not work because I was simply reconverting it back into a Unicode string (C#). I also did not have a PrintNormal method I could call that would accept a byte array. I therefore computed the decimal value of {145, 6} (256 * 145 + 6 = 37126) and looked it up to see it was indeed the character I was looking for ()!  I therefore implemented an ugly byte-by-byte conversion and sent it off to the printer. It worked!

   1:  StringBuilder builder = new StringBuilder();
   2:   
   3:  // simplified chinese
   4:  var encoding = Encoding.GetEncoding(936);
   5:   
   6:  // convert the text into a byte array
   7:  byte[] source = Encoding.Unicode.GetBytes(text);
   8:   
   9:  // convert that byte array to the new codepage. 
  10:  byte[] converted = Encoding.Convert(Encoding.Unicode, encoding, source);
  11:   
  12:  // take multi-byte characters and encode them as separate ascii characters 
  13:  foreach (byte b in converted)
  14:      builder.Append((char)b);
  15:   
  16:  // return the result
  17:  string result = builder.ToString();

Thanks to other Stack Overflow users, I found the following concise implementation.

string result =  Encoding.GetEncoding("ISO-8859-1").GetString(Encoding.GetEncoding(936).GetBytes(text));

Thus, it appears that although the printer supports multilingual characters, one needs to re-encode them in the target codepage and then back into Latin-1 encoding for the Epson TM-T88IV Multilingual to detect it properly. The only things left for us to fix was the string padding (because these characters are twice as wide as latin characters on our printer) and finish off the receipt translation before the grand opening.

Success!

As a side note, if any of our readers have experience with controls (ActiveX or other Windows-specific applications) that allow to print to receipt printer, control the cash drawer, and receive barcodes from a barcode scanner from within a web browser, Flash, or Silverlight, please let us know.



ASP.NET callback not being fired in Internet Explorer

clock January 13, 2010 10:03 by author JKealey

Happy New Year!

Very brief news: We’ve been working hard on our software products for the last few months. We’re building a large line of business application for a service franchise and a few stores opened with our software in Ireland, Scotland, Mexico, and, more recently, China. We’ve also contributed some work to the jUCMNav open source project, which focuses on visually representing software requirements.

Today, we encountered a weird little bug in Internet Explorer and we thought it might be good to describe the workaround which we found thanks to StackOverflow.

We're using a control that uses ASP.NET callbacks (not postbacks) in our page. However, in certain circumstances, the control stops working.

Problem: ASP.NET callbacks are not fired in Internet Explorer

  • The control works fine in FireFox, Google Chrome, etc.
  • The control works fine if we do not use ASP.NET AJAX History. As soon as we call the following code, the callbacks stop working in IE (6, 7, and 8)
ScriptManager.GetCurrent(Page).AddHistoryPoint("h", "12");

 

  • I did some server side debugging to figure out that RaiseCallbackEvent was not fired in IE, but was fired for others.
  • I have debugged using Fiddler and observed that it was not querying the appropriate URL. The server returns an invalid request error.

    Root Cause : IE thinks the anchor (hash tag) is part of the filename.

    Other browsers send the HTTP POST to: test.aspx but Internet Explorer is sending it to: test.aspx%23&&h=12

This is because the URL in the browser's bar is test.aspx#&&h=12, because of our AJAX History Control. For some reason, it URL Encodes the hash tag, but not the rest, and appends it to the aspx filename. Other browsers don’t exhibit this behaviour.

 

Goal: Force IE to drop the AJAX history anchor when calling WebForm_DoCallback via JavaScript?

Technique 1: Add a query string parameter

I found that if the browser was at a certain URL such as test.aspx?test=ing that the control worked fine. This is because appending the AJAX history to this URL makes test.aspx?test=ing%23&&h=12. This implies that we’re sending an invalid test query string of (ing%23&&h=12), but since we’re not using this query string parameter, it does not really matter. However, this does give you an ugly address.

Technique 2: Change the form’s action parameter

Gabriel McAdams lead me in this direction on StackOverflow.

Reading the contents on WebForm_DoCallback, I didn't see anything that set the URL of the server call. This means that it is either getting it from the form action or sending it to the current page. Try setting the form's action attribute.

Indeed, this is the solution we were looking for. ASP.NET has a single server-side FORM element named aspnetForm (auto-generated). If you remove the hash-tag from the form’s action parameter, the control works in all browsers. I assumed that the following code in the right spot would fix the issue, but it did not since ASP.NET’s action element is auto-generated. Any changes made to action don’t seem to do anything. (By default, Action is empty, forcing the browser to decide what URL to use for the current page, which must be the root problem here.)

this.Page.Form.Action = HttpContext.Current.Request.Url.PathAndQuery; 

 

However, if you change it using JavaScript (here I am using jQuery to make my life easier), then your problem is solved.

<div onmousedown="$('#aspnetForm').attr('action', '<%= HttpContext.Current.Request.Url.PathAndQuery %>')">
<!-- my control is in here - I could have executed the above code after every postback, but was sufficient for my needs -->
</div>

 

I’m executing the code when the mouse button is pushed here (sufficient for my needs, and executed before the click event which caused my problems). A cleaner solution would have you output this automatically when necessary. Note that I had tried changing window.location.hash to an empty string before the callback, but this caused the page to scroll, left the hash character in the URL, and broke my AJAX history.

kick it on DotNetKicks.com



Penniless Startup Founders

clock November 6, 2009 09:02 by author JKealey

Where will this path lead you? This post is a follow up to one of our previous posts that discussed starting a software business during the recession. In this post, I want to focus on the cash flow aspects for very early stage software startups. A few years ago, we started the company with nothing in the bank and we've managed to not only survive but prosper regardless of today's tough economic conditions. It is possible to launch a software startup with no money: the tradeoff is time. It will take longer to get out of the very early stages.

Context / Introduction

Before starting, I'd like to point out that the tips that follow are only valid in a particular context:

Understand that these tips are for the very early stage

  • Your first business goal is to get out of the very early stage as soon as possible.
  • Lots of these tips concern petty little details. However, together these details matter when at the very early stage, when you're fighting for survival.
  • Survival is a huge milestone but it isn't the end goal

You have no money and aren't interested in loans.

  • If you have no money, this is probably your first venture. I strongly feel loans are a bad idea for your first venture, but others have different opinions.
  • Cash is a great accelerator - once you've launched your first business you'll probably have a need for speed and will either have cash or be more open to debt/equity financing because you'll have already learned what you have to learn in organic growth.

You're starting a software company.

  • It is possible to start a software startup with limited cash. You've picked a good industry. If you wanted to become a dairy farmer, you would need a massive initial investment. However, for a software startup, your investment will be time writing code - not acquiring assets.

Tip 1) Sell to the right group

Since this is your first business and you have no money, you need to establish a consulting sideline selling to businesses that will give you a good return for your time (even if you're building a product for individuals). You won't be able to pay your bills selling $20 licenses to individuals in the early stage. We recommend selling customized versions of something that will help you grow your core product, as long as you can keep the intellectual property. Read more about this strategy in our previous post.

Everyone values the dollar differently. The earlier stage you are, the harder it is to define appropriate pricing given your credibility level and you don't give the same value to each dollar as your customers. As you grow, you’ll find your sweet spot and will be able to focus on your consulting clients that are right for you.

Eventually, you should aim at moving out of consulting, as it doesn't scale.

Tip 2) Minimize your expenses

No, this isn't where we started LavaBlast! Assuming you have no money, it's important that you only spend when necessary. At a high level, you need to be versatile and be able to do as much as you can on your own. Later, you'll be able to delegate but not in the early days. Of course, know your limits and get pros to do things that are impossible for you to do properly.

  • Don't hire an accountant to prepare invoices for you. Learn how to use accounting software and do it yourself. Only hire the accountant for an annual review or for real accounting work. Once you know how, it will take you a few seconds or a few minutes to do the most common tasks - you won't be paying someone four hours of work at a high hourly rate. 
  • Don't hire a law firm to review a simple non-disclosure agreement sent to you by a customer. Learn to read legal text on your own. Only hire a law firm when you've got something important to prepare or review.

There are plenty of examples of ways not to spend money when you're just starting out and have none of your own. It's time to learn things on your own.

Be smart about the commercial bank account you choose

I've dealt with a few different banks over the years. If you're a tiny business, it is good to know a few simple facts and comparison points.

Get a business account with a variable monthly fee

  • Don't bite when they offer you a $50/month fixed rate. You won't have enough transactions to make it worthwhile to upgrade. When you reach that point, switch to the fixed rate plan that is a best fit for your business. You can easily save $480 per year.
  • Some Canadian examples: Desjardins: $7/month, TD Canada Trust: $12.50/month, Royal Bank of Canada: $6/month
  • Some variable plans charge transactions on top of the minimum monthly fee. Do your homework.

Know the minimum balance you need to get it for free

    Get an ING Direct Savings Account
  • Some customers may pay you in advance or you may get grant money. Bottom line: you may end up with cash that you can't spend for a few months to a year. (Actually, you can spend it if you know more will be coming in - depends on your management style.) If you do have it in the account, earn interest on it.
  • Business accounts often don't give interest. If they do, the interest is horribly low if you don't lock it in. (Not paying service fees is often more than the amount you'd earn in interest anyways).
  • ING Direct's account is free. They have the best rates I've seen for low amounts that can be withdrawn at any time.
  • Best of all, they have a referral program. Both the new member and the referrer earn $25. In today's market, this could easily end up being worth more than the interest you'll generate in your first year. Our orange key is 33514316S1 – go ahead, signup (personal or business) and you’ll help support us and receive $25! :)

Don't get a commercial credit card for your purchases

  • Unlike personal cards, they're not free and most don't have any rewards programs.

Do you really need to accept credit cards?

  • It is a good fit for some users or services, but know the costs. If you have few transactions but most of them are high value, you're better off with a wire transfer.
  • Some banks charge you more for wire transfers than others.
  • Remember that cheques are slow - you don't have access to the funds are week.
  • You'll be paying $20-50 per month plus 2-3% per transaction. This quickly amounts to several thousand dollars.

Will you be dealing with multiple currencies?

  • We're a Canadian company but we have lots of clients in the US and in Europe. In the very early days, we chose not to open two separate bank accounts (one in each currency) because of the associated ongoing operating costs and increased accounting complexity.
  • Banks all have different exchange rates. However, I've found one bank consistently gives us a significantly worse rate when receiving transfers in another currency. A few percentage points makes a huge difference as the size of the payments increases.
  • The larger your conversion, the better your rates. Talk to a specialist like @JamesonBankTrav.

Minimize your telecommunication fees

Don't get a commercial telephone line via large companies

You'll pay much more than needed. Investigate Voice Over IP solutions such as Skype. You can get your own telephone number and free long distance in Canada + USA for an annual fee of $60. This service saves you hundreds per year. Don't get a fax unless your customers nag you for your fax phone number often enough. If you need one, look at online services such as myfax.com which deliver faxes by email and give you toll-free fax numbers for less than what you'd pay to get a separate telephone line in your office for the fax, without the clutter of a deprecated device.

Don't sign-up for a massive cell phone plan if you've got empty pockets

Depending on what you do with your phone, you can save upwards of a thousand dollars a year by downsizing to a prepaid plan. Smartphones are great, but depending on your situation, it might be a wise choice to minimize those expenses. Let's hope you're not locked into a crazy-expensive three year plan! In the end, this is a personal decision which depends on your personality; once you've tasted a smartphone you may be unable to go back. Just keep in mind you might be paying much more for your cell phone than the much faster Internet connection you use all day.

Minimize your rent

Use a co-working facility

One tip often given to people starting their own company is to avoid renting office space too early in the process. Instead, work from home or from a more affordable co-working location. Not only do co-working locations reduce costs, they help you build your business because of the contacts you can make there. Once you’re read, upgrade to shared office space.

Don't minimize everything

In addition to being able to exchange services with other companies to cut costs, there are a few places where you can't afford to cut costs.

Your Image

One thing you don't want to be cheap on is branding. Your image is everything - quality needs to be high. Get nice business cards created by pros. Don't do your own web design if that's not something you specialize in. Your product will look amateurish and you'll lose sales. There are tons of affordable graphic designers out there: find one and have something nice created. Use online marketplaces such as 99designs. Since you're still a software expert, however, you should know enough HTML and search engine optimization techniques to be able to maintain your website. If you're a horrible writer, have someone review your content. This basically boils down to knowing your limits; there are some things you won't be good enough at even if you try.

Hardware & work area

We agree with Joel Spolsky's view that you should buy the best computer hardware and computer chair you can find. These are your primary tools and they are relatively inexpensive compared to your salary, even when you have very low revenue. One investment that is definitely worth it is a second monitor as it tremendously increases your productivity.

Your health

As much as your work environment is important, you should also value your health. Even if you're living on a very tight budget, don't eat hot dogs all day. Proper nutrition and good sleep cycles keep you in good health and makes you more productive. You should not be falling asleep in the afternoon. Starting your business is a marathon, not a sprint. Make sure your lifestyle is well adapted for a marathon.

Tip 3) Leverage your money

We've already covered this part in a previous blog post. Know what government funding opportunities are out there. Some require matching contributions. Some are based on your expenditures. Look around for these opportunities but mostly talk to other people to know what's out there and what's worthwhile.

Tip 4) Cash flow projections for dummies

You should always keep an eye on your cash flow, not just your revenue. I've created a very simple Excel spreadsheet to help with our cash flow projections. This one is simply a template with some random numbers in there. The one we use internally is a bit more complicated as it includes things such as currency exchange rates, taxes, etc. Build it however you like, but I've found that the two most important elements in there are:

1) Past Sales versus Projected Sales

What are my known sales (recurring revenue) versus what serious leads do I have in the pipeline. Being conservative, I base my business decisions on my past sales not my projected sales because I've learned that projected sales are often postponed. We have long sales cycles that culminate with a large sale which has a big impact on that month's revenue. Separating known sales from projected sales is of critical importance because of this because we either make the sale or get zero revenue from that customer in that month. If you're selling lots of lower value items (subscriptions to your service, for example), each individual customer has less impact on your total monthly revenue.

2) Runway

Given our current burn rate, when will we run out of cash if none of the sales in the pipeline are realized. This is useful to help you decide if you can hire and/or if you can give yourself a raise. It can also make you realize you're heading towards a problem and you need to correct the situation as soon as possible.

cashflow

It would be nice to have a simple, open source, application that helps business owners track their cash flow projections in this fashion. You could go overboard and integrate it with accounting software, but I think it's nice when it's simple.

Conclusion

The path ends up being longer than expected When you start your first company, and you have no cash on hand, you need to focus on making money and keeping the little money you have. Survival is a major milestone, but remember that it isn't the end goal. You'll learn tons of things along the way, and once you do leave the very early stages, you'll need to manage your cash flow properly. Later on in life, you'll probably start another business - this time you hopefully won't be as strapped for cash - and you'll be able to speed up the whole process.

I'm not sure what is harder between:

  • A) Going from nothing to survival
  • B) Going from survival to success
    I do know, however, that going from nothing to survival appears a lot easier if you have cash to start off with or if you've done it before. Since success is in the eye of the beholder, it all depends on what you want to achieve.


Sweet Mischief Cakes

clock September 28, 2009 13:33 by author JKealey

Although we haven’t been very vocal lately, we have lots going on here at LavaBlast and hope to write up a few technical posts and make a few announcements this coming fall. In the meantime, don’t be shy and connect with two of LavaBlast’s co-founders on Twitter (@jkealey, @etiennet). Alternatively, come meet us in person at this week’s Startup Ottawa Drinks.

Last Friday, we gathered to celebrate Jason’s birthday.  As co-founder and president of LavaBlast, Jason has helped build this software start-up from the ground up over the past two and a half years; there’s no doubt that LavaBlast is a big part of his life!  Our friend Jean-Philippe had the great idea to buy a custom cake for LavaBlast’s leader!  Here are some pictures:

IMG_4097 IMG_4136 IMG_4141-Edit-2

 

As you can see, even Calis (Jason’s dog) wanted a piece of the cake!  In additional to be the only such cake in the universe, it was of high quality and tasted great!  Since it was a chocolate cake, it was a lot of work to have a white icing coating it.  I would recommend Sweet Mischief Cakes (another Ottawa startup!) to anyone living in the National Capital Region.  Cakes are hand made and you can choose pretty much any design you want on it.

Launching a startup is a tough job but, once in a while, you need to sit back and enjoy life’s simple pleasures.



Gotcha: gzip compression in SOAP message calls

clock August 20, 2009 10:30 by author JKealey

Some of our software systems here at LavaBlast exchange “large” data sets (only a few megabytes) in order to synchronize data between the systems. The data being exchanged is simple XML and we assumed that the SOAP calls were compressed by the built-in HTTP compression in the web server. However, checking the HTTP headers on the server when a call was made, we noticed that the Accept-Encoding header was missing. (HttpContext.Current.Request.Headers["Accept-Encoding"] did not exist).

Surprised, we turn to Google and immediately found a reference to the EnableDecompression property of the SOAP client. By setting it to true, we ensured our SOAP client was requesting gzip compression when making calls.

LavaBlastServer.Inventory centralServer = new LavaBlastServer.Inventory();
centralServer.EnableDecompression = true;

There is not much more to it. However, if you’re unaware that this setting is required, you may have clients running in the wild that don’t use gzip compression but could benefit from it.

kick it on DotNetKicks.com


Multi-level contextual menus in Eclipse/GEF

clock June 22, 2009 10:46 by author JKealey

 

Scenario: I want my ContextMenuProvider to have multiple levels

Imagine you already have a ContextMenuProvider setup in your GEF editor but you would like to have multiple levels of actions, grouping elements together. This is one of the elements we recently had to accomplish in jUCMNav and since the we couldn’t easily find sample code on Google, we thought it would be nice to share this code with you.

 multilevel_contextmenu

Step 1) Code for the sub menu container

   1:  package seg.jUCMNav.actions;
   2:   
   3:  import org.eclipse.jface.action.Action;
   4:  import org.eclipse.jface.action.IAction;
   5:  import org.eclipse.jface.action.IMenuCreator;
   6:  import org.eclipse.jface.resource.ImageDescriptor;
   7:  import org.eclipse.swt.SWT;
   8:  import org.eclipse.swt.events.SelectionEvent;
   9:  import org.eclipse.swt.events.SelectionListener;
  10:  import org.eclipse.swt.widgets.Control;
  11:  import org.eclipse.swt.widgets.Menu;
  12:  import org.eclipse.swt.widgets.MenuItem;
  13:   
  14:  /**
  15:   * This action contains other actions and helps create another level of
  16:   * contextual menus.
  17:   * 
  18:   * @author jkealey
  19:   * 
  20:   */
  21:  public class SubmenuAction extends Action implements SelectionListener
  22:  {
  23:      // / Who to inform when this action is fired (meaning display the submenu)
  24:      private SelectionListener actionInstance;
  25:   
  26:      // the list of actions that are contained within this action
  27:      private IAction[] actions;
  28:   
  29:      // should we hide the disabled ones (if not, they will appear as grayed out)
  30:      private boolean hideDisabled;
  31:   
  32:      /***
  33:       * Create a submenu.
  34:       * 
  35:       * @param subactions
  36:       *            the actions that are contained within
  37:       * @param text
  38:       *            the container's textual label
  39:       * @param toolTip
  40:       *            the container's tooltip
  41:       * @param descriptor
  42:       *            the container's image descriptor
  43:       * @param hideDisabledActions
  44:       *            should we hide the disabled ones (if not, they will appear as
  45:       *            grayed out)
  46:       */
  47:      public SubmenuAction(IAction[] subactions, String text, String toolTip, ImageDescriptor descriptor, boolean hideDisabledActions)
  48:      {
  49:          // indicate that this is a secondary fly-out menu.
  50:          super("", IAction.AS_DROP_DOWN_MENU);
  51:   
  52:          this.actionInstance = this;
  53:          this.actions = subactions;
  54:          this.hideDisabled = hideDisabledActions;
  55:   
  56:          setText(text);
  57:          setToolTipText(toolTip);
  58:          setImageDescriptor(descriptor);
  59:   
  60:          // the secondayr menu logic
  61:          setMenuCreator(new IMenuCreator()
  62:          {
  63:              public Menu getMenu(Control parent)
  64:              {
  65:                  // this would be used outside of a menu. not useful for us.
  66:                  return null;
  67:              }
  68:   
  69:              public Menu getMenu(Menu parent)
  70:              {
  71:                  // create a submenu
  72:                  Menu menu = new Menu(parent);
  73:                  // fill it with our actions
  74:                  for (int i = 0; i < actions.length; i++)
  75:                  {
  76:                      // skip the disabled ones if necessary (or null actions)
  77:                      if (actions[i] == null || !actions[i].isEnabled() && hideDisabled)
  78:                          continue;
  79:   
  80:                      // create the submenu item
  81:                      MenuItem item = new MenuItem(menu, SWT.NONE);
  82:   
  83:                      // memorize the index
  84:                      item.setData(new Integer(i));
  85:   
  86:                      // identify it
  87:                      item.setText(actions[i].getText());
  88:   
  89:                      // create its image
  90:                      if (actions[i].getImageDescriptor() != null)
  91:                          item.setImage(actions[i].getImageDescriptor().createImage());
  92:   
  93:                      // inform us when something is selected.
  94:                      item.addSelectionListener(actionInstance);
  95:                  }
  96:                  return menu;
  97:              }
  98:   
  99:              public void dispose()
 100:              {
 101:              }
 102:          });
 103:   
 104:      }
 105:   
 106:      /**
 107:       * Returns how many items are enabled in the flyout. Useful to hide the
 108:       * submenu when none are enabled.
 109:       * 
 110:       * @return the number of currently enabled menu items.
 111:       */
 112:      public int getActiveOperationCount()
 113:      {
 114:          int operationCount = 0;
 115:          for (int i = 0; i < actions.length; i++)
 116:              operationCount += actions[i] != null && actions[i].isEnabled() ? 1 : 0;
 117:   
 118:          return operationCount;
 119:      }
 120:   
 121:      /**
 122:       * Runs the default action
 123:       */
 124:      public void run()
 125:      {
 126:          actions[0].run();
 127:      }
 128:   
 129:      /**
 130:       * Runs the default action
 131:       */
 132:      public void widgetDefaultSelected(SelectionEvent e)
 133:      {
 134:          actions[0].run();
 135:      }
 136:   
 137:      /**
 138:       * Called when an item in the drop-down menu is selected. Runs the
 139:       * associated run() method
 140:       */
 141:      public void widgetSelected(SelectionEvent e)
 142:      {
 143:          // get the index from the data and run that action.
 144:          actions[((Integer) (((MenuItem) (e.getSource())).getData())).intValue()].run();
 145:      }
 146:  }

Step 2) Setup your GEF ContextMenuProvider

   1:  public class UrnContextMenuProvider extends ContextMenuProvider {
   2:   
   3:      private ActionRegistry actionRegistry;
   4:      /**
   5:       * @param viewer
   6:       * @param registry
   7:       *            has to be passed in case we don't want to use the action registry used in the viewer. [is this bad coding?]
   8:       */
   9:      public UrnContextMenuProvider(EditPartViewer viewer, ActionRegistry registry) {
  10:          super(viewer);
  11:          setActionRegistry(registry);
  12:      }
  13:   
  14:      /**
  15:       * 
  16:       * @return the action registry used by the context menu provider.
  17:       */
  18:      private ActionRegistry getActionRegistry() {
  19:          return actionRegistry;
  20:      }
  21:   
  22:      /**
  23:       * 
  24:       * @param registry
  25:       *            the action registry used by the context menu provider.
  26:       */
  27:      private void setActionRegistry(ActionRegistry registry) {
  28:          actionRegistry = registry;
  29:      }
  30:   
  31:   
  32:      /**
  33:       * Looks up a set of actions in the action registry. If they are enabled, adds them to the correct groups.
  34:       */
  35:      public void buildContextMenu(IMenuManager manager) {
  36:          GEFActionConstants.addStandardActionGroups(manager);
  37:   
  38:      // regular action
  39:      IAction action = getActionRegistry().getAction(AddLabelAction.ADDLABEL);
  40:          if (action.isEnabled())
  41:              manager.appendToGroup(GEFActionConstants.GROUP_REST, action);
  42:   
  43:      // compound action
  44:          IAction[] actions = new IAction[13];
  45:          actions[0] = getActionRegistry().getAction(AddOrForkAction.ADDORFORK);
  46:          actions[1] = getActionRegistry().getAction(AddAndForkAction.ADDANDFORK);
  47:          actions[2] = getActionRegistry().getAction(AddOrJoinAction.ADDORJOIN);
  48:          actions[3] = getActionRegistry().getAction(AddAndJoinAction.ADDANDJOIN);
  49:   
  50:          SubmenuAction submenu = new SubmenuAction(actions, "Path Operations", "Path operations", actions[0].getImageDescriptor(), true); 
  51:          if (submenu.getActiveOperationCount()>0)
  52:              manager.appendToGroup(GEFActionConstants.GROUP_REST, submenu);
  53:   
  54:      // ... add other actions ... 
  55:     }
  56:  }

That’s all there is to it!



Eclipse Contextual Help in WizardDialog

clock June 22, 2009 10:08 by author JKealey

Although LavaBlast mainly produces .NET-based solutions, we’re currently working on usability enhancements to our favourite requirements engineering tool, jUCMNav. jUCMNav is Java-based open source project (an Eclipse plug-in) for graphical software requirements modelling built using the Graphical Editing Framework (GEF) and the Eclipse Modeling Framework (EMF).  Personally, I love the development cycle (with automatic incremental compilation) in Java/Eclipse. Writing plug-ins for Eclipse is tons of fun and there is an active community contributing to the Eclipse project (and its subprojects). However, I often find myself wanting to do something simple and having to fiddle around with 12 source code excerpts to make it work as a whole for various reasons.

  • You know how to do something for general Eclipse plug-ins but not where to hook it up to your GEF editor.
  • You know how to do something in SWT/Draw2D but not where to hook it up to your GEF editor.
  • You get burned by random conventions that aren’t clearly defined
  • Some file you would think would be included is ignored by your build script.
    In any case, I’ll post a few technical details on some of the issues we had to solve, hoping it will help someone out in the future!

Scenario: You are using a WizardDialog in your application, but the ? button does nothing.

Or: How do you set up contextual help on a WizardDialog in Eclipse?

Step 1) Create a help_contexts.xml file.

<?xml version="1.0" encoding="UTF-8"?>
<?NLS TYPE="org.eclipse.help.contexts"?>
<contexts>
   <context  id="help_general" >
        <description>test</description>
        <topic label="test" href="http://domain.com/help.html"/>
   </context>
</contexts>

 

  • The name of the XML file is not important.
  • Important: DO NOT include your plug-in name in the context id (here: “help_general”)
  • Important: DO NOT include any periods in the context id (here: “help_general”, not “help.general”)
  • You may reference local help files – they don’t need to be external.

     

    Step 2) Reference the contexts file from your plugin.xml

    <extension point="org.eclipse.help.contexts">
             <contexts file="help_contexts.xml">
             </contexts>
    </extension>
  • The contexts element has an optional plugin-id parameter. Leave this empty unless you want to contribute help contexts to another plug-in.

Step 3) Ensure help_contexts.xml is packaged with your application

  • Edit your build.properties file to ensure it includes help_contexts.xml (bin.includes = …, help_contexts.xml, …)
  • Note the Bundle-SymbolicName in your Manifest.MF (also visible in your plugin.xml editor). If none found, note Bundle-Name.  Example: com.domain.myplugin

Step 4) Set the context id in the WizardPage

public class MyWizardPage extends WizardPage
    public void createControl(Composite parent) {
        PlatformUI.getWorkbench.getHelpSystem.setHelp(parent, "com.domain.myplugin.help_general");
    }
}
  • You must add this in each WizardPage, not the WizardDialog.
    Hope this helped!


YUI Compressor for Visual Studio

clock May 11, 2009 14:44 by author EtienneT

Although you don't want this for all things in life, you do want to ensure that your JavaScript and CSS files are as small as possible.  As a web programmer, a script minifier is a useful application that should be a part of your toolbelt. This article presents a simple way to hook up a popular minifer inside Visual Studio.

First you need to download YUI Compressor from Yahoo and unzip its contents anywhere.

External Tool

Now you want to make a new external tool in Visual Studio. (Tools -> External Tools...)

image

Then add a new tool and name it YUI Compressor.  You want to put the following values in the inputs.  Adjust the jar location depending of where you unzipped YUI Compressor.  It also assumes that java.exe is in your path.

image

  1. Title: Yui Compressor
  2. Command: java.exe
  3. Arguments: -jar "E:\yuicompressor-2.4.2\build\yuicompressor-2.4.2.jar" $(ItemPath) --charset "UTF8" --type js -o $(ItemFileName).min$(ItemExt)
  4. Initial Directory: $(ItemDir)
  5. Check “Use Output Window”

You can already test this.  Any problems will be presented in the Output window.  First you need to select your *.js file in the Solution Explorer.  Then in the Tool menu, select Yui Compressor.  Refresh the directory that contains your *.js file and you should have a file named *.min.js with the same prefix as your *.js.

You could do the same thing to minimize CSS just by making a new external tool with the –type argument value changed to css instead of js.

Toolbar

You can easily make a toolbar to quickly do this when you need it.  Go to Tools->Customize.  Then New…

image

Then go to the Command Tab and select Tools.

image

Drag and Drop the External Command 1 (or the # corresponding to your external tool if you have more than one) to the created toolbar.  Hit close and now you can put your new toolbar wherever you want.

I hope you find this useful! Setting up tools this way inside Visual Studio is a good way to speed up the development process because you don't need to memorize numerous command line parameters: everything is done with one click!

kick it on DotNetKicks.com


Month List

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2017

Sign in