〇.js
Basics
- Learning by experience, which I encourage anyone to do
- Having a framework doing exactly what I need, and I know how it's being done
- finally, having a framework's design as I believe it should
I believe in clear separation between code (Javascript), structure (HTML) and style (CSS). Having this distinction means the code can easily be replaced with having minimum change in the structure and the style, and vice versa. This means the code shouldn't dictate which tag is being used and all relevant style alternation are via classes that have a "js-" prefix.
I tried to be compatible with AirBNB code styling guide as I agree most notes will result with better, more readable code that is less likely to contain bugs.
When possible, methods will return the called object in order to allow chaining, for example: 〇.CSS.add(〇.ELM.myButton,'green').add(〇.ELM.button2,'red')
Although the official name is 〇 (It's zero, I pronounce it as "O"), which doesn't normally appear on keyboards, so to make life easier, you may want to use the unofficial name "O" (as in the 15th character in the English alphabet), for example O.ELM.button.onclick();
.
Download
- Uncompressed - (19KB)
- Compressed - (7KB)
Unit Tests
EVT
EVT can be used to listen and to dispatch events. Event-dispatching is a great way to tell other components that something happened.-
subscribe(sEventName,fHandler)
〇.EVT.subscribe("anEvent",foo)
Whenever anEvent is dispatched, the function foo will be called with event as an input. -
unsubscribe(sEventName,fHandler)
〇.EVT.unsubscribe("anEvent",foo)
Stop calling foo when anEvent is dispatched. -
dispatch(sEventName)
〇.EVT.dispatch("anEvent")
Calls an event. -
getDispatcher(sEventName[, fHandler])
dButton.onclick = 〇.EVT.getDispatcher("anEvent")
Returns a function that when triggered, will dispatch an event. if fHandler is provided, it will be subscribed to the event.
DOM
-
create(sTag,oAttribtues,sInnerText)
〇.DOM.create('a',{href:'#top'},'back to top')
Return a DOM element with the provided attributes. -
parse(sHTML)
〇.DOM.parse('<a href="#top">back to top</a>')
Converts the provided string to a DOM element. -
insertBefore(dElement,dNewElement)
〇.DOM.insertBefore(〇.ELM.homeLink,contactLink)
Inserts a new DOM element before a given as a sibling. -
insertAfter(dElement,dNewElement)
〇.DOM.insertAfter(〇.ELM.homeLink,contactLink)
Inserts a new DOM element after a given as a sibling. -
prepend(dElement,dNewElement)
〇.DOM.prepend(〇.ELM.menu,contactLink)
Inserts a new DOM element to another DOM element as its first child. -
append(dElement,dNewElement)
〇.DOM.append(〇.ELM.menu,contactLink)
Inserts a new DOM element to another DOM element as its last child. -
remove(dElement)
〇.DOM.remove(〇.ELM.homeLink)
Removes a DOM element.
ELM
It also provides a function to query several DOM elements and perform bulk actions on them.
-
refresh()
〇.ELM.refresh
Updates the DOM elements list. -
observe(isEnabled)
〇.ELM.observe(true)
Observe is an event-listening to changes in the DOM which automatically refreshes the 〇.ELM list. As it might influence performance, it is possible to disable and enable it. -
per()
var elements = 〇.ELM.per('.button');
Per returns a list of elements that corresponds to a provided query. Additionally, this returned list has the following methods:-
each(fPerItem)
〇.ELM.per('.user').each(function (dElement) {...});
Iterates the DOM elements using a forEach function. -
remove()
〇.ELM.per('.user').remove();
Removes all the DOM elements in the list from the page. -
html(sNewInnerHTML)
〇.ELM.per('.js-version').html(〇.VER.framework);
updates all DOM elements' innerHTML. -
useAsTemplate()
〇.ELM.per('.user').useAsTemplate();
Subscribes the DOM elements as templates. They do not have to be classes as 'js-template' but they must have an 'ID'. -
css.add('sClassName')/remove('sClassName')/toggle('sClassName')
〇.ELM.per('.user').css.add('hidden');
manipulates the DOM element's CSS classes.
-
CSS
Although it is possible to set style by tag or by id, as the ID should be used by javascript and tags are, obviously used by the HTML. Classes, which suppose to be in the style 'domain', have the 'js-' prefixed automatically to differentiate them from other classes.
-
has(dElement,sClassName)
if (〇.CSS.has(〇.ELM.button,'enabled')) {}
Returns true when a given DOM element has a class. -
add(dElement,sClassName)
〇.CSS.add(〇.ELM.button,'enabled');
Adds the class to the DOM element -
remove(dElement,sClassName)
〇.CSS.remove(〇.ELM.button,'enabled');
Removes a class from the DOM element -
toggle(dElement,sClassName)
〇.CSS.toggle(〇.ELM.button,'enabled');
Toggle a class (i.e. if element already has it, it be removed, otherwise it will be added) -
isJsPrefix(isJsPrefixAdded)
〇.CSS.isJsPrefix(true);
Sets whether the prefix js- should be added to any className handled by 〇.CSS. Default is true. If no value was provided, the function will only return the current state.
AJAX
Parameters used
sURL
- the request's target.oData
- data being sent as a json object. oData is relevant only for POST and PUT requests.oOptions
- additional options may include- credentials (boolean, default is false)
- type (string, default is 'json'
fCallback
- the function to be called once the request is completed.
Methods
-
get(sURL[, oOptions], fCallback)
〇.AJAX.get('/catalog', 〇.getDispatcher('catalogLoaded') );
Uses AJAX request to retrieve the content of sURL and sends it to fCallback -
post(sURL,oData[, oOptions], fCallback)
〇.AJAX.post('/catalog/item/13953',{price:485.3}, 〇.getDispatcher('itemUpdated'));
Sends oData to sURL via POST and its response to fCallback -
put(sURL,oData[, oOptions], fCallback)
〇.AJAX.put('/catalog/new',{name:'Red Pen',price:485.3}, 〇.getDispatcher('itemAdded'));
Sends oData to sURL via PUT and its response to fCallback. PUT is used to send a complete object, while POST is used to send partial information, usually for update purposes. -
delete(sURL[, oOptions], fCallback)
〇.AJAX.get('/catalog/item/13953', 〇.getDispatcher('itemRemoved') );
Sends DELETE command to sURL and its response to fCallback -
setDefaults
〇.AJAX.setDefaults(oSettings);
Sets AJAX defaults parameters, such as:- credentials (boolean, default is false)
- type (string, default is 'json'
- url (string, default is 'false
- callback (string, default is 'false
TPL
- have ID
- have the class 'js-template'
How to write a template
-
Simple example: Hello World!
Rendering the templateHello {{name}}
with the data{name:'World'}
will result the stringHello World
. -
When data is an object
Data may contain an object, for example{user:{name:'World'}}
, in which case the template should specifyHello {{user.name}}
. -
When data is a function
Data may contain a function, for example{name: function () { return 'world'}}
, but this will be automatically identified, so the template will remainHello {{name}}
. -
Conditionals
Conditionals allow hiding some of the template according to a variable. for example:Hello {{?showName}}{{name}}{{/showName}}
where showName can either be a boolean or a function returning a boolean.Hello {{?!showName}}no name provided{{/showName}}
will show 'no name provided' ONLY if show equals false. -
Shortcut to Object's sub-variables in the data
When data contain an object, it is possible to easily access Object's properties using '@', for example:{{@user}}{{fName}} {{lName}}{{/user}}
is equal to{{user.fName}} {{user.lName}}
. -
Iterating arrays in the data
It is possible to iterate arrays in the data using the loop {{item@group}}, for example:{{user@users}} {{name}} {{/user@users}}
. Note this means the data should look like{users:{user:[{name:'John'},{name:'David'}]}}
. You may define a counter by appending to the prefix tag ':' and the name of the new variable, for example:{{item@group:_idx}} {{_idx}} {{/item@group}}
. Please note the counter doesn't appear at the closing tag.However, if your data is a straightforward array, like this -{users:[{name:'John'},{name:'David'}]}
, the loop will run on its original scope, and the iterator variable will be in the variable provided (in our case - user). In such case, the template would be{{user@users:_idx}} {{_idx}}. {{user.name}} {{/user@users}}
. -
Sub-templates
A template may refer to another template using the syntax data:template, for example{{user:userTemplate}}
. In our example, the user will be sent as data for the userTemplate. -
using 'this'
Sometime you might want to retrieve the entire data. This can be done using '.'. For example, when our data contains an array of strings -{fruits:{fruit:['orange','apple','melon']}}
you may print the list using the iteration command{{fruit@fruits}} {{.}}, {{/fruit@fruits}}
-
Updating delimiters
If you template contains the string '{{' (or '}}') you might want to temporarily replace the delimiters. You can do so with the '{{'startTag','endTag'}} ... '{{/'startTag','endTag'}}, for example{{'<?','?>'}}Hello <?name?>{{/'<?','?>'}}
-
Translation
It is advised that strings shouldn't be hard-coded in your structure, rather the variables that can be translates. This can easily be done using '#string.code', for example{{#label.hello}}}
. The string code is comprised with two elements, separated by a '.': context and default-value. The context is meant to help you know where and how the string is used; default-value is the string that appear in case the current locale doesn't contain the string-code. It is worth to emphasize that the translation files include the full string code (in our example label.hello). The default value begins after the first '.' and may contain any character (including spaces). -
Order of rendering
Let's look at this sample-data{{main:{isVisible:true,sub:{isVisible:false}}}}
and the template{{@sub}}{{?isVisible}}Hello!{{/isVisible}}{{/sub}}
the variable that will be checked is sub.isVisible (and not main.isVisible). This means the order or parsing the template is crucial. It follows this logic -- Replace delimiters where needed
- Handle with sub-variables
- Handle with iterations
- Filter out elements according to conditionals
- Replace variables
- Translate string
Methods
-
load(sURL)
〇.TPL.load('myTemplates.html');
Load read an text/html online document and then reads and subscribes all the templates it contains. Once loaded, a 'TPL.templatesLoaded' event will be dispatched, where its detail is the current list of templates. -
use(domElement)
〇.TPL.load(〇.ELM.myNewTemplate);
Use read a single element and subscribe its ID to the template list for later use. -
loadLanguage(sURL)
〇.TPL.loadLanguage('/i18n/en-us.json');
LoadLanguage read an application/json online document and then append its content to the strings object. The expected json content is{'localeName':{'str.code1':'translation1','str.code2':'translation2'}}
. for example -{'en-us':{'label.hello':'hello}}
-
appendToLanguage(sLocale,oDictionary)
〇.TPL.appendToLanguage('en-us',{'label.hello':'Hello','button.cancel':'Cancel'});
AppendToLanguage appends a dictionary (set of key-values) to a given locale. -
translate(sStringCode)
〇.TPL.translate('button.signup');
Translate returns the translation from the current locale's dictionary. If the string-code doesn't not appear in the dictionary, the default-value will be returned. -
getLocale()
〇.TPL.getLocale();
GetLocale returns the current locale. -
list()
〇.TPL.list();
List return an array of all subscribed templates. -
render(oData)
〇.TPL.render({'linkTemplate':{label:'{{#link.Sign Up}}',href='/signup'}});
Render returns the HTML output of processing a provided template with given data. It receives a single data object, who single key is the name of the template to be used and its value is the data object that will be used per that template.
VER
-
framework
var version = 〇.VER.framework;
Return the current version of the framework
Final words
Code,as all Intellectual Property, should be free. Therefore this code is free under MIT License.
If you have any comments, questions and answers, don't hesitate to contact me via GitHub.