Связаться с нами

  • (097) ?601-88-87
    (067) ?493-44-27
    (096) ?830-00-01

Статьи

Використання можливостей HTML5 для збереження даних і оффлайновой роботи: Частина 2. Використання API-інтерфейсу IndexedDB в HTML5

  1. Серія контенту:
  2. Цей контент є частиною серії: Використання можливостей HTML5 для збереження даних і оффлайновой роботи
  3. Архітектурний план додатки
  4. Малюнок 1. Архітектурний план додатки Contact Manager
  5. Малюнок 2. Модель даних програми Contact Manager
  6. Підключення до бази даних і відключення від бази даних
  7. Лістинг 1. Відкриття бази даних
  8. Створення сховищ об'єктів
  9. Лістинг 2. Створення сховищ об'єктів
  10. Створення словника для списків вибору
  11. Лістинг 3. Збереження даних стану в сховище об'єктів
  12. Додавання і оновлення контакту
  13. Лістинг 4. Створення та оновлення записи контакту
  14. Видалення контакту при знаходженні в оффлайновом режимі
  15. Лістинг 5. Видалення запису контакту
  16. Лістинг 6. запрашіваніе контактів
  17. Синхронізація локальних даних з сервером
  18. Лістінг 7. Сінхронізація локальних Даних з сервером
  19. Ресурси для скачування

Використання можливостей HTML5 для збереження даних і оффлайновой роботи

Серія контенту:

Цей контент є частиною # з серії # статей: Використання можливостей HTML5 для збереження даних і оффлайновой роботи

http://www.ibm.com/search/csass/search/?sn=dw&lang=ru&cc=RU&en=utf&hpp=20&dws=rudw&lo=ru&q=%D0%98%D1%81%D0%BF%D0%BE% D0% BB% D1% 8C% D0% B7% D0% BE% D0% B2% D0% B0% D0% BD% D0% B8% D0% B5 +% D0% B2% D0% BE% D0% B7% D0% BC% D0% BE% D0% B6% D0% BD% D0% BE% D1% 81% D1% 82% D0% B5% D0% B9 + HTML5 +% D0% B4% D0% BB% D1% 8F +% D1% 81% D0% BE% D1% 85% D1% 80% D0% B0% D0% BD% D0% B5% D0% BD% D0% B8% D1% 8F +% D0% B4% D0% B0% D0% BD% D0% BD% D1% 8B% D1% 85 +% D0% B8 +% D0% BE% D1% 84% D1% 84% D0% BB% D0% B0% D0% B9% D0% BD% D0% BE% D0 % B2% D0% BE% D0% B9 +% D1% 80% D0% B0% D0% B1% D0% BE% D1% 82% D1% 8B & Search =% D0% 9F% D0% BE% D0% B8% D1% 81% D0% BA

Слідкуйте за виходом нових статей цієї серії.

Цей контент є частиною серії: Використання можливостей HTML5 для збереження даних і оффлайновой роботи

Слідкуйте за виходом нових статей цієї серії.

У першій статті серії "Використання бази даних HTML5 і офлайнових можливостей HTML5" були представлені доступні в специфікації HTML5 опції для підтримки офлайнових додатків і локального збереження даних, при цьому основна увага була приділена технології localStorage. У цій статті описується потужна технологія збереження даних IndexedDB (Indexed Database), що є частиною стандарту HTML5, і розглядається інтеграція провайдера даних IndexedDB з додатком Contact Manager, яке було створено в першій статті.

Навчальний додаток Contact Manager (для імен, адрес і номерів телефонів) має онлайновий і офлайновий режими роботи. При знаходженні додатки в оффлайновом режимі дані знаходяться в локальному персистентному сховище. Після перемикання в онлайновий режим проста функція синхронізації даних синхронізує локальні зміни даних з сервером. Описуваної програми підтримує чотири базові функції персистентного зберігання - створення / читання / оновлення / видалення (create / read / update / delete, CRUD) - і в онлайновому, і в оффлайновом режимах.

Архітектурний план додатки

інтерфейс сервера

Інтерфейс сервера складається з двох сервлетів: ContactServlet і DictionaryServlet. Таблиця з описом цих інтерфейсів приведена в першій статті . Реалізація цих сервлетів, а також відповідних бізнес-сервісів і провайдерів даних виходить за рамки цієї статті.

Спочатку звернемося до малюнка 1, на якому показана архітектурна схема додатки Contact Manager. Архітектура сервера складається з двох сервлетів, які відповідають бізнес-сервісів і провайдерам даних. Інтерфейс складається з одного HTML-файлу, чотирьох JavaScript-модулів і зовнішнього посилання на новітню версію бібліотеки jQuery.

У цій статті ми замінимо провайдера оффлайновой бази даних провайдером на основі API-інтерфейсу IndexedDB, а саме замінимо JavaScript-модуль localdb.js.

Малюнок 1. Архітектурний план додатки Contact Manager

модель даних

Модель даних складається з двох об'єктів даних: contact і state (див. Рис. 2). Таблиця contact містить фактичні контактні дані; таблиця state містить значення словника для списку вибору стану.

Малюнок 2. Модель даних програми Contact Manager

API-інтерфейс IndexedDB

Специфікація HTML5 включає кілька технологій персистентного зберігання. Технологія IndexedDB є кращою базою даних для HTML5-браузерів; вона замінює виходить з ужитку технологію WebSQL.

Підтримка веб-браузерами технології IndexedDB і реалізації API-інтерфейсу IndexedDB в веб-браузерах не завжди однакова. На момент написання цієї статті технологія IndexedDB підтримувалася браузерами Google Chrome 11+, Mozilla Firefox 4+ і Windows® Internet Explorer® 10.

Веб-сайт може містити одну або кілька баз даних, кожна з яких ідентифікується своїм унікальним ім'ям. Кожна база даних може містити одне або кілька сховищ об'єктів. Сховище об'єктів подібно таблиці в реляційної базі даних в тому сенсі, що воно являє собою набір записів, який ідентифікується по своєму унікальному імені. Однак сховище об'єктів відрізняється від таблиці реляційної бази даних в таких аспектах, як зберігання даних, доступ до даних і запити до даних. Дані в сховищі об'єктів зберігаються за принципом ключ-значення. Ключ повинен бути унікальним в межах сховища об'єктів; він може бути заданий цілеспрямовано або створений генератором ключів.

Специфікація API-інтерфейсу IndexedDB передбачає підтримку таких поширених конструкцій для баз даних, як транзакції, індексація, запити даних і покажчики. Ця специфікація включає як синхронний, так і асинхронний API-інтерфейси. Синхронний API призначений для використання всередині компонентів типу Web Worker. Слід, однак, відзначити, що не всі веб-браузери підтримують технологію Web Worker і синхронний API-інтерфейс IndexedDB. Асинхронний API-інтерфейс використовує запити та зворотні виклики. Всі операції з базами даних, такі як відкриття бази даних, вилучення даних, запрашіваніе даних і видалення даних, використовують виклики API-інтерфейсу. Кожен запит супроводжується зворотним викликом onsuccess або onerror в разі успішного або невдалого виконання операції відповідно. Зворотні виклики надають параметр "результат події" для повернутих даних.

Навчальний додаток Contact Manager складається з однієї бази даних з двома сховищами об'єктів. Перше сховище об'єктів є сховищем об'єктів "контакти" (contacts) і містить записи з фактичними контактними даними. Друге сховище об'єктів є сховищем об'єктів "стану" (states) і містить значення для списку вибору станів. У цій статті демонструється використання асинхронного API-інтерфейсу для доступу до даних.

Підключення до бази даних і відключення від бази даних

Оскільки в кожному веб-браузері реалізація технології IndexedDB має свої особливості, найкращий підхід полягає в тому, щоб створити глобальну змінну (localDatabase) і ініціалізувати її відповідно до особливостей реалізації в різних веб-браузерах. Ця глобальна змінна надає посилання на API-інтерфейс IndexedDB.

Наступний крок полягає у відкритті бази даних за допомогою методу open. Якщо запит на відкриття бази даних виявляється успішним, здійснюється зворотний виклик onsuccess. Весь операційний код бази даних повинен виконуватися в рамках зворотного виклику onsuccess. Як передзвонити onerror здійснюється в разі виникнення помилки при відкритті бази даних. У лістингу 1 показаний код для ініціалізації і відкриття бази даних.

Лістинг 1. Відкриття бази даних
var localDatabase = {}; localDatabase.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB; localDatabase.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange; localDatabase.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction; console.log ( 'opening local database'); var openRequest = localDatabase.indexedDB.open (dbName); openRequest.onerror = function (e) {console.log ( "Database error:" + e.target.errorCode); }; openRequest.onsuccess = function (event) {console.log ( "open database request succeeded"); console.log ( 'set db'); db = openRequest.result; ...};

Для відключення від бази даних досить викликати метод close об'єкта бази даних IndexedDB.

Створення сховищ об'єктів

Наступний крок полягає в створенні сховищ об'єктів в рамках бази даних. Створити сховище об'єктів можна в будь-який момент часу, однак після створення його можна буде змінити тільки в рамках зворотного виклику onupgradeneeded до запиту на відкриття бази даних. Цей зворотний виклик вказує на необхідність оновлення бази даних.

Код в лістингу 2 демонструє створення двох вищезгаданих сховищ об'єктів.

Лістинг 2. Створення сховищ об'єктів
openRequest.onupgradeneeded = function (evt) {console.log ( 'creating object stores'); var contactsStore = evt.currentTarget.result.createObjectStore (contactStore, {keyPath: "id"}); var statesStore = evt.currentTarget.result.createObjectStore (stateStore, {keyPath: "itemId"}); console.log ( 'object stores created'); };

Мінлива keyPath ідентифікує поле ключа для сховища об'єктів.

Створення словника для списків вибору

Створення та зміна записів в сховище об'єктів здійснюється за допомогою транзакцій. API-інтерфейс IndexedDB надає для транзакцій три режими.

  • readonly - Доступ до сховища об'єктів тільки для читання.
  • readwrite - Доступ до сховища об'єктів для читання-запису.
  • versionchange - Крім доступу до сховища об'єктів для читання-запису, надається можливість створення і видалення сховищ об'єктів.

Створення транзакції здійснюється за допомогою наступного виразу.

var transaction = db.transaction ( "states", "readwrite");

Після створення транзакції наступний крок полягає в отриманні посилання на сховище об'єктів. Об'єкт transaction містить властивість objectstores, яке забезпечує доступ до сховищ об'єктів, асоційованим з відповідною базою даних. Для отримання посилання на сховище об'єктів states використовуйте такий вираз.

var store = transaction.objectStore ( "states");

Після підключення до сервера нам потрібно замінити всі локальні дані states свіжими серверними даними. Для цього необхідно спочатку очистити сховище об'єктів станів і тільки після цього заповнювати його свіжими значеннями з сервера. Для очищення сховища об'єктів використовуйте метод clear.

Якщо сховище об'єктів було очищено успішно, здійснюється зворотний виклик onsuccess. Тепер в рамках цього зворотного виклику можна виконати циклічний обхід і додати кожне значення в локальне сховище об'єктів станів. Дані, що підлягають додаванню в сховище об'єктів, містяться в строковому масиві stateArray. Використовуйте jQuery-метод $ .each для ітеративного проходження по масиву. Для кожного значення в масиві states викличте метод put для додавання запису в сховище об'єктів станів.

Фрагмент коду в лістингу 3 демонструє заповнення сховища об'єктів значеннями в масиві states для локального і офлайнового доступу.

Лістинг 3. Збереження даних стану в сховище об'єктів
try {console.log ( 'saving local state data'); var openRequest = localDatabase.indexedDB.open (dbName); openRequest.onerror = function (e) {console.log ( "Database error:" + e.target.errorCode); }; openRequest.onsuccess = function (event) {db = openRequest.result; console.log ( 'opening states store'); var transaction = db.transaction ( "states", "readwrite"); var store = transaction.objectStore ( "states"); var clearReq = store.clear (); clearReq.onsuccess = function (ev) {console.log ( 'cleared state store'); $ .Each (stateArray, function (i, item) {var itemId = generateUUID (); var request = store.put ({ "text": item, "itemId": itemId}); request.onsuccess = function (e) {}; request.onerror = function (e) {console.log (e.value);};}); }; db.close (); }; } Catch (e) {console.log (e); }

Тепер пора переходити до додавання і оновленню записів контактів у оффлайновом режимі.

Додавання і оновлення контакту

У цій статті використовується такий же підхід до створення та оновлення записів, як і в першій статті (Коли ми зберігали офлайнові дані в сховищі localstorage). Нові записи ідентифікуються за унікальним негативного числа, згенеровані для поля ID. Негативне число вказує, що цей запис є новою і повинна бути створена в таблиці бази даних на сервері. Крім того, прапор isDirty вказує, що запис було змінено або створена при знаходженні в оффлайновом режимі. Для збереження запису в сховище об'єктів використовуйте метод put (як і при заповненні сховища об'єктів станів). У лістингу 4 показаний повний текст програмного коду для створення та оновлення записи в сховище об'єктів:

Лістинг 4. Створення та оновлення записи контакту
openRequest.onsuccess = function (event) {db = openRequest.result; var transaction = db.transaction (contactStore, "readwrite"); var objectStore = transaction.objectStore (contactStore); var id = $ ( '# contactId'). val (); var firstName = $ ( '# firstName'). val (); var lastName = $ ( '# lastName'). val (); var street1 = $ ( '# street1'). val (); var street2 = $ ( '# street2'). val (); var city = $ ( '# city'). val (); var zipCode = $ ( '# zipCode'). val (); var state = $ ( '# state'). val () ;; if (contactId> 0) {var getRequest = objectStore.get (parseInt (contactId)); getRequest.onsuccess = function (event) {var contact = event.target.result; contact.firstName = firstName; contact.lastName = lastName; contact.street1 = street1; contact.street2 = street2; contact.city = city; contact.zipCode = zipCode; contact.isDirty = true; contact.lastModifyDate = ""; contact.isDeleted = false; var addRequest = objectStore.put (contact); addRequest.onsuccess = function (event) {recordUpdated = true; }; addRequest.onerror = function (e) {console.log (e.value); }; }; getRequest.onerror = function (e) {console.log (e.value); }; } // if update else {var newContactId = (-1) * Math.floor (Math.random () * 100000); var lastModifyDate = ""; var newContact = { "timeStamp": "", "id": newContactId, "firstName": firstName, "lastName": lastName, "street1": street1, "street2": street2, "city": city, "zipCode" : zipCode, "state": state, "isDirty": true, "lastModifyDate": "", "isDeleted": false}; var request = objectStore.put (newContact); var nextIndex = data.length; data [nextIndex] = newContact; recordUpdated = true; } // if create

Тепер я покажу, як видалити запис контакту при знаходженні в оффлайновом режимі.

Видалення контакту при знаходженні в оффлайновом режимі

При видаленні контакту в оффлайновом режимі ми не хочемо переміщати відповідний запис. Замість цього ми хочемо заблокувати її відображення в оффлайновом списку контактів і вказати, що цей запис було видалено. З цією метою ми використовуємо прапор isDeleted (подібний метод описувався в першій статті цієї серії). Прапор isDirty також встановлюється в стан true, що вказує на зміну цього запису при знаходженні в оффлайновом режимі. Після того зміни запису в сховище об'єктів список контактів оновлюється з метою видалення цього запису зі списку (записи з прапором isDeleted в стані true не відображаються). Код в лістингу 5 демонструє виконання вищеописаної задачі.

Лістинг 5. Видалення запису контакту
try {console.log ( 'deleting local contact'); var openRequest = localDatabase.indexedDB.open (dbName); console.log ( 'after open'); openRequest.onerror = function (e) {console.log ( "Database error:" + e.target.errorCode); }; openRequest.onsuccess = function (event) {db = openRequest.result; console.log ( 'opening contacts store'); var transaction = db.transaction (contactStore, "readwrite"); var store = transaction.objectStore (contactStore); var getRequest = store.get (contactId); getRequest.onsuccess = function (ev) {var item = getRequest.result; item.isDeleted = true; item.isDirty = true; var request = store.put (item); request.onsuccess = function (e) {alert ( 'Contact deleted'); loadOfflineContacts (); }; request.onerror = function (e) {console.log (e.value); }; } GetRequest.onerror = function (e) {console.log (e.value); }; db.close (); }; loadOfflineContacts (); } Catch (e) {console.log (e); }

запрашіваніе контактів

Для отримання записів контактів зі сховища об'єктів контактів використовується покажчик IndexedDB. Подібно вказівниками в реляційних базах даних, покажчик IndexedDB дозволяє виконувати итеративное проходження по записах в рамках сховища об'єктів. В процесі ітеративного проходження по записах ми створюємо масив, що містить записи контактів. Записи з прапором isDeleted в стані true ігноруються. У лістингу 6 демонструється створення масиву контактів з використанням даних, що містяться в сховище об'єктів контактів.

Лістинг 6. запрашіваніе контактів
var data = new Array (); ... var cursorRequest = objectStore.openCursor (); cursorRequest.onsuccess = function (evt) {var cursor = evt.target.result; if (cursor) {if (! cursor.value.isDeleted) {var newContact = { "timeStamp": cursor.value.timeStamp, "id": cursor.value.id, "firstName": cursor.value.firstName, " lastName ": cursor.value.lastName," street1 ": cursor.value.street1," street2 ": cursor.value.street2," city ": cursor.value.city," zipCode ": cursor.value.zipCode," state ": cursor.value.state," lastModifyDate ": cursor.value.lastModifyDate," isDeleted ": cursor.value.isDeleted," isDirty ": cursor.value.isDirty}; //console.log('adding '+ newContact.toString ()); //console.log("adding contact to array: "+ data.length); data [data.length] = newContact; } Cursor.continue (); } // more records else {displayContactData (data); } // no more records}; // open cursor

Тепер я продемонструю простий алгоритм і підхід до синхронізації результатів офлайнових доповнень і змін з сервером.

Синхронізація локальних даних з сервером

Якщо ви працюєте в онлайновому режимі, всі CRUD-операції використовують сервлети, а база даних на сервері оновлюється негайно. Локальна база даних (IndexedDB) також оновлюється згідно зі змінами онлайнової бази даних, що гарантує постійну доступність свіжих даних як онлайновому, так і в оффлайновом режимах.

При знаходженні в оффлайновом режимі все CRUD-операції оновлюють дані в базі даних IndexedDB. Після відновлення з'єднання з сервером виконуються наступні дії.

  • Всі записи, які були створені в локальній базі даних, зберігаються на сервері.
  • Всі записи, які були змінені в локальній базі даних, оновлюються на сервері.
  • Всі записи, які були видалені з локальної бази даних, видаляються з сервера.

У лістингу 7 наведено повний текст методу синхронізації. Для операцій створення, оновлення та видалення використовуються ті ж онлайнові функції, які були описані в першій статті .

Перший крок полягає у виконанні ітеративного проходження по записах в сховище об'єктів контактів за допомогою покажчика і в побудові масиву, що містить всі записи, які повинні бути відправлені на сервер. У записів, які були оновлені або створені локально, властивість isDirty має значення true. Операція Save (зберегти) ідентифікується як нова, якщо її унікальний ідентифікатор запису має від'ємне значення (тобто база даних MySQL не привласнила йому жодного значення). Записи, які були видалені на локальній системі, позначаються за допомогою властивості isDeleted.

Після того як у вас буде масив, що містить всі записи, які повинні бути відправлені на сервер, використовуйте jQuery-метод $ .each для ітеративного проходження по масиву і відсилання кожного зміни на сервер.

І, нарешті, коли метод синхронізації даних закінчить свою роботу, він оновить локальне сховище об'єктів контактів свіжими даними з сервера, включаючи будь-які зміни, які ви (і інші користувачі) внесли, поки ви перебували в оффлайновом режимі.

Лістінг 7. Сінхронізація локальних Даних з сервером
... cursorRequest.onsuccess = function (evt) {var cursor = evt.target.result; if (cursor) {var isDirty = cursor.value.isDirty; var curId = cursor.value.id; console.log (curId + 'isDirty =' + isDirty); if (isDirty) {var newContact = { "timeStamp": cursor.value.timeStamp, "id": curId, "firstName": cursor.value.firstName, "lastName": cursor.value.lastName, "street1": cursor .value.street1, "street2": cursor.value.street2, "city": cursor.value.city, "zipCode": cursor.value.zipCode, "state": cursor.value.state, "lastModifyDate": cursor .value.lastModifyDate, "isDeleted": cursor.value.isDeleted, "isDirty": cursor.value.isDirty}; //console.log('adding '+ newContact.toString ()); //console.log("adding contact to array: "+ data.length); data [data.length] = newContact; } Cursor.continue (); } // more records else {console.log ( "no more records"); console.log ( 'number of modified records:' + data.length); var recordsUpdated = 0; var recordsCreated = 0; var recordsDeleted = 0; $ .Each (data, function (i, item) {console.log ( "processing record" + item.id); if (item.isDeleted) {deleteOnlineContact (item.id, true); recordsDeleted ++;} else if (item .isDirty &&! item.isDeleted) {$ ( 'input [name = "contactId"]') [0] .value = item.id; $ ( 'input [name = "firstName"]') [0] .value = item.firstName; $ ( 'input [name = "lastName"]') [0] .value = item.lastName; $ ( 'input [name = "street1"]') [0] .value = item .street1; $ ( 'input [name = "street2"]') [0] .value = item.street2; $ ( 'input [name = "city"]') [0] .value = item.city; $ ( 'select [name = "state"]') [0] .value = item.state; $ ( 'input [name = "zipCode"]') [0] .value = item.zipCode; var dataString = $ ( "# editContactForm"). serialize (); postEditedContact (dataString, true); if (item.id> 0) {recordsUpdated ++;} else {recordsCreated ++;}}}); var msg = "Synchronization Summary \ n \ tRecords Updated:" + recordsUpdated + "\ n \ tRecords Created:" + recordsCreated + "\ n \ tRecords Deleted:" + recordsDeleted; alert (msg);

Висновок

Дана стаття спирається на базовий матеріал, який був викладений в першій частині даної серії статей. У ній використовується один і той же шаблон як для онлайнової, так і для оффлайновой підтримки, а також для надання користувачеві однакових можливостей. У статті представлений API-інтерфейс IndexedDB, за допомогою якого здійснюється синхронізація результатів офлайнового створення, видалення і зміни записів. Використовуваний в статті програмний код не має потужних механізмів для обробки помилок і вирішення конфліктів (коли одна і та ж запис змінена і вами на локальній системі, і іншим користувачем на сервері), тому він не готовий до використання у виробничому середовищі. Проте він є хорошим фундаментом для майбутньої роботи.

Ресурси для скачування

Схожі тими

  • Оригінал статті: Using HTML5 database and offline capabilities, Part 2: Leveraging the IndexedDB API in HTML5 .
  • Основи HTML5: Частина 1. Початок роботи , DeveloperWorks травень 2011 р р Перша стаття серії з чотирьох частин, присвяченій розгляду реалізованих в HTML5 змін.
  • HTML5, CSS3, and related technologies (HTML5, CSS3 і пов'язані технології), developerWorks, апрель 2011 р Зустрітися зі специфікацією HTML5 і з її впливом на технологію CSS3.
  • основи HTML5 . Дізнайтеся більше про технології HTML5 за допомогою цієї навчальної програми.
  • технологія Web Workers . Цей API-інтерфейс дозволяє розробнику створювати фонові компоненти для виконання скриптів паралельно з основною сторінкою.
  • технологія Web Storage надає API-інтерфейс для персистентних сховищ даних типу key-value (ключ-значення) в веб-клієнтів.

Підпішіть мене на ПОВІДОМЛЕННЯ до коментарів

Com/search/csass/search/?

Новости

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

Пиротехника своими руками в домашних
Самые лучшие полезные самоделки рунета! Как сделать самому, мастер-классы, фото, чертежи, инструкции, книги, видео. Главная САМОДЕЛКИ Дизайнерские

Фольгированные шары с гелием
Для начала давайте разберемся и чего же выполнен фольгированный шар и почему он летает дольше?! Как вы помните, наши латексные шарики достаточно пористые, поэтому их приходится обрабатывать специальным

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

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

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

Надувные шарики с гелием с доставкой
На праздники часто бывают востребованы воздушные шарики, надутые гелием. Обычно, их покупают уже готовыми (надутыми) и привозят на праздник. Или, приглашают специалистов, которые приезжают и надувают

Аниматоры на детские праздники в Зеленограде
Уж сколько раз твердили миру…Что готовиться ко дню рождения нужно заранее, а не бегать в предпраздничный день угорелой кошкой. Нельзя впихнуть в 24 часа дела, рассчитанные на недели. К празднику нужно