Bartebuss – the beginning

Rutedata m/sanntid

Rutedata m/sanntid

Tidlig i sommer snublet jeg over et prosjekt kalt BusBuddy, hvor noen studenter ved IDI hadde laget et kult busskart med sanntidsdata fra AtB. Men de hadde ikke bare stoppet der, de hadde også lagd et API for å dele rådataene. Siden AtB offisielt enda ikke hadde gjort dette selv, var det nesten som en liten «undergrunnsbevegelse» av bussnerder som begynte å mekke smarte løsninger, meg selv inkludert.

Jeg hadde lyst å lage en mobilvennlig webapp, heller enn noe som bare funket på Android eller iOS (iPhone etc). Trafikantens presentasjon av ruteinfo for buss/trikk i Oslo var utgangspunktet, og jeg satte i gang å leke. Det skulle bli en HTML5- og JavaScript-drevet «dings» som ikke krevde pageloads, som var rask, så bra ut og utnyttet nye HTML5-muligheter, som f.eks. å bufre data og hente brukerens posisjon.

Resultatet av det jeg beskriver under finnes på bartebuss.no, samt som Android-appen Bartebuss på Android Market.

Dette kommer til å bli en ekstremt nerdete, usammenhengende og lang bloggpost. Du er herved advart!

Rammeverket

En webapp bør se ut og oppføre seg litt annerledes enn en vanlig webside, blant annet ville jeg ikke ha mulighet til zooming siden appen likevel skulle bruke standard fontstørrelse og være full skjermbredde, og jeg ville kunne angi pene, store ikoner og splash-screens. iPhone kan kjøre sider i webappmodus (uten adresse- og verktøylinjer), det ville jeg også dra nytte av.

Til slikt finnes det mange fine meta-elementer man kan bruke.

<!-- Full bredde, ingen zooming -->
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
<!-- Tillat å kjøre i fullskjermmodus i iOS -->
<meta name="apple-mobile-web-app-capable" content="yes">
<!-- Ikoner og splashscreens for alle varianter av iOS (iPhone/iPad/iPhone retina) -->
<link rel="apple-touch-icon" href="/img/icon-57x57.png" />
<link rel="apple-touch-icon" href="/img/icon-72x72.png" sizes="72x72" />
<link rel="apple-touch-icon" href="/img/icon-114x114.png" sizes="114x114" />
<link rel="apple-touch-startup-image" href="/img/splash-748x1024.png" media="screen and (min-device-width: 481px) and (max-device-width: 1024px) and (orientation:landscape)" />
<link rel="apple-touch-startup-image" href="/img/splash-768x1004.png" media="screen and (min-device-width: 481px) and (max-device-width: 1024px) and (orientation:portrait)" />
<link rel="apple-touch-startup-image" href="/img/splash-320x460.png" media="screen and (max-device-width: 320px)" />

For Android må man jukse litt for å unngå konflikt med iOS-ikonene.

if(navigator.userAgent.match(/Android/i))
    $('head').append('<link rel="apple-touch-icon-precomposed" href="/img/icon-114x114.png" />');

Siden verktøylinjene i browseren forsvinner når man kjører i webappmodus, startet jeg med å bygge en verktøylinje med utgangspunkt i hvordan Seesmic og Facebook gjør det. All HTML for denne, pluss skjelett for alle undersidene, ble plassert i én HTML-fil, slik at lasting av undersider ble unødvendig. Målet var i tillegg å bruke minst mulig PHP i brukerens ende, slik at mest mulig kunne kjøres client-side.

HTML5 history

For å slippe pageloads, men samtidig ikke ødelegge tilbake-knappen tok jeg i bruk HTML5 history. Det fungerer slik at man dytter en URL og en sidetittel på en stack, sammen med et JSON-objekt som beskriver en tilstand (state). Man velger selv hvordan man ønsker å bygge state-objektet sitt, så lenge man greier å gjenskape den forrige siden basert på dataene det inneholder. Jeg valgte å ta vare på sidens ID, hvilken fane i appen det tilsvarte, og dobbeltlagre lenka.

var state = {
    'page': page.attr('id'),
    'tab': tab.attr('id'),
    'link': link
};
history.pushState(state, null, link);

For hvert pushState man gjør, får man et ekstra innslag i historikken å gå tilbake til. Samtidig endres URLen i adressefeltet, akkurat som om man hadde lastet en ny side. For å unngå å skape et logginslag ved første sidelasting (men samtidig gjøre det mulig å gå tilbake dit), bruker man replaceState ved første pageload. Litt logikk for å finne ut hva som er første er naturligvis også nødvendig.

Når brukeren trykker tilbake-knappen trigges en popstate-event, denne må man knytte en handler til.

$(window).bind('popstate', function(e){
    // Hent det gamle state-objektet fra eventen (eller originalEvent for jQuery)
    var previousState = e.originalEvent.state;
    // Gjør noe magisk, last riktig side, sett riktige variabler osv.
});

Handleren gjør det som er nødvendig for å gjenskape hvordan den forrige siden så ut.

Direktelenker til tilstand

Hvis man ønsker å la det være mulig å gå direkte til lenker/tilstander inni appen (direkte til en søkeside el.l.) må man sjekke sidens URL ved lasting, og gjøre de nødvendige tingene derfra. Jeg brukte JQuery URL Parser plugin, og trigget klikk/tap på lenkene/knappene som normalt fører brukeren til angitt tilstand/side.

var urlSegment = $.url().segment(1);
switch(urlSegment){
    case 'alle':
    case 'sok':
        $('#searchTab a').trigger('tap');
        break;
    case 'orakel':
        $('#oracleTab a').trigger('tap');
        break;
    case 'kart':
        $('#mapTab a').trigger('tap');
        break;
    ...
}

Det er en god grunn til at jeg ikke trigger en click-event, men heller den egendefinerte tap-eventen, mer om det under.

HTML5 localStorage

Resource inspector, ChromeÅ jobbe med ca 1400 holdeplasser for Trondheimsområdet betød at det ville være en fordel å mellomlagre alle på mobilen, heller enn å laste dem ned hver eneste gang. Jeg skrev derfor et script som lastet dem ned én gang, lagret dem i localStorage, og kun oppdaterte hvis lista var over en uke gammel – eller den fikk beskjed om å oppdatere seg (ved viktige endringer).

For å avsløre om brukerens enhet/browser støttet forskjellig «ny» funksjonalitet tok jeg i bruk Modernizr. Datoer tok jeg hånd om med date.js.

$.ajax({
    url: 'http://api.busbuddy.no/api/1.2/busstops?callback',
    dataType: 'jsonp',
    jsonpCallback: 'busbuddyResponse',
    data: {'apiKey': xxx},
    timeout: 5000,
    success: function(data){
        if(Modernizr.localstorage){
            localStorage.setItem(
               'busStops',
               JSON.stringify(data.busStops))
            localStorage.setItem(
               'lastUpdated',
               Date.today().setTimeToNow().toString(bartebuss.timeformat));
        }
        stops = data.busStops;
    },
    error: function(){...},
    complete: function(){...}
});

Tap delay

Selv gode webapps føles tregere enn native apps, og en av grunnene er at det i browseren er en forsinkelse på omkring 300 ms fra brukeren berører skjermen til en klikk-event sendes. Årsaken er at browseren venter på et evt. dobbelklikk, som brukes hvis man vil zoome inn på en bestemt del av siden.

Denne forsinkelsen får man ikke fjernet, men man kan velge å lytte til touchstart-eventen, heller enn click-eventen.

Det finnes forskjellige script som mener å løse problemet, men de fleste har mangler. Ulempen er at det i browsere på enheter uten touch-skjerm ikke fyres touchstart, bare click. Man trenger dermed en fallback i disse tilfellene. Man kan likevel ikke lytte til begge eventene på én gang, fordi Safari på iOS fyrer både touchstart og click (etter 300 ms), og man får da dobbelt opp. Først touchstart, og så click 300 ms senere. Hvis man rekker å endre til en ny side innen de 300 ms, havner click-eventen på den nye sida, heller enn på den opprinnelige knappen/lenka, og ting begynner å skje av seg selv. Veeeldig frustrerende å debugge.

Heldigvis løser  jQuery.tappable plugin problemet rimelig greit. Android 2.1 hadde problem med at klikkene døde med mindre man var veldig lett på fingeren, fordi den trodde fingeren hadde flyttet seg mellom touchstart og touchend. Jeg løste det ved å hacke inn en buffersone på noen få piksler rundt berøringsstedet. Jeg la også til min egen tap-event, som jeg kunne trigge for å simulere en berøring eller et klikk – avhengig av hvilken enhet brukeren hadde.

$('#backButton').tappable(function(){
    history.back();
});

Siste rest av weben

Markering av valgt elementFremdeles er det småting som avslører web vs. native app, f.eks. markeringen/outline browseren legger på områdene man berører/klikker, samt klipp-lime-funksjonen. Dette løser man enkelt i CSS.

/* Ingen klipp-lim */
-webkit-user-select: none;
/* Ingen popups på siden */
-webkit-touch-callout: none;
/* Ingen markering av "berørt" område */
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
/* Ingen automatisk tekstskalering */
-webkit-text-size-adjust: 100%;

/* Hvis man er dristig kan man fjerne outline på lenker/knapper osv også */
input[type=submit],
button,
a.knapp {
    outline: none;
}

Det siste avslørende momentet overshoot på scrolling, altså at man kan trekke siden opp/ned og få en gummistrikkeffekt om der ikke er mer innhold å vise. Det kan skrus av ved å fikle med touchmove-eventen, men siden jeg ønsker å ha rulling på siden likevel (pga. lange lister) har jeg ikke tatt meg bryet med å ordne en «fast» side.

Installering

Maseboble (Cubiq-versjonen) På iPhone/iPad er det veldig enkelt å «installere» en webapp. Alt man trenger gjøre er å legge til et bokmerke på hjem-skjermen, og vips får man et pent ikon med runde kanter og gloss. (Gitt at man har fulgt oppskriften over). Når man starter webappen derfra forsvinner verktøylinjene og appen vises i fullskjerm-/webappmodus. På Android må man først lage et bokmerke, og deretter plassere bokmerket på hjem-skjermen. Litt mer omstendelig, men man oppnår også da et pent ikon. Splash og fullskjermmodus må man dog være foruten.

Hvis man ønsker/tør å plage brukerne en ekstra gang for å få dem til å «installere» (dvs. bokmerke) appen, kan man legge til en «maseboble» (annen variant) som spretter opp og ber brukeren om å legge til et bokmerke.

Jeg brukte Googles variant i starten, men bestemte meg for å fjerne den i tilfelle den var til bry og irritasjon.

Da skulle basiskunnskapen være på plass for å lage en god og rask webapp, som det vil kreve et trent øye for å skille fra en native app.

Festninger og vafler

Lørdag gikk turgruppa fra Kapellet i retning Grønlia. Vi skulle «fylle magasinene» med sukker på Grønlia, før vi gikk videre til og rundt Gråkallen. Det var iallfall forslaget mitt da jeg ble spurt om turvalg. Damene ymtet imidlertid frempå om at det slett ikke var sikkert jeg kom til å få viljen min, og at de sannsynligvis måtte bestikkes på de grusomste måter (for meg) for å la meg bestemme.

Etter vaffelrasten satte vi kursen nordover. Jeg spurte forsiktig om vi skulle innom Skihytta da vi passerte, men det skulle vi tydeligvis ikke! Rundt Gråkallen skulle vi heller ikke, dermed var to av mine to innspill solid nedstemt. De var to, jeg var bare én, så det var ingenting å gjøre med saken. Demokrati er demokrati. Det vil si… egentlig er vi mer som et opplyst enevelde. Ellen spør hvilken retning i et veiskille vi vil ta, men i realiteten er valget allerede tatt. Valgfrihetens illusjon. Og velger man feil er man ille ute! Jaja, Anne og jeg får være fornøyd så lenge vi ikke blir forlatt for å dø ute i marka.

Middelalderbro i sikte!

Middelalderbro i sikte!

Forlatt festning

Forlatt festning

Fra Gråkallen gikk vi mot Kobberdammen, hvor jeg ikke har vært tidligere i sommer. Slappe tendenser… Der spiste vi sjokolade mens Kiska badet og de andre hundene hylte om å få være med. Det var en del vind og ble etterhvert ganske kaldt, så jeg fikk kranglet meg til å gå videre. På veien ned mot Kapellet passerte vi under en bro som Ellen daterte tilbake til middelalderen. Gammel var den iallfall. Anne var mer usikker. Jeg syntes den så relativt «ny» ut på oversiden. Men at den var brukt til militære formål var iallfall sikkert – ifølge Ellen. Lenger oppe i veien fant vi restene av en festning/forlegning i tilknytning til broa. Der hadde, i følge meg og min frie fantasi, en styrke på syv modige nordmenn forsvart riket mot tyskere, og når jeg tenker mer på det hjemme, sannsynligvis også drager. De hadde jobbet, sovet og beskutt fiender fra sin lille forsvarsposisjon og ble etter krigen glemt av både venner og historikere. Makan…

På trappa på Kapellet benyttet Anne og jeg sjansen til å føle litt på hvordan det var å være gift i gamle dager. Nei, vi satte ikke til verden 17 unger (hvorav to ble satt på skogen), men vi poserte strengt alvorlig for bryllupsfotograf Ellen.

Totalt ble det 8,6 km tilbakelagt på 3,5 timer.

[picasaview album=»KapelletGrNliaKobberdammen» instantview=»true»]

 

Søndag startet vi fra Ringvål og satte kursen mot den lille perlen Bjørkhylla. I sekkene hadde vi alt som skulle til for å steike vafler i naturen. Bente hadde vaffelrøre, syltetøy, rømme, smør og bestikk. Jeg hadde ved, fyrtøy og opptenningspapir. Ellen hadde støvlene sine. For «hun skulle iallfall holde seg tørr»!

Og vått var det. Det kom neppe som noen overraskelse på damene som hadde diskutert føret dagen i forveien, men apa med joggeskoene fikk smake myrvann mellom tærne. Til tross for det vekslende underlaget kom vi oss trygt til Bjørkhylla og fyrte opp i ovnen. Det krevde noen parafinvoksbiter, men etterhvert brant det riktig så bra. Panikken begynte å bre seg i gruppa da det viste seg at Bente bare hadde laget ca 3 dl røre, men hun måtte avbryte bløffen da Ellens stressnivå begynte å bli farlig høyt. Det fantes en flaske til i sekken. Ellen roet følelsene med et par Oreo-kjeks mens Bente lo seg ferdig.

Det stekes

Det stekes

Første vellykkede jern

Første vellykkede jern

Vaffeljernet ble smurt så godt det lot seg gjøre, og røra helt i. Vi var enige om at det første hjertet slett ikke var noen suksess… Men det smakte godt til tross for at det besto av en kombo av skorper, ustekt røre og kull. Plate nummer 2 var også en knakende fiasko. Til tross for umenneskelige mengder smør i jernet satt det fast. Det er merkelig hvordan man skal kunne greie å både brenne fast et vaffeljern, og samtidig få det rått inni…

Men så begynte teknikken å ta seg opp, forvarming av jern og smør var nøkkelen, og etterhvert ble det riktig så gode smørvafler (les: bade-i-fett-vafler/frityrvafler). Føler meg fremdeles feit på fingrene. Alle fikk smake, selv om Bente var beskjeden/skeptisk, og Ellen mett på Oreo-kjeks, men mesteparten gikk i hundkjeften. Altså meg.

Totalt: 11,2 km på 4,5 time (på ingen måte nok til å forbrenne vaflene)

Bilder gjengitt med tillatelse, takk til Ellen for fotoapparatbruk og resultater!

[picasaview album=»VaffelturRingvalBjRkhylla» instantview=»true»]

Nes-stevnet 2011

I helga deltok Team Rambo for første gang på Nes-stevnet. I likhet med Oslo Hundeshow, som arrangeres av samme klubb(er), var også dette et ganske stort stevne etter vår målestokk. Rundt 800 starter ble det nevnt over høyttalerne.

Stevnet ble lurt inn i ferien, etter bryllupet til Eirik og Lene, og ei lita uke hos svigers i Kongsberg. Det var styggvarmt begge steder, og da vi kjørte gjennom Lillestrøm kom det en enorm troperegnskur. Vi begynte å grue oss for å ligge i telt, men regnet ga seg heldigvis omtrent idet vi var fremme ved stevneplassen.

"Jeg vant en polakk eller pukkel eller noe sånt!"

"Jeg vant en polakk eller pukkel eller noe sånt!"

"Ble kaldt i regnet, brrr"

"Ble kaldt i regnet, brrr"

Etter å ha satt opp utstillingsteltet dro vi til Frognerstrand camping, hvor vi skulle bo. Vi fikk lov å snylte terrasseplass og sosialt samvær hos Trine, Agnete, Kari og Tina. Kake hadde de også 🙂 Andre Nidarosinger bodde på hotell, på stevneplassen og andre steder.

Lørdag var det solsteik og oppholdsvær. Det var varmt i lufta, så Rambo ble fuktet ned og nøt livet i skyggen før og mellom løpene. I agilityløpet ble det to feil. Det ene pga. noe han aldri har gjort før: å springe vippa helt ut og hoppe av enden før den beveget seg (også kjent som «belgervippe»), og det andre pga feil slalominngang. Det var likevel så mange andre som gikk feil at vi havnet på en tredjeplass av 16 startende til slutt (og med beste tid av alle). Fôrsekk og fin pokal ble plukket med fra pallen.

Hoppløpet ble feilfritt (napp!), men vi var 5 sekunder bak vinneren, så det endte med en fjerdeplass av 20 startende. Rambo fikk en keramikkskål og telyseholder i premie. Keramikkskålen er allerede innviet som nødfôrskål.

På ettermiddagen spiste vi grillmat sammen med resten på campinga, og utpå kvelden slo noen av Gauldalingene (som også bodde på hytte der) seg ned sammen med oss. Det ble bydd opp til Geni-spill, en utfordring vi tok på strak arm. Vi utklasset dem selvsagt, men minst ett poeng 😉

Søndag kom regnet tilbake. Vi rakk å pakke ned soveteltet før vi forlot campinga, men etter førsteløpet i agility (som ble disk) kom øse-pøse. Vi var heldige og slapp å gå hoppløpet i det verste regnet, men telt og klær var gjennomtrukket av vann. Ikke gøy å pakke masse vått utstyr i bilen, men slik ble det denne gangen.

Hoppen ble også disk. Rambo var på vei inn i riktig inngang av to rør som lå like ved hverandre, men så plutselig noe spennende gjennom den andre (strake) røret, og kastet seg til side akkurat tidsnok til å diske seg. Skulle vært keeper-puddel den gutten. Så ble han stående bom i ro i røret og kikke ut, sikkert til stor glede for publikum. Etter at han bestemte seg for å bli med videre ble det litt improvisering og raskeste vei ut av banen.

Status etter helga: 2 napp i A2, 4 napp i H2.

 

[picasaview album=»Nesstevnet2011″ instantview=»true»]