[ADD] document documentation (esp. JS APIDoc via JsDoc, which is actually a fairly complex endeavour)
bzr revid: xmo@openerp.com-20110327120515-wdcxc19j4exuu2zu
This commit is contained in:
parent
ab43c4ee48
commit
bdddf2269d
|
@ -65,6 +65,10 @@
|
|||
// OpenERP initialisation and black magic about the pool
|
||||
//---------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @name openerp
|
||||
* @namespace
|
||||
*/
|
||||
(function() {
|
||||
if (this.openerp)
|
||||
return;
|
||||
|
@ -106,6 +110,7 @@
|
|||
// OpenERP initialisation and black magic about the pool
|
||||
//---------------------------------------------------------
|
||||
|
||||
/** @namespace */
|
||||
openerp.base = function(instance) {
|
||||
openerp.base$chrome(instance);
|
||||
openerp.base$views(instance);
|
||||
|
|
|
@ -5,17 +5,6 @@
|
|||
openerp.base$chrome = function(openerp) {
|
||||
|
||||
openerp.base.callback = function(obj, method) {
|
||||
// openerp.base.callback( obj, methods, [arg1, arg2, ... ] )
|
||||
//
|
||||
// The callback object holds a chain that can be altered:
|
||||
// callback.add( handler , [arg1, arg2, ... ] )
|
||||
// callback.add( {
|
||||
// callback: function
|
||||
// self: object or null
|
||||
// args: array
|
||||
// position: "first" or "last"
|
||||
// unique: boolean
|
||||
// })
|
||||
var callback = function() {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var r;
|
||||
|
@ -64,11 +53,13 @@ openerp.base.callback = function(obj, method) {
|
|||
});
|
||||
};
|
||||
|
||||
openerp.base.BasicController = Class.extend({
|
||||
openerp.base.BasicController = Class.extend(
|
||||
/** @lends openerp.base.BasicController# */{
|
||||
/**
|
||||
* Controller contructor
|
||||
* rpc operations, event binding and callback calling should be done in
|
||||
* start() instead of init so that event can be hooked in between.
|
||||
*
|
||||
* @constructs
|
||||
*/
|
||||
init: function(element_id) {
|
||||
this.element_id = element_id;
|
||||
|
@ -117,7 +108,15 @@ openerp.base.Console = openerp.base.BasicController.extend({
|
|||
}
|
||||
});
|
||||
|
||||
openerp.base.Session = openerp.base.BasicController.extend({
|
||||
openerp.base.Session = openerp.base.BasicController.extend(
|
||||
/** @lends openerp.base.Session# */{
|
||||
/**
|
||||
* @constructs
|
||||
* @extends openerp.base.BasicController
|
||||
* @param element_id
|
||||
* @param server
|
||||
* @param port
|
||||
*/
|
||||
init: function(element_id, server, port) {
|
||||
this._super(element_id);
|
||||
this.server = (server == undefined) ? location.hostname : server;
|
||||
|
@ -259,6 +258,7 @@ openerp.base.Session = openerp.base.BasicController.extend({
|
|||
/**
|
||||
* Fetches a cookie stored by an openerp session
|
||||
*
|
||||
* @private
|
||||
* @param name the cookie's name
|
||||
*/
|
||||
get_cookie: function (name) {
|
||||
|
@ -273,8 +273,9 @@ openerp.base.Session = openerp.base.BasicController.extend({
|
|||
return null;
|
||||
},
|
||||
/**
|
||||
* Create a new secure cookie with the provided name and value
|
||||
* Create a new cookie with the provided name and value
|
||||
*
|
||||
* @private
|
||||
* @param name the cookie's name
|
||||
* @param value the cookie's value
|
||||
* @param ttl the cookie's time to live, 1 year by default, set to -1 to delete
|
||||
|
@ -328,7 +329,12 @@ openerp.base.Database = openerp.base.BasicController.extend({
|
|||
// Non Session Controller to manage databases
|
||||
});
|
||||
|
||||
openerp.base.Controller = openerp.base.BasicController.extend({
|
||||
openerp.base.Controller = openerp.base.BasicController.extend(
|
||||
/** @lends openerp.base.Controller# */{
|
||||
/**
|
||||
* @constructs
|
||||
* @extends openerp.base.BasicController
|
||||
*/
|
||||
init: function(session, element_id) {
|
||||
this._super(element_id);
|
||||
this.session = session;
|
||||
|
|
|
@ -115,12 +115,14 @@ openerp.base.ViewManager = openerp.base.Controller.extend({
|
|||
},
|
||||
on_edit: function() {
|
||||
},
|
||||
do_search: function (domains, contexts) {
|
||||
do_search: function (domains, contexts, group_contexts) {
|
||||
var self = this;
|
||||
this.rpc('/base/session/eval_domain_and_context', {
|
||||
domains: domains,
|
||||
contexts: contexts
|
||||
contexts: contexts,
|
||||
groupby: group_contexts
|
||||
}, function (results) {
|
||||
// TODO: group by
|
||||
self.dataset.set({
|
||||
context: results.context,
|
||||
domain: results.domain
|
||||
|
@ -137,11 +139,19 @@ openerp.base.ViewManagerRoot = openerp.base.Controller.extend({
|
|||
openerp.base.ViewManagerUsedAsAMany2One = openerp.base.Controller.extend({
|
||||
});
|
||||
|
||||
/**
|
||||
openerp.base.DataSet = openerp.base.Controller.extend(
|
||||
/** @lends openerp.base.DataSet# */{
|
||||
|
||||
/**
|
||||
* Management interface between views and the collection of selected OpenERP
|
||||
* records (represents the view's state?)
|
||||
*
|
||||
* @constructs
|
||||
* @extends openerp.base.Controller
|
||||
*
|
||||
* @param {openerp.base.Session} session current OpenERP session
|
||||
* @param {String} model the OpenERP model this dataset will manage
|
||||
*/
|
||||
openerp.base.DataSet = openerp.base.Controller.extend({
|
||||
init: function(session, model) {
|
||||
this._super(session);
|
||||
this.model = model;
|
||||
|
@ -205,10 +215,9 @@ openerp.base.DataSet = openerp.base.Controller.extend({
|
|||
return this;
|
||||
},
|
||||
/**
|
||||
* @event
|
||||
*
|
||||
* Fires after the DataSet fetched the records matching its internal ids selection
|
||||
*
|
||||
* @event
|
||||
* @param {Array} records An array of the DataRecord fetched
|
||||
* @param event The on_fetch event object
|
||||
* @param {Number} event.offset the offset with which the original DataSet#fetch call was performed
|
||||
|
@ -239,10 +248,9 @@ openerp.base.DataSet = openerp.base.Controller.extend({
|
|||
return this;
|
||||
},
|
||||
/**
|
||||
* @event
|
||||
*
|
||||
* Fires after the DataSet fetched the records matching its internal active ids selection
|
||||
*
|
||||
* @event
|
||||
* @param {Array} records An array of the DataRecord fetched
|
||||
*/
|
||||
on_active_ids: function (records) { },
|
||||
|
@ -268,6 +276,7 @@ openerp.base.DataSet = openerp.base.Controller.extend({
|
|||
/**
|
||||
* Fires after the DataSet fetched the record matching the current active record
|
||||
*
|
||||
* @event
|
||||
* @param record the record matching the provided id, or null if there is no record for this id
|
||||
*/
|
||||
on_active_id: function (record) {
|
||||
|
@ -391,7 +400,25 @@ openerp.base.DataRecord = openerp.base.Controller.extend({
|
|||
}
|
||||
});
|
||||
|
||||
openerp.base.SearchView = openerp.base.Controller.extend({
|
||||
openerp.base.SearchView = openerp.base.Controller.extend(
|
||||
/** @lends openerp.base.SearchView# */{
|
||||
/**
|
||||
* Manager for the Search view type.
|
||||
*
|
||||
* Handles laying out and rendering the various search widgets, as well
|
||||
* as collecting contexts and domain and broadcasting them to whoever
|
||||
* registered itself on the :js:func:`~openep.base.SearchView.on_search`
|
||||
* event.
|
||||
*
|
||||
* @constructs
|
||||
* @extends openerp.base.Controller
|
||||
*
|
||||
* @param {openerp.base.Session} session the current OpenERP session
|
||||
* @param {String} element_id the root element where the search view should render itself
|
||||
* @param {openerp.base.DataSet} dataset the dataset with which the search view will work
|
||||
* @param {Number} view_id the id of this view object
|
||||
* @param {Object} defaults default values to set on the various fields of the view
|
||||
*/
|
||||
init: function(session, element_id, dataset, view_id, defaults) {
|
||||
this._super(session, element_id);
|
||||
this.dataset = dataset;
|
||||
|
@ -539,7 +566,13 @@ openerp.base.SearchView = openerp.base.Controller.extend({
|
|||
map(function (input) { return input.get_context(); }).
|
||||
compact().
|
||||
value();
|
||||
this.on_search(domains, contexts);
|
||||
// TODO: group_by on field contexts?
|
||||
var group_contexts = _(this.enabled_filters).
|
||||
chain().
|
||||
map(function (filter) { return filter.get_context(); }).
|
||||
compact().
|
||||
value();
|
||||
this.on_search(domains, contexts, group_contexts);
|
||||
},
|
||||
/**
|
||||
* Event hook for searches: triggers after the SearchView has collected
|
||||
|
@ -551,8 +584,9 @@ openerp.base.SearchView = openerp.base.Controller.extend({
|
|||
*
|
||||
* @param {Array} domains an array of string or literal domains
|
||||
* @param {Array} contexts an array of string or literal contexts
|
||||
* @param {Array} group_contexts an ordered array of contexts which may need to be used to resolve grouping
|
||||
*/
|
||||
on_search: function (domains, contexts) { },
|
||||
on_search: function (domains, contexts, group_contexts) { },
|
||||
do_clear: function (e) {
|
||||
if (e && e.preventDefault) { e.preventDefault(); }
|
||||
this.on_clear();
|
||||
|
@ -583,8 +617,19 @@ openerp.base.SearchView = openerp.base.Controller.extend({
|
|||
}
|
||||
});
|
||||
|
||||
/** @namespace */
|
||||
openerp.base.search = {};
|
||||
openerp.base.search.Invalid = Class.extend({
|
||||
openerp.base.search.Invalid = Class.extend(
|
||||
/** @lends openerp.base.search.Invalid# */{
|
||||
/**
|
||||
* Exception thrown by search widgets when they hold invalid values,
|
||||
* which they can not return when asked.
|
||||
*
|
||||
* @constructs
|
||||
* @param field the name of the field holding an invalid value
|
||||
* @param value the invalid value
|
||||
* @param message validation failure message
|
||||
*/
|
||||
init: function (field, value, message) {
|
||||
this.field = field;
|
||||
this.value = value;
|
||||
|
@ -595,8 +640,17 @@ openerp.base.search.Invalid = Class.extend({
|
|||
': [' + this.value + '] is ' + this.message);
|
||||
}
|
||||
});
|
||||
openerp.base.search.Widget = openerp.base.Controller.extend({
|
||||
openerp.base.search.Widget = openerp.base.Controller.extend(
|
||||
/** @lends openerp.base.search.Widget# */{
|
||||
template: null,
|
||||
/**
|
||||
* Root class of all search widgets
|
||||
*
|
||||
* @constructs
|
||||
* @extends openerp.base.Controller
|
||||
*
|
||||
* @param view the ancestor view of this widget
|
||||
*/
|
||||
init: function (view) {
|
||||
this.view = view;
|
||||
},
|
||||
|
@ -682,7 +736,14 @@ openerp.base.search.Group = openerp.base.search.Widget.extend({
|
|||
});
|
||||
}
|
||||
});
|
||||
openerp.base.search.Input = openerp.base.search.Widget.extend({
|
||||
openerp.base.search.Input = openerp.base.search.Widget.extend(
|
||||
/** @lends openerp.base.search.Input# */{
|
||||
/**
|
||||
* @constructs
|
||||
* @extends openerp.base.search.Widget
|
||||
*
|
||||
* @param view
|
||||
*/
|
||||
init: function (view) {
|
||||
this._super(view);
|
||||
this.view.inputs.push(this);
|
||||
|
@ -747,12 +808,21 @@ openerp.base.search.Filter = openerp.base.search.Input.extend({
|
|||
return this.attrs.domain;
|
||||
}
|
||||
});
|
||||
openerp.base.search.Field = openerp.base.search.Input.extend({
|
||||
openerp.base.search.Field = openerp.base.search.Input.extend(
|
||||
/** @lends openerp.base.search.Field# */ {
|
||||
template: 'SearchView.field',
|
||||
default_operator: '=',
|
||||
// TODO: set default values
|
||||
// TODO: get context, domain
|
||||
// TODO: holds Filters
|
||||
/**
|
||||
* @constructs
|
||||
* @extends openerp.base.search.Input
|
||||
*
|
||||
* @param view_section
|
||||
* @param field
|
||||
* @param view
|
||||
*/
|
||||
init: function (view_section, field, view) {
|
||||
this._super(view);
|
||||
this.attrs = _.extend({}, field, view_section.attrs);
|
||||
|
@ -795,7 +865,18 @@ openerp.base.search.Field = openerp.base.search.Input.extend({
|
|||
return this.attrs['filter_domain'];
|
||||
}
|
||||
});
|
||||
openerp.base.search.CharField = openerp.base.search.Field.extend({
|
||||
/**
|
||||
* Implementation of the ``char`` OpenERP field type:
|
||||
*
|
||||
* * Default operator is ``ilike`` rather than ``=``
|
||||
* * The Javascript and the HTML values are identical (strings)
|
||||
*
|
||||
* @class
|
||||
* @extends openerp.base.search.Field
|
||||
*
|
||||
*/
|
||||
openerp.base.search.CharField = openerp.base.search.Field.extend(
|
||||
/** @lends openerp.base.search.CharField# */ {
|
||||
default_operator: 'ilike',
|
||||
get_value: function () {
|
||||
return this.$element.val();
|
||||
|
|
|
@ -19,6 +19,193 @@ Merge proposals
|
|||
Writing documentation
|
||||
+++++++++++++++++++++
|
||||
|
||||
The OpenERP Web project documentation uses Sphinx_ for the literate
|
||||
documentation (this document for instance), the development guides
|
||||
(for Python and Javascript alike) and the Python API documentation
|
||||
(via autodoc_).
|
||||
|
||||
For the Javascript API, documentation should be written using the
|
||||
`JsDoc Toolkit`_.
|
||||
|
||||
Guides and main documentation
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The meat and most important part of all documentation. Should be
|
||||
written in plain english, using reStructuredText_ and taking advantage
|
||||
of `Sphinx's extensions`_, especially `cross-references`_.
|
||||
|
||||
Python API Documentation
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
All public objects in Python code should have a docstring written in
|
||||
RST, using Sphinx's `Python domain`_ [#]_:
|
||||
|
||||
* Functions and methods documentation should be in their own
|
||||
docstring, using Sphinx's `info fields`_
|
||||
|
||||
For parameters types, built-in and stdlib types should be using the
|
||||
combined syntax::
|
||||
|
||||
:param dict foo: what the purpose of foo is
|
||||
|
||||
unless a more extensive explanation needs to be given (e.g. the
|
||||
specification that the input should be a list of 3-tuple needs to
|
||||
use ``:type:`` even though all types involved are built-ins). Any
|
||||
other type should be specified in full with a cross-reference using
|
||||
the ``:type:`` field::
|
||||
|
||||
:param foo: what the purpose of foo is
|
||||
:type foo: :class:`some.addon.Class`
|
||||
|
||||
Likewise, mentions of other methods (including within the same
|
||||
class), modules or types should be cross-referenced.
|
||||
|
||||
* Classes should likewise be documented using their own docstring, and
|
||||
should include the documentation of their construction (``__init__``
|
||||
and ``__new__``), using the `info fields`_ as well.
|
||||
|
||||
* Attributes (class and instance) should be documented in their
|
||||
class's docstrin via the ``.. attribute::`` directiveg, following
|
||||
the class's own documentation.
|
||||
|
||||
* The relation between modules and module-level attributes is similar:
|
||||
modules should be documented in their own docstring, public module
|
||||
attributes should be documented in the module's docstring using the
|
||||
``.. data::`` directive.
|
||||
|
||||
Javascript API documentation
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Javascript API documentation uses JsDoc_, a javascript documentation
|
||||
toolkit with a syntax similar to (and inspired by) JavaDoc's.
|
||||
|
||||
Due to limitations of JsDoc, the coding patterns in OpenERP Web and
|
||||
the Sphinx integration, there are a few peculiarities to be aware of
|
||||
when writing javascript API documentation:
|
||||
|
||||
* Namespaces and classes *must* be explicitly marked up even if they
|
||||
are not documented, or JsDoc will not understand what they are and
|
||||
will not generate documentation for their content.
|
||||
|
||||
As a result, the bare minimum for a namespace is::
|
||||
|
||||
/** @namespace */
|
||||
foo.bar.baz = {};
|
||||
|
||||
while for a class it is::
|
||||
|
||||
/** @class */
|
||||
foo.bar.baz.Qux = [...]
|
||||
|
||||
* Because the OpenERP Web project uses `John Resig's Class
|
||||
implementation`_ instead of direct prototypal inheritance [#]_,
|
||||
JsDoc fails to infer class scopes (and constructors or super
|
||||
classes, for that matter) and has to be told explicitly.
|
||||
|
||||
See :ref:`js-class-doc` for the complete rundown.
|
||||
|
||||
* Much like the JavaDoc, JsDoc does not include a full markup
|
||||
language. Instead, comments are simply marked up in HTML.
|
||||
|
||||
This has a number of inconvenients:
|
||||
|
||||
* Complex documentation comments become nigh-unreadable to read in
|
||||
text editors (as opposed to IDEs, which may handle rendering
|
||||
documentation comments on the fly)
|
||||
|
||||
* Though cross-references are supported by JsDoc (via ``@link`` and
|
||||
``@see``), they only work within the JsDoc
|
||||
|
||||
* More general impossibility to integrate correctly with Sphinx, and
|
||||
e.g. reference JavaScript objects from a tutorial, or have all the
|
||||
documentation live at the same place.
|
||||
|
||||
As a result, JsDoc comments should be marked up using RST, not
|
||||
HTML. They may use Sphinx's cross-references as well.
|
||||
|
||||
.. _js-class-doc:
|
||||
|
||||
Documenting a Class
|
||||
*******************
|
||||
|
||||
The first task when documenting a class using JsDoc is to *mark* that
|
||||
class, so JsDoc knows it can be used to instantiate objects (and, more
|
||||
importantly as far as it's concerned, should be documented with
|
||||
methods and attributes and stuff).
|
||||
|
||||
This is generally done through the ``@class`` tag, but this tag has a
|
||||
significant limitation: it "believes" the constructor and the class
|
||||
are one and the same [#]_. This will work for constructor-less
|
||||
classes, but because OpenERP Web uses Resig's class the constructor is
|
||||
not the class itself but its ``init()`` method.
|
||||
|
||||
Because this pattern is common in modern javascript code bases, JsDoc
|
||||
supports it: it is possible to mark an arbitrary instance method as
|
||||
the *class specification* by using the ``@constructs`` tag.
|
||||
|
||||
.. warning:: ``@constructs`` is a class specification in and of
|
||||
itself, it *completely replaces* the class documentation.
|
||||
|
||||
Using both a class documentation (even without ``@class`` itself)
|
||||
and a constructor documentation is an *error* in JsDoc and will
|
||||
result in incorrect behavior and broken documentation.
|
||||
|
||||
The second issue is that Resig's class uses an object literal to
|
||||
specify instance methods, and because JsDoc does not know anything
|
||||
about Resig's class, it does not know about the role of the object
|
||||
literal.
|
||||
|
||||
As with constructors, though, JsDoc provides a pluggable way to tell
|
||||
it about methods: the ``@lends`` tag. It specifies that the object
|
||||
literal "lends" its properties to the class being built.
|
||||
|
||||
``@lends`` must be specified right before the opening brace of the
|
||||
object literal (between the opening paren of the ``#extend`` call and
|
||||
the brace), and takes the full qualified name of the class being
|
||||
created as a parameter, followed by the character ``#`` or by
|
||||
``.prototype``. This latter part tells JsDoc these are instance
|
||||
methods, not class (static) methods..
|
||||
|
||||
Finally, specifying a class's superclass is done through the
|
||||
``@extends`` tag, which takes a fully qualified class name as a
|
||||
parameter.
|
||||
|
||||
Here are a class without a constructor, and a class with one, so that
|
||||
everything is clear (these are straight from the OpenERP Web source,
|
||||
with the descriptions and irrelevant atttributes stripped):
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
/**
|
||||
* <Insert description here, not below>
|
||||
*
|
||||
* @class
|
||||
* @extends openerp.base.search.Field
|
||||
*/
|
||||
openerp.base.search.CharField = openerp.base.search.Field.extend(
|
||||
/** @lends openerp.base.search.CharField# */ {
|
||||
// methods here
|
||||
});
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
|
||||
openerp.base.search.Widget = openerp.base.Controller.extend(
|
||||
/** @lends openerp.base.search.Widget# */{
|
||||
/**
|
||||
* <Insert description here, not below>
|
||||
*
|
||||
* @constructs
|
||||
* @extends openerp.base.Controller
|
||||
*
|
||||
* @param view the ancestor view of this widget
|
||||
*/
|
||||
init: function (view) {
|
||||
// construction of the instance
|
||||
},
|
||||
// bunch of other methods
|
||||
});
|
||||
|
||||
OpenERP Web over time
|
||||
---------------------
|
||||
|
||||
|
@ -30,3 +217,35 @@ Roadmap
|
|||
|
||||
Release notes
|
||||
+++++++++++++
|
||||
|
||||
.. [#] because Python is the default domain, the ``py:`` markup prefix
|
||||
is optional and should be left out.
|
||||
|
||||
.. [#] Resig's Class still uses prototypes under the hood, it doesn't
|
||||
reimplement its own object system although it does add several
|
||||
helpers such as the ``_super()`` instance method.
|
||||
|
||||
.. [#] Which is the case in normal Javascript semantics. Likewise, the
|
||||
``.prototype`` / ``#`` pattern we will see later on is due to
|
||||
JsDoc defaulting to the only behavior it can rely on: "normal"
|
||||
Javascript prototype-based type creation.
|
||||
|
||||
.. _reStructuredText:
|
||||
http://docutils.sourceforge.net/rst.html
|
||||
.. _Sphinx:
|
||||
http://sphinx.pocoo.org/index.html
|
||||
.. _Sphinx's extensions:
|
||||
http://sphinx.pocoo.org/markup/index.html
|
||||
.. _Python domain:
|
||||
http://sphinx.pocoo.org/domains.html#the-python-domain
|
||||
.. _info fields:
|
||||
http://sphinx.pocoo.org/domains.html#info-field-lists
|
||||
.. _autodoc:
|
||||
http://sphinx.pocoo.org/ext/autodoc.html?highlight=autodoc#sphinx.ext.autodoc
|
||||
.. _cross-references:
|
||||
http://sphinx.pocoo.org/markup/inline.html#xref-syntax
|
||||
.. _JsDoc:
|
||||
.. _JsDoc Toolkit:
|
||||
http://code.google.com/p/jsdoc-toolkit/
|
||||
.. _John Resig's Class implementation:
|
||||
http://ejohn.org/blog/simple-javascript-inheritance/
|
||||
|
|
Loading…
Reference in New Issue