Site with editable content from frontend – starting guide

___________________________________________________________________________
|main page                                 |add page button|
|
| ___________________     ___________________     ___________________     
||page1              |   |page2              |   |page3              |    
||                   |   |                   |   |                   |
||                   |   |                   |   |                   |
||                   |   |                   |   |                   |
|| ________________  |   | ________________  |   | ________________  |
|||subpage1 + edit | |   ||subpage1 + edit | |   ||subpage1 + edit | |
|||subpage2 + edit | |   ||subpage2 + edit | |   ||subpage2 + edit | |
|||subpage3 + edit | |   ||subpage3 + edit | |   ||subpage3 + edit | |
||| ...            | |   || ...            | |   || ...            | |
|||________________| |   ||________________| |   ||________________| |
||                   |   |                   |   |                   |
||edit (page1)       |   |edit (page2)       |   |edit (page3)       |
||___________________|   |___________________|   |___________________|   ...
|
|___________________________________________________________________________

hi!

we are about to develop this project and there are some main questions about structuring and code usage.
being pretty unexperienced in php/javascript and also kirby should not mean that we want you to basically write everything for us, but i think it’s best to get some points straight before we get started and lose ourselves in chaos.
so if you like to, we’d be really happy:

CONTENT
the content should be saved in classic kirby-folder-scheme, that’s why there is pages and subpages for each.
i’ll try to keep it abstract, but:
pages are “projects” with basic information like title, description etc. and their subpages are like blog entries for capturing text, images and pdfs.

EDIT
the content should be editable from frontend via forms(?).
so there is the main page to display its subpages (page1/subpage1s, page2/subpages, etc.) and to add new pages (e.g. page4) and give it the basic content.
then the page appears and has following editing possibilies:
-edit content
-add subpage
-delete

its subpages:
-edit content
-delete

HTML STRUCTURE
so we’ve got it running without the edit functions (content input via panel) and it’s one foreach loop for subpages inside another one for the pages.

USING FORMS
i tried uniform, because i think it would be easier to handle multiple forms, and was successfull for adding/changing some basic stuff on a basic site. (edit field data, uploading images,…)

  1. now one question is, use AJAX (+ more javascript) or REDIRECT, and is it a good solution for working with “loop-dependant” variables (like the $page for pages or subpages).

  2. the other question: is the foreach-structure maybe more complicating than simple (regarding the forms) because of the variable handling?

side question: forms would be allright and do the job, but as we have to learn how to get there anyways, maybe the “would-be-nice” feature of directly editing the content (e.g. by clicking into the text-containers and just write) could be similarly difficult.

(what i didn’t mention because i think it can be added afterwards or doesn’t matter at this point: password authentication; maybe users (40max.) / groups; kirby-content-folder-to-nextcloud-link)

i hope you understand basically what we want to achieve and maybe have some tipps on how to start it.

thanks!

I just have a question. I don’t understand why are trying to make it possible to edit the content via forms on the site, when the panel does this job really well.

On a basic level, its pretty easy to add buttons to the front end that when clicked will take you to the relevant page in the panel directly. This button only shows up for logged in users.

For example, put this in a snippet called edit-link:

<!-- edit link -->
<?php if ($user = $site->user() and $user->hasPanelAccess()): ?>
<a href="<?= $site->url() ?>/panel/pages/<?= $hook->uri() ?>/edit" target="_blank" title="Edit content" class="admineditlink">
  <span class="fa fa-edit editpage"></span>
</a>
<?php endif; ?>

Then anywhere you need an edit link, just add the snippet to a template or even another snippet:

<?php snippet('edit-link', ['hook' => $page]) ?>

For something more powerful, consider using the PanelBar plugin, which puts this feature and much more into a toolbar on your sites frontend.

The foreach loops are pretty irrelevant, what you would need is to give each submit button a route to call via Ajax and pass along the page information. Thus, saving the correct page wouldn’t be a problem and your route action would handle the rest.

The main problem is that you would have to reinvent the Panel on the frontend, considering that you may want to use Markdown to edit content etc… Or integrate some sort of editor later. Or what are your plans?

It’s pretty easy to edit some basic text, but what about blueprint settings, i.e. if a page is allowed to have files etc. Where would you define that? Or, you would have to read the blueprint files etc. etc. etc. (and all that with little PHP/JavaScript experience).

I agree with @jimbobrjames: It is easy to integrate edit links that redirect user directly to the Panel form without having to reinvent the wheel.

our first thought was to modify kirby’s panel to fit our needs (disable some options, custom css,…). but i thought that it would possibly be nice to keep that " admin section".
it’s for a group of people we know, including us, and should mainly help organise simple data-input for different projects in a nice overview and folder structure.
you could say it’s a very stripped down project-management tool like trello, etc.

i think there is no need of editing blueprints from user-side, because the page-settings are fixed.

the goal is to have a “onepage” setup, so you never leave the overview position (like illustrated above) while you can add simple text as field-data and upload files which then add up to the “project-stream”.
i don’t think that it would be easy anymore, but achievable.

do you think the panel (even with adjustments – speaking of little php experience) would make the easier way?

I didn’t mean editing blueprints, but reading blueprint settings. If you don’t have any particular requirements for your forms, then you probably don’t need this.

Hard to tell without knowing your specific requirements. I wouldn’t recommend touching the source code…

Why create a project management tool with Kirby, when there are plenty of project management solutions out there already. I am a great believer in using the right tool for the job. Some of these are free, some of these are not. I can recommend Asana. Whilst you can build something like this to a point with Kirby, i think its probably worth looking for something that was designed for project management. I think it depends how stripped back your going for in terms of being like Trello.

Hi there! It’s been a while.

So, we had taken some time already evaluating which tool could do the job without having to reinvent any wheels. But the decision fell on trying it ourselves because of our specific needs and getting into php etc.

Modifying / Including the panel did not work as we wished, thinking of keeping it straight for administration-level, kirby-updates and spacial appearance, so the uniform-fight is on the run.

That’s why i want to keep this thread running, if anyone is interested.

Right now, i’m trying to include several forms into one page, which is actually working, but also making me curious. It’s about two different types of forms (fileupload / textchange) which are inside the subpages-loop. so every subpage is displayed and gets it’s referring forms.
i started static and then translated to dynamic, thinking of this previous question. The thing is, in the end it worked without it:

Template:

<?php if($page->hasChildren()):
        $id = 0;
        foreach($page->Children()->sortBy('date', 'desc') as $protocol): 
        $id = $id + 1; ?>
   
            <form id="uploadform<?= $id ?>" class="aform" action="<?php echo $page->url() ?>" method="POST" enctype="multipart/form-data">
                <input name="filefield" type="file" required/>
                <input type="hidden" name="formid" value="fileupload">
                <input type="hidden" name="pageuri" value="<?= $protocol->diruri() ?>">
                <?php echo csrf_field() ?>
                <?php echo honeypot_field() ?>
                <input type="submit" value="Submit" class="xyz">
            </form>

            <form id="textform<?= $id ?>" class="aform" action="<?php echo $page->url() ?>" method="POST">
                    <input type="hidden" name="formid" value="textchange">
                    <input type="hidden" name="pageuri" value="<?= $protocol->diruri() ?>">
                    <?php echo csrf_field() ?>
                    <?php echo honeypot_field() ?>
                    <textarea name="ptext" class="markdown" style="height: 200px"></textarea>
                
                <input class="" type="submit" value="save">
            </form>
    
    <?php endforeach; endif; ?>

Javascript:

var message = document.getElementById('message');

window.addEventListener('load', function () {
var forms = document.getElementsByClassName('aform');
for (var i = 0; i < forms.length; i++) {
    var fields = {};
    var form = forms[i];
    form.querySelectorAll('[name]').forEach(function (field) {
        fields[field.name] = field;
    });
    
    var handleError = function (response) {
        var errors = [];
        for (var key in response) {
                if (!response.hasOwnProperty(key)) continue;
                if (fields.hasOwnProperty(key)) fields[key].classList.add('error');
                Array.prototype.push.apply(errors, response[key]);
            }
            message.innerHTML = errors.join('<br>');
    };

    var onload = function (e) {
        if (e.target.status === 200) {
            message.innerHTML = 'got it!'
        } else {
            handleError(JSON.parse(e.target.response));
        }
    };

    var submit = function (e) {
        e.preventDefault();
        var request = new XMLHttpRequest();
        request.open('POST', e.target.action);
        request.onload = onload;
        request.send(new FormData(e.target));
        // Remove all 'error' classes of a possible previously failed validation.
        for (var key in fields) {
            if (!fields.hasOwnProperty(key)) continue;
            fields[key].classList.remove('error');
        }
    };
    form.addEventListener('submit', submit);
    
};
}); 

Config:

c::set('routes', [[
'pattern' => 'gettoken',
'method' => 'GET',
'action'  => function() {
  return response::json(['token' => csrf()]);
},
]]);

c::set('cache.ignore', ['gettoken']);
c::set('routes', array(
array(
    'pattern' => 'you',
    'method' => 'POST',
    'action' => function() {
        $textform = new \Uniform\Form([
            'ptext' => [
                'rules' => ['required'],
                'message' => ['input required']
            ],
        ], 'textform');
        $uploadform = new \Uniform\Form([
            'filefield' => [
                'rules' => [
                    'required', 
                    'file',
                    'mime' => ['application/pdf'],
                    'filesize' => 5000,
                ],
                'message' => [
                    'choose files', 
                    'choose files',
                    'pdf only',
                    'smaller than 5mb',
                ],
            ]
        ], 'uploadform');
        $pageuri = get('pageuri');
        $page = page($pageuri);
        $target = $page->root();
        if(get('formid') === 'textchange') {
            $textform->withoutFlashing()
                ->withoutRedirect()
                ->guard();

            if (!$textform->success()) {
                // Return validation errors.
                return response::json($textform->errors(), 400);
            };
            
            $textform->ChangeTextAction(['page' => $page]);
            
            if (!$textform->success()) {
                // This should not happen and is our fault.
                return response::json($textform->errors(), 500);
            };

            // Return code 200 on success.
            
        } elseif(get('formid') === 'fileupload') {
            $uploadform->withoutFlashing()
                ->withoutRedirect()
                ->guard();

            if (!$uploadform->success()) {
                // Return validation errors.
                return response::json($uploadform->errors(), 400);
            };
            
            $uploadform->uploadAction(['fields' => [
                'filefield' => [
                    'target' => $target,
                    'prefix' => false,
                ],
                
            ]]);
            
            if (!$uploadform->success()) {
                // This should not happen and is our fault.
                return response::json($uploadform->errors(), 500);
            };

            // Return code 200 on success.
        };
        }
        
    )
));

Yesterday it threw a json-error yet doing its job, today it works fine (except in Safari, which loads the page without content on redirect). So i think i didn’t get as lucky as i thought and the problem is with the tokens? (referring again to the link above – i could use some help there)

I really appreciate your help, thank you!

1 Like