Это небольшой учебный пример редактора карты памяти. За счёт очень подробных комментариев и простого кода, понять его не составит проблем. Статья предназначена для знающих и изучающих Javascript.

Я опишу особенности создания редактора карты памяти, который использует базу данных браузера. Причём, это будет не LocalStorage, который не может превышать 5 мегабайт. Объём данных сможет превысить 100-200 мегабайт, так как используется IndexedDB или webSQL, смотря что доступно в конкретном браузере.

Исходники выложены в открытый доступ на Github .

Мы уложимся в 520 строк кода, при этом в нашей карте можно будет перетаскивать узлы между собой, удалять, переименовывать и создавать новые. А также можно будет назначать одну из 120 иконок через контекстное меню.

Секрет минимализма в том, что мы будем использовать проверенные в боюплагины:

  1. Ydn.db — хранение информации в базе данных браузера с автоматическим выбором лучшего метода и единым API
  2. jQuery context menu — контекстное меню, которое можно наполнять динамически при помощи Javascript
  3. jsPlumb — расширение позволяющее рисовать линии между HTML элементами
  4. jQuery UI — Drag&drop — перетаскивание элементов между собой

PS: Также мы научимся создавать «синглтон», облегчать себе асинхронное программирование при помощи jQuery и встроенного объекта $.Deferred(), а также при помощи плагина LiveReload , сохраним краску на клавише F5 при изменении свойств CSS и кода в HTML и Javascript.

Код программы с комментариями, для тех, кто торопится

Многие могут дальше не читать, а просто ознакомиться с кодом.

Главный Javascript код с очень подробными комментариями
Инструменты которыми я пользуюсь при программировании

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

Программирую я в Coda , а иногда в Sublime Text . Coda привычнее, но чуть подтормаживает раскрашивая код (она только под Мак), а Sublime Text — очень быстрый и работает на любой платформе, но, во первых, я ещё к нему не привык, а во вторых, люблю заходить прямо в Coda на сервер, чтобы быстро исправить несколько файлов. И терминалом для связи с Debian пользуюсь через Coda.

Настоящим прорывом в скорости работы для меня стало открытие GIT . Это система контроля версий. Использую её через официальную программу GitHub :

Github использую как облачное хранение своего кода, а также публикую релизы с его помощью. Чтобы «зарелизить» версию своего сайта, я делаю так:

  1. Проверяю работу сайта на локальной машине и делаю «Коммит», т.е. подтверждаю изменения (см.картинку выше, кнопка Commit)
  2. «Пушу» изменения на сервер одной кнопкой (в том же окне внизу есть кнопка Sync)
  3. Захожу в терминал на свой сервер в Германии по SSH и запускаю комманду:
    git pull httрs://myuser_name:mysslpassword@github.com/Imater/tree.git master
  4. Запускаю скрипт, который сжимает все JS и CSS файлы, выкидывая из них комментарии и ZIP-уя их в один файл js и один файл css. Чем меньше файлов вы вставляете в свою HTML страничку, тем быстрее загружается сайт

Во время работы с кодом у меня всегда открыт Chrome и его консоль. В консоли можно попробовать создаваемые функции, поиграться с данными и отладить код при помощи встроенного отладчика:

Научитесь работать с консолью и Developer Tools и будете экономить много времени.

Теперь поговорим о краске на клавише F5 (в случае MacOS — cmd+r). Совсем недавно открыл для себя LiveReload . Есть версия и под Win и под Mac.

Устанавливаете программу, потом ставите плагин под Хром или другой браузер или вставляете код сразу за тегом body в основном HTML файле:

<script>document.write('<script src="http://' + (location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1"></' + 'script>')</script>

И после этого получаете массу удовольствия. Сценарий такой: открываете свой сайт, который программируете, потом пишете код в CSS или Javascript или заменяете картинки в папке, которую «мониторит» livereload, и как только вы сохранили, сайт сразу обновляется. Если вы заменили CSS, то изменения применяются мгновенно без перезагрузки страницы.

Ну и в конце, описания инструментов, хочу рекомендовать уменьшитель картинок PNG, который экономит до 70% объёма и, тем самым, ускоряет загрузку сайта.

Описание кода программы для создания карты памяти

Весь код выложен на Github . Вы можете скачать его и развернуть в любую папку. Всё что нужно, уже в репозитории. Все плагины в папке. Комментарии в коде очень подробные.

Начнём с основ. Я люблю оборачивать всё множество функций в «синглтон «.Делаю это следующим образом:

var API_4_MINDMAP = function(){  //singleton - при многократном запуске инициализируется единожды
     if ( (typeof arguments.callee.instance=='undefined') ) { //если объект ещё не создан
         arguments.callee.instance = new function() {
             var my_all_data = {};
             var this_api = this; //кэшируем самого себя, чтобы использовать внутри асинхронных функций
             this.jsAlert = function( name ) {
                if( prompt("Hello "+name+", are you ok?") ) {
                   var is_ok = true;
                } else {
                   var is_ok = false;
                }
                my_all_data[name] = {};
                my_all_data[name] = {name: name, hi_is_ok: is_ok};
                return my_all_data;
             }                     
     }
     return arguments.callee.instance; //возвращаем все функции
}

api4mindmap = new API_4_MINDMAP(); //регистрируем собственное api из "синглтона"
console.info( api4mindmap.jsAlert("Habrahabr") );

Преимущество такого подхода в возможности сохранять большие массивы данных внутри такого «синглтона» и не бояться, что пользователь или какой либо плагин изменит данные. Мы изолируем функции и переменные. А из практики, мне нравится набирать в консоли: «api4mindmap.js…» и ждать всплывающий список всех функций, которые я создал. Быстро и удобно.

В своё время для меня было открытием , что в вышеуказанном примере, данные в массиве my_all_data сохраняются между вызовами функций.

Теперь поговорим о плагинах, которые мы используем:

Ydn.db  — база данных браузера

Это плагин для jQuery, который позволяет при помощи единого api сохранять и считывать данные из локальных баз данных браузеров. Работает и на любых платформах, в том числе на мобильных. Даже в Phonegap.

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

 this.jsLoadAllFromDB = function() { //загружаем весь массив из базы данных браузера или из массива
     var d=new $.Deferred(); //объект позволяющий работать асинхронно

 	 my_all_data = {}; //обнуляем данные
    db.values("mindmap_db",null,99999999).done(function(records) {
     	if(records.length) {
     	$.each(records, function(i, el){
         	my_all_data["n"+el.id] = {};
         	my_all_data["n"+el.id] = el;
     	});
     	} else { //если это первый запуск, заполняю данные по шаблону и сохраняю в базе данных
         	my_all_data = my_all_data_template;
         	this_api.jsSaveAllToDB();
 	}
 	d.resolve(); //выполняем обещание, при этом выполнится функция done
 });

 return d.promise(); //говорим, что скоро выполним обещание, когда всё загрузится

 }

 this.jsFind = function(id, changes) { //возвращаем элемент с id или меняем его параметры

 	 //находим элемент в массиве объектов, буква n нужна для отработки отрицательных id
 	 var answer = my_all_data["n"+id]; 
 	 if(!answer) return false; //если элемента в массиве нет

 	 if(changes) { //если нужно внести изменения, присваиваем их по очереди
     	 $.each(changes, function(name_field, new_field_value){
 	 	 answer[name_field] = new_field_value;
     	 });

  		 db.put("mindmap_db", answer ).done(function(){ //асинхронно сохраняем данные в базе браузера
  		 	console.info("Изменения сохранены в базу данных браузера"); //выводим в консоль браузера
  		 });

 	 }
     return answer;
 }

Это пример из кода для редактора карт памяти. Тут очень простые команды: db.values(«mindmap_db»,null,99999999) — считывает все элементы из таблицы IndexedDb (если в Хроме) и возвращает через некоторое время в функцию которая в параметрах .done(). Так вы можете считывать элементы из базы данных. Можно считать один элемент при помощи команды: db.get(«mindmap_db»,5) — так вы считаете элемент с id = 5.

Для записи в базы данных используется команда: db.put(«mindmap_db», answer ).Так как эта команда асинхронна и браузер не ждёт её выполнения, она не замедляет работу. Именно поэтому эта программа будет работать в Chrome быстрее.

Для работы с асинхронными командами, применяю встроенный в jQuery объект$.Deferred() . Пример приведён выше. Вы просто обещаете в конце функции, что вернёте результат как только, так сразу, при помощи команды: return d.promise(x); А потом когда у вас всё в функции выполнилось, например данные отправлены на сервер, вы выполняете d.resolve(200); Тогда выполняется .done() функция и ей передаётся параметр x.

Это очень удобно, так как позволяет не писать функции внутри друг друга. А ещё, рекомендую изучить команду $.when. Я её использую тогда, когда у нас есть много асинхронных функций, которые мы запустили одновременно и хотим выполнить кое-что сразу после завершения всех асинхронных. Вот пример:

function jsDo() {
  var dfdArray = [];
  for(var i=0; i<1000; i++) {
    dfdArray.push( jsAsync() );
  }
  $.when.apply(null, dfdArray).then( function(){ alert("Все функции выполнены") } );
}

function jsAcync() {
  var d = new $.Deferred();
  setTimeout(function(){
     d.resolve();
  }, Math.random()*5000 );
 return d.promise();
}

По сути, тут все «обещания» набираются в массив и передаются команде $.when, которая выполняет функцию «.done» ровно в тот момент, когда последнее обещание выполнено.

Ydn.db позволяет работать с данными, не задумываясь о методах их хранения в браузере, так как оборачивает их многообразие в единый api. Он, в том числе, позволяет работать с индексами, делать отборы, накладывать фильтры, вычислять суммы в таблицах и так далее. Но в данном примере, мы используем его только для хранения и считывания данных, а роль индекса у нас выполняет массив my_all_data, это обеспечивает очень большую скорость. При этом сохранность данных обеспечивается тем, что функция api4mindmap.jsFind(id, {title: «new_title»}), обновляет этот массив сразу, а в базу отправляет данные асинхронно. Но, тем не менее, вы можете изменить заголовок узла в карте памяти и тут же обновить браузер, а все данные при этом сохранятся. База данных в браузере работает быстро, надёжно и имеет возможность хранить больше 100 мегабайт информации.

Изучив Ydn.db, можно забыть про 5 мегабайт ограничений LocalStorage.Единственное, что пользователя будут спрашивать разрешение на хранение данных каждые 5 мегабайт (Chrome не спрашивает).

Этот плагин отлично работает на Android и в iOS. Но помните, чтобы избежать ошибок, возможно вам придётся отключить выбор IndexedDB в Android. Что примечательно, в последних версиях Internet Explorer используется IndexedDB, что ускоряет работу подобных приложений за счёт асинхронности. Если вы разом сохраняете 1000 элементов, они будут делать это параллельно, но с разной скоростью, что быстрее, чем последовательная запись.

jsPlumb — рисуем линии SVG  в любом браузере

Для рисования линий между разными элементами html страницы можно было бы использовать Canvas, но он имеет множество недостатков. Вам пришлось бы создать Canvas значительного размера, например 4000 x 4000 px, что привело бы к нестабильной работе браузера. Линии были бы растровыми и на современных экранах Retina, смотрелись бы хуже, чем векторные. А самое страшное, что все эти 16 миллионов пикселей, требовали бы перерисовки при каждом вводе буквы в карту памяти.

jsPlumb рисует каждую линию в SVG и размещает его в нужное место, ставя ему CSS свойство absolute и рассчитанные координаты. Есть возможность мгновенно перерисовать все линии одной командой. По сути, вам достаточно один раз указать начальную и конечную точку, затем соединить их линией и вы можете забыть про плагин. Практически 3 команды и вы умеете обращаться с линиями.

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

Изучите, пригодится для рисования: иерархических структур, простых графиков, обучалок (которые показывают линией со стрелкой на элемент про который говорят), организационных структур предприятий, диаграмм и так далее. Работает практически во всех браузерах, в том числе на iOS и Android.

jQuery context menu  — контекстное меню

Все плагины меню которые я видел, используют для создания своей иерархии html. А в этом плагине можно создавать структуру в простом, практически JSON формате, при помощи массива объектов.

Задать набор команд в меню можно при помощи вот такого кода:

    $.contextMenu({ //назначаем на левый клик в .contextmenu
        selector: '.contextmenu', 
        trigger: 'left',
        callback: function(key, options) {
        	var id = $(this).parents("li:first").attr("myid");
            if( /icon-/ig.test(key) ) { //назначаем иконку
            	api4mindmap.jsFind(id, {icon:key});
                api4mindmap.jsRefreshMindmap();
            } else if(key == "delete") { //удаляем элемент и потомков
               api4mindmap.jsDeleteById(id);
               api4mindmap.jsRefreshMindmap(id);
            } else if(key == "add_down") { //добавляем вниз
            	var parent_id = api4mindmap.jsFind(id).parent_id;
                var new_id = api4mindmap.jsAddNew(parent_id, "Новый элемент");
                api4mindmap.jsRefreshMindmap();
                $("#node_"+new_id+" .n_title").focus();
            } else if(key == "add_right") { //добавляем внутрь
                var new_id = api4mindmap.jsAddNew(id, "Новый элемент");
                $(this).parents("li").removeClass("hide");
                api4mindmap.jsRefreshMindmap();
                $("#node_"+new_id+" .n_title").focus();
            }
        },
        delay:0,
        items: {
        	"add_down": {"name":"Добавить вниз", "icon": "icon-down-1"},
        	"add_right": {"name":"Добавить вправо", "icon": "icon-right-1"},
        	"sep1": "--------",
        	"delete": {"name":"Удалить", "icon": "icon-trash"},
            "context_make_did1011": {"name": "Иконка", "icon": "icon-emo-wink", 
                "items": icons_html //сгенерированные пункты меню с иконками
            }
        }
        });	

Обратите внимание на пункт items, всё очень понятно и красиво. Тем более, что хранить все функции вызываемые контекстным меню вместе в функции callback, позволяет меньше путаться.

Единственное, что мне пришлось сделать, это подправить скрипты плагина так, чтобы он рисовал не растровые иконки, а иконки из шрифта Fontello.

Основным преимуществом этого контекстного меню является то, что вы можете управлять клавишами для выбора пунктов и, даже, назначать горячие клавиши на определённые команды. Все преимущества становятся понятны после изучениядемонстрации на оф.сайте .

Fontello  — шрифт с векторными иконками

Тут как в супермаркете, заходите на сайт и набираете те иконки, которые вам смогут пригодиться:

После этого скачиваете архив и вставляете в свой основной html файл ссылку на CSS файл. С тех пор, независимо от браузера, вы пользуетесь этими иконками вот так: <i></i> — такой html код превратится в иконку.

Сами иконки можете оценить в демонстрации карты памяти, зайдя в контекстное меню. Там 120 иконок в естественной среде обитания.

jQuery UI  — библиотека плагинов для взаимодействия с пользователем

Это очень знаменитая библиотека в которой есть такие элементы как: выбор даты из календарика, изменение размеров любых элементов, перетаскивание, верхние закладки, сортировка элементов в списке мышкой и так далее. Изучите, не пожалеете.

В данном примере карты памяти, мы используем Drag&drop для перетаскивания узлов карты между другими узлами. Тут всё просто, единственное что нам пришлось «замудрить», это проверку того, что мы не перетаскиваем родителя к своим потомкам, так как это привело бы к зацикливанию дерева.

CSS  — рисование карты памяти

Каждый узел у нас выглядит вот так:

<div id="mindmap">
 <ul class='childs'>
  <li>
   <div class='big_n_title'><div class='n_title'></div></div>
   <ul class='childs'>
      ......
   </ul>
  </li>
 </ul>
</div>

Если правильно назначить CSS свойства элементам ul, li, .big_n_title и .n_title, то получается именно такая карта памяти, которую вы видите. Все CSS свойства вы можете посмотреть в исходниках .

По сути, весь секрет в том, что мы делаем так:

#mindmap {
    background-image: url(cross.png);
    background-attachment: scroll;
    white-space: nowrap;
}

#mindmap ul {
    display: inline-block;
    white-space: nowrap;
    vertical-align: middle;
    list-style: none;
}

#mindmap .big_n_title {
    display: inline-block;
    vertical-align: baseline;
    margin-right: 40px;
    position: relative;
}

#mindmap .n_title {
    display: inline-block;
    white-space: normal;
}

Т.е. запрещаем спискам переносить элементы на новую строку, а элементы списков делаем display:inline-block, так что они становятся подобием символов в строке текста. Также мы добавляем центрирование по вертикали, а чтобы оно работало для каждого узла в отдельности, мы оборачиваем узел в div.big_n_title.

Ничего революционного и очень сложного. И всё работает.

Проверено, что если комбинировать этот приём с direction: rtl , то можно рисовать карту и в другую сторону — справа налево. Можно сделать так, чтобы левая часть карты уходила в одну сторону, а правая в другую. Но я больше люблю односторонние карты — их проще читать.

Начнём закругляться. Все кто хочет, почитают подробные комментарии в листинге кода на github, хотя я предпочитаю называть функции длинными именами, которые сами говорят о том, что функция делает. Мой код, вполне можно улучшить, но тут передо мной стояла задача продемонстрировать работу нескольких плагинов, которые используются в значительно более сложном моёмпроекте .

Изучайте готовый пример , все исходники открыты.

PS: Если вы «чуть» доработаете пример, вы получите mindmaster и сможете брать с пользователей от 5 до 15 долларов в месяц (шутка).

Спасибо вам за внимание, а jQuery и создателям плагинов за экономию времени.

Источник http://habrahabr.ru/post/191850/