SlideShare a Scribd company logo
1 of 236
Download to read offline
Introduction to Jifty
Sterling Hanenkamp, PPW 2008
I Work for a Living
I Work for a Living

• Contract work / Billable Hours: How do I track them?
I Work for a Living

• Contract work / Billable Hours: How do I track them?


• Weekly Status Reports: What did I do?
I Work for a Living

• Contract work / Billable Hours: How do I track them?


• Weekly Status Reports: What did I do?


• Reliable Memory: Did I do that? How? When? Why?
I Work for a Living

• Contract work / Billable Hours: How do I track them?


• Weekly Status Reports: What did I do?


• Reliable Memory: Did I do that? How? When? Why?


• Task Lists: What should I do next?
Existing Solutions

                     Time Sheet
Existing Solutions

                     Planner
Existing Solutions

                     Scratch Paper
Existing Solutions
Existing Solutions

                                        Things
 Sunbird
                     Day Planner

      Hiveminder
                               Entourage

                     Stikkit
                                    iCal/Apple Mail
      Lightning
                          OmniFocus
Qublog
Qublog

• I don’t want to store this information in four places
Qublog

• I don’t want to store this information in four places


• I have a habit of writing down what I’m doing to focus
Qublog

• I don’t want to store this information in four places


• I have a habit of writing down what I’m doing to focus


• I needed something else to do with my lack of spare time
Qublog

• I don’t want to store this information in four places


• I have a habit of writing down what I’m doing to focus


• I needed something else to do with my lack of spare time


• I built Qublog (originally MyToDo and Kiln for a short while)
Qublog

• I don’t want to store this information in four places


• I have a habit of writing down what I’m doing to focus


• I needed something else to do with my lack of spare time


• I built Qublog (originally MyToDo and Kiln for a short while)


• It’s written using Jifty
Qublog

• I don’t want to store this information in four places


• I have a habit of writing down what I’m doing to focus


• I needed something else to do with my lack of spare time


• I built Qublog (originally MyToDo and Kiln for a short while)


• It’s written using Jifty


• We’re going to look at how I did it to explain Jifty
What’s it look like?
What’s it look like?
What’s it look like?
What’s it look like?
What’s it look like?
What’s it look like?
What’s it look like?
What’s it look like?
What’s it look like?
What’s it look like?
What’s it look like?
What’s it look like?
What’s it look like?
What’s it look like?
What’s it look like?
Qublog
Qublog

• Provides a Journal for keeping thoughts
Qublog

• Provides a Journal for keeping thoughts


• Provides a To Do List for organizing tasks
Qublog

• Provides a Journal for keeping thoughts


• Provides a To Do List for organizing tasks


• Journal Comments are grouped into Timers that keep track of time spent
Qublog

• Provides a Journal for keeping thoughts


• Provides a To Do List for organizing tasks


• Journal Comments are grouped into Timers that keep track of time spent


• Timers are grouped into Entries to sum up work for reporting
Qublog

• Provides a Journal for keeping thoughts


• Provides a To Do List for organizing tasks


• Journal Comments are grouped into Timers that keep track of time spent


• Timers are grouped into Entries to sum up work for reporting


• Comments also connect with Tasks to keep track of what’s next
This is a talk about Jifty
This is a talk about Jifty

• Not Qublog


• Okay, so let’s see if we can build QublogMini in the time remaining...
Install Jifty




  As with “that-other-framework,”
  the worst part is over when you
          finish the install.
Install Jifty
Install Jifty

• Method 1: Shipwright
Install Jifty

• Method 1: Shipwright
    svn co http://code.bestpractical.com/shipwright/jifty/ jifty-builder
    cd jifty-builder
    ./bin/shipwright-builder # optionally --skip perl --skip-test
    cp -rvp /tmp/jifty-$random/jifty /usr/local/jifty
    ln -s /usr/local/jifty/bin/jifty /usr/local/bin/jifty
Install Jifty

• Method 1: Shipwright
    svn co http://code.bestpractical.com/shipwright/jifty/ jifty-builder
    cd jifty-builder
    ./bin/shipwright-builder # optionally --skip perl --skip-test
    cp -rvp /tmp/jifty-$random/jifty /usr/local/jifty
    ln -s /usr/local/jifty/bin/jifty /usr/local/bin/jifty

• Method 2: CPAN
Install Jifty

• Method 1: Shipwright
    svn co http://code.bestpractical.com/shipwright/jifty/ jifty-builder
    cd jifty-builder
    ./bin/shipwright-builder # optionally --skip perl --skip-test
    cp -rvp /tmp/jifty-$random/jifty /usr/local/jifty
    ln -s /usr/local/jifty/bin/jifty /usr/local/bin/jifty

• Method 2: CPAN
    perl -MCPAN -eshell # or just cpan
    cpan> notest install Jifty
Install Jifty

• Method 1: Shipwright
    svn co http://code.bestpractical.com/shipwright/jifty/ jifty-builder
    cd jifty-builder
    ./bin/shipwright-builder # optionally --skip perl --skip-test
    cp -rvp /tmp/jifty-$random/jifty /usr/local/jifty
    ln -s /usr/local/jifty/bin/jifty /usr/local/bin/jifty
                     “notest” saves ~60 minutes according to Audrey
• Method 2: CPAN
    perl -MCPAN -eshell # or just cpan
    cpan> notest install Jifty
Install Jifty

• Method 1: Shipwright
    svn co http://code.bestpractical.com/shipwright/jifty/ jifty-builder
    cd jifty-builder
    ./bin/shipwright-builder # optionally --skip perl --skip-test
    cp -rvp /tmp/jifty-$random/jifty /usr/local/jifty
    ln -s /usr/local/jifty/bin/jifty /usr/local/bin/jifty
                     “notest” saves ~60 minutes according to Audrey
• Method 2: CPAN
    perl -MCPAN -eshell # or just cpan
    cpan> notest install Jifty




                    “notest” will get you no support according to Jesse
Install Jifty

• Method 1: Shipwright
    svn co http://code.bestpractical.com/shipwright/jifty/ jifty-builder
    cd jifty-builder
    ./bin/shipwright-builder # optionally --skip perl --skip-test
    cp -rvp /tmp/jifty-$random/jifty /usr/local/jifty
    ln -s /usr/local/jifty/bin/jifty /usr/local/bin/jifty

• Method 2: CPAN
    perl -MCPAN -eshell # or just cpan
    cpan> notest install Jifty
Install Jifty

• Method 1: Shipwright
    svn co http://code.bestpractical.com/shipwright/jifty/ jifty-builder
    cd jifty-builder
    ./bin/shipwright-builder # optionally --skip perl --skip-test
    cp -rvp /tmp/jifty-$random/jifty /usr/local/jifty
    ln -s /usr/local/jifty/bin/jifty /usr/local/bin/jifty

• Method 2: CPAN
    perl -MCPAN -eshell # or just cpan
    cpan> notest install Jifty

• ActiveState installation on Windows: http://jifty.org/view/DownloadJifty
Install Jifty

• Method 1: Shipwright
    svn co http://code.bestpractical.com/shipwright/jifty/ jifty-builder
    cd jifty-builder
    ./bin/shipwright-builder # optionally --skip perl --skip-test
    cp -rvp /tmp/jifty-$random/jifty /usr/local/jifty
    ln -s /usr/local/jifty/bin/jifty /usr/local/bin/jifty

• Method 2: CPAN
    perl -MCPAN -eshell # or just cpan
    cpan> notest install Jifty

• ActiveState installation on Windows: http://jifty.org/view/DownloadJifty
• Jifty is in Debian unstable
% jifty app --name QublogMini
% jifty app --name QublogMini
Creating new application QublogMini
Creating directory QublogMini/lib
Creating directory QublogMini/lib/QublogMini
Creating directory QublogMini/bin
Creating directory QublogMini/etc
Creating directory QublogMini/doc
Creating directory QublogMini/log
Creating directory QublogMini/var
Creating directory QublogMini/var/mason
Creating directory QublogMini/share
Creating directory QublogMini/share/po
Creating directory QublogMini/share/web
Creating directory QublogMini/share/web/templates
Creating directory QublogMini/share/web/static
Creating directory QublogMini/lib/QublogMini/Model
Creating directory QublogMini/lib/QublogMini/Action
Creating directory QublogMini/t
Creating configuration file QublogMini/etc/config.yml
% cd QublogMini
% find . -type f
% cd QublogMini
% find . -type f
./bin/jifty
./etc/config.yml
./Makefile.PL
% ./bin/jifty server
% ./bin/jifty server
WARN - Application schema has no version in the database.
WARN - Automatically creating your database.
INFO - Generating SQL for application QublogMini...
INFO - Using Jifty::Model::Session, as it appears to be new.
INFO - Using Jifty::Model::Metadata, as it appears to be new.
INFO - Set up version v0.0.1, jifty version 0.804080
INFO - You can connect to your server at http://localhost:8888/
% ./bin/jifty server
WARN - Application schema has no version in the database.
WARN - Automatically creating your database.
INFO - Generating SQL for application QublogMini...
INFO - Using Jifty::Model::Session, as it appears to be new.
INFO - Using Jifty::Model::Metadata, as it appears to be new.
INFO - Set up version v0.0.1, jifty version 0.804080
INFO - You can connect to your server at http://localhost:8888/
Take a Look
OMG! Ponies!!!
Not a very interesting journaling app... let’s fix it.
% ./bin/jifty model --name Comment
% ./bin/jifty model --name Comment
Writing file t/00-model-Comment.t
Writing file lib/QublogMini/Model/Comment.pm
% ./bin/jifty model --name Comment
Writing file t/00-model-Comment.t
Writing file lib/QublogMini/Model/Comment.pm

% vim lib/QublogMini/Model/Comment.pm
package QublogMini::Model::Comment;
use Jifty::DBI::Schema;

use QublogMini::Record schema {




};
package QublogMini::Model::Comment;
use Jifty::DBI::Schema;

use QublogMini::Record schema {
    column commented_on =>
        type is 'datetime',
        label is 'Commented on',
        filters are qw(
            Jifty::DBI::Filter::DateTime
        );

     column name =>
         type is 'text',
         label is 'Name',
         render as 'textarea';
};
% bin/jifty server
% bin/jifty server
WARN - Application schema has no version in the database.
WARN - Automatically creating your database.
INFO - Generating SQL for application QublogMini...
INFO - Using QublogMini::Model::Comment, as it appears to be
new.
INFO - Using Jifty::Model::Session, as it appears to be new.
INFO - Using Jifty::Model::Metadata, as it appears to be new.
INFO - Set up version v0.0.1, jifty version 0.804080
INFO - You can connect to your server at http://localhost:
8888/
% bin/jifty model --name Timer
% bin/jifty model --name Timer
Writing file lib/QublogMini/Model/Timer.pm
Writing file t/00-model-Timer.t
% bin/jifty model --name Timer
Writing file lib/QublogMini/Model/Timer.pm
Writing file t/00-model-Timer.t

% vim lib/QublogMini/Model/Timer.pm
package QublogMini::Model::Timer;
use Jifty::DBI::Schema;

use QublogMini::Record schema {




};
package QublogMini::Model::Timer;
     use Jifty::DBI::Schema;

use QublogMini::Record schema {
    column started_on =>
        label is 'Started on',
        filters are qw( Jifty::DBI::Filter::DateTime );

      column stopped_on =>
          label is 'Stopped on',
          filters are qw( Jifty::DBI::Filter::DateTime );

      column comments =>
          references QublogMini::Model::CommentCollection
              by 'timer';

};

sub since { '0.0.2' }
package QublogMini::Model::Comment;
use Jifty::DBI::Schema;

use QublogMini::Record schema {
    column commented_on =>
        type is 'datetime',
        label is 'Commented on',
        filters are qw( Jifty::DBI::Filter::DateTime ),
        ;

     column name =>
         type is 'text',
         label is 'Name',
         render as 'textarea',
         ;




};
package QublogMini::Model::Comment;
use Jifty::DBI::Schema;

use QublogMini::Record schema {
    column commented_on =>
        type is 'datetime',
        label is 'Commented on',
        filters are qw( Jifty::DBI::Filter::DateTime ),
        ;

     column name =>
         type is 'text',
         label is 'Name',
         render as 'textarea',
         ;

     column timer =>
         label is 'Timer',
         since '0.0.2',
         references QublogMini::Model::Timer;
};
% bin/jifty server
% bin/jifty server
WARN - Application schema version in database (v0.0.1) doesn't match
application schema version (0.0.2)
WARN - Automatically upgrading your database to match the current
application schema.
Jifty version 0.809130 up to date.
Jifty::Plugin::LetMe version v0.0.1 up to date.
Jifty::Plugin::SkeletonApp version v0.0.1 up to date.
Jifty::Plugin::REST version v0.0.1 up to date.
Jifty::Plugin::Halo version v0.0.1 up to date.
Jifty::Plugin::ErrorTemplates version v0.0.1 up to date.
Jifty::Plugin::OnlineDocs version v0.0.1 up to date.
Jifty::Plugin::CompressedCSSandJS version v0.0.1 up to date.
Jifty::Plugin::AdminUI version v0.0.1 up to date.
INFO - Generating SQL to upgrade QublogMini v0.0.1 database to v0.0.2
INFO - Upgrading through 0.0.2
INFO - Running upgrade script
INFO - Upgraded to version v0.0.2
INFO - You can connect to your server at http://localhost:8888/
package QublogMini::Model::Timer;

# ...

sub name {
    my $self = shift;
    $self->started_on . ' to ' . $self->stopped_on
}
% bin/jifty model --name Entry
% bin/jifty model --name Entry
Writing file t/00-model-Entry.t
Writing file lib/QublogMini/Model/Entry.pm
% bin/jifty model --name Entry
Writing file t/00-model-Entry.t
Writing file lib/QublogMini/Model/Entry.pm

% vim lib/QublogMini/Model/Entry.pm
package QublogMini::Model::Entry;
use Jifty::DBI::Schema;

use QublogMini::Record schema {




};
package QublogMini::Model::Entry;
use Jifty::DBI::Schema;

use QublogMini::Record schema {
    column name =>
        type is 'text',
        label is 'Name',
        is mandatory;

     column url =>
         type is 'text',
         label is 'Name';

     column timers =>
         references QublogMini::Model::TimerCollection
             by 'entry';
};

sub since { '0.0.3' }
package QublogMini::Model::Timer;
use Jifty::DBI::Schema;

use QublogMini::Record schema {
    # ...

     column entry =>
         label is 'Entry',
         since '0.0.3',
         references QublogMini::Model::Entry;

     # ...
};

sub name {
    my $self = shift;
    my $name = '';
       $name .= $self->entry->name . ': ' if $self->entry->id;
       $name .= $self->started_on . ' to ' . $self->stopped_on
    return $name;
}
---
framework:
  AdminMode: 1
  ApplicationClass: QublogMini
  ApplicationName: QublogMini
  ApplicationUUID: 1198420A-95A9-11DD-8ED4-3E4643B9151E
  ConfigFileVersion: 4
  Database:
    AutoUpgrade: 1
    CheckSchema: 1
    Database: qublogmini
    Driver: SQLite
    Host: localhost
    Password: ''
    RecordBaseClass: Jifty::DBI::Record::Cachable
    User: ''
    Version: 0.0.2
---
framework:
  AdminMode: 1
  ApplicationClass: QublogMini
  ApplicationName: QublogMini
  ApplicationUUID: 1198420A-95A9-11DD-8ED4-3E4643B9151E
  ConfigFileVersion: 4
  Database:
    AutoUpgrade: 1
    CheckSchema: 1
    Database: qublogmini
    Driver: SQLite
    Host: localhost
    Password: ''
    RecordBaseClass: Jifty::DBI::Record::Cachable
    User: ''
    Version: 0.0.2
---
framework:
  AdminMode: 1
  ApplicationClass: QublogMini
  ApplicationName: QublogMini
  ApplicationUUID: 1198420A-95A9-11DD-8ED4-3E4643B9151E
  ConfigFileVersion: 4
  Database:
    AutoUpgrade: 1
    CheckSchema: 1
    Database: qublogmini
    Driver: SQLite
    Host: localhost
    Password: ''
    RecordBaseClass: Jifty::DBI::Record::Cachable
    User: ''
    Version: 0.0.3
% bin/jifty server
% bin/jifty server
WARN - Application schema version in database (v0.0.2) doesn't match
application schema version (0.0.3)
WARN - Automatically upgrading your database to match the current
application schema.
Jifty version 0.809130 up to date.
Jifty::Plugin::LetMe version v0.0.1 up to date.
Jifty::Plugin::SkeletonApp version v0.0.1 up to date.
Jifty::Plugin::REST version v0.0.1 up to date.
Jifty::Plugin::Halo version v0.0.1 up to date.
Jifty::Plugin::ErrorTemplates version v0.0.1 up to date.
Jifty::Plugin::OnlineDocs version v0.0.1 up to date.
Jifty::Plugin::CompressedCSSandJS version v0.0.1 up to date.
Jifty::Plugin::AdminUI version v0.0.1 up to date.
INFO - Generating SQL to upgrade QublogMini v0.0.2 database to v0.0.3
INFO - Upgrading through 0.0.3
INFO - Running upgrade script
INFO - Upgraded to version v0.0.3
INFO - You can connect to your server at http://localhost:8888/
Ponies are
  great!
Ponies are   But where’s the UI?
  great!
# Dispatcher maps URLs to Views
% vim lib/QublogMini/Dispatcher.pm
package QublogMini::Dispatcher;
use Jifty::Dispatcher -base;
package QublogMini::Dispatcher;
use Jifty::Dispatcher -base;

on '/' => dispatch '/today';
package QublogMini::Dispatcher;
use Jifty::Dispatcher -base;

on '/' => dispatch '/today';
on '/today' => run {
    my $entries = QublogMini::Model::EntryCollection->new;
    my $timer_alias = $entries->join(
        column1 => 'id',
        table2 => QublogMini::Model::Timer->table,
        column2 => 'entry',
    );
    $entries->limit(
        alias    => $timer_alias,
        column   => 'started_on',
        operator => '>=',
        value    => DateTime->today,
        entry_aggregator => 'AND',
    );
    $entries->limit(
        alias    => $timer_alias,
        column   => 'started_on',
        operator => '<',
        value    => DateTime->today->add( days => 1 ),
        entry_aggregator => 'AND',
    );
    set entries => $entries;
    show '/today';
};
package QublogMini::View;
use Jifty::View::Declare -base;

template '/today' => page {
    { title is quot;Today's Journalquot; }

     my $entries = get 'entries';
     while (my $entry = $entries->next) {
         h2 { $entry->name };

         p {
               hyperlink
                   label => $entry->url,
                   url => $entry->url
         };
     }
};
template '/today' => page {
       { title is quot;Today's Journalquot; }

      my $entries = get 'entries';
      while (my $entry = $entries->next) {
          h2 { $entry->name };

          p {
                hyperlink
                    label => $entry->url,
                    url => $entry->url
          };

          show '/timers', $entry;
      }
};
private template '/timers' => sub {
      my ($self, $entry) = @_;

      my $timers = $entry->timers;

      while (my $timer = $timers->next) {
          show '/show_comment', $timer->stopped_on, _('Stopped timer.')
              if $timer->stopped_on;
          show '/comments', $timer;
          show '/show_comment', $timer->started_on, _('Started timer.');
      }
};
private template '/comments' => sub {
      my ($self, $timer) = @_;

      my $comments = $timer->comments;

      while (my $comment = $comments->next) {
          show '/show_comment', $comment->commented_on, $comment->name;
      }
};

private template '/show_comment' => sub {
    my ($self, $time, $message) = @_;
    p { { class is 'time' } $time };
    p { { class is 'comment' } $message };
};
Fine. We already
knew that. Where’s the
      “interface”?
template '/today' => page {
      { title is quot;Today's Journalquot; }

      form {
          render_region
              name => 'add_entry',
              path => '/add_entry_button',
              ;
      };

      my $entries = get 'entries';
      while (my $entry = $entries->next) {
          h2 { $entry->name };

          p {
                hyperlink
                    label => $entry->url,
                    url => $entry->url;
          };

          show '/timers', $entry;
      }
};
template '/add_entry_button' => sub {
       form_submit
           label   => _('Add Entry'),
           onclick => {
               region       => Jifty->web->current_region,
               replace_with => '/add_entry_form',
           },
           ;
};
template '/add_entry_form' => sub {
       my $entry_action = new_action class => 'CreateEntry';
       render_action $entry_action, [ qw( name url ) ];
       form_submit
           label   => _('Add Entry'),
           onclick => {
               submit       => $entry_action,
               region       => Jifty->web->current_region,
               replace_with => '/add_entry_button',
           },
           ;
};
Where’d it go?
Where’d it go?


2 Problems
Where’d it go?


2 Problems
             We need a timer
                  too.
Where’d it go?


2 Problems
             We need a timer
                    too.
   We need to
redraw the journal.
package QublogMini::Model::Entry;
use Jifty::DBI::Schema;

use DateTime;

# ...

# Add a timer when we add an entry
sub after_create {
    my ($self, $id) = @_;
    return unless $$id;

    my $timer = QublogMini::Model::Timer->new;
    $timer->create(
        entry      => $$id,
        started_on => DateTime->now,
    );

    return 1;
}
template '/today' => page {
    { title is quot;Today's Journalquot; }

     my $entries = get 'entries';
     while (my $entry = $entries->next) {
         h2 { $entry->name };

         p {
               hyperlink
                   label => $entry->url,
                   url => $entry->url
         };

         show '/timers', $entry;
     }
};
template '/today' => page {
    { title is quot;Today's Journalquot; }

     my $entries = get 'entries';
     while (my $entry = $entries->next) {
         h2 { $entry->name };

         p {
               hyperlink
                   label => $entry->url,
                   url => $entry->url
         };

         show '/timers', $entry;
     }
};
# Replace /today with a region
template '/today' => page {
     { title is quot;Today's Journalquot; }

     render_region
         name => 'entries',
         path => '/entries',
         ;
};
# Replace /today with a region
template '/today' => page {
     { title is quot;Today's Journalquot; }

     render_region
         name => 'entries',
         path => '/entries',
         ;
};
# Add a new entries fragment
template '/entries' => sub {
    form {
        render_region
            name => 'add_entry',
            path => '/add_entry_button',
            ;
    };

     my $entries = get 'entries';
     while (my $entry = $entries->next) {
         h2 { $entry->name };

         p {
               hyperlink
                   label => $entry->url,
                   url => $entry->url;
         };

         show '/timers', $entry;
     }
};
on '/today' => run {
      my $entries = QublogMini::Model::EntryCollection->new;
      my $timer_alias = $entries->join(
          column1 => 'id',
          table2 => QublogMini::Model::Timer->table,
          column2 => 'entry',
      );
      $entries->limit(
          alias    => $timer_alias,
          column   => 'started_on',
          operator => '>=',
          value    => DateTime->today,
          entry_aggregator => 'AND',
      );
      $entries->limit(
          alias    => $timer_alias,
          column   => 'started_on',
          operator => '<',
          value    => DateTime->today->add( days => 1 ),
          entry_aggregator => 'AND',
      );
      set entries => $entries;
};
on '/today' => run {
      my $entries = QublogMini::Model::EntryCollection->new;
      my $timer_alias = $entries->join(
          column1 => 'id',
          table2 => QublogMini::Model::Timer->table,
          column2 => 'entry',
      );
      $entries->limit(
          alias    => $timer_alias,
          column   => 'started_on',
          operator => '>=',
          value    => DateTime->today,
          entry_aggregator => 'AND',
      );
      $entries->limit(
          alias    => $timer_alias,
          column   => 'started_on',
          operator => '<',
          value    => DateTime->today->add( days => 1 ),
          entry_aggregator => 'AND',
      );
      set entries => $entries;
};
on '/entries' => run {
    my $entries = QublogMini::Model::EntryCollection->new;
    my $timer_alias = $entries->join(
        column1 => 'id',
        table2 => QublogMini::Model::Timer->table,
        column2 => 'entry',
    );
    $entries->limit(
        alias    => $timer_alias,
        column   => 'started_on',
        operator => '>=',
        value    => DateTime->today,
        entry_aggregator => 'AND',
    );
    $entries->limit(
        alias    => $timer_alias,
        column   => 'started_on',
        operator => '<',
        value    => DateTime->today->add( days => 1 ),
        entry_aggregator => 'AND',
    );
    set entries => $entries;
};
template '/add_entry_form' => sub {
    my $entry_action = new_action class => 'CreateEntry';
    render_action $entry_action, [ qw( name url ) ];
    form_submit
        label   => _('Add Entry'),
        onclick => {
            submit       => $entry_action,
            region       => Jifty->web->current_region,
            replace_with => '/add_entry_button',
        },
        ;
};
template '/add_entry_form' => sub {
    my $entry_action = new_action class => 'CreateEntry';
    render_action $entry_action, [ qw( name url ) ];
    form_submit
        label   => _('Add Entry'),
        onclick => {
            submit       => $entry_action,
            region       => Jifty->web->current_region,
            replace_with => '/add_entry_button',
        },
        ;
};
# Make our entry form update everything properly
template '/add_entry_form' => sub {
     my $entry_action = new_action class => 'CreateEntry';
     render_action $entry_action, [ qw( name url ) ];
     form_submit
         label   => _('Add Entry'),
         onclick => [
             {
                 submit => $entry_action,
             },
             {
                 region       => Jifty->web->current_region,
                 replace_with => '/add_entry_button',
             },
             {
                 refresh => Jifty->web->current_region->parent,
             },
         ],
         ;
};
# Make our entry form update everything properly
template '/add_entry_form' => sub {
     my $entry_action = new_action class => 'CreateEntry';
     render_action $entry_action, [ qw( name url ) ];
     form_submit
         label   => _('Add Entry'),
         onclick => [
             {
                 submit => $entry_action,
             },
             {
                 region       => Jifty->web->current_region,
                 replace_with => '/add_entry_button',
             },
             {
                 refresh => Jifty->web->current_region->parent,
             },
         ],
         ;
};
Bummer.
Wrong Order.
Bummer.
Wrong Order.
on '/entries' => run {
       my $entries = QublogMini::Model::EntryCollection->new;
       my $timer_alias = $entries->join(
           column1 => 'id',
           table2 => QublogMini::Model::Timer->table,
           column2 => 'entry',
       );

      # ...

      $entries->order_by({
          alias => $timer_alias,
          column => 'started_on',
          order => 'DES',
      });
      set entries => $entries;
};
on '/entries' => run {
       my $entries = QublogMini::Model::EntryCollection->new;
       my $timer_alias = $entries->join(
           column1 => 'id',
           table2 => QublogMini::Model::Timer->table,
           column2 => 'entry',
       );

      # ...

      $entries->order_by({
          alias => $timer_alias,
          column => 'started_on',
          order => 'DES',
      });
      set entries => $entries;
};
We have entries
and timers, but what
 about comments?
package QublogMini::Model::Comment;
use Jifty::DBI::Schema;

use DateTime;

# ...

sub before_create {
    my ($self, $args) = @_;

    $args->{commented_on} = DateTime->now;

    return 1;
}
template '/add_entry_button' => sub {
       form_submit
           label   => _('Add Entry'),
           onclick => {
               region       => Jifty->web->current_region,
               replace_with => '/add_entry_form',
           },
           ;
       form_submit
           label   => _('Add Comment'),
           onclick => {
               region       => Jifty->web->current_region,
               replace_with => '/add_comment_form',
           },
           ;
};
template '/add_entry_button' => sub {
       form_submit
           label   => _('Add Entry'),
           onclick => {
               region       => Jifty->web->current_region,
               replace_with => '/add_entry_form',
           },
           ;
       form_submit
           label   => _('Add Comment'),
           onclick => {
               region       => Jifty->web->current_region,
               replace_with => '/add_comment_form',
           },
           ;
};
template '/add_comment_form' => sub {
 my $timer = QublogMini::Model::Timer->new;
 $timer->load_current; # loads the latest running timer
 my $comment_action = new_action class => 'CreateComment';
 render_action $comment_action, [ qw( name ) ];
 form_submit
     label   => _('Add Comment'),
     onclick => [
         {
             submit => {
                 action    => $comment_action,
                 arguments => {
                     timer => $timer->id,
                  },
             },
         },
         {
             region       => Jifty->web->current_region,
             replace_with => '/add_entry_button',
         },
         {
             refresh => Jifty->web->current_region->parent,
         },
     ],
     ;
 # ...
# ...

     form_submit
         label   => _('Cancel'),
         onclick => {
             region       => Jifty->web->current_region,
             replace_with => '/add_entry_button',
         },
         ;
};
What about Validation
What about Validation

• Easy
What about Validation

• Easy


• Add a validate_XXX method to the model
What about Validation

• Easy


• Add a validate_XXX method to the model


• Let’s try it
sub validate_url {
    my ($self, $url) = @_;

    if ($url !~ m[^https?://]) {
        return (0, 'That does not look like a URL.');
    }

    return 1;
}
Canonicalization
Canonicalization

• You can also canonicalize something
Canonicalization

• You can also canonicalize something


• What the heck is that?
Canonicalization

• You can also canonicalize something


• What the heck is that?


• Rather than slap their hand, fix it for them
Canonicalization

• You can also canonicalize something


• What the heck is that?


• Rather than slap their hand, fix it for them


• For example, we could... (but won’t)...
sub canonicalize_url {
    my ($self, $url) = @_;

    if ($url !~ m[^https?://]) {
        return 'http://'.$url;
    }

    return $url;
}
sub canonicalize_url {
    my ($self, $url) = @_;

    if ($url !~ m[^https?://]) {
        return 'http://'.$url;
    }

    return $url;      Too naïve, I refuse to
}
                         implement it!
But it was the best I
                              could come up with on
sub canonicalize_url {             short notice.
    my ($self, $url) = @_;

    if ($url !~ m[^https?://]) {
        return 'http://'.$url;
    }

    return $url;      Too naïve, I refuse to
}
                         implement it!
Halos
Halos
Halos
Halos




          I’m the view code
        for this “frame” in the
                 page!
Halos
Halos
Halos
Halos




        View the HTML
         source, Luke.
Halos
Halos
Halos
Page Info
Page Info
Page Info
Page Info
Page Info
Page Info
Page Info

• Click on things to view info
Page Info

• Click on things to view info


• SQL Logging
Page Info

• Click on things to view info


• SQL Logging


• SQL Profiling
Page Info

• Click on things to view info


• SQL Logging


• SQL Profiling


• Template Profiling
Developer Mode
Developer Mode

• Module::Refresh
Developer Mode

• Module::Refresh


• Tracing with Log4Perl
Developer Mode

• Module::Refresh


• Tracing with Log4Perl


• Administration Console
Developer Mode

• Module::Refresh


• Tracing with Log4Perl


• Administration Console


  • (actually, that’s Administrator Mode)
Deployment
Deployment

• Choice of web servers: FastCGI on Apache / Lighttpd, HTTP::Server::Simple,
  mod_perl support (a little weak at the moment)
Deployment

• Choice of web servers: FastCGI on Apache / Lighttpd, HTTP::Server::Simple,
  mod_perl support (a little weak at the moment)


• Choice of DB servers: SQLite (great for getting started/testing, but production
  is possible), PostgreSQL, MySQL, and other servers supported
Deployment

• Choice of web servers: FastCGI on Apache / Lighttpd, HTTP::Server::Simple,
  mod_perl support (a little weak at the moment)


• Choice of DB servers: SQLite (great for getting started/testing, but production
  is possible), PostgreSQL, MySQL, and other servers supported


• Use a fast static server to server CSS, JS, images, etc.
Deployment

• Choice of web servers: FastCGI on Apache / Lighttpd, HTTP::Server::Simple,
  mod_perl support (a little weak at the moment)


• Choice of DB servers: SQLite (great for getting started/testing, but production
  is possible), PostgreSQL, MySQL, and other servers supported


• Use a fast static server to server CSS, JS, images, etc.


• Use CSS::Squish and JS::Squish to help compress and cache things
Other Goodies
Other Goodies

• Several authentication plugins: typical email sign-up login, OpenID,
  Facebook, LDAP, CAS


• Additional CRUD view helpers I haven’t demonstrated


• Built-in REST interface


• Full I18N support built-in


• Several Plugins for lots of things: Comments, Single Page Apps, Client-Side
  Templating, Charting, Tab helpers


• Built-in support for Comet (server push) in addition to Ajax
Applications
Applications

• Hiveminder (http://hiveminder.com)
Applications

• Hiveminder (http://hiveminder.com)


• Wifty (http://jifty.org)
Applications

• Hiveminder (http://hiveminder.com)


• Wifty (http://jifty.org)


• Qublog (http://qublog.net)
Applications

• Hiveminder (http://hiveminder.com)


• Wifty (http://jifty.org)


• Qublog (http://qublog.net)


• Doxory (http://doxory.com)
Applications

• Hiveminder (http://hiveminder.com)


• Wifty (http://jifty.org)


• Qublog (http://qublog.net)


• Doxory (http://doxory.com)


• CommitBit (http://code.bestpractical.com/)
Applications

• Hiveminder (http://hiveminder.com)


• Wifty (http://jifty.org)


• Qublog (http://qublog.net)


• Doxory (http://doxory.com)


• CommitBit (http://code.bestpractical.com/)


• Ping / Chat / Clock (examples available with Jifty)
Jifty Resources
Jifty Resources

• CPAN (http://search.cpan.org/dist/Jifty/)
Jifty Resources

• CPAN (http://search.cpan.org/dist/Jifty/)


• Jifty Wiki (http://jifty.org)
Jifty Resources

• CPAN (http://search.cpan.org/dist/Jifty/)


• Jifty Wiki (http://jifty.org)


• Jifty SVN (http://svn.jifty.org/svn/jifty.org)
Jifty Resources

• CPAN (http://search.cpan.org/dist/Jifty/)


• Jifty Wiki (http://jifty.org)


• Jifty SVN (http://svn.jifty.org/svn/jifty.org)


• Tutorial (http://search.cpan.org/dist/Jifty/lib/Jifty/Manual/Tutorial.pod)
Jifty Resources

• CPAN (http://search.cpan.org/dist/Jifty/)


• Jifty Wiki (http://jifty.org)


• Jifty SVN (http://svn.jifty.org/svn/jifty.org)


• Tutorial (http://search.cpan.org/dist/Jifty/lib/Jifty/Manual/Tutorial.pod)


• Manual (http://search.cpan.org/dist/Jifty/lib/Jifty/Manual.pod)
Jifty Resources

• CPAN (http://search.cpan.org/dist/Jifty/)


• Jifty Wiki (http://jifty.org)


• Jifty SVN (http://svn.jifty.org/svn/jifty.org)


• Tutorial (http://search.cpan.org/dist/Jifty/lib/Jifty/Manual/Tutorial.pod)


• Manual (http://search.cpan.org/dist/Jifty/lib/Jifty/Manual.pod)


• Mailing List: jifty‐devel@lists.jifty.org
Jifty Resources

• CPAN (http://search.cpan.org/dist/Jifty/)


• Jifty Wiki (http://jifty.org)


• Jifty SVN (http://svn.jifty.org/svn/jifty.org)


• Tutorial (http://search.cpan.org/dist/Jifty/lib/Jifty/Manual/Tutorial.pod)


• Manual (http://search.cpan.org/dist/Jifty/lib/Jifty/Manual.pod)


• Mailing List: jifty‐devel@lists.jifty.org


• IRC Channel: #jifty on irc.freenode.org
Qublog

• See http://qublog.net


• Sample code for QublogMini in SVN: http://qublog.net/svn/QublogMini
Thank you!

More Related Content

Similar to Introduction to Jifty and Building QublogMini App

Pipfile, pipenv, pip… what?!
Pipfile, pipenv, pip… what?!Pipfile, pipenv, pip… what?!
Pipfile, pipenv, pip… what?!Ivan Chernoff
 
Using Buildout to Develop and Deploy Python Projects
Using Buildout to Develop and Deploy Python ProjectsUsing Buildout to Develop and Deploy Python Projects
Using Buildout to Develop and Deploy Python ProjectsClayton Parker
 
Buildout: Fostering Repeatability
Buildout: Fostering RepeatabilityBuildout: Fostering Repeatability
Buildout: Fostering RepeatabilityClayton Parker
 
WebGeek AppNimbus (Nikko Bautista)
WebGeek AppNimbus (Nikko Bautista)WebGeek AppNimbus (Nikko Bautista)
WebGeek AppNimbus (Nikko Bautista)WebGeek Philippines
 
Writing a Ruby Gem for beginners
Writing a Ruby Gem for beginnersWriting a Ruby Gem for beginners
Writing a Ruby Gem for beginnersConFoo
 
RSC SE Teaching toolkit no 8 Todaysmeet, QR codes and Slideshare - Jane Macke...
RSC SE Teaching toolkit no 8 Todaysmeet, QR codes and Slideshare - Jane Macke...RSC SE Teaching toolkit no 8 Todaysmeet, QR codes and Slideshare - Jane Macke...
RSC SE Teaching toolkit no 8 Todaysmeet, QR codes and Slideshare - Jane Macke...Jane Mackenzie
 
Minipub lightning-english
Minipub lightning-englishMinipub lightning-english
Minipub lightning-englishRichard Barran
 
Singularity Registry HPC
Singularity Registry HPCSingularity Registry HPC
Singularity Registry HPCVanessa S
 
Design Thinking in the Product Development Process - Product tank oxford
Design Thinking in the Product Development Process - Product tank oxford Design Thinking in the Product Development Process - Product tank oxford
Design Thinking in the Product Development Process - Product tank oxford AJ Justo
 
Automating MySQL operations with Puppet
Automating MySQL operations with PuppetAutomating MySQL operations with Puppet
Automating MySQL operations with PuppetKris Buytaert
 
Git - Some tips to do it better
Git - Some tips to do it betterGit - Some tips to do it better
Git - Some tips to do it betterJonas De Smet
 
Debugging Your Plone Site
Debugging Your Plone SiteDebugging Your Plone Site
Debugging Your Plone Sitecdw9
 

Similar to Introduction to Jifty and Building QublogMini App (20)

Pinto+Stratopan+Love
Pinto+Stratopan+LovePinto+Stratopan+Love
Pinto+Stratopan+Love
 
Pimp my Plone
Pimp my PlonePimp my Plone
Pimp my Plone
 
Pipfile, pipenv, pip… what?!
Pipfile, pipenv, pip… what?!Pipfile, pipenv, pip… what?!
Pipfile, pipenv, pip… what?!
 
Skisser
SkisserSkisser
Skisser
 
Using Buildout to Develop and Deploy Python Projects
Using Buildout to Develop and Deploy Python ProjectsUsing Buildout to Develop and Deploy Python Projects
Using Buildout to Develop and Deploy Python Projects
 
Buildout: Fostering Repeatability
Buildout: Fostering RepeatabilityBuildout: Fostering Repeatability
Buildout: Fostering Repeatability
 
WebGeek AppNimbus (Nikko Bautista)
WebGeek AppNimbus (Nikko Bautista)WebGeek AppNimbus (Nikko Bautista)
WebGeek AppNimbus (Nikko Bautista)
 
Mojolicious lite
Mojolicious liteMojolicious lite
Mojolicious lite
 
Cloud Needs Devops
Cloud Needs DevopsCloud Needs Devops
Cloud Needs Devops
 
redhat_intern
redhat_internredhat_intern
redhat_intern
 
Writing a Ruby Gem for beginners
Writing a Ruby Gem for beginnersWriting a Ruby Gem for beginners
Writing a Ruby Gem for beginners
 
RSC SE Teaching toolkit no 8 Todaysmeet, QR codes and Slideshare - Jane Macke...
RSC SE Teaching toolkit no 8 Todaysmeet, QR codes and Slideshare - Jane Macke...RSC SE Teaching toolkit no 8 Todaysmeet, QR codes and Slideshare - Jane Macke...
RSC SE Teaching toolkit no 8 Todaysmeet, QR codes and Slideshare - Jane Macke...
 
Minipub lightning-english
Minipub lightning-englishMinipub lightning-english
Minipub lightning-english
 
Singularity Registry HPC
Singularity Registry HPCSingularity Registry HPC
Singularity Registry HPC
 
Design Thinking in the Product Development Process - Product tank oxford
Design Thinking in the Product Development Process - Product tank oxford Design Thinking in the Product Development Process - Product tank oxford
Design Thinking in the Product Development Process - Product tank oxford
 
Automating MySQL operations with Puppet
Automating MySQL operations with PuppetAutomating MySQL operations with Puppet
Automating MySQL operations with Puppet
 
Tweakers Anonymous
Tweakers AnonymousTweakers Anonymous
Tweakers Anonymous
 
Git - Some tips to do it better
Git - Some tips to do it betterGit - Some tips to do it better
Git - Some tips to do it better
 
Debugging Your Plone Site
Debugging Your Plone SiteDebugging Your Plone Site
Debugging Your Plone Site
 
Intro to Gulp
Intro to GulpIntro to Gulp
Intro to Gulp
 

Recently uploaded

DSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningDSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningLars Bell
 
Commit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyCommit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyAlfredo García Lavilla
 
What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024Stephanie Beckett
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 3652toLead Limited
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .Alan Dix
 
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Mark Simos
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfAddepto
 
Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Enterprise Knowledge
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsSergiu Bodiu
 
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostLeverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostZilliz
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsMark Billinghurst
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 
Dev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebDev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebUiPathCommunity
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024Lonnie McRorey
 
Powerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time ClashPowerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time Clashcharlottematthew16
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsPixlogix Infotech
 
Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteDianaGray10
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brandgvaughan
 
SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxNavinnSomaal
 
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdfHyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdfPrecisely
 

Recently uploaded (20)

DSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningDSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine Tuning
 
Commit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyCommit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easy
 
What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .
 
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
 
Gen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdfGen AI in Business - Global Trends Report 2024.pdf
Gen AI in Business - Global Trends Report 2024.pdf
 
Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platforms
 
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostLeverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR Systems
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 
Dev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebDev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio Web
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024
 
Powerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time ClashPowerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time Clash
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and Cons
 
Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test Suite
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brand
 
SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptx
 
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdfHyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
Hyperautomation and AI/ML: A Strategy for Digital Transformation Success.pdf
 

Introduction to Jifty and Building QublogMini App

  • 1. Introduction to Jifty Sterling Hanenkamp, PPW 2008
  • 2. I Work for a Living
  • 3. I Work for a Living • Contract work / Billable Hours: How do I track them?
  • 4. I Work for a Living • Contract work / Billable Hours: How do I track them? • Weekly Status Reports: What did I do?
  • 5. I Work for a Living • Contract work / Billable Hours: How do I track them? • Weekly Status Reports: What did I do? • Reliable Memory: Did I do that? How? When? Why?
  • 6. I Work for a Living • Contract work / Billable Hours: How do I track them? • Weekly Status Reports: What did I do? • Reliable Memory: Did I do that? How? When? Why? • Task Lists: What should I do next?
  • 7. Existing Solutions Time Sheet
  • 9. Existing Solutions Scratch Paper
  • 11. Existing Solutions Things Sunbird Day Planner Hiveminder Entourage Stikkit iCal/Apple Mail Lightning OmniFocus
  • 13. Qublog • I don’t want to store this information in four places
  • 14. Qublog • I don’t want to store this information in four places • I have a habit of writing down what I’m doing to focus
  • 15. Qublog • I don’t want to store this information in four places • I have a habit of writing down what I’m doing to focus • I needed something else to do with my lack of spare time
  • 16. Qublog • I don’t want to store this information in four places • I have a habit of writing down what I’m doing to focus • I needed something else to do with my lack of spare time • I built Qublog (originally MyToDo and Kiln for a short while)
  • 17. Qublog • I don’t want to store this information in four places • I have a habit of writing down what I’m doing to focus • I needed something else to do with my lack of spare time • I built Qublog (originally MyToDo and Kiln for a short while) • It’s written using Jifty
  • 18. Qublog • I don’t want to store this information in four places • I have a habit of writing down what I’m doing to focus • I needed something else to do with my lack of spare time • I built Qublog (originally MyToDo and Kiln for a short while) • It’s written using Jifty • We’re going to look at how I did it to explain Jifty
  • 35. Qublog • Provides a Journal for keeping thoughts
  • 36. Qublog • Provides a Journal for keeping thoughts • Provides a To Do List for organizing tasks
  • 37. Qublog • Provides a Journal for keeping thoughts • Provides a To Do List for organizing tasks • Journal Comments are grouped into Timers that keep track of time spent
  • 38. Qublog • Provides a Journal for keeping thoughts • Provides a To Do List for organizing tasks • Journal Comments are grouped into Timers that keep track of time spent • Timers are grouped into Entries to sum up work for reporting
  • 39. Qublog • Provides a Journal for keeping thoughts • Provides a To Do List for organizing tasks • Journal Comments are grouped into Timers that keep track of time spent • Timers are grouped into Entries to sum up work for reporting • Comments also connect with Tasks to keep track of what’s next
  • 40. This is a talk about Jifty
  • 41. This is a talk about Jifty • Not Qublog • Okay, so let’s see if we can build QublogMini in the time remaining...
  • 42. Install Jifty As with “that-other-framework,” the worst part is over when you finish the install.
  • 44. Install Jifty • Method 1: Shipwright
  • 45. Install Jifty • Method 1: Shipwright svn co http://code.bestpractical.com/shipwright/jifty/ jifty-builder cd jifty-builder ./bin/shipwright-builder # optionally --skip perl --skip-test cp -rvp /tmp/jifty-$random/jifty /usr/local/jifty ln -s /usr/local/jifty/bin/jifty /usr/local/bin/jifty
  • 46. Install Jifty • Method 1: Shipwright svn co http://code.bestpractical.com/shipwright/jifty/ jifty-builder cd jifty-builder ./bin/shipwright-builder # optionally --skip perl --skip-test cp -rvp /tmp/jifty-$random/jifty /usr/local/jifty ln -s /usr/local/jifty/bin/jifty /usr/local/bin/jifty • Method 2: CPAN
  • 47. Install Jifty • Method 1: Shipwright svn co http://code.bestpractical.com/shipwright/jifty/ jifty-builder cd jifty-builder ./bin/shipwright-builder # optionally --skip perl --skip-test cp -rvp /tmp/jifty-$random/jifty /usr/local/jifty ln -s /usr/local/jifty/bin/jifty /usr/local/bin/jifty • Method 2: CPAN perl -MCPAN -eshell # or just cpan cpan> notest install Jifty
  • 48. Install Jifty • Method 1: Shipwright svn co http://code.bestpractical.com/shipwright/jifty/ jifty-builder cd jifty-builder ./bin/shipwright-builder # optionally --skip perl --skip-test cp -rvp /tmp/jifty-$random/jifty /usr/local/jifty ln -s /usr/local/jifty/bin/jifty /usr/local/bin/jifty “notest” saves ~60 minutes according to Audrey • Method 2: CPAN perl -MCPAN -eshell # or just cpan cpan> notest install Jifty
  • 49. Install Jifty • Method 1: Shipwright svn co http://code.bestpractical.com/shipwright/jifty/ jifty-builder cd jifty-builder ./bin/shipwright-builder # optionally --skip perl --skip-test cp -rvp /tmp/jifty-$random/jifty /usr/local/jifty ln -s /usr/local/jifty/bin/jifty /usr/local/bin/jifty “notest” saves ~60 minutes according to Audrey • Method 2: CPAN perl -MCPAN -eshell # or just cpan cpan> notest install Jifty “notest” will get you no support according to Jesse
  • 50. Install Jifty • Method 1: Shipwright svn co http://code.bestpractical.com/shipwright/jifty/ jifty-builder cd jifty-builder ./bin/shipwright-builder # optionally --skip perl --skip-test cp -rvp /tmp/jifty-$random/jifty /usr/local/jifty ln -s /usr/local/jifty/bin/jifty /usr/local/bin/jifty • Method 2: CPAN perl -MCPAN -eshell # or just cpan cpan> notest install Jifty
  • 51. Install Jifty • Method 1: Shipwright svn co http://code.bestpractical.com/shipwright/jifty/ jifty-builder cd jifty-builder ./bin/shipwright-builder # optionally --skip perl --skip-test cp -rvp /tmp/jifty-$random/jifty /usr/local/jifty ln -s /usr/local/jifty/bin/jifty /usr/local/bin/jifty • Method 2: CPAN perl -MCPAN -eshell # or just cpan cpan> notest install Jifty • ActiveState installation on Windows: http://jifty.org/view/DownloadJifty
  • 52. Install Jifty • Method 1: Shipwright svn co http://code.bestpractical.com/shipwright/jifty/ jifty-builder cd jifty-builder ./bin/shipwright-builder # optionally --skip perl --skip-test cp -rvp /tmp/jifty-$random/jifty /usr/local/jifty ln -s /usr/local/jifty/bin/jifty /usr/local/bin/jifty • Method 2: CPAN perl -MCPAN -eshell # or just cpan cpan> notest install Jifty • ActiveState installation on Windows: http://jifty.org/view/DownloadJifty • Jifty is in Debian unstable
  • 53. % jifty app --name QublogMini
  • 54. % jifty app --name QublogMini Creating new application QublogMini Creating directory QublogMini/lib Creating directory QublogMini/lib/QublogMini Creating directory QublogMini/bin Creating directory QublogMini/etc Creating directory QublogMini/doc Creating directory QublogMini/log Creating directory QublogMini/var Creating directory QublogMini/var/mason Creating directory QublogMini/share Creating directory QublogMini/share/po Creating directory QublogMini/share/web Creating directory QublogMini/share/web/templates Creating directory QublogMini/share/web/static Creating directory QublogMini/lib/QublogMini/Model Creating directory QublogMini/lib/QublogMini/Action Creating directory QublogMini/t Creating configuration file QublogMini/etc/config.yml
  • 55. % cd QublogMini % find . -type f
  • 56. % cd QublogMini % find . -type f ./bin/jifty ./etc/config.yml ./Makefile.PL
  • 57.
  • 59. % ./bin/jifty server WARN - Application schema has no version in the database. WARN - Automatically creating your database. INFO - Generating SQL for application QublogMini... INFO - Using Jifty::Model::Session, as it appears to be new. INFO - Using Jifty::Model::Metadata, as it appears to be new. INFO - Set up version v0.0.1, jifty version 0.804080 INFO - You can connect to your server at http://localhost:8888/
  • 60. % ./bin/jifty server WARN - Application schema has no version in the database. WARN - Automatically creating your database. INFO - Generating SQL for application QublogMini... INFO - Using Jifty::Model::Session, as it appears to be new. INFO - Using Jifty::Model::Metadata, as it appears to be new. INFO - Set up version v0.0.1, jifty version 0.804080 INFO - You can connect to your server at http://localhost:8888/
  • 63. Not a very interesting journaling app... let’s fix it.
  • 64. % ./bin/jifty model --name Comment
  • 65. % ./bin/jifty model --name Comment Writing file t/00-model-Comment.t Writing file lib/QublogMini/Model/Comment.pm
  • 66. % ./bin/jifty model --name Comment Writing file t/00-model-Comment.t Writing file lib/QublogMini/Model/Comment.pm % vim lib/QublogMini/Model/Comment.pm
  • 68. package QublogMini::Model::Comment; use Jifty::DBI::Schema; use QublogMini::Record schema { column commented_on => type is 'datetime', label is 'Commented on', filters are qw( Jifty::DBI::Filter::DateTime ); column name => type is 'text', label is 'Name', render as 'textarea'; };
  • 70. % bin/jifty server WARN - Application schema has no version in the database. WARN - Automatically creating your database. INFO - Generating SQL for application QublogMini... INFO - Using QublogMini::Model::Comment, as it appears to be new. INFO - Using Jifty::Model::Session, as it appears to be new. INFO - Using Jifty::Model::Metadata, as it appears to be new. INFO - Set up version v0.0.1, jifty version 0.804080 INFO - You can connect to your server at http://localhost: 8888/
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80. % bin/jifty model --name Timer
  • 81. % bin/jifty model --name Timer Writing file lib/QublogMini/Model/Timer.pm Writing file t/00-model-Timer.t
  • 82. % bin/jifty model --name Timer Writing file lib/QublogMini/Model/Timer.pm Writing file t/00-model-Timer.t % vim lib/QublogMini/Model/Timer.pm
  • 84. package QublogMini::Model::Timer; use Jifty::DBI::Schema; use QublogMini::Record schema { column started_on => label is 'Started on', filters are qw( Jifty::DBI::Filter::DateTime ); column stopped_on => label is 'Stopped on', filters are qw( Jifty::DBI::Filter::DateTime ); column comments => references QublogMini::Model::CommentCollection by 'timer'; }; sub since { '0.0.2' }
  • 85. package QublogMini::Model::Comment; use Jifty::DBI::Schema; use QublogMini::Record schema { column commented_on => type is 'datetime', label is 'Commented on', filters are qw( Jifty::DBI::Filter::DateTime ), ; column name => type is 'text', label is 'Name', render as 'textarea', ; };
  • 86. package QublogMini::Model::Comment; use Jifty::DBI::Schema; use QublogMini::Record schema { column commented_on => type is 'datetime', label is 'Commented on', filters are qw( Jifty::DBI::Filter::DateTime ), ; column name => type is 'text', label is 'Name', render as 'textarea', ; column timer => label is 'Timer', since '0.0.2', references QublogMini::Model::Timer; };
  • 88. % bin/jifty server WARN - Application schema version in database (v0.0.1) doesn't match application schema version (0.0.2) WARN - Automatically upgrading your database to match the current application schema. Jifty version 0.809130 up to date. Jifty::Plugin::LetMe version v0.0.1 up to date. Jifty::Plugin::SkeletonApp version v0.0.1 up to date. Jifty::Plugin::REST version v0.0.1 up to date. Jifty::Plugin::Halo version v0.0.1 up to date. Jifty::Plugin::ErrorTemplates version v0.0.1 up to date. Jifty::Plugin::OnlineDocs version v0.0.1 up to date. Jifty::Plugin::CompressedCSSandJS version v0.0.1 up to date. Jifty::Plugin::AdminUI version v0.0.1 up to date. INFO - Generating SQL to upgrade QublogMini v0.0.1 database to v0.0.2 INFO - Upgrading through 0.0.2 INFO - Running upgrade script INFO - Upgraded to version v0.0.2 INFO - You can connect to your server at http://localhost:8888/
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95. package QublogMini::Model::Timer; # ... sub name { my $self = shift; $self->started_on . ' to ' . $self->stopped_on }
  • 96. % bin/jifty model --name Entry
  • 97. % bin/jifty model --name Entry Writing file t/00-model-Entry.t Writing file lib/QublogMini/Model/Entry.pm
  • 98. % bin/jifty model --name Entry Writing file t/00-model-Entry.t Writing file lib/QublogMini/Model/Entry.pm % vim lib/QublogMini/Model/Entry.pm
  • 100. package QublogMini::Model::Entry; use Jifty::DBI::Schema; use QublogMini::Record schema { column name => type is 'text', label is 'Name', is mandatory; column url => type is 'text', label is 'Name'; column timers => references QublogMini::Model::TimerCollection by 'entry'; }; sub since { '0.0.3' }
  • 101. package QublogMini::Model::Timer; use Jifty::DBI::Schema; use QublogMini::Record schema { # ... column entry => label is 'Entry', since '0.0.3', references QublogMini::Model::Entry; # ... }; sub name { my $self = shift; my $name = ''; $name .= $self->entry->name . ': ' if $self->entry->id; $name .= $self->started_on . ' to ' . $self->stopped_on return $name; }
  • 102. --- framework: AdminMode: 1 ApplicationClass: QublogMini ApplicationName: QublogMini ApplicationUUID: 1198420A-95A9-11DD-8ED4-3E4643B9151E ConfigFileVersion: 4 Database: AutoUpgrade: 1 CheckSchema: 1 Database: qublogmini Driver: SQLite Host: localhost Password: '' RecordBaseClass: Jifty::DBI::Record::Cachable User: '' Version: 0.0.2
  • 103. --- framework: AdminMode: 1 ApplicationClass: QublogMini ApplicationName: QublogMini ApplicationUUID: 1198420A-95A9-11DD-8ED4-3E4643B9151E ConfigFileVersion: 4 Database: AutoUpgrade: 1 CheckSchema: 1 Database: qublogmini Driver: SQLite Host: localhost Password: '' RecordBaseClass: Jifty::DBI::Record::Cachable User: '' Version: 0.0.2
  • 104. --- framework: AdminMode: 1 ApplicationClass: QublogMini ApplicationName: QublogMini ApplicationUUID: 1198420A-95A9-11DD-8ED4-3E4643B9151E ConfigFileVersion: 4 Database: AutoUpgrade: 1 CheckSchema: 1 Database: qublogmini Driver: SQLite Host: localhost Password: '' RecordBaseClass: Jifty::DBI::Record::Cachable User: '' Version: 0.0.3
  • 106. % bin/jifty server WARN - Application schema version in database (v0.0.2) doesn't match application schema version (0.0.3) WARN - Automatically upgrading your database to match the current application schema. Jifty version 0.809130 up to date. Jifty::Plugin::LetMe version v0.0.1 up to date. Jifty::Plugin::SkeletonApp version v0.0.1 up to date. Jifty::Plugin::REST version v0.0.1 up to date. Jifty::Plugin::Halo version v0.0.1 up to date. Jifty::Plugin::ErrorTemplates version v0.0.1 up to date. Jifty::Plugin::OnlineDocs version v0.0.1 up to date. Jifty::Plugin::CompressedCSSandJS version v0.0.1 up to date. Jifty::Plugin::AdminUI version v0.0.1 up to date. INFO - Generating SQL to upgrade QublogMini v0.0.2 database to v0.0.3 INFO - Upgrading through 0.0.3 INFO - Running upgrade script INFO - Upgraded to version v0.0.3 INFO - You can connect to your server at http://localhost:8888/
  • 107.
  • 108. Ponies are great!
  • 109. Ponies are But where’s the UI? great!
  • 110. # Dispatcher maps URLs to Views % vim lib/QublogMini/Dispatcher.pm
  • 112. package QublogMini::Dispatcher; use Jifty::Dispatcher -base; on '/' => dispatch '/today';
  • 113. package QublogMini::Dispatcher; use Jifty::Dispatcher -base; on '/' => dispatch '/today'; on '/today' => run { my $entries = QublogMini::Model::EntryCollection->new; my $timer_alias = $entries->join( column1 => 'id', table2 => QublogMini::Model::Timer->table, column2 => 'entry', ); $entries->limit( alias => $timer_alias, column => 'started_on', operator => '>=', value => DateTime->today, entry_aggregator => 'AND', ); $entries->limit( alias => $timer_alias, column => 'started_on', operator => '<', value => DateTime->today->add( days => 1 ), entry_aggregator => 'AND', ); set entries => $entries; show '/today'; };
  • 114. package QublogMini::View; use Jifty::View::Declare -base; template '/today' => page { { title is quot;Today's Journalquot; } my $entries = get 'entries'; while (my $entry = $entries->next) { h2 { $entry->name }; p { hyperlink label => $entry->url, url => $entry->url }; } };
  • 115.
  • 116. template '/today' => page { { title is quot;Today's Journalquot; } my $entries = get 'entries'; while (my $entry = $entries->next) { h2 { $entry->name }; p { hyperlink label => $entry->url, url => $entry->url }; show '/timers', $entry; } };
  • 117. private template '/timers' => sub { my ($self, $entry) = @_; my $timers = $entry->timers; while (my $timer = $timers->next) { show '/show_comment', $timer->stopped_on, _('Stopped timer.') if $timer->stopped_on; show '/comments', $timer; show '/show_comment', $timer->started_on, _('Started timer.'); } };
  • 118. private template '/comments' => sub { my ($self, $timer) = @_; my $comments = $timer->comments; while (my $comment = $comments->next) { show '/show_comment', $comment->commented_on, $comment->name; } }; private template '/show_comment' => sub { my ($self, $time, $message) = @_; p { { class is 'time' } $time }; p { { class is 'comment' } $message }; };
  • 119.
  • 120. Fine. We already knew that. Where’s the “interface”?
  • 121. template '/today' => page { { title is quot;Today's Journalquot; } form { render_region name => 'add_entry', path => '/add_entry_button', ; }; my $entries = get 'entries'; while (my $entry = $entries->next) { h2 { $entry->name }; p { hyperlink label => $entry->url, url => $entry->url; }; show '/timers', $entry; } };
  • 122. template '/add_entry_button' => sub { form_submit label => _('Add Entry'), onclick => { region => Jifty->web->current_region, replace_with => '/add_entry_form', }, ; };
  • 123. template '/add_entry_form' => sub { my $entry_action = new_action class => 'CreateEntry'; render_action $entry_action, [ qw( name url ) ]; form_submit label => _('Add Entry'), onclick => { submit => $entry_action, region => Jifty->web->current_region, replace_with => '/add_entry_button', }, ; };
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 130. Where’d it go? 2 Problems
  • 131. Where’d it go? 2 Problems We need a timer too.
  • 132. Where’d it go? 2 Problems We need a timer too. We need to redraw the journal.
  • 133. package QublogMini::Model::Entry; use Jifty::DBI::Schema; use DateTime; # ... # Add a timer when we add an entry sub after_create { my ($self, $id) = @_; return unless $$id; my $timer = QublogMini::Model::Timer->new; $timer->create( entry => $$id, started_on => DateTime->now, ); return 1; }
  • 134. template '/today' => page { { title is quot;Today's Journalquot; } my $entries = get 'entries'; while (my $entry = $entries->next) { h2 { $entry->name }; p { hyperlink label => $entry->url, url => $entry->url }; show '/timers', $entry; } };
  • 135. template '/today' => page { { title is quot;Today's Journalquot; } my $entries = get 'entries'; while (my $entry = $entries->next) { h2 { $entry->name }; p { hyperlink label => $entry->url, url => $entry->url }; show '/timers', $entry; } };
  • 136. # Replace /today with a region template '/today' => page { { title is quot;Today's Journalquot; } render_region name => 'entries', path => '/entries', ; };
  • 137. # Replace /today with a region template '/today' => page { { title is quot;Today's Journalquot; } render_region name => 'entries', path => '/entries', ; };
  • 138. # Add a new entries fragment template '/entries' => sub { form { render_region name => 'add_entry', path => '/add_entry_button', ; }; my $entries = get 'entries'; while (my $entry = $entries->next) { h2 { $entry->name }; p { hyperlink label => $entry->url, url => $entry->url; }; show '/timers', $entry; } };
  • 139. on '/today' => run { my $entries = QublogMini::Model::EntryCollection->new; my $timer_alias = $entries->join( column1 => 'id', table2 => QublogMini::Model::Timer->table, column2 => 'entry', ); $entries->limit( alias => $timer_alias, column => 'started_on', operator => '>=', value => DateTime->today, entry_aggregator => 'AND', ); $entries->limit( alias => $timer_alias, column => 'started_on', operator => '<', value => DateTime->today->add( days => 1 ), entry_aggregator => 'AND', ); set entries => $entries; };
  • 140. on '/today' => run { my $entries = QublogMini::Model::EntryCollection->new; my $timer_alias = $entries->join( column1 => 'id', table2 => QublogMini::Model::Timer->table, column2 => 'entry', ); $entries->limit( alias => $timer_alias, column => 'started_on', operator => '>=', value => DateTime->today, entry_aggregator => 'AND', ); $entries->limit( alias => $timer_alias, column => 'started_on', operator => '<', value => DateTime->today->add( days => 1 ), entry_aggregator => 'AND', ); set entries => $entries; };
  • 141. on '/entries' => run { my $entries = QublogMini::Model::EntryCollection->new; my $timer_alias = $entries->join( column1 => 'id', table2 => QublogMini::Model::Timer->table, column2 => 'entry', ); $entries->limit( alias => $timer_alias, column => 'started_on', operator => '>=', value => DateTime->today, entry_aggregator => 'AND', ); $entries->limit( alias => $timer_alias, column => 'started_on', operator => '<', value => DateTime->today->add( days => 1 ), entry_aggregator => 'AND', ); set entries => $entries; };
  • 142. template '/add_entry_form' => sub { my $entry_action = new_action class => 'CreateEntry'; render_action $entry_action, [ qw( name url ) ]; form_submit label => _('Add Entry'), onclick => { submit => $entry_action, region => Jifty->web->current_region, replace_with => '/add_entry_button', }, ; };
  • 143. template '/add_entry_form' => sub { my $entry_action = new_action class => 'CreateEntry'; render_action $entry_action, [ qw( name url ) ]; form_submit label => _('Add Entry'), onclick => { submit => $entry_action, region => Jifty->web->current_region, replace_with => '/add_entry_button', }, ; };
  • 144. # Make our entry form update everything properly template '/add_entry_form' => sub { my $entry_action = new_action class => 'CreateEntry'; render_action $entry_action, [ qw( name url ) ]; form_submit label => _('Add Entry'), onclick => [ { submit => $entry_action, }, { region => Jifty->web->current_region, replace_with => '/add_entry_button', }, { refresh => Jifty->web->current_region->parent, }, ], ; };
  • 145. # Make our entry form update everything properly template '/add_entry_form' => sub { my $entry_action = new_action class => 'CreateEntry'; render_action $entry_action, [ qw( name url ) ]; form_submit label => _('Add Entry'), onclick => [ { submit => $entry_action, }, { region => Jifty->web->current_region, replace_with => '/add_entry_button', }, { refresh => Jifty->web->current_region->parent, }, ], ; };
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 153. on '/entries' => run { my $entries = QublogMini::Model::EntryCollection->new; my $timer_alias = $entries->join( column1 => 'id', table2 => QublogMini::Model::Timer->table, column2 => 'entry', ); # ... $entries->order_by({ alias => $timer_alias, column => 'started_on', order => 'DES', }); set entries => $entries; };
  • 154. on '/entries' => run { my $entries = QublogMini::Model::EntryCollection->new; my $timer_alias = $entries->join( column1 => 'id', table2 => QublogMini::Model::Timer->table, column2 => 'entry', ); # ... $entries->order_by({ alias => $timer_alias, column => 'started_on', order => 'DES', }); set entries => $entries; };
  • 155.
  • 156. We have entries and timers, but what about comments?
  • 157. package QublogMini::Model::Comment; use Jifty::DBI::Schema; use DateTime; # ... sub before_create { my ($self, $args) = @_; $args->{commented_on} = DateTime->now; return 1; }
  • 158. template '/add_entry_button' => sub { form_submit label => _('Add Entry'), onclick => { region => Jifty->web->current_region, replace_with => '/add_entry_form', }, ; form_submit label => _('Add Comment'), onclick => { region => Jifty->web->current_region, replace_with => '/add_comment_form', }, ; };
  • 159. template '/add_entry_button' => sub { form_submit label => _('Add Entry'), onclick => { region => Jifty->web->current_region, replace_with => '/add_entry_form', }, ; form_submit label => _('Add Comment'), onclick => { region => Jifty->web->current_region, replace_with => '/add_comment_form', }, ; };
  • 160. template '/add_comment_form' => sub { my $timer = QublogMini::Model::Timer->new; $timer->load_current; # loads the latest running timer my $comment_action = new_action class => 'CreateComment'; render_action $comment_action, [ qw( name ) ]; form_submit label => _('Add Comment'), onclick => [ { submit => { action => $comment_action, arguments => { timer => $timer->id, }, }, }, { region => Jifty->web->current_region, replace_with => '/add_entry_button', }, { refresh => Jifty->web->current_region->parent, }, ], ; # ...
  • 161. # ... form_submit label => _('Cancel'), onclick => { region => Jifty->web->current_region, replace_with => '/add_entry_button', }, ; };
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 173. What about Validation • Easy • Add a validate_XXX method to the model
  • 174. What about Validation • Easy • Add a validate_XXX method to the model • Let’s try it
  • 175. sub validate_url { my ($self, $url) = @_; if ($url !~ m[^https?://]) { return (0, 'That does not look like a URL.'); } return 1; }
  • 176.
  • 177.
  • 179. Canonicalization • You can also canonicalize something
  • 180. Canonicalization • You can also canonicalize something • What the heck is that?
  • 181. Canonicalization • You can also canonicalize something • What the heck is that? • Rather than slap their hand, fix it for them
  • 182. Canonicalization • You can also canonicalize something • What the heck is that? • Rather than slap their hand, fix it for them • For example, we could... (but won’t)...
  • 183. sub canonicalize_url { my ($self, $url) = @_; if ($url !~ m[^https?://]) { return 'http://'.$url; } return $url; }
  • 184. sub canonicalize_url { my ($self, $url) = @_; if ($url !~ m[^https?://]) { return 'http://'.$url; } return $url; Too naïve, I refuse to } implement it!
  • 185. But it was the best I could come up with on sub canonicalize_url { short notice. my ($self, $url) = @_; if ($url !~ m[^https?://]) { return 'http://'.$url; } return $url; Too naïve, I refuse to } implement it!
  • 186. Halos
  • 187. Halos
  • 188. Halos
  • 189. Halos I’m the view code for this “frame” in the page!
  • 190. Halos
  • 191. Halos
  • 192. Halos
  • 193. Halos View the HTML source, Luke.
  • 194. Halos
  • 195. Halos
  • 196. Halos
  • 203. Page Info • Click on things to view info
  • 204. Page Info • Click on things to view info • SQL Logging
  • 205. Page Info • Click on things to view info • SQL Logging • SQL Profiling
  • 206. Page Info • Click on things to view info • SQL Logging • SQL Profiling • Template Profiling
  • 209. Developer Mode • Module::Refresh • Tracing with Log4Perl
  • 210. Developer Mode • Module::Refresh • Tracing with Log4Perl • Administration Console
  • 211. Developer Mode • Module::Refresh • Tracing with Log4Perl • Administration Console • (actually, that’s Administrator Mode)
  • 213. Deployment • Choice of web servers: FastCGI on Apache / Lighttpd, HTTP::Server::Simple, mod_perl support (a little weak at the moment)
  • 214. Deployment • Choice of web servers: FastCGI on Apache / Lighttpd, HTTP::Server::Simple, mod_perl support (a little weak at the moment) • Choice of DB servers: SQLite (great for getting started/testing, but production is possible), PostgreSQL, MySQL, and other servers supported
  • 215. Deployment • Choice of web servers: FastCGI on Apache / Lighttpd, HTTP::Server::Simple, mod_perl support (a little weak at the moment) • Choice of DB servers: SQLite (great for getting started/testing, but production is possible), PostgreSQL, MySQL, and other servers supported • Use a fast static server to server CSS, JS, images, etc.
  • 216. Deployment • Choice of web servers: FastCGI on Apache / Lighttpd, HTTP::Server::Simple, mod_perl support (a little weak at the moment) • Choice of DB servers: SQLite (great for getting started/testing, but production is possible), PostgreSQL, MySQL, and other servers supported • Use a fast static server to server CSS, JS, images, etc. • Use CSS::Squish and JS::Squish to help compress and cache things
  • 218. Other Goodies • Several authentication plugins: typical email sign-up login, OpenID, Facebook, LDAP, CAS • Additional CRUD view helpers I haven’t demonstrated • Built-in REST interface • Full I18N support built-in • Several Plugins for lots of things: Comments, Single Page Apps, Client-Side Templating, Charting, Tab helpers • Built-in support for Comet (server push) in addition to Ajax
  • 222. Applications • Hiveminder (http://hiveminder.com) • Wifty (http://jifty.org) • Qublog (http://qublog.net)
  • 223. Applications • Hiveminder (http://hiveminder.com) • Wifty (http://jifty.org) • Qublog (http://qublog.net) • Doxory (http://doxory.com)
  • 224. Applications • Hiveminder (http://hiveminder.com) • Wifty (http://jifty.org) • Qublog (http://qublog.net) • Doxory (http://doxory.com) • CommitBit (http://code.bestpractical.com/)
  • 225. Applications • Hiveminder (http://hiveminder.com) • Wifty (http://jifty.org) • Qublog (http://qublog.net) • Doxory (http://doxory.com) • CommitBit (http://code.bestpractical.com/) • Ping / Chat / Clock (examples available with Jifty)
  • 227. Jifty Resources • CPAN (http://search.cpan.org/dist/Jifty/)
  • 228. Jifty Resources • CPAN (http://search.cpan.org/dist/Jifty/) • Jifty Wiki (http://jifty.org)
  • 229. Jifty Resources • CPAN (http://search.cpan.org/dist/Jifty/) • Jifty Wiki (http://jifty.org) • Jifty SVN (http://svn.jifty.org/svn/jifty.org)
  • 230. Jifty Resources • CPAN (http://search.cpan.org/dist/Jifty/) • Jifty Wiki (http://jifty.org) • Jifty SVN (http://svn.jifty.org/svn/jifty.org) • Tutorial (http://search.cpan.org/dist/Jifty/lib/Jifty/Manual/Tutorial.pod)
  • 231. Jifty Resources • CPAN (http://search.cpan.org/dist/Jifty/) • Jifty Wiki (http://jifty.org) • Jifty SVN (http://svn.jifty.org/svn/jifty.org) • Tutorial (http://search.cpan.org/dist/Jifty/lib/Jifty/Manual/Tutorial.pod) • Manual (http://search.cpan.org/dist/Jifty/lib/Jifty/Manual.pod)
  • 232. Jifty Resources • CPAN (http://search.cpan.org/dist/Jifty/) • Jifty Wiki (http://jifty.org) • Jifty SVN (http://svn.jifty.org/svn/jifty.org) • Tutorial (http://search.cpan.org/dist/Jifty/lib/Jifty/Manual/Tutorial.pod) • Manual (http://search.cpan.org/dist/Jifty/lib/Jifty/Manual.pod) • Mailing List: jifty‐devel@lists.jifty.org
  • 233. Jifty Resources • CPAN (http://search.cpan.org/dist/Jifty/) • Jifty Wiki (http://jifty.org) • Jifty SVN (http://svn.jifty.org/svn/jifty.org) • Tutorial (http://search.cpan.org/dist/Jifty/lib/Jifty/Manual/Tutorial.pod) • Manual (http://search.cpan.org/dist/Jifty/lib/Jifty/Manual.pod) • Mailing List: jifty‐devel@lists.jifty.org • IRC Channel: #jifty on irc.freenode.org
  • 234. Qublog • See http://qublog.net • Sample code for QublogMini in SVN: http://qublog.net/svn/QublogMini
  • 235.