JQuery-Mobile

 view release on metacpan or  search on metacpan

lib/JQuery/Mobile.pm  view on Meta::CPAN

package JQuery::Mobile;
use strict;
use warnings;
no warnings 'uninitialized';
use Exporter 'import';
our @EXPORT_OK = qw(new head header footer table panel popup page pages form listview collapsible collapsible_set navbar button controlgroup input rangeslider select checkbox radio textarea);

use Clone qw(clone);
use HTML::Entities qw(encode_entities);

our $VERSION = 0.03;
# 54.4

sub new {
	my ($class, %args) = (@_);
	my $self = bless {}, $class;
	$args{config} ||= {};
	
	$self->{config} = {
		'head' => 1, # include <html>, <head>, and <body> tag when rendering a page
		'viewport' => 'width=device-width, initial-scale=1', # default viewport
		'apple-mobile-web-app-capable' => 1,  # enable as apple web app
		'apple-touch-icon' => '', # path to apple web app icon image
		'apple-touch-icon-72' => '', # path to apple web app icon image (72x72 pixels)
		'apple-touch-icon-114' => '', # path to apple web app icon image (114x114 pixels)
		'apple-touch-startup-image' => '', # path to apple web app startup image
		'jquery-mobile-css' => 'http://code.jquery.com/mobile/1.3.2/jquery.mobile-1.3.2.min.css',
		'jquery-mobile-js' => 'http://code.jquery.com/mobile/1.3.2/jquery.mobile-1.3.2.min.js',
		'jquery' => 'http://code.jquery.com/jquery-1.9.1.min.js',
		'app-css' => [], # global application CSS files
		'app-js' => [], # global application JS files
		'app-inline-css' => '      span.invalid{color:#F00000;line-height: 1.5;}', # inline CSS code
		'app-inline-js' => '', # inline JS code
		'app-title' => '', # <title> in <head>
		# a list of default allowed HTML and data-* attributes for UI components (Reference: http://api.jquerymobile.com/data-attribute/)
		'header-footer-html-attribute' => ['id', 'class'],
		'header-footer-data-attribute' => ['id', 'fullscreen', 'position', 'theme'],
		'navbar-html-attribute' => ['id', 'class'],
		'navbar-data-attribute' => ['disable-page-zoom', 'enhance', 'fullscreen', 'iconpos', 'tap-toggle', 'theme', 'transition', 'update-page-padding', 'visible-on-page-show'],
		'navbar-item-html-attribute' => ['id', 'class', 'target'],
		'navbar-item-data-attribute' => ['ajax', 'icon', 'iconpos', 'iconshadow','prefetch', 'theme'],
		'page-html-attribute' => ['id', 'class'],
		# combine data-attributes for page and dialog
		'page-data-attribute' => ['add-back-btn', 'back-btn-text', 'back-btn-theme', 'close-btn', 'close-btn-text', 'corners', 'dom-cache', 'enhance', 'overlay-theme', 'role', 'shadow','theme', 'title', 'tolerance', 'url'],
		'table-html-attribute' => ['id', 'class'],
		'table-data-attribute' => ['mode'],
		'table-head-html-attribute' => ['id', 'class'],
		'table-head-data-attribute' => ['priority'],
		'panel-html-attribute' => ['id', 'class'],
		'panel-data-attribute' => ['corners', 'overlay-theme', 'shadow', 'theme', 'tolerance', 'position-to', 'rel', 'role', 'transition'],
		'popup-html-attribute' => ['id', 'class'],
		'popup-data-attribute' => ['animate', 'dismissible', 'display', 'position', 'position-fixed', 'swipe-close', 'role', 'theme'],
		'listview-html-attribute' => ['id', 'class'],
		'listview-data-attribute' => ['autodividers', 'count-theme', 'divider-theme', 'enhance', 'filter', 'filter-placeholder', 'filter-reveal', 'filter-theme', 'filtertext', 'header-theme', 'inset', 'split-icon', 'split-theme', 'theme'],
		'listview-item-html-attribute' => ['id', 'class'],
		'listview-item-data-attribute' => ['ajax', 'mini', 'rel', 'theme', 'transition'],
		'collapsible-html-attribute' => ['id', 'class'],
		'collapsible-data-attribute' => ['collapsed', 'collapsed-icon', 'content-theme', 'expanded-icon', 'iconpos', 'inset', 'mini', 'theme'],
		'collapsible-set-html-attribute' => ['id', 'class'],
		'collapsible-set-data-attribute' => ['collapsed-icon', 'content-theme', 'expanded-icon', 'iconpos', 'inset', 'mini', 'theme'],
		'controlgroup-html-attribute' => ['id', 'class'],
		'controlgroup-data-attribute' => ['enhance', 'iconpos', 'mini', 'theme', 'type'],
		'button-html-attribute' => ['id', 'name', 'class', 'maxlength', 'size', 'type', 'value'],
		'button-html-anchor-attribute' => ['id', 'class', 'href', 'target'],
		'button-data-attribute' => ['ajax', 'corners', 'dialog', 'direction', 'dom-cache', 'external', 'icon', 'iconpos', 'iconshadow', 'inline', 'mini', 'position-to', 'prefetch', 'rel', 'role', 'shadow', 'theme', 'transition'],
		'form-html-attribute' => ['id', 'action', 'class', 'enctype', 'method'],
		'form-data-attribute' => ['enhance', 'theme', 'ajax'],
		'input-html-attribute' => ['id', 'class', 'disabled', 'max', 'maxlength', 'min', 'name', 'pattern', 'placeholder', 'readonly', 'required', 'size', 'type', 'value', 'accept', 'capture'],
		'input-data-attribute' => ['clear-btn', 'clear-btn-text', 'corners', 'highlight', 'icon', 'iconpos', 'iconshadow', 'inline', 'mini', 'shadow', 'theme', 'track-theme'],
		'textarea-html-attribute' => ['id', 'name', 'class', 'rows', 'cols', 'readonly', 'disabled', 'title', 'required', 'placeholder', 'title', 'pattern'],
		'textarea-data-attribute' => ['clear-btn', 'clear-btn-text', 'mini', 'theme'],
		'select-html-attribute' => ['id', 'class', 'size', 'maxlength', 'readonly', 'disabled', 'title', 'required', 'placeholder', 'title', 'pattern', 'multiple'],
		'select-data-attribute' => ['icon', 'iconpos', 'inline', 'mini', 'native-menu', 'overlay-theme', 'theme', 'role'],
		'radio-checkbox-html-attribute' => ['id', 'class', 'readonly', 'disabled', 'title', 'required', 'placeholder', 'title', 'pattern', 'value'],
		'radio-checkbox-data-attribute' => ['mini', 'theme'],
		'rangeslider-html-attribute' => ['id', 'name', 'class'],
		'rangeslider-data-attribute' => ['highlight', 'mini', 'theme', 'track-theme'],
		'label' => sub {
			my $args = shift;
			return '<strong>' . $args->{label} . '</strong>' if $args->{required};
			return $args->{label};
		},
		'invalid' => sub {
			my $args = shift;
			my $message = $args->{message};

			if (! $message && $args->{type}) {
				$message = {
					'input' => 'Enter "%FIELDNAME%"',
					'checkbox' => 'Check one or more "%FIELDNAME%"',
					'radio' => 'Choose "%FIELDNAME%"',
					'select' => 'Select an option from "%FIELDNAME%"',
					'textarea' => 'Fill in the "%FIELDNAME%"'
				}->{$args->{type}};
			}

			$message ||= 'Enter "%FIELDNAME%"';
			$message =~ s/\%FIELDNAME\%/$args->{label}/g;

			return '<span class="invalid">' . $message . '</span>' if $args->{invalid};
		},
		%{$args{config}},
	};

	return $self;
}

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

	my $head = '  <head>' . "\n" . '    <title>' . $self->{config}->{'app-title'} . '</title>' . "\n" . 
	'    <meta name="viewport" content="' . $self->{config}->{'viewport'} . '" />' . "\n";

lib/JQuery/Mobile.pm  view on Meta::CPAN

		}

		$item_attributes = _html_attribute ($item_attributes, $self->{config}->{'navbar-item-html-attribute'}, $item);

		$navbar .= '            <li><a href="' . $item->{href} . '"' . $item_attributes . '>' . $item->{value} . '</a></li>' . "\n";

	}
	$navbar .= '          </ul>' . "\n";
	$navbar .= '        </div><!-- /navbar -->';
	return $navbar;
}

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

	$args{content} ||= '          <p>Panel Content</p>';
	$args{role} = 'panel';

	my $attributes = _html_attribute('', $self->{config}->{'panel-html-attribute'}, \%args);
	$attributes = _data_attribute($attributes, $self->{config}->{'panel-data-attribute'}, \%args);

	my $panel = '        <div' . $attributes . '>' . "\n";
	$panel .= $args{content} . "\n";
	$panel .= '        </div><!-- /panel -->' . "\n";

	return $panel;
}

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

	unless ($args{content}) {

		$args{content} =  '          <thead>' . "\n" . '            <tr>' . "\n";
		foreach my $header (@{$args{headers}}) {

			my $head_attributes = '';
			if ($args{th} && exists $args{th}->{$header}) {
				$head_attributes = _html_attribute($head_attributes, $self->{config}->{'table-head-html-attribute'}, $args{th}->{$header});
				$head_attributes = _data_attribute($head_attributes, $self->{config}->{'table-head-data-attribute'}, $args{th}->{$header});
			}

			$args{content} .= '              <th' . $head_attributes . '>' . $header . '</th>' . "\n";
		}
		$args{content} .=  '            </tr>' . "\n" . '          </thead>' . "\n";

		$args{content} .=  '          <tbody>'. "\n";
		foreach my $row (@{$args{rows}}) {
			$args{content} .=  '            <tr>' . "\n" . join("\n", map ({'              <td>' . $_ . '</td>'} @{$row})) . "\n" . '            </tr>' . "\n";
		}
		$args{content} .=  '          </tbody>';
	}

	my $attributes = _html_attribute('', $self->{config}->{'table-html-attribute'}, \%args);
	$attributes = _data_attribute($attributes, $self->{config}->{'table-data-attribute'}, \%args);

	my $table = '        <table data-role="table"' . $attributes . '>' . "\n" . $args{content} . "\n" . '        </table>' . "\n";
	return $table;
}

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

	$args{content} ||= '          <p>Popup Content</p>';
	$args{role} = 'popup';

	my $attributes = _html_attribute('', $self->{config}->{'popup-html-attribute'}, \%args);
	$attributes = _data_attribute($attributes, $self->{config}->{'popup-data-attribute'}, \%args);

	my $popup = '        <div' . $attributes . '>' . "\n";
	$popup .= $args{content} . "\n";
	$popup .= '        </div><!-- /popup -->' . "\n";

	return $popup;
}

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

	$args{content} ||= '        <p>Page Content</p>';
	$args{role} ||= 'page';

	my $attributes = _html_attribute('', $self->{config}->{'page-html-attribute'}, \%args);
	$attributes = _data_attribute($attributes, $self->{config}->{'page-data-attribute'}, \%args);

	my $page = '    <div' . $attributes . '>' . "\n";
	$page .= $self->header(%{$args{header}}) if $args{header};
	$page .= $self->panel(%{$args{panel}}) if $args{panel};
	$page .= '      <div data-role="content">' . "\n" . $args{content} . "\n" . '      </div><!-- /content -->' . "\n";
	$page .= $args{after} if $args{after};
	$page .= $self->footer(%{$args{footer}}) if $args{footer};
	$page .= '    </div><!-- /page -->' . "\n";

	return $page if (exists $args{head} && ! $args{head}) || ! $self->{config}->{'head'};
	return "<!DOCTYPE html>\n<html>\n" . $self->head() . "  <body>\n" . $page . "  </body>\n</html>";
}

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

	my $pages = '';	

	foreach my $page (@{$args{pages}}) {
		$page->{head} = 0;
		$pages .= $self->page(%{$page});
	}

	$pages ||= '<p>Multiple Pages</p>';

	return $pages if (exists $args{head} && ! $args{head}) || ! $self->{config}->{'head'};
	return "<!DOCTYPE html>\n<html>\n" . $self->head() . "  <body>\n" . $pages . "  </body>\n</html>";
}

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

	if ($args{collapsibles} && @{$args{collapsibles}}) {
		foreach my $collapsible (@{$args{collapsibles}}) {
			if ($args{active} && ! exists $collapsible->{active}) {
				$collapsible->{active} = $args{active};
			}

			$args{content} .= $self->collapsible(%{$collapsible});
		}
	}
	else {
		$args{content} ||= '          <p>Collapsible Set Content</p>';	
	}	

	my $attributes = _html_attribute('', $self->{config}->{'collapsible-set-html-attribute'}, \%args);
	$attributes = _data_attribute($attributes, $self->{config}->{'collapsible-set-data-attribute'}, \%args);

	my $collapsible_set = '        <div data-role="collapsible-set"' . $attributes . '>' . "\n" . $args{content} . "\n" . '        </div>' . "\n";
	return $collapsible_set;

lib/JQuery/Mobile.pm  view on Meta::CPAN

      	header => {content => '<h1>Home</h1>'}, 
      	content => $list
      },
      {
      	id => 'item-link', 
      	header => {content => '<h1>Item Heading</h1>'}, 
      	content => 'Lorem ipsum dolor sit amet, consectetur adipisicing elit'
      },
    ]
  );

=head1 DESCRIPTION

JQuery::Mobile is an interface to jQuery Mobile. It generates HTML markups, such as navbars, forms, and listviews, that are compatible with jQuery Mobile.

=head1 METHODS

=head2 C<new>

To instantiate a new JQuery::Mobile object:

  my $jquery_mobile = JQuery::Mobile->new();

Here is a list of optional parameters when instantiating a JQuery::Mobile object:

  # default values are shown
  my $jquery_mobile = JQuery::Mobile->new(
     config => {
      'head' => 1, # include <html>, <head>, and <body> tag when rendering a page
      'viewport' => 'width=device-width, initial-scale=1', # default viewport
      'apple-mobile-web-app-capable' => 1, # enable as apple web app
      'apple-touch-icon' => '', # path to apple web app icon image
      'apple-touch-icon-72' => '', # path to apple web app icon image (72x72 pixels)
      'apple-touch-icon-114' => '', # path to apple web app icon image (114x114 pixels)
      'apple-touch-startup-image' => '', # path to apple web app startup image
      'jquery-mobile-css' => 'http://code.jquery.com/mobile/1.3.2/jquery.mobile-1.3.2.min.css',
      'jquery-mobile-js' => 'http://code.jquery.com/mobile/1.3.2/jquery.mobile-1.3.2.min.js',
      'jquery' => 'http://code.jquery.com/jquery-1.9.1.min.js',
      'app-css' => [], # global application CSS files
      'app-js' => [], # global application JS files
      'app-inline-css' => '      span.invalid{color:#F00000;line-height: 1.5;}', # inline CSS code
      'app-inline-js' => '', # inline JS code
      'app-title' => '', # <title> in <head>
     }
  );

The allowed HTML and data-* attributes for each UI component can be customised. By default, HTML attributes are very strict to ensure a clean markup. For data-* attributes, see reference: http://api.jquerymobile.com/data-attribute/.

  # default values are shown
  my $jquery_mobile = JQuery::Mobile->new(
    config => {
      'header-footer-html-attribute' => ['id', 'class'],
      'header-footer-data-attribute' => ['id', 'fullscreen', 'position', 'theme'],
      'navbar-html-attribute' => ['id', 'class'],
      'navbar-data-attribute' => ['disable-page-zoom', 'enhance', 'fullscreen', 'iconpos', 'tap-toggle', 'theme', 'transition', 'update-page-padding', 'visible-on-page-show'],
      'navbar-item-html-attribute' => ['id', 'class', 'target'],
      'navbar-item-data-attribute' => ['ajax', 'icon', 'iconpos', 'iconshadow','prefetch', 'theme'],
      'page-html-attribute' => ['id', 'class'],
      # combine data-attributes for page and dialog
      'page-data-attribute' => ['add-back-btn', 'back-btn-text', 'back-btn-theme', 'close-btn', 'close-btn-text', 'corners', 'dom-cache', 'enhance', 'overlay-theme', 'role', 'shadow','theme', 'title', 'tolerance', 'url'],
      'popup-html-attribute' => ['id', 'class'],
      'popup-data-attribute' => ['corners', 'overlay-theme', 'shadow', 'theme', 'tolerance', 'position-to', 'rel', 'role', 'transition'],
      'listview-html-attribute' => ['id', 'class'],
      'listview-data-attribute' => ['autodividers', 'count-theme', 'divider-theme', 'enhance', 'filter', 'filter-placeholder', 'filter-theme', 'filtertext', 'header-theme', 'inset', 'split-icon', 'split-theme', 'theme'],
      'listview-item-html-attribute' => ['id', 'class'],
      'listview-item-data-attribute' => ['ajax', 'mini', 'rel', 'theme', 'transition'],
      'collapsible-html-attribute' => ['id', 'class'],
      'collapsible-data-attribute' => ['collapsed', 'collapsed-icon', 'content-theme', 'expanded-icon', 'iconpos', 'inset', 'mini', 'theme'],
      'collapsible-set-html-attribute' => ['id', 'class'],
      'collapsible-set-data-attribute' => ['collapsed-icon', 'content-theme', 'expanded-icon', 'iconpos', 'inset', 'mini', 'theme'],
      'controlgroup-html-attribute' => ['id', 'class'],
      'controlgroup-data-attribute' => ['enhance', 'iconpos', 'theme', 'type'],
      'button-html-attribute' => ['id', 'name', 'class', 'maxlength', 'size', 'type', 'value'],
      'button-html-anchor-attribute' => ['id', 'class', 'href', 'target'],
      'button-data-attribute' => ['ajax', 'corners', 'dialog', 'direction', 'dom-cache', 'external', 'icon', 'iconpos', 'iconshadow', 'inline', 'mini', 'position-to', 'prefetch', 'rel', 'role', 'shadow', 'theme', 'transition'],
      'form-html-attribute' => ['id', 'action', 'class', 'enctype', 'method'],
      'form-data-attribute' => ['enhance', 'theme', 'ajax'],
      'input-html-attribute' => ['id', 'class', 'disabled', 'max', 'maxlength', 'min', 'name', 'pattern', 'placeholder', 'readonly', 'required', 'size', 'type', 'value', 'accept', 'capture'],
      'input-data-attribute' => ['clear-btn', 'clear-btn-text', 'corners', 'highlight', 'icon', 'iconpos', 'iconshadow', 'inline', 'mini', 'shadow', 'theme', 'track-theme'],
      'textarea-html-attribute' => ['id', 'name', 'class', 'rows', 'cols', 'readonly', 'disabled', 'title', 'required', 'placeholder', 'title', 'pattern'],
      'textarea-data-attribute' => ['clear-btn', 'clear-btn-text', 'mini', 'theme'],
      'select-html-attribute' => ['id', 'class', 'size', 'maxlength', 'readonly', 'disabled', 'title', 'required', 'placeholder', 'title', 'pattern'],
      'select-data-attribute' => ['icon', 'iconpos', 'inline', 'mini', 'native-menu', 'overlay-theme', 'placeholder', 'theme'],
      'radio-checkbox-html-attribute' => ['id', 'class', 'readonly', 'disabled', 'title', 'required', 'placeholder', 'title', 'pattern', 'value'],
      'radio-checkbox-data-attribute' => ['mini', 'theme']
     }
  );

The C<label> parameter accepts a sub callback to alter how form field labels are being generated:

  my $jquery_mobile = JQuery::Mobile->new(
    config => {
      'label' => sub {
        my $args = shift;
        return '<strong>' . $args->{label} . '*</strong>' if $args->{required};
        return $args->{label};
      },
    }
  );

=head2 C<head>

C<head()> is called by C<page()> and C<pages()> internally to render the HTML header. Parameters via C<new()> controlls the output of C<head()>.

=head2 C<header>

C<header()> generates header toolbars. Text, buttons, or C<navbar()> can be passed to the C<content> parameter.

  print $jquery_mobile->header(
    content => $jquery_mobile->button(href => '#', value => 'Home', icon => 'home', iconpos => 'notext') . '<h1>Main Title</h1>',
  );

prints:

  <div data-role="header">
    <a data-icon="home" data-iconpos="notext" data-role="button" href="#">Home</a>
    <h1>Main Title</h1>
  </div><!-- /header -->

Attributes defined in C<header-footer-html-attribute> and C<header-footer-data-attribute> can be passed to C<header()>, based on the default configuration:

  print $jquery_mobile->header(
    'content' => '<h1>Header Content</h1>',
    'id' => 'home-main-header',
    'class' => 'site-header',
    'data-id' => 'main-header', # use the 'data-*' prefix since 'id' is both a HTML and data atrribute
    'fullscreen' => 'true',
    'position' => 'fixed',
    'theme' => 'e'
  );

=head2 C<footer>

Similar to C<header()>, C<footer()> generates footer toolbars. Attributes defined in C<header-footer-html-attribute> and C<header-footer-data-attribute> can be passed to C<footer()>.

  print $jquery_mobile->footer(
    position => 'fixed',
    content => 'Footer content'
  );

=head2 C<popup>

C<popup()> generates popup container divs. Content can be passed via the C<content> parameter:

  my $popup = $jquery_mobile->popup(id => 'popup', content => 'Lorem ipsum dolor sit amet, consectetur adipisicing elit');

Attributes defined in C<popup-html-attribute> and C<popup-data-attribute> can be passed to C<popup()>.
Please note that as of writing (jQuery Mobile 1.2), according to the jQuery Mobile website, popups "must live within the page wrapper (for now)".

=head2 C<page>

C<page()> generates a container divs. It accepts the following parameters:

=over

=item C<role>

C<role> can be either "page" or "dialog", defaulted to "page".

=item C<head>

C<page()> includes HTML C<head> and C<body> wrapper tags by default. Set C<head> to "0" to disable that:

  print $jquery_mobile->page(head => 0);

=item C<content>

C<content> accepts text string.

=item C<header>

C<header> includes a header toolbars. C<header> accepts a hashref of parameters that gets passed directly to C<header()>.

=item C<footer>

C<footer> includes a footer toolbars. C<footer> accepts a hashref of parameters that gets passed directly to C<footer()>.

=back

For example:

  print $jquery_mobile->page(
    header => {
      content => $jquery_mobile->button(href => '#', value => 'Home', icon => 'home', iconpos => 'notext') . '<h1>Main Title</h1>',
    },
    footer => {
      position => 'fixed',
      content => '<h3>Footer content</h3>'
    },
    content => 'Lorem ipsum dolor sit amet, consectetur adipisicing elit'
  );

=head2 C<pages>

C<pages()> generates a "multi-page" template. It accepts the following parameters:

=over

=item C<head>

C<pages()> includes HTML C<head> and C<body> wrapper tags by default. Set C<head> to "0" to disable that.

=item C<pages>

C<pages> accepts an arrayref of parameters acceptable by C<page()>.

=back



( run in 1.072 second using v1.01-cache-2.11-cpan-364913b4093 )