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
Hiveminder
Stikkit
Day Planner
iCal/Apple Mail
Sunbird
Lightning
Entourage
Things
OmniFocus
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
Qublog
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
• Method 1: Shipwright
Install Jifty
• Method 1: Shipwright
svn co http://code.bestpractical.com/shipwright/jifty/ jifty-buildercd jifty-builder./bin/shipwright-builder # optionally --skip perl --skip-testcp -rvp /tmp/jifty-$random/jifty /usr/local/jiftyln -s /usr/local/jifty/bin/jifty /usr/local/bin/jifty
Install Jifty
• Method 1: Shipwright
svn co http://code.bestpractical.com/shipwright/jifty/ jifty-buildercd jifty-builder./bin/shipwright-builder # optionally --skip perl --skip-testcp -rvp /tmp/jifty-$random/jifty /usr/local/jiftyln -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-buildercd jifty-builder./bin/shipwright-builder # optionally --skip perl --skip-testcp -rvp /tmp/jifty-$random/jifty /usr/local/jiftyln -s /usr/local/jifty/bin/jifty /usr/local/bin/jifty
• Method 2: CPAN
perl -MCPAN -eshell # or just cpancpan> notest install Jifty
Install Jifty
• Method 1: Shipwright
svn co http://code.bestpractical.com/shipwright/jifty/ jifty-buildercd jifty-builder./bin/shipwright-builder # optionally --skip perl --skip-testcp -rvp /tmp/jifty-$random/jifty /usr/local/jiftyln -s /usr/local/jifty/bin/jifty /usr/local/bin/jifty
• Method 2: CPAN
perl -MCPAN -eshell # or just cpancpan> notest install Jifty
Install Jifty
“notest” saves ~60 minutes according to Audrey
• Method 1: Shipwright
svn co http://code.bestpractical.com/shipwright/jifty/ jifty-buildercd jifty-builder./bin/shipwright-builder # optionally --skip perl --skip-testcp -rvp /tmp/jifty-$random/jifty /usr/local/jiftyln -s /usr/local/jifty/bin/jifty /usr/local/bin/jifty
• Method 2: CPAN
perl -MCPAN -eshell # or just cpancpan> notest install Jifty
Install Jifty
“notest” saves ~60 minutes according to Audrey
“notest” will get you no support according to Jesse
• Method 1: Shipwright
svn co http://code.bestpractical.com/shipwright/jifty/ jifty-buildercd jifty-builder./bin/shipwright-builder # optionally --skip perl --skip-testcp -rvp /tmp/jifty-$random/jifty /usr/local/jiftyln -s /usr/local/jifty/bin/jifty /usr/local/bin/jifty
• Method 2: CPAN
perl -MCPAN -eshell # or just cpancpan> notest install Jifty
Install Jifty
• Method 1: Shipwright
svn co http://code.bestpractical.com/shipwright/jifty/ jifty-buildercd jifty-builder./bin/shipwright-builder # optionally --skip perl --skip-testcp -rvp /tmp/jifty-$random/jifty /usr/local/jiftyln -s /usr/local/jifty/bin/jifty /usr/local/bin/jifty
• Method 2: CPAN
perl -MCPAN -eshell # or just cpancpan> 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-buildercd jifty-builder./bin/shipwright-builder # optionally --skip perl --skip-testcp -rvp /tmp/jifty-$random/jifty /usr/local/jiftyln -s /usr/local/jifty/bin/jifty /usr/local/bin/jifty
• Method 2: CPAN
perl -MCPAN -eshell # or just cpancpan> notest install Jifty
• ActiveState installation on Windows: http://jifty.org/view/DownloadJifty
• Jifty is in Debian unstable
Install Jifty
% jifty app --name QublogMini
% jifty app --name QublogMiniCreating new application QublogMiniCreating directory QublogMini/libCreating directory QublogMini/lib/QublogMiniCreating directory QublogMini/binCreating directory QublogMini/etcCreating directory QublogMini/docCreating directory QublogMini/logCreating directory QublogMini/varCreating directory QublogMini/var/masonCreating directory QublogMini/shareCreating directory QublogMini/share/poCreating directory QublogMini/share/webCreating directory QublogMini/share/web/templatesCreating directory QublogMini/share/web/staticCreating directory QublogMini/lib/QublogMini/ModelCreating directory QublogMini/lib/QublogMini/ActionCreating directory QublogMini/tCreating 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
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.804080INFO - 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.804080INFO - You can connect to your server at http://localhost:8888/
% ./bin/jifty server
Take a Look
OMG! Ponies!!!
Not a very interesting journaling app... let’s fix it.
% ./bin/jifty model --name Comment
% ./bin/jifty model --name CommentWriting file t/00-model-Comment.tWriting file lib/QublogMini/Model/Comment.pm
% ./bin/jifty model --name CommentWriting file t/00-model-Comment.tWriting 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.804080INFO - You can connect to your server at http://localhost:8888/
% bin/jifty model --name Timer
% bin/jifty model --name TimerWriting file lib/QublogMini/Model/Timer.pmWriting file t/00-model-Timer.t
% bin/jifty model --name TimerWriting file lib/QublogMini/Model/Timer.pmWriting 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.2INFO - Upgrading through 0.0.2INFO - Running upgrade scriptINFO - Upgraded to version v0.0.2INFO - 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 EntryWriting file t/00-model-Entry.tWriting file lib/QublogMini/Model/Entry.pm
% bin/jifty model --name EntryWriting file t/00-model-Entry.tWriting 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.3INFO - Upgrading through 0.0.3INFO - Running upgrade scriptINFO - Upgraded to version v0.0.3INFO - You can connect to your server at http://localhost:8888/
Ponies are great!
Ponies are great!
But where’s the UI?
# 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 "Today's Journal" }
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 "Today's Journal" }
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 "Today's Journal" }
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 ProblemsWe need a timer
too.
Where’d it go?
2 ProblemsWe 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 entrysub 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 "Today's Journal" }
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 "Today's Journal" }
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 regiontemplate '/today' => page { { title is "Today's Journal" }
render_region name => 'entries', path => '/entries', ;};
# Replace /today with a regiontemplate '/today' => page { { title is "Today's Journal" }
render_region name => 'entries', path => '/entries', ;};
# Add a new entries fragmenttemplate '/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 properlytemplate '/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 properlytemplate '/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!
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
short notice.
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)
• 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‐[email protected]
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‐[email protected]
• 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!