ארכיון פוסטים מהקטגוריה "JavaScript"

אירועי DOM בתחביר דמוי #C

יום רביעי, 10 בינואר, 2007
btn.Click+=new EventHandler(btn_Click);
btn.Click+=new EventHandler(btn_Click2);

btn.Click-=new EventHandler(btn_Click);

page.Load+=new EventHandler(page_Load);

מכירים את הסינטקס? כך מצרפים/מורידים אירוע למאזין ב-#C. ניתן ליישם סינטקס זה גם ב-JavaScript בצד לקוח.

בעת כתיבת הסקריפט נתקלתי בכמה קשיים. += היא פעולה המחברת בין מספרים ומשרשרת מחרוזות. שלא כמו ב-#C, אי אפשר לגרום לה לעשות משהו אחר. מכאן נובע, שחייבים להשתמש במספרים או מחרוזות כדי לסכם את כל ה-EventHanlerים המשוייכים לאלמנט. החלטתי על מספרים.

מכיוון שמחלקות מחזירות את עצמן בביטוי ה-new, יש למצוא שיטה להחזיר מספר. השיטה היא שכתוב מתודת valueOf. מתודת ה-valueOf הינה מתודה פנימית שמופעלת בכל פעם שקוראים לערך של אובייקט, כמו למשל בפעולת חיבור, חיסור וכו' (כמו מתודת toString שנקראת אוטומטית בשרשור מחרוזות). הערך שהמתודה תחזיר הוא הערך שיוחזר מביטוי ה-new אל תוך פעולת החיבור או החיסור.

הערך עצמו צריך להיות מזהה ייחודי. השתמשתי בשיטה "בינארית" הבנויה מקומבינציות של חזקות של 2.

קצת על השיטה הבינארית

השיטה מאפשרת למספר פריטים בחזקות של 2 – 1, 2, 4, 8, 16 וכו'. כל חיבור של מספרים אלו ייתן קומבינציה אחת בלבד.
דוגמה:
13 = 8+4+1
26 = 16+8+2
כל חזקה של 2 מייצגת "ביט" במיקום ספציפי.
הביט הראשון הוא 0 והמספר המייצג אותו הוא 1 (2 בחזקת 0)
הביט השני הוא 1 והמספר המייצג אותו הוא 2 (2 בחזקת 1)
הביט השלישי הוא 2 והמספר המייצג אותו הוא 4 (2 בחזקת 2)
וכן הלאה…
כאשר ביט אינו פעיל, הוא אינו נמצא בקומבינציה. כמו ש-2 לא נמצא בקומבינציה של 13.
כדי לבדוק האם ביט "דלוק" בתוך מספר אחר, משתמשים באופרטור & המחזיר את אותו מספר אם הוא דלוק ו-0 אם לא.

בכל פעולת חיבור, הרפרנס לפונקציה מצטרף למערך של כל ה-handlers ומודבק אליו אותו אינדקס בחזקת 2. האינדקס נקבע עפ"י מספר ה-handlers.

לאלמנט שאליו רוצים להוסיף/להוריד events מצמידים מאפיין, נניח Click, המאותחל ל-0. כל += של EventHandler יוסיף לתוך המאפיין Click מספר כלשהו שהוא חזקה של 2. חיבור של כמה EventHandlerים לתוך Click ייתן מספר שפירוק שלו ירכיב קומבינציה אחת בלבד של חזקות של 2.

בנוסף למאפיין, נשייך לאלמנט event בעזרת attachEvent/addEventListener שבהפעלתו נרוץ על כל מערך ה-handlers, ובמידה והמאפיין המספרי מכיל את המזהה של ה-handler מהמערך, נפעיל את הפונקציה.

הסינטקס הסופי הוא כזה:

Event.Register(element1,"Click");
element1.Click+=new EventHandler(handler1); // valueOf returns 1. add 1
element1.Click+=new EventHandler(handler2); // valueOf returns 2. add 2
element1.Click-=new EventHandler(handler2); // valueOf returns 2. subtract 2
element1.Click+=new EventHandler(handler3); // valueOf returns 4. add 4

לאחר כל שורות הקוד, ערכו של המאפיין Click הינו 5. 5, לאחר פירוק בינארי, הופך ל-4+1, שמייצגים "ביטים" 0 ו-2. לכן, הפונקציות באינדקס 0 ו-2 יקראו.

לחיצה על האלמנט תרוץ על מערך ה-handlers ותפעיל את פונקציות 0 ו-2 כי הביטים שלהן דלוקים במאפיין Click.

הקוד במלואו:

<script type="text/javascript">
//<![CDATA[
function EventHandler(handler) {
	this.Handler=handler;
	this._index=EventHandler._allHandlers.length;

	EventHandler._allHandlers.push(this);
}
EventHandler.prototype.valueOf=function () {
	// if function is already in the list, return its value
	for (var i=0;i<EventHandler._allHandlers.length;i++) {
		handler=EventHandler._allHandlers[i];
		if (handler.Handler==this.Handler && handler.registered) return Math.pow(2,i);
	}

	this.registered=true;

	return Math.pow(2,this._index);
};
EventHandler._allHandlers=[];

if (typeof(Event)=="undefined") Event={};
Event.Register=function (element,name) {
	element[name]=0;

	var fire=function (e) { Event.Fire(element[name],e); };

	if (element.attachEvent) element.attachEvent("on"+name.toLowerCase(),fire);
	else element.addEventListener(name.toLowerCase(),fire,false);
};
Event.Fire=function (handlers,e) {
	var idx=0;

	// cache power function for better performance
	var pow=Math.pow;

	for (var i=0;i<EventHandler._allHandlers.length;i++) {
		var idx=pow(2,i);
		// if the current index in the power of 2 contained in the handles list, call the function
		if ((handlers & idx)>0) EventHandler._allHandlers[i].Handler(e);
	}
}

function handler1() { alert(1); }
function handler2() { alert(2); }
function handler3() { alert(3); }
function handler4() { alert(4); }

onload=function () {
	var element1=document.getElementById("element1");
	Event.Register(element1,"Click");
	element1.Click+=new EventHandler(handler1);
	element1.Click+=new EventHandler(handler2);
	element1.Click-=new EventHandler(handler2);
	element1.Click+=new EventHandler(handler3);

	var element2=document.getElementById("element2");
	Event.Register(element2,"Click");
	element2.Click+=new EventHandler(handler1);
	element2.Click+=new EventHandler(handler2);
};
//]]>
</script>
<div id="element1">element1</div>
<div id="element2">element2</div>

באגים נפוצים ב-IE6

יום רביעי, 10 בינואר, 2007

דפדפן Interner Explorer בגרסה 6 היה שיפור עצום מבחינת תמיכה בתקנים מאז ימי 5 הזוהרים (ואף מילה על 5.5). ה-CSS התחיל להתנהג בצורה תקנית בעזרת DOCTYPE מתאים וגם ה-dom התחיל להיות תואם להגדרות W3C.

עם זאת, הרבה פעמים אנו נתקלים בבאגים מרגיזים שיכולים לעכב את העבודה שלנו כדי למצוא workarounds. מה לעשות ש-IE7 ו-Firefox עדיין לא השתלטו על השוק, ו-IE6 תופס נתח לא קטן, ואנחנו כמתכנתי web צריכים להתאים את האתרים שלנו לכל הדפדפנים הנפוצים.

אספתי כמה באגים כאלה, שבכולם נתקלתי במהלך העבודה בשנים האחרונות, ודרכים לפתרון.

#1 : ערכי margin כפולים ב-float

לכל אלמנט שחל עליו float כלשהו, ערכי ה-margin שיחולו עליו יוכפלו. אין הסבר הגיוני לזה.

דוגמה:

<div style="float:left;margin-left:100px;">IE6 doubles the margin and renders margin-left with 200px</div>

באג margin כפול ב-float

פתרון:

הצורה הכי אלגנטית לפתור את הבאג הזה היא לתת מאפיין display:inline לאלמנט בעל ה-float. הגדרת ה-float בכל מקרה משכתבת את ה-display ל-block. זה פותר את הבעיה, ולא מונע מהאלמנט להמשיך לתפקד כמו block.

#2 : תזוזה של פיקסל כאשר רוחב/גובה אי זוגיים

כאשר ממקמים אבסולטית אלמנט בתוך אלמנט אחר (בעל position:relative) ורוחבו או גובהו של אלמנט האב אי זוגיים, האלמנט הפנימי יזוז פיקסל אחד אחורה

<div style="width:199px;height:99px;position:relative;border:1px solid #000;">
	<div style="width:100px;position:absolute;background-color:red;right:0;bottom:0;"></div>
</div>
באג ריווח של פיקסל אחדניתן לראות כי יש רווח של פיקסל מימין
ומלמטה

פתרון:

לבאג זה לא מצאתי ממש פתרון קסם, אך בעזרת ה-underscore hack ניתן למשוך את האלמנט למיקום הנכון שלו:

<div style="width:199px;height:99px;position:relative;border:1px solid #000;">
	<div style="width:100px;position:absolute;background-color:red;right:0;bottom:0;_margin-bottom:-1px;_margin-right:-1px;"></div>
</div>

#3 : תמונות וצבעי רקע נחתכים

את מי שלא מכיר דרכי פתרון, הבאג הזה יכול להוציא מהכלים. קצת הסבר כללי – ב-css של IE יש מאפיין readonly שנקרא hasLayout. לאלמנט "יש" layout אם באחד המאפיינים הבאים יש ערך שונה מברירת המחדל: width, height, float, zoom, או אחד מהמאפיינים הבאים: display:inline-block, position:absolute, writing-mode:tb-rl.

הבאג עצמו חל על אלמנטים ללא layout וגורם להתנהגויות מוזרות: צבעי ותמונות רקע חתוכים או לא מופיעים, טקסט שנעלם ומופיע בסימון, טקסט שמיקומו נדחף.

הפתרון, כאמור, הוא לתת אחד מהמאפיינים שלמעלה. מכיוון שכל המאפיינים משפיעים על צורתו ומיקומו של האלמנט ולא תמיד זה מה שנרצה, נוכל להשתמש במאפיין zoom עם ערך של 1. ה-zoom לא באמת ישתנה, אך האלמנט יקבל layout והבאג ייפתר.

ul li {
	background-image:url('image.gif');
	zoom:1;
}

#4 : opacity, טקסט ו-ClearType (גם ב-IE7)

windows xp מגיעה עם אופצית ריכוך גופנים, כאשר הריכוך הקל יותר מופעל כברירת מחדל. כשמחילים opacity על אלמנט כשבתוכו טקסט מודגש, הטקסט הופך מגורען.

בדוגמה אפשר לראות טקסט בתוך div עם שקיפות של 40%. הטקסט המודגש נראה מגורען ולא חלק.

פתרון:

צבע רקע כלשהו לאלמנט המכיל פותר את הבאג.

#5 : תיבת select עולה על אלמנטים

ללא התחשבות ב-z-index, תיבות בחירה ב-IE6 עולות על כל אלמנט (כמעט).

אם יש לי תפריט שנפתח במעבר עכבר, ומתחת לאיזור בו אמור להפתח התפריט יש select, התיבה תופיע מעל אותו אלמנט.

פתרון:

אלמנט אחד ש-select לא עולה עליו הוא iframe. אבל אלמנטים אחרים כן יכולים לעלות על iframe. המסקנה המתבקשת היא – הצגת iframe מעל ה-select והצגת האלמנט מעל אותו iframe. את ה-iframe אפשר לעשות שקוף בעזרת opacity של 0. את המיקום והגודל של ה-iframe יש להעתיק מהאלמנט אותו אנו חפצים לשים מעל ה-select.

> html
<iframe id="hider-iframe" style="display:none" frameborder="0" scrolling="no"></iframe>

> css
#hider-iframe {
	position:absolute;
	filter:alpha(opacity=0);
}

> js
function ShowMenu() {
	HiderIframeShow(menuElement);
	menuElement.style.display="";
}
function HiderIframeShow(clone) {
	var hiderIframe=document.getElementById("hider-iframe");
	hiderIframe.style.top=clone.style.top;
	hiderIframe.style.left=clone.style.left;
	hiderIframe.style.width=clone.offsetWidth+"px";
	hiderIframe.style.height=clone.offsetHeight+"px";
	hiderIframe.style.display="";
}
אם משתמשים בספריית prototype, הפונקציה Position.clone תעתיק את המיקום המדוייק.

מאפייני ה-top/left מועתקים מערכי האלמנט, בהנחה שגם ה-iframe וגם האלמנט בעלי position:absolute ויושבים תחת אותו offsetParent (הא הראשון בעל position:absolute/relative או ה-body). אם לא כך הדבר – יש להשתמש במנגנון המחשב מיקום מדוייק של האלמנט.

#6 : צבעים של PNG שקוף משתנים

נתקלתי לאחרונה בתופעה מוזרה: כאשר משתמשים ב-AlphaImageLoader filter של IE6 להצגת תמונות PNG עם שקיפות, התמונות התמונות מוצגות כהות יותר ממה שהן במקור. כן, IE6 ממש משנה ערכי RGB על התמונה.

זה רע כשהתמונה צריכה לשבת/להתחבר עם סטריפ בעל צבע רקע שאמור להיות כביכול המשכי לתמונה.

לאחר מחקר קטן גיליתי שאכן יש באג במידה וה-PNG יוצאה מפוטושופ (כנראה גם בעורכי תמונות אחרים). מצאתי כלי חינמי המתקן את העוולה -

TweakPNG. לאחר ההפעלה של התוכנה פותחים את קובץ ה-PNG, מוחקים את ה-Chunk בשם gAMA, ושומרים.

(מיותר לציין, Firefox ו-IE7, בעזרת תגית img או background-image, עומדים בגבורה במשימה ומציגים צבעים נכונים מראש).

underscore hack

בהתקלות בכל באג נוסף שמצריך טיפול מיוחד רק ב-IE6 אפשר להשתמש ב-underscore hack: כל מאפיין css שיוצמד לו מקף תחתון לפני שמו – יוחל רק ב-IE6.

.className {
	background-color:red;
	_background-color:blue;
}

בכל דפדפן מלבד IE6 נראה צבע אדום, אך IE6 כן יתייחס ל-_background-color כמאפיין חוקי, ולכן צבע הרקע יהיה כחול.

כדאי לצמצם שימוש בהאקים, ולמצוא פתרון אחיד לכל הדפדפנים.

מידע נוסף

מידע נוסף ניתן למצוא בקישור באגים ב-CSS של IE6.

שיפור לולאת for ב-JavaScript

יום חמישי, 21 בדצמבר, 2006

בד"כ, לולאת for רגילה נכתבת כך:

for (var i=0;i<someArray.length;i++) {
	var item=someArray[i];
	alert(item);
}

שיפור קל ללולאה הזו:

for (var i=0,item;item=someArray[i];i++) {
	alert(item);
}

הגדרנו את item ב-condition statement של הלולאה, שקורה לפני כל איטרציה. כך אנו ניגשים אליו בצורה טבעית כמו בלולאת foreach.

הלולאה תעצר ברגע שהתנאי [item=someArray[i כבר לא יחזיר שווה ערך ל-true (ערך מהמערך) אלא שווה ערך ל-false (ערך שלא נמצא באינדקס i הנוכחי, undefined).

בלולאה כדאי להשתמש רק במעבר על collection, למשל getElementsByTagName, childNodes, זאת מאחר וריצה על ערכים שעלולים לכלול 0, undefined, null, מחרוזת ריקה או כל ערך השווה ל-false תופסק באמצע.

העתקת טקסט מתיבת alert

יום שבת, 16 בדצמבר, 2006

זה לא היה לי טריוויאלי, אולי מאחר ולא ניתן לסמן את הטקסט, אך לחיצה על ctrl+c כאשר תיבת alert (וגם confirm) מופיעה מאפשרת העתקת כל התוכן.

עובד רק ב-IE, ונותן תוצאה כזו:

---------------------------
Windows Internet Explorer
---------------------------
Hello World
---------------------------
OK
---------------------------

נראה לי שימושי.