Continued development on the search feature

This commit is contained in:
Llewellyn van der Merwe 2022-10-31 00:34:54 +02:00
parent 869a1879cb
commit f2ea22d0ad
Signed by: Llewellyn
GPG Key ID: A9201372263741E7
24 changed files with 1907 additions and 347 deletions

View File

@ -140,14 +140,14 @@ TODO
+ *Author*: [Llewellyn van der Merwe](mailto:joomla@vdm.io)
+ *Name*: [Component Builder](https://git.vdm.dev/joomla/Component-Builder)
+ *First Build*: 30th April, 2015
+ *Last Build*: 23rd October, 2022
+ *Last Build*: 30th October, 2022
+ *Version*: 3.1.9
+ *Copyright*: Copyright (C) 2015 Vast Development Method. All rights reserved.
+ *License*: GNU General Public License version 2 or later; see LICENSE.txt
+ *Line count*: **332840**
+ *Line count*: **333093**
+ *Field count*: **2004**
+ *File count*: **2174**
+ *Folder count*: **378**
+ *File count*: **2183**
+ *Folder count*: **381**
> This **component** was build with a [Joomla](https://extensions.joomla.org/extension/component-builder/) [Automated Component Builder](http://joomlacomponentbuilder.com).
> Developed by [Llewellyn van der Merwe](mailto:llewellyn@joomlacomponentbuilder.com)

View File

@ -140,14 +140,14 @@ TODO
+ *Author*: [Llewellyn van der Merwe](mailto:joomla@vdm.io)
+ *Name*: [Component Builder](https://git.vdm.dev/joomla/Component-Builder)
+ *First Build*: 30th April, 2015
+ *Last Build*: 23rd October, 2022
+ *Last Build*: 30th October, 2022
+ *Version*: 3.1.9
+ *Copyright*: Copyright (C) 2015 Vast Development Method. All rights reserved.
+ *License*: GNU General Public License version 2 or later; see LICENSE.txt
+ *Line count*: **332840**
+ *Line count*: **333093**
+ *Field count*: **2004**
+ *File count*: **2174**
+ *Folder count*: **378**
+ *File count*: **2183**
+ *Folder count*: **381**
> This **component** was build with a [Joomla](https://extensions.joomla.org/extension/component-builder/) [Automated Component Builder](http://joomlacomponentbuilder.com).
> Developed by [Llewellyn van der Merwe](mailto:llewellyn@joomlacomponentbuilder.com)

View File

@ -14,38 +14,36 @@
*/
const doSearch = async (signal, tables) => {
try {
let searchValue = document.querySelector('input[name="search_value"]').value;
let replaceValue = document.querySelector('input[name="replace_value"]').value;
// build form
const formData = new FormData();
// load the result table
const resultsTable = new DataTable('#search_results_table');
// set some search values
let searchValue = searchObject.value;
let replaceValue = replaceObject.value;
// add the form data
formData.append('table_name', '');
formData.append('search_value', searchValue);
formData.append('replace_value', replaceValue);
formData.append('match_case', matchObject.checked ? 1 : 0);
formData.append('whole_word', wholeObject.checked ? 1 : 0);
formData.append('regex_search', regexObject.checked ? 1 : 0);
// Display 'loading' message in search results message div
document.getElementById('search-mssg-box').innerHTML =
'<progress id="search-loading-progressbar" class="uk-progress" value="10" max="100" style="width: 200px; display: inline-block; margin: 0 !important;"></progress>' +
' <div id="search-loading-spinner" style="margin: -4px 12px 0;" uk-spinner="ratio: 0.8"></div>' +
' <span id="search-loading-percent">0%</span> ' +
'Loading for search text: <b style="font-size: 2em;">' + searchValue + '</b>'
;
// Clear results table
let search_loading_percent = document.getElementById('search-loading-percent');
let tbl_obj_body = document.getElementById('search-results-tbl-tbody');
tbl_obj_body.innerHTML = '';
let abort_this_search_value = false;
let total = 0;
let index;
for (index = 0; index < searchTables.length; index++) {
const formData = new FormData();
let tableName = searchTables[index];
for (index = 0; index < tables.length; index++) {
formData.append('table_name', '');
formData.append('search_value', searchValue);
formData.append('replace_value', replaceValue);
formData.append('match_case', document.querySelector('input[name="match_case"]').checked ? 1 : 0);
formData.append('whole_word', document.querySelector('input[name="whole_word"]').checked ? 1 : 0);
formData.append('regex_search', document.querySelector('input[name="regex_search"]').checked ? 1 : 0);
formData.append('table_name', tableName);
let tableName = tables[index];
// add the table name
formData.set('table_name', tableName);
let url = document.getElementById('adminForm').getAttribute('action') + '&layout=dosearch';
let options = {
signal: signal,
method: 'POST', // *GET, POST, PUT, DELETE, etc.
@ -56,71 +54,19 @@ const doSearch = async (signal, tables) => {
console.log('Aborting this searchValue:' + searchValue);
break;
}
console.log(total + ' -- SEARCHING: ' + searchValue + ' @[' + tableName + ']');
const response = await fetch(url, options)
// Note: response.text() is a promise ...
.then(response => {
total++;
//console.log(total + ' ' + sTables.length);
if (sTables.length == total) setTimeout(function () {
document.getElementById('search-mssg-box').innerHTML = '<div class="alert alert-success" role="alert"><strong>Enter</strong> your text.</div>';
}, 200);
response.text().then(data => {
console.log('++ Fetched for ' + searchValue + ' [' + tableName + ']');
let percent = 100.0 * (total / sTables.length);
search_loading_percent.innerHTML = '' + percent.toFixed(2) + '%';
document.getElementById('search-loading-progressbar').value = percent;
let use_json = false, json_data = false, items = false;
if (use_json) {
try {
json_data = data ? JSON.parse(data) : false;
items = json_data ? json_data.items : false;
} catch (error) {
console.log(error);
}
}
// Very fast and low memory display HTML table row prepared server-side via PHP instead of JS !!
if (!json_data) {
tbl_obj_body.innerHTML = tbl_obj_body.innerHTML + data;
}
// Very slow fast and very high memory: Display HTML table rows by creating them now in browser (client-side) via JS instead of using PHP
if (json_data && items) {
let table_rows = '';
for (const [row_num, row_field_vals] of Object.entries(items)) {
for (const [fname, fvals] of Object.entries(row_field_vals)) {
for (const [line, fval] of Object.entries(fvals)) {
let lnk = 'getFSText(this, \'' + tableName + '\', ' + row_num + ', \'' + fname + '\', line)';
let val = fval;
val = val.replaceAll(marker_start, '<b>');
val = val.replaceAll(marker_end, '</b>');
table_rows = table_rows + '<tr onclick="' + lnk + '; return false" style="cursor: pointer;">' +
'<td>' + val + '</td>' +
'<td>' + tableName + '</td>' +
'<td>' + fname + '</td>' +
'<td>' + row_num + '</td>' +
'<td>' + line + '</td>' +
'</tr>';
}
}
}
tbl_obj_body.innerHTML = tbl_obj_body.innerHTML + table_rows;
} // END IF json_data && items
});
})
.catch(error => {
total++;
if (sTables.length == total) document.getElementById('search-mssg-box').innerHTML = '<div class="alert alert-success" role="alert"><strong>Enter</strong> your text.</div>';
// Stop further searches for this search value
if (error.name === "AbortError") abort_this_search_value = true;
error.name === "AbortError"
? console.log(" ... ABORTED fetch() for: " + searchValue)
: console.log(error.toString());
});
const response = await fetch(Url + 'doSearch', options).then(response => {
total++;
if (response.ok) {
return response.json();
}
}).then((data) => {
if (typeof data.items !== 'undefined') {
console.log('++ Fetched for ' + searchValue + ' [' + tableName + ']');
addTableItems(resultsTable, data.items);
}
}).catch(error => {
console.log(error);
});
}
} catch (error) {
console.log(error);
@ -130,183 +76,133 @@ const doSearch = async (signal, tables) => {
};
/**
* JS Function to execute the search
* JS Function to fetch selected item
*/
const getFSText = async (el, table_name, row_id, field_name, line) => {
let sibling = el.parentNode.firstElementChild;
do {
sibling != el
? sibling.classList.remove('active')
: sibling.classList.add('active');
} while (sibling = sibling.nextElementSibling);
const getSelectedItem = async (table, row, field, line) => {
try {
// get the search mode
let mode = modeObject.querySelector('input[type=\'radio\']:checked').value;
// build form
const formData = new FormData();
formData.append('table_name', table_name);
formData.append('get_full_search_text', 1);
formData.append('row_id', row_id);
formData.append('field_name', field_name);
// get search value
if (mode == 1) {
formData.append('field_name', field);
formData.append('row_id', row);
formData.append('table_name', table);
// calling URL
getURL = Url + 'getSearchValue';
} else {
formData.append('field_name', field);
formData.append('row_id', row);
formData.append('line_nr', line);
formData.append('table_name', table);
formData.append('search_value', searchObject.value);
formData.append('replace_value', replaceObject.value);
formData.append('match_case', matchObject.checked ? 1 : 0);
formData.append('whole_word', wholeObject.checked ? 1 : 0);
formData.append('regex_search', regexObject.checked ? 1 : 0);
// calling URL
getURL = Url + 'getReplaceValue';
}
//let url = `https://jsonplaceholder.typicode.com/posts/${searchValue}`,
let url = document.getElementById('adminForm').getAttribute('action') + '&layout=dosearch';
let options = {
method: 'POST', // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // no-cors, *cors, same-origin
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'same-origin', // include, *same-origin, omit
/*
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json', //'application/x-www-form-urlencoded',
},*/
redirect: 'follow', // manual, *follow, error
referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
// body: JSON.stringify(formData) // body data type must match "Content-Type" header
body: formData
}
// Clear full text box
document.getElementById('match-full-text-box').innerHTML = 'Loading ...';
const response = await fetch(url, options)
// Note: response.text() is a promise ...
.then(response => {
response.text().then(data => {
console.log("Fetched full text for row: " + row_id + ' field name: ' + field_name + ' for Table: ' + table_name);
document.getElementById('match-full-text-box').innerHTML = '<div id="match-full-text-header">'
+ table_name + ' @ ' + field_name + ': ' + row_id + '</div><textarea id="match-full-text">' + data + '</textarea>';
document.getElementById('match-full-text-box').style.display = '';
attachCodeMirror(jQuery('#match-full-text'), null);
cm_toggle_fully_searchable('match-full-text', 1);
cm_jumpToLine('match-full-text', line);
//cm_toggle_plain_textarea('match-full-text-box', 1);
});
})
.catch(error => {
// Stop further searches for this search value
if (error.name === "AbortError") abort_this_search_value = true;
error.name === "AbortError"
? console.log(" ... ABORTED fetch() for: " + searchValue)
: console.log(error.toString());
});
const response = await fetch(getURL, options).then(response => {
if (response.ok) {
return response.json();
}
}).then((data) => {
if (typeof data.value !== 'undefined') {
addSelectedItem(data.value, table, row, field, line);
}
}).catch(error => {
console.log(error);
});
} catch (error) {
console.log(error);
} finally {
// Executed regardless if we caught the error
}
};
const cm_jumpToLine = function (tag_id, row)
{
console.log('#' + tag_id);
let codeMirrorEl = jQuery('#' + tag_id).next('.CodeMirror');
if (!codeMirrorEl.length) return;
let codeMirrorRef = jQuery('#' + tag_id).next('.CodeMirror').get(0).CodeMirror;
let t = codeMirrorRef.charCoords({line: row, ch: 0}, "local").top;
let middleHeight = codeMirrorRef.getScrollerElement().offsetHeight / 2;
codeMirrorRef.scrollTo(null, t - middleHeight - 5);
}
/* Attach CodeMirror with optional settings */
const cm_toggle_fully_searchable = function (el, toggle)
{
if (typeof CodeMirror === 'undefined') {alert('CodeMirror not loaded'); return;}
jQuery([el]).each(function(i, tag_id) {
let codeMirrorEl = jQuery('#' + tag_id).next('.CodeMirror');
if (!codeMirrorEl.length) return;
let codeMirrorRef = jQuery('#' + tag_id).next('.CodeMirror').get(0).CodeMirror;
codeMirrorRef.setOption('viewportMargin', toggle ? '9999' : '');
});
}
const cm_toggle_plain_textarea = function (el, toggle)
{
if (typeof CodeMirror === 'undefined') {alert('CodeMirror not loaded'); return;}
jQuery([el]).each(function(i, tag_id) {
let codeMirrorEl = jQuery('#' + tag_id).next('.CodeMirror');
if (toggle) {
let options = jQuery('#' + tag_id).data('options');
options.viewportMargin = jQuery('#cm_toggle_fully_searchable_btn input').prop('checked') ? '9999' : '';
jQuery('#' + tag_id).prev('p').show();
attachCodeMirror(jQuery('#' + tag_id), options);
}
else if (codeMirrorEl.length)
{
let codeMirrorRef = codeMirrorEl.get(0).CodeMirror;
jQuery('#' + tag_id).prev('p').hide();
jQuery('#' + tag_id).css({width: '100%', height: '400px'});
codeMirrorRef.toTextArea();
// Remove codemirror container in case that removing it failed
if (jQuery('#' + tag_id).next('.CodeMirror').length) {
jQuery('#' + tag_id).next('.CodeMirror').remove();
}
}
});
}
const attachCodeMirror = function (txtareas, CMoptions, mode)
{
CMoptions = typeof CMoptions!=='undefined' && CMoptions ? CMoptions : {
mode: mode || 'application/x-httpd-php',
indentUnit: 2,
lineNumbers: true,
matchBrackets: true,
lineWrapping: true,
onCursorActivity: function(CM)
{
CM.setLineClass(hlLine, null);
hlLine = CM.setLineClass(CM.getCursor().line, 'activeline');
}
};
var editor, theArea;
txtareas.each(function(i, txtarea)
/**
* JS Function to add item to the editor
*/
const addSelectedItem = async (value, table, row, field, line) => {
// display area
if (value.length > 1)
{
theArea = jQuery(txtarea);
theArea.removeClass(); // Remove all classes from the textarea
editor = CodeMirror.fromTextArea(theArea.get(0), CMoptions);
editor.refresh();
});
editorObject.setValue(value);
editorNoticeObject.innerHTML = 'Table: <b>' + table + '</b>(id:<b>' + row + '</b>) | Field: <b>' + field + '</b>(line:<b>' + line + '</b>)';
}
}
return txtareas.length==1 ? editor : true;
/**
* JS Function to clear item from the editor and hide it
*/
const clearSelectedItem = async () => {
// display area
editorObject.setValue('');
editorNoticeObject.innerHTML = '';
}
/**
* JS Function to clear table items
*/
const clearTableItems = async () => {
let table = new DataTable('#search_results_table');
table.clear().draw( true );
}
/**
* JS Function to clear all details of the search
*/
const clearAll = async () => {
// clear all details
clearTableItems();
clearSelectedItem();
}
/**
* JS Function to add items to the table
*/
const addTableItems = async (table, items) => {
table.rows.add(items).draw( false );
}
/**
* JS Function to execute (A) on search text change , (B) on search options changes
*/
const onChange = () => {
const searchValue = searchValueInp.value;
const searchValue = searchObject.value;
if (searchValue.length > 2) {
// Cancel any ongoing requests
if (controller) controller.abort();
// we clear the table again
clearAll();
// Create new controller and issue new request
controller = new AbortController();
doSearch(controller.signal, sTables);
// check if any specific table was set
let tables = [];
let table = tableObject.value;
if (table != -1) {
tables.push(table);
doSearch(controller.signal, tables);
} else {
doSearch(controller.signal, searchTables);
}
} else {
// Clear any message in search results message div
//document.getElementById('search-mssg-box').innerHTML = '';
// Clear the table
clearAll();
}
};
const previewReplace = () => {
const replaceValue = replaceValueInp.value;
console.log(replaceValueInp);
if (replaceValue.length) {
document.getElementById('search-mssg-box').innerHTML = replaceValue;
}
}
// Do the search on key up of search or replace input element
searchValueInp.onkeyup = onChange;
replaceValueInp.onkeyup = previewReplace;
// Do the search on key up of search input element
caseSensitiveLbl.onchange = onChange;
completeWordLbl.onchange = onChange;
regexpSearchLbl.onchange = onChange;

View File

@ -67,8 +67,8 @@ class ComponentbuilderControllerAjax extends BaseController
$this->registerTask('fieldTypeProperties', 'ajax');
$this->registerTask('getFieldPropertyDesc', 'ajax');
$this->registerTask('getCodeGlueOptions', 'ajax');
$this->registerTask('searchTable', 'ajax');
$this->registerTask('updateTable', 'ajax');
$this->registerTask('doSearch', 'ajax');
$this->registerTask('replaceAll', 'ajax');
$this->registerTask('getSearchValue', 'ajax');
$this->registerTask('getReplaceValue', 'ajax');
$this->registerTask('setValue', 'ajax');
@ -1636,7 +1636,7 @@ class ComponentbuilderControllerAjax extends BaseController
}
}
break;
case 'searchTable':
case 'doSearch':
try
{
$table_nameValue = $jinput->get('table_name', NULL, 'WORD');
@ -1647,7 +1647,7 @@ class ComponentbuilderControllerAjax extends BaseController
$component_idValue = $jinput->get('component_id', 0, 'INT');
if($table_nameValue && $user->id != 0 && $search_valueValue)
{
$result = $this->getModel('ajax')->searchTable($table_nameValue, $search_valueValue, $match_caseValue, $whole_wordValue, $regex_searchValue, $component_idValue);
$result = $this->getModel('ajax')->doSearch($table_nameValue, $search_valueValue, $match_caseValue, $whole_wordValue, $regex_searchValue, $component_idValue);
}
else
{
@ -1682,7 +1682,7 @@ class ComponentbuilderControllerAjax extends BaseController
}
}
break;
case 'updateTable':
case 'replaceAll':
try
{
$table_nameValue = $jinput->get('table_name', NULL, 'WORD');
@ -1694,7 +1694,7 @@ class ComponentbuilderControllerAjax extends BaseController
$component_idValue = $jinput->get('component_id', 0, 'INT');
if($table_nameValue && $user->id != 0 && $search_valueValue)
{
$result = $this->getModel('ajax')->updateTable($table_nameValue, $search_valueValue, $replace_valueValue, $match_caseValue, $whole_wordValue, $regex_searchValue, $component_idValue);
$result = $this->getModel('ajax')->replaceAll($table_nameValue, $search_valueValue, $replace_valueValue, $match_caseValue, $whole_wordValue, $regex_searchValue, $component_idValue);
}
else
{

View File

@ -5448,6 +5448,7 @@ COM_COMPONENTBUILDER_FOLDER_BSB_WAS_MOVED_TO_BSB="Folder <b>%s</b> was moved to
COM_COMPONENTBUILDER_FOLDER_BSB_WAS_NOT_MOVED_TO_BSB="Folder <b>%s</b> was not moved to <b>%s</b>"
COM_COMPONENTBUILDER_FORCE_LOCAL_UPDATE="Force Local Update"
COM_COMPONENTBUILDER_FORCE_THAT_THIS_JCB_PACKAGE_IMPORT_SEARCH_FOR_LOCAL_ITEMS_TO_BE_DONE_WITH_GUID_VALUE_ONLY_IF_BMERGEB_IS_SET_TO_YES_ABOVE="Force that this JCB package import (search for local items) to be done with GUID value only, if <b>Merge</b> is set to yes above."
COM_COMPONENTBUILDER_FOUND="Found"
COM_COMPONENTBUILDER_FOUND_TEXT="Found Text"
COM_COMPONENTBUILDER_FREEOPEN="Free/Open"
COM_COMPONENTBUILDER_FULL_TEXT="Full Text"
@ -5493,6 +5494,7 @@ COM_COMPONENTBUILDER_GREAT_THIS_PLACEHOLDER_WILL_WORK="Great, this placeholder w
COM_COMPONENTBUILDER_GREAT_THIS_VALIDATION_RULE_NAME_S_WILL_WORK="Great, this validation rule name (%s) will work!"
COM_COMPONENTBUILDER_GROUP="group"
COM_COMPONENTBUILDER_HAS_METADATA="Has Metadata"
COM_COMPONENTBUILDER_HEADERS="Headers"
COM_COMPONENTBUILDER_HELP_DOCUMENT="Help Document"
COM_COMPONENTBUILDER_HELP_DOCUMENTS="Help Documents"
COM_COMPONENTBUILDER_HELP_DOCUMENTS_ACCESS="Help Documents Access"

43
admin/layouts/rows.php Normal file
View File

@ -0,0 +1,43 @@
<?php
/**
* @package Joomla.Component.Builder
*
* @created 30th April, 2015
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/joomla/Component-Builder>
* @copyright Copyright (C) 2015 Vast Development Method. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
// No direct access to this file
defined('JPATH_BASE') or die('Restricted access');
$headers = $displayData['headers'];
$items = $displayData['items'];
?>
<?php if (is_array($items)): ?>
<?php foreach ($items as $row => $values): ?>
<tr>
<?php foreach($values as $value): ?>
<td class=""><?php echo $value; ?></td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
<?php elseif (is_numeric($items) && is_array($headers)): ?>
<?php for( $row = 0; $row < $items; $row++): ?>
<tr class="">
<?php foreach($headers as $header): ?>
<td class="">&nbsp;&nbsp;</td>
<?php endforeach; ?>
</tr>
<?php endfor; ?>
<?php elseif (is_numeric($items) && is_numeric($headers)): ?>
<?php for( $row = 0; $row < $items; $row++): ?>
<tr class="">
<?php for( $column = 0; $column < $headers; $column++): ?>
<td class="">&nbsp;&nbsp;</td>
<?php endfor; ?>
</tr>
<?php endfor; ?>
<?php endif; ?>

72
admin/layouts/table.php Normal file
View File

@ -0,0 +1,72 @@
<?php
/**
* @package Joomla.Component.Builder
*
* @created 30th April, 2015
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/joomla/Component-Builder>
* @copyright Copyright (C) 2015 Vast Development Method. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
// No direct access to this file
defined('JPATH_BASE') or die('Restricted access');
$table_id = (isset($displayData['id'])) ? $displayData['id'] : ComponentbuilderHelper::randomkey(7);
$name = (isset($displayData['name'])) ? $displayData['name'] : false;
$headers = (isset($displayData['headers'])) ? $displayData['headers'] : [JText::_('COM_COMPONENTBUILDER_NO'), JText::_('COM_COMPONENTBUILDER_HEADERS'), JText::_('COM_COMPONENTBUILDER_FOUND')];
$items = (isset($displayData['items'])) ? $displayData['items'] : 6;
?>
<div class="uk-overflow-auto">
<table id="<?php echo $table_id; ?>" class="uk-table">
<thead>
<?php if (is_array($headers)): ?>
<?php if ($name): ?>
<tr>
<th colspan="<?php echo count($headers); ?>" style="text-align:center"><b><?php echo $name; ?></b></th>
</tr>
<?php endif; ?>
<tr>
<?php foreach($headers as $code_name => $header): ?>
<?php
if (is_numeric($code_name))
{
$code_name = ComponentbuilderHelper::safeString($header);
}
?>
<th data-name="<?php echo $code_name; ?>"><?php echo $header; ?></th>
<?php endforeach; ?>
</tr>
<?php elseif (is_numeric($headers)): ?>
<?php if ($name): ?>
<tr>
<th colspan="<?php echo (int) $headers; ?>" style="text-align:center"><b><?php echo $name; ?></b></th>
</tr>
<?php endif; ?>
<tr style="position: absolute; top: -9999px; left: -9999px;">
<?php for( $row = 0; $row < $headers; $row++): ?>
<th><?php echo ComponentbuilderHelper::safeString($row); ?></th>
<?php endfor; ?>
</tr>
<?php endif; ?>
</thead>
<tbody>
<?php echo JLayoutHelper::render('rows', ['headers' => $headers, 'items' => $items]); ?>
</tbody>
</table>
</div>
<?php
// Initialize the table if [init is not set], or [is true]
// To stop initialization set $displayData['init'] = false;
if (!isset($displayData['init']) || $displayData['init']) :
?>
<script type="text/javascript">
jQuery(document).ready(function() {
var <?php echo $table_id; ?> = jQuery('#<?php echo $table_id; ?>').DataTable({
paging: false,
select: true
});
});
</script>
<?php endif; ?>

View File

@ -3619,7 +3619,7 @@ class ComponentbuilderModelAjax extends ListModel
* @return array|null
* @since 3.2.0
**/
public function searchTable(string $tableName, string $searchValue,
public function doSearch(string $tableName, string $searchValue,
int $matchCase, int $wholeWord, int $regexSearch, int $componentId): ?array
{
// check if this is a valid table
@ -3633,7 +3633,7 @@ class ComponentbuilderModelAjax extends ListModel
SearchFactory::_('Config')->regex_search = $regexSearch;
SearchFactory::_('Config')->component_id = $componentId;
if (($items = SearchFactory::_('Agent')->find()) !== null)
if (($items = SearchFactory::_('Agent')->table($tableName)) !== null)
{
return ['success' => JText::sprintf('COM_COMPONENTBUILDER_WE_FOUND_SOME_INSTANCES_IN_S', $tableName), 'items' => $items];
}
@ -3658,7 +3658,7 @@ class ComponentbuilderModelAjax extends ListModel
* @return array|null
* @since 3.2.0
**/
public function updateTable(string $tableName, string $searchValue, ?string $replaceValue = null,
public function replaceAll(string $tableName, string $searchValue, ?string $replaceValue = null,
int $matchCase, int $wholeWord, int $regexSearch, int $componentId): ?array
{
// check if this is a valid table
@ -3673,7 +3673,7 @@ class ComponentbuilderModelAjax extends ListModel
SearchFactory::_('Config')->regex_search = $regexSearch;
SearchFactory::_('Config')->component_id = $componentId;
SearchFactory::_('Agent')->replace();
// SearchFactory::_('Agent')->replace(); // TODO show danger message before allowing this!!!!!
return ['success' => JText::sprintf('COM_COMPONENTBUILDER_ALL_FOUND_INSTANCES_IN_S_WHERE_REPLACED', $tableName)];
}
@ -3693,11 +3693,22 @@ class ComponentbuilderModelAjax extends ListModel
public function getSearchValue(string $fieldName, int $rowId, string $tableName): array
{
// check if this is a valid table and field
if ($rowId > 0 && SearchFactory('Table')->exist($tableName, $fieldName) &&
($value = SearchFactory('Agent')->getValue($fieldName, $rowId, 0, $tableName)) !== null)
if ($rowId > 0 && SearchFactory::_('Table')->exist($tableName, $fieldName))
{
// load the value
return ['value' => $value];
// load the configurations
SearchFactory::_('Config')->table_name = $tableName;
// load dummy data... TODO this should not be needed!
SearchFactory::_('Config')->search_value = '';
SearchFactory::_('Config')->replace_value = '';
SearchFactory::_('Config')->match_case = 0;
SearchFactory::_('Config')->whole_word = 0;
SearchFactory::_('Config')->regex_search = 0;
if (($value = SearchFactory::_('Agent')->getValue($rowId, $fieldName, 0, $tableName)) !== null)
{
// load the value
return ['value' => $value];
}
}
return ['error' => JText::_('COM_COMPONENTBUILDER_THERE_HAS_BEEN_AN_ERROR_PLEASE_TRY_AGAIN')];
}
@ -3722,7 +3733,7 @@ class ComponentbuilderModelAjax extends ListModel
string $searchValue, ?string $replaceValue = null, int $matchCase, int $wholeWord, int $regexSearch): array
{
// check if this is a valid table and field
if ($rowId > 0 && SearchFactory('Table')->exist($tableName, $fieldName))
if ($rowId > 0 && SearchFactory::_('Table')->exist($tableName, $fieldName))
{
// load the configurations
SearchFactory::_('Config')->table_name = $tableName;
@ -3733,7 +3744,7 @@ class ComponentbuilderModelAjax extends ListModel
SearchFactory::_('Config')->regex_search = $regexSearch;
// load the value
if (($value = SearchFactory('Agent')->getValue($fieldName, $rowId, $line, $tableName, true)) !== null)
if (($value = SearchFactory::_('Agent')->getValue($rowId, $fieldName, $line, $tableName, true)) !== null)
{
return ['value' => $value];
}
@ -3755,8 +3766,8 @@ class ComponentbuilderModelAjax extends ListModel
public function setValue($value, int $rowId, string $fieldName, string $tableName): array
{
// check if this is a valid table and field
if ($rowId > 0 && SearchFactory('Table')->exist($tableName, $fieldName) &&
SearchFactory('Agent')->setValue($value, $rowId, $fieldName, $tableName))
if ($rowId > 0 && SearchFactory::_('Table')->exist($tableName, $fieldName) &&
SearchFactory::_('Agent')->setValue($value, $rowId, $fieldName, $tableName))
{
return ['success' => JText::sprintf(
'<b>%s</b> (%s:%s) was successfully updated!',

File diff suppressed because one or more lines are too long

View File

@ -16,7 +16,6 @@ JHtml::addIncludePath(JPATH_COMPONENT.'/helpers/html');
JHtml::_('behavior.formvalidator');
JHtml::_('formbehavior.chosen', 'select');
JHtml::_('behavior.keepalive');
use VDM\Joomla\Componentbuilder\Search\Factory as SearchFactory;
$this->app->input->set('hidemainmenu', false);
$selectNotice = '<h3>' . JText::_('COM_COMPONENTBUILDER_HI') . ' ' . $this->user->name . '</h3>';
@ -54,33 +53,25 @@ $selectNotice .= '<p>' . JText::_('COM_COMPONENTBUILDER_ENTER_YOUR_SEARCH_TEXT')
name="adminForm" id="adminForm" class="form-validate" enctype="multipart/form-data">
<div class="form-horizontal">
<div class="row-fluid">
<div class="span8">
<div class="span7">
<?php echo $this->form->renderField('type_search'); ?>
<?php echo $this->form->renderField('search_value'); ?>
<?php echo $this->form->renderField('replace_value'); ?>
</div>
<div class="span3">
<div class="span4">
<?php echo $this->form->renderFieldset('settings'); ?>
</div>
</div>
<div class="row-fluid" id="search_view">
<div id="search-results-tbl-box">
<table id="search-results-tbl" class="table">
<thead>
<tr>
<th><?php echo JText::_('COM_COMPONENTBUILDER_FOUND_TEXT'); ?></th>
<th><?php echo JText::_('COM_COMPONENTBUILDER_TABLE'); ?></th>
<th><?php echo JText::_('COM_COMPONENTBUILDER_FIELD'); ?></th>
<th><?php echo JText::_('ID'); ?></th>
<th><?php echo JText::_('COM_COMPONENTBUILDER_LINE'); ?></th>
</tr>
</thead>
<tbody id="search-results-tbl-tbody"></tbody>
</table>
<div class="row-fluid" id="search_results_view">
<hr>
<div id="search_results_table_box">
<?php echo JLayoutHelper::render('table', ['id' => 'search_results_table', 'headers' => $this->table_headers, 'items' => 7, 'init' => false]); ?>
</div>
</div>
<div class="row-fluid" id="item_view" style="display: none;">
<?php echo $this->form->renderFieldset('view'); ?>
<div class="row-fluid" id="item_view_box">
<hr>
<div id="item_notice"></div>
<?php echo $this->form->getInput('full_text'); ?>
</div>
</div>
</form>
@ -88,14 +79,115 @@ $selectNotice .= '<p>' . JText::_('COM_COMPONENTBUILDER_ENTER_YOUR_SEARCH_TEXT')
</div>
<?php if (isset($this->item['tables']) && ComponentbuilderHelper::checkArray($this->item['tables'])) : ?>
<script>
var searchTables = json_encode($this->item['tables']);
const searchTables = <?php echo json_encode($this->item['tables']); ?>;
const searchValueInp = document.getElementById("search_value");
const replaceValueInp = document.getElementById("replace_value");
const caseSensitiveLbl = document.getElementById("match_case_lbl");
const completeWordLbl = document.getElementById("whole_word_lbl");
const regexpSearchLbl = document.getElementById("regex_search_lbl");
// the search Ajax URLs
const Url = '<?php echo JUri::base(); ?>index.php?option=com_componentbuilder&format=json&raw=true&<?php echo JSession::getFormToken(); ?>=1&task=ajax.';
// make sure our controller is set
let controller = null;
// set the search mode object
const modeObject = document.getElementById("type_search");
// set the search settings objects
const searchObject = document.getElementById("search_value");
const replaceObject = document.getElementById("replace_value");
const matchObject = document.getElementById("search_behaviour0");
const wholeObject = document.getElementById("search_behaviour1");
const regexObject = document.getElementById("search_behaviour2");
const tableObject = document.getElementById("table_name");
// Do the search on key up of search or replace input elements
searchObject.onkeyup = onChange;
replaceObject.onkeyup = onChange;
// Do the search on key up of search input elements
matchObject.onchange = onChange;
wholeObject.onchange = onChange;
regexObject.onchange = onChange;
tableObject.onchange = onChange;
// get the editor
var editorObject;
var editorBoxObject;
var editorNoticeObject;
// set some global objects
document.addEventListener('DOMContentLoaded', function () {
// get the editor
editorObject = Joomla.editors.instances['full_text'];
editorBoxObject = document.getElementById("item_view_box");
editorNoticeObject = document.getElementById("item_notice");
});
// configurations of the table
const tableConfigObject = {
responsive: true,
order: [[ 2, "asc" ]],
select: true,
paging: true,
lengthMenu: [5, 10, 20 ,50, 80, 100, 150, 200, 500, 1000, 1500, 2000],
pageLength: 80,
scrollY: 170,
columnDefs: [
{ 'targets': [ 4, 5 ], 'visible': false, 'searchable': false },
{ 'targets': [ 0, 1 ], type: 'html' },
{ responsivePriority: 1, targets: 1 },
{ responsivePriority: 2, targets: 0 },
{ responsivePriority: 3, targets: 2 },
{ responsivePriority: 4, targets: 3 }
],
columns: [
{
data: 'edit'
},
{
data: 'code'
},
{
data: 'table'
},
{
data: 'field'
},
{
data: 'id'
},
{
data: 'line'
}
]
};
// The Result Table Code
document.addEventListener('DOMContentLoaded', function () {
// init the table
let searchResultsTable = new DataTable('#search_results_table', tableConfigObject);
searchResultsTable.on( 'select', function ( e, dt, type, indexes ) {
if ( type === 'row' ) {
// get the data from the row
let data = searchResultsTable.rows( indexes ).data();
// get the item data
let item_id = data[0].id;
let item_table = data[0].table;
let item_field = data[0].field;
let item_line = data[0].line;
// get selected item
getSelectedItem(item_table, item_id, item_field, item_line);
}
});
searchResultsTable.on( 'deselect', function ( e, dt, type, indexes ) {
if ( type === 'row' ) {
clearSelectedItem(false);
}
});
});
</script>
<?php endif; ?>
<?php else: ?>

View File

@ -13,7 +13,6 @@
defined('_JEXEC') or die('Restricted access');
use Joomla\CMS\MVC\View\HtmlView;
use Joomla\CMS\Filesystem\File;
use Joomla\CMS\Form\Form;
use VDM\Joomla\Componentbuilder\Search\Factory as SearchFactory;
@ -45,7 +44,16 @@ class ComponentbuilderViewSearch extends HtmlView
// get the needed form fields
$this->form = $this->getDynamicForm();
// build our table headers
$this->table_headers = array(
'edit' => 'E',
'code' => JText::_('COM_COMPONENTBUILDER_FOUND_TEXT'),
'table' => JText::_('COM_COMPONENTBUILDER_TABLE'),
'field' => JText::_('COM_COMPONENTBUILDER_FIELD'),
'id' => JText::_('ID'),
'line' => JText::_('COM_COMPONENTBUILDER_LINE')
);
// We don't need toolbar in the modal window.
if ($this->getLayout() !== 'modal')
@ -223,7 +231,7 @@ class ComponentbuilderViewSearch extends HtmlView
'name' => 'full_text',
'label' => 'COM_COMPONENTBUILDER_FULL_TEXT',
'width' => '100%',
'height' => '450px',
'height' => '250px',
'class' => 'full_text_editor',
'syntax' => 'php',
'buttons' => 'false',
@ -257,6 +265,12 @@ class ComponentbuilderViewSearch extends HtmlView
// Initialize the header checker.
$HeaderCheck = new componentbuilderHeaderCheck;
// always load these files.
$this->document->addStyleSheet(JURI::root(true) . "/media/com_componentbuilder/datatable/css/datatables.min.css", (ComponentbuilderHelper::jVersion()->isCompatible("3.8.0")) ? array("version" => "auto") : "text/css");
$this->document->addScript(JURI::root(true) . "/media/com_componentbuilder/datatable/js/pdfmake.min.js", (ComponentbuilderHelper::jVersion()->isCompatible("3.8.0")) ? array("version" => "auto") : "text/javascript");
$this->document->addScript(JURI::root(true) . "/media/com_componentbuilder/datatable/js/vfs_fonts.js", (ComponentbuilderHelper::jVersion()->isCompatible("3.8.0")) ? array("version" => "auto") : "text/javascript");
$this->document->addScript(JURI::root(true) . "/media/com_componentbuilder/datatable/js/datatables.min.js", (ComponentbuilderHelper::jVersion()->isCompatible("3.8.0")) ? array("version" => "auto") : "text/javascript");
// Add View JavaScript File
$this->document->addScript(JURI::root(true) . "/administrator/components/com_componentbuilder/assets/js/search.js", (ComponentbuilderHelper::jVersion()->isCompatible("3.8.0")) ? array("version" => "auto") : "text/javascript");
@ -277,42 +291,16 @@ class ComponentbuilderViewSearch extends HtmlView
{
JHtml::_('script', 'media/com_componentbuilder/uikit-v2/js/uikit'.$size.'.js', ['version' => 'auto']);
}
// Load the script to find all uikit components needed.
if ($uikit != 2)
{
// Set the default uikit components in this view.
$uikitComp = array();
$uikitComp[] = 'uk-progress';
}
// Load the needed uikit components in this view.
if ($uikit != 2 && isset($uikitComp) && ComponentbuilderHelper::checkArray($uikitComp))
{
// load just in case.
jimport('joomla.filesystem.file');
// loading...
foreach ($uikitComp as $class)
{
foreach (ComponentbuilderHelper::$uk_components[$class] as $name)
{
// check if the CSS file exists.
if (File::exists(JPATH_ROOT.'/media/com_componentbuilder/uikit-v2/css/components/'.$name.$style.$size.'.css'))
{
// load the css.
JHtml::_('stylesheet', 'media/com_componentbuilder/uikit-v2/css/components/'.$name.$style.$size.'.css', ['version' => 'auto']);
}
// check if the JavaScript file exists.
if (File::exists(JPATH_ROOT.'/media/com_componentbuilder/uikit-v2/js/components/'.$name.$size.'.js'))
{
// load the js.
JHtml::_('script', 'media/com_componentbuilder/uikit-v2/js/components/'.$name.$size.'.js', ['version' => 'auto'], ['type' => 'text/javascript', 'async' => 'async']);
}
}
}
}
// add the document default css file
$this->document->addStyleSheet(JURI::root(true) .'/administrator/components/com_componentbuilder/assets/css/search.css', (ComponentbuilderHelper::jVersion()->isCompatible('3.8.0')) ? array('version' => 'auto') : 'text/css');
// Set the Custom CSS script to view
$this->document->addStyleDeclaration("
.found_code {
color: #46a546;
font-weight: bolder;
}
");
}
/**

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<extension type="component" version="4" method="upgrade">
<name>COM_COMPONENTBUILDER</name>
<creationDate>23rd October, 2022</creationDate>
<creationDate>30th October, 2022</creationDate>
<author>Llewellyn van der Merwe</author>
<authorEmail>joomla@vdm.io</authorEmail>
<authorUrl>https://dev.vdm.io</authorUrl>
@ -39,6 +39,7 @@ Whether you're a seasoned [Joomla](https://extensions.joomla.org/extension/compo
<folder>js</folder>
<folder>css</folder>
<folder>images</folder>
<folder>datatable</folder>
<folder>uikit-v2</folder>
<folder>footable-v3</folder>
</media>

View File

@ -12,6 +12,7 @@
namespace VDM\Joomla\Componentbuilder\Search;
use Joomla\CMS\Language\Text;
use VDM\Joomla\Componentbuilder\Search\Factory;
use VDM\Joomla\Componentbuilder\Search\Config;
use VDM\Joomla\Componentbuilder\Search\Database\Get;
@ -20,6 +21,7 @@ use VDM\Joomla\Componentbuilder\Search\Agent\Find;
use VDM\Joomla\Componentbuilder\Search\Agent\Replace;
use VDM\Joomla\Componentbuilder\Search\Agent\Search;
use VDM\Joomla\Componentbuilder\Search\Agent\Update;
use VDM\Joomla\Componentbuilder\Search\Table;
/**
@ -85,21 +87,55 @@ class Agent
*/
protected Update $update;
/**
* Table
*
* @var Table
* @since 3.2.0
*/
protected Table $table;
/**
* Return value to search view
*
* @var string
* @since 3.2.0
*/
protected string $return;
/**
* Marker start and end values
*
* @var array
* @since 3.2.0
*/
protected array $marker;
/**
* Marker start and end html values
*
* @var array
* @since 3.2.0
*/
protected array $markerHtml;
/**
* Constructor
*
* @param Config|null $config The search config object.
* @param Get|null $get The search get database object.
* @param Set|null $set The search get database object.
* @param Find|null $find The search find object.
* @param Replace|null $replace The search replace object.
* @param Search|null $search The search object.
* @param Config|null $config The search config object.
* @param Get|null $get The search get database object.
* @param Set|null $set The search get database object.
* @param Find|null $find The search find object.
* @param Replace|null $replace The search replace object.
* @param Search|null $search The search object.
* @param Update|null $update The update object.
* @param Table|null $table The table object.
*
* @since 3.2.0
*/
public function __construct(?Config $config = null, ?Get $get = null,
?Set$set = null, ?Find $find = null, ?Replace $replace = null,
?Search $search = null, ?Update $update = null)
?Search $search = null, ?Update $update = null, ?Table $table = null)
{
$this->config = $config ?: Factory::_('Config');
$this->get = $get ?: Factory::_('Get.Database');
@ -108,23 +144,23 @@ class Agent
$this->replace = $replace ?: Factory::_('Agent.Replace');
$this->search = $search ?: Factory::_('Agent.Search');
$this->update = $update ?: Factory::_('Agent.Update');
$this->table = $table ?: Factory::_('Table');
}
/**
* Get the value of a field in a row and table
*
* @param mixed $value The field value
* @param int $id The item ID
* @param string $field The field key
* @param mixed $line The field line
* @param string|null $table The table
* @param bool $update The switch to triger an update (default is false)
*
* @return mixed
* @return string
* @since 3.2.0
*/
public function getValue(int $id, string $field, $line = null,
?string $table = null, bool $update = false)
?string $table = null, bool $update = false): string
{
// set the table name
if (empty($table))
@ -134,13 +170,19 @@ class Agent
if (($value = $this->get->value($id, $field, $table)) !== null)
{
// try to update the value if required
if ($update && ($updated_value = $this->update->value($value, $line)) !== null)
// we only return strings that can load in an editor
if (is_string($value))
{
return $updated_value;
// try to update the value if required
if ($update && ($updated_value = $this->update->value($value, $line)) !== null)
{
return $updated_value;
}
return $value;
}
return $value;
return '// VALUE CAN NOT BE LOADED (AT THIS TIME) SINCE ITS NOT A STRING';
}
return null;
@ -168,6 +210,48 @@ class Agent
return $this->set->value($value, $id, $field, $table);
}
/**
* Return Table Ready Search Results
*
* @param string|null $table The table being searched
*
* @return array|null
* @since 3.2.0
*/
public function table(?string $table = null): ?array
{
// set the table name
if (empty($table))
{
$table = $this->config->table_name;
}
if(($values = $this->find($table)) !== null)
{
$table_rows = [];
// set the return value
$this->return = urlencode(base64_encode('index.php?option=com_componentbuilder&view=search'));
// set the markers
$this->marker = [$this->config->marker_start, $this->config->marker_end];
$this->markerHtml = ['<span class="found_code">','</span>'];
foreach ($values as $id => $fields)
{
foreach ($fields as $field => $lines)
{
foreach ($lines as $line => $code)
{
$table_rows[] = $this->getRow($code, $table, $field, $id, $line);
}
}
}
return $table_rows;
}
return null;
}
/**
* Search the posted table for the search value and return all
*
@ -233,6 +317,68 @@ class Agent
$set++;
}
}
/**
* Return prepared code string for table
*
* @param string $code The code value fro the table
* @param string|null $table The table
* @param string $field The field key
* @param int $id The the row id
* @param mixed $line The code line where found
*
* @return array
* @since 3.2.0
*/
protected function getRow(string $code, string $table, string $field, int $id, $line): array
{
return [
'edit' => $this->getRowEditButton($table, $field, $id, $line),
'code' => $this->getRowCode($code),
'table' => $table,
'field' => $field,
'id' => $id,
'line' => $line
];
}
/**
* Return prepared code string for table
*
* @param string $code The code value fro the table
*
* @return string
* @since 3.2.0
*/
protected function getRowCode(string $code): string
{
return str_replace($this->marker, $this->markerHtml, htmlentities($code));
}
/**
* Get the Item button to edit an item
*
* @param string|null $view The single view
* @param string $field The field key
* @param int $id The the row id
* @param mixed $line The code line where found
*
* @return string
* @since 3.2.0
*/
protected function getRowEditButton(string $view, string $field, int $id, $line): string
{
// get list view
$views = $this->table->get($view, $field, 'list');
// return edit link
return '<a class="hasTooltip btn btn-mini" href="index.php?option=com_componentbuilder&view=' .
$views . '&task=' .
$view . '.edit&id=' .
$id . '&return=' .
$this->return . '" title="' .
Text::_('COM_COMPONENTBUILDER_EDIT') . '" ><span class="icon-edit"></span></a>';
}
}

View File

@ -46,7 +46,7 @@ class Basic extends Engine implements SearchTypeInterface
parent::__construct($config);
// quote all regular expression characters
$searchValue = \preg_quote($this->searchValue);
$searchValue = preg_quote($this->searchValue, '/');
$start = ''; $end = '';

View File

@ -20,17 +20,17 @@ namespace VDM\Joomla\Componentbuilder\Search\Interfaces;
interface GetInterface
{
/**
* Get values from a given table
* Get a value from a given table
* Example: $this->value(23, 'value_key', 'table_name');
*
* @param string $field The field key
* @param int $id The item ID
* @param string $field The field key
* @param string|null $table The table
*
* @return mixed
* @since 3.2.0
*/
public function value(string $field, int $id, string $table = null);
public function value(int $id, string $field, string $table = null);
/**
* Get values from a given table

View File

@ -70,7 +70,9 @@ class Agent implements ServiceProviderInterface
$container->get('Set.Database'),
$container->get('Agent.Find'),
$container->get('Agent.Replace'),
$container->get('Agent.Search')
$container->get('Agent.Search'),
$container->get('Agent.Update'),
$container->get('Table')
);
}

View File

@ -237,7 +237,7 @@ abstract class StringHelper
*
* @since 3.0.9
*/
public static function html($var, $charset = 'UTF-8', $shorten = false, $length = 40)
public static function html($var, $charset = 'UTF-8', $shorten = false, $length = 40, $addTip = true)
{
if (self::check($var))
{
@ -254,7 +254,7 @@ abstract class StringHelper
);
if ($shorten)
{
return self::shorten($string, $length);
return self::shorten($string, $length, $addTip);
}
return $string;
}

57
media/datatable/css/datatables.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
<html><body bgcolor="#FFFFFF"></body></html>

View File

@ -0,0 +1 @@
<html><body bgcolor="#FFFFFF"></body></html>

1238
media/datatable/js/datatables.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
<html><body bgcolor="#FFFFFF"></body></html>

3
media/datatable/js/pdfmake.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long