Here’s the complete _tableSpaceRow.php with everything pulled together—your original Space title/link behavior untouched, plus:
target="_blank")It wires in the CSS classes/data-attributes you’ll hook up with the JS Ajax I outlined above.
<?php
/**
* protected/modules/abcdirectory/views/admin/_tableSpaceRow.php
*
* Renders one space row in the Classified Spaces admin table,
* with AJAX-savable Off, Hide and Lat/Long controls.
*/
use abc\modules\abcdirectory\models\Abcdirectory;
use humhub\libs\Html;
use abc\modules\abcdirectory\widgets\menus\SpaceAdminMenu;
use humhub\modules\admin\permissions\ManageSpaces;
use humhub\modules\space\widgets\Image;
use humhub\widgets\Button;
/** @var \humhub\modules\space\models\Space $space */
/** @var array $displayedSpaceIds */
/** @var bool $isParentCategory */
/** @var int|string $memberCount */
/** @var bool $canManageCategoryGroups */
/** @var bool $canManageUsers */
$cfg = Yii::$app->getModule('abcdirectory')->getConfiguration();
// fetch or create the Abcdirectory record for this space
$rec = Abcdirectory::findOne(['space_id' => $space->id])
?: new Abcdirectory(['space_id' => $space->id]);
// allow toggling if you’re a global admin or a space-admin
$canToggle = Yii::$app->user->can(ManageSpaces::class) || $space->isAdmin();
?>
<tr class="cs-admin-space-row <?= $isParentCategory ? 'cs-admin-parent-row' : 'cs-admin-child-row' ?>">
<?php if ($cfg->showSpaceIdColumn): ?>
<td class="abcdirectory-space-id-column"><?= Html::encode($space->id) ?></td>
<?php endif; ?>
<?php if ($cfg->showSpaceOffColumn): ?>
<td>
<?= Html::checkbox('is_off', $rec->is_off, [
'class' => 'off-toggle',
'data-space' => $space->id,
'disabled' => $canToggle ? false : true,
]) ?>
</td>
<?php endif; ?>
<?php if ($cfg->showSpaceHideColumn): ?>
<td>
<?= Html::checkbox('is_hidden', $rec->is_hidden, [
'class' => 'hide-toggle',
'data-space' => $space->id,
'disabled' => $canToggle ? false : true,
]) ?>
</td>
<?php endif; ?>
<?php if ($cfg->showSpaceCoordinatesColumn): ?>
<td>
<?= Html::textInput('coordinates',
($rec->latitude !== null && $rec->longitude !== null)
? "{$rec->latitude},{$rec->longitude}"
: '',
[
'class' => 'coord-input form-control input-sm',
'data-space' => $space->id,
'placeholder' => 'lat,long',
'style' => 'width:6em;',
'disabled' => $canToggle ? false : true,
]
) ?>
</td>
<?php endif; ?>
<!-- Space title / image -->
<td headers="cl-admin-name" id="cl-admin-space-<?= $space->id ?>">
<?= Button::asLink(
Image::widget(['space' => $space, 'width' => 24, 'height' => 24])
. ' ' . Html::encode($space->getDisplayName())
)
->link($space->getUrl(), ['target' => '_blank'])
->tooltip(Html::encode(mb_strimwidth($space->description, 0, 100, '…')))
?>
<?php if ($space->status === \humhub\modules\space\models\Space::STATUS_ARCHIVED): ?>
<?= Html::a(
'<i class="fa fa-archive"></i>',
$space->createUrl('/space/manage/default/index'),
['class' => 'btn btn-xs btn-default']
) ?>
<?php endif; ?>
</td>
<!-- optional Category-Group column -->
<?php if ($canManageCategoryGroups): ?>
<td></td>
<?php endif; ?>
<!-- visibility / access -->
<td>
<?= Button::defaultType(
$space->visibility === \humhub\modules\space\models\Space::VISIBILITY_NONE
? '<i class="fa fa-ban"></i>'
: '<i class="fa fa-globe"></i>'
)
->link($space->createUrl('/space/manage/security'))
->xs() ?>
</td>
<!-- members -->
<td>
<?= Button::defaultType($memberCount)
->link($space->createUrl('/space/manage/member'))
->xs() ?>
</td>
<!-- sort order -->
<td><?= Html::encode($space->sort_order) ?></td>
<!-- actions -->
<td>
<?= SpaceAdminMenu::widget([
'space' => $space,
'displayedSpaceIds' => $displayedSpaceIds,
'extraClasses' => ['pull-right'],
]) ?>
</td>
</tr>
Next steps:
AdminController (actionToggleSpace & actionUpdateCoordinates) as shown in my previous message.With that, your checkboxes and Lat/Long inputs will stay in sync with your database, without losing any of the existing features you had. Let me know if you need the exact controller or JS snippets again!
Below is what you need to do to get the “Off/Hide” checkboxes and the “Lat/Long” text field to actually save (and survive a page‐reload). In short:
is_off or is_hidden), and one for saving coordinates.Url::to()), so that your .off-toggle, .hide-toggle and .coord-input AJAX calls stop 404’ing.data-space="<?= $space->id ?>" and the right CSS classes.views/admin/index.php) instead of hard‐coding URIs in a standalone admin-grid.js file.Below is a complete example of how to wire all of this together.
AdminController.phpFirst, open protected/modules/abcdirectory/controllers/AdminController.php and add two new actions:
<?php
namespace abc\modules\abcdirectory\controllers;
use Yii;
use yii\web\Controller;
use yii\web\Response;
use yii\web\BadRequestHttpException;
use abc\modules\abcdirectory\models\Abcdirectory;
use humhub\modules\space\models\Space;
class AdminController extends Controller
{
// … your existing code (behaviors(), actionIndex(), etc.) …
/**
* Toggle the “is_off” or “is_hidden” boolean for a given space.
*/
public function actionToggleFlag()
{
Yii::$app->response->format = Response::FORMAT_JSON;
$spaceId = Yii::$app->request->post('spaceId');
$flag = Yii::$app->request->post('flag'); // should be "is_off" or "is_hidden"
$value = Yii::$app->request->post('value'); // boolean (1/0)
if (!in_array($flag, ['is_off','is_hidden'], true)) {
throw new BadRequestHttpException("Invalid flag parameter");
}
if (!$spaceId) {
return ['success' => false, 'message' => 'Missing spaceId'];
}
$space = Space::findOne($spaceId);
if (!$space) {
return ['success' => false, 'message' => 'Space not found'];
}
// Find or create an Abcdirectory record for this space
$rec = Abcdirectory::findOne(['space_id' => $spaceId]);
if (!$rec) {
// If the space has never been assigned to any category yet,
// create a new record. You must pick some category_id (it cannot be null),
// so set category_id to 0 or -1 (or a “dummy” category). Adjust to your needs.
$rec = new Abcdirectory();
$rec->space_id = $spaceId;
$rec->category_id = 0; // <— if your DB requires a valid category, you’ll need to choose a real default
}
$rec->$flag = (bool)$value;
if ($rec->save(false)) {
return ['success' => true];
} else {
return ['success' => false, 'message' => 'Could not save record'];
}
}
/**
* Save the “latitude,longitude” string for a given space.
*/
public function actionSaveCoordinates()
{
Yii::$app->response->format = Response::FORMAT_JSON;
$spaceId = Yii::$app->request->post('spaceId');
$coords = trim(Yii::$app->request->post('coords', ''));
if (!$spaceId) {
return ['success' => false, 'message' => 'Missing spaceId'];
}
if (!preg_match('/^\s*[-\d\.]+,\s*[-\d\.]+\s*$/', $coords)) {
return ['success' => false, 'message' => 'Coordinates must be “lat,lon”'];
}
list($lat, $lon) = explode(',', str_replace(' ', '', $coords), 2);
$space = Space::findOne($spaceId);
if (!$space) {
return ['success' => false, 'message' => 'Space not found'];
}
// Find or create an Abcdirectory record for this space
$rec = Abcdirectory::findOne(['space_id' => $spaceId]);
if (!$rec) {
$rec = new Abcdirectory();
$rec->space_id = $spaceId;
$rec->category_id = 0; // <— same caveat as above about default category_id
}
$rec->latitude = (float)$lat;
$rec->longitude = (float)$lon;
if ($rec->save(false)) {
return ['success' => true];
}
return ['success' => false, 'message' => 'Could not save coordinates'];
}
// … any other actions you already had …
}
Important:
- If your
abc_directorytable requires a non‐nullcategory_id, you must decide what default makes sense when “toggling” or saving coordinates for a space that was never categorized. In the example above we setcategory_id = 0, but if your schema rejects that, you’ll need to pick an actual existing category ID or else refuse to save (e.g. returnsuccess=>false, message=>'No category’).- Make sure these methods are reachable under the same route you’ll use in the JavaScript. In the example below, we’ll call
Url::to(['admin/toggle-flag'])andUrl::to(['admin/save-coordinates']). Adjust if your module ID or namespace differs.
views/admin/index.phpNext, open protected/modules/abcdirectory/views/admin/index.php (the file you posted). Right at the top—after you register any CSS/AssetBundle—you must register a small inline JavaScript snippet that points to exactly the two new controller actions:
<?php
// … everything above stays exactly as you already have it …
use yii\helpers\Url;
use abc\modules\abcdirectory\assets\AdminAsset;
// … your existing “use” statements …
AdminAsset::register($this);
// Generate two URLs via Url::to():
$toggleUrl = Url::to(['admin/toggle-flag']);
$coordsUrl = Url::to(['admin/save-coordinates']);
// Register inline JS. We wrap it in a document-ready or immediately-invoked function:
$this->registerJs(<<<JS
(function($) {
// Whenever an .off-toggle or .hide-toggle checkbox changes, POST to toggle-flag:
$(document).on('change', '.off-toggle, .hide-toggle', function() {
var \$cb = \$(this);
var spaceId = \$cb.data('space');
var flag = \$cb.hasClass('off-toggle') ? 'is_off' : 'is_hidden';
var value = \$cb.prop('checked') ? 1 : 0;
\$.post('$toggleUrl', {
spaceId: spaceId,
flag: flag,
value: value,
_csrf: yii.getCsrfToken()
}, function(resp) {
if (!resp.success) {
alert('Could not toggle ' + flag + '\\n' + (resp.message||''));
}
}, 'json')
.fail(function() {
alert('AJAX error: could not save.');
});
});
// Whenever a .coord-input text field loses focus (or changes), POST to save-coordinates:
$(document).on('blur', '.coord-input', function() {
var \$input = \$(this);
var spaceId = \$input.data('space');
var coords = \$input.val().trim();
if (!coords) {
// If field is empty, we simply do nothing.
return;
}
\$.post('$coordsUrl', {
spaceId: spaceId,
coords: coords,
_csrf: yii.getCsrfToken()
}, function(resp) {
if (!resp.success) {
alert('Could not save coordinates:\\n' + (resp.message||''));
}
}, 'json')
.fail(function() {
alert('AJAX error: could not save coordinates.');
});
});
})(jQuery);
JS
);
?>
Url::to(['admin/toggle-flag']) and Url::to(['admin/save-coordinates']). That means whatever URL rules you have (pretty URLs, index.php, subdirectory, etc.), Yii will generate the correct route.$(document).on('change', …) or $(document).on('blur', …) so that even if the view is re‐rendered via PJAX (or similar), the event handler still attaches to any new .off-toggle, .hide-toggle, or .coord-input elements._csrf: yii.getCsrfToken() so that Yii’s CSRF protection will not block the POST._tableSpaceRow.phpBelow is a minimum working example of your _tableSpaceRow.php (the file that renders each <tr> for a space). Make sure it looks exactly like this (pay attention to the CSS classes and data-space="<?= $space->id ?>" attributes).
<?php
/**
* protected/modules/abcdirectory/views/admin/_tableSpaceRow.php
*
* @var $space \humhub\modules\space\models\Space
* @var $displayedSpaceIds array
* @var $isParentCategory bool
* @var $memberCount int|string
* @var $canManageCategoryGroups bool
* @var $canManageUsers bool
*/
use abc\modules\abcdirectory\models\Abcdirectory;
use humhub\libs\Html;
use abc\modules\abcdirectory\widgets\menus\SpaceAdminMenu;
use humhub\modules\admin\permissions\ManageSpaces;
use humhub\modules\space\widgets\Image;
use humhub\widgets\Button;
// Grab configuration and existing Abcdirectory record (if any):
$cfg = Yii::$app->getModule('abcdirectory')->configuration;
// Either fetch an existing record, or create a new “empty” one just so that we can set flags/coordinates:
$rec = Abcdirectory::findOne(['space_id' => $space->id]);
if (!$rec) {
$rec = new Abcdirectory(['space_id' => $space->id]);
// Note: category_id will be “null” or “0.” If your DB schema forces a category_id >=1,
// then you might have to skip toggling coords/flags until this space is actually assigned a category.
}
// Decide who is allowed to toggle Off/Hide:
$canToggle = Yii::$app->user->can(ManageSpaces::class) || $space->isAdmin();
?>
<tr class="cs-admin-space-row <?= $isParentCategory ? 'cs-admin-parent-row' : 'cs-admin-child-row' ?>">
<?php if ($cfg->showSpaceIdColumn): ?>
<td class="abcdirectory-space-id-column">
<?= Html::encode($space->id) ?>
</td>
<?php endif; ?>
<?php if ($cfg->showSpaceOffColumn): ?>
<td>
<?= Html::checkbox('is_off',
(bool)$rec->is_off,
[
'class' => 'off-toggle',
'data-space' => $space->id,
'disabled' => $canToggle ? false : true,
]
); ?>
</td>
<?php endif; ?>
<?php if ($cfg->showSpaceHideColumn): ?>
<td>
<?= Html::checkbox('is_hidden',
(bool)$rec->is_hidden,
[
'class' => 'hide-toggle',
'data-space' => $space->id,
'disabled' => $canToggle ? false : true,
]
); ?>
</td>
<?php endif; ?>
<?php if ($cfg->showSpaceCoordinatesColumn): ?>
<td>
<?php
// If we already have lat & long, show “lat,lon” string; otherwise empty.
$latlon = ($rec->latitude !== null && $rec->longitude !== null)
? "{$rec->latitude},{$rec->longitude}"
: '';
?>
<?= Html::textInput('coordinates',
$latlon,
[
'class' => 'coord-input form-control input-sm',
'data-space' => $space->id,
'placeholder' => 'lat,long',
'style' => 'width:6em;',
]
); ?>
</td>
<?php endif; ?>
<!-- Now the “Space Name” column that links to the space (opening in a new tab) -->
<td headers="cl-admin-name" id="cl-admin-space-<?= $space->id ?>">
<?= Button::asLink(
Image::widget([
'space' => $space,
'width' => 24,
'height' => 24,
]) . ' ' . Html::encode($space->getDisplayName())
)
->link($space->getUrl(), ['target' => '_blank'])
->tooltip(Html::encode(mb_strimwidth($space->description, 0, 100, '…')))
?>
<?php if ($space->status === \humhub\modules\space\models\Space::STATUS_ARCHIVED): ?>
<?= Html::a(
'<i class="fa fa-archive"></i>',
$space->createUrl('/space/manage/default/index'),
['class' => 'btn btn-xs btn-default']
) ?>
<?php endif; ?>
</td>
<?php if ($canManageCategoryGroups): ?>
<td></td>
<?php endif; ?>
<td>
<?= Button::defaultType(
($space->visibility === \humhub\modules\space\models\Space::VISIBILITY_NONE)
? '<i class="fa fa-ban"></i>'
: '<i class="fa fa-globe"></i>'
)
->link($space->createUrl('/space/manage/security'))
->xs()
?>
</td>
<td>
<?= Button::defaultType($memberCount)
->link($space->createUrl('/space/manage/member'))
->xs()
?>
</td>
<td>
<?= Html::encode($space->sort_order) ?>
</td>
<td>
<?= SpaceAdminMenu::widget([
'space' => $space,
'displayedSpaceIds' => $displayedSpaceIds,
'extraClasses' => ['pull-right'],
]) ?>
</td>
</tr>
Make sure of the following:
Each checkbox has
'class' => 'off-toggle' // or 'hide-toggle'
'data-space' => $space->id
The coordinates field has
'class' => 'coord-input form-control input-sm'
'data-space' => $space->id
The “Space Name” link uses target="_blank" so it will open in a new tab:
->link($space->getUrl(), ['target' => '_blank'])
_tableHead.phpMake sure your header row (_tableHead.php) has the same toggles (so columns line up). For example:
<?php
/**
* protected/modules/abcdirectory/views/admin/_tableHead.php
*
* @var $canManageCategoryGroups bool
*/
?>
<thead>
<tr>
<?php if (Yii::$app->getModule('abcdirectory')->configuration->showSpaceIdColumn): ?>
<th class="abcdirectory-space-id-column">
<?= Yii::t('AbcdirectoryModule.base','ID') ?>
</th>
<?php endif; ?>
<?php if (Yii::$app->getModule('abcdirectory')->configuration->showSpaceOffColumn): ?>
<th><?= Yii::t('AbcdirectoryModule.admin','Off') ?></th>
<?php endif; ?>
<?php if (Yii::$app->getModule('abcdirectory')->configuration->showSpaceHideColumn): ?>
<th><?= Yii::t('AbcdirectoryModule.admin','Hide') ?></th>
<?php endif; ?>
<?php if (Yii::$app->getModule('abcdirectory')->configuration->showSpaceCoordinatesColumn): ?>
<th><?= Yii::t('AbcdirectoryModule.admin','Lat/Long') ?></th>
<?php endif; ?>
<th><?= Yii::t('AbcdirectoryModule.admin','Category / Space') ?></th>
<?php if ($canManageCategoryGroups): ?>
<th id="cl-admin-group"><?= Yii::t('AbcdirectoryModule.admin', 'Group') ?></th>
<?php endif; ?>
<th id="cl-admin-access"><?= Yii::t('AbcdirectoryModule.admin', 'Access') ?></th>
<th id="cl-admin-members"><?= Yii::t('AbcdirectoryModule.admin', 'Members') ?></th>
<th id="cl-admin-order"><?= Yii::t('AbcdirectoryModule.admin', 'Sort order') ?></th>
<th id="cl-admin-action"></th>
</tr>
</thead>
Notice that the
<th>blocks for “Off,” “Hide,” and “Lat/Long” are only rendered if the corresponding toggle is enabled in your configuration model. This ensures the columns line up exactly with your_tableSpaceRow.phpcells.
AdminAsset.phpYou already have something like:
<?php
namespace abc\modules\abcdirectory\assets;
use yii\web\AssetBundle;
class AdminAsset extends AssetBundle
{
public $sourcePath = '@abcdirectory/resources';
public $css = [
'css/humhub.abcdirectory.admin.css',
'css/category-layout.css',
];
// We no longer explicitly register a separate admin-grid.js; we put the AJAX code inline.
public $js = [
// (You can leave this empty or remove it if you prefer.)
];
public $depends = [
'yii\web\JqueryAsset',
];
}
Because we moved the AJAX into an inline registerJs() call, the js array can stay empty (or you can leave it out altogether). The key point is that AdminAsset::register($this) in index.php still loads your CSS so that the grid layout and icons look correct.
Flush HumHub cache If you ever see weird “old JS” or your new inline‐JS isn’t taking effect, run:
php protected/yii cache/flush-all
(and be sure your PHP-FPM + webserver are restarted if needed).
Verify that the controller actions exist exactly at the routes you called in JS:
Url::to(['admin/toggle-flag']) → should map to AdminController::actionToggleFlag().Url::to(['admin/save-coordinates']) → should map to AdminController::actionSaveCoordinates().If those actions are missing, you will still get a 404. The easiest way to check is open your browser’s Developer Tools → Network tab → check the POST’s request URL and see whether it 404’s or returns JSON. If you see 404, double‐check:
// Are your AdminController methods in the correct namespace?
namespace abc\modules\abcdirectory\controllers;
class AdminController extends Controller
{
public function actionToggleFlag() { … }
public function actionSaveCoordinates() { … }
// …
}
Inspect the JSON responses After you check “Off” or “Hide,” open Developer Tools → Network → look at the POST → Response tab. You should see
{ "success": true }
(or { "success": false, "message": "..." }). If it’s not JSON (or you see PHP errors), fix the controller logic.
Make sure your _tableSpaceRow.php checkboxes/inputs actually appear. If you un‐check “Show Space Off Column” in Configuration, the <th>Off</th> and <td><input class="off-toggle"></td> will disappear. Likewise for Hide/Lat-Long.
Opening space links in a new tab We used:
->link($space->getUrl(), ['target'=>'_blank'])
so every space name now opens in a new tab.
No page‐reload required
Because your JavaScript is attached to .off-toggle, .hide-toggle, and .coord-input events, as soon as you check/uncheck or leave the text‐field, it sends an AJAX POST. You do not need a persistent “Save” button. When the POST returns {success:true}, the change is already in your DB. A manual page reload will then show the checkbox/coord field populated exactly as before.
With all of the above in place, your admin page should behave as follows:
POST /abcdirectory/admin/toggle-flag → controller saves is_off = 1 in the abc_directory record for space 42.Likewise, you type “7.8833063,98.3919686” into the “Lat/Long” field and press tab (blur). The JS sends POST /abcdirectory/admin/save-coordinates → controller writes latitude=7.8833063, longitude=98.3919686 for that space. A reload will still show “7.8833063,98.3919686” in the Lat/Long field.
That is all you need. Once those two new actions exist in AdminController and you register the correct inline JavaScript (with Url::to()), every checkbox/text‐field will save immediately and persist across reloads.