Putting Custom Text on a Google Maps Marker Image

If you've ever done anything with Google Maps, you may have dabbled with custom icons.  For example, you can have the default image of http://www.google.com/mapfiles/marker.png by not supplying a custom image.  Or, if you've ever done a search on Google, you may have noticed markers with a letter on it, like http://www.google.com/mapfiles/markerA.png.

A client had me write an implementation of Google Maps that maps distributors of their products.  Afterwards, they asked if they could make custom markers.  Their developers noticed some code I had:

var customIcon = new GIcon(G_DEFAULT_ICON);
if('< % Response.Write(MarkerIcon); % >' == '')
{
  var letter = String.fromCharCode("A".charCodeAt(0) + currentIndex< %=this.ClientID% >);
  customIcon.image = "http://www.google.com/mapfiles/marker" + letter + ".png";
  markerOption = { icon:customIcon };
  var marker = new GMarker(point, markerOption);
}

Where "point" is what was returned from getLatLng(); and MarkerIcon is a content-managed property.  This would be called if they never picked a custom icon from the content management system.

Their developers were wondering if they could just put whatever into that image URL and have it print out.  Like http://www.google.com/mapfiles/markerWHATEVERIWANT.png and it would superimpose "WHATEVERIWANT" on top of the base marker.  I told them that it doesn't work that way: Google has 26 png files and you can reference them as markerA.png, markerB.png, etc. (strangely enough, it's case sensitive...)  You can't type in whatever and have them print it back to you.

The client pushed back again, wondering if it was possible, somehow.  Well, I thought, I've made CAPTCHAs before, how much more difficult could this be?  So, I downloaded the default marker from Google and edited it in Paint.Net to get rid of that ugly black dot in the middle.  Then, I started Googling around for some quickstarts to what I was doing.  I found this on CodeProject: http://www.codeproject.com/KB/web-image/AspNetCreateTextImage.aspx.  It was written by a student and was all put in one huge Page_Load method, but it did most of what I wanted and got me off to a good headstart.  After dinking around, I figured out how to load a base image - the blank marker file - and get transparency in the text being written, the main missing pieces.  After refactoring things to make them a little easier to understand, (mainly by making a bunch of methods instead of one super Page_Load) I was all set.

I attached the code to this post, zipped up.  Remember to change line 90 of the C Sharp file to wherever you put your BlankMarker.gif file.

Realize that, since the Google Marker image is so small, 20 x 34, and the marker inside of the image is even smaller, only two characters can conceivably fit inside of this particular marker.  You could make a larger image, or get rid of the base image altogether, if you wanted to allow for more characters, but realize that the GIcon is meant to be 20 x 34 and you'll need to change those dimensions accordingly.

Here is a screenshot of my ImageCreator.aspx in action:

As you can see from the URL, I just typed in the page name with a query string variable of text set to "Hi" and I get a nice little Google Maps marker with "Hi" superimposed over the top.  And it's transparent!

In order to set the Google Maps icon marker to the result of this page, simply say:

customIcon.image = "http://[Base Website URL]/ImageCreator.aspx?text=" + myString;

Where [Base Website URL] is your website's URL to where you put the ImageCreator.aspx file and myString is what you want it to say.  Once again, any more than two characters will make this not look so good.

Happy geocoding!

Posted by vbullinger

Validation for CheckBoxLists

Apparently, you are not allowed to attach validators to CheckBoxLists.  Not allowing them to be attached to CheckBoxes kind of makes sense, because how do you validate a "no" response?  Although, I think they should be validatable, too, because what if it's required that you check a box to agree to some terms or something to that effect?

Anyway, a list of options should be able to be made required, IMHO.  This was a requirement from a client, so I rolled up my sleeves and figured it out.

I wanted to make a custom validator, behind the scenes, and just sneak it into the validation process.

All validators are written out on the page as <span> tags with a few properties.  For example, here's the output of a RequiredFieldValidator on a TextBox:

<span id="_zipRequired" controltovalidate="_zip" errormessage="Please enter a zip code." display="None" evaluationfunction="RequiredFieldValidatorEvaluateIsValid" initialvalue="" style="color:Red;display:none;"></span>

That "RequiredFieldValidatorEvaluateIsValid" is built-in, by the way.

I had to get me one o' them spans... so I built one per field the user wanted validated - which was dynamic, no less - and attached them to the Page_Validators array, like so:

       var errorMessage = 'Please choose one of the following'; // Or some similar error message
       // Create a new span that is a mock up of a required field validator
       var newValidator = document.createElement('span');
       newValidator.setAttribute('id', "_myCheckBoxList_Required");
       newValidator.setAttribute('controltovalidate', '_myCheckBoxList');
       newValidator.setAttribute('errormessage', errorMessage);
       newValidator.setAttribute('display', 'None');
       newValidator.setAttribute('evaluationfunction', 'ValidateCheckBoxChecked');
       newValidator.setAttribute('initialvalue', '');
       newValidator.setAttribute('style', 'color:Red;display:none;');
       document.getElementById("_myCheckBoxList").appendChild(newValidator);

       // Add it to the Page_Validators array
       Page_Validators[Page_Validators.length] = document.all["_myCheckBoxList_Required"];

Now we have a validator in the array.  But we have to write the validation method.  Then again, that part's much easier:

 function ValidateCheckBoxChecked(val)
 {
  for(var i = 0; document.all[val.controltovalidate + "_" + i] != null; i++)
   if(document.all[val.controltovalidate + "_" + i].checked)
    return true;

  return false;
 }

Now, you're thinking, "Ok, we're done, right?"  Nope.  Problem is, just because we added something to the Page_Validators array, doesn't mean that it's truly hooked up properly.  A little digging around in the source code of a page with some validators on it and alerting the functions being called led me to this: ValidatorOnLoad().  You have to call it AFTER you create the validator and add it to the Page_Validators array.  It will pull everything apart (the evaluationfunction, for example) and hook everything up for you.

Now, you should be set to go.  I've seen people do things like create a new validator, too, from the base validator class.  That can work as well.

Posted by vbullinger

MDC Development Conference Wrap-Up

On Tuesday, the 12th, I went to the MSDN Developer's Conference: http://www.msdndevcon.com/Pages/Minneapolis.aspx.  It was quite a good deal at just $99!  Especially since Inetium picked up the tab Smile

I went with Erik O'Leary, but Justin Vogt, Eric Raarup and Julie Korte went, as well, and we had a booth set up.

The weather was awful, and traffic was worse.  We ended up getting there at 8:30, which is when registration and breakfast were supposed to end.  But, since we weren't the only ones who had issues with the traffic, they moved things back.

The keynote was pretty interesting, actually.  I went to VSLive in New York this past September and the keynotes were pretty weak, to be honest.  But this keynote introduced a lot of cool new, upcoming technologies.  They talked about Windows Azure, Microsoft Surface, Windows 7Microsoft Live Mesh and general philosophies behind them and where Microsoft is going.  Lots of demos on everything were shown.

In the first breakout session, I went to see "The Future of Managed Languages: C# and Visual Basic," given by Jason Bock.  He took us through a bit of history with the two languages, where they are now and where they're going.  It was a good talk, except that part of it needed internet connectivity, which failed miserably Smile  Not his fault.  Too much information for one blog post.

After that, we had lunch.  During lunch, we had a TechMasters meeting.  This week, I was the Court Jester and read off some quotes from our soon-to-be ex-president, as a farewell, with a little commentary by me.  It was truncated to a half an hour, when it's usually at least twice that long.  There were a TON of people there, eating while listening to the speakers.  I think we did pretty well for the tight time frame.  TechMasters had a booth, too.

For the first breakout session after lunch, I went to "Developing Data-Centric Applications Using WPF DataGrid and Ribbon Controls," given by Mike Benkovich.  It focused on creating a little more meaningful applications (data-centric) instead of just the fun stuff you might dink around doing on your spare time, and using some of the new controls from the WPF Toolkit.  It was a good talk from a great speaker.  The only problem with Mike's talks is that he's so ahead of the game that he has to slow down and explain tangential and sometimes entirely unrelated parts of his presentation because some of the viewers don't understand his code Smile  I was glad to hear that most of the items in this talk are available to Silverlight, too.

In the next breakout session, I went to "Building Business-Focused Applications Using Silverlight 2," given by Robert Boedigheimer.  I've been getting into Silverlight quite a bit, so I didn't learn too much, and some of the demo didn't work properly.  I've been to talks by Robert before and he really puts together good sessions, so I'm pretty sure that this was due to the fact that a lot of these demos were written by other people (from PDC).  I would tell the original writer to take tips from Mike Benkovich.  For example, Mike makes snippets that he just plops in and pieces things together and it all works out as planned.  This session had a separate file with these snippets all mixed together in it and things didn't work 100% properly.  Otherwise, it was good content.

The last session to which I went was "An Introduction to Microsoft F#," by Chris Williams.  Jason Olson piggy-backed on at the last second, which was great because he's a great and engaging speaker.  I was interested in F# because, embarrassingly, I had never looked into it and have used functional languages in the past.  I used functional languages in college, but I didn't know how similar F# was to Lisp and Scheme.  It's very similar, and if you know .Net and either Lisp or Scheme, you would very easily be able to pickup F#.  Seems useful for not just learning for the sake of learning a new way to program, but it seems to have a niche in parallelization.  It'll be coming in VS 2010 and the 4.0 framework.  Should be pretty useful in these certain situations.  I think I'll be giving a developer's meeting here at Inetium about it soon.

I'm glad to have had the chance to go to MDC.  It was a good, cost-effective learning experience for all involved.

Posted by vbullinger

Disabling All CRM Form Fields

I was recently asked to disable all fields on a CRM form if certain conditions were met.  At first, I though "Eww!  I'm going to have to type 500 lines of code" like the following:

crmForm.all.customerid.Disabled = true;
crmForm.all.name.Disabled = true;
crmForm.all.description.Disabled = true;
...

But then I thought to myself "'Disabled' is a field that only CRM fields would have.  Why not just cycle through all the crmForm items and disable them in a loop?"  So I came up with this:

for (var index = 0; index < crmForm.all.length; index++)
 if(crmForm.all[index].Disabled != null)
  crmForm.all[index].Disabled = true;

Worked like a charm.  Much quicker.  Only three lines of code, too.

Posted by vbullinger

VS Live Conference: Day Two

Day two of the VS Live conference is over, as is another day of lectures.  Again, I attended a lot of lectures on WPF and Silverlight all day.  In the evening, though, I sat in on a lecture on the new .Net MVC Framework.  I was interested in seeing what they did with the MVC concept I learned about back in college.

Today's lectures were given under the assumption that no one was a complete newb.  There was a keynote in the morning by Mary Jo Foley, author of a book about the post Gates-era Microsoft.  We talked about anything from when she was able to interview Bill Gates back when Microsoft was much smaller to Google Chrome.

Today, I attended the following lectures:

http://vslive.com/2008/newyork/rich_clients.aspx#vt2 - Silverlight and Expression from a developer's point of view
http://vslive.com/2008/newyork/rich_clients.aspx#vt6 - Making your line-of-business applications look pretty using Silverlight
http://vslive.com/2008/newyork/rich_clients.aspx#vt10 - Determining when to use AJAX or Silverlight instead of normal .Net
http://vslive.com/2008/newyork/rich_clients.aspx#vt14 - How to turn your Silverlight code into a user control and use it in other places in the application
http://vslive.com/2008/newyork/services_mashups.aspx#vt18 - Silverlight with SOA
http://vslive.com/2008/newyork/night_sessions.aspx#ASP - ASP.Net MVC

Most of the lectures I attended were kind of unimportant looks at different case studies (usually made up).  Nothing too fantastic, but it was good to get more exposure and more looks at the technologies from different angles.  It was nice to see Silverlight code turned into controls, though, and Rocky Lhotka's talk on Silverlight and SOA was very good.  Rocky is a local - he works at Magenic - and the talk went over some good things to consider, best practices, etc.  But the most important thing to think about is that, if you want a serious application and you're using Silverlight, you almost undoubtedly will be using some services.  More than likely, you'll have to write them.

Since Silverlight is meant to operate on the client and not the server, you'll need some asynchronous calls to web services to get anything done.  We went over a simple service, a simple Silverlight app (can't even remember what it did) and some things to think about and tips, etc.  For example, if you want to re-use code, you have some things to think about:

Since we're talking Silverlight here, you obviously can't use much of the .Net framework.  That means that if you build a massive application, your Silverlight project can NOT use it.  Directly, anyway.  It can't reference your other projects: it can only use other Silverlight projects.  And the rest of your application can NOT use any of your Silverlight projects.  He did show us a couple of tricks to re-use a little bit: write code that can be used by both projects in the Silverlight app and reference the individual file as a link in the service project.  Make it a partial class.  In each of the projects, finish off the partial class with the Silverlight or .Net specifics in a different file.

The rest of any kind of work should be done with services, and is something that needs to be considered when working with Silverlight.

One more day of lectures in New York and then it'll be a day of sightseeing and respect-paying.

Ben is totally AWOL.  I think he's too excited to be in the city and had to get out :)

We're planning on going to an authentic New York pizza parlor tomorrow, though.

Posted by vbullinger

VS Live Conference: Day One

Well, the first day of the VS Live conference has finished, and Ben and I have been in lectures all day.  Ben was sitting in on lectures on LINQ and similar topics all day, I believe.  Haven't touched base with him since the lectures ended, though, so I don't know how it's been going for him.

I spent the day listening in on some pretty fun lectures.  WPF and Silverlight have been the main topics, and there are enough of those to last me through tomorrow, too.

The first lecture wsa kind of a 101 on WPF, which was good because I've never really rolled my sleeves up and worked with it before.  I've just seen some introductions.  The next two I attended were presented by the same guy, a Microsoft MVP who is the president of Oak Leaf Enterprises - http://www.oakleafsd.com/ - and were a little more in-depth.  He re-created the iPhone's interface using WPF.  It obviously didn't quite pull up the same operations that the iPhone does, but it did behave in a very similar manner and looked almost identical.  And the cell phone bars were solely a mock up :)

The last couple of lectures were a cross between WPF and Silverlight.  We learned a lot about how to minimize code and how to be more productive with your XAML, as well as how to do some pretty fancy data binding stuff.

The biggest thing I've found out is that this is not just some fad or some kind of *other* way to do things: this is it.  WPF and its ilk are the future.  Truly.  I'm not saying that in a gimmicky way.  I never do that.  I say what I mean and I mean what I say.  Microsoft has literally dead-ended Windows Forms.  There will be no major enhancements for the Windows Forms technologies: WPF is the way to go.  WPF is also very easily converted from a Windows application to a web application.  This is not the case with normal Windows Forms and Windows Web Applications as it stands today.

And I, for one, welcome our new development technology overlords.  As the internet meme goes.

Designers can learn XAML, in my opinion, or at least the ones worth keeping around.  Also, we developers can have a much easier time developing richer, better looking applications.  This will really help (expression) blend the two categories of coding.  That (terrible) pun does make me wonder about the future of Microsoft Expression Blend and Microsoft Visual Studio.  I find it very strange to keep the two separate.  Maybe they could be offerred separately, so that the costs of either one could stay down, if you didn't need the other, but I'd prefer a package deal that wasn't too outrageously expensive, myself.

I'll be getting some other things in tomorrow and Wednesday, but today was all WPF, XAML and Silverlight.  I'll check in again tomorrow, with more information on the aformentioned technologies, plus a .Net MVC Framework lecture I'll be attending, as well.

I'll be keeping tabs on my non-VS Live dealings here in New York on my MySpace blog if you are privvy to it (meh, it's public, anyone can look it up) if you care to hear about my non-professional goings-on.

Posted by vbullinger

Cleaning Your Database After a SQL Injection Attack

If you have an older website that doesn't have the top-notch security that most Inetium websites have, you may be vulnerable to a SQL injection attack.  I wrote a SQL script to clean up a database that has been hit with a SQL injection attack.

It goes through the database and grabs all the string columns (ntext, text, varchar and nvarchar) from all the tables.  Then, it replaces some bad text that was added by the SQL injection attack - "[Text added via a SQL injection attack]" - with nothing.

Good luck cleaning your database!

DECLARE CleanDatabaseCursor CURSOR
READ_ONLY
FOR

SELECT o.name as [Object Name], c.name as [Column Name]
FROM syscolumns c INNER JOIN sysobjects o ON o.id = c.id
WHERE c.type in (35, 39) and o.xtype like 'U'

declare @query VARCHAR(8000)
declare @ObjectName varchar(8000)
declare @ColumnName varchar(8000)
declare @newValue varchar(8000)

select @ObjectName = ''
open CleanDatabaseCursor
fetch next from CleanDatabaseCursor into @ObjectName, @ColumnName

While @@FETCH_STATUS = 0
BEGIN

SET @query = 'UPDATE [' + @ObjectName + ']' + ' SET [' + @ColumnName + '] = REPLACE(CONVERT(varchar(8000), [' + @ColumnName + ']), ''[Text added via a SQL injection attack]'', '''')'
EXECUTE (@query)
FETCH NEXT FROM CleanDatabaseCursor INTO @ObjectName, @ColumnName

END
CLOSE CleanDatabaseCursor
DEALLOCATE CleanDatabaseCursor

Posted by vbullinger

Copying Files from the GAC

We've all installed files in the GAC before, (right?) but how do you copy files FROM the GAC?  I found a lot of little things here and there while Googling it, but nothing about copying a lot of files.  I needed to copy a bunch of files and copying each file, one by one, takes a long time.  I wanted one action to take care of all of it.  The directory c:\windows\assembly doesn't allow you to copy-paste out of it.  In order to pull files out, you have to use the command line.  If you did, you'd notice that the directory structure is a bit different from what windows file explorer would tell you.  After a little tinkering, I came up with this:

To pull out all files in the GAC, run this at this location:

C:\WINDOWS\assembly\GAC_MSIL>xcopy *.* C:\GACDLLs\ /s /r

The exact subdirectory of assembly might change.  Check first.  It might be GAC or GAC_32.  Look for the files you want first, as you might have more than one of the subdirectories I listed above (I had all three).

Very cool.  Saved me a lot of time.

Posted by vbullinger
Filed under: ,

Modifying Queue Views in CRM

I was recently asked to change the queue views for a client.  At first I thought, "hey, wait a tic... that's just a normal view that any old CRM customizer can modify, right?"  And went directly to the customizations...  No luck.

Apparently, they don't want you to be able to change the queue views.  Queues are not customizable entities.  So, I went into the actual page in the CRM web application (the aspx code) to see what was under the hood.  I didn't have access to the compiled C# code, but maybe I could do something on that actual control.  On the page, I found the queue view's grid, but my heart sank when I found out the entirety of the queue view came from a compiled dll!  I.e. there was no way I could get to the page/control where the actual grid was put together.

I shook my head and gave a ridiculous estimate to the client because of the insanity of what I was going to have to do in order to modify this queue.  Oddly enough, they accepted this lofty estimate.  I kind of hoped they wouldn't, because I didn't want to have to do what they were asking :)

They wanted three major changes: they wanted numbers next to the queues that said how many items were in the queue (like the numbers next to your folders in Outlook that say how many unread items you have in them); they wanted certain queues to be shown to certain people; and they wanted the queue view to have more columns (it only starts with three and offers a very limited amount of information, almost useless, really).

Keep in mind I have no way of accessing any code that does any of what you get out of the box: it's all compiled and accessed through a dll.  Microsoft would never let that code out.

So, what did I do?  I'll tell you:

Step one: putting numbers next to the queues.  I wrote a web service that grabs all the queues available to the logged in user in one method and counts up the number of items in a given queue in another.  So, I parse through the html returned by the dll and then send the queue name (from some TD in some table somewhere in the code) to the second method mentioned before and inject some text in the TD's innerText.  Wow, that's hacky.  But, hey, it works, and the client loves it!

Step two: showing only the queues you want to show to certain people.  Ronald Lemmen had a great blog that listed some JavaScript methods that allow you to get the user ID of the logged in user, get their roles and then test whether or not the user is in a certain role.  That post is located here.  So, that wasn't too difficult to modify to what I needed it to do.  The way I did it was pretty slick, though.  I put together all the roles and queues in two arrays, then created a JavaScript object and assigned queues to roles that way.  An example would be below, granting access to the first five queues to the System Administrator.

var roleNames = new Array("System Administrator", "CEO-Business Manager", "CSR Manager", "Customer Service Representative", "DH CSR", "Marketing Manager", "Marketing Professional", "Sales Manager", "Salesperson", "Scheduler", "Schedule Manager", "SRS Management", "System Customizer", "Test CRM", "Vice President of Marketing", "Vice President of Sales");

var queueNameArray = new Array("DH Engineering", "DH PM", "DH Pro Support", "DH QA", "DH Sales", "DH Support", "DH Training", "IFIX Acctg", "IFIX Dispatch", "MPi Accounting", "MPI Engineering", "MPI Fulfillment", "MPi Implementation", "MPi Installation", "MPi Pend Rel", "MPi PM", "MPi QA", "MPi Support", "MPi Tech-Ops", "MPi Trainers");

var roleSecurities = new Object();

roleSecurities[roleNames[0]] = new Array(queueNameArray[0], queueNameArray[1], queueNameArray[2], queueNameArray[3], queueNameArray[4]);

I then hide all queues and only show them when the user has access to them.

Step three: modifying what columns are in the queue view.  Normally, you would go to the customizations section and just have at it.  The grid views are usually customizable.  For some reason, Microsoft must not want you to modify the queue view.  I had given a large estimate, time-wise, as to how long it would take to get this done, so I set out to do some serious R & D to tackle this problem.  I reeeeeeally didn't want to make another web service to find out what other information was being hidden and then display it.  Injecting a little parenthesized number is quite different from adding columns to a grid view.

I had researched how to get to the queue view and got nothing.  So, I relented and decided to do something in the code.  I needed to see what was available in the queue view, I thought, so I started outputting all the properties of the crmGrid (the ID of the grid on the queue view page).  I had to bring in my own assembly to do this.  I wrote a recursive method to grab the crmGrid and display its properties.  I tried one thing after another and just kept hitting a brick wall

I decided to go back and see if there was anything to be gleamed from Googling modifying the queue view.  I got nothing again, in the way of modifying the queue view, but I did notice this blog.  It gave a way to quickly access views.  It then told about a way to query string hack and get to other views.  I was intrigued, to say the least.

Though the author of the blog did not list the exact queue view number, I knew that there was some JavaScript that was looking at the queue view ID on the page I was working on (Workplace/home_workplace.aspx):

function nodeSelect( sQueueId, sViewId, sMenuId )
{
crmGrid.SetParameter("viewid", sViewId);
crmGrid.SetParameter("qid", sQueueId);
crmGrid.Reset();

resetMenuItems(sMenuId);
}

So, I just alerted sViewId :)

So, the URL you need to modify the queue view is... http://crm/tools/viewEditor/viewManager.aspx?id={00000000-0000-0000-00AA-000010001400}.  Obviously, change "crm" to be your CRM web application URL.

I like obliterating estimates :)

Here's what a normal queue view looks like:

Normal Queue View

Here's what it looks like with some numbers next to the queues:

Queue View With Numbers

And here's what it looks like with all of the columns being shown (most can only be seen when you scroll over, and this is a screenshot so you can't do that, but you get the idea):

Queue View With All Columns

Happy queue view hacking!

Posted by vbullinger | 8 comment(s)

Adding Transparency to SWFObjects

I was asked to add a lot of Flash to a website recently.  This website was a content-managed solution (our own content management solution), so I thought "hey, instead of them asking me to add flash movies everywhere, why not allow them to do so, very easily?"  So, I used Geoff Stearns' SWFObject and packaged it up so that the user would just need to point to which file they wanted it to play, the width and the height of the video.  Worked pretty well.  The SWFObject removes the box around the Flash and the "alt" text that prompts you to click the Flash or press spacebar to activate and use the video.  Then, the client wanted to put a Flash object directly below the main menu, which had drop downs.

The drop down menus hovered BEHIND the Flash object!  Needless to say, this was unacceptable.  I Googled it for a while, and found that most people who had this problem were abandoning SWFObject and going with custom code.  I thought that was silly.  So, I figured out the fix (you need to tell it to be transparent) and decided to jump into Geoff's code.  It was a little difficult to navigate around, but when you find the areas to change, it's pretty easy to figure out what to do.

You have two things you have to do.  1) Go into line 60 of the swfobject.js (I have version 1.4), which looks like this:

_19="<embed type=\"application/x-shockwave-flash\" src=\""+this.getAttribute("swf")+"\" width=\""+this.getAttribute("width")+"\" height=\""+this.getAttribute("height")+"\"";

And add this attribute to the embed object:

wmode='transparent'

For a finished product of:

_19="<embed wmode='transparent' type=\"application/x-shockwave-flash\" src=\""+this.getAttribute("swf")+"\" width=\""+this.getAttribute("width")+"\" height=\""+this.getAttribute("height")+"\"";

After line 69, which should look like this:

_19="<object id=\""+this.getAttribute("id")+"\" classid=\"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\" width=\""+this.getAttribute("width")+"\" height=\""+this.getAttribute("height")+"\">";

Add this line:

_19+="<param name='wmode' value='transparent' />";

That's it!

Now, if that's something you'll only be doing once, and you don't want your other instances of SWFObject to be transparent, then don't change the JavaScript code!  When creating the SWFObject, you would normally see code like this:

var so = new SWFObject('someurl', 'mymovie', 'somewidth', 'someheight', '7', '');
so.write('mydiv
');

If you want just one instance (or a few instances) to have transparency, just add this line of JavaScript before the write statement:

so.addParam('wmode', 'transparent');

Posted by vbullinger | 2 comment(s)

CRM JScript Bug

Sure, we all know how to write comments in JavaScr... I mean "JScript" - you just put this before your one-line comment: //.  Well, recently, I had a strange problem with some JScript.  I knew it was 100% correctly written, but it was throwing errors, saying I was missing an end bracket - }.  Weird, I thought, as I closed all my if/else statements and all my functions that I defined, (you know how to do that in CRM, right?  crmForm.MyFunctionName = MyFunctionName(){//stuff} and then you can use it throughout the rest of that same form's events - even on field events - by saying crmForm.MyFunctionName().) so I was really in a quandary.  I looked at the last line in my event handler, and it was a comment.  I.e.

 // The previous line did whatever

So, I moved that comment above the aforementioned line, and it worked!

So, what's the bug mentioned in the title?  You can't end your JScript event handlers with a comment, even if it's at the end of a line of code.  I.e. this is illegal:

alert("Hello, world!");
// Say hello to the world

As is this:

alert("Hello, world!"; // Say hello to the world

Just say this:

// Say hello to the world
alert("Hello, world!");

Pretty lame, I know, but it should save some readers some headaches.

Posted by vbullinger | 3 comment(s)

IFrame Print Styles In CRM

I've written a few IFrames to help extend CRM in the past, but recently, an issue with them was brought to my attention that no one had ever mentioned to me (and, therefore, I'd assume that no client had ever complained about, either).

A client had tried to print a page that had an IFrame I developed on it.  Instead of whatever was in the IFrame, there was nothing.  I.e. my IFrame was hidden.  Immediately, I realized that IFrames must be hidden in CRM.  Whether on purpose or by accident (on Microsoft's part), I had to get this resolved.  All my hard work was being hidden by CRM!

So I parsed through the myriad of pages and style sheets that CRM installs on the server, looking for how it interprets IFrames, and found this in one of the style sheets:

IFRAME.custom
{
width:    100%;
height:    100%;
border:    1px solid #7b9ebd;
behavior:   url(/_forms/controls/IFRAME.htc);
}

All IFrames get the class "custom" by default.  So, all I had to do was go into the print style sheet and put something awfully similar: 

IFRAME.custom
{
width:    100%;
height:    30px;
border:    1px solid #7b9ebd;
behavior:   url(/_forms/controls/IFRAME.htc);
}

I had to give it a height, because it seems to make the height 0% or 0px, though I don't know where that's defined.  Of course, you'll want to put new, print-oriented styles in your style sheet you use for the IFrame.  For this IFrame, I was creating what looked like a lookup for contacts, so I had these styles (yours may vary):

input
{
 font-family: Tahoma;
 font-size: 11px;
 border-color: White;
 border-width: 0px;
 color: Black;
}

DIV.lu
{
 height:    0px;
}

TABLE.lu
{
 height: 0px;
}

IMG.lu
{
 height: 0px;
}

This will set the linked text (the contact's name) to black, the search button to being invisible and all the other stuff surrounding the search button to being invisible.  You don't want it to looked like a linked text box, you want it to look like plain, black text.

It doesn't recognize "media='print'" in your style sheet reference, btw, so you'll have to do something like I did:

   <script type='text/javascript'>
    var parentLocation = '' + window.parent.location + ''
    if(parentLocation.indexOf('/print/') >= 0)
    {
    // CSSReference is your <link> tag to your style sheet
     var cssReference = document.getElementById('CSSReference');
     if(cssReference != null)
     {
      cssReference.href = "MyStyleSheetForPrinting.css";
     }
    }
   </script>

So, from now on, when you make an IFrame in CRM, you should consider all of this or your IFrames will not print at all.

Posted by vbullinger | 2 comment(s)

Search Engine Friendly Query Strings

I recently was asked to develop something for a client that seemed a little ill-conceived.  This is a very particular client who is very involved with his work.  He knows exactly what he wants and is very organized.  Usually, I just assume he's got the right idea and code away.  He seems like a pretty savvy business man.  But this time, it was a bit different.  I felt there was a better way.

He wanted to have a list of products he could manage through our content management system and link each product to a different webpage where he would add content through the same content management system about the product.  Each product would have its own page.  Ew.

I told the project manager that it would be better if we just had one page for the categories and one page for the products.  You would pass an id of the category into a query string for the category page and a product id into a a query string for the product page.  Much easier for the client.  The project manager told me that the client didn't care about things like that because he was more concerned about search engine results than his time.  I replied by saying that it didn't matter: search engines crawl dynamic URLs on pages.  The project manager wasn't too certain, and I wasn't really an "expert" on the subject, either, so I kind of tabled the decision and decided to look it up a bit.

The very first page I found, located here, explained it really well.  The article states that, though search engine spiders do crawl through sites, they have some serious - and very understandable - limitations.  The synopsis of the important points about query strings are:

  • Keep them short. Less variables gain more visibility.
  • Keep your variable names short, but do not use 'ID' or composites of entities and 'ID'.
  • Hide user tracking from search engine crawlers in all URLs appearing in (internal) links. That's tolerated cloaking, because it helps search engines. Ensure to output useful default values when a page gets requested without a session ID and the client does not accept cookies.
  • Keep the values short. If you can, go for integers. Don't use UUIDs/GUIDs and similar randomly generated stuff in query strings if you want the page indexed by search engines. Exception: in forms enabling users to update your database use GUIDs/UUIDs only, because integers encourage users to play with them in the address bar, which leads to unwanted updates and other nasty effects.

These are pretty important rules I'll remember for all my consulting days.  The website, http://www.smart-it-consulting.com/, sounds like a pretty nice site for us consultants to check out in general :)

Posted by vbullinger | with no comments

Accessing Parent DataItem of a Child DataItem

We've all iterated through some kind of repeater (datalist, datagrid, an ACTUAL repeater) and used the DataBinder to display a property of the object bound to the current DataItem without wasting time writing an ItemDataBound event handler:

<asp:Repeater>
   <ItemTemplate>
      <%#DataBinder.Eval(Container.DataItem, "PropertyName")%>
   </ItemTemplate>
</asp:Repeater>

But have you ever been iterating through a Repeater WITHIN a Repeater and wondered: "gee, how do I access the outer DataItem for display within the inner DataItem's template without writing a complicated ItemDataBound event handler?"  I.e.

<asp:Repeater ID="_outerRepeater">
   <ItemTemplate>
      <%#DataBinder.Eval(Container.DataItem, "PropertyName")%>
      <asp:Repeater ID="_innerRepeater">
         <ItemTemplate>
            [Outer Repeater DataItem]
            <%#DataBinder.Eval(Container.DataItem, "PropertyName")%>
         </ItemTemplate>
      </asp:Repeater>
   </ItemTemplate>
</asp:Repeater>

Well, I wondered just that the other day.  I asked around to see if there was a shortcut anybody around inetium knew, to no avail.  Seems everyone thought it should be easy, but they had never thought to attempt it before.  Googling was a bit challenging for this task (what would you put in the search box?), so I begain tinkering in code, using asp.net 2.0 in Visual Studio 2005 for increased intellisense and figured out this solution:

<asp:Repeater ID="_outerRepeater">
   <ItemTemplate>
      <%#DataBinder.Eval(Container.DataItem, "PropertyName")%>
      <asp:Repeater ID="_innerRepeater">
         <ItemTemplate>
<%#((System.Web.UI.WebControls.RepeaterItem)Container.Parent.Parent).DataItem%>
            <%#DataBinder.Eval(Container.DataItem, "PropertyName")%>
         </ItemTemplate>
      </asp:Repeater>
   </ItemTemplate>
</asp:Repeater>

The container is what holds the DataItem (in an event handlers, that'd be e.Item I'd guess) and it's parent is the inner repeater.  So you'll need the inner repeater's parent, the outer repeater's DataItem.  Seems to call the ToString() method of it, so it's limited, but that's exactly what I wanted (I was binding the outer repeater do directories and the inner repeater to files inside of that directory, so the point was to get a link to [Directory Name]/[File Name].

Posted by vbullinger | 7 comment(s)

Writing A CRM Callout Assembly: A How To

I was recently asked to write a callout assembly to be attached to a client's CRM workflow service.  A callout assembly (in this regard) is an assembly that is attached to the CRM workflow service and is sort of like an external event handler.  So when someone adds/updates/deletes records in CRM, the callout assembly is fired.  I had never done it before, but I thought it wouldn't be too much of a problem.  There are some samples in the CRMSDK and I was sure that searching on Google and MSDN would yield the answers to any questions I'd have.  I was wrong.

There isn't too much help in either of those two sources, as I found out the hard way.  This possibly isn't the most common thing in the world to do, I'm guessing.

The problem I had with the "readme"s in the callout samples given in the CRMSDK is that they just blindly say "do this" and "do that," without any explanation of what's going on.  They also missed a step, as far as I can see.  They don't explain the callout.config.xml file at all.  I'll attempt to explain things, step-by-step, since I don't really see a definitive explanation of how these callout assemblies work anywhere (I've seen similar posts on other blog sites with less information, however).

Step 1: create a blank solution with a class library project inside.

Step 2: add a web reference to your CRM web service.  It should look something like this: http://<yourserver>/mscrmservices/2006/CrmService.asmx, as the readme got correct.  If you need to login, you better have all that information handy, as you'll need it later.  This information will include: a) user name b) password c) domain d) CRM server.

Step 3: add a reference to the Microsoft.Crm.Platform.Callout.Base.dll file.  If you need to download that, you should do so.  I had to.  This is necessary for the next step.

Step 4: create a class for your callout that extends the CrmCalloutBase class.  You will need the dll from the previous step to do this.  Make sure this class has "using Microsoft.Crm.Callout;" in the using directives for the base class and "using <yourassemblyname>.<yourwebreferencename>;" for the web service.  If you are ever going to need to connect to the web server (say, if a child object is updated/added/deleted, the parent needs to update its information, for example), then you'll need the user name, password, domain and CRM server information from before.  You'll need this code to access the CRM web service:

CrmService service = new CrmService();
service.Url = <CRM_SERVER> + "crmservice.asmx";
service.PreAuthenticate = true;
service.Credentials = new NetworkCredential(<CRM_USER>, <CRM_PASSWORD>, <CRM_DOMAIN>);
WhoAmIRequest userRequest = new WhoAmIRequest();
WhoAmIResponse userResponse = (WhoAmIResponse)service.Execute(userRequest);

And your service is now ready to use.  I.e. if a child's information is edited, you may want to use the web service to update the parent.  This may be the whole reason for creating a callout assembly.

Step 5: decide what events you want to listen into.  Do you want to react to whenever a case is submitted?  Do you want to react to whenever someone updates their contact information?  Do you want to check submitted information before it gets submitted?  Whatever it is, there are plenty of things to listen into.  To find out what you can listen to, inside your class, start a new line (with intellisense enabled) and type override and then a space to see what possibilities you have.  For quick reference, there's pre- and post-:

  • Create
  • Update
  • Delete
  • Assign
  • Set state
  • MergePersonally
  • PreSend
  • PostDeliver

Step 6: implement the code you want to fire when your event is triggered.  You can override a bunch of methods (all of them, actually) in the same callout class, so feel free.  I overrode three in mine.  I don't quite understand why the entity context is passed, as you can set your event to only listen for when an event is fired for an entity of a particular type.  I personally don't have any code in my callout I just wrote that worries about what type of entity I'm dealing with.

Step 7: create the callout.config.xml file.  Assuming you're using CRM 3.0, you should have this line of code as your second line of code instead of the ones in the CRMSDK samples: "<callout.config version="3.0" xmlns=" http://schemas.microsoft.com/crm/2006/callout/">" or your config file might not be recognized.  Inside the callout config file, you'll need a <callout> node for each event/entity combination you are listening into that looks like this: <callout entity="myentityname" event="Post/Pre<see above bulleted list for ideas>"> with an optional onerror property with the value of "abort" or "ignore."  Inside this node, you'll need a subscription node that looks something like this: <subscription assembly="myassembly.dll" class="myfullnamespace.myclass(from step four)">.  Inside this node, you'll need one or more <postvalue> nodes IF you are listening in on a "Post" event (not a "Pre" event).  Each of these <postvalue> nodes needs to have the name of a property of the entity you are going to work with in your code.  For example, if you working with an incident entity and want to look at the ticket number in your callout assembly's class, you'll need this inside your subscription node: <postvalue>ticketnumber</postvalue>.  One short cut, if you're going to use a lot of properties, or an expanding number of properties (in case your assembly will be modified as time goes on) or if you're just really lazy is to put this in your subscription: <postvalue>@all</postvalue>, and you'll then get all of the properties.  That's what I did.  Mainly because I knew that the objects don't have a lot of properties.  I would also suggest writing to a log file throughout your code so you can debug more easily.

Step 8: compile your code and attach it.  Attaching it is pretty tricky, so make sure you pay close attention.  I am not sure you'll need to do all of these things, but I did, so here's what should work for sure.  First, go into the services console (of the CRM server...) and stop the Microsoft CRM Workflow Service.  Next, if you've already deployed a previous version of your callout assembly, RENAME IT.  It won't let you delete it, but you can rename it...  Then, move (the new version of) your callout assembly to the \Program Files\Microsoft CRM\Server\bin\assembly folder.  Then, restart IIS.  I'll wait...  Ok, now start the Microsoft CRM Workflow Service.  Delete the old assembly (the one you renamed) if this isn't the first time you deployed it.  IF YOU MAKE ANY CHANGES TO THE DLL OR THE CALLOUT.CONFIG.XML FILE(S), YOU MUST REPEAT STEP 8 OR IT WON'T TAKE EFFECT!

That should do it!  Now, if you have trouble getting into the workflow or think it's too restrictive/not powerful enough, you can write C# code (or VB code, if you are so inclined) that can do whatever you want and then just attach it to the workflow process using the process outlined above.  Happy calling out!

More Posts Next page »