Monday, November 17, 2008

Tables for Layout?

I love this

http://shouldiusetablesforlayout.com/

It was a response to a page that claimed that the average time that a web developer took before abandoning CSS for web layout was 47 minutes. Anyone who has worked with me (or watched me work on a web project) will vouch that I have toiled untold hours of my life away, simply trying to get a CSS layout to work. It warms my heart to see that other people are willing to be as fanatical about standards and best practices as I am.

Also, I'll be at the Philadelphia Game Expo this weekend. If you're there, say hi!

Sorry about the lack of posts, I've been working quite dilligently in what little free time I've had, but hopefully some more posts will be forthcoming soon.

Wednesday, October 22, 2008

Lock's Quest Recap

This is sort of a game review, but also a discussion about interface design, and the nature of fun in games. I recently purchased Lock's Quest, which was the best Strategy game at E3, which for a DS title, is pretty impressive. It's developers also put out Drawn to Life, a game that I wanted, but never got around to purchasing. Basically, you're a special boy in a war...blah blah blah, the story isn't really that important, but the game itself is really involving. 


You have so build walls and turrets to fend off increasingly large and tough armies of clockwork robots. That in and of itself sold me, but the DS's touchscreen is really well implemented in both combat and repairing damaged structures. Depending on what you're trying to do, there's an associated task (pulling a lever, spinning a gear, etc) that makes the action you're attempting go faster, or more effective. While this sounds kind of hackneyed and repetitive, they're mixed up nicely, and you don't just sit around for 10 minutes doing the same thing over and over again.

I'm also really impressed with how they set the flow of the game up. After a few rounds with your defenses, you have an impenetrable death-fortress, and you're able to handle even the most vicious onslaughts. Then, the game tells you that you have to go grab an objective all the way on the other side of the map. It's frustrating, yes, but it breaks the whole build-repair-fight cycle, which is nice. It's also worth noting that there's few things more awesome than watching wave after waves of bad guys crash on the rocky cliffs of your defenses.

One thing is infuriating though. The screen doesn't zoom out, and there's a fixed speed to the camera, so panning from one wall to another can sometimes take precious seconds. This in and of itself is not too bad, since (if you're like me) you can set up relatively smaller, close defenses. However, when you pan to a location, and then tap (which tells your character to move there), the AI that keeps the main character from running aimlessly into a tree is a little bit lacking. So far, I have lost a solid number of turrets, and even a level or two, just because there was a rock or tree or bit of something in the way, and the flax-haired hero did nothing except struggle against the properties of matter until I realized and gave him an easier location to get to (after a while, you get used to tapping out smaller, shorter paths).

With that behind me, I do have a few more good things to say about this game. Despite the "ehh" story, the game has a good length to it. I thoroughly enjoy the game mechanics that are in place, and was worried that with all the cool stuff in it, that the levels would be cut tragically short, leaving me wanting more, much like when you when you visit home, and only get a single strip of bacon, because that's "healthier." Don't worry, this game has platters of bacon. I've been playing it for about two solid weeks now, mostly during my commute and at home and I think I'm only 2/3 of the way through it. 

Also, the difficulty is really well done. While I didn't like that there wasn't a standard dial-a-difficulty, which would be pretty easy to do with this game, I was able to clear most of the levels on the first try, though a good number of those were nail-biters, and a couple of them required a do-over. The increases in challenge over time are well-paced. I never felt too bored, and when I was right on the edge, something new popped up. Also, there's a fun little seige mini-game which would be right at home as a flash game.

Monday, October 13, 2008

Using A LIKE Operator With A String Parameter In SSRS

Hey-o,


Just something quick that I figured out today, that I felt warranted sharing. I'm doing some work with SQL Server Reporting Services (SSRS) and the Business Intelligence Development Studio (BIDS), creating reports for work. Today, I was asked to create one that used a LIKE statement in a WHERE clause where the argument passed to the LIKE statement is a string parameter. The challenge here is that concatenating "%"s to the parameter didn't work in the query, nor did the same process in the Dataset -> Filter By tab. The only possible answer I could find was on experts exchange,  which requires a subscription, so I had to figure it out myself. Here's what I did:

  1. Create the String parameter you actually want to search with. Make sure that it can be blank, but don't let it be null. For the sake of the example, I'm calling it Filter.
  2. Create another, hidden parameter that is also a string. Make sure it is hidden! Then, set the default value to be non-queried ="%" & Parameters!Filter.Value & "%". I gave this parameter the name FilterFormatted. Now, hit okay and close out your Report Parameters.
  3. Now, in the query, all you need to do is write "WHERE columntofilter LIKE @FilterFormatted". If you're testing the report, make sure you put the "%" before and after your search string, but going to the "Preview" tab will let you test it the way it'll be deployed (though you should probably know that already).
That's it! Interesting problem, simple fix, solution posted.

Wednesday, September 24, 2008

OpenSocial Version 0.8 App Development From Scratch


With the newest version of OpenSocial out, I felt compelled to revisit it, despite the fact that my new job couldn't have me farther from it. A labor of love, some would say, to stay up-to-date on a constantly evolving platform, like trying to hold onto pudding with your bare hands. Anyways, where was I?

I'm going to develop an OpenSocial 0.8 app from scratch. Let's get started. First, I need to set up the initial scaffold.


<?xml version="1.0" encoding="UTF-8" ?>
<Module>
  <ModulePrefs title="Pandaface">
    <Require feature="opensocial-0.8"/>
  </ModulePrefs>
  <Content type="html">
    <![CDATA[
         <div id="gadgetdiv">
             Panda!
         </div>
    ]]>
  </Content>
</Module>

This app is going to be named "Pandaface," as you can see from the line (  ModulePrefs title="Pandaface"). It's also important to point out the gadgets.util.registerOnLoadHandler line. This sets the Javascript function that will execute when the gadget loads. Usually, one would define this in the body tag of an html page, but declaring it in that manner makes sure that the callback function (in this case gogoGadget, I prefer that over "main") won't execute until all the gadget code and its dependencies are loaded.

Step 2 is setting up the first data request. In my first request, I like to get the OWNER and VIEWER objects. These will allow me to know if the viewer is looking at their own version of the App or someone else's. To do this, gogoGadget is changed to look like this.

function gogoGadget()
{
var request = opensocial.newDataRequest();
request.add(request.newFetchPersonRequest("OWNER"), "get_owner");
request.add(request.newFetchPersonRequest("VIEWER"), "get_viewer");
request.send(response);
};
This creates a request object, adds two FetchPersonRequests, and sends the request out, saying that the function 'response' will deal with the data response. It is important to note that the callback will not execute until it has all the data. Let's lake a took at the response function.

function response(responseData) { owner = responseData.get('get_owner').getData(); viewer = responseData.get('get_viewer').getData(); };

Not terribly interesting at the moment, but it is important to note a few things. In gogoGadget, the last paramter that is given is a name for the specific response. MAKE SURE THESE MATCH UP. In my other OpenSocial adventures, I got that mixed up at one point, and couldn't for the life of me figure out what was going wrong.

Now, let's pull down some persistent data. I'm just going to have a single variable, face_data, and pull it from the orkut sandbox server. To do this, I have to declare another datarequest, and add a newFetchPersonAppDataRequest. Back in .7, you could just pass the user's id, and the name of the app data you wanted to pull. Now, however, you have to create an IdSpec object for it. This is new, and fairly unintuitive (especially to people who've been working with it as long as I have).  Here's the full data request.

var ownerspec = opensocial.newIdSpec({ "userId" : "OWNER" , "groupId" : "SELF"});
var datarequest = opensocial.newDataRequest();
datarequest.add(datarequest.newFetchPersonAppDataRequest(ownerspec,"face_data"),"face_data");
datarequest.add(datarequest.newFetchPersonAppDataRequest(ownerspec,"face_url"),"face_url");
datarequest.send(loadUI);

Make sure to pay attention to what you name the variable, like with the personrequest. You also have to make sure your idspec is constructed correctly. At this point, you can only have "OWNER" and "VIEWER" for "userId", and "SELF" or "FRIENDS" for "groupId." There's also an important thing to note at the 3rd line. The first "face_data" is actually the name of the variable the request is supposed to retrieve. Along with named variables (which return as "null" if they haven't been set yet), you can also just put a *, for all variables associated with your app. On the response side, there is another main difference from the person requests.

function loadUI(responseData)
        {
          var facedata = responseData.get('face_data').getData()[owner.getId()];
          var faceurl = responseData.get('face_url').getData()[owner.getId()];
          var to_output = owner.getDisplayName() + " is a ";
          
          if(facedata == null)
          {
            to_output += "lazy";
          }else{
            to_output += facedata.face_data;
          }
          to_output += " panda.";
          if(faceurl == null)
          {
            faceurl = "http://www.cs.drexel.edu/~asc38/Google/meh.png";
          }else{
            faceurl = faceurl.face_url;
          }
It is important to note how to retrieve the data from the response object. Not only do you have to call get() with the field name, and getData(), but you must also grab the data from the index of the owner's id, and then call the name of the variable off THAT. 

What this does is set default values for facedata and faceurl, and then combines them to make the entire gadget rendering. Now, if you look at my profile, either as me, or as someone else, you see the following:













Not terribly exciting, but you can see what it's trying to do. Now, to add a bit of user functionality. For this app, the only real interactivity is going to be when the owner changes their emotion or the panda's face. To do that, we first have to see if the person viewing the app is the owner. That's pretty intuitive:

if(owner.isViewer())
          {

See? Now, what I do is build an admin panel/form that has a text box for the user to input their emotion, and then radio buttons.

owner_output = '


New Panda Emotion?
';
            owner_output += '
New Panda Face?
';
            owner_output += '
';
            owner_output += '
';
            owner_output += '
';
            owner_output += '
';
            document.getElementById("gadgetdiv").innerHTML += owner_output;
          }

For the sake of brevity, I'm not going to detail both changing functions, but just the important bits of changeFace. You get the value of the new emotion, and then create an updateAppDataRequest and submit it. You still need to specify a callback though. This lets you do error checking to make sure that the request made it through alright.

var updaterequest = opensocial.newDataRequest();
 updaterequest.add(updaterequest.newUpdatePersonAppDataRequest("VIEWER", "face_data", newemotion), "update1");
updaterequest.send(finishUpdate);

For finishUpdate, I just have it mirror the initial data pull, and loop back to loadUI, thus completing a beautiful life cycle.

        function finishUpdate(responseData)
        {
          var ownerspec = opensocial.newIdSpec({ "userId" : "OWNER" , "groupId" : "SELF"});
          var datarequest = opensocial.newDataRequest();
          datarequest.add(datarequest.newFetchPersonAppDataRequest(ownerspec,"face_data"),"face_data");
          datarequest.add(datarequest.newFetchPersonAppDataRequest(ownerspec,"face_url"),"face_url");
          datarequest.send(loadUI);
        }

Obviously this example could be fleshed out a LOT more. By adding posting to the activity stream (which I may still do) and a much prettier, this app could be a lot more solid. All in all, it's not bad for a few hours of work. The full gadget xml is at:

Monday, September 8, 2008

Browser Inconsistencies: Part 2

Hey-o

Today was my first day at my new job. Exciting, but nothing notable yet. Hence, this is a relatively shorter entry.

Anyways, I was playing around with HTML and CSS again, putting together a chess/checkers-style board with HTML and CSS, and found another browser inconsistency.

I have a 4x4 grid of alternating white and black tiles. The tiles are arranged in rows, and there are tokens that are placed absolutely on top of them. The rows of tiles and tokens are all in a master div. The rows are each divs that have a clear: both property to make them cascade properly. At first, I didn't define the horizontal position of the tokens. In firefox/opera/chrome, you see the following:
















The absolutely positioned tokens are removed from where they "should" be, and are placed in the upper-left corner of their containing div (the white token has been defined to be that low). However, in IE8, this happens:















Apparently, if a horizontal position is not specified, the absolutely positioned elements stay where they would, even though they are removed from the box model. You can tell they have still been removed, because if they hadn't, then the border would be surrounding them as well. Setting the left: 0px; property makes IE8 render the same as all the others, but it is still interesting.

Monday, August 18, 2008

Browser Inconsistencies: Part 1

I did a freelance gig recently that required me to do some HTML/CSS. Like all web tasks, one of the primary concerns was browser compatibility. After several hours of cursing at IE and Firefox, I found a good php script that reliably detected browser, so I used that, but doing that always feels like cheating, so I'm going to be spending some time in the coming months playing around with CSS and HTML, looking at where the breakdowns occur. Here's my first post on the subject.

Surprisingly, when just messing around, it took a little while before I found my first break. I put two divs, one absolutely positioned, one relatively positioned, inside a larger div. The larger div was placed relatively at the top of the screen, and given the style "top: 20%;" What happened then? I'll show you.


















Surprisingly, Firefox was the one that did not register this style correctly. I'm pretty sure it is an issue, and not something laid down by the W3C. This is because when I changed the style to read "top: 20px;", both browsers behaved the same. Interesting.

Thursday, August 7, 2008

Little Bit of Good News

Apparently, my blog is the top hit for the search "add hidden fields programmatically" on Google.

Hooray!

Welcome everyone who's looking for how to add hidden fields programmatically. Please leave comments as to whether or not you found my entry useful!