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