Blog tech

MacRuby - Introduction à HotCocoa - Ajout de fonctionnalités

Rédigé par Nicolas Cavigneaux | 29 mars 2011

Dans nos précédents articles concernant HotCocoa nous avons vu les bases de la création d’un projet ainsi que la mise en place de l’interface utilisateur. Pour l’heure, les interactions utilisateurs sont gérées de manière factices. Nous allons aujourd’hui mettre en place les routines réelles de récupération et d’affichage des flux RSS / ATOM.

Préparation

Le but est de récupérer un flux RSS (ou ATOM) depuis l’adresse fournie par l’utilisateur. Plutôt que d’analyser nous même le flux récupéré nous allons utiliser un gem pour nous faciliter la tâche. En effet, il est tout à fait possible d’utiliser des gems écrits pour Ruby MRI depuis MacRuby. Les deux interpréteurs étant compatibles, vous ne devriez pas rencontrer de problèmes hormis pour certains gems écrits en C et non en Ruby pur.

J’ai choisi d’utiliser FeedParser qui suffira largement à notre utilisation. Avant de pouvoir l’utiliser il faut bien évidemment l’installer, nous allons donc passer par macgem (version dédiée à la gestion des gems pour MacRuby) :

macgem install ruby-feedparser

Nous pouvons maintenant passer à l’écriture du code. Vous retrouverez le code complet sur notre compte GitHub.

require 'rubygems'
require 'hotcocoa'
require 'feedparser'
require 'open-uri'

class Application

  include HotCocoa

  def start
    application :name => "SynbiozFeeds" do |app|
      app.delegate = self
      window(:size => [640, 480], :center => true, :title => "Synbioz Feed", :view => :nolayout) do |win|
        win.will_close { exit }

        win.view = layout_view(:layout => {:expand => [:width, :height], :padding => 0, :margin => 0}) do |vert|
          vert << layout_view(:frame => [0, 0, 0, 40], :mode => :horizontal, :layout => {:padding => 0, :margin => 0, :start => false, :expand => [:width]}) do |horiz|
            horiz << label(:text => "Flux RSS", :layout => {:align => :center})
            horiz << @feed_field = text_field(:layout => {:expand => [:width]})
            horiz << button(:title => 'lire', :layout => {:align => :center}) do |b|
              b.on_action { load_feed }
            end
          end

          vert << scroll_view(:layout => {:expand => [:width, :height]}) do |scroll|
            scroll.setAutohidesScrollers(true)
            scroll << @table = table_view(:columns => [column(:id => :data, :title => '')], :data => []) do |table|
               table.setUsesAlternatingRowBackgroundColors(true)
               table.setGridStyleMask(NSTableViewSolidHorizontalGridLineMask)
               table.on_action do
                 url = NSURL.URLWithString(table.dataSource.data[table.clickedRow][:link])
                 NSWorkspace.sharedWorkspace.openURL(url)
               end
            end
          end
        end
      end
    end
  end

  def load_feed
    url = @feed_field.stringValue
    unless url.nil? || url =~ /^\s*$/
      feed = FeedParser::Feed.new open(url).read
      latest_posts = feed.items[0..9]

      latest_posts.each do |p|
        @table.dataSource.data << {:data => "#{p.title} par #{p.creators.join(', ')}", :link => p.link}
      end
      @table.reloadData
    end
  end
end

Application.new.start

Détaillons les quelques changements qui rendent notre application fonctionnelle.

Chargement des données d’un flux

L’idée étant de pouvoir entrer l’URL d’un flux RSS ou ATOM pour en récupérer les 10 dernières entrées et les afficher dans un tableau par ordre chronologique inverse. Il faut donc pouvoir lire les données distante (flux XML) et les interpréter.

Voici comment procéder :

def load_feed
  url = @feed_field.stringValue
  unless url.nil? || url =~ /^\s*$/
    feed = FeedParser::Feed.new open(url).read
    latest_posts = feed.items[0..9]

    latest_posts.each do |p|
      @table.dataSource.data << {:data => "#{p.title} par #{p.creators.join(', ')}", :link => p.link}
    end
    @table.reloadData
  end
end

Il faut commencer par modifier notre méthode load_feed.

On récupère l’URL du flux entré par l’utilisateur qu’on lit directement grâce à open. open fournit par open-uri permet, entre autre, de lire directement le contenu d’une réponse HTTP. On obtient donc une chaîne contenant notre flux RSS / ATOM.

Maintenant entre en jeu FeedParser qui va nous retourner une structure de données contenant les articles ainsi que toutes les infos dont on peut avoir besoin.

À la ligne suivante, on ne conserve que les 10 derniers articles parus. On parcourt ensuite ces articles pour mettre à jour le tableau qui sera affiché à l’utilisateur. Pour chaque article, on crée une ligne dans laquelle on affiche le titre et l’auteur.

Vous noterez qu’en plus des données (clé :data), on passe également le lien (clé :link) vers la page web de l’article. Ceci sera utilisé pour ouvrir le navigateur de l’utilisateur lorsqu’il cliquera sur un titre d’article.

Voici le résultat obtenu :

Interaction utilisateur

Modifions maintenant le code qui génère notre tableau :

vert << scroll_view(:layout => {:expand => [:width, :height]}) do |scroll|
  scroll.setAutohidesScrollers(true)
  scroll << @table = table_view(:columns => [column(:id => :data, :title => '')], :data => []) do |table|
     table.setUsesAlternatingRowBackgroundColors(true)
     table.setGridStyleMask(NSTableViewSolidHorizontalGridLineMask)
     table.on_action do
       url = NSURL.URLWithString(table.dataSource.data[table.clickedRow][:link])
       NSWorkspace.sharedWorkspace.openURL(url)
     end
  end
end

Seul un bloc d’instructions a été ajouté. Il permet, lorsque l’utilisateur clique sur une ligne de résultat, de charger l’article dans le navigateur par défaut.

On définie donc le comportement de l’action via la méthode on_action qui prend un bloc en paramètre. Dans ce bloc, on récupère depuis la source de données l’adresse associée à la ligne cliquée : table.dataSource.data[table.clickedRow][:link]. On utilise ensuite NSURL.URLWithString() sur la chaîne récupérée pour obtenir une URL facilement manipulable par les classes et méthodes Cocoa.

Il ne nous reste plus maintenant qu’à demander l’ouverture de l’URL dans le navigateur par défaut. Cocoa nous propose une classe et des méthodes pour le faire en toute simplicité : NSWorkspace.sharedWorkspace.openURL(url)

Conclusion

Avec seulement quelques lignes de code modifiées, on passe d’une maquette sans vie à quelque chose de fonctionnel.

On voit clairement que grâce à la concision de Ruby et la richesse de Cocoa, il est très simple de prototyper une application pour ensuite lui ajouter des fonctionnalités. Je trouve réellement plaisant de pouvoir écrire des applications Mac natives si facilement et de surcroît à l’aide de mon langage préféré sans pour autant sacrifier les performances.

L’équipe Synbioz.

Libres d’être ensemble.