クラウドMongoDBをVanillaJS経由でパイロットするか、15分で無料でプライベートToDoリストを作成する方法



写真:映画「ベストシューター」のトム・クルーズ


この記事では、シングルページ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ファイルのメインコードには、次のコードが含まれています(完全に記載されているわけではありません。主なことは本質を理解することです)。



 @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



All Articles