27 January 2009

jGrowl

I started developing a web application that needs a sophisticated notification system. It turns out the actual notification message is the easy part since jGrowl does all the work for me.



Here's all you have to do to get jGrowl to work:



Download jGrowl.

Copy the 3 files into your web site directory:



jquery-1.2.6.js (or your newer or minimized version of jQuery)

jquery.jgrowl.js (or the minimized version)

jquery.jgrowl.css



(I put the 2 .js files in /scripts and the .css file in /styles.)



Link to the 3 files like so:



<link href="styles/jquery.jgrowl.css" rel="Stylesheet" type="text/css" />
<script src="scripts/jquery-1.2.6.js" type="text/javascript"></script>
<script src="scripts/jquery.jgrowl.js" type="text/javascript"></script>

Then within any Javascript file, you should be able to call jGrowl:



$.jGrowl("This is a jGrowl notification.");

Also, the notification text was blue for me, which didn't look to good on a black background. I fixed it by adding the following CSS to my main CSS file:



div.jGrowl div.message
{
color: #fff;
}

24 September 2008

More Navigation

I recently designed a navigation bar that meets the following criteria:

Cross-browser support (IE, Chrome, Firefox)
Scriptable (add/change links by only changing HTML, can create an ASP .NET or other server control with it)

The design I came up with looks like this:



The images are replaceable to match whatever theme. Sizes and so forth can be adjusted in the CSS.

Here's the HTML layout:

<div class="subNav">
  <div class="line"></div>
  <table class="links" cellpadding="0" cellspacing="0">
  <tr><td>
    <a href="#">
      <span class="left"></span>
      <span class="mid-wrapper">
        <span class="mid"></span>
        <span class="text">Text</span>
      </span>
      <span class="right-mid"></span>
    </a>
  </td><td>
    <span class="divider"><span>
  </td><td>
    <a href="#">
      <span class="left-mid"></span>
      <span class="mid-wrapper">
        <span class="mid"></span>
        <span class="text">Text</span>
      </span>
      <span class="right-mid"></span>
    </a>
  </td><td>
    <span class="divider"><span>
  </td><td>
    <a class="selected" href="#">
      <span class="left-mid"></span>
      <span class="mid-wrapper">
        <span class="mid"></span>
        <span class="text">And More Text</span>
      </span>
      <span class="right"></span>
    </a>
  </td></tr>
  </table>
</div>


It is a lot of HTML, compared to some other ways to do it, but it doesn't have any weird, oversized target areas, and it is very scriptable.

Here's the CSS (for transitional doctype):

.subNav
{
  position: relative ;
  width: 100% ;
  height: 32px ;
  text-align: center ; /* centers in IE */
}

.subNav .line
{
  position: absolute ;
  left: 0 ;
  top: 15px ;
  height: 1px ;
  border-top: 2px solid #aaa ;
  width: 100% ;
}

.subNav .links
{
  position: relative ;
  top: 0 ;
  margin: 0 auto ; /* centers in firefox and chrome */
  text-align: center ;
  vertical-align: bottom ;
}

.subNav .links a
{
  position: relative ;
  display: block ;
  text-decoration: none ;
  color: #000 ;
  height: 32px ;
  padding: 0 4px;
}

.subNav .links a:hover
{
  cursor: pointer ;
}

.subNav .links a .left
{
  position: absolute ;
  display: block ;
  left: -4px ;
  width: 8px ;
  height: 32px ;
  background: url(subNav-grey-left.png) no-repeat ;
}

.subNav .links .selected .left
{
  background: url(subNav-blue-left.png) no-repeat ;
}

.subNav .links a:active .left
{
  background: url(subNav-blue-left.png) no-repeat ;
}

.subNav .links a .right
{
  position: absolute ;
  display: block ;
  top: 0 ;
  right: -4px ;
  width: 8px ;
  height: 32px ;
  background: url(subNav-grey-right.png) no-repeat ;
}

.subNav .links .selected .right
{
  background: url(subNav-blue-right.png) no-repeat ;
}

.subNav .links a:active .right
{
  background: url(subNav-blue-right.png) no-repeat ;
}

.subNav .links a .left-mid
{
  position: absolute ;
  left: 0 ;
  top: 0 ;
  display: block ;
  width: 4px ;
  height: 32px ;
  background: url(subNav-grey-mid.png) repeat-x;
}

.subNav .links .selected .left-mid
{
  background: url(subNav-blue-mid.png) repeat-x;
}

.subNav .links a:active .left-mid
{
  background: url(subNav-blue-mid.png) repeat-x;
}

.subNav .links a .right-mid
{
  position: absolute ;
  right: 0 ;
  top: 0 ;
  display: block ;
  width: 4px ;
  height: 32px ;
  background: url(subNav-grey-mid.png) repeat-x;
}

.subNav .links .selected .right-mid
{
  background: url(subNav-blue-mid.png) repeat-x;
}

.subNav .links a:active .right-mid
{
  background: url(subNav-blue-mid.png) repeat-x;
}

.subNav .links a .mid-wrapper
{
  position: relative ;
  display: block ;
}

.subNav .links a .mid
{
  position: absolute ;
  display: block ;
  height: 32px ;
  width: 100% ;
  background: url(subNav-grey-mid.png) ;
}

.subNav .links .selected .mid
{
  background: url(subNav-blue-mid.png) ;
}

.subNav .links a:active .mid
{
  background: url(subNav-blue-mid.png) ;
}

.subNav .links a .text
{
  position: relative ;
  top: 8px ;
  font-size: 14px ;
  font-family: sans-serif ;
  line-height: 16px ;
  vertical-align: top ; /* makes it v-centered in IE */
  padding: 0 8px ;
}

.subNav .links .divider
{
  display: block ;
  height: 32px ;
  width: 1px ;
  background: url(subNav-divider.png) no-repeat ;
}


As you can see, everything to do with the layout is defined in the CSS, from sizes to images.

23 June 2008

MMO Corruption

I have no doubt that corruption exists in some form in every MMO out there. See, it's a double-edged sword. Either the people who make and maintain the game have no interest in playing it, and then they can easily miss the mark on what's fun. Or the GMs and Devs are avid gamers and as a consequence probably make a pretty fun game, but they daily are tempted to put their self-interest above their integrity by cheating. And there are shades everywhere in between those two.

But I had to laugh the other day when I read about the EverQuest 2 dev "scandals" that happened months ago. There were a couple of instances where a dev told someone how to defeat raid encounters.

Lol, is that the best you can do?!

Heck, in Eve, a GM or Dev spawned items on the Live server and let their corp profit off of them for many months. It was discovered by Eve staff, and nothing was even done about it. And then when it was discovered and forced to the forefront by an (arguably malicious) player, that player got banned, any mention in the forums of it got you banned or moderated, and the Employee in question was not even fired.

Granted, enough people were outraged by the cheating that CCP now has a player-elected representative visit Iceland to inspect the works. However, I'm not so confident in most any player's ability to detect cheating by the game's own developers, lol. But anyway it was a nice gesture, and a sign that perhaps they will take cheating a bit more seriously next time.

So anyway, a GM telling someone how to defeat an encounter or use a pathing bug to defeat a mob was bad, but ridiculous to get too hot and bothered over compared to those things.

I thought the more heinous thing that the EQ2 GM did was to quote word-for-word someone's bug report who was reporting the pathing bug, and tell their friend to use that to defeat the mob. I can't imagine the horror on the face of the guy who submitted that bug report if they ever saw how the GM used it instead of actually fixing the bug. That GM gets bonus evil points in my book.

28 February 2008

Eve Online: Hating to Love It

Eve Online is a space-based MMO that I have played it for several years.

One of the hallmarks of Eve Online is freedom. You are free to be just about as good or as evil as you want to be. In some ways, the game encourages you to be evil. In all cases the denizens of the game punish naivete with impunity, and the game encourages cut-throat capitalism.

The major draw of the game is purportedly PvP combat (even though only 19% of the game's population actually visit lawless space, according to CCP) -- blowing up other players for the sheer enjoyment of it. However, unlike other games, loss of a ship or death of your character has a potentially significant cost in the form of your time. In most PvP games, the death of one's character carries a negligible penalty. In Eve, you have to replace that ship, that clone, those enhancements. And that costs money (in-game), potentially a lot of money, which represents a significant time investment in the game. Smaller ships cost maybe a few hours of your time to earn back the money and buy and refit the ships. Larger ships can cost weeks or months of your time to recoup a loss. The largest of ships can take the efforts of an entire corporation or alliance many months to recoup. Character enhancements can potentially take weeks of effort to recoup a loss on top of ship loss.

Outsiders might wonder why you would play a game where one's time was wasted so wholesale. Well, Eve does have some less risky areas known as "high security space" (hi-sec). Anyone who attacks your ship unprovoked in hi-sec will quickly get their ship blown up by the police. It seems that most of the Eve Universe stays in hi-sec according to the same report from CCP.

However, hi-sec is not without risk. You are liable to be openly attacked if you carry very valuable cargo in an indefensible ship. The attackers will lose their ship, but you will likely lose your cargo to other players that scavenge your ships remains. They can scan your cargo hold before hand to make sure they will gain more than they lose. Hi-sec is also subject to corporate wars. Essentially a corporation or alliance may pay off the police to look the other way while you fight each other. The vast majority of hi-sec corp wars are started by small mercenary corps who are paid to do so or think your corp is a juicy enough target to profit from. Essentially corp wars are a version of police bribery and gangster-style "protection". Also, hi-sec is subject to scams of various kinds. Sometimes CCP (the makers of the game) punishes scams, but often times not. There are a lot of "scams" on the market where people count on your ignorance or mistakes (e.g. in one click, you've sold something for 500 thousand that you meant to or could have sold for 500 million). More often than not, the scams involve other people blowing up your ship. Either by using the market to lure you to low security space, or convoluted methods using the poor or non-existent warning indicators to mask a war that other people in a gang are involved in, making you also a valid target of their war (and said war was contrived only to enable the scam in the first place). [Edit: After many years of people being taken advantage of by poor gang mechanics, CCP finally decided to fix it with a recent patch.] There are also internal threats, corporate espionage, etc. If you make someone a director of a corp, and that person leaves the corp, taking every corporate asset with them, the GMs standard response is a politely-worded "Sucks to be you."

Eve is a very cold place. Being naive is the quickest path to losing something. Trust is never assumed. The game publishers seem to thrive on the whines of the swindled. Being cruel can be a profitable business. The worst parts of the real world are the worst parts of Eve, but in Eve they seem to be more pronounced because the game allows it, the consequences are light, and the people less inhibited.

Eve Online is a game that I hate to love. I hate the fact that Eve enables people to try to wreck the gameplay of others like no other game that I've played. The popular excuse for this is: "Eve is a pvp game". In reality, this sentiment should read "Eve is a griefer's game". Despite itself, Eve still manages to have a draw on non-maligned people. Maybe it's the market, or the skill system, or the open-ended nature of the game, or the free expansions, or the fact that you can play for free using in-game money, or the friends. Whatever it is, it's a fragile balance, and I hope one day the game moves further away from being a griefer's game.

04 December 2007

PNGs - One Image To Rule The Navbar

Graphics for the navigation bar take up 181 bytes of space
A realization struck me today. With PNG files, I can create one tiny image for an entire navigation bar, including mouseovers. Specifically, I was thinking how often I only want to change a color on mouse-over, which has normally meant a separate image, but no more. And it gets better... using this one image method, I can even dynamically change the color of the "image" with CSS or Javascript if I need to. Did I mention it's cross-browser compatible?

The One Image Way

A lot of folks tired of dealing with the tedium of image-based navigation buttons are probably already using the tileable background approach. This is where you create a small sliver of a background and repeat it in the x direction (for horizontal navigation bars), and place your text-based links on top. One Image takes that a step further.

Create the tileable background, but design the image with a solid background color and layer your effects on top of it rather than integrating blends and gloss into the background. Then delete the background color and save only the effects. (This takes some practice if you're not used to it.) Then, in the CSS background property, specify the image as well as the background color, like so:


...
background: #000 url(menu-fx.png) repeat-x ;
...


Now, if you want to change the hover, use the same background image, but only change the color:


...
background: #025 url(menu-fx.png) repeat-x ;
...


Working Example

To see an example of this in action, check out this page. The total amount of space taken up by graphics for this navigation bar is 181 bytes! And I can change the color at design time without saving out a new image (by specifying a different color in the CSS). And I can even change it dynamically with client-side script. If I do have to make changes to the shading or lighting, I only have to save down 1 file, not one file for every color.

Why It Works

This works with PNG files, but not GIFs, because PNG saves partial-transparencies. Those partial transparencies will be blended at run time with whatever color is behind it. So, in our case, the light effects blend at run time with the background color we specify. Contrast with GIFs, which saves transparent or not transparent, but no in-between. So, you have to know ahead of time the color of the background. If you later change background colors, you have to re-save the image with the proper blending around the edges or it will have noticeable distortions around the transparent parts.

Small Caveat

PNGs do not render transparency correctly by default in IE 6 and below. However, there's a fairly easy fix for this. If you follow that site's directions for inserting the script it will only run on browsers that need it. It won't even get loaded on the others! The script essentially invokes a Microsoft proprietary transform (filter:progid:DXImageTransform.Microsoft.AlphaImageLoader, for the twisted among you that has to know) which correctly renders the image.

There are other scripts out there that accomplish this same thing, but this is the first one I ran across, and it has worked like a charm for me.

What's Next

One day, I'd like to see a cross between a programming API and an image format where I can store all my variations of a graphic in one file and change attributes of it at run time (like background color, or size) as needed for the task at hand through client-side code or CSS. It will require some intelligent design and layering on the part of the graphic artist, but it's reuse value will be extremely high.

29 November 2007

ASP .NET 2.0 User Controls

I'm struggling to find a good use for User Controls.

All I wanted to do was wrap some standard web controls (like a login box) in a pretty container. .NET's default theming is insufficient for this, as it allows you to theme certain properties, but doesn't allow you to customize the control to any large degree. For instance, what I wanted to do was create a pretty table-based box, which is about 19 lines of normal HTML/ASP:


<table class="prettyBox" cellpadding="0" cellspacing="0" border="0">
<tr>
<td class="nw"><br /></td>
<th class="n"><asp:Label ID="title" runat="server" Text="Label"></asp:Label></th>
<td class="ne"><br /></td>
</tr>
<tr>
<td class="w"><br /></td>
<td class="contents">
<asp:PlaceHolder ID="content" runat="server"></asp:PlaceHolder>
</td>
<td class="e"><br /></td>
</tr>
<tr>
<td class="sw"><span></span></td>
<td class="s"><span></span></td>
<td class="se"><span></span></td>
</tr>
</table>


And be able to specify them like so:


<uc:prettyBox ID="pb1" runat="server" Title="Log In">
<asp:Login ID="login1" runat="server"></asp:LoginControl>
</uc:prettyBox>


or even:


<uc:prettyBox ID="pb1" runat="server" Title="Log In">
<ContentTemplate>
<asp:Login ID="login1" runat="server"></asp:LoginControl>
</ContentTemplate>
</uc:prettyBox>


I was able to get the latter to work to a very limited degree, but it had enough limits that it wouldn't work for me.

First, I looked at a templated control from MSDN. Unfortunately, Microsoft's idea of a templated user control revolves more around the data than the UI, so this example is no help.

Another way is found here, and it correctly renders the controls inside the PlaceHolder, but in VS 2005, the designer refuses to keep tabs on any controls inside my user control, so without some manual accounting on my part (i.e. declaring the controls as page variables manually), I lose programmatic access to any controls inside the user control, which is unacceptable.

I found that I could user Mr. Seder's code and make the code designer behave correctly by putting these lines over the class declaration of the user control:


[Designer("ContainerControlDesigner")]
[ParseChildren(false)]


But this caused the ContentTemplate to be completely ignored, so all the controls inside my user control would render after the pretty box rather than inside it's PlaceHolder.

The closest working thing I found was this, but that was a lot more hassle than I wanted just to save myself 15-17 lines of code for each box, and all the controls were created dynamically. It's essentially a server control, which defeats the whole point of a user control that you can see more easily from a visual design standpoint.

I find it very frustrating that I can't make a simple user control that wraps HTML around other controls in ASP .NET 2.0. Probably what I'll end up resorting to is making two user controls, one called prettyBoxStart with a title attribute, and one called prettyBoxEnd, both of them being unary tags:


<uc:prettyBoxStart ID="pbs1" runat="server" Title="Log In" />
<asp:Login ID="login1" runat="server"></asp:LoginControl>
<uc:prettyBoxEnd ID="pbe1" runat="server />


It's pretty lame, and it's barely better than an include, but at least it works, unlike all that user control junk.

26 November 2007

Leopard Tricks and Changes

I've installed Leopard a few times now. (2 home machines and 1 work machine twice). Here are some tips from my experience.

P.S. ps is different

[Edit: As of 10.5.1 or 10.5.2, they changed it back to the BSD style command-line arguments.]
Those of you who do linux system admin probably know the ps command. In Tiger, the ps command used BSD syntax (e.g. ps -aux to get all processes, with -e not working), but in Leopard, it seems that the Linux syntax is now favored (e.g. ps -ef to get all processes). The -u flag of ps no longer works and will give a warning.

Build a New Firewall

When I was restoring my laptop, one of the things that did not translate over (probably because it didn't translate from the upgrade) was my firewall. I had to reconfigure the firewall in Leopard. Fortunately, I only had one custom entry, but still...

Restoring From an Unsupported Drive

You may or may not have seen the nice little command that lets Time Machine backup to SMB shares:

defaults write com.apple.systempreferences TMShowUnsupportedNetworkVolumes 1

Well, one of the things that was sketchy about the whole thing (aside from backing up over wireless) was how you're going to restore that backup. I did this and I'll tell you how.

Note: I used the User Migration Assistant, not the Leopard DVD to restore the backup. I played at using the DVD, but couldn't get the defaults write command to work (read-only filesystem of the DVD, I guess), so I figured it was pointless. However, I didn't actually try to mount_smb the filesystem, hoping that Time Machine would already be able see it.

Assumptions:
You used the defaults write command to allow Time Machine to backup to unsupported network drives (e.g. SMB share).
You successfully made a recent backup to the SMB share.

Firstly, you need to do a fresh install of Leopard on the target machine. When you get to the point where it asks you for a username, make sure the new username is different from the other usernames on the old system. I'd do something like tmpadmin (Temp Admin).

Secondly, log in and run the defaults write command to get Time Machine cooperative.

Thirdly, mount the SMB share you want to restore from and double-click the .sparsebundle inside the SMB share to mount it. Then start the User Migration Assistant (under Applications -> Utilities). Select the option to use a Time Machine backup. It may take it a while to pop up the SMB share as an option. Afterwards, select what you want to restore. I selected everything I could check.

Lastly, log out and back in as your normal user (assuming it's an Admin account also). Then delete the temporary account, so you don't have an extra admin account floating around.

I was actually really impressed with this. It even restored a custom /etc/hosts.allow that I created!