26 мая 2011 г.

Выбор цвета как в photoshop на javascript

Всем, здравствуйте!
Сегодня мы напишем скрипт, который позволит нам выбирать цвет, если не как в photoshop, то близкое к тому. Ведь самое главное суть!
Для этого нам понадобятся следующие изображения.













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


Итак, приступим!

<form action="" method="POST">
<table style="margin:auto; border:1px solid #000; position:relative;">
<tr><td>Введите свой цвет или выберите из набора.
 <div class="ppic" id="surname_pic" style="height:20px; width:20px;position:relative;top:0;right:0;margin:0;float:right;padding:0;border:1px solid #000000;z-index:5"></div>
 <div class="all_for_clr" id="surname_main" style="background:#aaaaaa;height:0; width:0;position:relative;top:0;right:0;margin:0;padding:0;overflow:hidden;float:right;border:1px solid #000000;filter:alpha(opacity=0);opacity:0;z-index:0">
 <div class="preview" id="surname_pre" style="height:50px;width:50px;position:relative;float:right;z-index:0;background:#ffffff;border:1px solid #000000;"></div>
 <div class="brightness" id="surname_br" style="height:128px;width:20px;position:relative;float:right;z-index:0;background:url(nasyschen.png);overflow:hidden;border:1px solid #000000;margin:0 5px 0 0;padding:0;"><div id="surname_line" style="height:1px;width:20px;position:absolute;top:0;left:0;background:#ff0000;margin:0;padding:0;border:0"></div></div>
 <div class="gradus" id="surname" style="height:128px;width:128px;background:url(spec.png) no-repeat 0 0;overflow:hidden;position:relative;float:right;z-index:0;margin:0 5px 0 0;padding:0;border:1px solid #000000;"><div id="surname_krest" style="height:15px;width:15px;position:absolute;top:0;left:0;background:url(krest.png);margin:0;padding:0;border:0"></div></div></div></td>
 <td><input type="text" name="surname" /></td>
</tr>
</table>
</form>
это у нас непосредственно сама форма.
div с class="ppic" - это у нас квадратик, в котором будет показан ткущий цвет.
div с классом "all_for_color" - здесь мы разместим спектр, где у нас будет бегать крестик и где мы будем выбирать цвет, а так же полосу, в которой будем выбирать яркость. Здесь же у нас будет окошко, в котором будет отображаться выбираемый цвет.
В этом диве у нас будет:
div с классом "preview" - т.е. предпросмотр цвета
div с классом "gradus" - не знаю почему я его так назвал, здесь у нас спектр, по которому будем кликать, чтобы выбрать цвет
div с классом "drightness" - здесь черно-белый градиент, выбор яркости.
div с ID="surname_line" - это линия, которая будет держать нас в курсе, где мы кликнули последний раз по яркости
и div с ID="surname_krest" - это крестик, который показывает, где мы кликнули последний раз по спектру.

Ну, а теперь самое страшное :)
Только не забудьте подключить библиотеку jQuery!

<script type="text/javascript">
open_color=false;
function findX(el){
 var curX=0;
 if(el.offsetParent){
  while(el.offsetParent){
   curX+=el.offsetLeft;
   el=el.offsetParent;
  }
 }
 else if(el.x) curX+=el.x;
 return curX;
}
function findY(el){
 var curY=0;
 if(el.offsetParent){
  while(el.offsetParent){
   curY+=el.offsetTop;
   el=el.offsetParent;
  }
 }
 else if(el.y) curY+=el.y;
 return curY;
}
function getColor(x,y){
 var dec_r;
 var dec_g;
 var dec_b;
 if(y<22){
  dec_g=(256-y*12)/128;
  dec_r=0;
  dec_b=2;
 }
 else if(y>21&&y<43){
  dec_g=0;
  dec_r=(y*12-256)/128;
  dec_b=2;
 }
 else if(y>42&&y<65){
  dec_r=2;
  dec_b=(768-y*12)/128;
  dec_g=0;
 }
 else if(y>64&&y<86){
  dec_b=0;
  dec_g=(y*12-768)/128;
  dec_r=2;
 }
 else if(y>85&&y<107){
  dec_g=2;
  dec_r=(1280-y*12)/128;
  dec_b=0;
 }
 else if(y>106){
  dec_r=0;
  dec_b=(y*12-1280)/128;
  dec_g=2;
 }
 red=Number(Math.abs(Math.round(255-x*dec_r))).toString(16);
 green=Number(Math.abs(Math.round(255-x*dec_g))).toString(16);
 blue=Number(Math.abs(Math.round(255-x*dec_b))).toString(16);
 if(red.length<2) red="0"+red;
 if(green.length<2) green="0"+green;
 if(blue.length<2) blue="0"+blue;
 return '#'+red+green+blue;
}
function chColor(x,y,k){
 var dec_r;
 var dec_g;
 var dec_b;
 if(y<22){
  dec_g=(256-y*12)/128;
  dec_r=0;
  dec_b=2;
 }
 else if(y>21&&y<43){
  dec_g=0;
  dec_r=(y*12-256)/128;
  dec_b=2;
 }
 else if(y>42&&y<65){
  dec_r=2;
  dec_b=(768-y*12)/128;
  dec_g=0;
 }
 else if(y>64&&y<86){
  dec_b=0;
  dec_g=(y*12-768)/128;
  dec_r=2;
 }
 else if(y>85&&y<107){
  dec_g=2;
  dec_r=(1280-y*12)/128;
  dec_b=0;
 }
 else if(y>106){
  dec_r=0;
  dec_b=(y*12-1280)/128;
  dec_g=2;
 }
 red=Number(Math.abs(Math.round((255-x*dec_r)*k))).toString(16);
 green=Number(Math.abs(Math.round((255-x*dec_g)*k))).toString(16);
 blue=Number(Math.abs(Math.round((255-x*dec_b)*k))).toString(16);
 if(red.length<2) red="0"+red;
 if(green.length<2) green="0"+green;
 if(blue.length<2) blue="0"+blue;
 return '#'+red+green+blue;
}
mmousedown=false;
bmousedown=false;
$(document).ready(function(){
 $(".gradus").mousedown(function(){
  mmousedown=true;
 });
 $(".gradus").mouseup(function(){
  mmousedown=false;
 });
 $(".gradus").mousemove(function(e){
  if(!mmousedown) return;
  var x=e.pageX-findX(this);
  var y=e.pageY-findY(this);
  if(x>127 || x<0 || y>127 || y<0){
   mmousedown=false;
   return;
  }
  var color=getColor(x,y);
  var field=$(this).attr("id");
  $("#"+field+"_krest").css("top",y-7);
  $("#"+field+"_krest").css("left",x-7);
  $("[name="+field+"]").val(color);
  $("#"+field+"_pre").css("background",color);
  $("#"+field+"_pic").css("background",color);
  $("#"+field+"_line").css("top",0);  
 });
 $(".gradus").click(function(e){
  var x=e.pageX-findX(this);
  var y=e.pageY-findY(this);
  if(x>127 || x<0 || y>127 || y<0){
   mmousedown=false;
   return;
  }
  var color=getColor(x,y);
  var field=$(this).attr("id");
  $("#"+field+"_krest").animate({top:y-7, left:x-7},"fast");
  $("[name="+field+"]").val(color);
  $("#"+field+"_pre").css("background",color);
  $("#"+field+"_pic").css("background",color);
  $("#"+field+"_line").css("top",0);  
 });
 $(".ppic").mouseenter(function(e){
  if(open_color) return;
  open_color=true;
  var len=$(this).attr("id");
  len=len.length-4;
  var id=$(this).attr("id").substr(0,len);
  $(this).css("z-index","-1");
  $("#"+id+"_main").css("position","absolute");
  $("#"+id+"_main").css("left",e.pageX-100);
  $("#"+id+"_main").css("width","215px");
  $("#"+id+"_main").css("height","130px");
  $("#"+id+"_main").css("padding","5px");
  $("#"+id+"_main").css("z-index","0");
  $("#"+id+"_main").animate({opacity:1},"slow");
  open_color=false;
 });
 $(".all_for_clr").mouseleave(function(){
  if(open_color) return;
  open_color=true;
  $(this).animate({opacity:0},"slow",function(){
   $(this).css("position","relative");
   $(this).css("left","0");
   $(this).css("width","0");
   $(this).css("height","0");
   $(this).css("padding","0");
   $(this).css("z-index","-1");
   $(".ppic").css("z-index","5");
   open_color=false;
  });
 });
 $(".brightness").mousedown(function(){
  bmousedown=true;
 });
 $(".brightness").mouseup(function(){
  bmousedown=false;
 });
 $(".brightness").mousemove(function(e){
  if(!bmousedown) return;
  var y=e.pageY-findY(this);
  var x=e.pageX-findX(this);
  if(x>19 || x<0 || y>127 || y<0){
   bmousedown=false;
   return;
  }
  var k=(127-y)/127;
  var len=$(this).attr("id");
  len=len.length-3;
  var id=$(this).attr("id").substr(0,len);
  var spec=document.getElementById(id);
  var krest=document.getElementById(id+"_krest");
  var x_color=findX(krest)+7-findX(spec);
  var y_color=findY(krest)+7-findY(spec);
  var color=chColor(x_color,y_color,k);
  $("#"+id+"_line").css("top",y);
  $("[name="+id+"]").val(color);
  $("#"+id+"_pre").css("background",color);
  $("#"+id+"_pic").css("background",color);
 });
 $(".brightness").click(function(e){
  var y=e.pageY-findY(this);
  var x=e.pageX-findX(this);
  if(x>19 || x<0 || y>127 || y<0){
   bmousedown=false;
   return;
  }
  var k=(127-y)/127;
  var len=$(this).attr("id");
  len=len.length-3;
  var id=$(this).attr("id").substr(0,len);
  var spec=document.getElementById(id);
  var krest=document.getElementById(id+"_krest");
  var x_color=findX(krest)+7-findX(spec);
  var y_color=findY(krest)+7-findY(spec);
  var color=chColor(x_color,y_color,k);
  $("#"+id+"_line").animate({top:y},"fast");
  $("[name="+id+"]").val(color);
  $("#"+id+"_pre").css("background",color);
  $("#"+id+"_pic").css("background",color);
 });
});
</script>

Первая переменная нам необходима, чтобы контролировать панель выбора цвета. В начале она у нас содержит значение false. Т.е. панель не отображается.
Функция findX() и findY() возвращают значение X и Y от верхнего левого угла страницы.
getColor() возвращает значение цвета в формате #xxxxxx. Функция работает по той же логике, что и php-скрипт, которым мы нарисовали спектр.
chColor() возвращает измененный цвет, т.е. меняет яркость выбранного цвета. k это коэффициент.
mmousedown - переменная, которая требуется нам для отслеживания, где нажата кнопка мыши. Она принимает значение true, если кнопка мыши была нажата на спектре. Когда пользователь отпускает кнопку, она принимает значение false. Также ведет себя переменная bmousedown по нажатию/отпусканию кнопки мыши на яркости. Это нам необходимо, чтобы при нажатой кнопке, цвет изменялся при каждом смещении мыши.
Дальше мы прикрепляем обработчики событий на каждое важное для нас поле. Т.е. если курсор попадает на маленький квадрат с текущим цветом, то мы отображаем панель выбора цвета. Если курсов покидает панель, она скрывается. Такие же действия мы поделываем и со всем остальным.
Думаю остальное понятно. Если будут вопросы, задавайте, буду рад ответить!
А пока до скорых встреч!

24 мая 2011 г.

Рисуем градиент на PHP

Как-то мне поступил заказ, чтобы я сделал выбор цвета как в Photoshop. Конечно, один в один я его не сделал, ну, этого и не требовалось, но суть не в этом. Для того, чтобы осуществить нечто подобное, необходимо знать по какому цвету ты щелкаешь. Для этого оказалось проще самому программным образом нарисовать такой квадратик, тогда я буду на 100% уверен какой пиксель, какой цвет содержит. Т.е. его шестнадцатеричный код.
Вот, что у меня получилось.

Теперь я продемонстрирую сам код:
<?php
$im = imagecreatetruecolor(128,128);
$dec_r = 0;
$dec_g = 2;
$dec_b = 2;
for($y = 0; $y < 128; $y++){
 $red = 255;
 $green = 255;
 $blue = 255;
 if($y < 22) $dec_g = (256 - $y * 12) / 128;
 else if($y > 21 && $y < 43){
  $dec_g = 0;
  $dec_r = ($y * 12 - 256) / 128;
 }
 else if($y > 42 && $y < 65){
  $dec_r = 2;
  $dec_b = (768 - $y * 12) / 128;
 }
 else if($y > 64 && $y < 86){
  $dec_b = 0;
  $dec_g = ($y * 12 - 768) / 128;
 }
 else if($y > 85 && $y < 107){
  $dec_g = 2;
  $dec_r = (1280 - $y * 12) / 128;
 }
 else if($y > 106){
  $dec_r = 0;
  $dec_b = ($y * 12 - 1280) / 128;
 }
 for($x = 0; $x < 128; $x++){
  $color_now = imagecolorallocate($im, round($red - $x * $dec_r), round($green - $x * $dec_g),round($blue - $x * $dec_b));
  imagesetpixel($im, $x, $y, $color_now);
 }
}

header("Content-type: image/png");
imagepng($im);
imagedestroy($im);
?>

Тут логика проста. Создаем изображение размером 128х128. Потом для каждой строчки определяем правило, по которому будут происходить изменения цвета каждого следующего пикселя.
Переменные $dec_r, $dec_g, $dec_b отвечают за шаг каждого цвета. Т.е. сначала у нас $dec_r = 0, $dec_g = 2 и $dec_b = 2. Это означает, что красный цвет изменятся не будет. А зеленый и синий будут уменьшаться на 2. Так будет только в первой строчке. Во второй правило изменится для шага зеленого цвета. Его шаг замедлится и не достигнет нуля. Таким образом, в конце второй строчки у нас уже будет не чистый красный цвет, а начнет приближаться к желтому. На 21-й строчке значение зеленого цвета практический перестает изменятся и, начиная с 22-й строчке оно будет равно 255. Но теперь начинает увеличиваться шаг красного цвета, когда он достигнет 2, начинает уменьшаться шаг синего цвета и т.д. Таким образом, мы пройдем по всей цветовой гамме. Конечно, частота дискретизации не позволит нам сделать это более плавно уместившись в такой маленький участок экрана, но наш глаз скачков цвета не заметит.

Дорогие читатели. В следующей статье я хочу вам рассказать о том, как осуществить выбор цвета как в photoshop на javascript. Если же вас интересуют другие темы, то пишите, обсудим.
До новых встреч!

23 мая 2011 г.

Парсер BBcode на PHP для нашего редактора (заключительная статья)

Здравствуйте! Сегодня я продолжу написание редактора, вернее скрипт, который будет переводить bb-код в нужный вид. В прошлой статье мы дописали наш редактор на javascript. Мы научили его вставлять необходимые теги в необходимое место. Теперь нам нужен скрипт на стороне сервера, который это будет сохранять в файл или базу данных, и еще один скрипт который все это будет переводить в надлежащий вид.
Я решил поступить следующим образом. Данные будут храниться в bbcode и парситься при выдаче. Чтобы при редактировании не надо было переводить из html опять в bbcode. Кто с этим не согласен, давайте поспорим. :)
Итак, я создаю файл save.php

<?php
$txt = htmlspecialchars($_POST['text']);
file_put_contents('for_pars.txt', $txt);
?>
<a href="pars.php">посмотреть результат</a>

Все данные я предварительно обрабатываю функцией htmlspecialchars(), и просто помещаю в текстовый файл for_parse.txt. После этих незамысловатых действий я предоставляю пользователю возможность просмотреть результат в том виде, в котором все это будет отображаться на сайте.
Теперь создаем следующий файл pars.php

<?php
$txt = file_get_contents('for_pars.txt');
 
function pars_bb($txt){
 $txt = str_replace('[b]', "<b>", $txt);
 $txt = str_replace('[/b]', "</b>", $txt);
 $txt = str_replace('[u]', "<u>", $txt);
 $txt = str_replace('[/u]', "</u>", $txt);
 $txt = str_replace('[i]', "<i>", $txt);
 $txt = str_replace('[/i]', "</i>", $txt);
 $txt = str_replace('[s]', "<span style=\"text-decoration: line-through;\">", $txt);
 $txt = str_replace('[/s]', "</span>", $txt);
 $txt = str_replace('[left]', "<div align=\"left\">", $txt);
 $txt = str_replace('[/left]', "</div>", $txt);
 $txt = str_replace('[center]', "<div align=\"center\">", $txt);
 $txt = str_replace('[/center]', "</div>", $txt);
 $txt = str_replace('[right]', "<div align=\"right\">", $txt);
 $txt = str_replace('[/right]', "</div>", $txt);
 
 preg_match_all('/\[link href=([^]]*)]/i', $txt, $match);
 if(count($match[0]) > 0){
  foreach($match[1] as $key => $val)
   $txt = str_replace('[link href=' . $val . ']', '<a href="' . $val . '">', $txt);
  $i++;
 }
 
 preg_match_all('/\[image]([^[]*)\[\/image]/i', $txt, $match);
 if(count($match[0]) > 0){
  foreach($match[1] as $val)
   $txt = str_replace('[image]'.$val.'[/image]', "<img src=\"$val\" />", $txt);
  $i++;
 }
 
 preg_match_all('/\[video]([^[]*)\[\/video]/i', $txt, $match);
 if(count($match[0]) > 0){
  foreach($match[1] as $val)
   $txt = get_ifrm_src($val) ? str_replace('[video]'.$val.'[/video]', get_ifrm_src($val), $txt) : str_replace('[video]'.$val.'[/video]', "<a href=\"" . $val . "\">Ссылка на видео</a>", $txt);
  $i++;
 }
 
 preg_match_all('/\[email=([^\]]*)]/i', $txt, $match);
 if(count($match[0]) > 0){
  foreach($match[1] as $key => $val)
   $txt = str_replace('[email='.$val.']', "<a href=\"mailto:$val\">", $txt);
  $i++;
 }
 
 preg_match_all('/\[media]([^[]*)\[\/media]/i', $txt, $match);
 if(count($match[0]) > 0){
  foreach($match[1] as $val)
   $txt = str_replace('[media]'.$val.'[/media]', "<a href=\"$val\">media</a>", $txt);
  $i++;
 }
 
 preg_match_all('/\[font=([^\]]*)]/i', $txt, $match);
 if(count($match[0]) > 0){
  foreach($match[1] as $key => $val)
   $txt =str_replace('[font='.$val.']', "<span style=\"font-family: $val;\">", $txt);
  $i++;
 }
 
 preg_match_all('/\[size=([^\]]*)]/i', $txt, $match);
 if(count($match[0]) > 0){
  foreach($match[1] as $key => $val)
   $txt =str_replace('[size='.$val.']', "<font size=\"$val\">", $txt);
  $i++;
 }
 
 preg_match_all('/\[color=([^\]]*)]/i', $txt, $match);
 if(count($match[0]) > 0){
  foreach($match[1] as $key => $val)
   $txt =str_replace('[color='.$val.']', "<font color=\"$val\">", $txt);
  $i++;
 }
 
 $txt =str_replace('[/color]', "</font>", $txt);
 $txt =str_replace('[/size]', "</font>", $txt);
 $txt =str_replace('[/font]', "</span>", $txt);
 $txt =str_replace('[/code]', "</pre>", $txt);
 $txt =str_replace('[code]', "<pre>", $txt);
 $txt = str_replace('[quote]', "<div class=\"quote\">", $txt);
 $txt = str_replace('[/quote]', "</div>", $txt);
 $txt = str_replace('[/email]', "</a>", $txt);
 $txt = str_replace('[/link]', "</a>", $txt);
 $txt = str_replace('[indent]', "<div style=\"padding:20px\">", $txt);
 $txt = str_replace('[/indent]', "</div>", $txt);
 
 preg_match_all('/:(\w*):/i', $txt, $match);
 if(count($match[0]) > 0){
  foreach($match[1] as $val)
   $txt = file_exists("./images/$val.gif") ? str_replace(":$val:", "<img class=\"smile\" src=\"./images/$val.gif\" />", $txt) : $txt;
  $i++;
 }
 
 
 else $txt = find_video($txt);
 
 return $txt;
}
 
function get_ifrm_src($url){
 include('config.php');
 preg_match('/http:\/\/([^\/]*)\/(.*)/i', $url, $match);
 if(count($match[1]) == 0) return false;
 $pat = $pat_ar[$match[1]];
 $str = $pat['tpl'];
 $player = $pat['player'];
 if($str){
  preg_match($pat['pat'], $match[2], $s_match);
  if(count($s_match)){
   foreach($s_match as $key => $s_val)
    if($key) $str = str_replace('['.$key.']', "$s_val", $str);
   $str = str_replace("[url]", $str, $player);
  }
 }
 return $str == $pat['tpl'] ? false : $str;
}
 
function find_video($txt){
 include('config.php');
 if(count($pat_ar) == 0) return $txt;
 foreach($pat_ar as $val){
  if(preg_match_all($val['url_pat'], $txt, $match)){
   foreach($match[0] as $val)
    $txt = str_replace($val, (get_ifrm_src($val) ? get_ifrm_src($val) : $val), $txt);
  }
 }
 return $txt;
}
 
 
echo pars_bb($txt);
?>

И получился вот такой файл. Теперь настала пора его разобрать. Сначала мы считываем данные из файла, который содержит данные введенные пользователем в виде bbcode. И отображаем результат вызова функции pars_bb(), которой для парсинга мы передали данные пользователя.
Что же делает данная функция? Она по регулярным выражениям ищет находит то, что надо заменить и заменяет. Некоторые теги теги мы меняем, применяя простую функцию str_replace() без preg_match_all().
Дальше мы видим, что тег [video] у нас преобразуется не как все. А дело в том, что если видео вставлено с youtube, то оно заменяется фреймом с видео-плеером. В расчете на то, что подобные замены могут быть довольно-таки разные, я некоторые параметры вынес в отдельный файл config.php

<?php
$pat_ar = array();
$pat_ar['www.youtube.com'] = array('url_pat' => '/http:\/\/www.youtube.com[^\s]+/i', 'pat' => '/\?v=([^&]*)/i', 'tpl' => 'http://www.youtube.com/embed/[1]', 'player' => '<iframe width="425" height="349" src="[url]" frameborder="0" allowfullscreen></iframe>');
?>

Взглянув, на файл конфигурации и функции, которые его применяют, сразу становится ясно, что и для чего необходимо. Думаю, что здесь ничего объяснять не надо. Конечно, если у кого-то возникнут вопросы, спрашивайте, буду отвечать. Если кто-то желает посмотреть все файлы, то их можно скачать одним архивом здесь.

До новых встреч!

21 мая 2011 г.

Пишем редактор для форума на javascript (продолжение)

Итак, наконец-то у меня появилось немного свободного времени, чтобы дописать наш редактор. В прошлый раз я объяснил, как я начинал работу над ним, и мы разобрали как написан внешний вид, а также прикинули в уме, какие функции нам понадобятся и что они должны делать.
Переходим к самому коду, т.е. сейчас мы с вами напишем функции, которые будут выполнять все необходимые действия. Для начала вспомним отрывок листинга файла index.html, созданного в прошлый раз. Сейчас нас интересует следующие строки:

<script>
...
...
</script>

Сюда, вместо многоточия мы вставим все, что напишем сегодня.
Итак, начнем.
Первый элемент меню нашего редактора отвечает за шрифт. Т.е., если рассматривать это с точки зрения css, то мы под этим подразумеваем font-family. Чтобы заставить его работать нам надо прикрепить к нему обработчик события jQuery change():

$('#fontFamily').change(function(){
    var ff = $(this).val();
    insTag('[font=' + ff + ']','[/font]', true);
    $(this).css('font-family', ff);
   });

Первой строкой мы прикрепляем к элементу select с ID="fontFamily" обработчик события, который будет реагировать на смену значения данного элемента.
Во второй строке мы создаем переменную ff, которой присваиваем значение элемента. Дальше мы вызываем функцию insTag() с тремя параметрами (эту функцию мы напишем чуть позже), третий параметр - true, чтобы пользователю не приходилось переставлять курсор, а сразу мог набирать текст. Первые два параметра содержат открывающий и закрывающий теги соответственно.
И в третьей строке мы изменяем font-family самого элемента select. Эта строчка для красоты. В принципе, ее можно убрать, кто считает лишним.
Идем дальше. Обратите внимание на, еще неопределенную функцию insTag(). Она еще не написана, но при решении первой же задачи она нам уже понадобилась. Значит сейчас самое время, чтобы ее написать:

insTag = function (text1, text2, edit)
   {
   el = document.getElementById('text');
   if ((document.selection))
   {
     el.content.focus();
     el.document.selection.createRange().text = text1+document.form.document.selection.createRange().text+text2;
   } else if(el.selectionStart != undefined && ((edit && edit === true) || !edit)) {
    var element    = el;
    var str     = element.value;
    var str_len = element.value.length;
    var end = element.selectionEnd;
    var start    = element.selectionStart;
    var length    = element.selectionEnd - element.selectionStart;
    element.value = str.substr(0, start) + text1 + str.substr(start, length) + text2 + str.substr(start + length);
   } else if(el.selectionStart != undefined && edit && edit !== 'true') {
    var element    = el;
    var str     = element.value;
    var start    = element.selectionStart;
    var length    = element.selectionEnd - element.selectionStart;
    element.value = str.substr(0, start) + text1 + edit + text2 + str.substr(start + length);
   } else if (el.selectionStart == undefined && edit && edit !== true) {
    document.form.content.value += text1+edit+text2;
   } else document.form.content.value += text1+text2;
     el.focus();
     if(edit === true){
      el.selectionStart = start + text1.length;
      el.selectionEnd = el.selectionStart;
     }
 
   }

Сначала мы объявляем функцию и переменные, в которые будут заносится значения переданные в вызове функции. text1 - начальный тег, text2 - конечный тег, edit - дополнительный параметр, который можно и не указывать при вызове функции. Он у нас отвечает, за то, куда поставить курсор. Если edit === true, курсор между вставленных тегов, если не указан или false, то после вставленных тегов. Если же edit - строка, значит мы эту строку должны вставить между тегами. Описывать здесь много чего можно и подробно, но т.к. моя статья рассчитана на тех,кто уже работает на javascript, то просто объясню логику. Если что, задавайте вопросы.
Сначала мы определяем где у нас курсор, выделен ли участок текста, если выделение не нулевое, то запоминаем выделенный текст, определяем начальную и конечную позицию выделения. Так же мы проверяем значение переменной edit. Если edit === true или идентичен false, т.е. является false, undefined, 0 или пустой строкой, то выполняем вставку, размещая выделенный текст между тегами. Если edit содержит какое-либо значение отличное от 0 и "", то делаем вставку тегов и заменяем выделенный текст (если таковой имеет место быть, иначе просто вставляем между тегами) на содержимое переменной edit. В конце мы сравниваем переменную edit с true, если они эквивалентны, то курсор располагаем перед конечным тегом. Если браузер не может работать с выделением, то все будет просто добавляться в конец всего написанного в textarea.

Ну, вот самую необходимую функцию, на которой, собственно говоря и будет работать весь редактор, мы написали. Дальше проще.

$('#fontSize').change(function(){
    var fs = $(this).val();
    insTag('[size=' + fs + ']', '[/size]', true);
   });

Это мы прикрепили обработчик события change() на select, который отвечает за размер шрифта. Тут объяснять, я думаю, нечего, потому что он работает точно так же, как и обработчик события change() первого элемента.

А теперь мы приступим с вами к выбору цвета. Здесь нам понадобится несколько функций. Одна будет создавать окошко, в котором будет расположена таблица цветов. При нажатии на поле с определенным цветом, должен формироваться тег в кодом цвета. Итак, листинг:

showColor = function(id){
    $('#popup').remove();
    var el = $('#' + id);
    var prop = get_obj_prop(el);
    show_color_table(prop);
    return void(0);
   }
   
   show_color_table = function(prop){
    var top = prop.offset.top + prop.size.height;
    var left = prop.offset.left;
    var txt = '<h3>Цвет текста</h3><table class="editor-color-table">';
    txt += '<tr><td data-color="#000000"></td><td data-color="#A0522D"></td><td data-color="#556B2F"></td><td data-color="#006400"></td><td data-color="#483D8B"></td><td data-color="#000080"></td><td data-color="#4B0082"></td><td data-color="#2F4F4F"></td></tr>';
    txt += '<tr><td data-color="#8B0000"></td><td data-color="#FF8C00"></td><td data-color="#808000"></td><td data-color="#008000"></td><td data-color="#008080"></td><td data-color="#0000FF"></td><td data-color="#708090"></td><td data-color="#696969"></td></tr>';
    txt += '<tr><td data-color="#FF0000"></td><td data-color="#F4A460"></td><td data-color="#9ACD32"></td><td data-color="#2E8B57"></td><td data-color="#48D1CC"></td><td data-color="#4169E1"></td><td data-color="#800080"></td><td data-color="#808080"></td></tr>';
    txt += '<tr><td data-color="#FF00FF"></td><td data-color="#FFA500"></td><td data-color="#FFFF00"></td><td data-color="#00FF00"></td><td data-color="#00FFFF"></td><td data-color="#00BFFF"></td><td data-color="#9932CC"></td><td data-color="#C0C0C0"></td></tr>';
    txt += '<tr><td data-color="#FFC0CB"></td><td data-color="#F5DEB3"></td><td data-color="#FFFACD"></td><td data-color="#98FB98"></td><td data-color="#AFEEEE"></td><td data-color="#ADD8E6"></td><td data-color="#DDA0DD"></td><td data-color="#FFFFFF"></td></tr></table>';
    $('#editor').append('<div id="popup" style="position:absolute;top:' + top + ';left:' + left + ';" />');
    $('#popup').html(txt);
    for(i = 0; i < 40; i++)
   $('#popup table td:eq(' + i + ')').css('background', $('#popup table td:eq(' + i + ')').data('color'));
    $('#popup table td').html('&nbsp;');
   }
   
   $('#popup table.editor-color-table td').live('click', function(){
    var color = $(this).data('color');
    insTag('[color=' + color + ']', '[/color]', true);
    $('#popup').remove();
   });

Здесь все просто. Сначала, мы удаляем окно, если такое есть, и создаем его заново. Генерируем html-код таблицы. Каждая ячейка таблицы содержит данные о цвете. Потом с помощью цикла проходим по каждой ячейке и закрашиваем тем цветом, данные о котором она содержит. Вы можете задать резонный вопрос: А не проще ли сразу генерировать таблицу со style="background-color: #xxxxxx"? Может и проще, но дело в том что потом проще редактировать выводимую цветовую гамму. Если бы мы сделали "как проще", то нам пришлось бы менять в два раза больше значений, дублирующих друг друга. А так, в атрибуте data-color мы сразу задаем цвет и значение которое будем вставлять в тег.
Еще нам надо прикрепить обработчик события click() на каждую ячейку. Здесь мы извлекаем данные о цвете и вставляем в начальный тег и вызываем insTag, после чего удаляем окошко выбора цвета.

Следующие 8 кнопок меня нашего редактора сами вызывают функцию insTag() с определенными параметрами. Поэтому на них мы задерживаться не будем и пойдем дальше. А дальше у нас кнопка вставки ссылки. Для ее работы, так же как и для работы кнопки вставки e-mail, нам понадобятся следующие функции:

var label = new Object;
 label = {
  video: 'Видео',
  link: 'Ссылка',
  image: 'Изображение',
  email: 'E-mail',
  media: 'Медиа'
 }
 
 var tags = new Object;
 tags = {
  video:{
   start: '[video]',
   end: '[/video]'
  },
  link:{
   start: '[link href=',
   startEnd: ']',
   end: '[/link]'
  },
  image:{
   start: '[image]',
   end: '[/image]'
  },
  email:{
   start: '[email=',
   startEnd: ']',
   end: '[/email]'
  },
  media:{
   start: '[media]',
   end: '[/media]'
  }
 }

   pre_ins2 = function(id){
    $('#popup').remove();
    var el = $('#' + id);
    var prop = get_obj_prop(el);
    show_ins2(id, prop);
    return void(0);
   }

   get_obj_prop = function(el){
    var offset = el.offset();
    var size = new Object;
    size.height = el.outerHeight();
    size.width = el.outerWidth();
    return ({
     offset: offset,
     size: size
    });
   }

   show_ins2 = function(type, prop){
    var top = prop.offset.top + prop.size.height;
    var left = prop.offset.left;
    var txt = '';
    el = document.getElementById('text');
    if ((document.selection))
   {
    el.content.focus();
    txt = document.form.document.selection.createRange().text;
   }
   else if(el.selectionStart != undefined) {
    var element    = el;
    var str     = element.value;
    var start    = element.selectionStart;
    var length    = element.selectionEnd - element.selectionStart;
    txt = str.substr(start, length);
   }
    var tag = '<div id="popup" style="position:absolute;top:' + top + ';left:' + left + ';" />';
    var ins_form = '<h3>' + label[type] + '</h3><small><br />Введите url<br /></small><input type="text" name="url" id="url" /><br /><small>Введите текст</small><br /><input type="text" name="txt" id="txt" value="' + txt + '" /><br /><br /><button onclick="doIns2(\'' + type + '\');">Вставить</button>';
    $('#editor').append(tag);
    $('#popup').html(ins_form);
   }

   doIns2 = function(type){
    var url = $('#url').val();
    var txt = $('#txt').val();
    if(!txt) txt = 'No_text';
    insTag(tags[type].start + url + tags[type].startEnd, tags[type].end, txt);
    $('#popup').remove();
   }

Для удобства создаем объекты, которые будут хранить определенные данные. Потом при вызове функции pre_ins2(id) мы получаем нужный нам объект, необходимые данные о положении на странице с помощью get_obj_prop(). Потом вызываем show_ins2(). В ней мы генерируем окошко в нужном месте страницы. Поле "введите текст" будет заполнено выделение, если выделен какой-то текст и браузер может работать с выделениями. После того, как пользователь заполнит форму и нажмет кнопку вставки, произойдет вызов функции doIns2(). Дальше все понятно.

Функция для вставки данных, где пользователь может добавить какое-то одно значение pre_ins() организовано точно также. Здесь практически они продублированы, потому что у меня не было времени на какие-либо раздумья, когда я выполнял этот заказ, изменения приходилось вносить очень быстро, поэтому много недоработок. Итак, листинг:

pre_ins = function(id){
    $('#popup').remove();
    var el = $('#' + id);
    var prop = get_obj_prop(el);
    show_ins(id, prop);
    return void(0);
   }

   show_ins = function(type, prop){
    var top = prop.offset.top + prop.size.height;
    var left = prop.offset.left;
    var tag = '<div id="popup" style="position:absolute;top:' + top + ';left:' + left + ';" />';
    var ins_form = '<h3>' + label[type] + '</h3><br /><small>Введите адрес</small><br /><input type="text" name="url" /><br /><br /><button onclick="doIns(\'' + type + '\');">Вставить</button>';
    $('#editor').append(tag);
    $('#popup').html(ins_form);
   }
   
   doIns = function(type){
    var str = $('#popup input').val();
    insTag(tags[type].start, tags[type].end, str);
    $('#popup').remove();
   }

Тут без объяснений.

Теперь у нас осталась кнопка добавления смайликов. Организуем работу следующим образом:

var smiles_shown = false;

   show_smiles = function(){
    var el = $('#smiles');
    var w_width = $('#work_pan').width() + 200;
    if(smiles_shown){
     el.animate({width: 0}, 1000);
     $('#work_pan').animate({width: w_width}, 1000);
     smiles_shown = false;
     return;
    }
    smiles_shown = true;
    w_width = $('#work_pan').width() - 200;
    $('#work_pan').animate({width: w_width}, 1000);
    el.animate({width: 200}, 1000);
   }

Для начала объявим одну переменную, которая будет хранить информацию о том, спрятан блок смайликов или нет. Дальше аккуратненько выдвигаем.

Напишем еще немного кода, для решения разных проблем, которые могут возникнуть при перезагрузке страницы и чтобы кликнув на textarea исчезали разные окна, требующие ввода информации от пользователя:

$('textarea').focus(function(){
    $('#popup').remove();
   });

   $('#fontFamily').css('font-family', $('#fontFamily').val());

Теперь нам весь этот скрипт надо поместить вот сюда:

$(function(){

       // Сюда мы помещаем теперь все написанное

});

Вот и все. Если кто-то желает посмотреть все это целиком, то вот ссылка на архив, в котором находится весь редактор.

На днях мы с вами напишем php обработчик для этого редактора. А пока до новых встреч!

20 мая 2011 г.

Пишем редактор для форума на javascript

Всем привет!
В прошлой статье я писал о том, где стоит работать фрилансеру, а вернее на кого. Сегодня же я решил, что у меня есть несколько самостоятельных работ, которые могут понадобиться начинающим программистам. Т.к. я сам являюсь новичком в этом деле, то мне иногда приходится рыскать по сети в поисках нужного скрипта, чтобы выполнить срочный заказ. Поэтому надеюсь, что данная статья окажется кому-нибудь полезной.
Ну, что же. Приступим!
Итак, сегодня мы с вами начнем писать простенький редактор для форума. Который будет выглядеть примерно так.


Для демонстрации работы этого редактора нам придется написать три страницы. Первая будет содержать непосредственно сам редактор и кнопку "Сохранить". Вторая страница будет сохранять все принятое в файл и выдавать ссылку на следующую страницу, которая будет показывать все в нужном виде.

Редактор

Создаем index.html, в заголовке пишем, все что нам необходимо. К примеру:
<html>
<head>
<title>Редактор на JavaScript</title>
<link rel="stylesheet" type="text/css" href="./style.css" />

Здесь мы вставляем файл css, который отвечает за то, как наш редактор будет выглядеть. (Если кому интересно посмотреть листинг css-файла, пишите)
Дальше нам понадобится подключить библиотеку jQuery для удобства и эффектов.

<script type="text/javascript" src="/js/jquery-1.4.4.min.js"></script>

После этого у нас будет находится сам скрипт редактора, но т.к. я (на самом деле, не я первый, не я последний) сначала пишу внешнюю сторону скрипта, а потом только код, то я позволю себе пока попустить этот блок и перейти дальше.

<script>
...
...
</script>
</head>
<body>
<div id="editor">
<div id="work_pan">
<ul class="buttons">
<li class="sel">
<select id="fontFamily">
 <option value=""></option>
 <option style="font-family:Arial;" value="Arial">Arial</option>
 <option style="font-family:Arial Black;" value="Arial Blac">Arial Black</option>
 <option style="sont-family:Arial Narrow;" value="Arial Narrow">Arial Narrow</option>
 <option style="font-family: Book Antiqua;" value="Book Antiqua">Book Antiqua</option>
 <option style="font-family:Century Gothic;" value="Century Gothic">Century Gothic</option>
 <option style="font-family:Comic Sans MS;" value="Comic Sans MS">Comic Sans MS</option>
 <option style="font-family:Courier New;" value="Courier New">Courier New</option>
 <option style="font-family:Franklin Gothic Medium;" value="Franklin Gothic Medium">Franklin Gothic Medium</option>
 <option style="font-family:Garamond;" value="Garamond">Garamond</option>
 <option style="font-family:Georgia;" value="Georgia">Georgia</option>
 <option style="font-family:Impact;" value="Impact">Impact</option>
 <option style="font-family:Lucida Console;" value="Lucida Console">Lucida Console</option>
 <option style="font-family:Lucida Sans Unicode;" value="Lucida Sans Unicode">Lucida Sans Unicode</option>
 <option style="font-family:Microsoft Sans Serif;" value="Microsoft Sans Serif">Microsoft Sans Serif</option>
 <option style="font-family:Palatino;" value="Palatino">Palatino</option>
 <option style="font-family:Tahoma;" value="Tahoma">Tahoma</option>
 <option style="font-family:Times New Roman;" value="Times New Roman">Times New Roman</option>
 <option style="font-family:Trebuchet MS;" value="Trebuchet MS">Trebuchet MS</option>
 <option style="font-family:Verdana;" value="Verdana">Verdana</option>
</select>
</li>
<li class="sel">
<select id="fontSize">
 <option value=""></option>
 <option style="font-size:8pt" value="1">1</option>
 <option style="font-size:10pt;" value="2">2</option>
 <option style="font-size:12pt;" value="3">3</option>
 <option style="font-size:14pt;" value="4">4</option>
 <option style="font-size:18pt;" value="5">5</option>
 <option style="font-size:24pt;" value="6">6</option>
 <option style="font-size:36pt;" value="7">7</option>
</select>
</li>
<li><a href="javascript:showColor('font_color');" id="font_color"><img alt="Цвет" src="images/buttons/font_color.png" /></a></li>
</ul>

Теперь попытаюсь это объяснить. Значит сам скрипт мы пока пропускаем, как уже было сказано выше. Теперь мы видим тело страницы. Давайте поместим наш редактор в DIV с ID="editor", чтобы к нему можно было легко обращаться. Потом всю рабочую область мы поместим в DIV с ID="work_pan". Здесь у нас будут находится меню и текстовое поле. Почему я все это помещаю еще в один DIV, станет ясно потом, когда мы доберемся до блока смайликов.
Дальше мы пишем первую полоску меню, где можно будет выбрать формат текста. Первые два элемента SELECT вполне понятны и объяснения не требуют. Третий элемент меню - это ссылка, нажатие на которую должно будет показать нам окошко для выбора цвета, поэтому мы сразу даем название будущей функции showColor().

Вторая полоска меню:

<ul class="buttons">
<li><a href="javascript:insTag('[b]','[/b]', true);"><img alt="Жирный" src="images/buttons/bold.png" /></a></li>
<li><a href="javascript:insTag('[i]','[/i]', true);"><img alt="Курсив" src="images/buttons/italic.png" /></a></li>
<li><a href="javascript:insTag('[u]','[/u]', true);"><img alt="Подчеркнутый" src="images/buttons/underline.png" /></a></li>
<li><a href="javascript:insTag('[s]','[/s]', true);"><img alt="Зачеркнутый" src="images/buttons/strike.png" /></a></li>
<li class="separator"></li>
<li><a href="javascript:insTag('[left]','[/left]', true);"><img alt="По левому краю" src="images/buttons/align_left.png" /></a></li>
<li><a href="javascript:insTag('[center]','[/center]', true);"><img alt="По центру" src="images/buttons/align_center.png" /></a></li>
<li><a href="javascript:insTag('[right]','[/right]', true);"><img alt="По правому краю" src="images/buttons/align_right.png" /></a></li>
<li><a href="javascript:insTag('[indent]','[/indent]', true);"><img alt="Увеличить отступ" src="images/buttons/indent.png" /></a></li>
<li class="separator"></li>
<li><a href="javascript:pre_ins2('link');" id="link"><img alt="Ссылка" src="images/buttons/link.png" /></a></li>
<li><a href="javascript:pre_ins('image');" id="image"><img alt="Изображение" src="images/buttons/picture.png" /></a></li>
<li><a href="javascript:pre_ins('video');" id="video"><img alt="Видео" src="images/buttons/video.png" /></a></li>
<li><a href="javascript:pre_ins2('email');" id="email"><img alt="E-mail" src="images/buttons/email.png" /></a></li>
<li><a href="javascript:pre_ins('media');" id="media"><img alt="Meida" src="images/buttons/media.png" /></a></li>
<li class="separator"></li>
<li><a href="javascript:insTag('[quote]','[/quote]', true);"><img alt="Цитата" src="images/buttons/quote.png" /></a></li>
<li><a href="javascript:insTag('[code]','[/code]',true);"><img alt="Код" src="images/buttons/code.png" /></a></li>
<li><a href="javascript:show_smiles();"><img alt="Смайлики" src="images/buttons/emoticons.png" /></a></li>
</ul>

Во второй полосе мы размещаем кнопки, которые будут добавлять жирность, курсив, подчеркивание, зачеркивание и т.д. Для функционирования всех этих кнопок нам понадобится всего 4pre функции:
  • insTag()
  • pre_ins()
  • pre_ins2()
  • show_smiles()

Итак, разберем эти функции.
insTag() нам нужна для того, чтобы вставлять тэги, кторые мы укажем как параметры. Т.е. к примеру, insTag('[b]','[/b]'). Причем, этой функции я решил добавить еще один параметр, который может принимать значения true, false или какая-нибудь строка, которая, будет вставляться между тегами. Если указан третий параметр и он равен true то курсор после вставки тегов, скрипт будет ставить между тегами. Если третий парамерт строка или не указан, то курсор после последнего вставленного символа. Хотя третий параметр можно было бы упростить.

pre_ins() - функция, которая выводит окошко с запросом URL некоего ресурса. Может принимать только один параметр - ID элемента. Это необходимо для того, чтобы окошко выводилось под кнопкой. А также у нас будет два объекта, которые будут содержать для каждого элемента определенные значения.

pre_ins2() - почти идентична предыдущей функции, за одним исключением - вызывает окошко не с один, а с двумя полями.

show_smiles() - последняя функция, которая не принимает никаких параметров, а только выводит блок со смайликами, по нажатию на которые в текстовое поле будут вставляться определенные теги.

Идем дальше.

<form action="save.php" method="post">
<textarea id="text" name="text" cols=50 rows=7></textarea><br /><br />
</form>

Как вы догадались, это само текстовое поле для ввода текста, куда мы все и будем вставлять.

</div>

Здесь у нас заканчивается DIV с ID="work_pan".
Дальше мы огранизовываем блок смайликов:

<div id="smiles">
<div id="smiles_header">
<a href="javascript:show_smiles();" class="close"></a><h3>Смайлики</h3>
</div>
<div id="smiles_content">
 
 <ul class="smile">
  <li><a href="javascript:insTag(' :sport_boxing: ','');"><img src="images/sport_boxing.gif" /></a></li>
  <li><a href="javascript:insTag(' :new_russian: ','');"><img src="images/new_russian.gif" /></a></li>
  <li><a href="javascript:insTag(' :alcoholic: ','');"><img src="images/alcoholic.gif" /></a></li>
  <li><a href="javascript:insTag(' :brunette: ','');"><img src="images/brunette.gif" /></a></li>
 </ul>
</div>
</div>

Здесь, я думаю, объяснения не требуются.
Теперь можно создать кнопку "Сохранить" и закончить нашу страницу.

</div>
<button onclick="document.forms[0].submit();">Сохранить</button>
</body>
</html>

Конечно, кнопку сохранения можно было организовать иначе, но почему-то в тот момент, мне захотелось сделать именно так, а не иначе. Может в этом даже был какой-то смысл :)

На этом я с вами сегодня прощаюсь в силу того, что за окном уже глубокая ночь, а завтра, т.е. уже сегодня, надо рано вставать и ехать на работу. Продолжение будет обязательно. Скорее всего завтра вы его уже сможете прочитать.

Пока-пока. Хорошего настроения!

19 мая 2011 г.

Где работать, чтобы заработать?

Привет, всем!
Давно уже я задаюсь вопросом: где работать, чтобы заработать хотя бы столько, чтобы хватало на семью?
Если честно, то ответа на этот вопрос я точного не знаю, но мне кажется, что для себя я его нашел. Дело в том, что я фрилансер. Сколько я не пытался работать на обычных работах, все равно жизнь привела меня суда, т.е. в сеть. Самое главное, что я понял, это то, что если ты не умеешь находить себе заказы сам, значит тебе просто необходимо найти, человека, который сможет тебе эти заказы найти. Таких людей навалом, стоит только позвать.
С помощью одного из таких людей я смог найти себе две постоянки, где платят нормально, только работай. На семью хватает.
Но, как говорится, чем дальше в лес, тем больше дров. Так и у меня: мне начинает казаться, что мой труд оплачивается довольно скудно. А куда денешься? Загнешь цену - лишишься заказа. Держать планку наравне с остальными, получается, что работаешь много, а получаешь мало. Как быть? Вроде и выхода нет... Но не все так уж и плохо, как кажется на первый взгляд. Выход есть всегда! Надо работать не на русских, а к примеру на англичан, австралийцев, американцев или канадцев. Поверьте уж у них-то знают цену нашему труду!

Конечно, это не аксиома, это просто мысль вслух. Но она уже проверяется на практике, и пока подтверждает себя.