Patch Guide - Moveable Tabs November 28, 2024

View all articles from Gyroscope Development Blog

Tabs in Gyroscope v21.6 can now be moved around. Just hold the Ctrl key (Cmd for a Mac), and arrange the tabs to your heart's desire. The non-closeable tabs are not moveable. No tabs can be dragged in front of the non-closeable tabs either. One example is the Home tab.

For older versions of Gyroscope, you can patch the following files:

  • index.php
    • #tabshadow
  • gyroscope_css.php
    • .tt img: prevent user select
  • tabs.js
    • addtab
      • mouse event
      • clear document.lasttab
    • showtab
      • override last tab
    • tab_reflow
      • use DOM collection
  • viewport.js
    • autosize
      • override last tab

For the most accurate patch, please refer to the latest Gyroscope code. But the patches are documented here nonetheless for your convenience.

index.php

Instead of moving the tab itself, a shadow node takes on the appearance of the source tab. Once the target tab is moved onto, the source tab will be removed from the tab title container, and inserted before the target tab.

<div id="tabtitles" ...

<span class="activetab"

id="tabshadow"

style="position:absolute;top:0;left:0;display:none;"></span>

</div>

gyroscope_css.php

Older versions of Gyroscope serves a static gyroscope.css, so modify that file instead. A tab may carry an icon. Dragging the icon (an <img> tag) causes an awkward user interaction. The following code disables the user select:

.tt img{vertical-align:middle;user-select:none;-webkit-user-select:none;-moz-user-select:none;-webkit-user-drag:none;}

tabs.js / addtab

In the addtab function, locate this line:

t.reloadinfo={...

Add before the above line:

t.onmousedown=function(e){

  var metakey=0;
  if (document.keyboard['key_17']

  ||document.keyboard['key_91']

  ||document.keyboard['key_224']) metakey=1;


  if (!metakey) return; //hold Ctrl key

  if (t.reloadinfo

  &&t.reloadinfo.opts

  &&t.reloadinfo.opts.noclose) return;

  var shadow=gid('tabshadow');
  shadow.style.left=t.offsetLeft+'px';
  shadow.style.top=(t.offsetTop)+'px';
  shadow.innerHTML=t.innerHTML;
  shadow.style.display='block';
  shadow.style.opacity=0.6;
  shadow.style.filter='sepia(1)';
  shadow.style.overflow='hidden';


  var ox=e?e.clientX:event.clientX;
  var oy=e?e.clientY:event.clientY;

  var posx=t.offsetLeft;
  var posy=t.offsetTop;

  document.tabmovesrc=t;
  t.onmousemove=function(e){
  var x=e?e.clientX:event.clientX;
  var y=e?e.clientY:event.clientY;
  var nx=posx+x-ox;
  var ny=posy+y-oy;
  shadow.style.left=nx+'px';
  shadow.style.top=ny+'px';

  if (document.tabmovedst){
    document.tabmovedst.style.border='none';
    document.tabmovedst=null;
  }

  var mybox=shadow.getBoundingClientRect();

  for (var i=0;i<document.tabkeys.length;i++){
    var dst=document.tabtitles[i];
    if (dst==null) continue;
    if (t==dst) continue; //skip self
    if (dst.reloadinfo

    && dst.reloadinfo.opts

    && dst.reloadinfo.opts.noclose) continue;

    var box=document.tabtitles[i].getBoundingClientRect();

    if (mybox.x>box.x

    &&mybox.x<box.x+box.width

    && mybox.y+mybox.height>box.y

    &&mybox.y<box.y+box.height){
        document.tabmovedst=dst;
        dst.style.borderLeft='solid 3px #ffab00';
        break;
      }
    }
  }

  t.onmouseup=function(){
    if (document.tabmovedst) document.tabmovedst.style.border='none';
    if (document.tabmovesrc&&document.tabmovedst){
      var p=document.tabmovedst.parentNode;
      p.removeChild(document.tabmovesrc);
      p.insertBefore(document.tabmovesrc,document.tabmovedst);
    }
    shadow.style.display='none';
    t.onmousemove=null;
    document.onmousemove=null;
    document.tabmovesrc=null;
    document.tabmovedst=null;

    document.lasttab=null;
    var os=gid('tabtitles').getElementsByTagName('span');
    for (var i=0;i<os.length;i++){

      if (os[i].reloadinfo) document.lasttab=os[i];

    }

    if (document.appsettings.uiconfig.toolbar_position=='left') {

      tab_reflow();

    }

  }

  document.onmousemove=t.onmousemove;
  document.onmouseup=t.onmouseup;

}

The drag target detection uses a different strategy than other parts of Gyroscope. Instead of registering an onmouseover event, which would be blocked by the shadow tab anyway, the function takes note of every tab's coordinates, skipping the non-closeable tabs as well as the source tab itself.

Next, after this line:

t.reloadinfo={...

add:

document.lasttab=null;

The above seemingly trivial line is vital for the tab system to function correctly. The previous design assumes that tabs are added in order, and the lastly added tab is visually the last tab. When tabs are rearranged, the code inside the mouseup event places a marker on the visual last tab. But when a new tab is inserted, the new tab should be marked as the last. The rest of the tab system falls back to the Insertion Order, if Visual Order is not established. Unsetting document.lasttab leaves added clue whether the tabs were ever rearranged, in case the new feature introduces any complications.

tabs.js / showtab

The showtab function needs to use the last tab per Visual Order to determine the number of rows:

var t=document.tabtitles[document.tabcount-1];
if (document.lasttab) t=document.lasttab;

tabs.js / tab_reflow

The original loop in tab_reflow needs to be updated to use Visual Order instead of Insertion Order:

//for (i=0;i<document.tabcount;i++){

var os=gid('tabtitles').getElementsByTagName('span');

for (var idx=0;idx<os.length;idx++){

  var tab=os[idx];

  if (!tab.reloadinfo) continue;

  rowsum+=tab.offsetWidth+8; //tab_gap=8

  if (rowsum>rowcap){

    rowcap=idw;

    rowsum=0;

    tab.style.clear='left';

  } else tab.style.clear='none';

}

viewport.js / autosize

Similar to the showtab function in tabs.js, any Visual last tab must be used in place of Inserted last tab, if applicable:

    var t=document.tabtitles[document.tabcount-1];
    if (document.lasttab) t=document.lasttab;

The existing public Gyroscope projects, including SQLDash, and other Gyroscope deployments that are managed by Antradar will be retrofitted with this feature, regardless of versions. The complications we encounter when patching ancient Gyroscope apps will be documented here.

Our Services

Targeted Crawlers

Crawlers for content extraction, restoration and competitive intelligence gathering.

Learn More

Gyroscope™ ERP Solutions

Fully integrated enterprise solutions for rapid and steady growth.

Learn More

E-Commerce

Self-updating websites with product catalog and payment processing.

Learn More
Chat Now!
First Name*:
Last Name*:
Email: optional