First attempt at generating proper paging google tables. However, somehow they are kind of messed up, so I'm probably going to drop this.

This commit is contained in:
James Cole 2014-11-14 08:40:16 +01:00
parent f511a25c94
commit b388dcc7d4
10 changed files with 466 additions and 190 deletions

View File

@ -1,5 +1,6 @@
<?php
use FireflyIII\Exception\FireflyException;
use Illuminate\Support\Collection;
/**
* Class GoogleTableController
@ -99,9 +100,9 @@ class GoogleTableController extends BaseController
$chart->addColumn('ID_Delete', 'string');
$chart->addColumn('Name_URL', 'string');
$chart->addColumn('Name', 'string');
$chart->addColumn('Matches','string');
$chart->addColumn('Minimum amount','number');
$chart->addColumn('Maximum amount','number');
$chart->addColumn('Matches', 'string');
$chart->addColumn('Minimum amount', 'number');
$chart->addColumn('Maximum amount', 'number');
/** @var \FireflyIII\Database\Recurring $repository */
$repository = App::make('FireflyIII\Database\Recurring');
@ -110,8 +111,8 @@ class GoogleTableController extends BaseController
/** @var \RecurringTransaction $entry */
foreach ($set as $entry) {
$row = [$entry->id, route('recurring.edit', $entry->id), route('recurring.delete', $entry->id), route('recurring.show', $entry->id), $entry->name
, $entry->match,$entry->amount_min,$entry->amount_max
$row = [$entry->id, route('recurring.edit', $entry->id), route('recurring.delete', $entry->id), route('recurring.show', $entry->id), $entry->name,
$entry->match, $entry->amount_min, $entry->amount_max
];
$chart->addRowArray($row);
@ -136,99 +137,12 @@ class GoogleTableController extends BaseController
return Response::json($chart->getData());
}
public function transactionsByRecurring(RecurringTransaction $recurring) {
/** @var \Grumpydictator\Gchart\GChart $chart */
$chart = App::make('gchart');
$chart->addColumn('ID', 'number');
$chart->addColumn('ID_Edit', 'string');
$chart->addColumn('ID_Delete', 'string');
$chart->addColumn('Date', 'date');
$chart->addColumn('Description_URL', 'string');
$chart->addColumn('Description', 'string');
$chart->addColumn('Amount', 'number');
$chart->addColumn('From_URL', 'string');
$chart->addColumn('From', 'string');
$chart->addColumn('To_URL', 'string');
$chart->addColumn('To', 'string');
$chart->addColumn('Budget_URL', 'string');
$chart->addColumn('Budget', 'string');
$chart->addColumn('Category_URL', 'string');
$chart->addColumn('Category', 'string');
$journals = $recurring->transactionjournals()->get();
/** @var TransactionJournal $transaction */
foreach ($journals as $journal) {
$date = $journal->date;
$descriptionURL = route('transactions.show', $journal->id);
$description = $journal->description;
/** @var Transaction $transaction */
foreach ($journal->transactions as $transaction) {
if (floatval($transaction->amount) > 0) {
$amount = floatval($transaction->amount);
$to = $transaction->account->name;
$toURL = route('accounts.show', $transaction->account->id);
} else {
$from = $transaction->account->name;
$fromURL = route('accounts.show', $transaction->account->id);
}
}
if (isset($journal->budgets[0])) {
$budgetURL = route('budgets.show', $journal->budgets[0]->id);
$component = $journal->budgets[0]->name;
} else {
$budgetURL = '';
$component = '';
}
if (isset($journal->categories[0])) {
$categoryURL = route('categories.show', $journal->categories[0]->id);
$category = $journal->categories[0]->name;
} else {
$categoryURL = '';
$category = '';
}
$id = $journal->id;
$edit = route('transactions.edit', $journal->id);
$delete = route('transactions.delete', $journal->id);
$chart->addRow(
$id, $edit, $delete, $date, $descriptionURL, $description, $amount, $fromURL, $from, $toURL, $to, $budgetURL, $component, $categoryURL,
$category
);
}
$chart->generate();
return Response::json($chart->getData());
}
/**
* @param Account $account
*/
public function transactionsByAccount(Account $account)
{
/** @var \Grumpydictator\Gchart\GChart $chart */
$chart = App::make('gchart');
$chart->addColumn('ID', 'number');
$chart->addColumn('ID_Edit', 'string');
$chart->addColumn('ID_Delete', 'string');
$chart->addColumn('Date', 'date');
$chart->addColumn('Description_URL', 'string');
$chart->addColumn('Description', 'string');
$chart->addColumn('Amount', 'number');
$chart->addColumn('From_URL', 'string');
$chart->addColumn('From', 'string');
$chart->addColumn('To_URL', 'string');
$chart->addColumn('To', 'string');
$chart->addColumn('Budget_URL', 'string');
$chart->addColumn('Budget', 'string');
$chart->addColumn('Category_URL', 'string');
$chart->addColumn('Category', 'string');
$table = new \FireflyIII\Shared\Google\Table\Transactions;
/*
* Find transactions:
*/
@ -241,8 +155,10 @@ class GoogleTableController extends BaseController
Session::get('start')
)->orderBy('date', 'DESC')->get();
$collection = new Collection;
/** @var Transaction $transaction */
foreach ($transactions as $transaction) {
$date = $transaction->transactionJournal->date;
$descriptionURL = route('transactions.show', $transaction->transaction_journal_id);
$description = $transaction->transactionJournal->description;
@ -390,6 +306,18 @@ class GoogleTableController extends BaseController
}
public function transactionsByRecurring(RecurringTransaction $recurring)
{
/** @var \FireflyIII\Shared\Google\Table\Transactions $table */
$table = new \FireflyIII\Shared\Google\Table\Transactions;
$journals = $recurring->transactionjournals()->get();
$table->addData($journals);
return $table->generate();
}
/**
* @param $what
*
@ -397,24 +325,21 @@ class GoogleTableController extends BaseController
*/
public function transactionsList($what)
{
/** @var \Grumpydictator\Gchart\GChart $chart */
$chart = App::make('gchart');
$chart->addColumn('ID', 'number');
$chart->addColumn('ID_Edit', 'string');
$chart->addColumn('ID_Delete', 'string');
$chart->addColumn('Date', 'date');
$chart->addColumn('Description_URL', 'string');
$chart->addColumn('Description', 'string');
$chart->addColumn('Amount', 'number');
/*
* Process some google stuff:
*/
$parameters = explode(' ',trim(Input::get('tq')));
$limit = intval($parameters[1]);
$offset = intval($parameters[3]);
$request = explode(':',Input::get('tqx'));
$reqID = $request[1];
$chart->addColumn('From_URL', 'string');
$chart->addColumn('From', 'string');
$chart->addColumn('To_URL', 'string');
$chart->addColumn('To', 'string');
$chart->addColumn('Budget_URL', 'string');
$chart->addColumn('Budget', 'string');
$chart->addColumn('Category_URL', 'string');
$chart->addColumn('Category', 'string');
/** @var \FireflyIII\Shared\Google\Table\Transactions $table */
$table = new \FireflyIII\Shared\Google\Table\Transactions;
$table->setPaging(true);
$table->setLimit($limit);
$table->setOffset($offset);
$table->setReqID($reqID);
/** @var \FireflyIII\Database\TransactionJournal $repository */
$repository = App::make('FireflyIII\Database\TransactionJournal');
@ -422,72 +347,21 @@ class GoogleTableController extends BaseController
switch ($what) {
case 'expenses':
case 'withdrawal':
$list = $repository->getWithdrawals();
$list = $repository->getWithdrawals($limit, $offset);
break;
case 'revenue':
case 'deposit':
$list = $repository->getDeposits();
$list = $repository->getDeposits($limit, $offset);
break;
case 'transfer':
case 'transfers':
$list = $repository->getTransfers();
$list = $repository->getTransfers($limit, $offset);
break;
}
/** @var TransactionJournal $journal */
foreach ($list as $journal) {
$date = $journal->date;
$descriptionURL = route('transactions.show', $journal->id);
$description = $journal->description;
$id = $journal->id;
if(!isset($journal->transactions[0]) || !isset($journal->transactions[1])) {
continue;
}
$table->addData($list);
if ($journal->transactions[0]->amount < 0) {
$fromURL = route('accounts.show', $journal->transactions[0]->account->id);
$fromName = $journal->transactions[0]->account->name;
$amount = floatval($journal->transactions[0]->amount);
$toURL = route('accounts.show', $journal->transactions[1]->account->id);
$toName = $journal->transactions[1]->account->name;
} else {
$fromURL = route('accounts.show', $journal->transactions[1]->account->id);
$fromName = $journal->transactions[1]->account->name;
$amount = floatval($journal->transactions[1]->amount);
$toURL = route('accounts.show', $journal->transactions[0]->account->id);
$toName = $journal->transactions[0]->account->name;
}
if (isset($journal->budgets[0])) {
$budgetURL = route('budgets.show', $journal->budgets[0]->id);
$budget = $journal->budgets[0]->name;
} else {
$budgetURL = '';
$budget = '';
}
if (isset($journal->categories[0])) {
$categoryURL = route('categories.show', $journal->categories[0]->id);
$category = $journal->categories[0]->name;
} else {
$categoryURL = '';
$category = '';
}
$edit = route('transactions.edit', $journal->id);
$delete = route('transactions.delete', $journal->id);
$chart->addRow(
$id, $edit, $delete, $date, $descriptionURL, $description, $amount, $fromURL, $fromName, $toURL, $toName, $budgetURL, $budget, $categoryURL,
$category
);
}
$chart->generate();
return Response::json($chart->getData());
echo $table->generate();
exit;
}
}

View File

@ -218,6 +218,7 @@ class TransactionJournal implements TransactionJournalInterface, CUD, CommonData
}
$transaction->save();
}
return new MessageBag;
}
@ -509,9 +510,17 @@ class TransactionJournal implements TransactionJournalInterface, CUD, CommonData
*
* @return Collection
*/
public function getDeposits()
public function getDeposits($limit = null, $offset = null)
{
return $this->getUser()->transactionjournals()->withRelevantData()->transactionTypes(['Deposit'])->get(['transaction_journals.*']);
$query = $this->getUser()->transactionjournals()->withRelevantData()->transactionTypes(['Deposit']);
if(!is_null($limit)) {
$query->take($limit);
}
if(!is_null($offset) && intval($offset) > 0) {
$query->skip($offset);
}
return $query->get(['transaction_journals.*']);
}
/**
@ -542,9 +551,17 @@ class TransactionJournal implements TransactionJournalInterface, CUD, CommonData
*
* @return Collection
*/
public function getTransfers()
public function getTransfers($limit = null, $offset = null)
{
return $this->getUser()->transactionjournals()->withRelevantData()->transactionTypes(['Transfer'])->get(['transaction_journals.*']);
$query = $this->getUser()->transactionjournals()->withRelevantData()->transactionTypes(['Transfer']);
if(!is_null($limit)) {
$query->take($limit);
}
if(!is_null($offset) && intval($offset) > 0) {
$query->skip($offset);
}
return $query->get(['transaction_journals.*']);
}
/**
@ -552,8 +569,16 @@ class TransactionJournal implements TransactionJournalInterface, CUD, CommonData
*
* @return Collection
*/
public function getWithdrawals()
public function getWithdrawals($limit = null, $offset = null)
{
return $this->getUser()->transactionjournals()->withRelevantData()->transactionTypes(['Withdrawal'])->get(['transaction_journals.*']);
$query = $this->getUser()->transactionjournals()->withRelevantData()->transactionTypes(['Withdrawal']);
if(!is_null($limit) && intval($limit) > 0) {
$query->take($limit);
}
if(!is_null($offset) && intval($offset) > 0) {
$query->skip($offset);
}
return $query->get(['transaction_journals.*']);
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace FireflyIII\Shared\Google;
class Table {
}

View File

@ -0,0 +1,13 @@
<?php
namespace FireflyIII\Shared\Google\Table;
use Illuminate\Support\Collection;
interface Table {
public function generate();
public function addData(Collection $data);
}

View File

@ -0,0 +1,192 @@
<?php
/**
* Created by PhpStorm.
* User: sander
* Date: 13/11/14
* Time: 21:31
*/
namespace FireflyIII\Shared\Google\Table;
use Firefly\Exception\FireflyException;
use Illuminate\Support\Collection;
class Transactions implements Table
{
/** @var \Grumpydictator\Gchart\GChart */
protected $chart;
/** @var int */
protected $limit;
/** @var int */
protected $offset;
/** @var bool */
protected $paging;
/** @var string */
protected $reqID;
public function __construct()
{
/** @var \Grumpydictator\Gchart\GChart chart */
$this->chart = \App::make('gchart');
$this->chart->addColumn('ID', 'number');
$this->chart->addColumn('ID_Edit', 'string');
$this->chart->addColumn('ID_Delete', 'string');
$this->chart->addColumn('Date', 'date');
$this->chart->addColumn('Description_URL', 'string');
$this->chart->addColumn('Description', 'string');
$this->chart->addColumn('Amount', 'number');
$this->chart->addColumn('From_URL', 'string');
$this->chart->addColumn('From', 'string');
$this->chart->addColumn('To_URL', 'string');
$this->chart->addColumn('To', 'string');
$this->chart->addColumn('Budget_URL', 'string');
$this->chart->addColumn('Budget', 'string');
$this->chart->addColumn('Category_URL', 'string');
$this->chart->addColumn('Category', 'string');
}
public function addData(Collection $data)
{
/** @var \TransactionJournal $entry */
foreach ($data as $entry) {
$date = $entry->date;
$descriptionURL = route('transactions.show', $entry->id);
$description = $entry->description;
/** @var Transaction $transaction */
foreach ($entry->transactions as $transaction) {
if (floatval($transaction->amount) > 0) {
$amount = floatval($transaction->amount);
$to = $transaction->account->name;
$toURL = route('accounts.show', $transaction->account->id);
} else {
$from = $transaction->account->name;
$fromURL = route('accounts.show', $transaction->account->id);
}
}
if (isset($entry->budgets[0])) {
$budgetURL = route('budgets.show', $entry->budgets[0]->id);
$component = $entry->budgets[0]->name;
} else {
$budgetURL = '';
$component = '';
}
if (isset($entry->categories[0])) {
$categoryURL = route('categories.show', $entry->categories[0]->id);
$category = $entry->categories[0]->name;
} else {
$categoryURL = '';
$category = '';
}
$id = $entry->id;
$edit = route('transactions.edit', $entry->id);
$delete = route('transactions.delete', $entry->id);
$this->chart->addRow(
$id, $edit, $delete, $date, $descriptionURL, $description, $amount, $fromURL, $from, $toURL, $to, $budgetURL, $component, $categoryURL,
$category
);
}
}
public function generate()
{
if ($this->getPaging() && (is_null($this->getLimit()) || is_null($this->getOffset()) || is_null($this->getReqID()))) {
throw new FireflyException('Cannot page without parameters!');
}
$this->chart->generate();
if($this->getPaging()) {
$data = [
'version' => '0.6',
'reqId' => $this->getReqID(),
'status' => 'warning',
'warnings' => [
[
'reason' => 'data_truncated',
'message' => 'Retrieved data was truncated',
'detailed_message' => 'Data has been truncated due to userrequest (LIMIT in query)'
]
],
'sig' => '12345',
'table' => $this->chart->getData()
];
$return = '// Data table response'."\n".'google.visualization.Query.setResponse(' . json_encode($data).');';
return $return;
//"version":"0.6","reqId":"0","status":"warning","warnings":[{"reason":"data_truncated","message":"Retrieved data was truncated","detailed_message":"Data has been truncated due to userrequest (LIMIT in query)"}],"sig":"253683512","table
}
return \Response::json($this->chart->getData());
}
/**
* @return mixed
*/
public function getPaging()
{
return $this->paging;
}
/**
* @param mixed $paging
*/
public function setPaging($paging)
{
$this->paging = $paging;
}
/**
* @return int
*/
public function getLimit()
{
return $this->limit;
}
/**
* @param int $limit
*/
public function setLimit($limit)
{
$this->limit = $limit;
}
/**
* @return int
*/
public function getOffset()
{
return $this->offset;
}
/**
* @param int $offset
*/
public function setOffset($offset)
{
$this->offset = $offset;
}
/**
* @return string
*/
public function getReqID()
{
return $this->reqID;
}
/**
* @param string $reqID
*/
public function setReqID($reqID)
{
$this->reqID = $reqID;
}
}

View File

@ -17,7 +17,7 @@
{{HTML::style('assets/stylesheets/metisMenu/metisMenu.min.css')}}
{{HTML::style('assets/stylesheets/sbadmin/sb.css')}}
{{HTML::style('assets/stylesheets/fa/css/font-awesome.min.css')}}
{{HTML::style('http://fonts.googleapis.com/css?family=Roboto2')}}
{{HTML::style('https://fonts.googleapis.com/css?family=Roboto2')}}
@yield('styles')
<!--[if lt IE 9]>

View File

@ -7,20 +7,7 @@
<i class="fa {{$subTitleIcon}}"></i> {{{$subTitle}}}
</div>
<div class="panel-body">
<div id="transaction-table"></div>
<!--<table id="transactionTable" class="table table-striped table-bordered" >
<thead>
<tr>
<th>Date</th>
<th>Description</th>
<th>Amount (&euro;)</th>
<th>From</th>
<th>To</th>
<th>Budget / category</th>
<th>ID</th>
</tr>
</thead>
</table>-->
<div id="transaction-table"></div>
</div>
</div>
</div>
@ -34,6 +21,7 @@ var what = '{{{$what}}}';
</script>
<!-- load the libraries and scripts necessary for Google Charts: -->
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
{{HTML::script('assets/javascript/google/TableQueryWrapper.js')}}
{{HTML::script('assets/javascript/firefly/gcharts.options.js')}}
{{HTML::script('assets/javascript/firefly/gcharts.js')}}

View File

@ -314,4 +314,21 @@ function googleTable(URL, container) {
} else {
console.log('No container found called "' + container + '"');
}
}
/**
*
* @param URL
* @param container
*/
function googleTablePaged(URL, container) {
var query, options;
query = new google.visualization.Query(URL);
objContainer = document.getElementById(container);
options = {'pageSize': 5};
query.abort();
var tableQueryWrapper = new TableQueryWrapper(query, objContainer, options);
tableQueryWrapper.sendAndDraw();
}

View File

@ -15,7 +15,7 @@ if ($('input[name="category"]').length > 0) {
}
$(document).ready(function () {
if(typeof googleTable != 'undefined') {
googleTable('table/transactions/' + what,'transaction-table');
if(typeof googleTablePaged != 'undefined') {
googleTablePaged('table/transactions/' + what,'transaction-table');
}
});

View File

@ -0,0 +1,159 @@
/**
* A wrapper for a query and a table visualization.
* The object only requests 1 page + 1 row at a time, by default, in order
* to minimize the amount of data held locally.
* Table sorting and pagination is executed by issuing
* additional requests with appropriate query parameters.
* E.g., for getting the data sorted by column 'A' the following query is
* attached to the request: 'tq=order by A'.
*
* Note: Discards query strings set by the user on the query object using
* google.visualization.Query#setQuery.
*
* DISCLAIMER: This is an example code which you can copy and change as
* required. It is used with the google visualization API table visualization
* which is assumed to be loaded to the page. For more info see:
* https://developers.google.com/chart/interactive/docs/gallery/table
* https://developers.google.com/chart/interactive/docs/reference#Query
*/
/**
* Constructs a new table query wrapper for the specified query, container
* and tableOptions.
*
* Note: The wrapper clones the options object to adjust some of its properties.
* In particular:
* sort {string} set to 'event'.
* page {string} set to 'event'.
* pageSize {Number} If number <= 0 set to 10.
* showRowNumber {boolean} set to true.
* firstRowNumber {number} set according to the current page.
* sortAscending {boolean} set according to the current sort.
* sortColumn {number} set according to the given sort.
* @constructor
*/
var TableQueryWrapper = function(query, container, options) {
this.table = new google.visualization.Table(container);
this.query = query;
this.sortQueryClause = '';
this.pageQueryClause = '';
this.container = container;
this.currentDataTable = null;
var self = this;
var addListener = google.visualization.events.addListener;
addListener(this.table, 'page', function(e) {self.handlePage(e)});
addListener(this.table, 'sort', function(e) {self.handleSort(e)});
options = options || {};
options = TableQueryWrapper.clone(options);
options['sort'] = 'event';
options['page'] = 'event';
options['showRowNumber'] = true;
var buttonConfig = 'pagingButtonsConfiguration';
options[buttonConfig] = options[buttonConfig] || 'both';
options['pageSize'] = (options['pageSize'] > 0) ? options['pageSize'] : 10;
this.pageSize = options['pageSize'];
this.tableOptions = options;
this.currentPageIndex = 0;
this.setPageQueryClause(0);
};
/**
* Sends the query and upon its return draws the Table visualization in the
* container. If the query refresh interval is set then the visualization will
* be redrawn upon each refresh.
*/
TableQueryWrapper.prototype.sendAndDraw = function() {
this.query.abort();
var queryClause = this.sortQueryClause + ' ' + this.pageQueryClause;
this.query.setQuery(queryClause);
this.table.setSelection([]);
var self = this;
this.query.send(function(response) {self.handleResponse(response)});
};
/** Handles the query response after a send returned by the data source. */
TableQueryWrapper.prototype.handleResponse = function(response) {
this.currentDataTable = null;
if (response.isError()) {
google.visualization.errors.addError(this.container, response.getMessage(),
response.getDetailedMessage(), {'showInTooltip': false});
} else {
// make data:
//this.currentDataTable= new google.visualization.DataTable(response);
//console.log(response);
this.currentDataTable = response.getDataTable();
this.table.draw(this.currentDataTable, this.tableOptions);
}
};
/** Handles a sort event with the given properties. Will page to page=0. */
TableQueryWrapper.prototype.handleSort = function(properties) {
var columnIndex = properties['column'];
var isAscending = properties['ascending'];
this.tableOptions['sortColumn'] = columnIndex;
this.tableOptions['sortAscending'] = isAscending;
// dataTable exists since the user clicked the table.
var colID = this.currentDataTable.getColumnId(columnIndex);
this.sortQueryClause = 'order by `' + colID + (!isAscending ? '` desc' : '`');
// Calls sendAndDraw internally.
this.handlePage({'page': 0});
};
/** Handles a page event with the given properties. */
TableQueryWrapper.prototype.handlePage = function(properties) {
var localTableNewPage = properties['page']; // 1, -1 or 0
var newPage = 0;
if (localTableNewPage != 0) {
newPage = this.currentPageIndex + localTableNewPage;
}
if (this.setPageQueryClause(newPage)) {
this.sendAndDraw();
}
};
/**
* Sets the pageQueryClause and table options for a new page request.
* In case the next page is requested - checks that another page exists
* based on the previous request.
* Returns true if a new page query clause was set, false otherwise.
*/
TableQueryWrapper.prototype.setPageQueryClause = function(pageIndex) {
var pageSize = this.pageSize;
if (pageIndex < 0) {
return false;
}
var dataTable = this.currentDataTable;
if ((pageIndex == this.currentPageIndex + 1) && dataTable) {
if (dataTable.getNumberOfRows() <= pageSize) {
return false;
}
}
this.currentPageIndex = pageIndex;
var newStartRow = this.currentPageIndex * pageSize;
// Get the pageSize + 1 so that we can know when the last page is reached.
this.pageQueryClause = 'limit ' + (pageSize + 1) + ' offset ' + newStartRow;
// Note: row numbers are 1-based yet dataTable rows are 0-based.
this.tableOptions['firstRowNumber'] = newStartRow + 1;
return true;
};
/** Performs a shallow clone of the given object. */
TableQueryWrapper.clone = function(obj) {
var newObj = {};
for (var key in obj) {
newObj[key] = obj[key];
}
return newObj;
};