diff --git a/kfet/static/kfet/js/kfet.api.js b/kfet/static/kfet/js/kfet.api.js index e0bc2c35..a655786d 100644 --- a/kfet/static/kfet/js/kfet.api.js +++ b/kfet/static/kfet/js/kfet.api.js @@ -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 'article_category' - * @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 'article' - * @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.
* 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}.
+ * 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 = $('
'); + $container.attr('id', node.type+'-'+node.content.id); - //TODO: less dirty - var $container = $('
'); - 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 [Article, ArticleCategory] + * @default {'article': Article, + 'category': ArticleCategory} */ static get models() { - return [Article, ArticleCategory]; + return {'article': Article, + 'category': ArticleCategory}; } /** * Default url to get ArticlList data * @abstract * @default django-js-reverse('kfet.kpsul.articles_data') - * @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']();