• Home
  • About
  • Doom II
  • Flog 2010
  • Inspiration

Archive for the ‘Computing’ Category

You can use the search form below to go through the content and find a specific post or page:

Jul 07

Seam Framework JSF Component Validation: At Least One Checkbox

For some reason Seam Framework doesn’t include form validation to ensure at least one checkbox is selected out of a group of checkboxes. Like a radio button, but more than one may be selected. One custom code sample was bullshit because validation takes place in JSF lifecycle before the model is updated, so you must wait for the component tree to be built before such a validation can take place.

The solution I found wasn’t optimal but hey, I’m not the only one wanting this simple form functionality.

/resources/WEB-INF/web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">
.
.
.
	<context-param>
		<param-name>facelets.LIBRARIES</param-name>
		<param-value>/WEB-INF/compositions.taglib.xml</param-value>
	</context-param>
</web-app>

/resources/WEB-INF/compositions.taglib.xml

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0"?>
<!DOCTYPE facelet-taglib PUBLIC "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN" "facelet-taglib_1_0.dtd">
<facelet-taglib>
    <namespace>http://mytaglib.com/jsf</namespace>
    <tag>
    	<tag-name>atLeastOneValidator</tag-name>
    	<validator>
    		<validator-id>atLeastOneValidator</validator-id>
    	</validator>
    </tag>
</facelet-taglib>

/src/main/validator/AtLeastOneValidator.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package yourpackage.validator;
 
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.html.HtmlSelectBooleanCheckbox;
import javax.faces.context.FacesContext;
import javax.faces.validator.ValidatorException;
 
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.faces.Validator;
import org.jboss.seam.annotations.intercept.BypassInterceptors;
import org.jboss.seam.ui.component.html.HtmlLabel;
 
import ca.phenogenomics.util.StringUtils;
 
@Name("atLeastOneValidator")
@Validator
@BypassInterceptors
@Scope(ScopeType.CONVERSATION)
public class AtLeastOneValidator implements javax.faces.validator.Validator, Serializable {
 
	private static final long serialVersionUID = -4249428843435574402L;
 
	private static Map<String, Map<String, Boolean>> formCheckboxes;
 
	public void validate(FacesContext context, UIComponent component, Object value)
		throws ValidatorException {
 
		List<HtmlSelectBooleanCheckbox> checkboxes = new ArrayList<HtmlSelectBooleanCheckbox>();
		String groupWith = "";
		UIComponent rootComponent = FacesContext.getCurrentInstance().getViewRoot();
 
		if (!(component instanceof HtmlSelectBooleanCheckbox)) {
			throw new ValidatorException(createErrorMessage("atLeastOneValidator can only be used on HtmlSelectBooleanCheckbox components."));
		}
 
		if (component.getAttributes().get("groupWith") != null) {
			groupWith = (String)component.getAttributes().get("groupWith");
		}
 
		if (formCheckboxes == null) {
			formCheckboxes = new HashMap<String, Map<String, Boolean>>();
		}
 
		if (formCheckboxes.get(groupWith) == null) {
			formCheckboxes.put(groupWith, new HashMap<String, Boolean>());
		}
 
		// Store this component's value, queuing to be checked at the last
		// checkbox in the group to see of any component has the value
		// set to "true"
		formCheckboxes.get(groupWith).put((String)component.getAttributes().get("id"), (Boolean)value);
 
		// retrieve all checkboxes under this group in the component tree
		getCheckboxes(rootComponent, checkboxes, groupWith);
 
		// Last checkbox in the component tree of this group, so
		// now check whether at least one is checked.
		if (component.equals(checkboxes.get(checkboxes.size()-1))) {
			boolean atLeastOneChecked = false;
			String styleClass = "";
 
			for (String componentId : formCheckboxes.get(groupWith).keySet()) {
				if (formCheckboxes.get(groupWith).get(componentId)) {
					atLeastOneChecked = true;
					break;
				}
			}
 
			// highlight or unmark checkboxes/labels
			if (!atLeastOneChecked) {
				styleClass = "required";
			}
			for (HtmlSelectBooleanCheckbox checkbox : checkboxes) {
				if (checkbox.getParent() instanceof HtmlLabel) {
					((HtmlLabel)checkbox.getParent()).setStyleClass(styleClass);
				} else {
					checkbox.setStyleClass(styleClass);
				}
			}
 
			if (!atLeastOneChecked) {
				throw new ValidatorException(createErrorMessage("At least one " +
					(StringUtils.emptyString(groupWith) ? "" : "\"" + groupWith + "\" ")
					+ "checkbox must be selected."));
			}
		}
 
    }
 
	/**
	 * Recursively traverse a component tree to retrieve all the
	 * HtmlSelectBooleanCheckbox objects containing a specific groupWith=""
	 * attribute.
	 *
	 * @param component Initial invocation should be the root component.
	 * @param checkboxes Initial invocation should be instantiated blank list.
	 * @param groupWith Name/label given to the group of checkboxes. null
	 *                  or a blank string returns ALL checkboxes in the tree.
	 * @return List of child checkboxes in a component tree.
	 */
	protected List<HtmlSelectBooleanCheckbox> getCheckboxes(UIComponent component, List<HtmlSelectBooleanCheckbox> checkboxes, String groupWith) {
 
		if (component != null) {
			for (UIComponent childComponent : component.getChildren()) {
				if (childComponent instanceof HtmlSelectBooleanCheckbox) {
					if (StringUtils.emptyString(groupWith) ||
					    groupWith.equals(childComponent.getAttributes().get("groupWith"))) { 
						checkboxes.add((HtmlSelectBooleanCheckbox)childComponent);
					}
				} else if (childComponent.getChildCount() > 0) {
					getCheckboxes(childComponent, checkboxes, groupWith);
				}
			}
		}
 
		return checkboxes;
	}
 
	private FacesMessage createErrorMessage(String s) {
		FacesMessage message = new FacesMessage();
		message.setDetail(s);
		message.setSummary(s);
		message.setSeverity(FacesMessage.SEVERITY_ERROR);
		return message;
	}
 
}

/view/atLeastOneValidatorTest.xhtml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
                xmlns:s="http://jboss.com/products/seam/taglib"
                xmlns:ui="http://java.sun.com/jsf/facelets"
                xmlns:f="http://java.sun.com/jsf/core"
                xmlns:h="http://java.sun.com/jsf/html"
                xmlns:rich="http://richfaces.org/rich"
                xmlns:mytaglib="http://mytaglib.com/jsf">
 
	<h:form>
		<h:messages />
		.
		.
		.
		<s:label>
			<h:selectBooleanCheckbox value="male" groupWith="Sex">
				<tcp:atLeastOneValidator />
			</h:selectBooleanCheckbox>
			<h:outputText value="Duder" />
		</s:label>
		<s:label>
			<h:selectBooleanCheckbox value="female" groupWith="Sex">
				<tcp:atLeastOneValidator />
			</h:selectBooleanCheckbox>
			<h:outputText value="Dudette" />
		</s:label>
		<s:label>
			<h:selectBooleanCheckbox value="unknown" groupWith="Sex">
				<tcp:atLeastOneValidator />
			</h:selectBooleanCheckbox>
			<h:outputText value="???" />
		</s:label>
		.
		.
		.
	</h:form>
 
</ui:composition>
Jun 16

Hibernate’s Inexplicable Failure

DEBUG [SequenceGenerator] Sequence identifier generated: 1025
DEBUG [AbstractBatcher] about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
DEBUG [ConnectionManager] aggressively releasing JDBC connection
DEBUG [ConnectionManager] releasing JDBC connection [ (open PreparedStatements: 0, globally: 0) (open ResultSets: 0, globally: 0)]
DEBUG [AbstractSaveEventListener] generated identifier: 1025, using strategy: org.hibernate.id.SequenceGenerator
DEBUG [AbstractSaveEventListener] generated identifier: 1025, using strategy: org.hibernate.id.ForeignGenerator
DEBUG [AbstractFlushingEventListener] processing flush-time cascades
DEBUG [AbstractEntityManagerImpl] mark transaction for rollback

It’s a one-to-one foreign key generated by a DB2 sequence to persist multiple entities in one transaction. It seems to be created and assigned to the two entities mapped to each other but I have no clue why the JPA/Hibernate transaction decides it’s a no go as all the required fields are assigned in the objects being persisted. I’ve been working on this Seam Framework wizard for a week and this is an example of roadblocks in the dozens that have taken me to varying edges of sanity when it comes to troubleshooting this project.

I can only take solace in knowing once these niggles are straightened out, the rest of the system will be easy to implement. But the technological learning curve and unseen issues derived from requirement complexities make me believe Seam is a terrible framework solution. Absolutely terrible.

Update July 2: This error was occurring because a RunTimeException was thrown in the Hibernate library which wasn’t displaying in the server’s log or on the JBoss stack trace because the entrance method was invoked using EL. Wrapping my persist() call with a try {} containing a generic Exception catch() revealed it was a constraint violation occurring on a value set as a Hibernate TrueFalseType (char(1) with ‘T’ or ‘F’ values) when it should’ve been a YesNoType (char(1) with ‘Y’ or ‘N’ values).

May 01

Disable <rich:inplaceInput> in Seam

You can’t. But you can use <h:outputText> for read-only visitors and <rich:inplaceInput> for write-enabled logged-in users. Customize the inplaceInputTest.xhtml s:hasRole() parameter to give the proper boolean value on whether the logged-in user is an administrator or not of the field you want to control access of.

/resources/WEB-INF/web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">
.
.
.
	<context-param>
		<param-name>facelets.LIBRARIES</param-name>
		<param-value>/WEB-INF/compositions.taglib.xml</param-value>
	</context-param>
</web-app>

/resources/WEB-INF/compositions.taglib.xml

1
2
3
4
5
6
7
8
9
<?xml version="1.0"?>
<!DOCTYPE facelet-taglib PUBLIC "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN" "facelet-taglib_1_0.dtd">
<facelet-taglib>
    <namespace>http://mytaglib.com/jsf</namespace>
    <tag>
        <tag-name>inplaceInput</tag-name>
        <source>../compositions/inplaceInput.xhtml</source>
    </tag>
</facelet-taglib>

/view/compositions/inplaceInput.xhtml

1
2
3
4
5
6
7
8
9
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
                xmlns:ui="http://java.sun.com/jsf/facelets"
                xmlns:h="http://java.sun.com/jsf/html"
                xmlns:rich="http://richfaces.org/rich">
 
	<rich:inplaceInput value="#{value}" showControls="true" editEvent="ondblclick" layout="block" rendered="#{isManager}" />
	<h:outputText value="#{value}" rendered="#{not isManager}" />
 
</ui:composition>

/view/inplaceInputTest.xhtml

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
                xmlns:s="http://jboss.com/products/seam/taglib"
                xmlns:ui="http://java.sun.com/jsf/facelets"
                xmlns:f="http://java.sun.com/jsf/core"
                xmlns:h="http://java.sun.com/jsf/html"
                xmlns:rich="http://richfaces.org/rich"
                xmlns:mytaglib="http://mytaglib.com/jsf">
 
	<mytaglib:inplaceInput value="Example text" isManager="#{s:hasRole('module_manager')}" />
 
</ui:composition>
Apr 27

JBoss Seam, Hibernate, DB2, and Lucene Indexing

Yeah, it’s all happening. Migrating a CMS to Java’s Seam framework using an existing DB2 schema/dataset, the Lucene search indexes on a few tables caused DB2 error SQL1584N. The bizarre part was it was only happening locally on my machine, but not for two others at work, who each have the same amount of RAM. It may have been because I was on Ubuntu and the other devs use Red Hat Enterprise Linux.

System temporary tablespace with page size of at least pagesize could not be found.
Explanation:
A system temporary table space was required to process the statement. There was no system temporary table space available that had a page size of pagesize or larger.

The statement cannot be processed.

User Response:
Create a system temporary table space with a page size of at least pagesize.

sqlcode: -1584

sqlstate: 57055

Following the instructions for ensuring system temporary table spaces page sizes meet requirements, I ran DB2 query:

LIST TABLESPACES SHOW DETAIL

…checking for the entry with Type = System managed space, Contents = System Temporary data, I confirmed the page size used was 32k, which is the highest possible with DB2.

After googling a bit, it seemed the solution was to increase the bufferpool size used by the tablespace giving me this error. The instructions for adjusting the bufferpool are to simply run DB2 query:

SELECT * FROM syscat.bufferpools

Confirm the bufferpool size, then increase the number of pages available until the Lucene indexing doesn’t shit the bed. e.g.,

ALTER BUFFERPOOL ibmdefaultbp SIZE 2000

During this process, you may get the error:

SQL20169W The buffer pool is not started.

sqlcode: +20169

sqlstate: 01654

This just means the change won’t take effect until DB2 is restarted, so execute:

db2 force applications all
db2stop
db2start

Now, if you have the Seam observer Java annotation setup for Lucene indexing, this DB2 error will begone on the next deploy. And yes, this whole post makes me yearn for a non-Java project started from scratch.

Apr 19

The Screwdriver and the Damage Done

iPod dismantled

When visiting the family last Christmas, I decided to avoid my iPod earbuds on the trip due to their deteriorated state (srsly, do they last more than three months without having their volume cut in half or the wire fraying?) Instead I took my Seinnhesier HD-595 for the plane/car ride for an obviously superior listening experience. I’ve done this before but this had an unexpected consequence: destruction of my iPod Touch’s audio plug.

On the plane’s return trip, the iPod would only play full stereo audio if pressure were applied to the headphone jack. But not left or right pressure like most jack problems, but pushing straight in. It seemed like an adjustment of the iPod’s internal jack would fix it easily enough. Problem was how could I easily open it? The manufacturing process tightly seals iPods to the point that they need a spudger (sharp plastic tool) to pop them open without causing aesthetic damage. One Amazon retailer won’t ship to Canada and I couldn’t find other online computer retailers carrying such items. My anti-eBay/Paypal policy prevented other channels.

So I just threw “fix iPod” onto my never-shrinking to-do list and held my iPod tight for use in the near future. With winter weather, that proved harder as the act left holes in my coat pockets along with the odd cramped hand (so business as usual – heyo, etc.) At work, I even devised a way to weigh down the earbud’s jack using a coffee mug. How these temporary solutions end up as three month-long habits are indicators of my lifelong love with procrastination.

Last week I decided to finally suck it up and fixed the damned thing or buy a new iPod already. I discovered it’s seemingly impossible to find a brick-and-mortar in Toronto that sells spudgers. The few College/Spadina stores I asked seemed offended that I would do such a repair myself without them to price gouge another witless customer. And the pretentious cunt as iRepair that just stood behind the counter staring at his Mac screen adjusting an iTunes playlist without even looking me in the eye while taking a few seconds between sentences while plainly repeating, “that’s how it’s done”… you can go fuck yourself.

Instead I found an eyeglass screwdriver in a kit given as a Christmas gift a few years ago. It ended up causing scratches after it took 10 minutes to pop open the iPod. There’s a decent iPhone jack repair tutorial available but bits smaller than those for eyeglasses work best for dismantling iPod components.

Adjusting the jack’s ground contact didn’t fix the problem, so I took off the circuit board where flash memory is soldered to get better access to secure the jack down. Since it was a two year old iPod Touch, I didn’t feel a need to be delicate in the process. Needless to say, a small circuit board piece broke off by the screw and after reassembly the iPod’s screen went to an iTunes & USB cable icon bootup loop that interrupted each iteration. And I couldn’t get to recovery mode by pressing home+power.

I basically bricked my iPod Touch because I didn’t bother buying the right tools.

Then I made the mistake of jokingly suggesting in a Facebook status update about getting a replacement iPhone, which spawned a 19 comments argument between three friends of Apple vs. Blackberry vs. Android. There’s still more than a year left of my terrible Canadian mobile phone contract; there will be no smartphone purchases. I’ll likely be a bitch by getting another iPod Touch that’ll require a $70 repair in another two years, simply because I like the WiFi+Safari+Facebook+Tweetdeck when I’m too lazy to get out of bed or away at a hotel.

Lessons learned? Get the right tools if you want to DIY it and don’t use huge indoor headphones for an iPod (big things, small holes).

Mar 27

Install Linux, Problem (Not) Solved

Ubuntu partition setup

An activity I don’t miss from my university days: setting up a Linux dev box. At work, we decided to get away from Red Hat Enterprise Linux (which would seriously take over two minutes to boot) and test out Ubuntu on a development box. However, the 9.10 installer was decidedly uncooperative. Firstly, it booted to a screen of garbled colours since the setup uses generic drivers and I had dual Dell 1907FPV monitors. The simple fix was to disconnect one of the monitors and install Nvidia’s proprietary drivers for a GeForce 6800 after installation had completed.

However, the real showstopper came at step 4 of 7 for setting up the partitions where the above disabled screen appeared. The only selectable option is to click “Forward”, which provides this dialog error message:

No root file system is defined. Please correct this from the partitioning menu.

Setup obviously wasn’t recognizing the hard drive and playing with BIOS settings didn’t help. This machine doesn’t exactly require crazy drivers since it has an Intel; 82945G Express Chipset (mobo) and ATA Maxtor 7L250S0 250gb (hd). The solution was to quit the installer, which then automatically boots into a mounted Ubuntu preview, and from the top menu select Application » Terminal, typing:

sudo apt-get remove dmraid

Running the installation from within Ubuntu now allowed setup to recognize my existing partitions for reconfiguration. The rest of the steps were relatively painless, aside from a file copy hiccup at 70% due to a DVD-R thumbprint (of course you can’t umount within the installation to eject the disc, clean, and insert back into to try again… so I had to start that whole install process again.)

When it came time to add a local NFS, inserting its entry to /etc/fstab and attempting the mount gave error message:

mount: wrong fs type, bad option, bad superblock on tcpserver:/tcphome, missing codepage or helper program, or other error (for several filesystems (e.g. nfs, cifs) you might need a /sbin/mount. helper program) In some cases useful info is found in syslog – try dmesg | tail or so

Nope, not enabled OOTB, so I had to run:

sudo apt-get install nfs-common

Using Synaptic Package Manager to retrieve all the software development tools was a well-done process, so it must be the year of the Linux desktop! Or not.

Jan 30

Relative Time in JavaScript

There are plenty of robust JavaScript libraries out there for handling timestamps, but I came across a situation where I needed a lightweight solution to represent dates returned from a JSON request that meaningfully indicate the proximity to an expiration. Something to the effect of, “Feb 3, 2010 (in 5 days)”, or, “Jan 1, 2010 (30 days ago)”. This implementation doesn’t handle language localization or customized messages like optionally breaking down to also include years or decades. But it does contain nested ugliness with magic numbers ahoy. I didn’t say it was elegant; just lightweight (see: took less than 10 minutes to create, even with clever pluralization of the time’s unit!)

 
/**
 * Get a string describing the difference between two timestamps.
 *
 * @var time Date object to retrieve relative description string of
 *
 * @return string indicating how far in the past or future the date is
 */
function getTimeDiff(time)
{
 
	var now = new Date();
	var diff = now.getTime() - time.getTime();
 
	var timeDiff = getTimeDiffDescription(diff, 'day', 86400000);
	if (!timeDiff) {
		timeDiff = getTimeDiffDescription(diff, 'hour', 3600000);
		if (!timeDiff) {
			timeDiff = getTimeDiffDescription(diff, 'minute', 60000);
			if (!timeDiff) {
				timeDiff = getTimeDiffDescription(diff, 'second', 60);
				if (!timeDiff) {
					timeDiff = 'just now';
				}
			}
		}
	}
 
	return timeDiff;
 
}
 
/**
 * Get a string describing the difference between two timestamps,
 * based on unit of time. Run sequentially starting from the greatest
 * unit and stopping once this function doesn't return null.
 *
 * @var diff current time (in millisec since 1970) minus time to compare to.
 *           e.g., (new DateTime()).getTime() - someOtherTime.getTime()
 * @var unit Time description to place in a string. e.g., 'hour'
 * @var timeDivisor Number to divide into milliseconds to get the value
 *                  described by the unit. e.g., 60*60*1000 for 'hour'
 *
 * @return A string representing the difference between the given time
 *         and now, if there is a difference for the given unit of time.
 *         null is returned if they're the same (e.g., provided time falls
 *         on today when unit = 'day')
 */
function getTimeDiffDescription(diff, unit, timeDivisor)
{
 
	var unitAmount = (diff / timeDivisor).toFixed(0);
	if (unitAmount > 0) {
		return unitAmount + ' ' + unit + (unitAmount == 1 ? '': 's') + ' ago';
	} else if (unitAmount < 0) {
		return 'in ' + Math.abs(unitAmount) + ' ' + unit + (unitAmount == 1 ? '' : 's');
	} else {
		return null;
	}
 
}
Dec 04

The Bitter Web Producer

  • How a Web design Goes Straight to Hell
  • Clients From Hell
  • Business Guys on Business Trips

Read these links and a proper catharsis should be met. But we’re a fussy bunch.

Oct 18

Disconnected Toronto Concert Listings

Just thought I’d highlight issues I have with concert listings in my dear city. I’m talking about advanced ticketing for bands that can pack medium-sized halls. The main problem I have with all of them is a lack of centralized event listings or even a common format where concert-goers can aggregate dates and details through RSS feeds or RESTful apps. Venues in this camp include El Mocambo, Lee’s Palace, Horseshow Tavern, Phoenix, Opera House, and The Mod Club (which has Flash listings that your browser can’t even search.) Sneaky Dee’s are in there too but they haven’t had a working events calendar since their latest event booker started in June. Promoters like Against the Grain and rootmeansquare also have separate listings that go across multiple venues.

Up to now, I’ve been tracking upcoming concerts by using local record stores, Rotate This and Soundscapes. They’re the best way for downtownizens to avoid Ticketmaster/LiveNation service fee money-grabs, instead getting tickets in-person with no scalping pretense. There are still issues with certain events falling through the cracks (no advanced ticketing?) and these listings not allowing custom sort criteria such as band name, date, price, or dated added to listings.

What I’d really like to see is for venues and promoters to use a feed or web service with full show details. It’s great when they show band set times, convenient when the openers are inexplicably terrible. With the event name, venue name, address, and list of artists with start times alongside links on where to buy tickets, all the necessary details are there for a potential customer. It’s a pipedream to get all these competitive profit-seeking interests to use one common service, so maybe just RSS can even the playing field.

Of course, the obvious part-solution to this whole problem would be if promoters would use Facebook . For aforementioned Sneaky Dee events held this summer, the only way I was able to find out about shows was through my friends’ joining Facebook events created by the venue’s event organizer. ATG’s group has also been on the ball (at times) by creating events for headlining artists and sending out invites to every member of their group. However, if all promoters pulled this, you’d be bombarded by dozens of invites everyday. It would be much better to narrow down invites based on genre (crowdsourcing killing art aside) but Facebook doesn’t have that capability unless each promoter created multiple groups. Then the overhead becomes overkill. The coolest filter I can think of is only allow invites for bands in a user’s Last.FM artist library but eh, that’s a web 2.0 mash-up where there wouldn’t be enough of a userbase to make it worthy. Last.FM does have an event recommendation system but it’s focused on the next week’s events that have likely sold out.

I believe Toronto is the second highest ranked city as far as Facebook registration number go (behind London), especially in the core audience of 18 to 35 year olds. Having centralized event pages with integration for Facebook, MySpace, and Last.FM would allow for advertising using free tools (no hosting, monthly registration, or web site development/maintenance fees) that take advantage of social networking for more pleasant concert organizing experiences.

But, of course:

Oct 16

Sony Ericsson K850i Contacts Sync

After having a Sony Ericsson K850i for almost 18 months (and under contract on until June 2012 unless I buy an unlocked device), I thought it was about time to get all my contacts in order. Trying to find a seamless way to keep the phone, iPod Touch, and Hotmail contacts all synchronized wasn’t so easy. My main stumbling block was me being a stickler for an email address I’ve been using since 1997, but a solution must exist!

I had to install the Sony Ericsson PC Suite which promptly gave me the message:

Reading mobile phone’s contacts failed.

Few relevant Google results match my problem other than this site which claims you need to install drivers (which the PC Suite does.) The error message is also mentioned nowhere in Ericsson’s support pages.

I figured out I had disabled phone modem-related drivers a year ago because every time it was connected via USB, Windows would throw up five popups on new hardware installed. Every time. So I enabled those in Device Manager and also had to reinstall the PC Suite before my phone would be recognized for syncing.

You can use Google Sync for over-the-air Gmail contacts but I don’t have any data plan so I’m not touching that with Canadian per-kilobyte pricing. Over USB, Thunderbird isn’t an option; only MS Outlook. So my solution was to download a Hotmail .csv, import that into MS Outlook Express (like I’d pay for MS Office), then use the PC Suite to do the final sync.

Problem is I still had contacts on my SIM that the software can’t remove. The K850i has no method on the phone itself to batch delete contacts from the SIM. I had to do that manually or else there were redundant contacts for most people. Sony Ericsson’s OS already has latency issues, so it was unbelievably laggy on 165 contacts where I’d have to wait half a second between deletes for the list to (twice) refresh. Then the PC Suite offered no option to only sync contacts with (mobile, work, or home) phone numbers so I had to manually remove entries under the software’s Contacts tab which luckily has a table with phone numbers in view. So in the end, I found no elegant solution to sync contacts.

Oct 14

With Nothing to Offer: Aggregate

Social networking activity feeds are all the rage, where it’s now necessary to microblog every detail of our lives. I think it’s funny that Windows Live profiles started a new promotion for, “Your Online Updates Together in One Place”, which finally conceding that Microsoft is giving up on that game. Windows Live Spaces failed, so they now leech off other systems to hide how inactive users are with Microsoft’s services. I’ve already highlighted how I think Live profiles are jokes.

Now most timestamped updates you’ve made online can be placed in one activity feed. I have noticed other apps are following along the same lines when it comes to displaying user details. My profiles for Nine Inch Nails and Dredg both have iconized links to my third party accounts, with nin.com integrating YouTube videos and Flickr photos into its internal interface using each services’ tagging system. TweetDeck and other third party apps already split up Twitter threads alongside Facebook statuses. As third-party APIs open up, more sites will start combining shared information.

By mixing in text typed by me in this blog, Twitter, Facebook, Last.FM, film log, Goodreads, Windows Live Messenger statuses, and posts from the half-dozen forums I frequent (ir)regularly, you can probably piece together a rather disturbing stalker tracker. Is it banal? Most likely, but at least you’re now more likely to have alibi after getting mistakenly picked up by the po-po.

Oct 13

LiveDocx: Pleasant But Lacking

On a work project, one client wanted printable documents that weren’t in HTML. So we built RTF template in MS Word, formatted variable placeholders as <<VARIABLE_NAME>>, and in PHP read the whole file to run str_replace on those variable names. This didn’t work well because had to cut and paste RTF codes into separate files (sometimes up to 10) for one document when there were repeating sections. Any change requests to that document also meant breaking it all up again from scratch. Many times we came across the document not formatting correctly in all apps (MS Word, Wordpad, and OpenOffice on Windows or Mac.)

An alternate was found in LiveDocx with phpLiveDocx just recently added to the Zend Framework library. Instead .docx files are created in MS Word with MailMerge fields used for variable placeholders and loop start/end points defined by MS Word bookmarks. The phpLiveDocx code is dead simple too. We could also now export the completed document into .doc or .pdf while the whole service is free since it’s now part of the Zend Framework.

However I came across major limitations in these templates, specifically:

  • No nested loops – I wanted to group employees into their home country and have a separate table for each but the MergeBlock names created by the MS Word bookmark functionality are used as identifiers. I wanted it setup something like:

    1
    2
    3
    4
    5
    6
    
    country_start
    	[country_name]
    	employee_start
    		[employee_name] / [employee_visa_number] / [employee_visa_expiry]
    	employee_end
    country_end

    However the LiveDocx assign() functions ignore any inner loops and variables, so I would have to know beforehand what countries to include and manually created those tables separately (like having one block named “country_canada” and “country_usa”) which isn’t optimal when there are hundreds of countries worldwide.

  • No repeating blocks – Again, since the bookmarks are identifiers, I can’t reuse a block. So I couldn’t pull off even/odd table row background shading to improve readability or have a separate table for each country unless I manually created them separately. I ended up just making one flat table with the database SELECT query alphabetically sorting employees by their country. The redundant repeating country field decreases readability, which is the whole purpose of the printed document.
  • Poor template error feedback – This is probably the issue that ticked me off the most. The first time I built a template, the document generated would either being missing elements (like a table cell) or some elements would be aligned on the document in a nonsensical format. Only through trial-and-error was I able to diagnose and fix the layout issues I was having. The best tip I can give is if there is a problem, add more linebreaks. I found you can’t have two bookmarked blocks start/end on the same line. So for a table row, add two line breaks before adding in another block for LiveDocx to parse. Some of the exception messages thrown by the web service when your MergeMail field assignments aren’t properly called also make zero sense.

That’s really it. I’m impressed by the ease to integrate a web app into this service but its layout limitations leave a lot to be desired.

Sep 26

Yell at Bell

My home’s Internet connection was out for over 3 weeks. Incoming wall of text.

Aug 20
A massive thunderstorm goes through the GTA, with multiple tornadoes touching down. Our Internet connection with Teksavvy stops at 7:11PM. I get home after footy training (where I was standing in the middle of a UoT sports field with lightning all around me which was really not awesome) to find our 2wire modem’s DSL indicator is blinking red, meaning no signal. I figure it’s a temporary outage and go to bed.

Jun 06

Eye Over You

The proliferation of mobile devices and cost of GPS hardware implementations have raised interesting new methods for positioning end-points on the globe. IP address lookups are completely inaccurate (when in downtown Toronto near the lakeshore, one IP locator placed me near the 401, about 10km away.) I thought it was interesting that the iPod Touch OS doesn’t contain GPS (as its iPhone sibling), but instead uses Skyhook Wireless, a location lookup based on the (password secured or not) wireless router MACs within your range. Needless to say, it’ll only be accurate in dense urban areas. But almost pinpoint it is.

May 08

Like a Burmese Python

Kenny Powers Dance

I guess Microsoft’s sad attempt at playing catch-up on the social media game led them to start pushing Messenger status updates on their rarely-visited Windows Live user profiles in order to force an activity feed that nobody will follow. No biggie, except by default they’re published to anyone.

Feb 13

Paypal Development: The Art of Obfuscation

Create simple management tools for a subscription site. Find your payment gateway, solidify a set of packages, and program the elements to interface recurring payment options with different payment methods. At this point in e-commerce’s maturity, such systems should be dead simple to implement but some jerks are still around to ruin your day in the name of poor management and pursuit of the Yankee Dollar. Enter PayPal.

Feb 08

Best Buy is Murdering Our Insect Population

A pair of iAudio 8805 Metallic Earphones purchased (only because I had a $50 gift card) from the Bay/Dundas Best Buy contained a decomposing corpse of our friendly flying brethren within its packaged seal.

The only good bug is a dead bug

Poor little bugger.

Dec 11

He’s Going for That Anti-Marketing Dollar

Ignorance is Strength

That’s a good market. One (and only one) of my eyebrows were raised by Facebook Connect’s implementation policies, specifically two clauses:

Nov 27

Best Programming Error Message, Ever

I came across this doozy today:

Catchable fatal error: Argument 2 passed to Opm_Project_Milestone::findForProjectInTimeframe() must be an instance of int, integer given

Nov 21

iPod Touch & iPhone Last.FM Scrobbling

Since Apple doesn’t allow third party apps in their iTunes store to run in the background, uploading your listened-to music in real time over the air (EDGE/3G/WiFi) is not possible. That is, unless you jailbreak your device and install MobileScrobbler. The free Last.FM iPhone app is for listening to online radio and checking listening histories, not for actual scrobbling.

Older Posts »

Derek MacDonald

  • Photo Stream
  • Categories
    • Computing
    • Film & TV
    • Gaming
    • General
    • Music
    • Sports
    • Visual Art
  • Search






  • Home
  • About
  • Doom II
  • Flog 2010
  • Inspiration

© Copyright Derek MacDonald. All rights reserved.
Designed by FTL Wordpress Themes brought to you by Smashing Magazine

Back to Top