Monday, July 1, 2013

revoltdc hackathon 20130622 [iteration 3 ] {Success}

Executive Summary

Attempted/Accomplished

  • Decided to do an isolated http.call instead of the double dependent http.call and get it right first
  • Identified a solution to using the router using autorun to avoid an infinite rendering loop
  • Discovered a bug that I needed to resolve blocking my use of reactivity.

Lessons learned

  • LESS files must be stored in the client directory to be served
  • How to use a local collection
  • A good use of autorun
  • About a condition for infinite rendering

Questions and Todos

  • Find a way to test the existence of an image on another server before setting an image location (perhaps by looking at a status code via an http.call)
  • Better explore the relationship of the rendering chain when using Meteor.Router

The details

Changeset here. If you choose to review this changeset, don't get thrown off: there are the addition of a few images. Since SVG is stored as in an xml-ish format, you'll see a lot of not particularly meaningful changes. We are still on meteor 0.6.4, but now we are also using less. Less is one of a group of pre-compiler class CSS tools, so we can use less (no pun intended) CSS code to do more in a uniform manner. This helps enable us to use consistent CSS throughout our application at the cost of pre-compilation; but the beauty of having it as a meteorite package is that meteor will handle the pre-compilation for us automagically. In general, it is important to note that with coffeescript and less, if we are given errors once these scripts are deployed, traceback will also not line up directly with the numbering so you need to look at errors with a general understanding that this is the case. Finally remember if you check out the code that you'll need a sunlight api key of your own and that it goes in the root / Server.coffee file.

In this iteration I decided to take a little break from the multiple calls to sunlight on the server and just get a single call working right first. This call is for biographic information for a legislator.

So here is a case where we succeed, but it isn't really all that clear what the dependencies are. Therefore we will have to remove all possible dependencies to determine the truth or do further research.

Somewhere along the way I discovered that during rendering a "party" variable was undefined, and javascript interpretation for the function was halted on this error. We had a bug lingering in the code, and this bug revealed a few things to me:

  • I need to think more "lisp"y or (in other words) recursively: as reactive changes occur, templates get rerendered, so we need to be extra careful about the initial states of variables in these templates.
  • We can't guarantee there will be any initial population of the variables we are using, and if javascript on the client breaks, we lose our updates
  • The reactivity of a local cursors works fine with set and get Session variables on the client.

So here is what success looks like

Now the post here works, but its still very much a hack job. We pull the image using the govtrack id from the govtrack project to display it here, but when it isn't available we get a broken image. We have some code to post parties, but I don't know all of the codes sunlight uses. What happens when we get something more obscure for the party, like the justice party? So these links should be tested and need a default for not being able to fetch an image. In short there is obviously a lot to fix, but it is nice to see things working to some degree

Of note in the image, party rendering is called a few times, and we can see the fetched row from our cursor pointing to mini-mongo. Please note there is a distinction here - mini-mongo is created on the client only! Take a moment to examine the hints from the documentation or just trust the following statement

"The name of the collection. If null, creates an unmanaged (unsynchronized) local collection."

So let's look at how this is accomplished

Here is the file at this point in it's history. Time to tear it up for my edification (and possibly your own):

#somewhere close to the beginning in Client.coffee under the root/client subdirectory
#Establish a null local collection, ooh la la
localbiocollection = new Meteor.Collection(null)


#let's observe, for the lulz, the collection as items are added
localbiocollection.find().observe(
        added: (doc,beforeIndex)->
            console.log(Session)
            console.log(doc)
)

So at this point we have declared a collection available to the client. This creates an empty mini-mongo db client side we can store to. I have a lot of questions about this: is this basically html5 only? What is the compatibility of mini-mongo? I guess time will tell. I am testing in a fairly modern version of Chrome.

Ok, now that we accepted this magic on faith, let's look at a bug that consumed me for a while. I didn't investigate the error thrown by an undefined "bio" in the variables below, but that threw me off for a while. Our helpers are called during rendering and let us populate corresponding handlebar template variables (with what we return)

#somewhere in Client.coffee under the root/client subdirectory
Template.bio.helpers
        result: ->
            console.log("party rendering")
            #at this point we want the only record in this collection and we want it now, so we call fetch
            bio = localbiocollection.find().fetch()[0]
            console.log(bio)
            return bio
        party:->
            console.log("party rendering")
            #at this point we want the only record in this collection and we want it now, so we call fetch
            bio = localbiocollection.find().fetch()[0]
            #before this "if", everything was broken!!!!!
            #I could not coun't on my biocollection being populated before rendering
            if bio
                party = bio.party    
                img_url = "/img/parties/"
                if party == 'R'
                    img_url +="republican.svg"
                else if party == 'D'
                    img_url +="democratic.svg"
            else
                img_url = "/img/hamsterload.gif"
            return img_url

That if statement is very important. If we try to do things we shouldn't with a variable and it throws an error, the interpretation halts and our code doesn't update things properly. Javascript can be very forgiving and just as unforgiving.

Let's take a quick look at the template and bio.less files.


Let's take a moment to look at less. Based on the syntax highlighting can you guess the code that is "less" specific? Hint: this is css highlighting. Note that despite your training, even though a LESS file is css, probably because of the pre-processing involved LESS files are not to be stored in the public directory. bio.less is in the root client directory!

/*Located in the CLIENT directory */
.rounded-corners (@radius: 5px) {
  -webkit-border-radius: @radius;
  -moz-border-radius: @radius;
  -ms-border-radius: @radius;
  -o-border-radius: @radius;
  border-radius: @radius;
}

#bio_img {
  position: fixed;
  left: 0;
  top: 20em;
  width: 20em;
  margin-top: -2.5em;
  .rounded-corners (25px)
}

#bio_party_img{
  position: fixed;
  left: 125em;
  top: 20em;
  width: 20em;
  height: 20em;
  margin-top: -2.5em;
  .rounded-corners (25px)
}

body{
 font-family: "Book Antiqua",Georgia,"MS Sans Serif", Geneva, sans-serif;
 background: #A3181E url(/img/bg_body.gif) fixed repeat-x;
 margin: 0;
 margin: 0;
 padding: 0;
 height: 100%;
}

div.header_capitol{
    height: 149px;
    background: url(/img/Capitol_unselected.png) fixed no-repeat;
}

div.header_capitol_selected{
    height: 149px;
    background: url(/img/Capitol_Selected.png) fixed no-repeat;
}

div.content{
    position: relative;
    margin-left: 21em;
    margin-top: 7.5em;
    height: 100em;
    width: 100em;
    background: #eee;
    font: 18px;
    .rounded-corners (10px)
}

So now you've more or less seen the css behind this. If you answered "rounded-corners" by the way, give yourself a cigar (or your personal preferential substitute). Other than reusing that code, it more or less looks just like css, huh? Moving on.

An issue to consider is rendering, particularly with the Meteor router add on package. If we create dependencies in the router, it more or less seems to become part of the rendering process. Inferring this might be the case, I had to decide a way to excise rendering dependencies from the router code. So here, if we set something reactive and create dependencies inside our helpers or rendering, we can end up in an infinite loop - if it even works. Instead, we rely at this juncture on autorun. We'll show you how to do that now:

Meteor.Router.add
     #.....snip out code .......
    '/bio/:query': (query) ->
            Session.set("bioquery",query)
            #Session.set("biographic",bio)
            Session.set("federal",true)
            #console.log(bio)
            #externalcall_dep.changed()
            return "bio"

You can easily tell I've been experimenting looking at the comments in this code. In any event, here I set a bioquery, and I will be monitoring this with an autorun. If I elected to run the meteor method which in turn calls out to the Sunlight api, I can end up in an infinite loop right; assuming I am correct about dependencies rerendering - because the call comes later, so when the results return just milleseconds later, the router gets triggered, we get rerendering but the same meteor method would get called in the process and we would get the same data fetched and asynchronously injected again. Bad news. Instead of letting this happen, we have an autorun block monitor for changes in what we want to query and insert this in the aforementioned local collection. This enables us to take the calls out of the rendering process and set a reactive source (the localbiocollection cursor) to a value to update rendering.

Meteor.autorun ->
    bioquery = Session.get("bioquery")
    if bioquery
        bioquery_result = Meteor.call("fetchBiographic", bioquery,
                                (err,res) ->
                                    localbiocollection.insert(res)
                            )

Now we get the dependencies updated properly. Fantastisch!!

No comments:

Post a Comment