(1)
element1 이 element2 의 parentNode 이고, element2 에서 event 가 발생하고 (ex. element2 를 click) bubbling phase 에서 element1.addEventListener( event) 에 의해 등록된 event listener 가 실행되는 경우:
그 안에서 event.preventDefault() 를 하면 element2 에서도 default event listener 가 실행되지 않는다.
(2)
textarea 가 한 개 있고 button 을 하나 만들어서 이 button 을 click 하면 button 의 click 의 event listener 안에서 textarea-element.focus() 하면, keyboard 가 나타났다가 바로 사라지는 현상이 발생:
event listener 안에서 event.preventDefault() 를 추가했더니 keyboard 가 사라지지 않는다.
(3)
앞에서 textarea 대신에 div 를 쓰면:
div-element.contenteditable = true; 하면 안 된다.
div-element.contentEditable = true; 하면 잘 된다.
div-element.contentEditable = "true"; 해도 잘 된다.
div-element.setAttribute("contenteditable","true"); 하면 잘 된다.
attribute 이름은 html attribute 이름에 대응하고, 따라서 case-insensitive 하다고 하는데, 왜 이런지 모르겠다.
또한, contentEditable attribute 는 enumerated attribute 이고 boolean 이 아니므로, true 가 아니라 "true" 를 assign 해야 할 것 같은데 그냥 해도 되는 게 이상하다. (참고로, div-element.isContentEditable 은 case-sensitive 하다.)
또 한 가지 이상한 것은, div-element.contenteditable 을 alert 로 찍어보면, undefined 로 나오는 것이다.
ps. div-element.contentEditable 을 alert 로 찍어보면, false, true 값이 잘 나온다.
즉,
**************** setAttribute() 를 사용할 때는 case-insensitive 하고, element.contentEditable 을 사용할 때는 case-sensitive 하다는 것이다.
(4)
앞의 (3)에서 button 의 style.display 에 'none' 값으로 초기화 하고, event listener 를 더해서 touch 할 때마다 'inline' 값과 toggle 되도록 했다.
event listener 에서 button element 를 e.target 을 이용해서 얻으니까 (div 와 button 이 서로 sibling 으로서 한 div 의 children 들이다. 따라서 div 를 얻고 그것의 parentNode 의 두 번째 element 를 얻으면 그것이 바로 button 이다), 첫 line에서만 touch 가 먹혔다. 왜냐하면, div 에서 다음 라인으로 이동하면 child div 를 만들어서 그 안에 text 를 넣는 식으로 구현돼 있기 때문이다. 그래서 getElementByID 를 이용해서 div element 를 얻고 그것의 parentNode 를 얻는 식으로 다시 고쳤다. 그랬더니 다음 line 들에서도 toggle 이 잘 먹힌다. 이것은 이 child element 들에서 발생한 event 들이 bubbling phase 에서 ancestor 들로 올라가다가 결국 toggle 하는 event listener 를 만나게 되는데 정작 event 는 몇 대 아래의 descendant 에서 발생하는지 모르기 때문에 event 를 기준으로는 button 을 제대로 구할 수 없기 때문이다.
즉,
**************** capturing phase 에서 event 가 발생한 element 까지 가계도를 죽 내려왔다가 bubbling phase 로 진입해서 다시 가계도를 죽 올라가면서, 만나는 event listener 들을 수행하는 규칙은 잘 적용된다는 것을 확인 할 수 있고,
event 는 자신이 가계도에서 어느 특정한 element 에서 비롯된 건지 기억하고 있다. event.target 으로.
(5)
button 의 touchend event listener 를 추가하고 그 listener 안에서 alert() 를 한다. 그랬더니 이 button 을 touch 하고 바로 다음에 다른 button 이나 다른 element 를 touch 하면 마치 처음 button 을 touch 한 것처럼 같은 alert() 가 불려진다.
http://stackoverflow.com/questions/7492070/touchend-handler-fires-twice
에 따르면 alert() 가 event handling cycle 과 interfere 하면서 일어나는 현상이므로 setTimeout() 안에 alert() 를 넣어서 약간의 delay 를 주면 된다고 한다. 어쨌든 alert() 가 없는 실제 실행에서는 문제가 되지 않을 것 같다.
ps. 약간의 delay 도 없이, 그러니까 delay 를 0으로 줘도 잘 된다. cf. (20)
(6)
var newEle = document.createElement("div") 하고 document.body.appendChild() 를 한다. 그런데 newEle.style.left 와 newEle.style.top 의 값이 무시되는 것 같았다. 그래서 newEle.style.position = "absolute" 를 추가했더니 원하는 위치에 잘 생긴다.
(7)
event invoking code block:
var newEvent;
if (document.createEvent) {
newEvent = document.createEvent("HTMLEvents");
newEvent.initEvent("touchstart", true, true);
} else {
newEvent = document.createEventObject();
newEvent.eventType = "touchstart";
}
newEvent.eventName = "touchstart";
//newEvent.memo = memo || { };
newEvent.memo = { };
if (document.createEvent) {
aTextarea.dispatchEvent(newEvent);
} else {
aTextarea.fireEvent("on" + newEvent.eventType, newEvent);
}
(8)
element 의 style 은 class attribute 에 의해 정해진다. 하지만 이것은 element.className ="someStyle" 의 형태로 assign 돼야 한다.
그리고, element 가 SVG 의 것인 경우에 이런 assignment 는 안 되고 element.setAttributeNS(null, "class", "someStyle") 의 형태가 돼야 한다.
(9)
<svg> ... </svg> 는 html 파일 안에 처음 한 개만 효력을 발휘한다.
(10)
element.style.left 나 top 은 string 이다.
또한, 50px 처럼 px 를 뒤에 붙여줘야 한다. 즉, x = 50 의 number 인 경우 element.style.left = x.toString() + "px"; 라고 해야 한다.
그런데, 거꾸로 x = parseInt( element.style.left); 는 그냥 된다. parseInt 가 px 를 적절히 제거해 주는 것 같다.
(11)
SVG 의 style 에 SVG 영역의 border 를 실선으로 그려보았더니 약간의 margin 혹은 padding 이 있는 게 보였다. 그래서 html element 와 svg element 위치들 사이에 약간의 어긋남이 보였었다.
그래서 <body style="margin:0px; padding:0px"> 해 줬더니 그런 어긋남이 없어졌다.
(12)
SVG element G 에 path 를 두 개 넣었는데, g.children[] 로 path 를 refer 할 수 없었다. 대신에 g.childNodes[] 로 할 수 있었다.
(13)
SVG path 에서 끝에 arrow head 를 넣고 싶었다.
http://stackoverflow.com/questions/7530689/how-do-you-draw-an-arrow-at-the-end-of-a-bezier-curve-in-svg-raphael
에 따르면 marker 를 쓰면 된다고 하는데, 이 방법에는 문제가 있다. 먼저 arrow head 가 좌우만 가리키고 path 의 각도에 따라 변하지 않는다. 더 심각한 문제는 head 가 path 끝에 더해진다는 것. path 에 겹쳐져야 하는데.
그래서 control point 와 end point 를 이용해서 적당한 각도로 돌아간 정삼각형을 path 로 그렸다. 다음과 같다.
function makeArrowHeadD( fromX, fromY, toX, toY) {
var control2x = 0.5 * fromX + 0.5 * toX;
var control2y = 0.1 * fromY + 0.9 * toY;
var xp = control2x - toX; // parallel translation
var yp = control2y - toY;
var theta = Math.atan( yp / xp);
if( xp < 0)
theta += Math.PI;
var x = 5 * Math.cos( theta); // of (5 * unit vector)
var y = 5 * Math.sin( theta);
// points of arrow-head-triangle
var x1 = x * 0.866 - y * 0.5; // 0.866 = cos(30 deg.), 0.5 = sin(30 deg.)
var y1 = x * 0.5 + y * 0.866;
var x2 = x * 0.866 + y * 0.5;
var y2 = x * (-0.5) + y * 0.866;
x1 += toX;
y1 += toY;
x2 += toX;
y2 += toY;
return "M " + toX + " " + toY + " L " + x1 + " " + y1 + " L " + x2 + " " + y2 + " Z";
}
(14)
div 의 style.display 를 none 으로 초기화했다. 그리고 다른 button 을 click 할 때마다 inline 으로 toggle 되게 했다. (event listener 를 통해서)
그런데 다음과 같이 className 으로 초기화를 하면 마치 event listen 를 add 할 시점에는 초기값이 none 이 아니라 undefined(정확한 값을 확인해 보지는 않았음) 값이 들어간 것처럼 행동한다.
하지만 실제로 그 div 를 load 할 때는 none 값이 들어가 있는 것처럼 화면에 display 되지 않는다.
참고로, addEventListener 는 className 에 assign 하는 것 다음에 나온다. 한 function 안에 있다.
그런데, 다음에서 comment 를 풀면 none 으로 초기화가 잘 되는 것 같다.
아무튼 undefined 가 들어있을 가능성을 고려해서 toggle 함수를 수정했다. 아직 이유는 모르겠다.
var boxmenu = document.createElement("div");
boxmenu.className = "boxmenuStyle";
//boxmenu.style.display = "none";
(15)
어떤 element 에 addEventListener 로 touchend 에 연결시켜 놓은 event listener 에서 event.preventDefault() 를 해 놓았더니, 그 element 에 대해서 mousedown 등등의 다른 event 들이 fire 되지 않는다.
(iOS simulator 에서 mouse 로 click 했다. 그리고 touchstart, touchmove 에서는 원래 preventDefault() 가 없었다.)
event.preventDefault() 를 없앴더니 mouse event 들, click event 는 fire 된다. (click 은 원래 mousedown + mouseup 이라고 함.)
그런데 focus 는 여전히 fire 되지 않는다.
그래서 하나의 독립적인 div 를 document.createElement 로 만들고 addEventListener 를 사용했는데, focus 만 역시 fire 되지 않았다. 마치 iOS 에서는 아예 지원이 되지 않는 것 같다.
하지만 애플의 자료에 의하면 지원한다고 돼 있었다. (참고로 drag, drop 같은 것은 지원하지 않는다고 나와 있다.)
조금 더 찾아보니까, html 의 모든 element 가 focusable 한 게 아니라고 한다. focusable 의 조건은 다음과 같다.
(16)
UIWebView 에서 div 를 contenteditable 로 세팅하고 그 안에 http://.../ 를 넣으니까 자동으로 detect 를 해서 link 로 인식하고 link 표시로 바뀐다. 하지만 edit 하자마자 바뀌는 것은 아니고 다시 load 를 하면서 인식하고 바뀐다. 어떤 패턴의 string 을 인식해서 바꿀지는 webview.dataDetectorTypes 가 결정하는데 아마 default 로 모든 type 들이 켜져 있는 것 같다. (그런데 NSTimer 를 이용해서 확인해 봤는데, [webview reload] 해서는 안 되고 [webview loadHTMLString ...] 을 다시 해야지 다시 load 된다. 왜 그런지 모르겠다.)
UIWebView 에서 http link 를 click 했을 때 동일한 바로 그 UIWebView 에서 새 page 가 loading 된다. 그러면 go back button 이 따로 있지 않아서 곤란하다. iPhone 의 messge app 같은 경우에는 link 를 click 했을 때 safari 가 뜨면서 이 safari 에서 새 page 가 load 된다.
이렇게 하려면 .h 에서 <UIWebViewDelegate> 를 추가하고, .m 에서 다음의 코드를 추가하면 된다.
즉, [[UIApplication sharedApplication] openURL:[inRequest URL]]; 에서 safari 를 띄우는 듯.
...
self.webview.delegate = self;
...
- (BOOL) webView:(UIWebView *)inWeb shouldStartLoadWithRequest:(NSURLRequest *)inRequest navigationType:(UIWebViewNavigationType)inType {
if ( inType == UIWebViewNavigationTypeLinkClicked ) {
[[UIApplication sharedApplication] openURL:[inRequest URL]];
return NO;
}
return YES;
}
(17)
UIWebView 에서 <img src="url"> 의 방식으로 local device 의 camera roll 에 있는 image file 을 load 해서 보여줄 수 있는지 찾아봤다.
UIWebView 에서 camera roll 을 선택하는 view 를 띄우고 image 를 선택하고 그 image 를 받아오는 것 또는 그것의 asset URL 을 알아내는 것까지는 가능하다.
그런데 이 image 를 다시 UIWebView 에서 보여주는 방법이 쉽지 않다.
<img src="asset url"> 을 사용할 수 있으면 좋을 것 같은데 된다는 얘기도 있고 안 된다는 얘기도 있다.
image 는 아니지만 video 파일에 대해서 잘 된다는 주장이 있다.
asset URL 은 Asset Library 에서만 인식되고 UIWebView 는 못 한다는 주장도 있다.
안 된다는 것은 작년 글이고 된다는 것은 올해 글이라서, 그 사이에 변화가 있었을 수도 있다.
그래서 된다는 사람의 방식을 직접 iOS simulator 에서 구현해 봤는데 잘 안 된다.
이미지 파일은 그냥 supporting files 디렉토리에 Canada.png 를 넣어서 해 봤다.
embed 대신에 img 를 사용해도 안 된다.
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
[self dismissViewControllerAnimated:YES completion:nil];
NSURL* url = [info objectForKey:UIImagePickerControllerReferenceURL];
...
에서 url 을 받아오는 것까지는 잘 되는 것 같다. [url absoluteString] 을 찍어보면 "assets-library://..." 가 찍힌다.
UIImage* image = [info objectForKey:UIImagePickerControllerOriginalImage];
NSData *imageData = UIImageJPEGRepresentation (image, 0.1);
[self.webview loadData:imageData MIMEType:@"image/jpeg" textEncodingName:@"UTF-8" baseURL:nil];
로 이미지를 직접 보여주는 것도 가능하다.
ps.
video file 을 photo library 에 넣어서 확인해 보니까, video 는 잘 된다. URL 을 UIImagePickerController 로 부터 다음과 같이 받아오면 된다.
(그런데 iOS simulator 에서 iPhone 으로 설정하면 audio 부분에서 error 가 난다. 그냥 Photos 앱에서 play 하는 것도 같은 에러가 난다. iPad 에서 하면 잘 된다.)
NSURL* url = [info objectForKey:UIImagePickerControllerMediaURL];
이 경우 [url absoluteString] 은 file://... 의 형태를 갖는다. assets-library 가 아니라.
image file 의 경우에는 이렇게 url 을 받아오면, url == nil 이다.
UIImagePickerControllerDelegate Protocol Reference 에 다음과 같은 구절이 있다.
...
imagePickerController:didFinishPickingMediaWithInfo:
...
info
A dictionary containing the original image and the edited image, if an image was picked; or a filesystem URL for the movie, if a movie was picked.
pps.
이 작업을 완성하려면, objective C 부분에서 선택한 파일에 대한 정보를 html 안에 끼워넣어야 한다. movie 파일의 경우 다음과 같이 할 수 있다.
NSString* javascriptFunctionCall = [NSString stringWithFormat:@"addEmbed(\"%@\",\"%@\")", self.fileToUpload.elementId, self.fileToUpload.urlString];
[self.webview stringByEvaluatingJavaScriptFromString: javascriptFunctionCall];
addEmbed() 는 javascript function 이다.
(18)
<img ...> 를 javascript function 으로 추가하는데
var img = document.createElement("img");
img.width = "80px";
img.height = "80px";
로 하면 안 된다.
mg.width = "80";
img.height = "80";
로 하면 된다. 찾아보니까, px 를 붙이는 것은 CSS style 에서 하는 것이고 attribute 에는 그냥 수를 쓴다는 얘기가 있다. 즉,
img.style.width = "80px";
같은 식으로 하면 된다는 것.
그런데, <embed...> 를 추가할 때는
embed.width = "80px"; 같은 식으로 해도 잘 됐었는데. 이상하다.
(19)
JSON.parse() 는 object 를 반환하지만 generic object 다. properties 는 잘 들어가 있지만, 그 object 가 특정 object 라는 것을 나타내지는 못한다. 따라서 특정 object 로 변환해 주는 과정이 필요하다. 예를 들어 특정 object 의 constructor 에서 duplicate 할 수 있도록 정의하고 그 constructor 를 사용해서 새 object 를 만들면 된다.
(20)
button 에 addEventListener("touchend", listener, false) 를 하고 listener 내에서 var empty = confirm("really?"); 를 이용해서 사용자의 확인을 받고 진행하는 방식인데, alert() 를 사용할 때처럼 button 이 두 번 click 되는 부작용이 생겼다. (바로 다음에 다른 element를 click 해도 이 button 이 click 된 것처럼 반응한다.) 그런데 "touchend" 를 "touchstart" 로 고쳤더니 그러지 않는다. 다른 것들이 대부분 touchend 로 돼 있어서 그러는지... 아니면 다른 이유인지, 이유는 모르겠다.
결국에는, touchstart 를 쓰기 싫어서, setTimeout() 을 이용했다. setTimeout() 는 그 속에 alert() 나 confirm() 을 포함한 callback 함수를 event queue 의 맨 마지막에 집어넣는다. 그러면 설령 setTimeout( function(){...}, 0} 으로 하더라도, (즉, 0 만큼 delay 되더라도 queue 의 마지막이므로) 현재 진행중인 event 들 사이에 끼여서 뭔가 예측하지 못한 일이 발생하는 것을 막는 것 같다.
event queue 의 내용을 알아내거나 clear 하는 방법을 찾아보려고 했는데 잘 못 찾겠다.
(21)
box object 는 outbox 라는 property 를 가지고 있는데 이것은 <div></div> 이다. 이 div 를 document.body.appendChild() 하기 전에 outbox.offsetHeight 나 offsetWidth 의 값은 0이다. 즉, 이 값들은 document body 에 들어가고 나서 적당한 값이 들어가게 된다.