This article is located on outdated, archived website. Please go to the new website to continue browsing. Thank you.

Open new website

How I Structured English Tenses Into JSON and Visualized It.

I have been learning English strenuously since the end of May. I have got some progress but I’m still suffering from tenses. Yesterday I decided to stop suffering and started the process of structuring of English tenses. As I was expecting the result was successful and now I’m going to explain in which way I have done it.

I have tried to explain everything, so I keep very simple things here too.

You can check the final result here:

http://english-for-engineers.tryshchenko.com/

In the end of the topic I also added GitHub link, feel free to check it and contribute. It’s OpenSource.

Step 1: Choosing of data storage

I asked myself a question: which action should we do on the stored data. And the answer was pretty simple: “read”. Any multiple select, save, delete, edit operations, nothing. So I have decided to keep it stupidly simple and use JSON for a moment. Actually, jSON is a perfect solution for our case, because:

  • It can be easily edited by anybody without admin panel or specific knowledge (I looking for an English teacher’s review)
  • Doesn’t require any backend to manage data (Fast in use, fast in development)
  • Array of objects seemed a good option to structure data (and it is!)
  • It the future can be easily migrated to any document-oriented database (possibly, mongo)

Step 2: Choosing of data format

I decided to use separate JSON file for the each tense. It allows us

  • It should have loaded only when user will request it
  • It is easy to manage from editors point
    I have written the simple XHR wrapper which allows my application to load the correct tense’s JSON file by tense name. It’s scalable and you can easily add new tenses.

I have based my structure on the next assumptions:

  • Tenses can contain different flows so each JSON is an array of objects in JS terms (you can represent it in your mind like an array of associative arrays).
  • Each tense always has three different constructions: positive, negative and question.
  • We aren’t providing cases with different tenses in the one sentence.
  • Each sentence has:

    • Person (subject)
    • Verb
  • Sentence can have:

    • Auxiliary verb (Also, I added “Auxiliary verb 2” for some cases, when auxiliary verb should have been splitting)
    • Subject (the second noun which is the subject of action)
    • Time Markers (sometimes they aren’t important)
  • Auxiliary verbs may be different for singular and plural values
    As result, you can see the simplest example: Present Simple Tense

    [
    	{
    		"title": "Present simple",
    		"description": "Some regular action",
    		"schema": {
    			"positive": [
    				"P", "V1", "S", "TM"
    			],
    			"negative": [
    				"P", "N", "V1", "S", "TM"
    			],
    			"question": [
    				"Q", "P", "V1", "S", "TM"
    			]
    		},
    		"axilary": {},
    		"negativeMarker": {
    			"singular": {
    				"1": "don't",
    				"2": "don't",
    				"3": "doesn't"
    			},
    			"plural": {
    				"1": "don't",
    				"2": "don't",
    				"3": "doesn't"
    			}
    		},
    		"questionMarker": {
    			"singular": {
    				"1": "Do",
    				"2": "Do",
    				"3": "Does"
    			},
    			"plural": {
    				"1": "Do",
    				"2": "Do",
    				"3": "Does"
    			}
    		},
    		"timeMarkers": [
    			"always",
    			"frequently",
    			"often",
    			"usually",
    			"generally",
    			"rarely",
    			"regulary",
    			"accasionally",
    			"sometimes",
    			"seldom",
    			"never",
    			"every ...",
    			"once a ...",
    			"twice a ...",
    			"X times a ..."
    		]
    	}
    ]

I added two text fields for the short text explanation (title and description).

Auxiliary verbs and markers will be explained in the next steps.

Time markers are just an array of time words which can be used for this tense.

Step 3: Schema

The schema consists of 3 parts: positive, negative and question. The most interesting part is covering them. I have been calling them ‘tense schema’ and it’s describing in which way we should build our sentence. It’s an array which consists of few strange keys:

  • P - person
  • V1, V2, V3, Ving - Verb in different forms
  • S - subject
  • A - auxiliary verb
  • N - negative auxiliary verb
  • Q - question auxiliary verb (on practice, A === Q, but I am still researching it)
    Using factories my frontend just creating the target elements on the page depending on the array’s order.
"schema": {
	"positive": [
		"P", "V1", "S", "TM"
	],
	"negative": [
		"P", "N", "V1", "S", "TM"
	],
	"question": [
		"Q", "P", "V1", "S", "TM"
	]
}

I used the object literal as “switch” to map the reductions to their factories. Probably you’ve already understood that each reduction is a marker. The controller parses all the schema in the forEach cycle and creates DOM elements for the each reduction.

var exampleElements = {
	P: app.components.person,
	V1: app.components.verbFactory(1),
	V2: app.components.verbFactory(2),
	V3: app.components.verbFactory(3),
	Ving: app.components.verbFactory('+ ing'),
	S: app.components.plainFactory('details'),
	TM: app.components.timeMarkerFactory,
	A: app.components.axilaryFactory,
	AS: app.components.axilaryFactory,
	N: app.components.axilaryFactory,
	Q: app.components.axilaryFactory,
}

For static entities like ‘Verb label’ or ‘Details label,has’ it doesn’t have any sense to run factory each time, so I have written their’s preparation on the initialization event.

Others factories will have been called when we will parse the schema.

Step 4: Auxiliary verbs

And, as result, the factory is going to be something like this:

var app = app || {};
app.components = app.components || {};

app.components.axilaryFactory = (function(){

	var mapper = {
		N: 'negativeMarker',
		A: 'questionMarker',
		Q: 'questionMarker',
		AS: 'specialMarker',
	}

	/**
	 * Build dropdown from income data
	 * @param  {object]} mapping Simple relations mapper
	 * @param  {object} node    datasheet
	 * @return {object}         DOM element
	 */
	function buildDropdown(mapping, node) {
		var element = document.createElement('div');
		var select = document.createElement('select');

		element.className = 'col-md-2 plain-element';
		select.className = 'form-control';

		['singular', 'plural'].forEach(function(element){
			var optgroup = document.createElement('optgroup');

			optgroup.label = element;
			["1","2","3"].forEach(function(type) {
				var option = document.createElement('option');

				option.innerHTML = type + ': ' + node[mapping][element][type];
				optgroup.appendChild(option);
			});
			select.appendChild(optgroup);
		});
		element.appendChild(select);

		return element;
	}

	return function(type, node) {
		var target = buildDropdown(mapper[type], node);

		return target;
	}
})();

I used jQuery for AJAX request, but keep the other code without it. I’m going to switch XHR calls to the vanilla JS in the near future.

Step 5: Building the DOM

I’m going to split all method into the small parts and explain the each one.

We are starting from creation the blank element which should contain all generated elements:

var parent = document.createElement('div');

As I have explained before - each JSON (tense) consists of one or more sections, so it’s an array and we should iterate over itю

Inside the each cycle, we’re validating that JSON has correct form and going to build our elements. I’m going to keep this trivial code outside this topic and proceed to draw operations.

Firstly, let’s draw a title:

var title = document.createElement('h2');
title.innerHTML = (index + 1) + '. ' + node.title;
parent.appendChild(title);

I have done the description section in the same way, so I’ll also skip it.

The most interesting part for us is schema rendering, so let’s go deeper with it. As you can remember - the each time’s flow should consist of three parts: Positive, Negative and Question forms. So we’re going to iterate over them and build our DOM using factories like that what I have listed before.

['positive', 'negative', 'question'].forEach(function(type){
	var block = document.createElement('div');
	block.className = "example";
	parent.appendChild(headersFactory(type));
	[].forEach.call(node.schema[type], function(el) {
		var atom;
		if (typeof exampleElements[el] == 'function') {
			atom = exampleElements[el](el, node).cloneNode(true);
		} else {
			atom = exampleElements[el].cloneNode(true);
		}
		block.appendChild(atom);
	});
	parent.appendChild(block);
});

I’d like to prefer to use “No ‘else’ rule” but it can really simplify the code here.

Step 6: Receiving data from the server

When user clicks on tenses in the menu - our listener triggers XHR event:

app.xhr.get(file, render, fail);

File is just a name of JSON file, I have added it to HTML in ‘data-rule’ attribute:

<div class="col-md-3 sidebar">
		<h3>Present</h3>
		<div class="list-group">
		  <a data-rule="presentSimple" class="list-group-item active">Present simple</a>
		  <a data-rule="presentContinuous" class="list-group-item">Present continuous</a>
		  <a data-rule="presentPerfect" class="list-group-item">Present perfect</a>
		  <a data-rule="presentPerfectContinuous" class="list-group-item">Present perfect continuous</a>
		</div>
		<h3>Past</h3>
		<div class="list-group">
		  <a data-rule="pastSimple" class="list-group-item">Past simple</a>
		  <a data-rule="pastContinuous" class="list-group-item">Past continuous</a>
		  <a data-rule="pastPerfect" class="list-group-item">Past perfect</a>
		  <a data-rule="pastPerfectContinuous" class="list-group-item">Past perfect continuous</a>
		</div>
		<h3>Future</h3>
		<div class="list-group">
		  <a data-rule="futureSimple" class="list-group-item">Future simple</a>
		  <a data-rule="futureContinuous" class="list-group-item">Future continuous</a>
		  <a data-rule="futurePerfect" class="list-group-item">Future perfect</a>
		  <a data-rule="futurePerfectContinuous" class="list-group-item">Future perfect continuous</a>
		</div>
	</div>

So the ‘data-rule’ equals the ‘file’ variable. Second two variables are callbacks, which we are passing into the XHR method. Let’s take a look at the render method:

function render(data) {
	var element = prebuildResponse(data);

	sections.primary.innerHTML = '';
	sections.primary.appendChild(element);
}

This method is just wrapping the ‘prebuildResponse’ method. It clears the old information and appends a new.

Now I’m going to show use how XHR layer has implemented.

There were two questions which I was solving when I was encapsulating XHR logic

  • Reduce amount of useless code
  • Add caching layer and prevent useless traffic:

    • Less request to a server
    • Faster works on the client
      It’s pretty simple, right? Let’s take a look on xhr.helper.js:
      /**
       * jQuery Get method decorator (Cache + Fallback for an old path)
       * @param  {string} what    Filename without path and extension
       * @param  {function} success callback
       * @param  {function} fail    callback
       * @return {object}         Object with decorated method
       */
      var get = function(what, success, fail) {
      	var file = what + '.json';
      
      	// If it exists in the cache
      	if (window.localStorage[_storageKey + file] !== undefined &amp;&amp; !isCacheExpired()) {
      		return success(JSON.parse(window.localStorage[_storageKey + file]));
      	}
      
      	success = successDecorator(file, success);
      
      	$.get(_storageUrl + file, success).done().fail(function(error){
      		if (error.status == 200) {
      			return;
      		}
      		// Fallback
      		$.get(_shortHandUrl + file, success).done().fail(fail);
      	});
      }

It implements:

  1. Prepare a real filename
  2. Check this file in a cache

    1. If it’s existing in a cache - take a look is cache not expired
    2. Call the callback method with cache data without any requests if all is ok.
  3. If it doesn’t exist in a cache - let’s decorate a success method and call it

  4. Process request through the decorated method.
    What is decorator method doing? It’s a good questing. I have encapsulated caching functionality using something similar to decorator pattern (actually it’s not a true decorator, but It’s a good solution for this purposes):
    /** 
        * Decorator for success calback. Saves value to cache after success.
     * @param  {string} key             filename (also a key in cache)
     * @param  {function} successCallback Original success callback
     * @return {function}                 Decorated success callback
     */
    var successDecorator = function(key, successCallback) {
    	return function(response) {
    		if (window.localStorage[_storageKey + _timePerfix] == undefined) {
    			window.localStorage.setItem(_storageKey + _timePerfix, new Date().getTime() + 360000);
    		}
    		window.localStorage.setItem(_storageKey + key, JSON.stringify(response));
    		successCallback(response);
    	}
    }

You can see the expiring period setup for a cache and cache of the response itself.

Afterwards, it’s going to call the original callback method with a response.

It’s all from my side for a moment. Follow the project and wait for updates, I’m going to continue work with this service. Feel free to contribute it on GitHub:

https://github.com/ensaier/english-for-engineers