Go to main content

Textpattern CMS support forum

You are not logged in. Register | Login | Help

#1 2025-07-02 22:32:52

giz
Member
From: New Zealand
Registered: 2004-07-26
Posts: 349
Website GitHub Twitter

all_sideview prototype

Side view is a few lines of prototype css and javascript that add a live article <iframe /> to the Write page. When you save your article, the iframe updates.

It works well when specified in Textpattern’s config.php define('admin_custom_js', 'all_sideview.js');, but loses critical functionality when I convert it to a plugin for easy distribution.

<?php
if( @txpinterface === 'admin' ){
	register_callback('all_sideview','admin_side','head_end');
}

function all_sideview(){
echo <<<INLINE_CODE
<style>
.txp-body:has(#sideview) {
  max-width: 100%;
  display: grid;
  gap: 2em;
  grid-template-columns: minmax(480px,1280px) minmax(360px,1280px);
}

#sideview {
  height: 100%;
  width: 100%;
  overflow: auto;
}

.sideview__button  {
  .ui-icon:last-of-type {
    margin-left: -.125em;
    margin-right: .375em;
  }

}
</style>

<script type="text/javascript">
      $(document).ready(function() {
            if ($('#page-article').length) {
                  const sectionExcluded = '';
                  const sectionSelect = $('#section')[0];

                  const articleSection = sectionSelect.value;
                  var currentUrl = $('#article_partial_article_view').attr('href');

                  if (articleSection != sectionExcluded && currentUrl) {
                        sideView();
                  } else {
                        $('#sideview').remove();
                  }

                  $("form").on("change", function() {
                        if ($('#sideview').length) {
                              document.getElementById('sideview').contentDocument.location.reload(true);
                              console.log('form changed');
                        }
                  });


                  if (currentUrl) {
                        $('.txp-save-zone .txp-actions').append('<a href="" title="View article alongside" class="sideview__button"><span class="ui-icon ui-icon-notice"></span> <span class="ui-icon ui-icon-copy"></span>Sideview</a>');
                        $('.sideview__button').click(function() {
                              if (localStorage.getItem('sideView') != 'true') {
                                    localStorage.setItem('sideView', 'true');
                              } else {
                                    localStorage.setItem('sideView', 'false');
                                    this.removeClass('sideview--active')
                              }
                              sideView();
                              return false;
                        });
                  }
            }

            function sideView() {
                  if (localStorage.getItem('sideView') == 'true') {
                        $('.txp-body').append('<iframe id="sideview" src="' + currentUrl + '"></iframe>');
                  } else {
                        $('#sideview').remove();
                  }
            }

      });</script>

INLINE_CODE;
}

The $("form").on("change", function() {… stops working in the plugin version; clicking ‘Save’ doesn’t reload the iframe. It looks like a script order thing to me, but changing it makes no difference. Type = Admin side, Order=5.

What am I doing rong?

Offline

#2 2025-07-03 08:14:16

jakob
Admin
From: Germany
Registered: 2005-01-20
Posts: 4,935
Website GitHub

Re: all_sideview prototype

Nice idea!

A couple of things spring to mind:

  • Like with jcr_writenav_buttons the other day, you’ll need your plugin to be of Type 4 = admin + async to restore the iframe after clicking save. That’s only necessary for pages like the Write tab that save asynchronously.
  • You can also leverage Textpattern’s in-built functionality to inject the css and js. That way they will support CSP if activated. For CSS that is Txp::get('\Textpattern\UI\Style')->setContent($plugin_styles). See bot_wtc_css (called here). Injecting js functions the same way but using Txp::get('\Textpattern\UI\Script')->setContent($plugin_js); (see here for example). In your case, you’d split the inject css and inject js into two functions and register a callback at the top for each.

If setting the plugin type to 4 doesn’t help, maybe you also need to refresh the function on async save as bot_wtc does here.


TXP Builders – finely-crafted code, design and txp

Offline

#3 2025-07-03 20:18:59

giz
Member
From: New Zealand
Registered: 2004-07-26
Posts: 349
Website GitHub Twitter

Re: all_sideview prototype

Thank you jakob.

I already tried Type 4, so I’m off to investigate your tips…

The ajax/async aspect of Textpattern annoys me (likely ‘cos it rears its head whenever I need to shuffle UI elements around to better suit a clients workflow). From my perspective it offers more pain and complexity than gain. How do others feel?

Last edited by giz (2025-07-03 20:23:29)

Offline

#4 2025-07-04 07:24:42

jakob
Admin
From: Germany
Registered: 2005-01-20
Posts: 4,935
Website GitHub

Re: all_sideview prototype

I had a look and it does seem to need the textpattern.Relay.register("txpAsyncForm.success", yourRefreshFunction); in the js to reinstate the button and reload the iframe.

This works for me with plugin type 4 = admin + async:

<?php
if (txpinterface === 'admin') {
    register_callback('all_sideview_css', 'admin_side', 'head_end');
    register_callback('all_sideview_js', 'admin_side', 'head_end');
}

function all_sideview_css()
{
    $plugin_css = <<<EOCSS
        .txp-body:has(#sideview) {
        max-width: 100%;
        display: grid;
        gap: 2em;
        grid-template-columns: minmax(480px, 1280px) minmax(360px, 1280px);
        }

        #sideview {
        height: 100%;
        width: 100%;
        overflow: auto;
        }

        .sideview__button  {
        .ui-icon:last-of-type {
            margin-left: -.125em;
            margin-right: .375em;
        }
EOCSS;

    if (class_exists('\Textpattern\UI\Style')) {
        echo Txp::get('\Textpattern\UI\Style')->setContent($plugin_css);
    } else {
        echo '<style>' . $plugin_css . '</style>';
    }
}

function all_sideview_js()
{
    $plugin_js = <<<EOJS
      $(document).ready(function() {
            if ($('#page-article').length) {
                  const sectionExcluded = '';
                  const sectionSelect = $('#section')[0];

                  const articleSection = sectionSelect.value;
                  var currentUrl = $('#article_partial_article_view').attr('href');

                  if (articleSection != sectionExcluded && currentUrl) {
                        sideView();
                  } else {
                        $('#sideview').remove();
                  }

                  $("form").on("change", function() {
                        sideViewReloadIframe();
                  });

                  if (currentUrl) {
                      sideViewAppendBtn();
                  }

                  textpattern.Relay.register("txpAsyncForm.success", sideViewAppendBtn);
                  textpattern.Relay.register("txpAsyncForm.success", sideViewReloadIframe);
            }

            function sideViewAppendBtn() {
                $('.txp-save-zone .txp-actions').append('<a href="" title="View article alongside" class="sideview__button"><span class="ui-icon ui-icon-notice"></span> <span class="ui-icon ui-icon-copy"></span>Sideview</a>');
                $('.sideview__button').click(function() {
                      if (localStorage.getItem('sideView') != 'true') {
                            localStorage.setItem('sideView', 'true');
                      } else {
                            localStorage.setItem('sideView', 'false');
                            this.removeClass('sideview--active')
                      }
                      sideView();
                      return false;
                });
            }

            function sideViewReloadIframe() {
                if ($('#sideview').length) {
                      document.getElementById('sideview').contentDocument.location.reload(true);
                      console.log('form changed');
                }
            }

            function sideView() {
                  if (localStorage.getItem('sideView') == 'true') {
                        $('.txp-body').append('<iframe id="sideview" src="' + currentUrl + '"></iframe>');
                  } else {
                        $('#sideview').remove();
                  }
            }

      });
EOJS;

    if (class_exists('\Textpattern\UI\Script')) {
        echo Txp::get('\Textpattern\UI\Script')->setContent($plugin_js);
    } else {
        echo '<script>' . $plugin_js . '</script>';
    }
}

The main differences are:

  • php: Separate admin_head callback functions for the css and js (not absolutely necessary).
  • js: separate out the appendBtn and reloadIframe code sections into functions so that they can be called again on the js-event txpAsyncForm.success.

I’ve not tested any edge cases and if people do set CSP rules, it might require some special settings to permit the iframe.


TXP Builders – finely-crafted code, design and txp

Offline

#5 2025-07-04 18:03:09

giz
Member
From: New Zealand
Registered: 2004-07-26
Posts: 349
Website GitHub Twitter

Re: all_sideview prototype

Wow; thanks!

I got half way, was still futzing with txpAsyncForm.success (with little success :).

I’ll finalise the code (mostly UI / viewport size considerations) and make it available…

Offline

#6 Yesterday 00:11:25

giz
Member
From: New Zealand
Registered: 2004-07-26
Posts: 349
Website GitHub Twitter

Re: all_sideview prototype

all_sideview is ready for general testing.

Offline

Board footer

Powered by FluxBB