Отправляет email-рассылки с помощью сервиса Sendsay

Секреты программирования

  Все выпуски  

Секреты программирования - 'Динамическое оглавление' на JavaScript'


Уважаемые подписчики!

Сегодняшняя тема - "Динамическое оглавление' на JavaScript".
Для улучшения "обратной связи" приглашаю обсудить рассылку на форуме сайта http://www.pvobr.ru в разделе "Программирование". Пишите также, какие темы вы хотите рассмотреть в будущем.

Динамическое оглавление используется в проигрывателе курсов в системе дистанционного обучения "Карат".
Переход к курсу.

Код динамической загрузки оглавления


<style>
/* menu item */
A.menuel:link { COLOR: #0000ff; }
A.menuel:visited { COLOR: #0000cc; }
A.menuel:hover { COLOR: #00ffff; }
A.menuel:active { COLOR: #00ffff; }

/* current menu item */
A.menuact:link { color: #ffff00; }
A.menuact:visited { COLOR: #eeee00; }
A.menuact:hover { COLOR: #00ffff; }
A.menuact:active { COLOR: #00ffff; }

/* hint for menu item */
 .hint
{
    BACKGROUND-COLOR: khaki
}
</style>

<script>
//navigation block 
var navdata = "";   //navigation data
var oldClass = "";   //class of not selected menu
var curPage = -1;   //current page displayed
var pages = new Array(); //array of course page objects
var lastSel = -1;   //selected page
var iForward = 2;   //1 - step forward, -1 - step back, 2 - initial open menu, 0 - show/hide
var maxwidth = 1;   //menu width
var pageTotal = 0;   //number of real pages
var yOffMenu = -1;   //y-offset of menu table
var skin = "";
var bEnglish = 0;

function coursePage( pid, title, url, level, num )
{
 this.pid = pid;
 this.title = title;
 this.url = url;
 this.level = level;  //= 0 - main menu, < 0 - submenu item
 this.type = 0;   //0 - content, 1 - test
 this.stat = "i";  //incomplete
 this.iMenu = 0;   //submenu: 0 - none, 1 - opened, -1 - closed
 this.num = num;   //
}
//parse course navigation data
function parsenavdata()
{
 pageTotal = 0;
 maxwidth = 1;
 var lastlevel = 0;
 var arr = navdata.split( "\n" );
 for( var i = 0; i < arr.length; i++ )
 {
  var s = trimSpaces( arr[ i ] );
  if( s == "" ) continue;
  var arr2 = s.split( "|" ); //pid, title, url, level <= 0
  var cur = pages.length;
  var level = Math.abs( 1 * arr2[ 3 ] );
  if( arr2[ 2 ] != "" ) pageTotal ++;
  pages[ cur ] = new coursePage( arr2[ 0 ], arr2[ 1 ], arr2[ 2 ], level, pageTotal );
  if( level > lastlevel && cur > 0 )
  {
   pages[ cur - 1 ].iMenu = -1; //prev.item has submenu, close it
  }
  lastlevel = level;
  
  var titlen = arr2[ 1 ].length; //title length
  if( titlen > maxwidth ) maxwidth = titlen;
 }
 maxwidth *= 11;
}
function createmenu()
{
 if( document.getElementById == null ) return;
 var objDiv = document.getElementById( "andr" );
 if( objDiv == null ) return;
 
 //start table for menu
 var s = ( '<table cellspacing="0" cellpadding="2" border="0" width="' + maxwidth + '"><tr><td>' ); 
 var iSkipLevel = 100;
 for( var i = 0; i < pages.length; i++ )
 { //check every page
  if( pages[ i ].level > iSkipLevel ) 
  { //hide 
  }
  else
  { //display with indent
   var iconspace = '<img border="0" height="16" width="' + ( pages[ i ].level * 16 ) + '" src="' + skin + 'm_space.gif">';
   var icon = '<img border="0" ';
   if( pages[ i ].iMenu == 0 ) 
   { //$ - non-free resourse
    if( pages[ i ].url.indexOf( "$" ) >= 0 ) icon += 'src="' + skin + 'm_pagebook$.gif">';
    else icon += 'src="' + skin + 'm_pagebook.gif">';
   }
   else if( pages[ i ].iMenu > 0 ) icon += 'src="' + skin + 'm_openbook.gif">';
   else icon += 'src="' + skin + 'm_closebook.gif">';
  
   s += ( iconspace + '<a class="menuel" href="#" id="page' + i + '" onclick="return loadpage(' + i + ')" onmouseover="displaystatus(event,this,' + i + ')" onmouseout="resetstatus(' + i + ')">' + icon + pages[ i ].title + '</a><br>' );
   
   if( pages[ i ].iMenu < 0 ) iSkipLevel = pages[ i ].level;
   else iSkipLevel = 100;
  }
 }
 s += '</td></tr></table>'; //end menu table
 
 //hint to display the status of menu item
 var hint = '<div class="hint" id="hint" style=""visibility:hidden;xposition:absolute;left:10;top:200;width:100;">bla-bla</div>"';
 s += hint;
 
 objDiv.innerHTML = s;
 
 lastSel = -1;
 yOffMenu = -1;
}
//display a stus of menu item
function displaystatus( e, elem, n )
{
 if( yOffMenu == -1 )
 { //calculate the y-offset of table with menu items
  var p = elem;
  while( p && p.nodeName != "BODY" )
  {
   if( p.nodeName == "DIV" || p.nodeName == "TABLE" )
   {
    yOffMenu += p.offsetTop;
   }
   p = p.parentNode;
  }
 }

 var stat = ( bEnglish == 1 ) ? "completed" : "завершен";
 var mylevel = pages[ n ].level;
 for( var i = n; i < pages.length; i++ )
 {
  if( i > n && pages[ i ].level >= 0 &&
   pages[ i ].level <= mylevel ) break; //upper menu item reached
  if( ( pages[ i ].stat == "i" || pages[ i ].stat == "f" ) &&
   pages[ i ].url != "" )
  {
   stat = ( bEnglish == 1 ) ? "incomplete" : "незавершен";
   break;
  }
 }
 if( document.getElementById )
 {
  var obj = document.getElementById( "hint" );
  if( obj != null ) 
  {
   obj.style.visibility='visible';
   obj.style.top = yOffMenu + elem.offsetTop + elem.offsetHeight + 3;
   obj.innerHTML = stat;
  }
 }
}
function resetstatus( n )
{
 if( document.getElementById )
 {
  var obj = document.getElementById( "hint" );
  if( obj != null ) obj.style.visibility='hidden';
 }
}
function findOpenedItem( n )
{ //find index of opened item
 var mylevel = pages[ n ].level;
 if( mylevel == 0 ) return n;

 var i, iVis = n; //assume it's visible
 for( i = n; i >= 0; i-- )
 {
  if( pages[ i ].level == mylevel - 1 )
  { //parent
   mylevel--;  
   if( pages[ i ].iMenu < 0 ) iVis = i; //submenu is invisible
   if( pages[ i ].level == 0 ) break;
  }
 } 
 return iVis;
}
//open all upper submenus of n
function extendmenuitem( n )
{ 
 var nextlevel = pages[ n ].level - 1, i;
 for( i = n; i >= 0; i-- )
 {
  if( pages[ i ].level == nextlevel )
  { //parent
   nextlevel--;  
   if( pages[ i ].iMenu < 0 ) pages[ i ].iMenu = 1; //submenu will be visible
   if( pages[ i ].level == 0 ) break;
  }
 } 
 return;
}
//select item - change color
function selectmenuitem( n )
{
 var i = n, obj;
 if( i == lastSel ) return; //already selected
 
 if( document.getElementById )
 {
  if( lastSel >= 0 )
  { //deselect
   obj = document.getElementById( "page" + lastSel );
   if( obj != null ) obj.className = oldClass;
  }
  //select
  obj = document.getElementById( "page" + i );
  if( obj != null ) 
  {
   oldClass = obj.className;
   obj.className = "menuact";
  }
 }
 lastSel = i;
}
//iForward: 1 - step forward, -1 - step back, 2 - open all external menus, 0 - show/hide
function loadpage( m )
{
 var bLoad = true, bUpdateMenu = false;
 var n = 1 * m;
 if( n < 0 || n >= pages.length ) 
 { //invalid index
  if( curPage >= 0 ) return false;
  n = 0;
  m = 0;
 }
 
 if( iForward == 1 || iForward == 2 )
 { //go down
  for( ; n < pages.length; n++ )
  { //url can be "" for menu item
   if( pages[ n ].url != "" ) break;
  }
 }
 if( iForward == -1 )
 { //go up
  for( ; n >= 0; n-- )
  { //url can be "" for menu item
   if( pages[ n ].url != "" ) break;
  }
 } 
 if( iForward == 0 && pages[ n ].iMenu != 0 )
 { //show/hide submenu
  { //don't navigate
   pages[ n ].iMenu = - pages[ n ].iMenu; //show/hide submenu
   bUpdateMenu = true; 
   if( pages[ n ].url == "" ) 
   {
    n = curPage;  
    bLoad = false;
   }
  }
 }
 if( n < 0 || n >= pages.length ) 
 {
  iForward = 0;
  return false; //invalid index
 }
 curPage = n;
 
 //open url
 var url = pages[ curPage ].url;
 if( bLoad ) 
 {  
  openit( url ); 
 }

 if( pages[ curPage ].stat == "i" && 
  url.indexOf( "test" ) < 0 )
 { //if resource is not a test and available then mark a page as completed
  pages[ curPage ].stat = "c";
 }
 
 if( !bUpdateMenu && n != findOpenedItem( n ) )
 { //new menu item is hidden 
  extendmenuitem( n );
  bUpdateMenu = true;
 }
 
 if( bUpdateMenu ) 
 {
  createmenu();
 }
 
 selectmenuitem( n );
 iForward = 0;
 return false;  //false to supress <a href> behavior
}
//remove leading and trailing spaces, \r, \n
function trimSpaces( buf )
{
 //remove leading spaces
 var len = buf.length;
 while( len > 0 ) 
 {
  var s = buf.charAt( 0 );
  if( s == " " || s == "\n" || s == "\r" ) 
  {
   len --;
   buf = buf.substr( 1, len );
  } 
  else break;
 }
 //remove trailing spaces
 while( len > 0 ) 
 {
  var s = buf.charAt( len - 1 );
  if( s == " " || s == "\n" || s == "\r" ) 
  {
   len --;
   buf = buf.substr( 0, len );
  } 
  else break;
 }
 return buf;
}
//open url in new window
function openit( url, w, h, scroll, resize, left, top )
{
 if( url == "" ) return;
 
 var thisloc = "" + window.location;
 if( "" + w == "undefined" ) w = 600;
 if( "" + h == "undefined" ) h = 350;
 if( "" + scroll == "undefined" ) scroll = 'yes';
 if( "" + resize == "undefined" ) resize = 'yes';
 if( "" + left == "undefined" ) {
  left = ( screen.width - w ) / 2;
  if( left < 0 ) left = 0;
 }
 if( "" + top == "undefined" ) {
  top = ( screen.height - h - 30 ) / 2;
  if( top < 0 ) top = 0;
 }

 var pos2 = url.toLowerCase().indexOf("http:"), loc = url;
 if( pos2 < 0 && url.charAt( 0 ) != "/" )
 { //relative path
  var pos = thisloc.lastIndexOf("/");
  if( pos >= 0 )
  {
   loc = thisloc.substring(0, pos+1) + url;
  }
 }
 
 if( loc == "" ) return;

 window.open( loc, '_blank', 'width=' + w + ',height=' + h + ',left=' + left + ',top=' + top + ',scrollbars=' + scroll + ',resizable=' + resize );
 return;
}
//load sample
function loadsample()
{
 if( pages.length > 0 ) return false;
 
 navdata = "1|JavaScript||0\n2|алгоритм SHA-1|sub002.htm|-1\n3|Построение гистограмм|sub003.htm|-1\n4|Как скопировать файл с CD-ROM'а в приложении MFC|sub001.htm|0";
 parsenavdata();
 createmenu();
 return false;
}
</script>
<div id="andr">Оглавление не загружено</div><a href="#" onclick="return loadsample()">Загрузить</a>


Переход к примеру.

Комментарии. Cтруктура оглавления хранится в переменной navdata как список строк в формате
"Идентификатор страницы | название страницы | url страницы | уровень вложенности".
Пример данных для оглавления из 4-х элементов.
navdata = "1|JavaScript||0\n2|алгоритм SHA-1|sub002.htm|-1\n3|Построение гистограмм|sub003.htm|-1\n4|Как скопировать файл с CD-ROM'а в приложении MFC|sub001.htm|0";
Анализ данных о структуре оглавления происходит в функции parsenavdata(). Переменная navdata расщепляется в массив arr. Каждый элемент массива есть одна строка описания структуры курса. Каждая непустая строка в свою очередь расщепляется в массив arr2, который используется для генерации объекта coursePage, который добавляется в конец массива pages.
Функция createmenu() создает код таблицы с оглавлением, а также элемент "hint". Каждый элемент оглавления реализуется как тег <A> c методами onclick="return loadpage()" onmouseover="displaystatus()" onmouseout="resetstatus()" Затем этот код "вставляется" в элемент документа с идентификатором "andr". Если уровень элемента меньше нуля, то вначале он не показывается (меню свернуто). Меню можно раскрыть с помощью клика. Следующий клик приведет к сворачиванию меню. Если url элемента оглавления непуст, то он запускается в новом окне с помощью функции openit(). При подведении курсора к элементу оглавления появляется подсказка (hint) о статусе элемента.

Для улучшения "обратной связи" приглашаю обсудить рассылку на форуме сайта http://www.pvobr.ru в разделе "Программирование". Пишите также, какие темы вы хотите рассмотреть в будущем.

Успехов!
Андрей

В избранное