* reorganize code * +searchBuilder * +SearchBuilder * fix searchBuilder * fix searchBuilder * add object_hash * Add files via upload * fix use DIWikiPage * fix position sticky * fix searchBuilderType * add datatables.mark * add datatables.mark * add mark, spinner, fix keyword * add mark, spinner * fix context * fix test * fix spinner hide * prevents double spinner * fix regex * fix delimiter --------- Co-authored-by: Bernhard Krabina <bernhard.krabina@km-a.net>
这个提交包含在:
父节点
6b4c2f2cf3
当前提交
6dd7e46c19
|
@ -1017,10 +1017,14 @@ return [
|
|||
|
||||
'ext.srf.datatables.v2.module' => $moduleTemplate + [
|
||||
'scripts' => [
|
||||
'resources/jquery/datatables/object_hash.js',
|
||||
'resources/jquery/datatables/jquery.mark.min.js',
|
||||
'resources/jquery/datatables/datatables.mark.min.js',
|
||||
'resources/jquery/datatables/datatables.min.js',
|
||||
'resources/jquery/datatables/jquery.dataTables.extras.js',
|
||||
],
|
||||
'styles' => [
|
||||
'resources/jquery/datatables/datatables.mark.min.css',
|
||||
'resources/jquery/datatables/datatables.min.css',
|
||||
]
|
||||
],
|
||||
|
|
|
@ -31,9 +31,11 @@ class Api extends ApiBase {
|
|||
// get request parameters
|
||||
$requestParams = $this->extractRequestParams();
|
||||
|
||||
$data = json_decode( $requestParams['data'], true );
|
||||
|
||||
// @see https://datatables.net/reference/option/ajax
|
||||
$datatableData = json_decode( $requestParams['datatable'], true );
|
||||
$settings = json_decode( $requestParams['settings'], true );
|
||||
$datatableData = $data['datatableData'];
|
||||
$settings = $data['settings'];
|
||||
|
||||
if ( empty( $datatableData['length'] ) ) {
|
||||
$datatableData['length'] = $settings['defer-each'];
|
||||
|
@ -59,7 +61,7 @@ class Api extends ApiBase {
|
|||
$parameters[$def->getName()] = $def->getDefault();
|
||||
}
|
||||
|
||||
$printoutsRaw = json_decode( $requestParams['printouts'], true );
|
||||
$printoutsRaw = $data['printouts'];
|
||||
|
||||
// add/set specific parameters for this call
|
||||
$parameters = array_merge(
|
||||
|
@ -67,7 +69,6 @@ class Api extends ApiBase {
|
|||
[
|
||||
// *** important !!
|
||||
'format' => 'datatables',
|
||||
|
||||
"apicall" => "apicall",
|
||||
// @see https://datatables.net/manual/server-side
|
||||
// array length will be sliced client side if greater
|
||||
|
@ -104,10 +105,10 @@ class Api extends ApiBase {
|
|||
foreach ( $printoutsRaw as $printoutData ) {
|
||||
|
||||
// create property from property key
|
||||
if ( $printoutData[0] === SMWPrintRequest::PRINT_PROP ) {
|
||||
$data = $dataValueFactory->newPropertyValueByLabel( $printoutData[1] );
|
||||
if ( $printoutData[0] === SMWPrintRequest::PRINT_PROP ) {
|
||||
$data_ = $dataValueFactory->newPropertyValueByLabel( $printoutData[1] );
|
||||
} else {
|
||||
$data = null;
|
||||
$data_ = null;
|
||||
if ( $hasMainlabel && trim( $parameters['mainlabel'] ) === '-' ) {
|
||||
continue;
|
||||
}
|
||||
|
@ -118,7 +119,7 @@ class Api extends ApiBase {
|
|||
$printouts[] = new SMWPrintRequest(
|
||||
$printoutData[0], // mode
|
||||
$printoutData[1], // (canonical) label
|
||||
$data, // property name
|
||||
$data_, // property name
|
||||
$printoutData[3], // output format
|
||||
$printoutData[4] // parameters
|
||||
);
|
||||
|
@ -127,8 +128,8 @@ class Api extends ApiBase {
|
|||
|
||||
// SMWQueryProcessor::addThisPrintout( $printouts, $parameters );
|
||||
|
||||
$printrequests = json_decode( $requestParams['printrequests'], true );
|
||||
$columnDefs = json_decode( $requestParams['columndefs'], true );
|
||||
$printrequests = $data['printrequests'];
|
||||
$columnDefs = $data['columnDefs'];
|
||||
|
||||
$getColumnAttribute = function( $label, $attr ) use( $columnDefs ) {
|
||||
foreach ( $columnDefs as $value ) {
|
||||
|
@ -171,13 +172,74 @@ class Api extends ApiBase {
|
|||
}
|
||||
}
|
||||
|
||||
// @see https://datatables.net/extensions/searchbuilder/customConditions.html
|
||||
// @see https://datatables.net/reference/option/searchBuilder.depthLimit
|
||||
if ( !empty( $datatableData['searchBuilder'] ) ) {
|
||||
$searchBuilder = [];
|
||||
foreach ( $datatableData['searchBuilder']['criteria'] as $criteria ) {
|
||||
foreach ( $printoutsRaw as $key => $value ) {
|
||||
// @FIXME $label isn't simply $value[1] ?
|
||||
$printrequest = $printrequests[$key];
|
||||
$label = ( $printrequest['key'] !== '' ? $value[1] : '' );
|
||||
if ( $label === $criteria['data'] ) {
|
||||
|
||||
// nested condition, skip for now
|
||||
if ( !array_key_exists( 'condition', $criteria ) ) {
|
||||
continue;
|
||||
}
|
||||
$v = implode( $criteria['value'] );
|
||||
$str = ( $label !== '' ? "$label::" : '' );
|
||||
switch( $criteria['condition'] ) {
|
||||
case '=':
|
||||
$searchBuilder[] = "[[{$str}{$v}]]";
|
||||
break;
|
||||
case '!=':
|
||||
$searchBuilder[] = "[[{$str}!~$v]]";
|
||||
break;
|
||||
case 'starts':
|
||||
$searchBuilder[] = "[[{$str}~$v*]]";
|
||||
break;
|
||||
case '!starts':
|
||||
$searchBuilder[] = "[[{$str}!~$v*]]";
|
||||
break;
|
||||
case 'contains':
|
||||
$searchBuilder[] = "[[{$str}~*$v*]]";
|
||||
break;
|
||||
case '!contains':
|
||||
$searchBuilder[] = "[[{$str}!~*$v*]]";
|
||||
break;
|
||||
case 'ends':
|
||||
$searchBuilder[] = "[[{$str}~*$v]]";
|
||||
break;
|
||||
case '!ends':
|
||||
$searchBuilder[] = "[[$str}!~*$v]]";
|
||||
break;
|
||||
// case 'null':
|
||||
// break;
|
||||
case '!null':
|
||||
if ( $label ) {
|
||||
$searchBuilder[] = "[[$label::+]]";
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( $datatableData['searchBuilder']['logic'] === 'AND' ) {
|
||||
$queryConjunction = array_merge( $queryConjunction, $searchBuilder );
|
||||
} else if ( $datatableData['searchBuilder']['logic'] === 'OR' ) {
|
||||
$queryDisjunction = array_merge( $queryDisjunction, $searchBuilder );
|
||||
}
|
||||
}
|
||||
|
||||
global $smwgQMaxSize;
|
||||
|
||||
if ( !count( $queryDisjunction ) ) {
|
||||
$queryDisjunction = [''];
|
||||
}
|
||||
|
||||
$query = $requestParams['query'] . implode( '', $queryConjunction );
|
||||
$query = $data['queryString'] . implode( '', $queryConjunction );
|
||||
|
||||
$conditions = array_map( static function( $value ) use ( $query ) {
|
||||
return $query . $value;
|
||||
|
@ -188,8 +250,6 @@ class Api extends ApiBase {
|
|||
|
||||
$queryStr = implode( 'OR', $conditions );
|
||||
|
||||
// trigger_error('queryStr ' . $queryStr);
|
||||
|
||||
$log['queryStr '] = $queryStr;
|
||||
|
||||
$query = SMWQueryProcessor::createQuery(
|
||||
|
@ -205,7 +265,6 @@ class Api extends ApiBase {
|
|||
// $smwgQMaxSize = max( $smwgQMaxSize, $size );
|
||||
// trigger_error('smwgQMaxSize ' . $smwgQMaxSize);
|
||||
|
||||
|
||||
$applicationFactory = ServicesFactory::getInstance();
|
||||
$queryEngine = $applicationFactory->getStore();
|
||||
$results = $queryEngine->getQueryResult( $query );
|
||||
|
@ -234,6 +293,8 @@ class Api extends ApiBase {
|
|||
'data' => $res,
|
||||
'recordsTotal' => $settings['count'],
|
||||
'recordsFiltered' => $count,
|
||||
'cacheKey' => $data['cacheKey'],
|
||||
'datalength' => $datatableData['length']
|
||||
];
|
||||
|
||||
if ( $settings['displayLog'] ) {
|
||||
|
@ -275,44 +336,10 @@ class Api extends ApiBase {
|
|||
*/
|
||||
protected function getAllowedParams() {
|
||||
return [
|
||||
'query' => [
|
||||
'data' => [
|
||||
ApiBase::PARAM_TYPE => 'string',
|
||||
ApiBase::PARAM_REQUIRED => true,
|
||||
],
|
||||
'columndefs' => [
|
||||
ApiBase::PARAM_TYPE => 'string',
|
||||
ApiBase::PARAM_REQUIRED => true,
|
||||
],
|
||||
'printouts' => [
|
||||
ApiBase::PARAM_TYPE => 'string',
|
||||
ApiBase::PARAM_REQUIRED => true,
|
||||
],
|
||||
'printrequests' => [
|
||||
ApiBase::PARAM_TYPE => 'string',
|
||||
ApiBase::PARAM_REQUIRED => true,
|
||||
],
|
||||
'settings' => [
|
||||
ApiBase::PARAM_TYPE => 'string',
|
||||
ApiBase::PARAM_REQUIRED => true,
|
||||
],
|
||||
'datatable' => [
|
||||
ApiBase::PARAM_TYPE => 'string',
|
||||
ApiBase::PARAM_REQUIRED => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of parameter descriptions.
|
||||
* Don't call this function directly: use getFinalParamDescription() to
|
||||
* allow hooks to modify descriptions as needed.
|
||||
*
|
||||
* @return array|bool False on no parameter descriptions
|
||||
*/
|
||||
protected function getParamDescription() {
|
||||
return [
|
||||
'query' => 'Original query',
|
||||
'printouts' => 'Printouts used in the original query',
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
|
|
文件差异内容过多而无法显示
加载差异
|
@ -0,0 +1,746 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* SRF DataTables and SMWAPI.
|
||||
*
|
||||
* @see http://datatables.net/
|
||||
*
|
||||
* @licence GPL-2.0-or-later
|
||||
* @author thomas-topway-it for KM-A
|
||||
*/
|
||||
|
||||
namespace SRF\DataTables;
|
||||
use SMW\DIWikiPage;
|
||||
use SMW\DataValueFactory;
|
||||
use SMW\DataTypeRegistry;
|
||||
use SMW\DIProperty;
|
||||
use SMW\SQLStore\SQLStore;
|
||||
use SMW\SQLStore\TableBuilder\FieldType;
|
||||
use SMW\QueryFactory;
|
||||
use SMW\Services\ServicesFactory as ApplicationFactory;
|
||||
use SMWDataItem as DataItem;
|
||||
use SMWQueryProcessor;
|
||||
use SMWPrintRequest;
|
||||
use SMW\Query\PrintRequest;
|
||||
use SMW\SQLStore\QueryEngineFactory;
|
||||
|
||||
class SearchPanes {
|
||||
|
||||
/** @var array */
|
||||
private $searchPanesLog = [];
|
||||
|
||||
private $queryEngineFactory;
|
||||
|
||||
private $datatables;
|
||||
|
||||
private $connection;
|
||||
|
||||
private $queryFactory;
|
||||
|
||||
|
||||
public function __construct( $datatables ) {
|
||||
$this->datatables = $datatables;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $printRequests
|
||||
* @param array $searchPanesOptions
|
||||
* @return array
|
||||
*/
|
||||
public function getSearchPanes( $printRequests, $searchPanesOptions ) {
|
||||
$this->queryEngineFactory = new QueryEngineFactory( $this->datatables->store );
|
||||
$this->connection = $this->datatables->store->getConnection( 'mw.db.queryengine' );
|
||||
$this->queryFactory = new QueryFactory();
|
||||
|
||||
$ret = [];
|
||||
foreach ( $printRequests as $i => $printRequest ) {
|
||||
if ( count( $searchPanesOptions['columns'] ) && !in_array( $i, $searchPanesOptions['columns'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parameterOptions = $this->datatables->printoutsParametersOptions[$i];
|
||||
|
||||
$searchPanesParameterOptions = ( array_key_exists( 'searchPanes', $parameterOptions ) ?
|
||||
$parameterOptions['searchPanes'] : [] );
|
||||
|
||||
if ( array_key_exists( 'show', $searchPanesParameterOptions ) && $searchPanesParameterOptions['show'] === false ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$canonicalLabel = ( $printRequest->getMode() !== SMWPrintRequest::PRINT_THIS ?
|
||||
$printRequest->getCanonicalLabel() : '' );
|
||||
|
||||
$ret[$i] = $this->getPanesOptions( $printRequest, $canonicalLabel, $searchPanesOptions, $searchPanesParameterOptions );
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getLog() {
|
||||
return $this->searchPanesLog;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PrintRequest $printRequest
|
||||
* @param string $canonicalLabel
|
||||
* @param array $searchPanesOptions
|
||||
* @param array $searchPanesParameterOptions
|
||||
* @return array
|
||||
*/
|
||||
private function getPanesOptions( $printRequest, $canonicalLabel, $searchPanesOptions, $searchPanesParameterOptions ) {
|
||||
|
||||
if ( empty( $canonicalLabel ) ) {
|
||||
return $this->searchPanesMainlabel( $printRequest, $searchPanesOptions, $searchPanesParameterOptions );
|
||||
}
|
||||
|
||||
// create a new query for each printout/pane
|
||||
// and retrieve the query segment related to it
|
||||
// then perform the real query to get the results
|
||||
|
||||
$queryParams = [
|
||||
'limit' => $this->datatables->query->getLimit(),
|
||||
'offset' => $this->datatables->query->getOffset(),
|
||||
'mainlabel' => $this->datatables->query->getMainlabel()
|
||||
];
|
||||
$queryParams = SMWQueryProcessor::getProcessedParams( $queryParams, [] );
|
||||
|
||||
// @TODO @FIXME
|
||||
// get original description and add a conjunction
|
||||
// $queryDescription = $query->getDescription();
|
||||
// $queryCount = new \SMWQuery($queryDescription);
|
||||
// ...
|
||||
|
||||
$isCategory = $printRequest->getMode() === PrintRequest::PRINT_CATS;
|
||||
|
||||
// @TODO @FIXME cover PRINT_CHAIN as well
|
||||
$newQuery = SMWQueryProcessor::createQuery(
|
||||
$this->datatables->query->getQueryString() . ( !$isCategory ? '[[' . $canonicalLabel . '::+]]' : '' ),
|
||||
$queryParams,
|
||||
SMWQueryProcessor::INLINE_QUERY,
|
||||
''
|
||||
);
|
||||
|
||||
$queryDescription = $newQuery->getDescription();
|
||||
$queryDescription->setPrintRequests( [$printRequest] );
|
||||
|
||||
$conditionBuilder = $this->queryEngineFactory->newConditionBuilder();
|
||||
|
||||
$rootid = $conditionBuilder->buildCondition( $newQuery );
|
||||
|
||||
\SMW\SQLStore\QueryEngine\QuerySegment::$qnum = 0;
|
||||
$querySegmentList = $conditionBuilder->getQuerySegmentList();
|
||||
|
||||
$querySegmentListProcessor = $this->queryEngineFactory->newQuerySegmentListProcessor();
|
||||
|
||||
$querySegmentListProcessor->setQuerySegmentList( $querySegmentList );
|
||||
|
||||
// execute query tree, resolve all dependencies
|
||||
$querySegmentListProcessor->process( $rootid );
|
||||
|
||||
$qobj = $querySegmentList[$rootid];
|
||||
|
||||
$property = new DIProperty( DIProperty::newFromUserLabel( $printRequest->getCanonicalLabel() ) );
|
||||
$propTypeid = $property->findPropertyTypeID();
|
||||
|
||||
if ( $isCategory ) {
|
||||
|
||||
// data-length without the GROUP BY clause
|
||||
$sql_options = [ 'LIMIT' => 1 ];
|
||||
|
||||
$dataLength = (int)$this->connection->selectField(
|
||||
$this->connection->tableName( $qobj->joinTable ) . " AS $qobj->alias" . $qobj->from
|
||||
. ' JOIN ' . $this->connection->tableName( 'smw_fpt_inst' ) . " AS insts ON $qobj->alias.smw_id = insts.s_id",
|
||||
"COUNT(*) AS count",
|
||||
$qobj->where,
|
||||
__METHOD__,
|
||||
$sql_options
|
||||
);
|
||||
|
||||
if ( !$dataLength ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$groupBy = "i.smw_id";
|
||||
$orderBy = "count DESC, $groupBy ASC";
|
||||
$sql_options = [
|
||||
'GROUP BY' => $groupBy,
|
||||
'LIMIT' => $dataLength, // $this->query->getOption( 'count' ),
|
||||
'ORDER BY' => $orderBy,
|
||||
'HAVING' => 'count >= ' . $searchPanesOptions['minCount']
|
||||
];
|
||||
|
||||
/*
|
||||
SELECT COUNT(i.smw_id), i.smw_id, i.smw_title FROM `smw_object_ids` AS t0
|
||||
JOIN `smw_fpt_inst` AS t1 ON t0.smw_id=t1.s_id
|
||||
JOIN `smw_fpt_inst` AS insts ON t0.smw_id=insts.s_id
|
||||
JOIN `smw_object_ids` AS i ON i.smw_id = insts.o_id
|
||||
WHERE (t1.o_id=1077)
|
||||
GROUP BY i.smw_id
|
||||
HAVING COUNT(i.smw_id) >= 1 ORDER BY COUNT(i.smw_id) DESC
|
||||
*/
|
||||
|
||||
$res = $this->connection->select(
|
||||
$this->connection->tableName( $qobj->joinTable ) . " AS $qobj->alias" . $qobj->from
|
||||
// @see https://github.com/SemanticMediaWiki/SemanticDrilldown/blob/master/includes/Sql/SqlProvider.php
|
||||
. ' JOIN ' . $this->connection->tableName( 'smw_fpt_inst' ) . " AS insts ON $qobj->alias.smw_id = insts.s_id"
|
||||
. ' JOIN ' . $this->connection->tableName( SQLStore::ID_TABLE ) . " AS i ON i.smw_id = insts.o_id",
|
||||
"COUNT($groupBy) AS count, i.smw_id, i.smw_title, i.smw_namespace, i.smw_iw, i.smw_sort, i.smw_subobject",
|
||||
$qobj->where,
|
||||
__METHOD__,
|
||||
$sql_options
|
||||
);
|
||||
|
||||
$isIdField = true;
|
||||
|
||||
} else {
|
||||
$tableid = $this->datatables->store->findPropertyTableID( $property );
|
||||
|
||||
$querySegmentList = array_reverse( $querySegmentList );
|
||||
|
||||
// get aliases
|
||||
$p_alias = null;
|
||||
foreach ( $querySegmentList as $segment ) {
|
||||
if ( $segment->joinTable === $tableid ) {
|
||||
$p_alias = $segment->alias;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $p_alias ) ) {
|
||||
$this->searchPanesLog[] = [
|
||||
'canonicalLabel' => $printRequest->getCanonicalLabel(),
|
||||
'error' => '$p_alias is null',
|
||||
];
|
||||
return [];
|
||||
}
|
||||
|
||||
// data-length without the GROUP BY clause
|
||||
$sql_options = [ 'LIMIT' => 1 ];
|
||||
|
||||
// SELECT COUNT(*) as count FROM `smw_object_ids` AS t0
|
||||
// INNER JOIN (`smw_fpt_mdat` AS t2 INNER JOIN `smw_di_wikipage` AS t3 ON t2.s_id=t3.s_id) ON t0.smw_id=t2.s_id
|
||||
// WHERE ((t3.p_id=517)) LIMIT 500
|
||||
|
||||
$dataLength = (int)$this->connection->selectField(
|
||||
$this->connection->tableName( $qobj->joinTable ) . " AS $qobj->alias" . $qobj->from,
|
||||
"COUNT(*) as count",
|
||||
$qobj->where,
|
||||
__METHOD__,
|
||||
$sql_options
|
||||
);
|
||||
|
||||
if ( !$dataLength ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
list( $diType, $isIdField, $fields, $groupBy, $orderBy ) = $this->fetchValuesByGroup( $property, $p_alias, $propTypeid );
|
||||
|
||||
/*
|
||||
---GENERATED FROM DATATABLES
|
||||
SELECT t0.smw_id,t0.smw_title,t0.smw_namespace,t0.smw_iw,t0.smw_subobject,t0.smw_hash,t0.smw_sort,COUNT( t3.o_id ) as count FROM `smw_object_ids` AS t0 INNER JOIN (`smw_fpt_mdat` AS t2 INNER JOIN `smw_di_wikipage` AS t3 ON t2.s_id=t3.s_id <<and t3.s_id = smw_object_ids.smw_id>> ) ON t0.smw_id=t2.s_id WHERE ((t3.p_id=517)) GROUP BY t3.o_id, t0.smw_id HAVING count >= 1 ORDER BY count DESC, t0.smw_sort ASC LIMIT 500
|
||||
|
||||
---GENERATED ByGroupPropertyValuesLookup
|
||||
SELECT i.smw_id,i.smw_title,i.smw_namespace,i.smw_iw,i.smw_subobject,i.smw_hash,i.smw_sort,COUNT( p.o_id ) as count FROM `smw_object_ids` `o` INNER JOIN `smw_di_wikipage` `p` ON ((p.s_id=o.smw_id)) JOIN `smw_object_ids` `i` ON ((p.o_id=i.smw_id)) WHERE o.smw_hash IN ('1_-_A','1_-_Ab','1_-_Abc','10_-_Abcd','11_-_Abc') AND (o.smw_iw!=':smw') AND (o.smw_iw!=':smw-delete') AND p.p_id = 517 GROUP BY p.o_id, i.smw_id ORDER BY count DESC, i.smw_sort ASC
|
||||
*/
|
||||
|
||||
global $smwgQMaxLimit;
|
||||
|
||||
$sql_options = [
|
||||
'GROUP BY' => $groupBy,
|
||||
// the following implies that if the user sets a threshold
|
||||
// close or equal to 1, and there are too many unique values,
|
||||
// the page will break, however the user has responsibility
|
||||
// for using searchPanes only for data reasonably grouped
|
||||
// shouldn't be 'LIMIT' => $smwgQMaxLimit, ?
|
||||
'LIMIT' => $dataLength,
|
||||
'ORDER BY' => $orderBy,
|
||||
'HAVING' => 'count >= ' . $searchPanesOptions['minCount']
|
||||
];
|
||||
|
||||
// @see QueryEngine
|
||||
$res = $this->connection->select(
|
||||
$this->connection->tableName( $qobj->joinTable ) . " AS $qobj->alias" . $qobj->from
|
||||
. ( !$isIdField ? ''
|
||||
: " JOIN " . $this->connection->tableName( SQLStore::ID_TABLE ) . " AS `i` ON ($p_alias.o_id = i.smw_id)" ),
|
||||
implode( ',', $fields ),
|
||||
$qobj->where . ( !$isIdField ? '' : ( !empty( $qobj->where ) ? ' AND' : '' )
|
||||
. ' i.smw_iw!=' . $this->connection->addQuotes( SMW_SQL3_SMWIW_OUTDATED )
|
||||
. ' AND i.smw_iw!=' . $this->connection->addQuotes( SMW_SQL3_SMWDELETEIW ) ),
|
||||
__METHOD__,
|
||||
$sql_options
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
// verify uniqueRatio
|
||||
|
||||
// @see https://datatables.net/extensions/searchpanes/examples/initialisation/threshold.htm
|
||||
// @see https://github.com/DataTables/SearchPanes/blob/818900b75dba6238bf4b62a204fdd41a9b8944b7/src/SearchPane.ts#L824
|
||||
|
||||
$threshold = !empty( $searchPanesParameterOptions['threshold'] ) ?
|
||||
$searchPanesParameterOptions['threshold'] : $searchPanesOptions['threshold'];
|
||||
|
||||
$outputFormat = $printRequest->getOutputFormat();
|
||||
|
||||
// *** if outputFormat is not set we can compute
|
||||
// uniqueness ratio by now, otherwise we have to
|
||||
// perform it after grouping the actual data
|
||||
if ( !$outputFormat ) {
|
||||
$binLength = $res->numRows();
|
||||
$uniqueRatio = $binLength / $dataLength;
|
||||
|
||||
$this->searchPanesLog[] = [
|
||||
'canonicalLabel' => $printRequest->getCanonicalLabel(),
|
||||
'dataLength' => $dataLength,
|
||||
'binLength' => $binLength,
|
||||
'uniqueRatio' => $uniqueRatio,
|
||||
'threshold' => $threshold,
|
||||
'grouped' => false,
|
||||
];
|
||||
|
||||
// || $binLength <= 1
|
||||
if ( $uniqueRatio > $threshold ) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// @see ByGroupPropertyValuesLookup
|
||||
$diType = DataTypeRegistry::getInstance()->getDataItemId(
|
||||
$propTypeid
|
||||
);
|
||||
|
||||
$diHandler = $this->datatables->store->getDataItemHandlerForDIType(
|
||||
$diType
|
||||
);
|
||||
|
||||
$fields = $diHandler->getFetchFields();
|
||||
|
||||
$deepRedirectTargetResolver = ApplicationFactory::getInstance()
|
||||
->newMwCollaboratorFactory()->newDeepRedirectTargetResolver();
|
||||
|
||||
$outputMode = SMW_OUTPUT_HTML;
|
||||
$isSubject = false;
|
||||
$groups = [];
|
||||
foreach ( $res as $row ) {
|
||||
|
||||
if ( $isIdField ) {
|
||||
$dbKeys = [
|
||||
$row->smw_title,
|
||||
$row->smw_namespace,
|
||||
$row->smw_iw,
|
||||
$row->smw_sort,
|
||||
$row->smw_subobject
|
||||
];
|
||||
|
||||
} else {
|
||||
$dbKeys = [];
|
||||
foreach ( $fields as $field => $fieldType ) {
|
||||
$dbKeys[] = $row->$field;
|
||||
}
|
||||
}
|
||||
|
||||
$dbKeys = count( $dbKeys ) > 1 ? $dbKeys : $dbKeys[0];
|
||||
|
||||
$dataItem = $diHandler->dataItemFromDBKeys(
|
||||
$dbKeys
|
||||
);
|
||||
|
||||
// try to resolve redirect
|
||||
if ( $isIdField && $row->smw_iw === SMW_SQL3_SMWREDIIW ) {
|
||||
$redirectTarget = null;
|
||||
// @see SMWExportController
|
||||
try {
|
||||
$redirectTarget = $deepRedirectTargetResolver->findRedirectTargetFor( $dataItem->getTitle() );
|
||||
} catch ( \Exception $e ) {
|
||||
}
|
||||
if ( $redirectTarget ) {
|
||||
$dataItem = DIWikiPage::newFromTitle( $redirectTarget );
|
||||
}
|
||||
}
|
||||
|
||||
$dataValue = DataValueFactory::getInstance()->newDataValueByItem(
|
||||
$dataItem,
|
||||
$property
|
||||
);
|
||||
|
||||
if ( $outputFormat ) {
|
||||
$dataValue->setOutputFormat( $outputFormat );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
|
||||
|
||||
// @see DIBlobHandler
|
||||
// $isKeyword = $dataItem->getOption( 'is.keyword' );
|
||||
|
||||
if ( $propTypeid === '_keyw' ) {
|
||||
$value = $dataItem->normalize( $value );
|
||||
}
|
||||
|
||||
*/
|
||||
$cellContent = $this->datatables->getCellContent(
|
||||
$printRequest->getCanonicalLabel(),
|
||||
[ $dataValue ],
|
||||
$outputMode,
|
||||
$isSubject,
|
||||
$propTypeid
|
||||
);
|
||||
|
||||
if ( !array_key_exists( $cellContent, $groups ) ) {
|
||||
$groups[$cellContent] = [ 'count' => 0, 'value' => '' ];
|
||||
|
||||
if ( $dataItem->getDiType() === DataItem::TYPE_TIME ) {
|
||||
// max Unix time
|
||||
$groups[$cellContent]['minDate'] = 2147483647;
|
||||
$groups[$cellContent]['maxDate'] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
$groups[$cellContent]['count'] += $row->count;
|
||||
|
||||
// @TODO complete with all the possible transformations of
|
||||
// datavalues (DataValues/ValueFormatters)
|
||||
// based on $printRequest->getOutputFormat()
|
||||
// and provide to the API the information to
|
||||
// rebuild the query when values are grouped
|
||||
// by the output of the printout format, e.g.
|
||||
// if grouped by unit (for number datatype)
|
||||
// value should be *, for datetime see the
|
||||
// method below
|
||||
|
||||
switch( $dataItem->getDiType() ) {
|
||||
case DataItem::TYPE_NUMBER:
|
||||
if ( $outputFormat === '-u' ) {
|
||||
$value = '*';
|
||||
} else {
|
||||
$value = $dataValue->getNumber();
|
||||
}
|
||||
break;
|
||||
|
||||
case DataItem::TYPE_BLOB:
|
||||
// @see IntlNumberFormatter
|
||||
// $requestedLength = intval( $outputFormat );
|
||||
$value = $dataValue->getWikiValue();
|
||||
break;
|
||||
|
||||
case DataItem::TYPE_BOOLEAN:
|
||||
$value = $dataValue->getWikiValue();
|
||||
break;
|
||||
|
||||
case DataItem::TYPE_URI:
|
||||
$value = $dataValue->getWikiValue();
|
||||
break;
|
||||
|
||||
case DataItem::TYPE_TIME:
|
||||
$currentDate = $dataItem->asDateTime()->getTimestamp();
|
||||
$value = $dataValue->getISO8601Date();
|
||||
if ( $currentDate < $groups[$cellContent]['minDate'] ) {
|
||||
$groups[$cellContent]['minDate'] = $currentDate;
|
||||
}
|
||||
if ( $currentDate > $groups[$cellContent]['maxDate'] ) {
|
||||
$groups[$cellContent]['maxDate'] = $currentDate;
|
||||
}
|
||||
break;
|
||||
|
||||
case DataItem::TYPE_GEO:
|
||||
$value = $dataValue->getWikiValue();
|
||||
break;
|
||||
|
||||
case DataItem::TYPE_CONTAINER:
|
||||
$value = $dataValue->getWikiValue();
|
||||
break;
|
||||
|
||||
case DataItem::TYPE_WIKIPAGE:
|
||||
$title_ = $dataValue->getTitle();
|
||||
if ( $title_ ) {
|
||||
$value = $title_->getFullText();
|
||||
} else {
|
||||
$value = $dataValue->getWikiValue();
|
||||
$this->searchPanesLog[] = [
|
||||
'canonicalLabel' => $printRequest->getCanonicalLabel(),
|
||||
'error' => 'TYPE_WIKIPAGE title is null',
|
||||
'wikiValue' => $value,
|
||||
];
|
||||
}
|
||||
break;
|
||||
|
||||
case DataItem::TYPE_CONCEPT:
|
||||
$value = $dataValue->getWikiValue();
|
||||
break;
|
||||
|
||||
case DataItem::TYPE_PROPERTY:
|
||||
|
||||
break;
|
||||
case DataItem::TYPE_NOTYPE:
|
||||
$value = $dataValue->getWikiValue();
|
||||
break;
|
||||
|
||||
default:
|
||||
$value = $dataValue->getWikiValue();
|
||||
|
||||
}
|
||||
|
||||
$groups[$cellContent]['value'] = $value;
|
||||
}
|
||||
|
||||
if ( $outputFormat ) {
|
||||
$binLength = count( $groups );
|
||||
$uniqueRatio = $binLength / $dataLength;
|
||||
|
||||
$this->searchPanesLog[] = [
|
||||
'canonicalLabel' => $printRequest->getCanonicalLabel(),
|
||||
'dataLength' => $dataLength,
|
||||
'binLength' => $binLength,
|
||||
'uniqueRatio' => $uniqueRatio,
|
||||
'threshold' => $threshold,
|
||||
'grouped' => true,
|
||||
];
|
||||
|
||||
// || $binLength <= 1
|
||||
if ( $uniqueRatio > $threshold ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
arsort( $groups, SORT_NUMERIC );
|
||||
|
||||
$ret = [];
|
||||
foreach( $groups as $content => $value ) {
|
||||
|
||||
// @see https://www.semantic-mediawiki.org/wiki/Help:Search_operators
|
||||
// the latest value is returned, with the largest range
|
||||
if ( array_key_exists( 'minDate', $value ) && $value['minDate'] != $value['maxDate'] ) {
|
||||
// ISO 8601
|
||||
// @TODO use a symbol instead and transform from the API
|
||||
$value['value'] = '>' . date( 'c', $value['minDate'] ) . ']][[' . $printRequest->getCanonicalLabel() . '::<' . date( 'c', $value['maxDate'] );
|
||||
}
|
||||
|
||||
$ret[] = [
|
||||
'label' => $content,
|
||||
'count' => $value['count'],
|
||||
'value' => $value['value']
|
||||
];
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ByGroupPropertyValuesLookup
|
||||
* @param DIProperty $property
|
||||
* @param string $p_alias
|
||||
* @param string $propTypeId
|
||||
* @return array
|
||||
*/
|
||||
private function fetchValuesByGroup( DIProperty $property, $p_alias, $propTypeId ) {
|
||||
$tableid = $this->datatables->store->findPropertyTableID( $property );
|
||||
// $entityIdManager = $this->store->getObjectIds();
|
||||
|
||||
$proptables = $this->datatables->store->getPropertyTables();
|
||||
|
||||
// || $subjects === []
|
||||
if ( $tableid === '' || !isset( $proptables[$tableid] ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$connection = $this->datatables->store->getConnection( 'mw.db' );
|
||||
|
||||
$propTable = $proptables[$tableid];
|
||||
$isIdField = false;
|
||||
|
||||
$diHandler = $this->datatables->store->getDataItemHandlerForDIType(
|
||||
$propTable->getDiType()
|
||||
);
|
||||
|
||||
foreach ( $diHandler->getFetchFields() as $field => $fieldType ) {
|
||||
if ( !$isIdField && $fieldType === FieldType::FIELD_ID ) {
|
||||
$isIdField = true;
|
||||
}
|
||||
}
|
||||
|
||||
$groupBy = $diHandler->getLabelField();
|
||||
$pid = '';
|
||||
|
||||
if ( $groupBy === '' ) {
|
||||
$groupBy = $diHandler->getIndexField();
|
||||
}
|
||||
|
||||
$groupBy = "$p_alias.$groupBy";
|
||||
$orderBy = "count DESC, $groupBy ASC";
|
||||
|
||||
$diType = $propTable->getDiType();
|
||||
|
||||
if ( $diType === DataItem::TYPE_WIKIPAGE ) {
|
||||
$fields = [
|
||||
"i.smw_id",
|
||||
"i.smw_title",
|
||||
"i.smw_namespace",
|
||||
"i.smw_iw",
|
||||
"i.smw_subobject",
|
||||
"i.smw_hash",
|
||||
"i.smw_sort",
|
||||
"COUNT( $groupBy ) as count"
|
||||
];
|
||||
|
||||
$groupBy = "$p_alias.o_id, i.smw_id";
|
||||
$orderBy = "count DESC, i.smw_sort ASC";
|
||||
} elseif ( $diType === DataItem::TYPE_BLOB ) {
|
||||
$fields = [ "$p_alias.o_hash, $p_alias.o_blob", "COUNT( $p_alias.o_hash ) as count" ];
|
||||
|
||||
// @see DIBlobHandler
|
||||
$groupBy = ( $propTypeId !== '_keyw' ? "$p_alias.o_hash, $p_alias.o_blob"
|
||||
: "$p_alias.o_hash" );
|
||||
|
||||
} elseif ( $diType === DataItem::TYPE_URI ) {
|
||||
$fields = [ "$p_alias.o_serialized, $p_alias.o_blob", "COUNT( $p_alias.o_serialized ) as count" ];
|
||||
$groupBy = "$p_alias.o_serialized, $p_alias.o_blob";
|
||||
} elseif ( $diType === DataItem::TYPE_NUMBER ) {
|
||||
$fields = [ "$p_alias.o_serialized,$p_alias.o_sortkey, COUNT( $p_alias.o_serialized ) as count" ];
|
||||
$groupBy = "$p_alias.o_serialized,$p_alias.o_sortkey";
|
||||
$orderBy = "count DESC, $p_alias.o_sortkey DESC";
|
||||
} else {
|
||||
$fields = [ "$groupBy", "COUNT( $groupBy ) as count" ];
|
||||
}
|
||||
|
||||
// if ( !$propTable->isFixedPropertyTable() ) {
|
||||
// $pid = $entityIdManager->getSMWPropertyID( $property );
|
||||
// }
|
||||
|
||||
return [ $diType, $isIdField, $fields, $groupBy, $orderBy ];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PrintRequest $printRequest
|
||||
* @param array $searchPanesOptions
|
||||
* @param array $searchPanesParameterOptions
|
||||
* @return array
|
||||
*/
|
||||
private function searchPanesMainlabel( $printRequest, $searchPanesOptions, $searchPanesParameterOptions ) {
|
||||
|
||||
// mainlabel consists only of unique values,
|
||||
// so do not display if settings don't allow that
|
||||
if ( $searchPanesOptions['minCount'] > 1 ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$threshold = !empty( $searchPanesParameterOptions['threshold'] ) ?
|
||||
$searchPanesParameterOptions['threshold'] : $searchPanesOptions['threshold'];
|
||||
|
||||
$this->searchPanesLog[] = [
|
||||
'canonicalLabel' => 'mainLabel',
|
||||
'threshold' => $threshold,
|
||||
];
|
||||
|
||||
if ( $threshold < 1 ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$query = $this->datatables->query;
|
||||
$queryDescription = $query->getDescription();
|
||||
$queryDescription->setPrintRequests( [] );
|
||||
|
||||
$conditionBuilder = $this->queryEngineFactory->newConditionBuilder();
|
||||
$rootid = $conditionBuilder->buildCondition( $query );
|
||||
|
||||
\SMW\SQLStore\QueryEngine\QuerySegment::$qnum = 0;
|
||||
$querySegmentList = $conditionBuilder->getQuerySegmentList();
|
||||
|
||||
$querySegmentListProcessor = $this->queryEngineFactory->newQuerySegmentListProcessor();
|
||||
|
||||
$querySegmentListProcessor->setQuerySegmentList( $querySegmentList );
|
||||
|
||||
// execute query tree, resolve all dependencies
|
||||
$querySegmentListProcessor->process( $rootid );
|
||||
|
||||
$qobj = $querySegmentList[$rootid];
|
||||
|
||||
global $smwgQMaxLimit;
|
||||
|
||||
$sql_options = [
|
||||
// *** should we set a limit here ?
|
||||
// it makes sense to show the pane for
|
||||
// mainlabel only when page titles are grouped
|
||||
// through the printout format or even the printout template
|
||||
// 'LIMIT' => $smwgQMaxLimit,
|
||||
// title
|
||||
'ORDER BY' => 't'
|
||||
];
|
||||
|
||||
// Selecting those is required in standard SQL (but MySQL does not require it).
|
||||
$sortfields = implode( ',', $qobj->sortfields );
|
||||
$sortfields = $sortfields ? ',' . $sortfields : '';
|
||||
|
||||
// @see QueryEngine
|
||||
$res = $this->connection->select(
|
||||
$this->connection->tableName( $qobj->joinTable ) . " AS $qobj->alias" . $qobj->from,
|
||||
"$qobj->alias.smw_id AS id," .
|
||||
"$qobj->alias.smw_title AS t," .
|
||||
"$qobj->alias.smw_namespace AS ns," .
|
||||
"$qobj->alias.smw_iw AS iw," .
|
||||
"$qobj->alias.smw_subobject AS so," .
|
||||
"$qobj->alias.smw_sortkey AS sortkey" .
|
||||
"$sortfields",
|
||||
$qobj->where,
|
||||
__METHOD__,
|
||||
$sql_options
|
||||
);
|
||||
|
||||
$diHandler = $this->datatables->store->getDataItemHandlerForDIType(
|
||||
DataItem::TYPE_WIKIPAGE
|
||||
);
|
||||
|
||||
$outputMode = SMW_OUTPUT_HTML;
|
||||
$isSubject = false;
|
||||
$groups = [];
|
||||
foreach( $res as $row) {
|
||||
$dataItem = $diHandler->dataItemFromDBKeys( [
|
||||
$row->t,
|
||||
intval( $row->ns ),
|
||||
$row->iw,
|
||||
'',
|
||||
$row->so
|
||||
] );
|
||||
|
||||
$dataValue = DataValueFactory::getInstance()->newDataValueByItem(
|
||||
$dataItem
|
||||
);
|
||||
|
||||
if ( $printRequest->getOutputFormat() ) {
|
||||
$dataValue->setOutputFormat( $printRequest->getOutputFormat() );
|
||||
}
|
||||
|
||||
$cellContent = $this->datatables->getCellContent(
|
||||
$printRequest->getCanonicalLabel(),
|
||||
[ $dataValue ],
|
||||
$outputMode,
|
||||
$isSubject
|
||||
);
|
||||
|
||||
if ( !array_key_exists( $cellContent, $groups ) ) {
|
||||
$groups[$cellContent] = [ 'count' => 0, 'value' => '' ];
|
||||
}
|
||||
|
||||
$groups[$cellContent]['count']++;
|
||||
$groups[$cellContent]['value'] = $dataValue->getTitle()->getText();
|
||||
}
|
||||
|
||||
arsort( $groups, SORT_NUMERIC );
|
||||
|
||||
$ret = [];
|
||||
foreach( $groups as $content => $value ) {
|
||||
$ret[] = [
|
||||
'label' => $content,
|
||||
'value' => $value['value'],
|
||||
'count' => $value['count']
|
||||
];
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
}
|
|
@ -35,4 +35,15 @@
|
|||
right: 8px;
|
||||
}
|
||||
|
||||
div.dataTables_processing {
|
||||
/* background: white; */
|
||||
/* box-shadow: 0px 2px 18px 0px rgba(0,0,0,0.12); */
|
||||
z-index: 1000;
|
||||
position: sticky !important;
|
||||
}
|
||||
|
||||
div.dataTables_processing>div:last-child>div {
|
||||
background: rgb(13,110,253);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -84,10 +84,10 @@
|
|||
* @private
|
||||
* @static
|
||||
*
|
||||
* @param {Object} context
|
||||
* @param {Object} table
|
||||
*/
|
||||
initColumnSort: function (context) {
|
||||
var column = context.data("column-sort");
|
||||
initColumnSort: function (table) {
|
||||
var column = table.data("column-sort");
|
||||
|
||||
var order = [];
|
||||
|
||||
|
@ -117,10 +117,10 @@
|
|||
});
|
||||
|
||||
if (order.length > 0) {
|
||||
context.data("order", order);
|
||||
table.data("order", order);
|
||||
} else {
|
||||
// default @see https://datatables.net/reference/option/order
|
||||
context.data("order", [[0, "asc"]]);
|
||||
table.data("order", [[0, "asc"]]);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -148,6 +148,8 @@
|
|||
}
|
||||
},
|
||||
|
||||
// this is used only if Ajax is disabled and
|
||||
// the table does not have fields with multiple values
|
||||
getPanesOptions: function (data, columnDefs, options) {
|
||||
var ret = {};
|
||||
var dataLength = {};
|
||||
|
@ -287,12 +289,53 @@
|
|||
return searchPanesOptions;
|
||||
},
|
||||
|
||||
parse: {
|
||||
// ...
|
||||
},
|
||||
callApi: function (
|
||||
data,
|
||||
callback,
|
||||
preloadData,
|
||||
searchPanesOptions,
|
||||
displayLog
|
||||
) {
|
||||
var payload = {
|
||||
action: "ext.srf.datatables.api",
|
||||
format: "json",
|
||||
data: JSON.stringify(data),
|
||||
};
|
||||
|
||||
exportlinks: function (context, data) {
|
||||
// ...
|
||||
new mw.Api()
|
||||
.post(payload)
|
||||
.done(function (results) {
|
||||
var json = results["datatables-json"];
|
||||
|
||||
if (displayLog) {
|
||||
console.log("results log", json.log);
|
||||
}
|
||||
|
||||
// cache all retrieved rows for each sorting
|
||||
// dimension (column/dir), up to a fixed
|
||||
// threshold (_cacheLimit)
|
||||
|
||||
if (data.datatableData.search.value === "") {
|
||||
preloadData[json.cacheKey] = {
|
||||
data: preloadData[json.cacheKey]["data"]
|
||||
.slice(0, data.datatableData.start)
|
||||
.concat(json.data),
|
||||
count: json.recordsFiltered,
|
||||
};
|
||||
}
|
||||
|
||||
// we retrieve more than "length"
|
||||
// expected by datatables, so return the
|
||||
// sliced result
|
||||
json.data = json.data.slice(0, data.datatableData.datalength);
|
||||
json.searchPanes = {
|
||||
options: searchPanesOptions,
|
||||
};
|
||||
callback(json);
|
||||
})
|
||||
.fail(function (error) {
|
||||
console.log("error", error);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -323,25 +366,16 @@
|
|||
sInfoThousands: mw.msg("srf-ui-datatables-label-sInfoThousands"),
|
||||
sLengthMenu: mw.msg("srf-ui-datatables-label-sLengthMenu"),
|
||||
sLoadingRecords: mw.msg("srf-ui-datatables-label-sLoadingRecords"),
|
||||
sProcessing: mw.msg("srf-ui-datatables-label-sProcessing"),
|
||||
|
||||
// *** hide "processing" label above the indicator
|
||||
// sProcessing: mw.msg("srf-ui-datatables-label-sProcessing"),
|
||||
|
||||
sSearch: mw.msg("srf-ui-datatables-label-sSearch"),
|
||||
sZeroRecords: mw.msg("srf-ui-datatables-label-sZeroRecords"),
|
||||
},
|
||||
|
||||
/**
|
||||
* UI components
|
||||
*
|
||||
* @private
|
||||
* @param {array} context
|
||||
* @param {array} container
|
||||
* @param {array} data
|
||||
*/
|
||||
ui: function (context, container, data) {
|
||||
// ...
|
||||
},
|
||||
|
||||
// we don't need it anymore, however keep is as
|
||||
// a reference for alternate use
|
||||
// we don't need it anymore, however keep it as
|
||||
// a reference for other use
|
||||
showNotice: function (context, container, msg) {
|
||||
var cookieKey =
|
||||
"srf-ui-datatables-searchpanes-notice-" +
|
||||
|
@ -436,37 +470,18 @@
|
|||
*
|
||||
* @since 1.9
|
||||
*
|
||||
* @param {array} context
|
||||
* @param {array} container
|
||||
* @param {array} data
|
||||
*/
|
||||
init: function (context, container, data) {
|
||||
init: function (container, data) {
|
||||
var self = this;
|
||||
|
||||
// Hide loading spinner
|
||||
context.find(".srf-loading-dots").hide();
|
||||
var table = container.find("table");
|
||||
table.removeClass("wikitable");
|
||||
|
||||
// Show container
|
||||
container.css({ display: "block" });
|
||||
|
||||
_datatables.initColumnSort(context);
|
||||
|
||||
var order = context.data("order");
|
||||
|
||||
// Setup a raw table
|
||||
container.html(
|
||||
html.element("table", {
|
||||
style: "width: 100%",
|
||||
class:
|
||||
context.data("theme") === "bootstrap"
|
||||
? "bordered-table zebra-striped"
|
||||
: "display", // nowrap
|
||||
cellpadding: "0",
|
||||
cellspacing: "0",
|
||||
border: "0",
|
||||
})
|
||||
);
|
||||
_datatables.initColumnSort(table);
|
||||
|
||||
var order = table.data("order");
|
||||
var options = data["formattedOptions"];
|
||||
|
||||
// add the button placeholder if any button is required
|
||||
|
@ -489,7 +504,10 @@
|
|||
}
|
||||
|
||||
var queryResult = data.query.result;
|
||||
var useAjax = context.data("useAjax");
|
||||
var useAjax = table.data("useAjax");
|
||||
var count = parseInt(table.data("count"));
|
||||
|
||||
// var mark = isObject(options.mark);
|
||||
|
||||
var searchPanes = isObject(options.searchPanes);
|
||||
|
||||
|
@ -501,6 +519,36 @@
|
|||
options.dom = options.dom.replace("P", "");
|
||||
}
|
||||
|
||||
var searchBuilder = options.searchBuilder;
|
||||
|
||||
if (searchBuilder) {
|
||||
if (options.dom.indexOf("Q") === -1) {
|
||||
options.dom = "Q" + options.dom;
|
||||
}
|
||||
|
||||
// @see https://datatables.net/extensions/searchbuilder/customConditions.html
|
||||
// @see https://github.com/DataTables/SearchBuilder/blob/master/src/searchBuilder.ts
|
||||
options.searchBuilder = {
|
||||
depthLimit: 1,
|
||||
conditions: {
|
||||
html: {
|
||||
null: null,
|
||||
},
|
||||
string: {
|
||||
null: null,
|
||||
},
|
||||
date: {
|
||||
null: null,
|
||||
},
|
||||
num: {
|
||||
null: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
} else {
|
||||
options.dom = options.dom.replace("Q", "");
|
||||
}
|
||||
|
||||
// add the pagelength at the proper place in the length menu
|
||||
if ($.inArray(options.pageLength, options.lengthMenu) < 0) {
|
||||
options.lengthMenu.push(options.pageLength);
|
||||
|
@ -510,19 +558,20 @@
|
|||
}
|
||||
|
||||
var query = data.query.ask;
|
||||
var printouts = context.data("printouts");
|
||||
var printouts = table.data("printouts");
|
||||
var queryString = query.conditions;
|
||||
var printrequests = context.data("printrequests");
|
||||
var printrequests = table.data("printrequests");
|
||||
var searchPanesOptions = data.searchPanes;
|
||||
|
||||
var searchPanesLog = data.searchPanesLog;
|
||||
|
||||
var displayLog = mw.config.get("performer") === context.data("editor");
|
||||
var displayLog = mw.config.get("performer") === table.data("editor");
|
||||
|
||||
if (displayLog) {
|
||||
console.log("searchPanesLog", searchPanesLog);
|
||||
}
|
||||
|
||||
var entityCollation = context.data("collation");
|
||||
var entityCollation = table.data("collation");
|
||||
|
||||
var columnDefs = [];
|
||||
$.map(printrequests, function (property, index) {
|
||||
|
@ -545,6 +594,10 @@
|
|||
name: printrequests[index].key !== "" ? printouts[index][1] : "",
|
||||
className: "smwtype" + property.typeid,
|
||||
targets: [index],
|
||||
|
||||
// @FIXME https://datatables.net/reference/option/columns.searchBuilderType
|
||||
// implement in the proper way
|
||||
searchBuilderType: "string",
|
||||
},
|
||||
options.columns,
|
||||
data.printoutsParametersOptions[index]
|
||||
|
@ -557,20 +610,10 @@
|
|||
if (searchPanes) {
|
||||
_datatables.initSearchPanesColumns(columnDefs, options);
|
||||
|
||||
// @TODO remove "useAjax" and use the following trick
|
||||
// https://github.com/Knowledge-Wiki/SemanticResultFormats/blob/2230aa3eb8e65dd33ff493ba81269689f50d2945/formats/datatables/resources/ext.srf.formats.datatables.js
|
||||
// to use searchPanesOptions created server-side when Ajax is
|
||||
// not required, unfortunately we cannot use the function
|
||||
// described here https://datatables.net/reference/option/columns.searchPanes.options
|
||||
// with the searchPanes data retrieved server-side, since
|
||||
// we cannot simply provide count, label, and value in the searchPanesOptions
|
||||
// (since is not allowed by the Api) -- however the current solution
|
||||
// works fine in most cases
|
||||
if (
|
||||
// options["searchPanes"]["forceClient"] ||
|
||||
!useAjax ||
|
||||
!Object.keys(searchPanesOptions).length
|
||||
) {
|
||||
// *** this should now be true only if ajax is
|
||||
// disabled and the table has no fields with
|
||||
// multiple values
|
||||
if (!Object.keys(searchPanesOptions).length) {
|
||||
searchPanesOptions = _datatables.getPanesOptions(
|
||||
queryResult,
|
||||
columnDefs,
|
||||
|
@ -586,38 +629,62 @@
|
|||
}
|
||||
}
|
||||
|
||||
// ***important !! this has already
|
||||
// been used for columnDefs initialization !
|
||||
// otherwise the table won't sort !!
|
||||
delete options.columns;
|
||||
|
||||
var conf = $.extend(options, {
|
||||
columnDefs: columnDefs,
|
||||
language: _datatables.oLanguage,
|
||||
order: order,
|
||||
search: {
|
||||
caseInsensitive: context.data("nocase"),
|
||||
caseInsensitive: table.data("nocase"),
|
||||
},
|
||||
initComplete: function () {
|
||||
$(container).find(".datatables-spinner").hide();
|
||||
}
|
||||
});
|
||||
|
||||
// cacheKey ensures that the cached pages
|
||||
// are related to current sorting and searchPanes filters
|
||||
var getCacheKey = function (obj) {
|
||||
return (
|
||||
JSON.stringify(obj.order) +
|
||||
(!searchPanes
|
||||
? ""
|
||||
: JSON.stringify(
|
||||
Object.keys(obj.searchPanes).length
|
||||
? obj.searchPanes
|
||||
: Object.fromEntries(
|
||||
Object.keys(columnDefs).map((x) => [x, {}])
|
||||
)
|
||||
))
|
||||
);
|
||||
// this ensures that the preload key
|
||||
// and the dynamic key match
|
||||
// this does not work: "searchPanes" in obj && Object.entries(obj.searchPanes).find(x => Object.keys(x).length ) ? obj.searchPanes : {},
|
||||
if ("searchPanes" in obj) {
|
||||
for (var i in obj.searchPanes) {
|
||||
if (!Object.keys(obj.searchPanes[i]).length) {
|
||||
delete obj.searchPanes[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return objectHash.sha1({
|
||||
order: obj.order,
|
||||
// search: obj.search,
|
||||
searchPanes:
|
||||
"searchPanes" in obj &&
|
||||
Object.entries(obj.searchPanes).find((x) => Object.keys(x).length)
|
||||
? obj.searchPanes
|
||||
: {},
|
||||
searchBuilder: "searchBuilder" in obj ? obj.searchBuilder : {},
|
||||
});
|
||||
};
|
||||
|
||||
if ((searchPanes || searchBuilder) && table.data("multiple-values")) {
|
||||
useAjax = true;
|
||||
}
|
||||
|
||||
if (!useAjax) {
|
||||
conf.serverSide = false;
|
||||
conf.data = queryResult;
|
||||
|
||||
// use Ajax only when required
|
||||
} else {
|
||||
// prevents double spinner
|
||||
$(container).find(".datatables-spinner").hide();
|
||||
|
||||
var preloadData = {};
|
||||
|
||||
// cache using the column index and sorting
|
||||
|
@ -627,26 +694,21 @@
|
|||
order: order.map((x) => {
|
||||
return { column: x[0], dir: x[1] };
|
||||
}),
|
||||
searchPanes: {},
|
||||
});
|
||||
|
||||
preloadData[cacheKey] = {
|
||||
data: queryResult,
|
||||
count: context.data("count"),
|
||||
count: count,
|
||||
};
|
||||
|
||||
var payload = {
|
||||
action: "ext.srf.datatables.api",
|
||||
format: "json",
|
||||
query: queryString,
|
||||
columndefs: JSON.stringify(columnDefs),
|
||||
printouts: JSON.stringify(printouts),
|
||||
printrequests: JSON.stringify(printrequests),
|
||||
settings: JSON.stringify(
|
||||
$.extend(
|
||||
{ count: context.data("count"), displayLog: displayLog },
|
||||
query.parameters
|
||||
)
|
||||
var payloadData = {
|
||||
queryString,
|
||||
columnDefs,
|
||||
printouts,
|
||||
printrequests,
|
||||
settings: $.extend(
|
||||
{ count: count, displayLog: displayLog },
|
||||
query.parameters
|
||||
),
|
||||
};
|
||||
|
||||
|
@ -658,16 +720,15 @@
|
|||
// instead we use the following hack: the Ajax function returns
|
||||
// the preloaded data as long they are available for the requested
|
||||
// slice, and then it uses an ajax call for not available data.
|
||||
// deferLoading: context.data("count"),
|
||||
|
||||
// deferLoading: table.data("count"),
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
ajax: function (datatableData, callback, settings) {
|
||||
// must match cacheKey
|
||||
var key = getCacheKey(datatableData);
|
||||
// must match initial cacheKey
|
||||
var cacheKey = getCacheKey(datatableData);
|
||||
|
||||
if (!(key in preloadData)) {
|
||||
preloadData[key] = { data: [] };
|
||||
if (!(cacheKey in preloadData)) {
|
||||
preloadData[cacheKey] = { data: [] };
|
||||
}
|
||||
|
||||
// returned cached data for the required
|
||||
|
@ -675,22 +736,21 @@
|
|||
if (
|
||||
datatableData.search.value === "" &&
|
||||
datatableData.start + datatableData.length <=
|
||||
preloadData[key]["data"].length
|
||||
preloadData[cacheKey]["data"].length
|
||||
) {
|
||||
return callback({
|
||||
draw: datatableData.draw,
|
||||
data: preloadData[key]["data"].slice(
|
||||
data: preloadData[cacheKey]["data"].slice(
|
||||
datatableData.start,
|
||||
datatableData.start + datatableData.length
|
||||
),
|
||||
recordsTotal: context.data("count"),
|
||||
recordsFiltered: preloadData[key]["count"],
|
||||
recordsTotal: count,
|
||||
recordsFiltered: preloadData[cacheKey]["count"],
|
||||
searchPanes: {
|
||||
options: searchPanesOptions,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// flush cache each 40,000 rows
|
||||
// *** another method is to compute the actual
|
||||
// size in bytes of each row, but it takes more
|
||||
|
@ -704,53 +764,21 @@
|
|||
}
|
||||
}
|
||||
|
||||
new mw.Api()
|
||||
.post(
|
||||
$.extend(payload, {
|
||||
datatable: JSON.stringify(datatableData),
|
||||
})
|
||||
)
|
||||
.done(function (results) {
|
||||
var json = results["datatables-json"];
|
||||
|
||||
if (displayLog) {
|
||||
console.log("results log", json.log);
|
||||
}
|
||||
|
||||
// cache all retrieved rows for each sorting
|
||||
// dimension (column/dir), up to a fixed
|
||||
// threshold (_cacheLimit)
|
||||
|
||||
if (datatableData.search.value === "") {
|
||||
preloadData[key] = {
|
||||
data: preloadData[key]["data"]
|
||||
.slice(0, datatableData.start)
|
||||
.concat(json.data),
|
||||
count: json.recordsFiltered,
|
||||
};
|
||||
}
|
||||
|
||||
// we retrieve more than "length"
|
||||
// expected by datatables, so return the
|
||||
// sliced result
|
||||
json.data = json.data.slice(0, datatableData.length);
|
||||
json.searchPanes = {
|
||||
options: searchPanesOptions,
|
||||
};
|
||||
callback(json);
|
||||
})
|
||||
.fail(function (error) {
|
||||
console.log("error", error);
|
||||
});
|
||||
_datatables.callApi(
|
||||
$.extend(payloadData, {
|
||||
datatableData,
|
||||
cacheKey,
|
||||
}),
|
||||
callback,
|
||||
preloadData,
|
||||
searchPanesOptions,
|
||||
displayLog
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
// console.log("conf", conf);
|
||||
container.find("table").DataTable(conf);
|
||||
},
|
||||
|
||||
update: function (context, data) {
|
||||
// ...
|
||||
table.DataTable(conf);
|
||||
},
|
||||
|
||||
test: {
|
||||
|
@ -766,13 +794,10 @@
|
|||
var datatables = new srf.formats.datatables();
|
||||
|
||||
$(document).ready(function () {
|
||||
$(".srf-datatable").each(function () {
|
||||
var context = $(this),
|
||||
container = context.find(".datatables-container");
|
||||
|
||||
$(".datatables-container").each(function () {
|
||||
var container = $(this);
|
||||
var data = JSON.parse(_datatables.getData(container));
|
||||
|
||||
datatables.init(context, container, data);
|
||||
datatables.init(container, data);
|
||||
});
|
||||
});
|
||||
})(jQuery, mediaWiki, semanticFormats);
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
mark{background:orange;color:black;}
|
|
@ -0,0 +1,7 @@
|
|||
/*!***************************************************
|
||||
* datatables.mark.js v2.1.0
|
||||
* https://github.com/julmot/datatables.mark.js
|
||||
* Copyright (c) 2016–2020, Julian Kühnel
|
||||
* Released under the MIT license https://git.io/voRZ7
|
||||
*****************************************************/
|
||||
"use strict";var _createClass=function(){function a(t,e){for(var n=0;n<e.length;n++){var a=e[n];a.enumerable=a.enumerable||!1,a.configurable=!0,"value"in a&&(a.writable=!0),Object.defineProperty(t,a.key,a)}}return function(t,e,n){return e&&a(t.prototype,e),n&&a(t,n),t}}(),_typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t};function _classCallCheck(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}!function(e,t,n){var a;"object"===("undefined"==typeof exports?"undefined":_typeof(exports))?(a=require("jquery"),require("datatables.net"),require("mark.js/dist/jquery.mark.js"),module.exports=e(0,n,a)):"function"==typeof define&&define.amd?define(["jquery","datatables.net","markjs"],function(t){return e(0,n,t)}):e(0,n,jQuery)}(function(t,e,i){var r=(_createClass(n,[{key:"initMarkListener",value:function(){var t=this,e="draw.dt.dth column-visibility.dt.dth column-reorder.dt.dth";e+=" responsive-display.dt.dth";var n=null;this.instance.on(e,function(){t.instance.rows({filter:"applied",page:"current"}).nodes().length>t.intervalThreshold?(clearTimeout(n),n=setTimeout(function(){t.mark()},t.intervalMs)):t.mark()}),this.instance.on("destroy",function(){t.instance.off(e)}),this.mark()}},{key:"mark",value:function(){var a=this,r=this.instance.search(),t=i(this.instance.table().body());t.unmark(this.options),this.instance.table().rows({search:"applied"}).data().length&&t.mark(r,this.options),this.instance.columns({search:"applied",page:"current"}).nodes().each(function(t,e){var n=a.instance.column(e).search()||r;n&&t.forEach(function(t){i(t).unmark(a.options).mark(n,a.options)})})}}]),n);function n(t,e){if(_classCallCheck(this,n),!i.fn.mark||!i.fn.unmark)throw new Error("jquery.mark.js is necessary for datatables.mark.js");this.instance=t,this.options="object"===(void 0===e?"undefined":_typeof(e))?e:{},this.intervalThreshold=49,this.intervalMs=300,this.initMarkListener()}i(e).on("init.dt.dth",function(t,e){var n,a;"dt"===t.namespace&&(a=null,(n=i.fn.dataTable.Api(e)).init().mark?a=n.init().mark:i.fn.dataTable.defaults.mark&&(a=i.fn.dataTable.defaults.mark),null!==a&&new r(n,a))})},window,document);
|
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
|
@ -26,7 +26,7 @@
|
|||
"subject": "Test/Datatables/Q.1",
|
||||
"assert-output": {
|
||||
"to-contain": [
|
||||
"<div .*data-collation.*data-datatables.*data-editor.*datatables-container"
|
||||
"<div .*class=\"datatables-container\">.*<table class=\"srf-datatable.*data-collation.*data-printrequests.*>"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
正在加载...
在新工单中引用