contentEditable로 WYSWYG 에디터를 만들일이 있어서 하다보니
생각보다 까다로와서 기록해둔다.
먼저 javascript 구현체
http://jsbin.com/erukis/6/edit
<!DOCTYPE html>
<html>
<head>
<script src="http://code.jquery.com/jquery.min.js"></script>
<link href="http://twitter.github.com/bootstrap/assets/css/bootstrap.css" rel="stylesheet" type="text/css" />
<link href="http://twitter.github.com/bootstrap/assets/css/bootstrap-responsive.css" rel="stylesheet" type="text/css" />
<script src="http://twitter.github.com/bootstrap/assets/js/bootstrap.js"></script>
<meta name="description" content="mobile" />
<meta charset=utf-8 />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title></title>
</head>
<body>
<div class="edit" contentEditable="true">
여기를 찍어서 수정가능
</div>
<button href="#myModal" role="button" class="btn" data-toggle="modal">set Color</button>
<div id="myModal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 id="myModalLabel">set Color</h4>
</div>
<div class="modal-body">
#<span id="color" contentEditable="true">000000</span>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
<button class="btn btn-primary save" data-dismiss="modal" data-result="save"> Save</button>
</div>
</div>
</body>
</html>
HTML은 이렇고
var range, selection;
var getRange = function() {
if (document.body.createTextRange) {
range = document.body.createTextRange();
} else if (window.getSelection) {
selection = window.getSelection();
range = selection.getRangeAt(0);
}
return range;
};
var setRange = function(range) {
if (document.body.createTextRange) {
range.select();
} else {
selection.removeAllRanges();
selection.addRange(range);
}
};
$("#myModal").on("hide", function(e) {
// 이쪽이 click 이벤트 이후에 발생?
// selection.removeAllRanges();
}).on("shown", function(e) {
// 여기서 선택 영역을 백업
range = getRange();
return true;
});
$(".save]").click(function() {
setRange(range);
console.log($("#color").text());
document.execCommand("ForeColor", false, $("#color").text());
});
javascript 는 이런 식
stackoverflow랑 microsoft, mozilla 사이트를 뒤져가면서 했는데.
IE 8 이하 일때랑 그렇지 않을때랑 영역을 잡는 방식이 다르다.
contentEditable 인 div 영역을 선택 후 modal을 띄워 해당 텍스트의 색을 execCommand 로 변경하는 예제인데
영역을 선택하고 칼라값을 입력하려면 선택영역의 포커스를 잃어버리기 때문에 execCommand 실행 시점에 적용할 수 없게 된다.
그래서 세운 작전은
이렇게 구현했다.
시행착오가 있었지만 일단 chrome, FF, IE 9, Safari 등에서 작동을 확인했다.
http://jsbin.com/erukis/7/edit
소스를 coffeescript로 바꾸고 Range class를 만들었다.
class Range
setRange: =>
if document.body.createTextRange
@range.select()
else
@selection.removeAllRanges();
@selection.addRange @range
getRange: =>
if document.body.createTextRange
@range = document.body.createTextRange()
else
@selection = window.getSelection()
@range = @selection && @selection.getRangeAt 0
return
range = new Range
$("#myModal").on "shown", ->
range.getRange()
true
$(".save").click ->
range.setRange()
document.execCommand "ForeColor", false, $("#color").text()
true
document.selection.createRange() 로 변경하고 다소 수정을 해보니
http://jsbin.com/erukis/11/edit
class Range
setRange: =>
if document.selection
@range.select()
else
@selection.removeAllRanges();
@selection.addRange @range
getRange: =>
if document.selection
@range = document.selection.createRange()
else
@selection = window.getSelection()
@range = @selection && @selection.getRangeAt 0
return
range = new Range
saveStatus = false
$("#myModal").on "shown", ->
range.getRange()
saveStatus = false
true
$("#myModal").on "hidden", ->
if saveStatus
range.setRange()
document.execCommand "ForeColor", false, $("#color").text()
true
$(".save").click ->
saveStatus = true
true
생각보다 까다로와서 기록해둔다.
먼저 javascript 구현체
http://jsbin.com/erukis/6/edit
<!DOCTYPE html>
<html>
<head>
<script src="http://code.jquery.com/jquery.min.js"></script>
<link href="http://twitter.github.com/bootstrap/assets/css/bootstrap.css" rel="stylesheet" type="text/css" />
<link href="http://twitter.github.com/bootstrap/assets/css/bootstrap-responsive.css" rel="stylesheet" type="text/css" />
<script src="http://twitter.github.com/bootstrap/assets/js/bootstrap.js"></script>
<meta name="description" content="mobile" />
<meta charset=utf-8 />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title></title>
</head>
<body>
<div class="edit" contentEditable="true">
여기를 찍어서 수정가능
</div>
<button href="#myModal" role="button" class="btn" data-toggle="modal">set Color</button>
<div id="myModal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 id="myModalLabel">set Color</h4>
</div>
<div class="modal-body">
#<span id="color" contentEditable="true">000000</span>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
<button class="btn btn-primary save" data-dismiss="modal" data-result="save"> Save</button>
</div>
</div>
</body>
</html>
HTML은 이렇고
var range, selection;
var getRange = function() {
if (document.body.createTextRange) {
range = document.body.createTextRange();
} else if (window.getSelection) {
selection = window.getSelection();
range = selection.getRangeAt(0);
}
return range;
};
var setRange = function(range) {
if (document.body.createTextRange) {
range.select();
} else {
selection.removeAllRanges();
selection.addRange(range);
}
};
$("#myModal").on("hide", function(e) {
// 이쪽이 click 이벤트 이후에 발생?
// selection.removeAllRanges();
}).on("shown", function(e) {
// 여기서 선택 영역을 백업
range = getRange();
return true;
});
$(".save]").click(function() {
setRange(range);
console.log($("#color").text());
document.execCommand("ForeColor", false, $("#color").text());
});
javascript 는 이런 식
stackoverflow랑 microsoft, mozilla 사이트를 뒤져가면서 했는데.
IE 8 이하 일때랑 그렇지 않을때랑 영역을 잡는 방식이 다르다.
contentEditable 인 div 영역을 선택 후 modal을 띄워 해당 텍스트의 색을 execCommand 로 변경하는 예제인데
영역을 선택하고 칼라값을 입력하려면 선택영역의 포커스를 잃어버리기 때문에 execCommand 실행 시점에 적용할 수 없게 된다.
그래서 세운 작전은
- 선택한 영역을 백업한다.
- save버튼을 누를 시 백업한 선택 영역을 재적용한다.
- execCommand 로 색상을 적용한다.
이렇게 구현했다.
시행착오가 있었지만 일단 chrome, FF, IE 9, Safari 등에서 작동을 확인했다.
http://jsbin.com/erukis/7/edit
소스를 coffeescript로 바꾸고 Range class를 만들었다.
class Range
setRange: =>
if document.body.createTextRange
@range.select()
else
@selection.removeAllRanges();
@selection.addRange @range
getRange: =>
if document.body.createTextRange
@range = document.body.createTextRange()
else
@selection = window.getSelection()
@range = @selection && @selection.getRangeAt 0
return
range = new Range
$("#myModal").on "shown", ->
range.getRange()
true
$(".save").click ->
range.setRange()
document.execCommand "ForeColor", false, $("#color").text()
true
역시 커피로 보면 깔끔해서 좋다.
분기를 한건 document.body에 createTextRange 함수가 있는지 여부에 따라 IE8 이하 버전의 처리를 하도록 했는데
document.body.createTextRange()를 하면 body 전체가 선택이 되어 문제가 있다.
document.selection.createRange() 로 변경하고 다소 수정을 해보니
http://jsbin.com/erukis/11/edit
class Range
setRange: =>
if document.selection
@range.select()
else
@selection.removeAllRanges();
@selection.addRange @range
getRange: =>
if document.selection
@range = document.selection.createRange()
else
@selection = window.getSelection()
@range = @selection && @selection.getRangeAt 0
return
range = new Range
saveStatus = false
$("#myModal").on "shown", ->
range.getRange()
saveStatus = false
true
$("#myModal").on "hidden", ->
if saveStatus
range.setRange()
document.execCommand "ForeColor", false, $("#color").text()
true
$(".save").click ->
saveStatus = true
true
이런 형태가 되었다.
오히려 IE 8 이전 버전의 방식이 더 좋아보인다.
- document.selection.createRange() 로 현재 range를 잡고 (물론 multiSelect가 아닌 가정)
- @range.select()로 1에서 받은 객체를 기준으로 select 함수를 통해 다시 선택
인셈이다.
달라진 점이 하나 있는데 save 버튼을 누르는 순간 IE는 포커스가 문제가 있는지 execCommand가 적용되지 않아
시점을 modal이 완전히 닫히고 난 다음에 하도록 flag를 주어서 처리하였다.
(hide도 안된다 transition 이 끝난 이후인 hidden으로 해야한다)
만일 이런 삽질이 애초에 싫다면 rangy(https://code.google.com/p/rangy/) 같은 cross-browser library를 사용한다면 좀 더 깔끔하게 할 수 있다.
html 에서
<script src="http://rangy.googlecode.com/svn/trunk/dev/rangy-core.js"></script>
를 삽입하여 rangy 라이브러리를 가지고 오자.
class Range
setRange: =>
@selection.removeAllRanges()
@selection.addRange @range
return
getRange: =>
@selection = rangy.getSelection()
@range = @selection.getRangeAt 0
return
range = new Range
saveStatus = false
$("#myModal").on "shown", ->
range.getRange()
saveStatus = false
true
$("#myModal").on "hidden", ->
if saveStatus
range.setRange()
document.execCommand "ForeColor", false, $("#color").text()
true
$(".save").click ->
saveStatus = true
true
if문을 없애서 좀 더 깔끔한 코드가 되었다.
댓글
댓글 쓰기