• Home
  • About
  • Doom II
  • Flog 2010
  • Inspiration
Twitter
    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>
    • Now Playing: Minus the Bear - Omni - 03 - Secret Country
    0 Comments   |   Posted by Derek @ 10:44pm UTC
    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).

    • Now Playing: Gridlock - Formless - 12 - Re/Module
    0 Comments   |   Posted by Derek @ 05:43pm UTC
    May 02

    My expectations of this reunion were pretty low but wow, Cornell still has the vocal chops and this performance highlights how Kim Thayil has been sorely missed for more than a decade.

    • Now Playing: TV on the Radio - Dear Science - 03 - Dancing Choose
    0 Comments   |   Posted by Derek @ 09:23pm UTC
    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>
    • Now Playing: Toadies - Rubberneck - 06 - Away
    0 Comments   |   Posted by Derek @ 12:24am UTC
    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.

    • Now Playing: Cave In - Perfect Pitch Black - 09 - Tension in the Ranks
    0 Comments   |   Posted by Derek @ 06:46pm UTC
    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).

    • Now Playing: Doomriders - Darkness Come Alive - 09 - The Equalizer
    2 Comments   |   Posted by Derek @ 11:45pm UTC
    Apr 16

    We’re Gonna Need a Montage

    Isn’t this sequence from Up just the ultimate proof of Pixar’s brilliance?

    • Now Playing: LCD Soundsystem - This Is Happening - 03 - One Touch
    0 Comments   |   Posted by Derek @ 11:32pm UTC
    Apr 06

    Saw these dudes twice last year; once at a NxNE afterparty and again when they were surprise headliner for a small show in Kensington Market (OK, I knew ahead of time). These performances were some of the most angular and frantic I’ve ever witnessed. A must see with a sweaty crowd.

    • Now Playing: Minus the Bear - Omni - 01 - My Time
    1 Comment   |   Posted by Derek @ 11:18pm UTC
    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.

    • Now Playing: Hot Snakes - Audit In Progress - 10 - Reflex
    0 Comments   |   Posted by Derek @ 02:46am UTC
    Mar 23

    Internet Memes Across the Seas

    Think of the kittens

    A friend mailed me this saucy-packaged packet of gum and postcard from Switzerland because, “it reminded me of you.” Take that as you will.

    • Now Playing: Bon Iver - For Emma, Forever Ago - 09 - Re: Stacks
    0 Comments   |   Posted by Derek @ 09:15pm UTC
    Mar 21

    Apple Crisp

    Apple Crisp

    Ingredients:

    1/3 cup margarine
    1 1/4 cups brown sugar
    1/3 cup flour
    2/3 cups rolled oats
    1 tsp cinnamon
    6 Macintosh apples

    Preheat oven to 375°F

    Grease an 8″ baking dish.
    Mix together melted margarine and lightly packed brown sugar.
    Add flour, rolled oats, and cinnamon.
    Arrange in prepared dish with peeled and sliced apples

    Bake in 375°F oven for 40 minutes or until apples are tender and topping is golden brown. Serves six.

    • Now Playing: Daughters - Daughters - 04 - The Theatre Goer
    0 Comments   |   Posted by Derek @ 08:09pm UTC
    Mar 09

    A Straight Shooter

    “Let’s take this offline for further discussion. We just don’t have the bandwidth to address that here. Besides, best practices dictate that we stay client-centric and focus on our core competencies on a going-forward basis using a holistic approach while still tending to our ROI and best-of-breed reputation. Once we get to the next level, we can touch base, move on from the low-hanging fruit and address issues such as sustainability, rightshoring, and management visibility.”

    - Do_Or_Die

    0 Comments   |   Posted by Derek @ 02:32pm UTC
    Mar 08

    Tag

    Tag - The Power of Paint

    The Game Informer reveal of Portal 2 details led to speculation that new gameplay elements will follow the same path as Digipen student project Narbacular Drop (which the original Portal was inspired by). Colour me as again missing the boat, but the 2008 release of Tag (~50mb download) passed me by. It was a first-person puzzle platformer with a goal to simply navigate through levels. It’s really a tech demo, but its possibilities really highlight how to improve Portal‘s mechanics… and it just so happens its creators may now be under Valve’s employ.

    The fun core of the game is manipulating surfaces through use of a paintball gun to allow jumping, speeding up your running speed, and allowing you to pull off a gravity-defying walks on any surface (like Prey‘s ceiling-traversal). The open-air urban setting (used due to the jumping component) recalls Mirror’s Edge environments but with monochrome, texture-free geometry combined with a graffiti aesthetic. Check it out if you want to see what Portal 2 may play like at year’s end.

    • Now Playing: Los Campesinos! - Romance is Boring - 01 - In Medias Res
    0 Comments   |   Posted by Derek @ 01:38am UTC
    Feb 21

    • Now Playing: A Tribe Called Quest - The Low End Theory - 05 - Verses From the Abstract
    0 Comments   |   Posted by Derek @ 06:02pm UTC
    Feb 07

    They Saw Her Dirty Pillows

    They're all gonna laugh at you

    • Now Playing: Primal Scream - Screamadelica - 04 - Higher Than the Sun
    0 Comments   |   Posted by Derek @ 02:30am UTC
    Feb 06

    Lost Avatar

    • Now Playing: The National - Boxer - 07 - Apartment Story
    0 Comments   |   Posted by Derek @ 03:15pm UTC
    Feb 05

    Retribution Gospel Choir – 2

    I can’t help but compare RGC’s new album 2 with recent Constantines, “Workin’ Hard” vs. “Working Full-Time” aside. They’ve extended past the Low covers found on their debut, going for more bombast with a few sonic twists like an ever-present banjo in “White Wolf”. There’s some noisy blues rock going on with snappy hooks in “Hide It Away” (above) and “Workin’ Hard” along with a few Low-isms, like the outro vocal harmonies of “Poor Man’s Daughter”. An under 34 minute runtime with eight “real” songs is more EP length but it feels just right with the variations in song duration and tempo. Besides, you get to hear Alan Sparhawk rock the fuck out with his unique voice; a bit more tender than the life-weariness of Constantines’ Bryan Webb.

    • Now Playing: Puscifer - "C" is for (Please Insert Sophomoric Genitalia Reference Here) - 06 - The Humbling River
    0 Comments   |   Posted by Derek @ 09:48pm UTC
    Jan 31

    Get Excited… Lost!

    In two days, the final season of Lost will be launched and we’ll find out if the brilliant trip so far isn’t all a ruse. It’s the journey that counts, right? Well the story is complex enough that it may be necessary to play catch-up on all the interpersonal connections and heavy allegory alluded to in the first five seasons. The New York Times put a Lost timeline together, including short video clips and commentary from the show’s producers.

    For a more detailed breakdown on the characters, if you have the patience, sit through this bad song:


    LOST: Every Timeline, Relationship and Twist (In 5 Minutes)

    Then relive the plane crash from different perspectives (including its cause), 24-style.

    Of course…


    Final Season Of ‘Lost’ Promises To Make Fans More Annoying Than Ever

    • Now Playing: The Smashing Pumpkins - Pisces Iscariot - 10 - Starla
    0 Comments   |   Posted by Derek @ 09:52pm UTC
    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;
    	}
     
    }
    • Now Playing: Clann Zú - Black Coats & Bandages - 02 - There Will Be No Morning Copy
    0 Comments   |   Posted by Derek @ 01:38am UTC
    Jan 22

    Putting Off Procastination

    I had this video loaded for three nights until finally completing all 76 minutes. Such is my life. Lazy list view:

    • If you work on a computer, always have a second monitor. Within a month, productivity increases pass the cost of a < $200 screen which is why I never understood my past employers forcing a single setup on me, even if it’s a 22″ widescreen.
    • I can say many of my university essays back in the day fell into quadrant 3 under “due soon” and “not important”. Damned relative values always get in the way, but I maintained my honours average!
    • The current reality of customer service: “Your call is extremely important to us. Watch while my actions are cognitively dissonant from my words.”
    • “When you have a meeting, lock the door, unplug the phone, and take everyone’s Blackberries.” Highlights the reason projects using a SCRUM methodology has daily meetings less than 15 minutes while standing up.
    • And WTF to the camera staying on the blond at ~1:06:15 for what felt like 30 seconds.
    • Now Playing: Bitcrush - In Distance - 08 - Every Ghost Has Its Spectre
    3 Comments   |   Posted by Derek @ 02:13am UTC
    Previous Page 1 of 12

    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