Polish ModelForest class

This commit is contained in:
Ludovic Stephan 2017-03-15 22:10:56 -03:00
parent 0219d998ac
commit 1570d9f494

View file

@ -107,13 +107,6 @@ class ModelObject {
*/
static get default_data() { return {}; }
/**
* Verbose name so refer to this model
* @abstract
* @type {string}
*/
static get verbose_name() { return ""; }
/**
* Create new instance from data or default values.
* @param {Object} [data={}] - data to store in instance
@ -170,14 +163,24 @@ class ModelObject {
return formatter.render(this, $container, options);
}
/**
* Returns a string value for the model, to use in comparisons
* @see {@link Models.ModelObject.compare|ModelObject.compare}
*/
comparevalue() {
return this.id.toString();
}
/**
* Compare function between two instances of the model
* by serializing them using comparevalue().
* @abstract
* @param {a} Models.ModelObject
* @param {b} Models.ModelObject
* @see {@link Models.ModelObject.comparevalue|ModelObject.comparevalue}
*/
static compare(a, b) {
return a.id - b.id ;
return a.comparevalue().localeCompare(b.comparevalue());
}
}
@ -493,13 +496,6 @@ class ArticleCategory extends ModelObject {
return {'id': 0, 'name': ''};
}
/**
* Verbose name for ArticleCategory model.
* @default <tt>'article_category'</tt>
* @see {@link Models.ModelObject.verbose_name[ModelObject.verbose_name}
*/
static get verbose_name() { return 'category'; }
/**
* @default {@link Formatters.ArticleCategoryFormatter}
*/
@ -511,8 +507,8 @@ class ArticleCategory extends ModelObject {
* Comparison function between ArticleCategory model instances.
* @see {@link Models.ModelObject.compare|ModelObject.compare}
*/
static compare(a, b) {
return a.name.localeCompare(b.name);
comparevalue() {
return this.name ;
}
}
@ -544,13 +540,6 @@ class Article extends ModelObject {
};
}
/**
* Verbose name for Article model
* @default <tt>'article'</tt>
* @see {@link Models.ModelObject.verbose_name|ModelObject.verbose_name}
*/
static get verbose_name() { return 'article'; }
/**
* @default {@link Formatters.ArticleFormatter}
*/
@ -562,8 +551,8 @@ class Article extends ModelObject {
* Comparison function between Article model instances.
* @see {@link Models.ModelObject.compare|ModelObject.compare}
*/
static compare(a, b) {
return a.name.localeCompare(b.name);
comparevalue() {
return a.name;
}
// Take care of 'price' type
@ -572,6 +561,11 @@ class Article extends ModelObject {
set price(v) { this._price = floatCheck(v); }
}
/**
* Node for ModelForest object
* @memberof Models
*/
class TreeNode {
constructor(type, content) {
@ -584,15 +578,15 @@ class TreeNode {
/**
* Simple {@link Models.ModelObject} list.
* Simple {@link Models.ModelObject} forest.
* @memberof Models
*/
class ModelTree {
class ModelForest {
/**
* Nested structure of the list
* Dictionary associating types to classes
* @abstract
* @type {Models.ModelObject[]}
* @type {Object}
*/
static get models() { return {}; }
@ -602,7 +596,6 @@ class ModelTree {
* @param {Object[]} [datalist=[]]
*/
constructor(datalist) {
this._root = new TreeNode({}) ;
this.from(datalist || []);
}
@ -611,10 +604,10 @@ class ModelTree {
* it does not exist yet.<br>
* If direction >= 0, parent objects are created recursively.
* If direction <= 0, child objects are created recursively.
* @param {number} depth depth on the nested structure of the list
* @param {Object} data
* @param {number} direction
*/
get_or_create(data, direction=0) {
get_or_create(data, direction) {
var model = this.constructor.models[data.type];
var existing = this.find(data.type, data.content.id);
@ -622,13 +615,17 @@ class ModelTree {
return existing;
}
var content = new this.constructor.models[data.type]();
var content = new this.constructor.models[data.type](data.content);
var node = new TreeNode(data.type, content);
if (direction <= 0) {
var parent = data.parent ? this.get_or_create(data.parent, -1) : this._root;
if (data.parent) {
var parent = this.get_or_create(data.parent, -1);
node.parent = parent;
parent.children.push(node);
} else {
this.roots.push(node);
}
}
if (direction >= 0) {
@ -643,65 +640,64 @@ class ModelTree {
}
/**
* Resets then populates the instance with the given data, starting from
* the lowest level Models.ModelObject in {@link Models.ModelList#models|models}.<br>
* Resets then populates the instance with the given data.
* @param {Object[]} datalist
*/
from(datalist) {
for (let key of this.constructor.names) {
this.data[key] = [];
}
this.roots = [];
for (let data of datalist) {
this.get_or_create(data, 0);
}
}
/**
* Removes all Models.ModelObject from the list.
* Removes all Models.TreeNode from the tree.
*/
clear() {
this.from([]);
}
/**
* Renders an element (and all its offspring) and returns the
* Renders a node (and all its offspring) and returns the
* corresponding jQuery object.
* @param {Models.ModelObject} elt
* @param {Models.TreeNode} node
* @param {Object} templates Templates to render each model
* @param {Object} [options] Options for element render method
*/
render_element(elt, templates, options) {
var name = elt.constructor.verbose_name;
var depth = this.constructor.names.indexOf(name);
var template = templates[name];
render_element(node, templates, options) {
var template = templates[node.type];
var options = options || {} ;
if (depth == -1) {
return $();
} else if (depth == 0) {
var $rendered = elt.display($(template), options);
$rendered.attr('data-'+name+'-id', elt.id);
return $rendered;
} else {
var child_model = this.constructor.models[depth-1];
var children = this.data[child_model.verbose_name]
.filter(v => v[name].id == elt.id) ;
children.sort(child_model.compare);
//TODO: less dirty
var $container = $('<div></div>');
var $elt = elt.display($(template), options);
$elt.attr('data-'+name+'-id', elt.id);
$container.append($elt);
$container.attr('id', node.type+'-'+node.content.id);
for (let child of children) {
$container.append(this.render_element(child, templates, options));
var $rendered = node.content.display($(template), options);
$container.append($rendered);
//TODO: better sorting control
node.children.sort(ModelObject.compare);
for (let child of node.children) {
var $child = this.render_element(child, templates, options);
$container.append($child);
}
return $container.html();
return $container;
}
add_to_container($container, node, templates, options) {
var existing = node.parent ;
var first_missing = node;
while (existing && !($container.find('#'+existing.type+'-'+existing.id))) {
first_missing = existing ;
existing = existing.parent;
}
var $to_insert = render_element(first_missing, templates, options);
var $insert_in = existing ? $container.find('#'+existing.type+'-'+existing.id)
: $container ;
$insert_in.prepend($to_insert);
}
/**
@ -711,9 +707,7 @@ class ModelTree {
* @param {Object} [options] Options for element render method
*/
display($container, templates, options) {
var root_model = this.constructor.models[this.constructor.models.length-1];
var roots = this.data[root_model.verbose_name];
roots.sort(root_model.compare);
this.roots.sort(ModelObject.compare);
for (let root of roots) {
$container.append(this.render_element(root, templates, options));
@ -728,28 +722,35 @@ class ModelTree {
* @param {Object} props Properties to match
*/
find(type, id) {
(function recurse(node) {
function recurse(node) {
if (node.type === type && node.data.id === id)
return node ;
for (let child of node.children) {
var result = recurse(child) ;
if (result)
if (result = recurse(node))
return result;
}
return null;
}
for (let root of this.roots) {
if (result = recurse(root))
return result;
}
return null ;
})(this._root;)
}
}
/**
* Describes a model list that can be filled through API.
* @extends Models.ModelList
* @extends Models.ModelForest
* @memberof Models
*/
class APIModelList extends ModelList {
class APIModelForest extends ModelForest {
/**
* Request url to fill the model.
@ -760,7 +761,7 @@ class APIModelList extends ModelList {
/**
* Fills the instance with distant data. It sends a GET HTTP request to
* {@link Models.APIModelList#url_model}.
* {@link Models.APIModelForest#url_model}.
* @param {object} [api_options] Additional data appended to the request.
* @param {jQueryAjaxSuccess} [on_success] A function to be called if the request succeeds.
* @param {jQueryAjaxError} [on_error] A function to be called if the request fails.
@ -786,25 +787,27 @@ class APIModelList extends ModelList {
/**
* ArticleList model. Can be accessed through API.
* @extends Models.APIModelList
* @extends Models.APIModelForest
* @memberof Models
*/
class ArticleList extends APIModelList {
class ArticleList extends APIModelForest {
/**
* Default structure for ArticleList instances
* @abstract
* @default <tt>[Article, ArticleCategory]</tt>
* @default <tt>{'article': Article,
'category': ArticleCategory}</tt>
*/
static get models() {
return [Article, ArticleCategory];
return {'article': Article,
'category': ArticleCategory};
}
/**
* Default url to get ArticlList data
* @abstract
* @default <tt>django-js-reverse('kfet.kpsul.articles_data')</tt>
* @see {@link Models.APIModelList.url_model|APIModelList.url_model}
* @see {@link Models.APIModelForest.url_model|APIModelList.url_model}
*/
static get url_model() {
return Urls['kfet.kpsul.articles_data']();