Skip to content
Browse files

Added server-side pagination, filtering and sorting for the Album view.

Using a modified version of kohana/pagination and the
 kostache-pagination-helper.
Overriding kohana/email since it hasn't been updated for 3.2
Overriding pagination/basic to put some space between items
 and add a 'current' class to the basic and punbb paginations
Modified style.css for wider layout and table stylings.
  • Loading branch information...
1 parent 10674c1 commit 7fdeb89bfddc0b2220f2c3e2eee42d9b30c32180 @ddrake committed Aug 2, 2011
View
12 .gitmodules
@@ -10,9 +10,15 @@
[submodule "modules/orm"]
path = modules/orm
url = git://github.com/kohana/orm.git
-[submodule "modules/email"]
- path = modules/email
- url = git://github.com/shadowhand/email.git
[submodule "modules/kostache"]
path = modules/kostache
url = git://github.com/zombor/KOstache.git
+[submodule "modules/pagination"]
+ path = modules/pagination
+ url = git://github.com/kohana/pagination.git
+[submodule "modules/kostache-pagination-helper"]
+ path = modules/kostache-pagination-helper
+ url = git://github.com/czukowski/kostache-pagination-helper.git
+[submodule "modules/email"]
+ path = modules/email
+ url = git://github.com/shadowhand/email.git
View
11 application/classes/controller/album.php
@@ -22,7 +22,7 @@ public function action_index()
$view = new View_Pages_Album_List;
$this->response->body($view); // . View::factory('profiler/stats');
}
-
+
public function action_add()
{
$album = ORM::factory('album');
@@ -70,8 +70,7 @@ public function action_save()
}
catch (ORM_Validation_Exception $e)
{
- // todo: specify a real messages file here...
- $errors = Arr::flatten($e->errors('hack'));
+ $errors = $e->errors('models');
}
if ($album->id == null)
{
@@ -114,6 +113,7 @@ public function action_details()
$error_msg = "<h3>couldn't find info for that album...</h3>";
if ( ! Fragment::load("album_{$id}", Date::DAY * 7))
{
+ $album = ORM::factory('album',$id);
try
{
$config = Kohana::$config->load('lastfm');
@@ -143,15 +143,16 @@ private function delete_album_fragment($id)
try
{
Fragment::delete("album_{$id}");
- echo Debug::vars('just deleted fragment');
}
catch (Exception $e) {}
}
// Redirect the user to the list
private function redirect_to_list()
{
- $uri = Route::get('default')->uri(array('controller'=>'album'));
+ $session = Session::instance();
+ $query = $session->get('album_list');
+ $uri = Route::get('default')->uri(array('controller'=>'album', 'action'=>'index')) . URL::query($query);
$this->request->redirect($uri);
}
View
367 application/classes/kohana/email.php
@@ -0,0 +1,367 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Email message building and sending.
+ *
+ * @package Kohana
+ * @category Email
+ * @author Kohana Team
+ * @copyright (c) 2007-2011 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Kohana_Email {
+
+ // Current module version
+ const VERSION = '1.0.0';
+
+ /**
+ * @var object Swiftmailer instance
+ */
+ protected static $_mailer;
+
+ /**
+ * Creates a SwiftMailer instance.
+ *
+ * @return object Swift object
+ */
+ public static function mailer()
+ {
+ if ( ! Email::$_mailer)
+ {
+ // Load email configuration, make sure minimum defaults are set
+ $config = Kohana::config('email')->as_array() + array(
+ 'driver' => 'native',
+ 'options' => array(),
+ );
+
+ // Extract configured options
+ extract($config, EXTR_SKIP);
+
+ if ($driver === 'smtp')
+ {
+ // Create SMTP transport
+ $transport = Swift_SmtpTransport::newInstance($options['hostname']);
+
+ if (isset($options['port']))
+ {
+ // Set custom port number
+ $transport->setPort($options['port']);
+ }
+
+ if (isset($options['encryption']))
+ {
+ // Set encryption
+ $transport->setEncryption($options['encryption']);
+ }
+
+ if (isset($options['username']))
+ {
+ // Require authentication, username
+ $transport->setUsername($options['username']);
+ }
+
+ if (isset($options['password']))
+ {
+ // Require authentication, password
+ $transport->setPassword($options['password']);
+ }
+
+ if (isset($options['timeout']))
+ {
+ // Use custom timeout setting
+ $transport->setTimeout($options['timeout']);
+ }
+ }
+ elseif ($driver === 'sendmail')
+ {
+ // Create sendmail transport
+ $transport = Swift_SendmailTransport::newInstance();
+
+ if (isset($options['command']))
+ {
+ // Use custom sendmail command
+ $transport->setCommand($options['command']);
+ }
+ }
+ else
+ {
+ // Create native transport
+ $transport = Swift_MailTransport::newInstance();
+
+ if (isset($options['params']))
+ {
+ // Set extra parameters for mail()
+ $transport->setExtraParams($options['params']);
+ }
+ }
+
+ // Create the SwiftMailer instance
+ Email::$_mailer = Swift_Mailer::newInstance($transport);
+ }
+
+ return Email::$_mailer;
+ }
+
+ /**
+ * Create a new email message.
+ *
+ * @param string message subject
+ * @param string message body
+ * @param string body mime type
+ * @return Email
+ */
+ public static function factory($subject = NULL, $message = NULL, $type = NULL)
+ {
+ return new Email($subject, $message, $type);
+ }
+
+ /**
+ * @var object message instance
+ */
+ protected $_message;
+
+ /**
+ * Initialize a new Swift_Message, set the subject and body.
+ *
+ * @param string message subject
+ * @param string message body
+ * @param string body mime type
+ * @return void
+ */
+ public function __construct($subject = NULL, $message = NULL, $type = NULL)
+ {
+ // Create a new message, match internal character set
+ $this->_message = Swift_Message::newInstance()
+ ->setCharset(Kohana::$charset)
+ ;
+
+ if ($subject)
+ {
+ // Apply subject
+ $this->subject($subject);
+ }
+
+ if ($message)
+ {
+ // Apply message, with type
+ $this->message($message, $type);
+ }
+ }
+
+ /**
+ * Set the message subject.
+ *
+ * @param string new subject
+ * @return Email
+ */
+ public function subject($subject)
+ {
+ // Change the subject
+ $this->_message->setSubject($subject);
+
+ return $this;
+ }
+
+ /**
+ * Set the message body. Multiple bodies with different types can be added
+ * by calling this method multiple times. Every email is required to have
+ * a "text/plain" message body.
+ *
+ * @param string new message body
+ * @param string mime type: text/html, etc
+ * @return Email
+ */
+ public function message($body, $type = NULL)
+ {
+ if ( ! $type OR $type === 'text/plain')
+ {
+ // Set the main text/plain body
+ $this->_message->setBody($body);
+ }
+ else
+ {
+ // Add a custom mime type
+ $this->_message->addPart($body, $type);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add one or more email recipients..
+ *
+ * // A single recipient
+ * $email->to('john.doe@domain.com', 'John Doe');
+ *
+ * // Multiple entries
+ * $email->to(array(
+ * 'frank.doe@domain.com',
+ * 'jane.doe@domain.com' => 'Jane Doe',
+ * ));
+ *
+ * @param mixed single email address or an array of addresses
+ * @param string full name
+ * @param string recipient type: to, cc, bcc
+ * @return Email
+ */
+ public function to($email, $name = NULL, $type = 'to')
+ {
+ if (is_array($email))
+ {
+ foreach ($email as $key => $value)
+ {
+ if (ctype_digit((string) $key))
+ {
+ // Only an email address, no name
+ $this->to($value, NULL, $type);
+ }
+ else
+ {
+ // Email address and name
+ $this->to($key, $value, $type);
+ }
+ }
+ }
+ else
+ {
+ // Call $this->_message->{add$Type}($email, $name)
+ call_user_func(array($this->_message, 'add'.ucfirst($type)), $email, $name);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add a "carbon copy" email recipient.
+ *
+ * @param string email address
+ * @param string full name
+ * @return Email
+ */
+ public function cc($email, $name = NULL)
+ {
+ return $this->to($email, $name, 'cc');
+ }
+
+ /**
+ * Add a "blind carbon copy" email recipient.
+ *
+ * @param string email address
+ * @param string full name
+ * @return Email
+ */
+ public function bcc($email, $name = NULL)
+ {
+ return $this->to($email, $name, 'bcc');
+ }
+
+ /**
+ * Add email senders.
+ *
+ * @param string email address
+ * @param string full name
+ * @param string sender type: from, replyto
+ * @return Email
+ */
+ public function from($email, $name = NULL, $type = 'from')
+ {
+ // Call $this->_message->{add$Type}($email, $name)
+ call_user_func(array($this->_message, 'add'.ucfirst($type)), $email, $name);
+
+ return $this;
+ }
+
+ /**
+ * Add "reply to" email sender.
+ *
+ * @param string email address
+ * @param string full name
+ * @return Email
+ */
+ public function reply_to($email, $name = NULL)
+ {
+ return $this->from($email, $name, 'replyto');
+ }
+
+ /**
+ * Add actual email sender.
+ *
+ * [!!] This must be set when defining multiple "from" addresses!
+ *
+ * @param string email address
+ * @param string full name
+ * @return Email
+ */
+ public function sender($email, $name = NULL)
+ {
+ $this->_message->setSender($email, $name);
+ }
+
+ /**
+ * Set the return path for bounce messages.
+ *
+ * @param string email address
+ * @return Email
+ */
+ public function return_path($email)
+ {
+ $this->_message->setReturnPath($email);
+
+ return $this;
+ }
+
+ /**
+ * Access the raw [Swiftmailer message](http://swiftmailer.org/docs/messages).
+ *
+ * @return Swift_Message
+ */
+ public function raw_message()
+ {
+ return $this->_message;
+ }
+
+ /**
+ * Attach a file.
+ *
+ * @param string file path
+ * @return Email
+ */
+ public function attach_file($path)
+ {
+ $this->_message->attach(Swift_Attachment::fromPath($path));
+
+ return $this;
+ }
+
+ /**
+ * Attach content to be sent as a file.
+ *
+ * @param binary file contents
+ * @param string file name
+ * @param string mime type
+ * @return Email
+ */
+ public function attach_content($data, $file, $mime = NULL)
+ {
+ if ( ! $mime)
+ {
+ // Get the mime type from the filename
+ $mime = File::mime_by_ext(pathinfo($file, PATHINFO_EXTENSION));
+ }
+
+ $this->_message->attach(Swift_Attachment::newInstance($data, $file, $mime));
+
+ return $this;
+ }
+
+ /**
+ * Send the email. Failed recipients can be collected by passing an array.
+ *
+ * @param array failed recipient list, by reference
+ * @return boolean
+ */
+ public function send(array & $failed = NULL)
+ {
+ return Email::mailer()->send($this->_message, $failed);
+ }
+
+} // End email
View
286 application/classes/kohana/pagination.php
@@ -0,0 +1,286 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Pagination links generator.
+ *
+ * @package Kohana/Pagination
+ * @category Base
+ * @author Kohana Team
+ * @copyright (c) 2008-2009 Kohana Team
+ * @license http://kohanaphp.com/license.html
+ */
+class Kohana_Pagination {
+
+ // Merged configuration settings
+ protected $config = array(
+ 'current_page' => array('source' => 'query_string', 'key' => 'page'),
+ 'total_items' => 0,
+ 'items_per_page' => 10,
+ 'view' => 'pagination/basic',
+ 'auto_hide' => TRUE,
+ 'first_page_in_url' => FALSE,
+ );
+
+ // Current page number
+ protected $current_page;
+
+ // Total item count
+ protected $total_items;
+
+ // How many items to show per page
+ protected $items_per_page;
+
+ // Total page count
+ protected $total_pages;
+
+ // Item offset for the first item displayed on the current page
+ protected $current_first_item;
+
+ // Item offset for the last item displayed on the current page
+ protected $current_last_item;
+
+ // Previous page number; FALSE if the current page is the first one
+ protected $previous_page;
+
+ // Next page number; FALSE if the current page is the last one
+ protected $next_page;
+
+ // First page number; FALSE if the current page is the first one
+ protected $first_page;
+
+ // Last page number; FALSE if the current page is the last one
+ protected $last_page;
+
+ // Query offset
+ protected $offset;
+
+ /**
+ * Creates a new Pagination object.
+ *
+ * @param array configuration
+ * @return Pagination
+ */
+ public static function factory(array $config = array())
+ {
+ return new Pagination($config);
+ }
+
+ /**
+ * Creates a new Pagination object.
+ *
+ * @param array configuration
+ * @return void
+ */
+ public function __construct(array $config = array())
+ {
+ // Overwrite system defaults with application defaults
+ $this->config = $this->config_group() + $this->config;
+
+ // Pagination setup
+ $this->setup($config);
+ }
+
+ /**
+ * Retrieves a pagination config group from the config file. One config group can
+ * refer to another as its parent, which will be recursively loaded.
+ *
+ * @param string pagination config group; "default" if none given
+ * @return array config settings
+ */
+ public function config_group($group = 'default')
+ {
+ // Load the pagination config file
+ $config_file = Kohana::config('pagination');
+
+ // Initialize the $config array
+ $config['group'] = (string) $group;
+
+ // Recursively load requested config groups
+ while (isset($config['group']) AND isset($config_file->$config['group']))
+ {
+ // Temporarily store config group name
+ $group = $config['group'];
+ unset($config['group']);
+
+ // Add config group values, not overwriting existing keys
+ $config += $config_file->$group;
+ }
+
+ // Get rid of possible stray config group names
+ unset($config['group']);
+
+ // Return the merged config group settings
+ return $config;
+ }
+
+ /**
+ * Loads configuration settings into the object and (re)calculates pagination if needed.
+ * Allows you to update config settings after a Pagination object has been constructed.
+ *
+ * @param array configuration
+ * @return object Pagination
+ */
+ public function setup(array $config = array())
+ {
+ if (isset($config['group']))
+ {
+ // Recursively load requested config groups
+ $config += $this->config_group($config['group']);
+ }
+
+ // Overwrite the current config settings
+ $this->config = $config + $this->config;
+
+ // Only (re)calculate pagination when needed
+ if ($this->current_page === NULL
+ OR isset($config['current_page'])
+ OR isset($config['total_items'])
+ OR isset($config['items_per_page']))
+ {
+ // Retrieve the current page number
+ if ( ! empty($this->config['current_page']['page']))
+ {
+ // The current page number has been set manually
+ $this->current_page = (int) $this->config['current_page']['page'];
+ }
+ else
+ {
+ switch ($this->config['current_page']['source'])
+ {
+ case 'query_string':
+ $this->current_page = isset($_GET[$this->config['current_page']['key']])
+ ? (int) $_GET[$this->config['current_page']['key']]
+ : 1;
+ break;
+
+ case 'route':
+ $this->current_page = (int) Request::current()->param($this->config['current_page']['key'], 1);
+ break;
+ }
+ }
+
+ // Calculate and clean all pagination variables
+ $this->total_items = (int) max(0, $this->config['total_items']);
+ $this->items_per_page = (int) max(1, $this->config['items_per_page']);
+ $this->total_pages = (int) ceil($this->total_items / $this->items_per_page);
+
+ $this->current_page = (int) min(max(1, $this->current_page), max(1, $this->total_pages));
+ $this->current_first_item = (int) min((($this->current_page - 1) * $this->items_per_page) + 1, $this->total_items);
+ $this->current_last_item = (int) min($this->current_first_item + $this->items_per_page - 1, $this->total_items);
+ $this->previous_page = ($this->current_page > 1) ? $this->current_page - 1 : FALSE;
+ $this->next_page = ($this->current_page < $this->total_pages) ? $this->current_page + 1 : FALSE;
+ $this->first_page = ($this->current_page === 1) ? FALSE : 1;
+ $this->last_page = ($this->current_page >= $this->total_pages) ? FALSE : $this->total_pages;
+ $this->offset = (int) (($this->current_page - 1) * $this->items_per_page);
+ }
+
+ // Chainable method
+ return $this;
+ }
+
+ /**
+ * Generates the full URL for a certain page.
+ *
+ * @param integer page number
+ * @return string page URL
+ */
+ public function url($page = 1)
+ {
+ // Clean the page number
+ $page = max(1, (int) $page);
+
+ // No page number in URLs to first page
+ if ($page === 1 AND ! $this->config['first_page_in_url'])
+ {
+ $page = NULL;
+ }
+
+ switch ($this->config['current_page']['source'])
+ {
+ case 'query_string':
+ return URL::site(Request::current()->uri()).URL::query(array($this->config['current_page']['key'] => $page));
+
+ case 'route':
+ return URL::site(Request::current()->uri(array($this->config['current_page']['key'] => $page))).URL::query();
+ }
+
+ return '#';
+ }
+
+ /**
+ * Checks whether the given page number exists.
+ *
+ * @param integer page number
+ * @return boolean
+ * @since 3.0.7
+ */
+ public function valid_page($page)
+ {
+ // Page number has to be a clean integer
+ if ( ! Validate::digit($page))
+ return FALSE;
+
+ return $page > 0 AND $page <= $this->total_pages;
+ }
+
+ /**
+ * Renders the pagination links.
+ *
+ * @param mixed string of the view to use, or a Kohana_View object
+ * @return string pagination output (HTML)
+ */
+ public function render($view = NULL)
+ {
+ // Automatically hide pagination whenever it is superfluous
+ if ($this->config['auto_hide'] === TRUE AND $this->total_pages <= 1)
+ return '';
+
+ if ($view === NULL)
+ {
+ // Use the view from config
+ $view = $this->config['view'];
+ }
+
+ if ( ! $view instanceof Kohana_View)
+ {
+ // Load the view file
+ $view = View::factory($view);
+ }
+
+ // Pass on the whole Pagination object
+ return $view->set(get_object_vars($this))->set('page', $this)->render();
+ }
+
+ /**
+ * Renders the pagination links.
+ *
+ * @return string pagination output (HTML)
+ */
+ public function __toString()
+ {
+ return $this->render();
+ }
+
+ /**
+ * Returns a Pagination property.
+ *
+ * @param string URI of the request
+ * @return mixed Pagination property; NULL if not found
+ */
+ public function __get($key)
+ {
+ return isset($this->$key) ? $this->$key : NULL;
+ }
+
+ /**
+ * Updates a single config setting, and recalculates pagination if needed.
+ *
+ * @param string config key
+ * @param mixed config value
+ * @return void
+ */
+ public function __set($key, $value)
+ {
+ $this->setup(array($key => $value));
+ }
+
+} // End Pagination
View
9 application/classes/model/album.php
@@ -19,7 +19,14 @@ public function rules()
),
);
}
-
+ public function labels()
+ {
+ return array(
+ 'name' => 'Album name',
+ 'artist' => 'Artist',
+ 'genre_id' => 'Genre',
+ );
+ }
public function filters()
{
return array(
View
24 application/classes/model/user.php
@@ -0,0 +1,24 @@
+<?php defined('SYSPATH') or die('No direct access allowed.');
+
+class Model_User extends Model_Auth_User {
+
+ public function labels()
+ {
+ return array(
+ 'username' => 'User Name',
+ 'email' => 'Email',
+ 'password' => 'Password',
+ );
+ }
+ public static function get_password_validation($values)
+ {
+ return Validation::factory($values)
+ ->rule('password', 'min_length', array(':value', 8))
+ ->rule('password_confirm', 'matches', array(':validation', ':field', 'password'))
+ ->labels(array(
+ 'password' => 'Password',
+ 'password_confirm' => 'Password Confirmation'
+ ));
+ }
+
+} // End User Model
View
58 application/classes/pagination/basic.php
@@ -0,0 +1,58 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Basic pagination style, based on example in Kostache readme
+ * @see https://github.com/zombor/KOstache#readme
+ *
+ * @package Kostache
+ * @category Pagination
+ * @author Korney Czukowski
+ * @copyright (c) 2011 Korney Czukowski
+ * @license MIT License
+ */
+class Pagination_Basic extends Kostache_Pagination_Base
+{
+ public $items = array();
+
+ /**
+ * @return Kohana_Pagination_Basic
+ */
+ public function render()
+ {
+ // First.
+ $first['title'] = 'first';
+ $first['name'] = __('first');
+ $first['url'] = ($this->pagination->first_page !== FALSE) ? $this->pagination->url($this->pagination->first_page) : FALSE;
+ $this->items[] = $first;
+
+ // Prev.
+ $prev['title'] = 'prev';
+ $prev['name'] = __('previous');
+ $prev['url'] = ($this->pagination->previous_page !== FALSE) ? $this->pagination->url($this->pagination->previous_page) : FALSE;
+ $this->items[] = $prev;
+
+ // Numbers.
+ for ($i=1; $i<=$this->pagination->total_pages; $i++)
+ {
+ $item = array();
+
+ $item['num'] = TRUE;
+ $item['name'] = $i;
+ $item['url'] = ($i != $this->pagination->current_page) ? $this->pagination->url($i) : FALSE;
+ $item['current'] = ($item['url']==FALSE); // added this so we can <strong> the current page
+ $this->items[] = $item;
+ }
+ // Next.
+ $next['title'] = 'next';
+ $next['name'] = __('next');
+ $next['url'] = ($this->pagination->next_page !== FALSE) ? $this->pagination->url($this->pagination->next_page) : FALSE;
+ $this->items[] = $next;
+
+ // Last.
+ $last['title'] = 'last';
+ $last['name'] = __('last');
+ $last['url'] = ($this->pagination->last_page !== FALSE) ? $this->pagination->url($this->pagination->last_page) : FALSE;
+ $this->items[] = $last;
+
+ return $this;
+ }
+}
View
88 application/classes/pagination/punbb.php
@@ -0,0 +1,88 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Basic pagination style like in PunBB
+ * @preview Displayed 31-60 of 150
+ * @preview Pages: 1 … 4 5 6 7 8 … 15
+ *
+ * @package Kostache
+ * @category Pagination
+ * @author Korney Czukowski
+ * @copyright (c) 2011 Korney Czukowski
+ * @license MIT License
+ */
+class Kohana_Pagination_PunBB extends Kostache_Pagination_Base
+{
+ public $items = array();
+
+ /**
+ * @return string
+ */
+ public function displayed()
+ {
+ return __('Displayed :start-:end of :total', array(
+ ':start' => $this->pagination->current_first_item,
+ ':end' => $this->pagination->current_last_item,
+ ':total' => $this->pagination->total_items,
+ ));
+ }
+
+ /**
+ * @return string
+ */
+ public function pages()
+ {
+ return __('Pages');
+ }
+
+ /**
+ * @return Kohana_Pagination_Basic
+ */
+ public function render()
+ {
+ // First
+ if ($this->pagination->current_page > 3)
+ {
+ $this->items[] = array(
+ 'name' => '1',
+ 'url' => ($this->pagination->first_page !== FALSE) ? $this->pagination->url($this->pagination->first_page) : FALSE,
+ );
+ if ($this->pagination->current_page != 4)
+ {
+ $this->items[] = array('name' => '&hellip;');
+ }
+ }
+ // Middle part
+ for ($i = $this->pagination->current_page - 2, $stop = $this->pagination->current_page + 3; $i < $stop; ++$i)
+ {
+ if ($i < 1 OR $i > $this->pagination->total_pages)
+ {
+ continue;
+ }
+ $item = array();
+ $item['name'] = $i;
+ if ($this->pagination->current_page == $i)
+ {
+ $item['num'] = TRUE;
+ }
+ else
+ {
+ $item['url'] = $this->pagination->url($i);
+ }
+ $this->items[] = $item;
+ }
+ // Last
+ if ($this->pagination->current_page <= $this->pagination->total_pages - 3)
+ {
+ if ($this->pagination->current_page != $this->pagination->total_pages - 3)
+ {
+ $this->items[] = array('name' => '&hellip;');
+ }
+ $this->items[] = array(
+ 'name' => $this->pagination->total_pages,
+ 'url' => $this->pagination->url($this->pagination->total_pages),
+ );
+ }
+
+ return $this;
+ }
+}
View
4 application/classes/view/pages/album/form.php
@@ -29,10 +29,8 @@ public function links()
public function genre_list()
{
- $genre_model = new Model_Genre;
- // FYI: example of how to combine db functions with ORM... NICE!
- //$db_genres_list = $genre_model->where('name', '<', 'F')->find_all();
$genre_list = array();
+ $genre_model = ORM::Factory('genre');
foreach ($genre_model->find_all()->as_array() as $genre)
{
$genre = $genre->as_array();
View
172 application/classes/view/pages/album/list.php
@@ -7,29 +7,108 @@ class View_Pages_Album_List extends Kostache_Layout {
public $meta_description = 'A list of awesome albums';
public $meta_copyright = 'dowdrake.com';
public $list_title = 'Current Album List';
-
+ private $query;
+
+ public function __construct($template = NULL, array $partials = NULL)
+ {
+ parent::__construct($template, $partials);
+ $this->query = Request::current()->query();
+ $session = Session::instance();
+ $session->set('album_list',$this->query);
+
+ // Create pagination instance
+ $this->pagination = Kostache_Pagination::factory(array(
+ 'kostache' => $this,
+ 'items_per_page' => 5,
+ // unless we pass the count here, the number of pages, etc. don't get calculated in the setup
+ 'total_items' => $this->get_count(),
+ 'partial' => 'pagination',
+ 'view' => 'pagination/basic',
+ ));
+ }
+ // PUBLIC METHODS FOR USE BY THE TEMPLATE
public function base()
{
return URL::base(Request::$initial, TRUE);
}
-
+ public function pagination()
+ {
+ return $this->pagination->render();
+ }
+ public function genre_list_filter()
+ {
+ $genre_list = array(array('id'=>NULL, 'name'=>'<No Selection>'));
+ $genre_model = ORM::Factory('genre');
+ $val = Request::current()->query('fc-albums:genre_id');
+ if (!empty($val))
+ {
+ list($op,$val) = explode('|',$val,2);
+ }
+ foreach ($genre_model->find_all()->as_array() as $g)
+ {
+ $genre = $g->as_array();
+ $genre['selected'] = ($genre['id'] == (int)$val);
+ $genre_list[] = $genre;
+ }
+ return $genre_list;
+ }
+ public function name_filter()
+ {
+ $val = Request::current()->query('fc-albums:name');
+ if (!empty($val))
+ {
+ list($op,$val) = explode('|',$val,2);
+ }
+ return $val;
+ }
+ public function artist_filter()
+ {
+ $val = Request::current()->query('fc-albums:artist');
+ if (!empty($val))
+ {
+ list($op,$val) = explode('|',$val,2);
+ }
+ return $val;
+ }
public function albums()
{
// using with('genre') improves efficiency by pre-loading the genre info (avoids lazy loading)
- $albums = array();
$route = Route::get('default');
- foreach(ORM::factory('Album')->with('genre')->order_by('artist')->order_by('name')->find_all()->as_array() as $album)
+ $as = ORM::factory('Album')
+ ->with('genre');
+ $as->select(array('genre.name','genre_name'));
+
+ $this->albums_apply_filters($as);
+
+ $sort = $this->get_sort_from_request(TRUE);
+ if ($sort !== NULL and count($sort) > 0)
+ {
+ $as->order_by($sort['column'], $sort['dir']);
+ }
+ if ($sort['column'] != 'name')
+ {
+ $as->order_by('name', 'asc');
+ }
+
+ $as = $as->limit($this->pagination->items_per_page)
+ ->offset($this->pagination->offset)
+ ->find_all()
+ ->as_array();
+
+ $albums = array();
+ $odd = TRUE;
+ foreach($as as $album)
{
$album = $album->as_array();
- $album['genre_name'] = $album['genre']['name'];
$album['details_link'] = $route->uri(array('controller'=>'album', 'action'=>'details', 'id'=>$album['id']));
$album['edit_link'] = $route->uri(array('controller'=>'album', 'action'=>'edit', 'id'=>$album['id']));
$album['delete_link'] = $route->uri(array('controller'=>'album', 'action'=>'delete', 'id'=>$album['id']));
+ $album['odd'] = $odd;
$albums[] = $album;
+ $odd = ! $odd;
}
return $albums;
}
-
public function links()
{
$route = Route::get('default');
@@ -45,7 +124,9 @@ public function scripts()
{
return array(
'assets/js/jquery-1_5_2_min.js',
- 'assets/js/ajax_list.js'
+ 'assets/js/ajax_list.js',
+ 'assets/js/jquery.url.js',
+ 'assets/js/filter_list.js',
);
}
public function logged_in_user()
@@ -57,4 +138,81 @@ public function user_is_admin()
{
return Auth::instance()->logged_in('admin');
}
+
+ // HELPERS
+ // Count for use by pagination
+ private function get_count()
+ {
+ // initially, just get a count of all the albums here. In general, we may want this to be based on some filter settings
+ // from the controller -- probably want to insert a where clause or two in there...
+ $as = ORM::factory('Album');
+ $this->albums_apply_filters($as);
+ return $as->count_all();
+ }
+
+ // FILTERS
+ // Takes an ORM Album object and applies the filters to it.
+ private function albums_apply_filters($as)
+ {
+ $filters = $this->get_filters();
+ if (count($filters) > 0)
+ {
+ $f = $filters[0];
+ $as = $as->where($f['column'], $f['op'], $f['value']);
+ }
+ for ($i = 1; $i < count($filters); $i++)
+ {
+ $f = $filters[$i];
+ $as = $as->and_where($f['column'], $f['op'], $f['value']);
+ }
+ }
+ // get the filters for use by the orm
+ private function get_filters()
+ {
+ $filters = array();
+ foreach (Request::current()->query() as $key => $value)
+ {
+ $ka = explode('-',$key,2);
+ if ($ka[0] == 'fc')
+ {
+ list($op,$val) = explode('|',$value,2);
+ if ($op == 'eq') $op = '=';
+ else $val = $val . '%';
+ $filters[] = array('column' => str_replace(':','.',$ka[1]), 'op' => $op, 'value' => $val);
+ }
+ }
+ return $filters;
+ }
+
+ // SORT
+ // get the sort spec for use by the orm or template
+ private function get_sort_from_request($colon_to_dot = FALSE)
+ {
+ $sort = NULL;
+ foreach (Request::current()->query() as $key => $value)
+ {
+ $ka = explode('-',$key,2);
+ if ($ka[0] == 'sc')
+ {
+ $sort=array('column' => $ka[1], 'dir' => $value);
+ if ($colon_to_dot)
+ {
+ $sort['column'] = str_replace(':','.',$sort['column']);
+ }
+ break;
+ }
+ }
+ return $sort;
+ }
+ // get the sort array for use by the template
+ public function sort()
+ {
+ $s = $this->get_sort_from_request();
+ $sort = array();
+ if ($s !== NULL)
+ {
+ $sort[$s['column']] = 'sort-' . $s['dir'];
+ }
+ return $sort;
+ }
}
View
16 application/config/pagination.php
@@ -0,0 +1,16 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+return array(
+
+ // Application defaults
+ 'default' => array(
+ // source: "query_string" or "route" (note: currently only "query_string" works...)
+ 'current_page' => array('source' => 'query_string', 'key' => 'page'),
+ 'total_items' => 0,
+ 'items_per_page' => 5,
+ 'view' => 'pagination/basic',
+ 'auto_hide' => TRUE,
+ 'first_page_in_url' => FALSE,
+ ),
+
+);
View
11 application/messages/models/_external.php
@@ -0,0 +1,11 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+return array(
+ 'password' => array(
+ 'not_empty' => 'You must provide a password.',
+ 'min_length' => ':field must be at least :param2 characters long',
+ ),
+ 'password_confirm' => array(
+ 'matches' => 'Passwords must match.',
+ ),
+);
View
34 application/messages/models/album.php
@@ -0,0 +1,34 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+return array(
+ 'not_exists' => ':field already exists',
+ 'email_available' => ':field is already in use',
+ 'username' => array(
+ 'username_available' => 'the username \':value\' is already in use',
+ ),
+
+ 'alpha' => ':field must contain only letters',
+ 'alpha_dash' => ':field must contain only numbers, letters and dashes',
+ 'alpha_numeric' => ':field must contain only letters and numbers',
+ 'color' => ':field must be a color',
+ 'credit_card' => ':field must be a credit card number',
+ 'date' => ':field must be a date',
+ 'decimal' => ':field must be a decimal with :param2 places',
+ 'digit' => ':field must be a digit',
+ 'email' => ':field must be a valid email address',
+ 'email_domain' => ':field must contain a valid email domain',
+ 'equals' => ':field must equal :param2',
+ 'exact_length' => ':field must be exactly :param2 characters long',
+ 'in_array' => ':field must be one of the available options',
+ 'ip' => ':field must be an ip address',
+ 'matches' => ':field must be the same as :param2',
+ 'min_length' => ':field must be at least :param2 characters long',
+ 'max_length' => ':field must not exceed :param2 characters long',
+ 'not_empty' => ':field must not be empty',
+ 'numeric' => ':field must be numeric',
+ 'phone' => ':field must be a phone number',
+ 'range' => ':field must be within the range of :param2 to :param3',
+ 'regex' => ':field does not match the required format',
+ 'url' => ':field must be a url',
+);
+
View
91 application/templates/pages/album/list.mustache
@@ -1,40 +1,57 @@
{{%DOT-NOTATION}}
<div id="wrap">
- <div id="header"><h1>Kohana Demo</h1></div>
- <div id="nav">
- <img src="assets/images/add.png" alt = "Add Button" />
- <span class="top-link"><a href="{{links.album_add}}">Add new album</a></span>
- {{#logged_in_user}}<span id="user-info">Current User: {{{logged_in_user}}}</span>
- <span class="top-link"><a href="{{links.user_logout}}">Log Out</a></span>
- <span class="top-link"><a href="{{links.user_profile}}">Edit Profile</a></span>{{/logged_in_user}}
- {{^logged_in_user}}<span class="top-link"><a href="{{links.user_login}}">Log In</a></span>{{/logged_in_user}}
- {{#user_is_admin}}<span class="top-link"><a href="{{links.admin_list}}">Administer Users</a></span>{{/user_is_admin}}
- </div>
- <div id="main">
- <table id="album_list" class="list" cellspacing="0">
- <tr id="-1">
- <td colspan="6" class="list_title">{{list_title}}</td>
- </tr>
- <tr id="0">
- <td class="headers">Artist</td>
- <td class="headers">Album name</td>
- <td colspan='4' class="headers">Genre</td>
- </tr>
- {{#albums}}
- <tr id="{{{id}}}">
- <td class='item'>{{artist}}</td>
- <td class='item'>{{name}}</td>
- <td class='item'>{{genre_name}}</td>
- <td class='item'><a href="{{edit_link}}"><img src="assets/images/edit.png" alt="Edit Button" /></a></td>
- <td class='item'><a href="{{delete_link}}"><img src="assets/images/delete.png" alt="Delete Button" /></a></td>
- </tr>
- {{/albums}}
- </table>
- </div>
- <div id="details">
- <h3>click a row to view album details...</h3>
- </div>
- <div id="footer">
- Powered by <a href="http://kohanaframework.org/">Kohana</a>
- </div>
+ <div id="header"><h1>Kohana Demo</h1></div>
+ <div id="nav">
+ <img src="assets/images/add.png" alt = "Add Button" />
+ <span class="top-link"><a href="{{links.album_add}}">Add new album</a></span>
+ {{#logged_in_user}}<span id="user-info">Current User: {{{logged_in_user}}}</span>
+ <span class="top-link"><a href="{{links.user_logout}}">Log Out</a></span>
+ <span class="top-link"><a href="{{links.user_profile}}">Edit Profile</a></span>{{/logged_in_user}}
+ {{^logged_in_user}}<span class="top-link"><a href="{{links.user_login}}">Log In</a></span>{{/logged_in_user}}
+ {{#user_is_admin}}<span class="top-link"><a href="{{links.admin_list}}">Administer Users</a></span>{{/user_is_admin}}
+ </div>
+ <div id="main">
+ <table id="album_list" class="tablesorter" cellspacing="0">
+ <thead>
+ <tr id="title">
+ <th colspan="6" class="list_title">{{list_title}}</th>
+ </tr>
+ <tr id="sort-heads">
+ <th id="sc-albums:artist" class="header sort-head {{sort.albums:artist}}">Artist</th>
+ <th id="sc-albums:name" class="header sort-head {{sort.albums:name}}">Album name</th>
+ <th id="sc-genre:name" class="header sort-head {{sort.genre:name}}">Genre</th>
+ <th colspan="2">Action</th>
+ </tr>
+ <tr id="filter-heads">
+ <th class="filter-head"><input id="fc-albums:artist" value="{{artist_filter}}" /></th>
+ <th class="filter-head"><input id="fc-albums:name" value="{{name_filter}}" /></th>
+ <th class="filter-head">
+ <select id="fc-albums:genre_id">
+ {{#genre_list_filter}}<option value="{{id}}" {{#selected}}SELECTED{{/selected}} >{{name}}</option>{{/genre_list_filter}}
+ </select>
+ </th>
+ <th><a href="#" id="refresh" title="Apply the current filters"><img src="assets/images/refresh.png" alt="Refresh Button" /></a></th>
+ <th><a href="#" id="clear-filters" title="Clear all filters"><img src="assets/images/delete.png" alt="Delete Button" /></a></th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#albums}}
+ <tr id="{{{id}}}" class="data {{#odd}}odd{{/odd}}{{^odd}}even{{/odd}}">
+ <td class="item">{{artist}}</td>
+ <td class="item">{{name}}</td>
+ <td class="item">{{genre_name}}</td>
+ <td class="item"><a href="{{edit_link}}" title="Edit this album"><img src="assets/images/edit.png" alt="Edit Button" /></a></td>
+ <td class="item"><a href="{{delete_link}}" title="Delete this album"><img src="assets/images/delete.png" alt="Delete Button" /></a></td>
+ </tr>
+ {{/albums}}
+ </tbody>
+ </table>
+ {{#pagination}}{{>pagination}}{{/pagination}}
+ </div>
+ <div id="details">
+ <h3>click a row to view album details...</h3>
+ </div>
+ <div id="footer">
+ Powered by <a href="http://kohanaframework.org/">Kohana</a>
+ </div>
</div>
View
5 application/templates/pagination/basic.mustache
@@ -0,0 +1,5 @@
+<p id="pagination" class="pagination">
+{{#items}}
+{{#url}}<a href="{{url}}" {{#title}}rel="{{rel}}"{{/title}}>{{/url}}{{#current}}<strong>{{/current}}{{name}}{{#current}}</strong>{{/current}}{{#url}}</a>{{/url}}&nbsp;
+{{/items}}
+</p>
View
7 application/templates/pagination/punbb.mustache
@@ -0,0 +1,7 @@
+<p><small>{{displayed}}</small></p>
+<p class="pagination">
+ {{pages}}:
+ {{#items}}
+ {{#url}}<a href="{{url}}" {{#title}}rel="{{rel}}"{{/title}}>{{/url}}{{#num}}<strong>{{/num}}{{name}}{{#num}}</strong>{{/num}}{{#url}}</a>{{/url}}
+ {{/items}}
+</p>
View
6 application/templates/pagination/punbb/inline.mustache
@@ -0,0 +1,6 @@
+<span class="pagination">
+ {{pages}}:
+ {{#items}}
+ {{#url}}<a href="{{url}}" {{#title}}rel="{{rel}}"{{/title}}>{{/url}}{{#num}}<strong>{{/num}}{{name}}{{#num}}</strong>{{/num}}{{#url}}</a>{{/url}}
+ {{/items}}
+</span>
View
66 assets/css/style.css
@@ -21,6 +21,12 @@
font-style: normal;
}
+strong {
+ font-weight: bold;
+}
+em {
+ font-style: italic;
+}
html, body {
font-family: Arial, Helvetica, sans-serif ;
font-weight: normal;
@@ -30,7 +36,7 @@ html, body {
background: #E3E0D1;
}
#wrap {
- width:810px;
+ width:910px;
}
#wrap-admin {
width:600px;
@@ -55,13 +61,13 @@ html, body {
#main {
float: left;
- width: 510px;
+ width: 610px;
padding: 10px;
border-right: 1px solid #fff;
}
#full {
- width: 510px;
+ width: 610px;
padding: 10px;
}
@@ -100,7 +106,7 @@ a {
font-weight: normal;
font-size: 12px;
color: #200;
- vertical-align:text-top;
+ /*vertical-align:text-top;*/
text-decoration: none;
}
@@ -136,6 +142,51 @@ select {
width:185px;
}
+table.tablesorter {
+ background-color: #CDCDCD;
+ font-family: arial;
+ font-size: 8pt;
+ margin: 10px 0 15px;
+ text-align: left;
+ width: 100%;
+}
+table.tablesorter thead tr th, table.tablesorter tfoot tr th {
+ background-color: #E6EEEE;
+ border: 1px solid #FFFFFF;
+ font-size: 8pt;
+ padding: 4px;
+}
+table.tablesorter thead tr .header {
+ background-image: url("../images/bg.gif");
+ background-position: right center;
+ background-repeat: no-repeat;
+ cursor: pointer;
+}
+table.tablesorter tbody td {
+ background-color: #FFFFFF;
+ color: #3D3D3D;
+ padding: 4px;
+ vertical-align: top;
+ border: 1px solid #FFF;
+}
+table.tablesorter tbody tr.odd td {
+ background-color: #F0F0F6;
+}
+table.tablesorter tbody tr.even td {
+ background-color: #E0E0E6;
+}
+table.tablesorter thead tr .sort-asc {
+ background-image: url("../images/asc.gif");
+}
+table.tablesorter thead tr .sort-desc {
+ background-image: url("../images/desc.gif");
+}
+table.tablesorter thead tr .sort-desc, table.tablesorter thead tr .sort-asc {
+ background-color: #8DBDD8;
+}
+
+
+
table.list
{
width: 500px;
@@ -205,3 +256,10 @@ div.message {
.message hr {
color: #fff;
}
+.pagination {
+ font-family: Arial, Helvetica, sans-serif ;
+ font-weight: normal;
+ font-size: 12px;
+ color: #200;
+/* vertical-align:text-top; */
+}
View
BIN assets/images/asc.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN assets/images/bg.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN assets/images/desc.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN assets/images/refresh.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
3 assets/info.php
@@ -0,0 +1,3 @@
+<?php
+
+phpinfo();
View
12 assets/js/ajax_list.js
@@ -1,17 +1,17 @@
$.ajaxSetup ({
cache: false,
- dataType: "html"
+ dataType: 'html'
});
var ajax_load = "<img src='assets/images/load.gif' alt='loading...' />";
-$('#album_list tr').live('click', function(e) {
+$('#album_list').find('tr.data').live('click', function(e) {
this.blur();
- $("#details").html(ajax_load).load('album/details/' + $(this).attr('id'));
+ $('#details').html(ajax_load).load('album/details/' + $(this).attr('id'));
});
$(function() {
- $("tr").hover(function() {
- $(this).addClass("highlight");
+ $('tr.data').hover(function() {
+ $(this).addClass('highlight');
}, function() {
- $(this).removeClass("highlight");
+ $(this).removeClass('highlight');
});
});
View
80 assets/js/filter_list.js
@@ -0,0 +1,80 @@
+// Remove sortUp and sortDn classes from all sort-head <th>'s
+function removeSortClasses()
+{
+ $('#sort-heads').find('th').removeClass('sort-asc sort-desc');
+}
+// reset values of all filter controls
+function clearFilters()
+{
+ $('#filter-heads').find('input').val('');
+ $('#filter-heads').find('select').val('');
+}
+
+// Build a parameter string from the filter controls
+function getParameterString()
+{
+ var filterParams = new Array
+ $('#filter-heads').find('input').each(function() {
+ if ($(this).val().length > 0) {
+ var filt = new Object();
+ filt.name = $(this).attr('id');
+ filt.value = 'like|' + $(this).val();
+ filterParams.push(filt)
+ }
+ });
+ $('#filter-heads').find('select').each(function() {
+ if ($(this).val().length > 0) {
+ var filt = new Object();
+ filt.name = $(this).attr('id');
+ filt.value = 'eq|' + $(this).val();
+ filterParams.push(filt)
+ }
+ });
+ $('#sort-heads').find('th.sort-asc').each(function() {
+ var filt = new Object();
+ filt.name = $(this).attr('id');
+ filt.value = 'asc';
+ filterParams.push(filt)
+ });
+ $('#sort-heads').find('th.sort-desc').each(function() {
+ var filt = new Object();
+ filt.name = $(this).attr('id');
+ filt.value = 'desc';
+ filterParams.push(filt)
+ });
+ return $.param(filterParams);
+}
+
+// Add the parameter string to the pagination links
+function appendParameterStringToPagination()
+{
+ var filterParams = getParameterString();
+ $('#pagination').find('a').each(function() {
+ $(this).attr('href') += getParameterString();
+ });
+}
+
+// onload jquery to call functions to build parameter string and add it to pagination links.
+$(function(){
+ //add onclick handler to add or toggle the sort class of a sort column head,
+ //and then performing the request to re-load the page with the current filter specs
+ $('#sort-heads').find('th').click(function() {
+ var isSortAsc = $(this).hasClass('sort-asc');
+ removeSortClasses();
+ $(this).addClass((isSortAsc ? 'sort-desc' : 'sort-asc'))
+ window.location = $.url().attr('path') + '?' + getParameterString();
+ });
+
+ //add onclick handler to the 'refresh' button to get the parameter string and re-load the page
+ $('#refresh').click(function(event) {
+ event.preventDefault();
+ window.location = $.url().attr('path') + '?' + getParameterString();
+ })
+ //add onclick handler to the 'clear filters' button to clear the filters, get the parameter string and re-load the page
+ $('#clear-filters').click(function(event) {
+ event.preventDefault();
+ clearFilters();
+ window.location = $.url().attr('path') + '?' + getParameterString();
+ })
+});
+
View
164 assets/js/jquery.url.js
@@ -0,0 +1,164 @@
+// JQuery URL Parser plugin - https://github.com/allmarkedup/jQuery-URL-Parser
+// Written by Mark Perkins, mark@allmarkedup.com
+// License: http://unlicense.org/ (i.e. do what you want with it!)
+
+;(function($, undefined) {
+
+ var tag2attr = {
+ a : 'href',
+ img : 'src',
+ form : 'action',
+ base : 'href',
+ script : 'src',
+ iframe : 'src',
+ link : 'href'
+ },
+
+ key = ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","fragment"], // keys available to query
+
+ aliases = { "anchor" : "fragment" }, // aliases for backwards compatability
+
+ parser = {
+ strict : /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/, //less intuitive, more accurate to the specs
+ loose : /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ // more intuitive, fails on relative paths and deviates from specs
+ },
+
+ querystring_parser = /(?:^|&|;)([^&=;]*)=?([^&;]*)/g, // supports both ampersand and semicolon-delimted query string key/value pairs
+
+ fragment_parser = /(?:^|&|;)([^&=;]*)=?([^&;]*)/g; // supports both ampersand and semicolon-delimted fragment key/value pairs
+
+ function parseUri( url, strictMode )
+ {
+ var str = decodeURI( url ),
+ res = parser[ strictMode || false ? "strict" : "loose" ].exec( str ),
+ uri = { attr : {}, param : {}, seg : {} },
+ i = 14;
+
+ while ( i-- )
+ {
+ uri.attr[ key[i] ] = res[i] || "";
+ }
+
+ // build query and fragment parameters
+
+ uri.param['query'] = {};
+ uri.param['fragment'] = {};
+
+ uri.attr['query'].replace( querystring_parser, function ( $0, $1, $2 ){
+ if ($1)
+ {
+ uri.param['query'][$1] = $2;
+ }
+ });
+
+ uri.attr['fragment'].replace( fragment_parser, function ( $0, $1, $2 ){
+ if ($1)
+ {
+ uri.param['fragment'][$1] = $2;
+ }
+ });
+
+ // split path and fragement into segments
+
+ uri.seg['path'] = uri.attr.path.replace(/^\/+|\/+$/g,'').split('/');
+
+ uri.seg['fragment'] = uri.attr.fragment.replace(/^\/+|\/+$/g,'').split('/');
+
+ // compile a 'base' domain attribute
+
+ uri.attr['base'] = uri.attr.host ? uri.attr.protocol+"://"+uri.attr.host + (uri.attr.port ? ":"+uri.attr.port : '') : '';
+
+ return uri;
+ };
+
+ function getAttrName( elm )
+ {
+ var tn = elm.tagName;
+ if ( tn !== undefined ) return tag2attr[tn.toLowerCase()];
+ return tn;
+ }
+
+ $.fn.url = function( strictMode )
+ {
+ var url = '';
+
+ if ( this.length )
+ {
+ url = $(this).attr( getAttrName(this[0]) ) || '';
+ }
+
+ return $.url({ url : url, strict : strictMode });
+ };
+
+ $.url = function( opts )
+ {
+ var url = '',
+ strict = false;
+
+ if ( typeof opts === 'string' )
+ {
+ url = opts;
+ }
+ else
+ {
+ opts = opts || {};
+ strict = opts.strict || strict;
+ url = opts.url === undefined ? window.location.toString() : opts.url;
+ }
+
+ return {
+
+ data : parseUri(url, strict),
+
+ // get various attributes from the URI
+ attr : function( attr )
+ {
+ attr = aliases[attr] || attr;
+ return attr !== undefined ? this.data.attr[attr] : this.data.attr;
+ },
+
+ // return query string parameters
+ param : function( param )
+ {
+ return param !== undefined ? this.data.param.query[param] : this.data.param.query;
+ },
+
+ // return fragment parameters
+ fparam : function( param )
+ {
+ return param !== undefined ? this.data.param.fragment[param] : this.data.param.fragment;
+ },
+
+ // return path segments
+ segment : function( seg )
+ {
+ if ( seg === undefined )
+ {
+ return this.data.seg.path;
+ }
+ else
+ {
+ seg = seg < 0 ? this.data.seg.path.length + seg : seg - 1; // negative segments count from the end
+ return this.data.seg.path[seg];
+ }
+ },
+
+ // return fragment segments
+ fsegment : function( seg )
+ {
+ if ( seg === undefined )
+ {
+ return this.data.seg.fragment;
+ }
+ else
+ {
+ seg = seg < 0 ? this.data.seg.fragment.length + seg : seg - 1; // negative segments count from the end
+ return this.data.seg.fragment[seg];
+ }
+ }
+
+ };
+
+ };
+
+})(jQuery);
1 modules/kostache-pagination-helper
@@ -0,0 +1 @@
+Subproject commit 68d5778eaef6969a5787e92021ce9d5254b5b4f8
1 modules/pagination
@@ -0,0 +1 @@
+Subproject commit 80b7cb163bde5d2447e1f2bbf809c0988b321153

0 comments on commit 7fdeb89

Please sign in to comment.
Something went wrong with that request. Please try again.