среда, 8 июля 2009 г.

DHTML Tab Connection Panel

Результат выполнения еще одного «тестового задания». ..И в который раз затрудняюсь с выбором кавычек. Выкладываю коды здесь, вдруг да когда пригодится. Использовался фреймворк Prototype. Сам текст задания, возможно, выложу позже.


Скриншот



Известные проблемы и ограничения в рамках тестового задания


  • при добавления большого количества вкладок отсутствуют боковые границы рядов;
  • на странице main.html не отслеживается обновление (через F5) страницы child.html
  • в IE 6 было замечено вертикальное смещение правой границы динамически созданных вкладок;
  • на странице child.html не отслеживается наличие открывающего документа document.opener (main.html);
  • скрипт бесполезно проверять инструментами вроде IETester. Как правило, эти инструменты не отслеживают document.opener;
  • отключена возможность удаления активной вкладки с помощью CSS, отсутствует контроль этого процесса средствами javascript.

Изображения




Изображения получены из открытых источников


main.html


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html dir='ltr' xmlns='http://www.w3.org/1999/xhtml'>
<head>
<title>Main Window</title>
<link type='text/css' rel='stylesheet' href='public/stylesheets/general.css' />
<script type="text/javascript" src="public/javascripts/prototype.js"></script>
<script type="text/javascript" src="public/javascripts/main.js"></script>
</head>
<body>
<p><input id="btnOpen" type="button" value="Изменить..." onclick="return btnOpen_onclick();" /></p>
<div id="connections"></div>
</body>
</html>


child.html


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>DHTML Tab Connection Panel</title>
<script type="text/javascript" src="public/javascripts/prototype.js"></script>
<link type='text/css' rel='stylesheet' href='public/stylesheets/general.css' />
<script type="text/javascript" src="public/javascripts/child.js"></script>
</head>
<body>
<p id="navBar">
<input id="btnAdd" type="button" tabindex="1" value="Добавить" onclick="tabView.addControl(); return false;" />
<a id="aTabPrev" href="#" tabindex="2" onclick="tabView.tabPrev(); return false;">←</a>
<kbd>Ctrl</kbd>
<a id="aTabNext" href="#" tabindex="3" onclick="tabView.tabNext(); return false;">→</a>
</p>

<div id="tabView1" class="tabView">
<div id="tabPanel" class="tabPane">
<div id="tabViewTab_0" class="tabActive" style="left: 0px;" onclick="tabView.tabSelect(0); return false;">
<div class="icon"></div>
<span tabindex="4"><span id="tabText_0" class="tabText">Guest</span>
<a href="#" onclick="tabView.destroy(0); return false;"><img class="close" width="8" height="8" src="public/images/icon_delete.gif" /></a>
</span>
<img class="tab_right" src="public/images/tab_right.gif" />
</div>
</div>
<div class="clear"></div>

<div id="aFormCont" class="aTab">
<form id="aForm" action="">
<input id="index" type="hidden" />
<div>Логин</div>
<div><input id="login" type="text" onchange="tabView.onChange();" /></div>
<div>Пароль</div>
<div><input id="password" type="password" onchange="tabView.onChange();" /></div>
<div><input id="remember_login" type="checkbox" onchange="tabView.onChange();" /><label for="remember_login">Запомнить логин</label></div>
<div><input id="remember_password" type="checkbox" onchange="tabView.onChange();" /><label for="remember_password">Запомнить пароль</label></div>
<div><input id="auto_login" type="checkbox" onchange="tabView.onChange();" /><label for="auto_login">Входить автоматически</label></div>
<p style="text-align:right;">
<input id="button_submit" type="button" value="Подключить" onclick="tabView.onChangeStatus();" />
</p>
</form>
</div>
</div>
</body>
</html>

general.css


/* General */
body{
margin:10px;
font-size:0.9em;
}

/* Main */
#connections p {
border:1px solid #919B9C;
padding: 0 0.2em;
width: 500px;
font-family: Trebuchet MS, Lucida Sans Unicode, Arial, sans-serif;
}

/* Child */
a{
color:#F00;
}
.tabView {
width:500px;
height:400px;
}
.tabPane{
height:21px; /* Height of tabs */
border-bottom:1px solid #919b9c;
}
.aTab{
border-left:1px solid #919b9c;
border-right:1px solid #919b9c;
border-bottom:1px solid #919b9c;
font-family: Trebuchet MS, Lucida Sans Unicode, Arial, sans-serif;
padding:5px;

}
.tabPane div{
float:left;
height:100%; /* Height of tabs */
padding-left:3px;
vertical-align:middle;
background-repeat:no-repeat;
background-position:bottom left;
cursor:pointer;
position:relative;
bottom:-1px;
margin-left:0px;
margin-right:0px;
}
.tabPane .tabActive{
background-image:url('../images/tab_left_active.gif');
margin-left:0px;
margin-right:0px;
z-index:10;
}
.tabPane .tabInactive{
background-image:url('../images/tab_left_inactive.gif');
margin-left:0px;
margin-right:0px;
z-index:10;
}
.tabPane .inactiveTabOver{
background-image:url('../images/tab_left_over.gif');
margin-left:0px;
margin-right:0px;
}
.tabPane span{
font-family:arial;
vertical-align:top;
font-size:11px;
padding-left:3px;
padding-right:3px;
line-height:21px;
float:left;
}
.tabPane .tabActive span{
padding-bottom:1px;
line-height:20px;
position: relative;
padding-left: 4px;
padding-right: 12px;
}
.tabPane .tabInactive span{
padding-bottom:1px;
line-height:20px;
position: relative;
padding-left: 3px;
padding-right:10px;
}
.tabPane img{
float:left;
}
.tabPane .tabActive img.tab_right {
background-image:url(../images/tab_right_active.gif);
}
.tabPane .tabInactive img.tab_right {
background-image:url(../images/tab_right_inactive.gif);
}
.tabActive a {
display:none;
}
.icon {
background-image:url(../images/globe_icon.gif);
background-repeat:no-repeat;
background-position:bottom left;
width:15px;
}
img.close {
position: absolute;
top: 6px;
right: 0;
border:0;
}
a img.close {
opacity:.01;
filter: alpha(opacity=1); /* IE */
-moz-opacity: 0.01; /* Firefox */
}
a:hover img.close {
opacity:1;
filter: alpha(opacity=100); /* IE */
-moz-opacity: 1; /* Firefox */
}
.tabText{
min-width:30px;
display:inline-block;
}
.clear {
clear: both;
}

/* Navigation Bar */
#navBar {
width:500px;
text-align:right;
}
#navBar input {
float:left;
}
#navBar a {
text-decoration:none;
}
#navBar kbd {
font-size:larger;
}

/* Form elements */
input[type="text"], input[type="password"] {border:1px solid #919b9c;}
/* for IE */
input { border:expression((this.type=='text')||(this.type=='password') ? '1px solid #919b9c' : ''); }

main.js


// <!CDATA[
var pPrefix = "connection_";
var prefixLogin = "login_";

document.addConnection = addConnection;
document.updateConnection = updateConnection;
document.destroyConnection = destroyConnection;
document.updateConnectionStatus = updateConnectionStatus;

/* Open Child document */
function btnOpen_onclick(){
open("child.html", "Child");
}

/* Helpers */
bool2human = function(value){
return (value == true) ? "да" : "нет";
}

humanStatus = function(value){
return (value == true) ? "Подключен" : "Отключен";
}

/* Dynamic template for #connections P.innerHTML */
getTemplate = function(attr){
return 'Логин: <span class="login">'+ attr.login +'</span><br />'+
'Пароль: <span class="password">'+ attr.password +'</span><br />'+
'Запомнить: логин (<span class="remember_login">'+ bool2human(attr.remember_login) +'</span>),'+
' пароль (<span class="remember_password">'+ bool2human(attr.remember_password) +'</span>)<br />'+
'Входить автоматически (<span class="auto_login">'+ bool2human(attr.auto_login) +'</span>)<br />'+
'<span class="status">'+ humanStatus(attr.status) +'</span>';
}

/* functions called by Child document */
function addConnection(attr){
try {
var p1 = document.createElement("p");
p1.id = pPrefix + attr.index;
var template = getTemplate(attr);
p1.innerHTML = template;
$("connections").appendChild(p1);
}
catch (e) {
alert(e.description);
}
}

function updateConnection(attr){
try {
var p1 = $(pPrefix + attr.index);
var template = getTemplate(attr);
p1.innerHTML = template;
}
catch (e) {
alert(e.description);
}
}

function destroyConnection(index){
var p1 = $(pPrefix + index);
Element.remove(p1);
}

function updateConnectionStatus(index, status){
var cssExpr = $$("#"+ pPrefix + index +" .status");
cssExpr[0].innerHTML = humanStatus(status);
}
// ]]>

child.js


document.onkeydown = navigateTabs;

function navigateTabs (event){
if (window.event) event = window.event;
if (event.ctrlKey){
switch (event.keyCode ? event.keyCode : event.which ? event.which : null){
case 0x25:
tabView.tabPrev();
break;
case 0x27:
tabView.tabNext();
break;
}
}
}

/* Class: Tab View */
TabView = function(){
this.tabPrefix = "tabViewTab_";
this.tabTextPrefix = "tabText_";
this.tabPanelId = "tabPanel";
this.items = new Array();
this.cnActive = "tabActive";
this.cnInactive = "tabInactive";
this.patternTabActive = "#tabView1 ." + this.cnActive;
this.tabIndexShift = 4; //сдвиг tabindex

this.init = function(){
this.add("Guest", "guest", true, true, false, false);
}

this.destroy = function(index){
if ( $(this.tabPrefix + index) != undefined ){
this.items[index] = null;
this.destroyConnection(index);
Element.remove(this.tabPrefix + index);
}
}

this.add = function(login, password, remember_login, remember_password, auto_login, status){
var index = this.items.length;
var Item = new Object();
Item.index = index;
Item.login = login;
Item.password = password;
Item.remember_login = remember_login;
Item.remember_password = remember_password;
Item.auto_login = auto_login;
Item.status = status;
this.items.push(Item);
this.addConnection(Item);
}

/* Functions for Document.Opener */
this.addConnection = function(obj){
opener.document.addConnection(obj);
}
this.updateConnection = function(obj){
opener.document.updateConnection(obj);
}
this.destroyConnection = function(index){
opener.document.destroyConnection(index);
}
this.updateConnectionStatus = function(index, status){
opener.document.updateConnectionStatus(index, status);
}

/* Helpers */
this.humanStatus = function(value){
return (value == true) ? "Отключить" : "Подключить";
}

this.checkbox2bool = function(value){
return (value == null) ? false : true;
}

/* DOM Functions */
this.addControl = function(){
this.add("", "", false, false, false, false);

var index = this.items.length-1;
var p1 = document.createElement("p");
var div1 = document.createElement("div");
div1.id = this.tabPrefix + index;
div1.className = this.cnInactive;//;
div1.onclick = function(){ tabView.tabSelect(index); return false; };

var div2 = document.createElement("div");
div2.className = "icon";
div1.appendChild(div2);

var span1 = document.createElement("span");
span1.setAttribute("tabindex", index*1 + this.tabIndexShift);

var span2 = document.createElement("span");
span2.id = this.tabTextPrefix + index;
span2.className = "tabText";
span1.appendChild(span2);

var a1 = document.createElement("a");
a1.href = "#";
a1.onclick = function(){ tabView.destroy(index); return false; };

var img1 = document.createElement("img");
img1.className = "close";
img1.setAttribute("width", 8);
img1.setAttribute("height", 8);
img1.setAttribute("src", "public/images/icon_delete.gif");
a1.appendChild(img1);
span1.appendChild(a1);

var img2 = document.createElement("img");
img2.className = "tab_right";
img2.setAttribute("src", "public/images/tab_right.gif");

div1.appendChild(span1);
div1.appendChild(img2);
$(this.tabPanelId).appendChild(div1);
}

this.fillForm = function(obj){
$("index").value = obj.index;
$("login").value = obj.login;
$("password").value = obj.password;
$("remember_login").checked = obj.remember_login;
$("remember_password").checked = obj.remember_password;
$("auto_login").checked = obj.auto_login;
$("button_submit").value = this.humanStatus(obj.status);
}

this.tabSelect = function(index){
//scope for each() function
cnActive = this.cnActive;
cnInactive = this.cnInactive;
if ( $(this.tabPrefix + index) != undefined ){
//off
$$(this.patternTabActive).each( function(elem){
Element.removeClassName(elem, cnActive);
Element.addClassName(elem, cnInactive);
});
//on
Element.removeClassName(this.tabPrefix + index, this.cnInactive);
Element.addClassName(this.tabPrefix + index, this.cnActive);
this.fillForm(this.items[index]);
return true;
}
return false;
}

/* onModyfy functions */
this.onChange = function(){
var index = $F("index");

var Item = this.items[index];
Item.index = index;
Item.login = $F("login");
Item.password = $F("password");
Item.remember_login = this.checkbox2bool( $F("remember_login") );
Item.remember_password = this.checkbox2bool( $F("remember_password") );
Item.auto_login = this.checkbox2bool( $F("auto_login") );

$(this.tabTextPrefix + index).innerHTML = Item.login;

this.updateConnection(Item);
}

this.onChangeStatus = function(){
var index = $F("index");
var Item = this.items[index];
Item.status = (Item.status == true) ? false : true;
this.updateConnectionStatus(Item.index, Item.status);
$("button_submit").value = this.humanStatus(Item.status);
}

/* Navigation through Tabs */
this.tabPrev = function(){
var index = $F("index");
for (var i = index*1-1; i >= this.items.first().index; i--){
if (this.tabSelect(i)){
break;
}
}
}

this.tabNext = function(){
var index = $F("index");
for (var i = index*1+1; i <= this.items.last().index; i++){
if (this.tabSelect(i)){
break;
}
}
}

this.init();
}
// End: Class TabView

// Init tabView
tabView = new TabView();
Event.observe(window, "load", function() {
tabView.fillForm(tabView.items[0]);
});

Вот как-то так :).


Creative Commons License
DHTML Tab Connection Panel by JCDen (aka Croaker) is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License.

7 Комментариев :

Michael de`Oz комментирует...

И где пример как это работает?
А на блог табы ставить не пробовал?

Croaker комментирует...

Я думаю, оно уже используется у тех мудаков, которым я это якобы «тестовое задание» отсылал.
Чтобы самому посмотреть, можно собрать локально. На блоге если табы ставить, под них нужен какой-то дизайн, на что времени пока нет; да, честно говоря, и не нужны они мне здесь.
Если ты именно про эти табы, то имхо для блога в них мало полезного – уж под больно специфическую задачу они заточены :).

Michael de`Oz комментирует...

Понятно.
А не знаешь как можно сделать такие вот табы - нет ли у тебя где можно почитать какой-нибудь учебник что бы понять как это делать, по шагово?

Croaker комментирует...

О! Известный сайт CSSplay ). Насколько я знаю, автор охотно делится своими примерами. Единственное требования – сохранять копирайты в коде.

Если нужен код:
Просто посмотри исходник страницы. Стили CSS для табов прямо в коде страницы, внутри тегов <style type="text/css">...</style> (там где указаны копирайты, которые автор просит не убирать: "This copyright notice must be untouched at all times...").

HTML-код, понятно, там же. Для первого меню внутри <div class="menu">...</div>; для второго, соответственно, внутри <div class="menu2">...</div>.

Это если вкратце. Как работает, опять же вкратце в следующем комментарии.

Croaker комментирует...

Меню сделано на чистом CSS (без использования javascript)
Для скрытия/отображения спользуется псевдокласс :hover. Когда элемент горизонтального списка в «состоянии покоя», элементы внутри него имеют свойства CSS position:absolute; left:-9999px; height:0; (абсолютно позиционированы и смещены влево на 9999 пикселей, высота 0 - не отображаются в окне браузера), при наведении мыши на элемент списка (для псевдокласса :hover), элементы внутри него «приобретают» свойства left:0; height:auto; (без смещения влево, высота автоматически).

Для прокрутки по вертикали задается фиксированная высота (height) и overflow:auto;

Вообще, пример по большей части сложный из-за желания заставить его нормально работать в столь любимом всеми разработчиками браузере Internet Explorer.

Michael de`Oz комментирует...

Спасибо.
В коде покопаюсь. Просто там кроме этого ещё много чего есть - нужный код надо постараться ещё выловить.

Michael de`Oz комментирует...

Получилось!
вставил на тестовый блог
После отладки вставлю на основной

Отправить комментарий

Жги!