For the past couple of months I've been learning PHP and using Octobercms and I'm still fairly new to it. The basic concept of what I'm trying to achieve is a page which contains 5 tiles. When I click the tile it replaces the current page with a partial depending on which tile you clicked and changes the url without having to reload the page. each tile has an id with the name of the partial it should return. e.g the settings tile has an data-id="settings"
. Here is a screenshot of the page
I've created my own plugin for this and placed the component on the page. On render it returns the dashboard partial which works and is shown in the screenshot above, the problem is when I click another tile. It makes the ajax call and calls my "test" method in my component and passes the tile id which contains the name of the partial to return. I use the method $this->renderPartial('partialName')
however I get the following error
Call to a member function setComponentContext() on null
Here are screenshots to the rest of my code:
$( document ).ready(function() {
$(document).on('click','.tile',function() {
let page = $(this).attr('id');
history.pushState({page}, '', 'planner/' + page );
getPage(page);
});
window.onpopstate = function(e){
if(e.state){
getPage(e.state.id);
}
};
history.replaceState({page: null}, 'Default state', './planner');
function getPage (page) {
$.ajax({
url: '/planner/' + page,
type: 'GET',
success: function(data){
$('.page-container').html(data);
},
error: function(data) {
console.log('Could not return page');
}
});
}
});
<?php
Route::get('/planner/{page}', 'myName\budgetplanner\Components\app@test');
<?php
namespace myName\budgetplanner\Components;
use Db;
class app extends \Cms\Classes\ComponentBase
{
public function componentDetails()
{
return [
'name' => 'budgetplanner',
'description' => 'Manage your finances.'
];
}
public function onRender()
{
echo ( $this->renderPartial('@dashboard.htm') );
}
public function test($page)
{
if ($page == 'undefined') {
echo ( $this->renderPartial('@dashboard.htm') );
}
elseif ($page == 'overview') {
echo ( $this->renderPartial('@overview.htm') );
}
elseif ($page == 'month') {
echo ( $this->renderPartial('@month.htm') );
}
elseif ($page == 'reports') {
echo ( $this->renderPartial('@reports.htm') );
}
elseif ($page == 'budget') {
echo ( $this->renderPartial('@budget.htm') );
}
elseif ($page == 'settings') {
echo ( $this->renderPartial('@settings.htm') );
}
}
}
I tried doing some testing and think I've found the issue but I don't really understand how to go about fixing it. here are some additional screenshots
I dump the component object testing component On render it looks fine onRender now its empty ? clicked settings page
This is Wrong way of handling Ajax request as you are breaking lifecycle of October CMS Page.
you are getting error because you directly ask component to handle request Route::get('/planner/{page}', 'myName\budgetplanner\Components\app@test');
as renderpartial
need controller context and if you do route
like this it will surely yield unexpected behaviour
Ok, We get this, BUT then , How to do it correctly ?
Your url lets say we use like this
/planner/:type
Add framework and extra to sure layout for ajax-framework [ make sure you add them before your script ]
<script src="{{ 'assets/javascript/jquery.js'|theme }}"></script>
{% framework extras %}
Your script should look like this
<script>
$(document).ready(function() {
$(document).on('click','.tile',function() {
let page = $(this).attr('data-tile');
history.pushState({page}, '', '/planner/' + page );
getPage(page);
});
window.onpopstate = function(e){
if(e.state){
getPage(e.state.page);
}
};
// not sure causing issues so commented
// history.replaceState({page: null}, 'Default state', './planner');
function getPage (page) {
$.request('onRenderTile', { data: { type: page }})
}
});
</script>
Your Component
public function onRender()
{
// if type is not passed default would be dashboard
$type = $this->param('type', 'dashboard');
return $this->onRenderTile($type)['#tile-area'];
}
public function onRenderTile($type = null)
{
$availableTiles = [
'dashboard',
'tile1',
'tile2',
'tile3',
];
// if not passed any value then also check post request
if(empty($type)) {
$type = post('type');
}
// we check partial is valid other wise just return dashboard content
if(in_array($type, $availableTiles)) {
return ['#tile-area' => $this->renderPartial('@'.$type.'.htm')];
}
else {
return ['#tile-area' => $this->renderPartial('@dashboard.htm')];
}
}
Your patials
file : _tiles.htm
<div class="tile" data-tile="dashboard">Dashboard</div>
<div class="tile" data-tile="tile1">Tile 1</div>
<div class="tile" data-tile="tile2">Tile 2</div>
<div class="tile" data-tile="tile3">Tile 3</div>
file : dashboard.htm
<div id="tile-area">
{% partial __SELF__~"::_tiles" %}
<h1>Dashboard</h1>
</div>
file : tile1.htm
<div id="tile-area">
{% partial __SELF__~"::_tiles" %}
<h1>Tile 1 Content</h1>
</div>
file : tile2.htm
<div id="tile-area">
{% partial __SELF__~"::_tiles" %}
<h1>Tile 2 Content</h1>
</div>
file : tile3.htm
<div id="tile-area">
{% partial __SELF__~"::_tiles" %}
<h1>Tile 3 Content</h1>
</div>
Initially it will render
default partial dashboard
if notype
is passed
dashboard
has all other tile links
and dashboard content
same as other tile partials
.
Now if you click any tile
it will fire October Ajax framework request
with handler onRenderTile
and type (dashboard|tile1|tile2 ...)
so it will properly call October lifecycle methods
and finally render partial through onRenderTile
and return json with key #tile-area
and as value it will return content of posted partial name(type)
OctoberCMS ajax framework
is smart enough that it will just replace this content with the give id so it will search #tile-area
and replace its content with new one.
for more information about updating partials using ajax
you can read this : https://octobercms.com/docs/ajax/update-partials
if any doubt or question please comment.