PhoneGap Bygg en flödesläsare - Application Logic

Det här är den andra delen av serien om Audero Feed Reader. I den här artikeln kommer vi att gräva in i företagslogiken för vår applikation och ge ytterligare bakgrund på plugin och API som används för vårt projekt.


1. Plugin & API Översikt

Anmälningsplugin

På flera punkter inom Audero Feed Reader app vi ska använda varna() Metoden för anmälningsplugin. Hur varningen kommer att visas beror verkligen på vilken plattform appen ska köras på. Faktum är att de flesta operativsystem som stöds använder en inbyggd dialogruta, men andra, som Bada 2.x, använder den klassiska webbläsarens varna() funktion, vilket är mindre anpassningsbart. Denna metod accepterar upp till fyra parametrar:

  1. meddelande: En sträng som innehåller meddelandet som ska visas.
  2. alertCallback: En återuppringning för att åberopa när varningsdialogrutan avvisas.
  3. titel: Titeln på dialogrutan (standardvärdet är "alert").
  4. button: Knappens text ingår i dialogrutan (standardvärdet är "OK")

Tänk på att Windows Phone 7 och 8 inte har en inbyggd webbläsarvarning. Så, om du vill använda alert ( 'meddelande');, du måste tilldela window.alert = navigator.notification.alert.

InAppBrowser Plugin

I den första delen av denna serie nämnde jag att en intressant punkt på kreditsidan är attributet target = "_ blank" appliceras på länkarna. Det här avsnittet kommer att förklara hur openLinksInApp () metod för Ansökan klassarbeten.

InAppBrowser är en webbläsare som visas i din app när du använder window.open ring upp. Som jag sa i första delen, från version 2.3.0, har den två nya metoder: executeScript () och insertCSS (). För närvarande tillhandahåller detta plugin följande fem metoder:

  • addeventlistener (): Tillåter användaren att lyssna på tre händelser (loadstart, loadstop, och utgång) och att bifoga en funktion som körs så snart händelserna är avfyrade.
  • removeEventListener (): Används för att ta bort en tidigare bifogad lyssnare.
  • stänga(): Används för att stänga InAppBrowser-fönstret.
  • executeScript (): Aktiverar injektion av JavaScript-kod i InAppBrowser fönster.
  • executeScript (): Aktiverar injektion av CSS-kod i InAppBrowser fönster.

Om du inte använde Cordova i flera månader, eller om du håller dig till version 2.0.0, kommer du ihåg att det som standard öppnade externa länkar i samma Cordova WebView som körde programmet. När en extern sida besöktes visades därför den senast visade sidan exakt som före användaren lämnade den. Från den versionen är det inte längre standardbeteendet. Faktum är att externa länkar öppnas nu med Cordova WebView om webbadressen finns i din apps vita lista. Webbadresser som inte finns på din vita lista öppnas med InAppBrowser-plugin (mer om detta i dokumentationen). Men vad betyder det praktiskt taget? Det betyder att om du inte hanterar länkarna korrekt och om dina apps användare klickar på en länk och sedan återgår till programmet, går alla jQuery Mobile eller andra sådana förbättringar förlorade. Detta händer eftersom alla CSS- och JavaScript-filerna bara laddas på huvudsidan och de efterföljande webbadresserna laddas med AJAX (standardsystemet adopterat av jQuery Mobile).

Åtgärden för problemet är implementerad i openLinksInApp () metod. Faktum är att lösningen är att fånga klick på alla externa länkar genom att ställa in target = "_ blank" attribut, förhindra oönskade standardbeteenden och öppna länkarna med hjälp av window.open () metod. För att kunna fungera kräver den här lösningen att du anger en vit lista i konfigurationsfilen.

Google Feed API

Innan vi pratar om klasserna av Audero Feed Reader, vi måste gräva in i den magiska världen av Google Feed API och Google Feed JSON-gränssnittet eftersom vi använder dem inom kärnan i vår applikation. Som jag påpekade i den första delen av denna serie analyserar gränssnittet ett RSS- eller ATOM-flöde och returnerar ett enhetligt och lätt att parsa JSON-objekt. Självklart kan vi lyckligtvis hantera det här JSON-objektet med hjälp av JavaScript.

Detta gränssnitt stöder två frågetyper: Hitta flöde och ladda flöde. De första sökningarna efter flöden baserat på de angivna sökorden passerade som ett argument, medan den andra söker efter flöden baserat på en tillförd webbadress. I vår ansökan använder vi bara funktionen Load Feed.

Varje förfrågan till detta Google API måste skicka minst två parametrar: v och q. Ja, de har mycket kryptiska namn! Den första parametern, v, specificerar protokollversionsnumret. Vid det här skrivandet är det enda giltiga värdet "1.0". I den andra parametern, q, Vi skickar URL-adressen för att analysera. Utöver dessa kommer vår applikation också att använda num parameter. Dokumentationen specificerar antalet poster som ska laddas från det flöde som anges av q. Ett värde på -1 anger det maximala antalet bidrag som stöds, för närvarande 100. Som standard returnerar matningen fyra resultat. Så det är viktigt att vi implementerar vår funktion för att ladda 10 inlägg som standard och sedan öka med ytterligare 10 varje gång användaren är skyldig att visa mer.

Nu när du är medveten om hur vi kommer att fråga Google-tjänsten är det viktigt att klargöra vilket resultat det kommer att returnera. Om URL-adressen vi angav var korrekt, hittar vi inmatningarna av flödet inuti responseData.feed.entries fast egendom. Varje post har mycket information, men vi använder bara några av dem. I synnerhet skriver vi ut följande egenskaper:

  • titel: Uppgiftstiteln.
  • länk: URL: n för HTML-versionen av posten.
  • författare: Författaren till posten.
  • contentSnippet: Ett utdrag av mindre än 120 tecken i innehållsattributet. Klippet innehåller inga HTML-taggar.

De uppgifter jag har angett ovan är tillräckliga för vår ansökan, men om du vill lära dig mer, ta en titt på Google Feed-dokumentationen.


2. Bygga matningsklassen

Det här avsnittet beskriver Utfodra klass och dess metoder, som alla ingår i feed.js fil. Som jag påpekade i föregående del sparar vi bara två fält för varje flöde: titeln och webbadressen. Så, accepterar denna klass dessa två datapunkter som parametrar. Inuti skapar vi två privata egenskaper: _db och _tableName. Kom ihåg att JavaScript inte har egendoms- och metodvisningsändringsmedel, så vi emulerar faktiskt privat data.

Den första är en genväg till lokalt utrymme egenskapen hos fönster objekt. Det brukar komma åt de metoder som exponeras av lagringsplugin, som vår app är baserad på, och som vi ska använda för att lagra flödena. Den andra är en sträng som innehåller namnet på nyckeln där vi ska spara data. I själva verket återkallar lagringsspecifikationerna, lagras den data med hjälp av ett nyckelvärdesformat. Därför behöver vi JSON-ify för att lagra vårt utbud av flöden. Det är precis vad vår spara() metoden kommer att göra. På samma sätt, för att hämta data måste vi analysera en JSON-sträng för att göra det till ett objekt. Denna uppgift uppnås av ladda() metod. Dessa metoder är de enda två som behöver vara inne i klassdefinitionen eftersom de använder privata fastigheter.

Den relativa delen av feed.js filen anges nedan:

 funktion Feed (namn, url) var _db = window.localStorage; var _tableName = 'feed'; this.name = name; this.url = url; this.save = function (feeds) _db.setItem (_tableName, JSON.stringify (feeds)); ; this.load = function () returnera JSON.parse (_db.getItem (_tableName)); ; 

Runt dessa två enkla metoder skapar vi en massa andra vanliga. Specifikt bygger vi några exempel metoder som Lägg till(), för att lägga till ett nytt flöde, radera(), för att radera ett flöde, och jämföra med(), för att jämföra en flödesinstans med ett annat flöde. Utöver dessa utvecklar vi också vissa statiska metoder som getFeeds () för att hämta alla flöden från lagring, getFeed () att hämta bara en, och jämföra() att jämföra två objekt.

Jämförelsemetoderna är värda en liten diskussion för att förstå på vilket sätt vi jämför dem Jag hoppar över beskrivningen av jämföra med() för att det inte gör annat än att kalla sin statiska motsvarighet, jämföra(), det gör jobbet egentligen. I det testar vi först om något av de givna värdena är nullliknande. Om ingen av dem är nullliknande, jämför vi lexikografiskt deras namn och, om de är lika, jämför deras URL. Men som du kommer att upptäcka senare, tvingar vi användaren att aldrig ha två flöden med samma namn eller URL.
De jämföra() Metoden är viktig eftersom den definierar hur vi jämför två flöden och det är avgörande att fastställa hur flödena sorteras i list-feeds.html sida. Faktum är att vi använder den infödda sortera() array-metod som accepterar en valfri parameter, en funktion som definierar sorteringsordningen för arrayen baserat på dess returvärden.

Koden som implementerar vad jag beskrivit är följande:

 Feed.prototype.compareTo = funktion (andra) returnera Feed.compare (detta, annat); ; Feed.compare = funktion (feed, other) if (other == null) return 1;  om (feed == null) return -1;  var test = feed.name.localeCompare (andra.namn); returnera (test === 0)? feed.url.localeCompare (other.url): test; ;

Utöver de metoder som hittills hittats skapar vi två sökmetoder som vi ska använda för att hitta och ta bort ett visst flöde: searchByName () och searchByUrl (). Den sista metoden jag vill markera är getIndex (), och det är det som brukade hämta indexet för en viss fil.

Nu när vi har upptäckt alla detaljer i den här klassen kan jag lista hela källkoden för filen:

 funktion Feed (namn, url) var _db = window.localStorage; var _tableName = 'feed'; this.name = name; this.url = url; this.save = function (feeds) _db.setItem (_tableName, JSON.stringify (feeds)); ; this.load = function () returnera JSON.parse (_db.getItem (_tableName)); ;  Feed.prototype.add = function () var index = Feed.getIndex (detta); var feeds = Feed.getFeeds (); om (index === false) feeds.push (this);  else feeds [index] = this;  this.save (feeds); ; Feed.prototype.delete = function () var index = Feed.getIndex (this); var feeds = Feed.getFeeds (); om (index! == false) feeds.splice (index, 1); this.save (feeds);  returnera flöden; ; Feed.prototype.compareTo = funktion (andra) returnera Feed.compare (detta, annat); ; Feed.compare = funktion (feed, other) if (other == null) return 1;  om (feed == null) return -1;  var test = feed.name.localeCompare (andra.namn); returnera (test === 0)? feed.url.localeCompare (other.url): test; ; Feed.getFeeds = function () var feeds = nytt Feed (). Load (); returnera (feeds === null)? []: matar; ; Feed.getFeed = funktion (feed) var index = Feed.getIndex (feed); om (index === false) return null;  var feed = Feed.getFeeds () [index]; returnera nytt flöde (feed.name, feed.url); ; Feed.getIndex = funktion (feed) var feeds = Feed.getFeeds (); för (var i = 0; i < feeds.length; i++)  if (feed.compareTo(feeds[i]) === 0)  return i;   return false; ; Feed.deleteFeeds = function ()  new Feed().save([]); ; Feed.searchByName = function (name)  var feeds = Feed.getFeeds(); for (var i = 0; i < feeds.length; i++)  if (feeds[i].name === name)  return new Feed(feeds[i].name, feeds[i].url);   return false; ; Feed.searchByUrl = function (url)  var feeds = Feed.getFeeds(); for (var i = 0; i < feeds.length; i++)  if (feeds[i].url === url)  return new Feed(feeds[i].name, feeds[i].url);   return false; ;

3. Bygga applikationsklassen

I det här avsnittet diskuteras projektets andra och sista klass, Ansökan, Innehållet i application.js fil. Syftet är att initiera sidans layout, bifoga händelser till appsidans element och använda Utfodra klass för att spara, ladda och hämta flöden.

Denna klass är organiserad för att ha ingångspunkten i initApplication () metod. Det heter så snart Cordova har initierats och dess API är redo att agera. Inom den här metoden bifogar vi en specifik hanterare för varje sidinitiering så att vi kan hantera händelser som utlöses av deras widgets. I det ringer vi också Application.openLinksInApp () av skäl som diskuterats tidigare. För att förbättra användarupplevelsen får vi dessutom varje tryckning på den fysiska tillbaka-knappen (där den finns) för att omdirigera användaren till vår apps hemsida.

Kärnfunktionen i vår ansökan är initShowFeedPage () eftersom det använder Google Feed JSON-gränssnittet. Innan vi skickar begäran till tjänsten räknar vi med antalet poster som redan är laddade (currentEntries variabel) och beräkna hur många poster tjänsten måste hämta (entriesToShow variabel). Då kör vi AJAX-förfrågan med jQuery ajax () metod, och samtidigt visar vi sidladdnings widget till användaren. När framgångsgenomgången körs testar vi först om antalet returnerade poster är desamma som numret som redan visas, i vilket fall vi visar meddelandet "Inga fler inmatningar att ladda". Annars lägger vi till dem i listan och uppdaterar dragspelet widgeten ($ List.collapsibleset (refresh)). Tillsammans med varje inmatning bifogar vi också en hanterare till knappen som skapas, så om anslutningen är av visas ett meddelande istället för att komma åt sidan.

Slutligen, den updateIcons () Metoden kommer att diskuteras i nästa och sista delen av serien.

Koden som implementerar den diskuterade klassen är listad nedan:

 var Application = initApplication: function () $ (dokument) .on ('pageinit', '# add-feed-page', funktion () Application.initAddFeedPage ();) .on ('pageinit' # list-feeds-page ', funktion () Application.initListFeedPage ();) .on (' pageinit ',' # show-feed-page ', funktion () var url = this.getAttribute url ') .on (' pageinit ',' # aurelio-page ', funktion () Application.initAurelioPage (url) ();) .on ('backbutton', funktion () $ .mobile.changePage ('index.html');); Application.openLinksInApp ();, initAddFeedPage: function () $ add-feed-form "). Skicka (funktion (händelse) event.preventDefault (); var feedName = $ ('# feed-name') .val (). trim (); var feedUrl = $ ('# feed -url ') .val (). trim (); om (feedName === ") navigator.notification.alert (' Namnfältet är obligatoriskt och kan inte vara tomt ', funktion () ,' Fel ') returnera false; if (feedUrl === ") navigator.notification.alert ('URL-fältet är obligatoriskt och kan inte vara tomt', funktion () , "Fel"); returnera false;  om (Feed.searchByName (feedName) === false && Feed.searchByUrl (feedUrl) === false) var feed = new Feed (feedName, feedUrl); feed.add (); navigator.notification.alert ("Feed saved correctly", funktion () $ .mobile.changePage ('index.html');, 'Framgång');  else navigator.notification.alert ('Feed not saved!' antingen namnet eller den angivna urlningen är redan i bruk ', funktion () ,' fel ');  returnera false; ); , initListFeedPage: function () var $ feedsList = $ ('# feeds-list'); var items = Feed.getFeeds (); var htmlItems = "; $ feedsList.empty (); items = items.sort (Feed.compare); för (var i = 0; i < items.length; i++)  htmlItems += '
  • '+ objekt [i] .name +'
  • '; $ feedsList.append (htmlItems) .listview ('refresh'); , initShowFeedPage: funktion (url) var steg = 10; var loadFeed = function () var currentEntries = $ ('# feed-entries'). hitta ('div [data-roll = collapsible]'). längd; var entriesToShow = currentEntries + step; $ .ajax (url: 'https://ajax.googleapis.com/ajax/services/feed/load?v=1.0&num=' + entriesToShow + '& q =' + encodeURI (url), dataType: 'json' , föreSänd: funktion () $ .mobile.loading ('show', text: 'Vänta medan du hämtar data ...', textVisible: true);, framgång: funktion (data) var $ list = $ '# feed-entries'); om (data.responseData === null) navigator.notification.alert ('Kan inte hämta flödet. Ogiltig URL', funktion () , 'Fel'); var items = data.responseData.feed.entries; var $ post; if (currentEntries === items.length) navigator.notification.alert ('Inga fler poster att ladda', funktion () , 'Info') ; return; för (var i = currentEntries; i < items.length; i++) $post = $('
    '); $ post .append ($ ('

    ') .text (items [i] .title)) .append ($ ('

    ') .html (' '+ objekt [i] .title +' ')) // Lägg till titel .append ($ ('

    ') .html (objekt [i] .contentSnippet)) // Lägg till beskrivning .append ($ ('

    ') .text (' Författare: '+ objekt [i] .author)) .append ($ (' ') .text (' Gå till artikeln ') .knapp () .click (funktion Application.checkRequirements () === false) event.preventDefault (); navigator.notification.alert ('Anslutningen är avstängd, sätt den på', funktion () , 'Fel'); returnera false; $ (detta) .removeClass ('ui-btn-active');)); $ List.append ($ post); $ list.collapsibleset ('refresh'); , fel: funktion () navigator.notification.alert ('Kan inte hämta feedet. Försök senare', funktion () , 'Fel'); , komplett: funktion () $ .mobile.loading ('hide'); ); ; $ ('# show-more-entries'). klicka (funktion () loadFeed (); $ (detta) .removeClass ('ui-btn-active');); $ ('# delete-feed'). Klicka på (funktion () Feed.searchByUrl (url) .delete (); navigator.notification.alert ('Feed deleted', function () $ .mobile.changePage -feeds.html ');,' Framgång ');); om (Application.checkRequirements () === true) loadFeed (); annars navigator.notification.alert ('För att använda den här appen måste du aktivera din internetanslutning', funktion () , 'Varning'); , initAurelioPage: funktion () $ ('a [target = _blank]'). klicka (funktion () $ (detta) .closest ('li'). removeClass ('ui-btn-active'); ); , checkRequirements: function () if (navigator.connection.type === Connection.NONE) return false; returnera sant; , updateIcons: function () var $ buttons = $ ('en [data-ikon], knapp [data-ikon]'); var isMobileWidth = ($ (fönster) .width () <= 480); isMobileWidth ? $buttons.attr('data-iconpos', 'notext') : $buttons.removeAttr('data-iconpos'); , openLinksInApp: function () $(document).on('click', 'a[target=_blank]', function (event) event.preventDefault(); window.open($(this).attr('href'), '_blank'); ); ;


    Slutsats

    I den tredje och sista delen av den här serien ser vi hur man bygger och testa installatörerna med hjälp av CLI och Adobe PhoneGap Build.