Working with data models in javascript

Hello, Habralyudi.

Small by little, from my experience and our projects, a small library was born for working with models in javascript. It is called Model.js .

I will tell you briefly about this library and in this post I request feedback from those who, by creating complex javascript applications, already solve this problem in a certain way without frameworks. The opinion of those who are just looking for the right tool for their needs is also interesting: what tool do you need and how much does Model.js suit you ?

What for?


To simplify working with the data layer, without using frameworks. The current first version of the library - v0.1 weighing about 12K - is designed to help primarily with data validation and event management, in particular with events when data changes.

What is so special?


Sugar. An ordinary pleasing to the eye syntactic sugar.

The model described is nowhere simpler.
var Note = new Model('Note', function () {
  this.attr('id!', 'number');
  this.attr('title', 'string', 'nonempty');
  this.attr('text', 'string');
});

Then we create entities, as usual objects.
var note = new Note({ id: 123, title: "Hello World" });


Entity Public Properties


Getters of attribute values. In our example, this is:
note.data.id
note.data.title
note.data.text

Setters In addition to changing values, the change event is also fired .
note.data.id =
note.data.title =
note.data.text =
note.data = {…}

The getter will note.get(attrName[, attrName, …])return an object with the values ​​of the requested attributes.

note.get()will return a copy of all data.

note.data()- the same as note.get().

Setters note.set(attrName, value)and note.set({…})not "shoot" change event.

note.hasChangedsays whether the entity data has changed since it was last saved.

note.isNewsays whether the entity data is saved at least once.

note.isPersistedsays whether recent changes are saved.

note.bind(eventName, handler)"Hangs" the handler. By the way, you can hang the handler of any event not only on a separate entity, but on all entities of the class ( Note.bind).

note.isValidsays whether current model data is valid.

note.errorsin fact, returns data errors, if any.

note.revert()rolls back unsaved changes and “shoots” the event revert.

It's all.

You may ask, where are the conservation methods and so on? - I will answer: the use of the library implies that the developer must implement these methods independently as required by the logic of his application.

It should be noted that with saving there is one nuance: if the data was saved successfully, you need to inform the entity about it using a private method note._persist(), which also “shoots” the event persist.

An example explanation is good. Let's say our application runs in a browser environment and the method note.save()should save data using ajax.

Note.prototype.save = function () {
  var note = this;
  return $.ajax({
    type: 'PUT',
    url: '/notes/'+note.data.id,
    data: note.data(),
    dataType: 'json'
  }).done(function (json) {
    note._persist();
  });
}
note.bind('persist', function () {
  $('h1', 'div#note'+this.data.id).html(this.data.title);
});
note.data = { id: 123, title: "abc", text: "" }
if (note.hasChanged && note.isValid) { // оппа Django-style
  note.save();
}


Data checking


When creating a model, each of its attributes is necessarily described.
this.attr('title', 'string', 'nonempty')
First, indicate the attribute name, then its validators. Validators, when the time comes, will check the value of the attribute in the declared order.

In general, validators are ordinary functions that take values ​​and return errors when these values ​​are invalid.
function validateMinLength(value, minLength) {
  if (value.length < minLength) return 'tooshort';
}

If you need to use the validator several times, it is reasonable to register it in order to connect by name.
Model.registerValidator('array', function (value) {
  if (Object.prototype.toString.call(v) === '[object Array]') return 'wrongtype';
});
Model.registerValidator('minLength', validateMinLength);

Model.js has multiple base (already recorded) validators: number, string, boolean, nonnull, nonemptyand in. They, just like in our example, can be connected to attributes by name.

To pass the parameter to the validator when describing the attribute, you need to write it, as in the example below, in the form of an array:
this.attr('title', 'string', 'nonempty', [ 'minLength', 6 ]);

Validators can be connected with the usual grandfather method, without registering them.
this.attr('title', 'string', 'nonempty', [ 'minLength', 6 ], function (title) {
  if (title[0] !== title[0].toUpperCase()) return 'downcase';
});

All this is necessary so that he note.isValidcould say trueor false, and he note.errorscould return the object with errors, if any.
note.data = { id: 'abc', title: '', text: 3 }; // полностью неверные данные
note.isValid // false
note.errors // { id: 'wrongtype', title: 'empty', text: 'wrongtype' }


Events


There are four events: initialize, change, persistand revert. As mentioned above, you can hang the handler on a specific entity ( note.bind), or you can also hang it on a class ( Note.bind), so that the handler will become common to all entities.

initialize"Fired" once when creating the entity: var note = new Note({…}). So it initializeonly makes sense to hang up a handler on a class.

changetriggered every time an attribute value changes. There are, however, setters who do not pull change (this was described above).

perist- when the entity receives a signal that the changes have been successfully saved.

revert - when the application logic ordered to roll back yet unsaved changes (using the method note.revert()).

var ALLOWED_LANGUAGES = ['en', 'ua', 'ru'];
var Note = new Model('Note', function () {
  this.attr('id!', 'number');
  this.attr('title', 'string', 'nonempty');
  this.attr('lang', 'string', 'nonempty', [ 'in', ALLOWED_LANGUAGES]);
  this.attr('text', 'string');
});
Note.bind('initialize', function () {
  if (!this.data.lang) {
    this.set('lang', 'en'); // change не выстрелит — специальный такой сеттер!
  }
});
note.bind('change', function (changes) {
  if (changes.title) $('h1', 'div#note'+this.data.id).html(changes.title);
});


In conclusion


Now Model.js is a pure state of the art , it is an intermediate, but confidently working result. If you are interested in the library, if you have a desire to try to use it, I will be happy to answer your questions. More information on what works can be found in the documentation on the github and in the tests.

In the meantime, wish the baby a good journey, because in order to become an “adult” library, she still needs to do a lot of work.

Also popular now: