Foreword
The purpose of this post is to popularize the Jiant web framework and search for people who would like to take part in its promotion, creating a community for further development and distribution of the framework. Jiant actively uses jQuery and naturally interacts with it, fulfilling its goal of organizing and simplifying web applications of any complexity.
Target audience - I think it will be useful to any web developer, but especially to those who came from the server - for example, from Java.
Link to github:
https://github.com/vecnas/jiantLink to todo-mvc:
https://github.com/vecnas/todo-mvcIntroduction
Jiant is a web application development framework that provides a set of tools that simplify and speed up the development and then support for JavaScript applications.
')
A jiant application is declared as a javascript variable and contains several sections. One of these sections is models. In this section, application data models are declared:
var app = { models: { user: {
For each model, its fields are described in minimal form, then Jiant automatically implements the model. Models are of two types - singleton (the only data object in the application) and the repository (collection of objects).
The advantages over all currently known frameworks are minimum of code, ease of completion in IDE, autodocumentation, ease of understanding, natural decomposition of the application into simplest parts.
Singleton model
The declaration includes a list of fields and several functions added by default, such as update, on, off:
models: { loggedUser: {
Or so:
app.models.loggedUser = {
Fields are all empty functions except a few predefined ones, such as update, on. The implementation is performed by Jiant at the moment when the following function is called, at this moment all models must be added to the application description file:
jiant.bindUi(app);
Modifying model properties
After binding, you can use the model, usually within the jiant.onUiBound method:
jiant.onUiBound(app, function($, app) { var loggedUser = app.models.loggedUser; loggedUser.firstName("Joy"); })
If there is data in the form of json, you can simply call update (the most frequently used script is getting data from the server):
var userData = { firstName: "John", lastName: "Smith", id: 1234 }; app.models.loggedUser.update(userData);
What is the convenience? Now we can access these data from anywhere, for example somewhere else, in code not associated with the first block:
var loggedUserId = app.models.loggedUser.id();
You can see that the setter and the getter have the same syntax, the model itself determines what is being called:
loggedUser.firstName()
Synthetic methods
Within the model, we can define methods that provide more complex functions than setter / getter:
loggedUser: {
You can return a link to another model, or simply report whether someone is logged in:
models: { env: { loggedUser: function() {return app.models.loggedUser} isLogged: function() {return !!app.models.loggedUser.id()} } }
No change notification is generated on synthetic methods (there are no on, off, asap methods).
Subscribe to changes
Each property of the model provides a .on (callback) method to subscribe to change this property:
jiant.onUiBound(app, function($, app) { app.models.loggedUser.id.on(function(loggedUser, id) { alert("logged user id changed: " + id); } })
The full callback syntax for the on method is:
function(modelObject, newValue, oldValue)
The off method is included. Also for the model object itself is available a subscription to any changes to its fields through the same on method:
app.models.loggedUser.on(function(loggedUser) {})
ASAP
There are frequent situations when it is necessary to execute some code when certain data is available, or immediately if they are already available. In this case, the asap (as soon as possible) method is used, which works only once - either immediately, or when the required value appears:
app.models.loggedUser.id.asap(function(loggedUser, id) { alert("Hello " + loggedUser.fullName()); })
Repository model
The main difference of the repository is that it contains many objects of this model, and not one. Accordingly, working with the repository is carried out as with a collection. The name of the repository is used since the idea was originally born on the basis of the Java Spring JpaRepository framework. At the same time, the syntax of the declaration is similar to the singleton, but the methods for working with the collection are added:
models: { listing: { updateAll: function(arr, removeMissing) {}, add: function(arr) {}, remove: function(obj) {}, all: function() {}, findById: function(val) {}, listByTp: function(val) {}, id: function(val) {}, tp: function(val) {}, price: function(val) {}, baths: function(val) {}, ui: function(val) {} } }
Repository modification
The add and remove methods do exactly what their name says. The only non-obvious method is updateAll — it compares by the id field of the array or object that is transferred to it and the current contents of the repository, after which it adds, deletes, updates the contents. If the object does not have an identifier, you can either declare the synthetic id () method, or pass the 3rd parameter to the updateAll method, a function that compares two objects and returns true / false.
The remove method can be used in any way, for example, if there is an object in this obj collection, then the following two lines do the same thing:
app.models.listing.remove(obj); obj.remove()
The update method in the case of a repository is applied to an individual object, and not to the repository as a whole:
var obj = app.models.listing.findById(365); obj.update(newJsonData);
Getting repository objects
The all method returns all repository objects and is always available. You can also define findByXXX, listByXXX search methods that search the repository by the field value (or several):
findByTp: function(val) {}
The list of fields if there are more than one - is divided by the word And. The implementation of these methods is carried out automatically. Suppose you need to search for another field - add an empty function and ready.
The difference between find and list is that find returns only one object (the first one that arrives, or null), and the list always returns an array (with a list of objects or an empty one).
Collection functions
The all and listBy methods always return arrays, but arrays are not simple, but enriched with basic model methods such as getters, update, remove. For example:
app.models.listing.listByTp("obsolete").remove()
Not only data
In the model, you can store not only data, but any objects, for example, in the example above there is a ui field - in it we will store a visual representation of the listing on the interface, a link to the jQuery object and we can access it at any time in the right place.
app.models.listing.findById(234).ui().addClass("active")
Examples
Here are some examples of using the above functions:
asap - google maps were used in the project, and it was necessary to put markers on them when they were initialized. In order not to shove all the code in one place, a gmap field was added to the env model and the code placing the markers started to work when it was set. Two small independent parts of the code instead of one mash turned out.
singleton - always used to work with the environment (the same example with google maps, analogies are easy to add) or information about the logged in user
repository - any lists of data downloaded from the server, displayed on the interface or used in program logic. For example - a list of states, with the ability to search by code and then display the full name on the interface:
models: { state: { updateAll: function(arr) {}, findByShort: function(val) {}, short: function(val) {}, full: function(val) {} } }
And then:
var fullStateName = app.models.state.findByShort(short).full()
Another example is notification and notification. Incoming notifications are simply dropped into the model. Separately, the existing independent code hangs over this model and shows notifications to the user, deleting those that the user has removed.
Even less code
Declaring
var f = function(val) {}
You can reduce the amount of code:
models: { state: { updateAll: function(arr) {}, findByShort: f, short: f, full: f } }
Do not do so, there will be no hint from the IDE that this is a function (until a plugin is developed).
It is possible to expand access to the models by sorting during data acquisition. Oddly enough in the top ten projects it is not needed. As a rule, the sorting is already done on the server, or is performed by additional functionality, its description is beyond the scope of this article
Bonuses
Documentation for free. Having declared the model, we immediately receive the documentation for the application.
Acceleration development. Write less.
Significant simplification of support. The application is divided into arbitrarily small parts. IDE auto-completion and search for “places of use” work, which is extremely rare for javascript code.
The main drawback - excessive simplicity - was such that at first it was not clear "how this can work, then it’s just an empty function." The rhetorical question is whether it is flawed.