forked from DGNum/gestioCOF
Polish ModelForest class
This commit is contained in:
parent
0219d998ac
commit
1570d9f494
1 changed files with 90 additions and 87 deletions
|
@ -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;
|
||||
node.parent = parent;
|
||||
parent.children.push(node);
|
||||
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];
|
||||
var options = options || {} ;
|
||||
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);
|
||||
var $container = $('<div></div>');
|
||||
$container.attr('id', node.type+'-'+node.content.id);
|
||||
|
||||
//TODO: less dirty
|
||||
var $container = $('<div></div>');
|
||||
var $elt = elt.display($(template), options);
|
||||
$elt.attr('data-'+name+'-id', elt.id);
|
||||
$container.append($elt);
|
||||
var $rendered = node.content.display($(template), options);
|
||||
$container.append($rendered);
|
||||
|
||||
for (let child of children) {
|
||||
$container.append(this.render_element(child, templates, options));
|
||||
}
|
||||
//TODO: better sorting control
|
||||
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
|
||||
*/
|
||||
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;
|
||||
})(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.
|
||||
* @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']();
|
||||
|
|
Loading…
Reference in a new issue