
写真:映画「ベストシューター」のトム・クルーズ
この記事では、シングルページHTMLアプリケーションがJavaScriptを介してクラウドMongoDBと対話する方法について説明します。 MongoDB-as-a-Serviceとして、 Mongolabを使用します。 500 MBのボリュームで展開されたMongoDBのコストはわずか0米ドルです。
ToDoリストを作成するために、 バックエンドは必要ありません。 REST APIを介してMongolabとやり取りし、サードパーティのJavaScriptフレームワークの助けを借りずに、クライアント部分でそのためのラッパーを作成します。
記事のナビゲーション
1. Mongolabに登録してAPIキーを取得する
2.ブラウザとMongoDBを通信する際のデータセキュリティ
3.そのような決定の範囲
4.ビジネスに取り掛かろう
5.アプリケーションコードを逆アセンブルします
6.完成したプロジェクトのデモ
1. Mongolabに登録してAPIキーを取得する
ステップ1-登録

登録は簡単で、拘束力のある支払いカードを必要としません。 Mongolabは非常に便利なサービスです。 当社では、Webアプリケーションの開発中にサンドボックスとして使用します。
ステップ2-ユーザーメニューに移動します

画面の右側に、ユーザーメニューへのリンクがあります。 このメニューでは、大切なAPIキーが私たちを待っています。
ステップ3-APIキーを取得する

APIキーを受け取ったら 、 Mongolab REST APIを使用できます
2.ブラウザとMongoDBを通信する際のデータセキュリティ

写真:トム・クルーズは笑う
あなたに警告したい-記事は本質的に純粋に教育的です。 ブラウザからクラウドデータベースとの通信は致命的なエラーになる可能性があります。 開発者のコンソールを開くだけで、攻撃者がデータベースに簡単にアクセスできることは明らかだと思います。 データベースの読み取り専用ユーザーを使用すると、クラウドMongoDBにあるすべてのデータに重要性とプライバシーがまったくない場合にのみ、この問題が解決します。
3.そのような決定の範囲
このアプローチに基づいて、あなたと私はあなたのコンピューターに保存できるtodoリストアプリケーションを作成し、1つのhtmlとjavascriptを使用してAndroid / iOS / Windows Phone / Windows 8.1用のアプリケーションを書くことができます。
4.ビジネスに取り掛かろう
Todoアプリケーションを書くのにちょうど15分かかり、この記事を書くのに2時間を費やしました(+コードについてコメントします)。 カラースキームはGoogleから取得しました 。これは、LESSの親切な人によって慎重に取り出されました。 私がやったことは、githubにアップロードしたので、貴重な時間を無駄にすることなくクラウドベースで作業することを評価できます。 リンクは記事の最後にあります。
XMLHttpRequestを介してREST APIと通信します。 Web開発の現代の世界は、jQueryやAngularなどのソリューションに非常に自信を持って焦点を当てています。それらはどこでもどこでもポップされています。 多くの場合、落ち着いてそれらなしで取得できます。
new XMLHttpRequest ()
オブジェクトは、メインのopenメソッドとsendメソッド(接続を開いてデータを送信する)およびメインのonreadystatechangeイベントを持つjsオブジェクトに関連付けられた一種のストリームです。 RESTと通信するには、 Content-Typeヘッダーを設定する必要があります:application / json; charset = UTF-8 、このためにsetRequestHeaderメソッドを使用します 。
簡単なRESTアプリケーションは次のようになります。
var api = new XMLHttpRequest(); api.onreadystatechange = function () { if (this.readyState != 4 || this.status != 200) return; console.log(this.responseText); }; // onreadystatechange onload api.open('GET', 'https://api.mongolab.com/api/1/databases?apiKey=XXX'); api.setRequestHeader('Content-Type', 'application/json;charset=UTF-8'); api.send();
メソッドをラップできませんか?
var api = new XMLHttpRequest(); api.call = function (method, resource, data, callback) { this.onreadystatechange = function () { if (this.readyState != 4 || this.status != 200) return; return (callback instanceof Function) ? callback(JSON.parse(this.responseText)) : null; }; this.open(method, 'https://api.mongolab.com/api/1/' + resource + '?apiKey=XXX'); this.setRequestHeader('Content-Type', 'application/json;charset=UTF-8'); this.send(data ? JSON.stringify(data) : null); }; /** */ api.call('GET', 'databases', null, function (databases) { console.log(databases); });
デモコレクションにタイトル「test」で新しいエントリを作成します
var test = { title: 'test' }; api.call('POST', 'databases/mydb/demo', test, function (result) { test = result; // ID });
同期ストリームの問題
api変数は1つのスレッドにすぎないため、次のコードは誤りです。
api.call('POST', 'databases/mydb/demo', test1); api.call('POST', 'databases/mydb/demo', test2);
同期をバイパスするには、最初のPOSTと2番目のスレッドに2つの別個のスレッドが必要です。 毎回呼び出しメソッドを記述しないために、「疑似クラス」 MongoRESTRequestをアセンブルする決定に至ります。実際には、呼び出しメソッドが用意された新しいXMLHttpRequestオブジェクトを返す関数になります。
var MongoRESTRequest = function () { var api = new XMLHttpRequest(); api.call = function (method, resource, data, callback) { this.onreadystatechange = function () { if (this.readyState != 4 || this.status != 200) return; return (callback instanceof Function) ? callback(JSON.parse(this.responseText)) : null; }; this.open(method, 'https://api.mongolab.com/api/1/' + resource + '?apiKey=XXX'); this.setRequestHeader('Content-Type', 'application/json;charset=UTF-8'); this.send(data ? JSON.stringify(data) : null); }; return api; }; var api1 = new MongoRESTRequest(); var api2 = new MongoRESTRequest(); api1.call('POST', 'databases/mydb/demo', test1); api2.call('POST', 'databases/mydb/demo', test2);
これで、このコードは正しく実行されます。
MongoRESTRequestの変更を続けると、おおよそ以下のアプリケーションのソースコードで説明されるオプションになります。
テンプレートエンジンなしでできることについて少し:
通常、平均的なjQueryファンのコードには次のようなものがあります。
$('#myDiv').html('<div class="red"></div>');
ここで、余分な93.6kb(圧縮された運用jQuery 1.11.2)を接続せずに、実際にどうあるべきかを見てみましょう。
var myDiv = document.getElementById('myDiv'); var newDiv = document.createElement('div'); // div newDiv.classList.add('red'); // red myDiv.appendChild(newDiv); // myDiv
わかりました、わかりました、もちろん、これは次のように実行できることをすべて知っています。
document.getElementById('myDiv').innerHTML = '<div class="red"></div>';
VanillaでDOMを操作することについてもう少し:
マップを使用してリストを作成します(ReactJS-way):
var myList = document.getElementById('myList'); var items = ['', '', '']; items.map(function (item) { var itemElement = document.createElement('li'); itemElement.appendChild(document.createTextNode(item)); myList.appendChild(itemElement); });
出力には( jsFiddleで遊ぶためのリンク )があります:
<ul id="myList"> <li></li> <li></li> <li></li> </ul>
このJavaScriptの機能の利点は、オブジェクトを完全に操作できることです。
var myList = document.getElementById('myList'); var items = [{id: 1, name: ''}, {id: 2, name: ''}, {id: 3, name: ''}]; items.map(function (item) { var itemElement = document.createElement('li'); itemElement.appendChild(document.createTextNode(item.name)); itemElement.objectId = item.id; // objectId itemElement itemElement.onclick = function () { alert('item #' + this.objectId); }; myList.appendChild(itemElement); });
チェックするJsFiddleリンク
5.アプリケーションコードを逆アセンブルします
コードのすべての行にコメントしようとしました。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> </title> <!-- : Font-Awesome. --> <link rel="stylesheet" type="text/css" href="client/styles/main.css"> <link rel="stylesheet" type="text/css" href="client/styles/font-awesome.css"> <link rel="icon" type="image/png" href="favicon.png"> </head> <body> <!-- tabindex fake-header, . --> <div id="fake-header" tabindex="1"></div> <header><i class="fa fa-bars"></i> </header> <div id="extendable"> <label> <!-- tabindex input, . --> <input name="task" tabindex="2"><button><i class="fa fa-external-link"></i></button> <!-- button tabindex , enter --> </label> </div> <div id="container"> <!-- , Font-Awesome. --> <i class="fa fa-circle-o-notch fa-spin fa-5x"></i> </div> <!-- , DuelJS, DuelJS --> <script type="text/javascript" src="client/scripts/duel/public/lib/duel.min.js"></script> <!-- DuelJS , , - . --> <script type="text/javascript"> /** * DOM : * header - <header></header> ( header) * taskInput - <input name="task"> ( input) * taskBtn - <button></button> ( ) * extendable - <div id="extendable"></div> */ var header = document.getElementsByTagName('header')[0]; var taskInput = document.getElementsByName('task')[0]; var taskBtn = document.getElementsByTagName('button')[0]; var extendable = document.getElementById('extendable'); /** * extendable. */ extendable.show = function () { /** * CSS {display: block} ( ) extendable. */ this.style.display = 'block'; /** * taskInput. */ taskInput.focus(); }; /** * extendable. */ extendable.hide = function () { /** * CSS {display: none} ( ) extendable. */ this.style.display = 'none'; }; /** * header. */ header.onclick = function () { /** * secondState * extendable. * , - : * this.secondState = !this.secondState; * extendable.show(this.secondState); */ if (!this.secondState) { extendable.show(); this.secondState = true; } else { extendable.hide(); this.secondState = false; } }; /** * tab . * fake-header tabindex = 1, tab. * callback onfocus. */ document.getElementById('fake-header').onfocus = function () { extendable.show(); header.secondState = true; }; /** * taskBtn. */ taskBtn.onclick = function () { /** * */ tasks.add({title: taskInput.value}); /** * taskInput */ taskInput.value = ''; }; /** * taskInput. */ taskInput.onkeyup = function (event) { /** * enter extendable * taskBtn ( ). */ if (event.keyCode == 13) { extendable.hide(); header.secondState = false; taskBtn.onclick(); } }; /** * - todoList. * firstRender - . * render(items) - , . * * : todoList.render(tasks); * ReactJS. * * : * http://facebook.github.io/react/index.html#todoExample */ var todoList = { firstRender: true, render: function (items) { /** * todoContainer <div id="container"></div>. */ var todoContainer = document.getElementById('container'); /** * document.createElement DOM-. */ var listElement = document.createElement('ul'); /** * DOM- innerHTML, * / HTML- . * * todoContainer. */ todoContainer.innerHTML = ''; /** * map items, * * li > * label > * input[type="checkbox"] + i + item.title. */ items.map(function (item) { var itemElement = document.createElement('li'), itemLabel = document.createElement('label'), itemCheck = document.createElement('input'), itemFACheck = document.createElement('i'), /** * TextNode , * - DOM-. */ itemText = document.createTextNode(item.title); /** * itemCheck input. * checkbox * . * * , * ( ). */ itemCheck.type = 'checkbox'; /** * JavaScript * . * * objectId - . * item._id.$oid MongoDB * ID . */ itemCheck.objectId = item._id.$oid; /** * checkbox' * checkbox * Font-Awesome. * * http://fortawesome.github.io/Font-Awesome/examples/#list * * classList - DOM-. * classList.add - . * classList.remove - . * * : * https://developer.mozilla.org/en-US/docs/Web/API/Element.classList */ itemFACheck.classList.add('fa'); itemFACheck.classList.add('fa-square'); itemFACheck.classList.add('fa-check-fixed'); /** * appendChild - * DOM- . * * : * li > * label > * input[type="checkbox"] + i + item.title. */ itemLabel.appendChild(itemCheck); itemLabel.appendChild(itemFACheck); itemLabel.appendChild(itemText); itemElement.appendChild(itemLabel); if (todoList.firstRender) { /* * , , * ( ). * * : * http://daneden.github.io/animate.css/ */ itemElement.classList.add('fadeInLeft'); } listElement.appendChild(itemElement); /** * checkbox. */ itemCheck.onclick = function (event) { itemFACheck.classList.remove('fa-check'); itemFACheck.classList.add('fa-check-square'); /** * textDecoration line-through . */ itemLabel.style.textDecoration = 'line-through'; /** * objectId DOM- * . */ tasks.remove(this.objectId); /** * . */ this.onclick = function () {}; }; }); /** * DOM- container. */ todoContainer.appendChild(listElement); if (todoList.firstRender) { todoList.firstRender = false; } } }; /** * MongoRESTRequest - , , - * , XMLHttpRequest. * * MongoRESTRequest () MongoDB REST-: * server - http:// * apiKey - API * collections - ( ) * * : * var x = new MongoRESTRequest({ * server: 'http://server/api/1', apiKey: '123', collections: '/databases/abc/collections' * }); * * @param {{server:string, apiKey:string, collections:string}} apiConfig * @returns {XMLHttpRequest} * @constructor */ var MongoRESTRequest = function (apiConfig) { /** * XMLHttpRequest. */ var api = new XMLHttpRequest(); /** * . */ api.server = apiConfig.server; api.key = apiConfig.apiKey; api.collections = apiConfig.collections; /** * . */ api.error = function () { console.error('database connection error'); }; /** * error. */ api.addEventListener('error', api.error, false); /** * REST-API * . * * : * http://docs.mongolab.com/restapi/#overview * * @param method - REST (GET, POST, PUT DELETE) * @param resource - MongoDB, , users * @param data - , * @param callback - , JSON- */ api.call = function (method, resource, data, callback) { /** * callback. */ this.onreadystatechange = function () { if (this.readyState != 4 || this.status != 200) return; return (callback instanceof Function) ? callback(JSON.parse(this.responseText)) : null; }; /** * method . * bypass . */ this.open(method, api.server + this.collections + '/' + resource + '?apiKey=' + this.key + '&bypass=' + (new Date()).getTime().toString()); /** * , JSON . */ this.setRequestHeader('Content-Type', 'application/json;charset=UTF-8'); /** * . */ this.send(data ? JSON.stringify(data) : null); }; /** * . */ api.get = function () { var bIsFunction = arguments[1] instanceof Function, resource = arguments[0], data = bIsFunction ? null : arguments[1], callback = bIsFunction ? arguments[1] : arguments[2]; return this.call('GET', resource, data, callback); }; api.post = function () { var bIsFunction = arguments[1] instanceof Function, resource = arguments[0], data = bIsFunction ? null : arguments[1], callback = bIsFunction ? arguments[1] : arguments[2]; return this.call('POST', resource, data, callback); }; api.put = function () { var bIsFunction = arguments[1] instanceof Function, resource = arguments[0], data = bIsFunction ? null : arguments[1], callback = bIsFunction ? arguments[1] : arguments[2]; return this.call('PUT', resource, data, callback); }; /** * JavaScript reserved words, * . */ api.delete = function () { var bIsFunction = arguments[1] instanceof Function, resource = arguments[0], data = bIsFunction ? null : arguments[1], callback = bIsFunction ? arguments[1] : arguments[2]; return this.call('DELETE', resource, data, callback); }; return api; }; /** * . */ var config = { server: 'https://api.mongolab.com/api/1', apiKey: '_API', collections: '/databases/_/collections' }; /** * tasks . * var tasks = new Array(); * * https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Array */ var tasks = []; /** * XMLHttpRequest MongoRESTRequest. * - MongoRESTRequest. */ var api = new MongoRESTRequest(config); /** * DuelJS ( http://habrahabr.ru/post/247739/ ). * * DuelJS . * * DuelJS * DuelJS. */ var channel = duel.channel('task_tracker'); /** * , tasks - , (Array) * JS new Array. * * tasks , * -, Data-Mapper object. * * sync . */ tasks.sync = function () { /** * if. * window.isMaster() - DuelJS, * , . */ if (window.isMaster()) { /** * REST- PaaS MongoDB . * * http://docs.mongolab.com/restapi/#list-documents * * : * GET /databases/{database}/collections/tasks * * . * * , OPTIONS. * , Network * Developer Toolbar . * * api.get('tasks', function (result) { ... * . * " tasks result" */ api.get('tasks', function (result) { /** * tasks, tasks. */ tasks.splice(0); /** * DuelJS . * . * DuelJS * , DuelJS. */ channel.broadcast('tasks.sync', result); for (var i = result.length - 1; i >= 0; i--) { /** * result. * , * tasks = result * . */ tasks.push(result[i]); } /** * ReactJS. * React, 128 render - * . * * React JSX VanillaJS. */ todoList.render(tasks); }); } else { /** * . * . * tasks () * . */ tasks.splice(0); var result = arguments[0]; for (var i = result.length - 1; i >= 0; i--) { tasks.push(result[i]); } todoList.render(tasks); } }; /** * * . * * ! */ tasks.rename = function (id, title) { for (var i = tasks.length - 1; i >= 0; i--) { if (tasks[i]._id.$oid === id) { tasks[i].title = title; todoList.render(tasks); if (window.isMaster()) { channel.broadcast('tasks.rename', id, title); var api = new MongoRESTRequest(config); /** * . * * http://docs.mongolab.com/restapi/#view-edit-delete-document */ api.put('tasks/' + id, {title: title}); } break; } } }; /** * task tasks. */ tasks.add = function (task) { /** * . * DuelJS * . */ if (window.isMaster()) { /** * , ( ). * * . * * task. * log. * logs * . * * , * / . */ var apiThread1 = new MongoRESTRequest(config); var apiThread2 = new MongoRESTRequest(config); apiThread1.post('tasks', task, function (result) { /** * task * . * * ID , * MongoDB callback. * * http://docs.mongolab.com/restapi/#insert-document */ tasks.push(result); channel.broadcast('tasks.add', result); todoList.render(tasks); }); /** * MongoDB. */ apiThread2.post('logs', { when: new Date(), type: 'created' }); } else { /** * . * . * task, ID, () * . */ tasks.push(arguments[0]); todoList.render(tasks); } }; /** * ID . */ tasks.remove = function (id) { /** * tasks . */ for (var i = tasks.length - 1; i >= 0; i--) { if (tasks[i]._id.$oid === id) { /** * , tasks, ID * ID. */ if (window.isMaster()) { /** * . * * POST - * . * * - , * * tasks, 30 . */ var apiThread1 = new MongoRESTRequest(config); var apiThread2 = new MongoRESTRequest(config); apiThread1.delete('tasks/' + id); apiThread2.post('logs', { when: new Date(), type: 'done' }); } break; } } }; /** * , 30 . */ setInterval(function () { if (window.isMaster()) { tasks.sync(); } }, 30000); /** * DuelJS - callbacks . */ channel.on('tasks.add', tasks.add); channel.on('tasks.sync', tasks.sync); channel.on('tasks.rename', tasks.rename); /** * - . */ tasks.sync(); </script> </body> </html>
1つの変数のみを変更してプロジェクト全体の配色を変更する方法
main.lessファイルのメインコードには、次のコードが含まれています(完全に記載されているわけではありません。主なことは本質を理解することです)。
上記のいずれかに変更し、同時にアプリケーション全体のテーマを変更します。私は以前、LESSでこのようなトリックを複数回行っていました。たとえば、次のようにできます(これは、LESSを見たことがない人にとってはボーナスと見なすことができます)。
@import 'palette'; @themeRed: 'red'; @themePink: 'pink'; @themePurple: 'purple'; @themeDeepPurple: 'deep-purple'; @themeIndigo: 'indigo'; @themeBlue: 'blue'; @themeLightBlue: 'light-blue'; @themeCyan: 'cyan'; @themeTeal: 'teal'; @themeGreen: 'green'; @themeLightGreen: 'light-green'; @themeLime: 'lime'; @themeYellow: 'yellow'; @themeAmber: 'amber'; @themeOrange: 'orange'; @themeDeepOrange: 'deep-orange'; @themeBrown: 'brown'; @themeGrey: 'grey'; @themeBlueGrey: 'blue-grey'; /** * http://www.google.com/design/spec/style/color.html#color-color-palette * thanks to https://github.com/shuhei/material-colors */ @theme: @themeBlueGrey; @r50: 'md-@{theme}-50'; @r100: 'md-@{theme}-100'; @r200: 'md-@{theme}-200'; @r300: 'md-@{theme}-300'; @r400: 'md-@{theme}-400'; @r500: 'md-@{theme}-500'; @r600: 'md-@{theme}-600'; @r700: 'md-@{theme}-700'; @r800: 'md-@{theme}-800'; @r900: 'md-@{theme}-900'; @color50: @@r50; @color100: @@r100; @color200: @@r200; @color300: @@r300; @color400: @@r400; @color500: @@r500; @color600: @@r600; @color700: @@r700; @color800: @@r800; @color900: @@r900; @font-face { font-family: 'Roboto Medium'; src: url('../fonts/Roboto-Regular.ttf') format('truetype'); } body { font-family: 'Roboto Medium', Roboto, sans-serif; font-size: 24px; background-color: @color900; color: @color50; margin: 0; padding: 0; }
@theme
上記のいずれかに変更し、同時にアプリケーション全体のテーマを変更します。私は以前、LESSでこのようなトリックを複数回行っていました。たとえば、次のようにできます(これは、LESSを見たことがない人にとってはボーナスと見なすことができます)。
@baseColor: #000000; @textColor: contrast(@baseColor); @someLightenColor: lighten(@baseColor, 1%);
6.
->
-> GitHub