3 * @description This a jQuery equivalent for Niceforms ( http://badboy.ro/articles/2007-01-30/niceforms/ ). All the forms are styled with beautiful images as backgrounds and stuff. Enjoy them!
4 * @param Hash hash A hash of parameters
5 * @option integer selectRightSideWidth width of right side of the select
6 * @option integer selectLeftSideWidth width of left side of the select
7 * @option integer selectAreaHeight
8 * @option integer selectAreaOPtionsOverlap
9 * @option imagesPath folder where custom form images are stored
11 * @cat Plugins/Interface/Forms
12 * @author Lucian Lature ( lucian.lature@gmail.com )
13 * @credits goes to Lucian Slatineanu ( http://www.badboy.ro )
16 * modified by naoki on 2009.3.18
22 selectRightSideWidth : 21,
23 selectLeftSideWidth : 8,
24 selectAreaHeight : 21,
25 selectAreaOptionsOverlap : 2,
26 imagesPath : "css/images/default/",
27 className : 'form' // default class name
31 selectText : 'please select',
32 preloads : new Array(),
33 // inputs : new Array(),
35 textareas : new Array(),
36 selects : new Array(),
38 checkboxes : new Array(),
40 buttons : new Array(),
41 radioLabels : new Array(),
42 checkboxLabels : new Array(),
45 keyPressed : function(event)
47 var pressedKey = event.charCode || event.keyCode || -1;
53 var fieldId = this.parentNode.parentNode.id.replace(/sarea/g, "");
55 var info = fieldId.split("-");
58 for(var q = 0; q < selects[index][no].options.length; q++) {if(selects[index][no].options[q].selected) {linkNo = q;}}
60 if(linkNo >= selects[index][no].options.length) {linkNo = 0;}
61 selectMe(selects[index][no].id, linkNo, fieldId);
67 var fieldId = this.parentNode.parentNode.id.replace(/sarea/g, "");
69 var info = fieldId.split("-");
72 for(var q = 0; q < selects[index][no].options.length; q++) {if(selects[index][no].options[q].selected) {linkNo = q;}}
74 if(linkNo < 0) {linkNo = selects[index][no].options.length - 1;}
75 selectMe(selects[index][no].id, linkNo, fieldId);
83 build : function(options)
86 jQuery.extend(jQuery.NiceJForms.options, options);
89 jQuery('body',document).bind('keyup', jQuery.NiceJForms.keyPressed);
91 jQuery(document).bind('keyup', jQuery.NiceJForms.keyPressed);
94 // test if images are disabled or not
95 var testImg = document.createElement('img');
96 $(testImg).attr("src", jQuery.NiceJForms.options.imagesPath + "blank.gif").attr("id", "imagineTest");
97 jQuery('body').append(testImg);
101 if(testImg.offsetWidth == '1') {jQuery.NiceJForms.hasImages = true;}
102 else {jQuery.NiceJForms.hasImages = false;}
107 if(jQuery.NiceJForms.hasImages)
111 // form class changed by naoki.
112 var name = 'form.' + jQuery.NiceJForms.options.className;
113 $(name).each( function()
116 jQuery.NiceJForms.preloadImages();
117 jQuery.NiceJForms.getElements(el, index);
118 jQuery.NiceJForms.replaceRadios(index);
119 jQuery.NiceJForms.replaceCheckboxes(index);
120 jQuery.NiceJForms.replaceSelects(index);
122 if (!$.browser.safari) {
123 jQuery.NiceJForms.replaceTexts(index);
124 jQuery.NiceJForms.replaceTextareas(index);
125 jQuery.NiceJForms.buttonHovers(index);
133 preloadImages: function()
135 jQuery.NiceJForms.preloads = $.preloadImages(jQuery.NiceJForms.options.imagesPath + "button_left_xon.gif", jQuery.NiceJForms.options.imagesPath + "button_right_xon.gif",
136 jQuery.NiceJForms.options.imagesPath + "input_left_xon.gif", jQuery.NiceJForms.options.imagesPath + "input_right_xon.gif",
137 jQuery.NiceJForms.options.imagesPath + "txtarea_bl_xon.gif", jQuery.NiceJForms.options.imagesPath + "txtarea_br_xon.gif",
138 jQuery.NiceJForms.options.imagesPath + "txtarea_cntr_xon.gif", jQuery.NiceJForms.options.imagesPath + "txtarea_l_xon.gif", jQuery.NiceJForms.options.imagesPath + "txtarea_tl_xon.gif", jQuery.NiceJForms.options.imagesPath + "txtarea_tr_xon.gif");
141 getElements: function(elm, index)
143 el = elm ? jQuery(elm) : jQuery(this);
145 var r = 0; var c = 0; var t = 0; var rl = 0; var cl = 0; var tl = 0; var b = 0;
147 // jQuery.NiceJForms.inputs = $('input', el);
148 jQuery.NiceJForms.labels[index] = $('label', el);
149 jQuery.NiceJForms.textareas[index] = $('textarea', el);
150 jQuery.NiceJForms.selects[index] = $('select', el);
151 jQuery.NiceJForms.radios[index] = $('input[type=radio]', el);
152 jQuery.NiceJForms.checkboxes[index] = $('input[type=checkbox]', el);
153 jQuery.NiceJForms.texts[index] = $('input[type=text]', el).add($('input[type=password]', el));
154 jQuery.NiceJForms.buttons[index] = $('input[type=submit]', el).add($('input[type=button]', el));
155 jQuery.NiceJForms.checkboxLabels[index] = new Array();
156 jQuery.NiceJForms.radioLabels[index] = new Array();
158 jQuery.NiceJForms.labels[index].each(function(i){
159 labelFor = $(jQuery.NiceJForms.labels[index][i]).attr("for");
160 jQuery.NiceJForms.radios[index].each(function(q){
161 if(labelFor == $(jQuery.NiceJForms.radios[index][q]).attr("id"))
163 if(jQuery.NiceJForms.radios[index][q].checked)
165 $(jQuery.NiceJForms.labels[index][i]).removeClass().addClass("chosen");
168 jQuery.NiceJForms.radioLabels[index][rl] = jQuery.NiceJForms.labels[index][i];
173 jQuery.NiceJForms.checkboxes[index].each(function(x){
174 if(labelFor == $(this).attr("id"))
178 $(jQuery.NiceJForms.labels[index][i]).removeClass().addClass("chosen");
180 jQuery.NiceJForms.checkboxLabels[index][cl] = jQuery.NiceJForms.labels[index][i];
187 replaceRadios: function(index)
191 jQuery.NiceJForms.radios[index].each(function(q){
193 $(this).removeClass().addClass('outtaHere'); //.hide(); //.className = "outtaHere";
195 var radioArea = document.createElement('div');
196 //console.info($(radioArea));
197 if(this.checked) {$(radioArea).removeClass().addClass("radioAreaChecked");} else {$(radioArea).removeClass().addClass("radioArea");};
199 radioPos = jQuery.iUtil.getPosition(this);
202 .attr({id: 'myRadio' + index + '-' + q})
203 .css({left: radioPos.x + 'px', top: radioPos.y + 'px', margin : '1px'})
204 .bind('click', {who: index + '-' + q}, function(e){self.rechangeRadios(e)})
205 .insertBefore($(this));
207 if (jQuery.NiceJForms.radioLabels[index][q]) $(jQuery.NiceJForms.radioLabels[index][q]).bind('click', {who: index + '-' + q}, function(e){self.rechangeRadios(e)});
209 if (!$.browser.msie) {
210 $(this).bind('focus', function(){self.focusRadios(q)}).bind('blur', function() {self.blurRadios(q)});
213 $(this).bind('click', {who: index + '-' + q}, function(e){self.radioEvent(e)});
219 changeRadios: function(who)
222 var info = e.data.who.split("-");
226 if(jQuery.NiceJForms.radios[index][no].checked) {
228 jQuery.NiceJForms.radios[index].each(function(q){
229 if($(this).attr("name") == $(jQuery.NiceJForms.radios[index][no]).attr("name"))
231 this.checked = false;
232 $(jQuery.NiceJForms.radioLabels[index][q]).removeClass();
235 jQuery.NiceJForms.radios[index][no].checked = true;
236 $(jQuery.NiceJForms.radioLabels[index][no]).addClass("chosen");
238 self.checkRadios(e.data.who);
242 rechangeRadios:function(e)
244 var info = e.data.who.split("-");
248 if(!jQuery.NiceJForms.radios[index][no].checked) {
249 for(var q = 0; q < jQuery.NiceJForms.radios[index].length; q++)
251 if(jQuery.NiceJForms.radios[index][q].name == jQuery.NiceJForms.radios[index][no].name)
253 jQuery.NiceJForms.radios[index][q].checked = false;
255 if (jQuery.NiceJForms.radioLabels[index][q]) jQuery.NiceJForms.radioLabels[index][q].className = "";
258 $(jQuery.NiceJForms.radios[index][no]).attr('checked', true);
259 if (jQuery.NiceJForms.radioLabels[index][no]) jQuery.NiceJForms.radioLabels[index][no].className = "chosen";
260 jQuery.NiceJForms.checkRadios(e.data.who);
264 checkRadios: function(who)
266 var info = who.split("-");
270 $('div').each(function(q){
271 if($(this).is(".radioAreaChecked") && $(this).next().attr("name") == $(jQuery.NiceJForms.radios[index][no]).attr("name")) {$(this).removeClass().addClass("radioArea");}
273 $('#myRadio' + who).toggleClass("radioAreaChecked");
276 focusRadios: function(who) {
277 $('#myRadio' + who).css({border: '1px dotted #333', margin: '0'}); return false;
280 blurRadios:function(who) {
281 $('#myRadio' + who).css({border: 'none', margin: '1px'}); return false;
284 radioEvent: function(e) {
286 if (!e) var e = window.event;
287 var info = e.data.who.split("-");
291 if(e.type == "click") {
292 for (var q = 0; q < jQuery.NiceJForms.radios[index].length; q++) {
293 if(this == jQuery.NiceJForms.radios[index][q]) {
294 self.changeRadios(q); break;
300 replaceCheckboxes: function (index)
304 jQuery.NiceJForms.checkboxes[index].each(function(q){
305 //move the checkboxes out of the way
306 $(jQuery.NiceJForms.checkboxes[index][q]).removeClass().addClass('outtaHere');
308 var checkboxArea = document.createElement('div');
310 //console.info($(radioArea));
311 if(jQuery.NiceJForms.checkboxes[index][q].checked) {$(checkboxArea).removeClass().addClass("checkboxAreaChecked");} else {$(checkboxArea).removeClass().addClass("checkboxArea");};
313 checkboxPos = jQuery.iUtil.getPosition(jQuery.NiceJForms.checkboxes[index][q]);
316 .attr({id: 'myCheckbox' + index + '-' + q})
318 left: checkboxPos.x + 'px',
319 top: checkboxPos.y + 'px',
322 .bind('click', {who: index + '-' + q}, function(e){self.rechangeCheckboxes(e)})
323 .insertBefore($(jQuery.NiceJForms.checkboxes[index][q]));
325 if(!$.browser.safari)
327 $(jQuery.NiceJForms.checkboxLabels[index][q]).bind('click', {who: index + '-' + q}, function(e){self.changeCheckboxes(e)})
330 $(jQuery.NiceJForms.checkboxLabels[index][q]).bind('click', {who: index + '-' + q}, function(e){self.rechangeCheckboxes(e)})
335 $(jQuery.NiceJForms.checkboxes[index][q]).bind('focus', {who: index + '-' + q}, function(e){self.focusCheckboxes(e)});
336 $(jQuery.NiceJForms.checkboxes[index][q]).bind('blur', {who: index + '-' + q}, function(e){self.blurCheckboxes(e)});
339 //$(jQuery.NiceJForms.checkboxes[index][q]).keydown(checkEvent);
344 rechangeCheckboxes: function(e)
346 var info = e.data.who.split("-");
351 if($(jQuery.NiceJForms.checkboxLabels[index][no]).is(".chosen")) {
353 $(jQuery.NiceJForms.checkboxLabels[index][no]).removeClass();
355 else if(jQuery.NiceJForms.checkboxLabels[index][no].className == "") {
357 $(jQuery.NiceJForms.checkboxLabels[index][no]).addClass("chosen");
359 jQuery.NiceJForms.checkboxes[index][no].checked = tester;
360 jQuery.NiceJForms.checkCheckboxes(e.data.who, tester);
363 checkCheckboxes: function(who, action)
365 var what = $('#myCheckbox' + who);
366 if(action == true) {$(what).removeClass().addClass("checkboxAreaChecked");}
367 if(action == false) {$(what).removeClass().addClass("checkboxArea");}
370 focusCheckboxes: function(who)
372 var what = $('#myCheckbox' + who);
375 border : "1px dotted #333",
381 changeCheckboxes: function(e)
383 var info = e.data.who.split("-");
387 //console.log('changeCheckboxes who is ' + who);
388 if($(jQuery.NiceJForms.checkboxLabels[index][no]).is(".chosen")) {
389 jQuery.NiceJForms.checkboxes[index][no].checked = true;
390 $(jQuery.NiceJForms.checkboxLabels[index][no]).removeClass();
391 jQuery.NiceJForms.checkCheckboxes(e.data.who, false);
393 else if(jQuery.NiceJForms.checkboxLabels[index][no].className == "") {
394 jQuery.NiceJForms.checkboxes[index][no].checked = false;
395 $(jQuery.NiceJForms.checkboxLabels[index][no]).toggleClass("chosen");
396 jQuery.NiceJForms.checkCheckboxes(e.data.who, true);
400 blurCheckboxes: function(who)
402 var what = $('#myCheckbox' + who);
411 replaceSelects: function(index)
415 jQuery.NiceJForms.selects[index].each(function(q){
416 //create and build div structure
417 var selectArea = document.createElement('div');
418 var left = document.createElement('div');
419 var right = document.createElement('div');
420 var center = document.createElement('div');
421 var button = document.createElement('a');
422 var text = document.createTextNode(jQuery.NiceJForms.selectText);
423 var widthStr = this.className.replace(/width_/g, "");
424 if (!widthStr) widthStr = jQuery.NiceJForms.options.selectWidth;
425 var selectWidth = parseInt(widthStr);
428 .attr({id:'mySelectText' + index + '-' + q})
429 .css({width: selectWidth - 10 + 'px'});
431 .attr({id:'sarea' + index + '-' + q})
433 width: selectWidth + jQuery.NiceJForms.options.selectRightSideWidth + jQuery.NiceJForms.options.selectLeftSideWidth + 'px'
435 .addClass("selectArea");
439 width : selectWidth + jQuery.NiceJForms.options.selectRightSideWidth + jQuery.NiceJForms.options.selectLeftSideWidth + 'px',
440 marginLeft : - selectWidth - jQuery.NiceJForms.options.selectLeftSideWidth + 'px',
443 .addClass("selectButton")
444 .bind('click', {who: index + '-' + q}, function(e){self.showOptions(e)})
445 .keydown(jQuery.NiceJForms.keyPressed);
447 jQuery(left).addClass("left");
448 jQuery(right).addClass("right").append(button);
449 jQuery(center).addClass("center").append(text);
451 jQuery(selectArea).append(left).append(right).append(center).insertBefore(this);
452 //hide the select field
455 //build & place options div
456 var optionsDiv = document.createElement('div');
457 selectAreaPos = jQuery.iUtil.getPosition(selectArea);
460 .attr({id:"optionsDiv" + index + '-' + q})
462 width : selectWidth + 1 + 'px',
463 left : selectAreaPos.x + 'px',
464 top : selectAreaPos.y + jQuery.NiceJForms.options.selectAreaHeight - jQuery.NiceJForms.options.selectAreaOptionsOverlap + 'px'
466 .addClass("optionsDivInvisible");
468 //get select's options and add to options div
469 $(jQuery.NiceJForms.selects[index][q]).children().each(function(w){
470 var optionHolder = document.createElement('p');
471 var optionLink = document.createElement('a');
472 var optionTxt = document.createTextNode(jQuery.NiceJForms.selects[index][q].options[w].text);
476 .css({cursor:'pointer'})
478 .bind('click', {who: index + '-' + q, id:jQuery.NiceJForms.selects[index][q].id, option:w, select: index + '-' + q}, function(e){self.showOptions(e);self.selectMe(jQuery.NiceJForms.selects[index][q].id, w, index + '-' + q)});
480 jQuery(optionHolder).append(optionLink);
481 jQuery(optionsDiv).append(optionHolder);
483 //check for pre-selected items
484 if(jQuery.NiceJForms.selects[index][q].options[w].selected) {self.selectMe($(jQuery.NiceJForms.selects[index][q]).attr("id"), w, index + '-' + q);}
487 jQuery('body').append(optionsDiv);
491 selectMe: function(selectFieldId, linkNo, selectNo) {
492 selectField = $('#' + selectFieldId);
493 sFoptions = selectField.children();
494 var valueChanged = false;
495 selectField.children().each(function(k){
497 if (sFoptions[k].selected == "") valueChanged = true;;
498 sFoptions[k].selected="selected";
500 sFoptions[k].selected = "";
504 textVar = $("#mySelectText" + selectNo);
505 var newText = document.createTextNode($(sFoptions[linkNo]).text());
506 textVar.empty().append(newText);
508 if (valueChanged) selectField.change();
511 showOptions: function(e) {
513 $("#optionsDiv"+e.data.who).toggleClass("optionsDivVisible").toggleClass("optionsDivInvisible").mouseout(function(e){self.hideOptions(e)});
516 hideOptions: function(e) {
517 if (!e) var e = window.event;
518 var reltg = (e.relatedTarget) ? e.relatedTarget : e.toElement;
519 if(((reltg.nodeName != 'A') && (reltg.nodeName != 'DIV')) || ((reltg.nodeName == 'A') && (reltg.className=="selectButton") && (reltg.nodeName != 'DIV'))) {this.className = "optionsDivInvisible";};
520 e.cancelBubble = true;
521 if (e.stopPropagation) e.stopPropagation();
524 replaceTexts: function(index) {
525 jQuery.NiceJForms.texts[index].each(function(q){
526 $(jQuery.NiceJForms.texts[index][q]).css({width:this.size * 10 + 'px'});
527 var txtLeft = new Image();
529 .attr({src:jQuery.NiceJForms.options.imagesPath + "input_left.gif"})
530 .addClass("inputCorner");
532 var txtRight = new Image();
534 .attr({src:jQuery.NiceJForms.options.imagesPath + "input_right.gif"})
535 .addClass("inputCorner");
537 $(jQuery.NiceJForms.texts[index][q]).before(txtLeft).after(txtRight).addClass("textinput");
540 $(jQuery.NiceJForms.texts[index][q]).focus(function(){$(this).addClass("textinputHovered");$(this).prev().attr('src', jQuery.NiceJForms.options.imagesPath + "input_left_xon.gif");$(this).next().attr('src', jQuery.NiceJForms.options.imagesPath + "input_right_xon.gif");});
542 $(jQuery.NiceJForms.texts[index][q]).blur(function() {$(this).removeClass().addClass("textinput");$(this).prev().attr('src', jQuery.NiceJForms.options.imagesPath + "input_left.gif");$(this).next().attr('src', jQuery.NiceJForms.options.imagesPath + "input_right.gif");});
546 replaceTextareas: function(index)
548 jQuery.NiceJForms.textareas[index].each(function(q){
550 var where = $(this).parent();
551 var where2 = $(this).prev();
553 // ##### textarea removed problem fixed by naoki. #####
554 var insertPos = document.createElement('span');
555 $(this).before(jQuery(insertPos));
557 $(this).css({width: $(this).attr("cols") * 10 + 'px', height: $(this).attr("rows") * 10 + 'px'});
559 var container = document.createElement('div');
561 .css({width: jQuery.NiceJForms.textareas[index][q].cols * 10 + 20 + 'px', height: jQuery.NiceJForms.textareas[index][q].rows * 10 + 20 + 'px'})
562 .addClass("txtarea");
564 var topRight = document.createElement('div');
565 jQuery(topRight).addClass("tr");
567 var topLeft = new Image();
568 jQuery(topLeft).attr({src: jQuery.NiceJForms.options.imagesPath + 'txtarea_tl.gif'}).addClass("txt_corner");
570 var centerRight = document.createElement('div');
571 jQuery(centerRight).addClass("cntr");
572 var centerLeft = document.createElement('div');
573 jQuery(centerLeft).addClass("cntr_l");
575 if(!$.browser.msie) {jQuery(centerLeft).height(jQuery.NiceJForms.textareas[index][q].rows * 10 + 10 + 'px')}
576 else {jQuery(centerLeft).height(jQuery.NiceJForms.textareas[index][q].rows * 10 + 12 + 'px')};
578 var bottomRight = document.createElement('div');
579 jQuery(bottomRight).addClass("br");
580 var bottomLeft = new Image();
581 jQuery(bottomLeft).attr({src: jQuery.NiceJForms.options.imagesPath + 'txtarea_bl.gif'}).addClass('txt_corner');
584 jQuery(topRight).append(topLeft);
585 jQuery(centerRight).append(centerLeft).append(jQuery.NiceJForms.textareas[index][q]);
586 jQuery(bottomRight).append(bottomLeft);
587 jQuery(container).append(topRight).append(centerRight).append(bottomRight);
589 // ##### textarea removed problem fixed by naoki. #####
590 //jQuery(where2).before(container);
591 jQuery(insertPos).after(container);
594 $(jQuery.NiceJForms.textareas[index][q]).focus(function(){$(this).prev().removeClass().addClass("cntr_l_xon"); $(this).parent().removeClass().addClass("cntr_xon"); $(this).parent().prev().removeClass().addClass("tr_xon"); $(this).parent().prev().children(".txt_corner").attr('src', jQuery.NiceJForms.options.imagesPath + "txtarea_tl_xon.gif"); $(this).parent().next().removeClass().addClass("br_xon"); $(this).parent().next().children(".txt_corner").attr('src', jQuery.NiceJForms.options.imagesPath + "txtarea_bl_xon.gif")});
595 $(jQuery.NiceJForms.textareas[index][q]).blur(function(){$(this).prev().removeClass().addClass("cntr_l"); $(this).parent().removeClass().addClass("cntr"); $(this).parent().prev().removeClass().addClass("tr"); $(this).parent().prev().children(".txt_corner").attr('src', jQuery.NiceJForms.options.imagesPath + "txtarea_tl.gif"); $(this).parent().next().removeClass().addClass("br"); $(this).parent().next().children(".txt_corner").attr('src', jQuery.NiceJForms.options.imagesPath + "txtarea_bl.gif")});
599 buttonHovers: function(index) {
600 jQuery.NiceJForms.buttons[index].each(function(i){
601 $(this).addClass("buttonSubmit");
602 var buttonLeft = document.createElement('img');
603 jQuery(buttonLeft).attr({src: jQuery.NiceJForms.options.imagesPath + "button_left.gif"}).addClass("buttonImg");
605 $(this).before(buttonLeft);
607 var buttonRight = document.createElement('img');
608 jQuery(buttonRight).attr({src: jQuery.NiceJForms.options.imagesPath + "button_right.gif"}).addClass("buttonImg");
610 if($(this).next()) {$(this).after(buttonRight)}
611 else {$(this).parent().append(buttonRight)};
614 function(){$(this).attr("class", $(this).attr("class") + "Hovered"); $(this).prev().attr("src", jQuery.NiceJForms.options.imagesPath + "button_left_xon.gif"); $(this).next().attr("src", jQuery.NiceJForms.options.imagesPath + "button_right_xon.gif")},
615 function(){$(this).attr("class", $(this).attr("class").replace(/Hovered/g, "")); $(this).prev().attr("src", jQuery.NiceJForms.options.imagesPath + "button_left.gif"); $(this).next().attr("src", jQuery.NiceJForms.options.imagesPath + "button_right.gif")}
621 jQuery.preloadImages = function()
623 var imgs = new Array();
624 for(var i = 0; i<arguments.length; i++)
626 imgs[i] = jQuery("<img>").attr("src", arguments[i]);
633 getPosition : function(e)
637 var restoreStyle = false;
639 if (jQuery(e).css('display') == 'none') {
640 oldVisibility = es.visibility;
641 oldPosition = es.position;
642 es.visibility = 'hidden';
643 es.display = 'block';
644 es.position = 'absolute';
649 x += el.offsetLeft + (el.currentStyle && !jQuery.browser.opera ?parseInt(el.currentStyle.borderLeftWidth)||0:0);
650 y += el.offsetTop + (el.currentStyle && !jQuery.browser.opera ?parseInt(el.currentStyle.borderTopWidth)||0:0);
651 el = el.offsetParent;
654 while (el && el.tagName && el.tagName.toLowerCase() != 'body')
656 x -= el.scrollLeft||0;
657 y -= el.scrollTop||0;
662 es.position = oldPosition;
663 es.visibility = oldVisibility;
667 getPositionLite : function(el)
671 x += el.offsetLeft || 0;
672 y += el.offsetTop || 0;
673 el = el.offsetParent;