Initial implementation of javascript live project search
Initial implementation of javascript live project search

 Binary files /dev/null and b/images/search-loader.gif differ
<?php <?php
/** /**
* GitPHP Controller ProjectList * GitPHP Controller ProjectList
* *
* Controller for listing projects * Controller for listing projects
* *
* @author Christopher Han <xiphux@gmail.com> * @author Christopher Han <xiphux@gmail.com>
* @copyright Copyright (c) 2010 Christopher Han * @copyright Copyright (c) 2010 Christopher Han
* @package GitPHP * @package GitPHP
* @subpackage Controller * @subpackage Controller
*/ */
   
/** /**
* ProjectList controller class * ProjectList controller class
* *
* @package GitPHP * @package GitPHP
* @subpackage Controller * @subpackage Controller
*/ */
class GitPHP_Controller_ProjectList extends GitPHP_ControllerBase class GitPHP_Controller_ProjectList extends GitPHP_ControllerBase
{ {
   
/** /**
* __construct * __construct
* *
* Constructor * Constructor
* *
* @access public * @access public
* @return controller * @return controller
*/ */
public function __construct() public function __construct()
{ {
parent::__construct(); parent::__construct();
} }
   
/** /**
* GetTemplate * GetTemplate
* *
* Gets the template for this controller * Gets the template for this controller
* *
* @access protected * @access protected
* @return string template filename * @return string template filename
*/ */
protected function GetTemplate() protected function GetTemplate()
{ {
if (isset($this->params['opml']) && ($this->params['opml'] === true)) { if (isset($this->params['opml']) && ($this->params['opml'] === true)) {
return 'opml.tpl'; return 'opml.tpl';
} else if (isset($this->params['txt']) && ($this->params['txt'] === true)) { } else if (isset($this->params['txt']) && ($this->params['txt'] === true)) {
return 'projectindex.tpl'; return 'projectindex.tpl';
} }
return 'projectlist.tpl'; return 'projectlist.tpl';
} }
   
/** /**
* GetCacheKey * GetCacheKey
* *
* Gets the cache key for this controller * Gets the cache key for this controller
* *
* @access protected * @access protected
* @return string cache key * @return string cache key
*/ */
protected function GetCacheKey() protected function GetCacheKey()
{ {
if (isset($this->params['opml']) && ($this->params['opml'] === true)) { if (isset($this->params['opml']) && ($this->params['opml'] === true)) {
return ''; return '';
} else if (isset($this->params['txt']) && ($this->params['txt'] === true)) { } else if (isset($this->params['txt']) && ($this->params['txt'] === true)) {
return ''; return '';
} }
return $this->params['order'] . '|' . (isset($this->params['search']) ? $this->params['search'] : ''); return $this->params['order'] . '|' . (isset($this->params['search']) ? $this->params['search'] : '');
} }
   
/** /**
* GetName * GetName
* *
* Gets the name of this controller's action * Gets the name of this controller's action
* *
* @access public * @access public
* @param boolean $local true if caller wants the localized action name * @param boolean $local true if caller wants the localized action name
* @return string action name * @return string action name
*/ */
public function GetName($local = false) public function GetName($local = false)
{ {
if (isset($this->params['opml']) && ($this->params['opml'] === true)) { if (isset($this->params['opml']) && ($this->params['opml'] === true)) {
if ($local) { if ($local) {
return __('opml'); return __('opml');
} }
return 'opml'; return 'opml';
} else if (isset($this->params['txt']) && ($this->params['txt'] === true)) { } else if (isset($this->params['txt']) && ($this->params['txt'] === true)) {
if ($local) { if ($local) {
return __('project index'); return __('project index');
} }
return 'project index'; return 'project index';
} }
if ($local) { if ($local) {
return __('projects'); return __('projects');
} }
return 'projects'; return 'projects';
} }
   
/** /**
* ReadQuery * ReadQuery
* *
* Read query into parameters * Read query into parameters
* *
* @access protected * @access protected
*/ */
protected function ReadQuery() protected function ReadQuery()
{ {
if (isset($_GET['o'])) if (isset($_GET['o']))
$this->params['order'] = $_GET['o']; $this->params['order'] = $_GET['o'];
else else
$this->params['order'] = 'project'; $this->params['order'] = 'project';
if (isset($_GET['s'])) if (isset($_GET['s']))
$this->params['search'] = $_GET['s']; $this->params['search'] = $_GET['s'];
} }
   
/** /**
* LoadHeaders * LoadHeaders
* *
* Loads headers for this template * Loads headers for this template
* *
* @access protected * @access protected
*/ */
protected function LoadHeaders() protected function LoadHeaders()
{ {
if (isset($this->params['opml']) && ($this->params['opml'] === true)) { if (isset($this->params['opml']) && ($this->params['opml'] === true)) {
$this->headers[] = "Content-type: text/xml; charset=UTF-8"; $this->headers[] = "Content-type: text/xml; charset=UTF-8";
GitPHP_Log::GetInstance()->SetEnabled(false); GitPHP_Log::GetInstance()->SetEnabled(false);
} else if (isset($this->params['txt']) && ($this->params['txt'] === true)) { } else if (isset($this->params['txt']) && ($this->params['txt'] === true)) {
$this->headers[] = "Content-type: text/plain; charset=utf-8"; $this->headers[] = "Content-type: text/plain; charset=utf-8";
$this->headers[] = "Content-Disposition: inline; filename=\"index.aux\""; $this->headers[] = "Content-Disposition: inline; filename=\"index.aux\"";
GitPHP_Log::GetInstance()->SetEnabled(false); GitPHP_Log::GetInstance()->SetEnabled(false);
} }
} }
   
/** /**
* LoadData * LoadData
* *
* Loads data for this template * Loads data for this template
* *
* @access protected * @access protected
*/ */
protected function LoadData() protected function LoadData()
{ {
$this->tpl->assign('order', $this->params['order']); $this->tpl->assign('order', $this->params['order']);
$projectList = GitPHP_ProjectList::GetInstance(); $projectList = GitPHP_ProjectList::GetInstance();
$projectList->Sort($this->params['order']); $projectList->Sort($this->params['order']);
   
if ((empty($this->params['opml']) || ($this->params['opml'] !== true)) && if ((empty($this->params['opml']) || ($this->params['opml'] !== true)) &&
(empty($this->params['txt']) || ($this->params['txt'] !== true)) && (empty($this->params['txt']) || ($this->params['txt'] !== true)) &&
(!empty($this->params['search']))) { (!empty($this->params['search']))) {
$this->tpl->assign('search', $this->params['search']); $this->tpl->assign('search', $this->params['search']);
$matches = $projectList->Filter($this->params['search']); $matches = $projectList->Filter($this->params['search']);
if (count($matches) > 0) { if (count($matches) > 0) {
$this->tpl->assign('projectlist', $matches); $this->tpl->assign('projectlist', $matches);
} }
} else { } else {
if ($projectList->Count() > 0) if ($projectList->Count() > 0)
$this->tpl->assign('projectlist', $projectList); $this->tpl->assign('projectlist', $projectList);
} }
   
  if ((empty($this->params['opml']) || ($this->params['opml'] !== true)) &&
  (empty($this->params['txt']) || ($this->params['txt'] !== true))) {
  $this->tpl->assign('extrascripts', array('js/projectsearch.js'));
  }
} }
   
} }
   
  /*
  * GitPHP javascript project search
  *
  * Live search of project list
  *
  * @author Christopher Han <xiphux@gmail.com>
  * @copyright Copyright (c) 2010 Christopher Han
  * @package GitPHP
  * @subpackage Javascript
  */
 
  var oldSearchValue = '';
 
  function runSearch() {
  var search = $('input.projectSearchBox').val();
  oldSearchValue = search;
 
  $('img.searchSpinner').show();
 
  if (search.length == 0) {
  $('a.clearSearch').hide();
  } else {
  $('a.clearSearch').show();
  }
 
  var visibleCats = [];
 
  $('table.projectList tr.projectRow').each(function() {
  if (search.length < 1) {
  $(this).show();
  return;
  }
 
  var category = '';
 
  var projectName = $(this).find('td.projectName a').text();
  if (projectName.length > 0) {
  if (projectName.indexOf(search) != -1) {
  $(this).show();
  category = $(this).data('category');
  if (category) {
  if (jQuery.inArray(category, visibleCats) == -1) {
  visibleCats.push(category);
  }
  }
  return;
  }
  }
  var projectDesc = $(this).find('td.projectDescription a').text();
  if (projectDesc.length > 0) {
  if (projectDesc.indexOf(search) != -1) {
  $(this).show();
  category = $(this).data('category');
  if (category) {
  if (jQuery.inArray(category, visibleCats) == -1) {
  visibleCats.push(category);
  }
  }
  return;
  }
  }
  var projectOwner = $(this).find('td.projectOwner em').text();
  if (projectOwner.length > 0) {
  if (projectOwner.indexOf(search) != -1) {
  $(this).show();
  category = $(this).data('category');
  if (category) {
  if (jQuery.inArray(category, visibleCats) == -1) {
  visibleCats.push(category);
  }
  }
  return;
  }
  }
  $(this).hide();
  });
 
  $('table.projectList tr.categoryRow').each(function() {
  if (search.length < 1) {
  $(this).show();
  return;
  }
 
  var category = $(this).children('th.categoryName').text();
  if (category.length > 0) {
  if (jQuery.inArray(category, visibleCats) !== -1) {
  $(this).show();
  } else {
  $(this).hide();
  }
  }
  });
 
  $('img.searchSpinner').hide();
  }
 
  function initProjectSearch() {
  $('#projectSearchForm').keypress(function(e) {
  if (e.which == 13) {
  return false;
  }
  });
 
  // Store project categories
  var category = '';
  $('table.projectList tr').each(function() {
  if ($(this).hasClass('categoryRow')) {
  category = $(this).children('th.categoryName').text();
  } else if ($(this).hasClass('projectRow')) {
  if (category.length > 0) {
  $(this).data('category', category);
  }
  }
  });
 
  $('a.clearSearch').click(function() {
  $('input.projectSearchBox').val('');
  oldSearchValue = '';
  runSearch();
  return false;
  });
 
  $('input.projectSearchBox').keyup(function() {
  if ($('input.projectSearchBox').val() != oldSearchValue)
  runSearch();
  });
  }
 
  $(document).ready(function() {
  initProjectSearch();
  });
 
{* {*
* projectlist.tpl * projectlist.tpl
* gitphp: A PHP git repository browser * gitphp: A PHP git repository browser
* Component: Project list template * Component: Project list template
* *
* Copyright (C) 2009 Christopher Han <xiphux@gmail.com> * Copyright (C) 2009 Christopher Han <xiphux@gmail.com>
*} *}
{include file='header.tpl'} {include file='header.tpl'}
   
<div class="index_header"> <div class="index_header">
{if file_exists('templates/hometext.tpl') } {if file_exists('templates/hometext.tpl') }
{include file='hometext.tpl'} {include file='hometext.tpl'}
{else} {else}
{* default header *} {* default header *}
<p> <p>
git source code archive git source code archive
</p> </p>
{/if} {/if}
</div> </div>
   
<div class="projectSearch"> <div class="projectSearch">
<form method="get" action="index.php" enctype="application/x-www-form-urlencoded"> <form method="get" action="index.php" id="projectSearchForm" enctype="application/x-www-form-urlencoded">
{t}Search projects{/t}: <input type="text" name="s" {if $search}value="{$search}"{/if} /> <a href="index.php" {if !$search}style="display: none;"{/if}>X</a> {t}Search projects{/t}: <input type="text" name="s" class="projectSearchBox" {if $search}value="{$search}"{/if} /> <a href="index.php" class="clearSearch" {if !$search}style="display: none;"{/if}>X</a> {if $javascript}<img src="images/search-loader.gif" class="searchSpinner" style="display: none;" />{/if}
</form> </form>
</div> </div>
   
<table cellspacing="0"> <table cellspacing="0" class="projectList">
{foreach name=projects from=$projectlist item=proj} {foreach name=projects from=$projectlist item=proj}
{if $smarty.foreach.projects.first} {if $smarty.foreach.projects.first}
{* Header *} {* Header *}
<tr> <tr>
{if $order == "project"} {if $order == "project"}
<th>{t}Project{/t}</th> <th>{t}Project{/t}</th>
{else} {else}
<th><a class="header" href="{$SCRIPT_NAME}?o=project">{t}Project{/t}</a></th> <th><a class="header" href="{$SCRIPT_NAME}?o=project">{t}Project{/t}</a></th>
{/if} {/if}
{if $order == "descr"} {if $order == "descr"}
<th>{t}Description{/t}</th> <th>{t}Description{/t}</th>
{else} {else}
<th><a class="header" href="{$SCRIPT_NAME}?o=descr">{t}Description{/t}</a></th> <th><a class="header" href="{$SCRIPT_NAME}?o=descr">{t}Description{/t}</a></th>
{/if} {/if}
{if $order == "owner"} {if $order == "owner"}
<th>{t}Owner{/t}</th> <th>{t}Owner{/t}</th>
{else} {else}
<th><a class="header" href="{$SCRIPT_NAME}?o=owner">{t}Owner{/t}</a></th> <th><a class="header" href="{$SCRIPT_NAME}?o=owner">{t}Owner{/t}</a></th>
{/if} {/if}
{if $order == "age"} {if $order == "age"}
<th>{t}Last Change{/t}</th> <th>{t}Last Change{/t}</th>
{else} {else}
<th><a class="header" href="{$SCRIPT_NAME}?o=age">{t}Last Change{/t}</a></th> <th><a class="header" href="{$SCRIPT_NAME}?o=age">{t}Last Change{/t}</a></th>
{/if} {/if}
<th>{t}Actions{/t}</th> <th>{t}Actions{/t}</th>
</tr> </tr>
{/if} {/if}
   
{if $currentcategory != $proj->GetCategory()} {if $currentcategory != $proj->GetCategory()}
{assign var=currentcategory value=$proj->GetCategory()} {assign var=currentcategory value=$proj->GetCategory()}
{if $currentcategory != ''} {if $currentcategory != ''}
<tr class="light"> <tr class="light categoryRow">
<th>{$currentcategory}</th> <th class="categoryName">{$currentcategory}</th>
<th></th> <th></th>
<th></th> <th></th>
<th></th> <th></th>
<th></th> <th></th>
</tr> </tr>
{/if} {/if}
{/if} {/if}
   
<tr class="{cycle values="light,dark"}"> <tr class="{cycle values="light,dark"} projectRow">
<td> <td class="projectName">
<a href="{$SCRIPT_NAME}?p={$proj->GetProject()|urlencode}&amp;a=summary" class="list {if $currentcategory != ''}indent{/if}">{$proj->GetProject()}</a> <a href="{$SCRIPT_NAME}?p={$proj->GetProject()|urlencode}&amp;a=summary" class="list {if $currentcategory != ''}indent{/if}">{$proj->GetProject()}</a>
</td> </td>
<td><a href="{$SCRIPT_NAME}?p={$proj->GetProject()|urlencode}&amp;a=summary" class="list">{$proj->GetDescription()}</a></td> <td class="projectDescription"><a href="{$SCRIPT_NAME}?p={$proj->GetProject()|urlencode}&amp;a=summary" class="list">{$proj->GetDescription()}</a></td>
<td><em>{$proj->GetOwner()}</em></td> <td class="projectOwner"><em>{$proj->GetOwner()}</em></td>
<td> <td class="projectAge">
{assign var=projecthead value=$proj->GetHeadCommit()} {assign var=projecthead value=$proj->GetHeadCommit()}
{if $projecthead} {if $projecthead}
{if $projecthead->GetAge() < 7200} {* 60*60*2, or 2 hours *} {if $projecthead->GetAge() < 7200} {* 60*60*2, or 2 hours *}
<span class="agehighlight"><strong><em>{$projecthead->GetAge()|agestring}</em></strong></span> <span class="agehighlight"><strong><em>{$projecthead->GetAge()|agestring}</em></strong></span>
{elseif $projecthead->GetAge() < 172800} {* 60*60*24*2, or 2 days *} {elseif $projecthead->GetAge() < 172800} {* 60*60*24*2, or 2 days *}
<span class="agehighlight"><em>{$projecthead->GetAge()|agestring}</em></span> <span class="agehighlight"><em>{$projecthead->GetAge()|agestring}</em></span>
{else} {else}
<em>{$projecthead->GetAge()|agestring}</em> <em>{$projecthead->GetAge()|agestring}</em>
{/if} {/if}
{/if} {/if}
</td> </td>
<td class="link"> <td class="link">
<a href="{$SCRIPT_NAME}?p={$proj->GetProject()|urlencode}&amp;a=summary">{t}summary{/t}</a> <a href="{$SCRIPT_NAME}?p={$proj->GetProject()|urlencode}&amp;a=summary">{t}summary{/t}</a>
{if $projecthead} {if $projecthead}
| |
<a href="{$SCRIPT_NAME}?p={$proj->GetProject()|urlencode}&amp;a=shortlog">{t}shortlog{/t}</a> | <a href="{$SCRIPT_NAME}?p={$proj->GetProject()|urlencode}&amp;a=shortlog">{t}shortlog{/t}</a> |
<a href="{$SCRIPT_NAME}?p={$proj->GetProject()|urlencode}&amp;a=log">{t}log{/t}</a> | <a href="{$SCRIPT_NAME}?p={$proj->GetProject()|urlencode}&amp;a=log">{t}log{/t}</a> |
<a href="{$SCRIPT_NAME}?p={$proj->GetProject()|urlencode}&amp;a=tree">{t}tree{/t}</a> | <a href="{$SCRIPT_NAME}?p={$proj->GetProject()|urlencode}&amp;a=tree">{t}tree{/t}</a> |
<a href="{$SCRIPT_NAME}?p={$proj->GetProject()|urlencode}&amp;a=snapshot&amp;h=HEAD" class="snapshotTip">{t}snapshot{/t}</a> <a href="{$SCRIPT_NAME}?p={$proj->GetProject()|urlencode}&amp;a=snapshot&amp;h=HEAD" class="snapshotTip">{t}snapshot{/t}</a>
{/if} {/if}
</td> </td>
</tr> </tr>
{foreachelse} {foreachelse}
<div class="message">{t}No projects found{/t}</div> <div class="message">{t}No projects found{/t}</div>
{/foreach} {/foreach}
   
</table> </table>
   
{include file='footer.tpl'} {include file='footer.tpl'}
   
   
comments