2013-04-02 18:28:42 +00:00
|
|
|
/*!
|
|
|
|
* VisualEditor DataModel Model class.
|
|
|
|
*
|
|
|
|
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
|
|
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Base class for DM models.
|
|
|
|
*
|
The Great ve.ui.Surface refactor of 2013
Prologue:
Farewell ve.Editor my good chap… Oh, hey there HTML frames - I didn't
see you there! In a world where iframes are outlaws, and symbols like
document and window are global, there were more than a few assumptions
about which document or window was being used. But fear not - for this
commit (probably) tracks them all down, leaving a trail of
iframe-compatible awesomeness in its wake. With the great ve.ui.Surface
now able to be used inside of iframes, let the reference editing
commence. But there, lurking in the darkness is a DM issue so fierce it
may take Roan and/or Ed up to 3 whole hours to sort it out.
Note to Roan and/or Ed:
Editing references seems to work fine, but when saving the page there
are "no changes" which is a reasonable indication to the contrary.
Objectives:
* Make it possible to have multiple surfaces be instantiated, get along
nicely, and be embedded inside of iframes if needed.
* Make reference content editable within a dialog
Approach:
* Move what's left of ve.Editor to ve.ui.Surface and essentially
obliterate all use of it
* Make even more stuff inherit from ve.Element (long live this.$$)
* Use the correct document or window anywhere it was being assumed to be
the top level one
* Resolve stacking order issues by removing the excessive use of z-index
and introducing global and local overlay elements for each editor
* Add a surface to the reference dialog, load up the reference contents
and save them back on apply
* Actually destroy what we create in ce and ui surfaces
* Add recursive frame offset calculation method to ve.Element
* Moved ve.ce.Surface's getSelectionRect method to the prototype
Bonus:
* Move ve.ce.DocumentNode.css contents to ve.ce.Node.css (not sure why it
was separate in the first place, but I'm likely the one to blame)
* Fix blatant lies in documentation
* Whitespace cleanup here and there
* Get rid of ve.ui.Window overlays - not used or needed
Change-Id: Iede83e7d24f7cb249b6ba3dc45d770445b862e08
2013-05-20 22:45:50 +00:00
|
|
|
* @class
|
2013-04-02 18:28:42 +00:00
|
|
|
* @abstract
|
The Great ve.ui.Surface refactor of 2013
Prologue:
Farewell ve.Editor my good chap… Oh, hey there HTML frames - I didn't
see you there! In a world where iframes are outlaws, and symbols like
document and window are global, there were more than a few assumptions
about which document or window was being used. But fear not - for this
commit (probably) tracks them all down, leaving a trail of
iframe-compatible awesomeness in its wake. With the great ve.ui.Surface
now able to be used inside of iframes, let the reference editing
commence. But there, lurking in the darkness is a DM issue so fierce it
may take Roan and/or Ed up to 3 whole hours to sort it out.
Note to Roan and/or Ed:
Editing references seems to work fine, but when saving the page there
are "no changes" which is a reasonable indication to the contrary.
Objectives:
* Make it possible to have multiple surfaces be instantiated, get along
nicely, and be embedded inside of iframes if needed.
* Make reference content editable within a dialog
Approach:
* Move what's left of ve.Editor to ve.ui.Surface and essentially
obliterate all use of it
* Make even more stuff inherit from ve.Element (long live this.$$)
* Use the correct document or window anywhere it was being assumed to be
the top level one
* Resolve stacking order issues by removing the excessive use of z-index
and introducing global and local overlay elements for each editor
* Add a surface to the reference dialog, load up the reference contents
and save them back on apply
* Actually destroy what we create in ce and ui surfaces
* Add recursive frame offset calculation method to ve.Element
* Moved ve.ce.Surface's getSelectionRect method to the prototype
Bonus:
* Move ve.ce.DocumentNode.css contents to ve.ce.Node.css (not sure why it
was separate in the first place, but I'm likely the one to blame)
* Fix blatant lies in documentation
* Whitespace cleanup here and there
* Get rid of ve.ui.Window overlays - not used or needed
Change-Id: Iede83e7d24f7cb249b6ba3dc45d770445b862e08
2013-05-20 22:45:50 +00:00
|
|
|
*
|
2013-04-02 18:28:42 +00:00
|
|
|
* @constructor
|
|
|
|
* @param {Object} element Reference to plain object in linear model
|
|
|
|
*/
|
|
|
|
ve.dm.Model = function VeDmModel( element ) {
|
|
|
|
// Properties
|
2013-04-16 00:04:06 +00:00
|
|
|
this.element = element || { 'type': this.constructor.static.name };
|
2013-04-02 18:28:42 +00:00
|
|
|
};
|
|
|
|
|
ve.Element refactor
Objectives:
* Move ve.ui.Element to ve.Element
* Make CE nodes inherit from ve.Element
Changes:
ve.ui.Element.js, ve.Element.js
* Move and rename
* Move ve.ui.get$$ to ve.Element.static.get$$
* Add static getDocument and getWindow methods
* Add instance getElementDocument and getElementWindow methods
* Add getTagName method, which by default reads the static tagName property, but when overridden can return a tag name based on other factors
*.php
* Updated file link
ve.ce.*Annotation.js, ve.ce.*Node.js, ve.ce.View.js, ve.ce.Document
* Added config options pass through
* Replaced passing elements through constructor with defining static tag names
* Added getTagName overrides where needed that derive tag name from model
* Refactore dom wrapper methods, now consistently using getTagName
ve.ce.Surface.js
* Removed static initialization (not needed)
ve.dm.Model.js, ve.ui.Window.js
* Added missing docs
ve.ui.GroupElement.js, ve.ui.Layout.js, ve.ui.Widget.js,
* Updated class name for elements
ve.ui.Frame.js, ve.ui.LookupInputWidget.js
* Updated location of get$$
ve.ui.js
* Move get$$ to ve.Element
ve.js
* Add auto-init of static properties to mixinClass
Change-Id: I39ae14966456903728e4d9e53f806ddce9ca2b70
2013-05-13 20:52:59 +00:00
|
|
|
/* Static Properties */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @static
|
|
|
|
* @property
|
|
|
|
* @inheritable
|
|
|
|
*/
|
2013-04-02 18:28:42 +00:00
|
|
|
ve.dm.Model.static = {};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Symbolic name for this model class. Must be set to a unique string by every subclass.
|
|
|
|
* @static
|
2013-11-19 08:32:37 +00:00
|
|
|
* @property {string}
|
2013-04-02 18:28:42 +00:00
|
|
|
* @inheritable
|
|
|
|
*/
|
|
|
|
ve.dm.Model.static.name = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Array of HTML tag names that this model should be a match candidate for.
|
|
|
|
* Empty array means none, null means any.
|
|
|
|
* For more information about element matching, see ve.dm.ModelRegistry.
|
|
|
|
* @static
|
2013-11-19 08:32:37 +00:00
|
|
|
* @property {string[]}
|
2013-04-02 18:28:42 +00:00
|
|
|
* @inheritable
|
|
|
|
*/
|
|
|
|
ve.dm.Model.static.matchTagNames = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Array of RDFa types that this model should be a match candidate for.
|
|
|
|
* Empty array means none, null means any.
|
|
|
|
* For more information about element matching, see ve.dm.ModelRegistry.
|
|
|
|
* @static
|
2013-11-19 08:32:37 +00:00
|
|
|
* @property {Array}
|
2013-04-02 18:28:42 +00:00
|
|
|
* @inheritable
|
|
|
|
*/
|
|
|
|
ve.dm.Model.static.matchRdfaTypes = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Optional function to determine whether this model should match a given element.
|
|
|
|
* Takes an HTMLElement and returns true or false.
|
|
|
|
* This function is only called if this model has a chance of "winning"; see
|
|
|
|
* ve.dm.ModelRegistry for more information about element matching.
|
|
|
|
* If set to null, this property is ignored. Setting this to null is not the same as unconditionally
|
|
|
|
* returning true, because the presence or absence of a matchFunction affects the model's
|
|
|
|
* specificity.
|
|
|
|
*
|
|
|
|
* NOTE: This function is NOT a method, within this function "this" will not refer to an instance
|
|
|
|
* of this class (or to anything reasonable, for that matter).
|
|
|
|
* @static
|
2013-11-19 08:32:37 +00:00
|
|
|
* @property {Function}
|
2013-04-02 18:28:42 +00:00
|
|
|
* @inheritable
|
|
|
|
*/
|
|
|
|
ve.dm.Model.static.matchFunction = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Static function to convert a DOM element or set of sibling DOM elements to a linear model element
|
|
|
|
* for this model type.
|
|
|
|
*
|
|
|
|
* This function is only called if this model "won" the matching for the first DOM element, so
|
|
|
|
* domElements[0] will match this model's matching rule. There is usually only one DOM node in
|
|
|
|
* domElements[]. Multiple elements will only be passed if this model supports about groups.
|
|
|
|
* If there are multiple nodes, the nodes are all adjacent siblings in the same about group
|
|
|
|
* (i.e. they are grouped together because they have the same value for the about attribute).
|
|
|
|
*
|
2013-04-10 21:54:29 +00:00
|
|
|
* The converter has some state variables that can be obtained by this function:
|
|
|
|
* - if converter.isExpectingContent() returns true, the converter expects a content element
|
|
|
|
* - if converter.isInWrapper() returns true, the returned element will be put in a wrapper
|
|
|
|
* paragraph generated by the converter (this is only relevant if isExpectingContent() is true)
|
|
|
|
* - converter.canCloseWrapper() returns true if the current wrapper paragraph can be closed,
|
|
|
|
* and false if it can't be closed or if there is no active wrapper
|
|
|
|
*
|
2013-04-02 18:28:42 +00:00
|
|
|
* This function is allowed to return a content element when context indicates that a non-content
|
|
|
|
* element is expected or vice versa. If that happens, the converter deals with it in the following way:
|
|
|
|
*
|
|
|
|
* - if a non-content element is expected but a content element is returned:
|
|
|
|
* - open a wrapper paragraph
|
|
|
|
* - put the returned element in the wrapper
|
|
|
|
* - if a content element is expected but a non-content element is returned:
|
|
|
|
* - if we are in a wrapper paragraph:
|
|
|
|
* - if we can close the wrapper:
|
|
|
|
* - close the wrapper
|
|
|
|
* - insert the returned element right after the end of the wrapper
|
|
|
|
* - if we can't close the wrapper:
|
|
|
|
* - alienate the element
|
|
|
|
* - if we aren't in a wrapper paragraph:
|
|
|
|
* - alienate the element
|
|
|
|
*
|
|
|
|
* For these purposes, annotations are considered content. Meta-items can occur anywhere, so if
|
|
|
|
* a meta-element is returned no special action is taken. Note that "alienate" always means an alien
|
|
|
|
* *node* (ve.dm.AlienNode) will be generated, never an alien meta-item (ve.dm.AlienMetaItem),
|
|
|
|
* regardless of whether the subclass attempting the conversion is a node or a meta-item.
|
|
|
|
*
|
|
|
|
* The returned linear model element must have a type property set to a registered model name
|
|
|
|
* (usually the model's own .static.name, but that's not required). It may optionally have an attributes
|
|
|
|
* property set to an object with key-value pairs. Any other properties are not allowed.
|
|
|
|
*
|
Allow nodes to handle their own children
For data->DOM, this is easy: .toDataElements() can optionally return an
array instead of an object, and that will be treated as the data to
insert. If this happens, the converter won't descend. The node handler
can recursively invoke the converter if it needs to (although I suspect
the current implementation is broken when converting block content in an
inline context).
For DOM->data, this is a bit more complex. The node sets
.static.handlesOwnChildren = true; , which triggers the converter to
pass a data slice rather than a single data element, and not to
descend. The node handler can invoke the converter to recursively
convert DOM subtrees to data.
ve.dm.Converter (data->DOM):
* Renamed createDataElement() to createDataElements()
** .toDataElement() may return element or array, handle this
* Renamed childDataElement to childDataElements, is now an array
* Actually alienate if .toDataElement() returns null
** Shockingly, this claimed to be supported before but wasn't
* Rather than pushing to data, concat to it
** Add closing if needed
* Don't descend if .toDataElement() returned an array of length >1, or
if the node has .handlesOwnChildren = true
ve.dm.Converter (DOM->data):
* Split getDomSubtreeFromData() and getDomFromData()
* When converting a node that handles its own children, pass in a data
slice and skip over that data
Change-Id: I196cb4c0895cbf0b428a189adb61b56565573ab3
2013-04-11 00:31:09 +00:00
|
|
|
* This function may return a single linear model element, or an array of balanced linear model
|
|
|
|
* data. If this function needs to recursively convert a DOM node (e.g. a child of one of the
|
2013-12-05 21:48:44 +00:00
|
|
|
* DOM elements passed in), it can call converter.getDataFromDom( domElement ). Note that
|
Allow nodes to handle their own children
For data->DOM, this is easy: .toDataElements() can optionally return an
array instead of an object, and that will be treated as the data to
insert. If this happens, the converter won't descend. The node handler
can recursively invoke the converter if it needs to (although I suspect
the current implementation is broken when converting block content in an
inline context).
For DOM->data, this is a bit more complex. The node sets
.static.handlesOwnChildren = true; , which triggers the converter to
pass a data slice rather than a single data element, and not to
descend. The node handler can invoke the converter to recursively
convert DOM subtrees to data.
ve.dm.Converter (data->DOM):
* Renamed createDataElement() to createDataElements()
** .toDataElement() may return element or array, handle this
* Renamed childDataElement to childDataElements, is now an array
* Actually alienate if .toDataElement() returns null
** Shockingly, this claimed to be supported before but wasn't
* Rather than pushing to data, concat to it
** Add closing if needed
* Don't descend if .toDataElement() returned an array of length >1, or
if the node has .handlesOwnChildren = true
ve.dm.Converter (DOM->data):
* Split getDomSubtreeFromData() and getDomFromData()
* When converting a node that handles its own children, pass in a data
slice and skip over that data
Change-Id: I196cb4c0895cbf0b428a189adb61b56565573ab3
2013-04-11 00:31:09 +00:00
|
|
|
* if an array is returned, the converter will not descend into the DOM node's children; the model
|
|
|
|
* will be assumed to have handled those children.
|
|
|
|
*
|
2013-04-02 18:28:42 +00:00
|
|
|
* @static
|
|
|
|
* @inheritable
|
|
|
|
* @method
|
|
|
|
* @param {HTMLElement[]} domElements DOM elements to convert. Usually only one element
|
2013-04-10 21:54:29 +00:00
|
|
|
* @param {ve.dm.Converter} converter Converter object
|
Allow nodes to handle their own children
For data->DOM, this is easy: .toDataElements() can optionally return an
array instead of an object, and that will be treated as the data to
insert. If this happens, the converter won't descend. The node handler
can recursively invoke the converter if it needs to (although I suspect
the current implementation is broken when converting block content in an
inline context).
For DOM->data, this is a bit more complex. The node sets
.static.handlesOwnChildren = true; , which triggers the converter to
pass a data slice rather than a single data element, and not to
descend. The node handler can invoke the converter to recursively
convert DOM subtrees to data.
ve.dm.Converter (data->DOM):
* Renamed createDataElement() to createDataElements()
** .toDataElement() may return element or array, handle this
* Renamed childDataElement to childDataElements, is now an array
* Actually alienate if .toDataElement() returns null
** Shockingly, this claimed to be supported before but wasn't
* Rather than pushing to data, concat to it
** Add closing if needed
* Don't descend if .toDataElement() returned an array of length >1, or
if the node has .handlesOwnChildren = true
ve.dm.Converter (DOM->data):
* Split getDomSubtreeFromData() and getDomFromData()
* When converting a node that handles its own children, pass in a data
slice and skip over that data
Change-Id: I196cb4c0895cbf0b428a189adb61b56565573ab3
2013-04-11 00:31:09 +00:00
|
|
|
* @returns {Object|Array|null} Linear model element, or array with linear model data, or null to alienate
|
2013-04-02 18:28:42 +00:00
|
|
|
*/
|
2013-04-10 21:54:29 +00:00
|
|
|
ve.dm.Model.static.toDataElement = function ( /*domElements, converter*/ ) {
|
2013-04-02 18:28:42 +00:00
|
|
|
throw new Error( 've.dm.Model subclass must implement toDataElement' );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Static function to convert a linear model data element for this model type back to one or more
|
|
|
|
* DOM elements.
|
|
|
|
*
|
Allow nodes to handle their own children
For data->DOM, this is easy: .toDataElements() can optionally return an
array instead of an object, and that will be treated as the data to
insert. If this happens, the converter won't descend. The node handler
can recursively invoke the converter if it needs to (although I suspect
the current implementation is broken when converting block content in an
inline context).
For DOM->data, this is a bit more complex. The node sets
.static.handlesOwnChildren = true; , which triggers the converter to
pass a data slice rather than a single data element, and not to
descend. The node handler can invoke the converter to recursively
convert DOM subtrees to data.
ve.dm.Converter (data->DOM):
* Renamed createDataElement() to createDataElements()
** .toDataElement() may return element or array, handle this
* Renamed childDataElement to childDataElements, is now an array
* Actually alienate if .toDataElement() returns null
** Shockingly, this claimed to be supported before but wasn't
* Rather than pushing to data, concat to it
** Add closing if needed
* Don't descend if .toDataElement() returned an array of length >1, or
if the node has .handlesOwnChildren = true
ve.dm.Converter (DOM->data):
* Split getDomSubtreeFromData() and getDomFromData()
* When converting a node that handles its own children, pass in a data
slice and skip over that data
Change-Id: I196cb4c0895cbf0b428a189adb61b56565573ab3
2013-04-11 00:31:09 +00:00
|
|
|
* If this model is a node with .handlesOwnChildren set to true, dataElement will be an array of
|
|
|
|
* the linear model data of this node and all of its children, rather than a single element.
|
|
|
|
* In this case, this function way want to recursively convert linear model data to DOM, which can
|
2013-04-17 17:53:26 +00:00
|
|
|
* be done with converter#getDomSubtreeFromData.
|
Allow nodes to handle their own children
For data->DOM, this is easy: .toDataElements() can optionally return an
array instead of an object, and that will be treated as the data to
insert. If this happens, the converter won't descend. The node handler
can recursively invoke the converter if it needs to (although I suspect
the current implementation is broken when converting block content in an
inline context).
For DOM->data, this is a bit more complex. The node sets
.static.handlesOwnChildren = true; , which triggers the converter to
pass a data slice rather than a single data element, and not to
descend. The node handler can invoke the converter to recursively
convert DOM subtrees to data.
ve.dm.Converter (data->DOM):
* Renamed createDataElement() to createDataElements()
** .toDataElement() may return element or array, handle this
* Renamed childDataElement to childDataElements, is now an array
* Actually alienate if .toDataElement() returns null
** Shockingly, this claimed to be supported before but wasn't
* Rather than pushing to data, concat to it
** Add closing if needed
* Don't descend if .toDataElement() returned an array of length >1, or
if the node has .handlesOwnChildren = true
ve.dm.Converter (DOM->data):
* Split getDomSubtreeFromData() and getDomFromData()
* When converting a node that handles its own children, pass in a data
slice and skip over that data
Change-Id: I196cb4c0895cbf0b428a189adb61b56565573ab3
2013-04-11 00:31:09 +00:00
|
|
|
*
|
2013-04-02 18:28:42 +00:00
|
|
|
* NOTE: If this function returns multiple DOM elements, the DOM elements produced by the children
|
|
|
|
* of this model (if it's a node and has children) will be attached to the first DOM element in the array.
|
|
|
|
* For annotations, only the first element is used, and any additional elements are ignored.
|
|
|
|
*
|
|
|
|
* @static
|
|
|
|
* @inheritable
|
|
|
|
* @method
|
Allow nodes to handle their own children
For data->DOM, this is easy: .toDataElements() can optionally return an
array instead of an object, and that will be treated as the data to
insert. If this happens, the converter won't descend. The node handler
can recursively invoke the converter if it needs to (although I suspect
the current implementation is broken when converting block content in an
inline context).
For DOM->data, this is a bit more complex. The node sets
.static.handlesOwnChildren = true; , which triggers the converter to
pass a data slice rather than a single data element, and not to
descend. The node handler can invoke the converter to recursively
convert DOM subtrees to data.
ve.dm.Converter (data->DOM):
* Renamed createDataElement() to createDataElements()
** .toDataElement() may return element or array, handle this
* Renamed childDataElement to childDataElements, is now an array
* Actually alienate if .toDataElement() returns null
** Shockingly, this claimed to be supported before but wasn't
* Rather than pushing to data, concat to it
** Add closing if needed
* Don't descend if .toDataElement() returned an array of length >1, or
if the node has .handlesOwnChildren = true
ve.dm.Converter (DOM->data):
* Split getDomSubtreeFromData() and getDomFromData()
* When converting a node that handles its own children, pass in a data
slice and skip over that data
Change-Id: I196cb4c0895cbf0b428a189adb61b56565573ab3
2013-04-11 00:31:09 +00:00
|
|
|
* @param {Object|Array} dataElement Linear model element or array of linear model data
|
2013-04-02 20:23:16 +00:00
|
|
|
* @param {HTMLDocument} doc HTML document for creating elements
|
Allow nodes to handle their own children
For data->DOM, this is easy: .toDataElements() can optionally return an
array instead of an object, and that will be treated as the data to
insert. If this happens, the converter won't descend. The node handler
can recursively invoke the converter if it needs to (although I suspect
the current implementation is broken when converting block content in an
inline context).
For DOM->data, this is a bit more complex. The node sets
.static.handlesOwnChildren = true; , which triggers the converter to
pass a data slice rather than a single data element, and not to
descend. The node handler can invoke the converter to recursively
convert DOM subtrees to data.
ve.dm.Converter (data->DOM):
* Renamed createDataElement() to createDataElements()
** .toDataElement() may return element or array, handle this
* Renamed childDataElement to childDataElements, is now an array
* Actually alienate if .toDataElement() returns null
** Shockingly, this claimed to be supported before but wasn't
* Rather than pushing to data, concat to it
** Add closing if needed
* Don't descend if .toDataElement() returned an array of length >1, or
if the node has .handlesOwnChildren = true
ve.dm.Converter (DOM->data):
* Split getDomSubtreeFromData() and getDomFromData()
* When converting a node that handles its own children, pass in a data
slice and skip over that data
Change-Id: I196cb4c0895cbf0b428a189adb61b56565573ab3
2013-04-11 00:31:09 +00:00
|
|
|
* @param {ve.dm.Converter} converter Converter object to optionally call .getDomSubtreeFromData() on
|
2013-04-02 18:28:42 +00:00
|
|
|
* @returns {HTMLElement[]} DOM elements
|
|
|
|
*/
|
Allow nodes to handle their own children
For data->DOM, this is easy: .toDataElements() can optionally return an
array instead of an object, and that will be treated as the data to
insert. If this happens, the converter won't descend. The node handler
can recursively invoke the converter if it needs to (although I suspect
the current implementation is broken when converting block content in an
inline context).
For DOM->data, this is a bit more complex. The node sets
.static.handlesOwnChildren = true; , which triggers the converter to
pass a data slice rather than a single data element, and not to
descend. The node handler can invoke the converter to recursively
convert DOM subtrees to data.
ve.dm.Converter (data->DOM):
* Renamed createDataElement() to createDataElements()
** .toDataElement() may return element or array, handle this
* Renamed childDataElement to childDataElements, is now an array
* Actually alienate if .toDataElement() returns null
** Shockingly, this claimed to be supported before but wasn't
* Rather than pushing to data, concat to it
** Add closing if needed
* Don't descend if .toDataElement() returned an array of length >1, or
if the node has .handlesOwnChildren = true
ve.dm.Converter (DOM->data):
* Split getDomSubtreeFromData() and getDomFromData()
* When converting a node that handles its own children, pass in a data
slice and skip over that data
Change-Id: I196cb4c0895cbf0b428a189adb61b56565573ab3
2013-04-11 00:31:09 +00:00
|
|
|
ve.dm.Model.static.toDomElements = function ( /*dataElement, doc, converter*/ ) {
|
2013-04-02 18:28:42 +00:00
|
|
|
throw new Error( 've.dm.Model subclass must implement toDomElements' );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Whether this model supports about grouping. When a DOM element matches a model type that has
|
|
|
|
* about grouping enabled, the converter will look for adjacent siblings with the same value for
|
|
|
|
* the about attribute, and ask toDataElement() to produce a single data element for all of those
|
|
|
|
* DOM nodes combined.
|
|
|
|
*
|
|
|
|
* The converter doesn't descend into about groups, i.e. it doesn't convert the children of the
|
|
|
|
* DOM elements that make up the about group. This means the resulting linear model element will
|
|
|
|
* be childless.
|
|
|
|
*
|
|
|
|
* @static
|
2013-11-19 08:32:37 +00:00
|
|
|
* @property {boolean}
|
2013-04-02 18:28:42 +00:00
|
|
|
* @inheritable
|
|
|
|
*/
|
|
|
|
ve.dm.Model.static.enableAboutGrouping = false;
|
|
|
|
|
|
|
|
/**
|
2013-05-07 21:23:07 +00:00
|
|
|
* Which HTML attributes should be preserved for this model type. HTML attributes on the DOM
|
|
|
|
* elements that match this specification will be stored as attributes in the linear model. The
|
2013-05-18 03:49:25 +00:00
|
|
|
* attributes will be stored in the .htmlAttributes property of the linear model element.
|
2013-04-02 18:28:42 +00:00
|
|
|
*
|
2013-05-08 04:25:55 +00:00
|
|
|
* When converting back to DOM, these HTML attributes will be restored except for attributes that
|
|
|
|
* were already set by toDomElements().
|
|
|
|
*
|
|
|
|
* The value of this property can be one of the following:
|
2013-07-25 02:02:50 +00:00
|
|
|
*
|
2013-05-07 21:23:07 +00:00
|
|
|
* - true, to preserve all attributes (default)
|
|
|
|
* - false, to preserve none
|
|
|
|
* - a string, to preserve only that attribute
|
|
|
|
* - a regular expression matching attributes that should be preserved
|
|
|
|
* - an array of strings or regular expressions
|
|
|
|
* - an object with the following keys:
|
|
|
|
* - 'blacklist': specification of attributes not to preserve (boolean|string|RegExp|Array)
|
|
|
|
* - 'whitelist': specification of attributes to preserve
|
|
|
|
*
|
|
|
|
* If only a blacklist is specified, all attributes will be preserved except the ones matching
|
|
|
|
* the blacklist. If only a whitelist is specified, only those attributes matching the whitelist
|
|
|
|
* will be preserved. If both are specified, only attributes that both match the whitelist and
|
|
|
|
* do not match the blacklist will be preserved.
|
2013-04-02 18:28:42 +00:00
|
|
|
*
|
|
|
|
* @static
|
2013-11-19 08:32:37 +00:00
|
|
|
* @property {boolean|string|RegExp|Array|Object}
|
2013-04-02 18:28:42 +00:00
|
|
|
* @inheritable
|
|
|
|
*/
|
|
|
|
ve.dm.Model.static.storeHtmlAttributes = true;
|
|
|
|
|
2013-05-07 21:23:07 +00:00
|
|
|
/* Static methods */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determine whether an attribute name matches an attribute specification.
|
|
|
|
*
|
|
|
|
* @param {string} attribute Attribute name
|
2013-11-19 08:32:37 +00:00
|
|
|
* @param {boolean|string|RegExp|Array|Object} spec Attribute specification, see #storeHtmlAttributes
|
2013-05-07 21:23:07 +00:00
|
|
|
* @returns {boolean} Attribute matches spec
|
|
|
|
*/
|
|
|
|
ve.dm.Model.matchesAttributeSpec = function ( attribute, spec ) {
|
|
|
|
function matches( subspec ) {
|
|
|
|
if ( subspec instanceof RegExp ) {
|
|
|
|
return !!subspec.exec( attribute );
|
|
|
|
}
|
|
|
|
if ( typeof subspec === 'boolean' ) {
|
|
|
|
return subspec;
|
|
|
|
}
|
|
|
|
return attribute === subspec;
|
|
|
|
}
|
|
|
|
|
|
|
|
function matchesArray( specArray ) {
|
|
|
|
var i, len;
|
|
|
|
if ( !ve.isArray( specArray ) ) {
|
|
|
|
specArray = [ specArray ];
|
|
|
|
}
|
|
|
|
for ( i = 0, len = specArray.length; i < len; i++ ) {
|
|
|
|
if ( matches( specArray[i] ) ) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( spec.whitelist === undefined && spec.blacklist === undefined ) {
|
|
|
|
// Not an object, treat spec as a whitelist
|
|
|
|
return matchesArray( spec );
|
|
|
|
}
|
|
|
|
return matchesArray( spec.whitelist || true ) && !matchesArray( spec.blacklist || false );
|
|
|
|
};
|
|
|
|
|
2013-07-17 02:00:18 +00:00
|
|
|
/**
|
2013-12-11 10:56:26 +00:00
|
|
|
* Get hash object of a linear model data element.
|
2013-07-17 02:00:18 +00:00
|
|
|
*
|
|
|
|
* @static
|
|
|
|
* @param {Object} dataElement Data element
|
|
|
|
* @returns {Object} Hash object
|
|
|
|
*/
|
|
|
|
ve.dm.Model.static.getHashObject = function ( dataElement ) {
|
|
|
|
return {
|
|
|
|
type: dataElement.type,
|
|
|
|
attributes: dataElement.attributes,
|
|
|
|
htmlAttributes: dataElement.htmlAttributes
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2013-08-07 09:35:03 +00:00
|
|
|
/**
|
|
|
|
* Array of RDFa types that this model should be a match candidate for.
|
2013-12-11 10:56:26 +00:00
|
|
|
*
|
2013-08-07 09:35:03 +00:00
|
|
|
* @static
|
|
|
|
* @returns {Array} Array of strings or regular expressions
|
|
|
|
*/
|
|
|
|
ve.dm.Model.static.getMatchRdfaTypes = function () {
|
|
|
|
return this.matchRdfaTypes;
|
|
|
|
};
|
|
|
|
|
2013-12-11 10:56:26 +00:00
|
|
|
/**
|
|
|
|
* Remove a specified HTML attribute from all DOM elements in the model.
|
|
|
|
*
|
|
|
|
* @static
|
|
|
|
* @param {Object} dataElement Data element
|
|
|
|
* @param {string} attribute Attribute name
|
|
|
|
*/
|
|
|
|
ve.dm.Model.static.removeHtmlAttribute = function ( dataElement, attribute ) {
|
2013-12-11 23:58:19 +00:00
|
|
|
function removeAttributeRecursive( children ) {
|
|
|
|
var i;
|
|
|
|
for ( i = 0; i < children.length; i++ ) {
|
|
|
|
delete children[i].values[attribute];
|
|
|
|
if ( ve.isEmptyObject( children[i].values ) ) {
|
|
|
|
delete children[i].values;
|
|
|
|
}
|
|
|
|
if ( children[i].children ) {
|
|
|
|
removeAttributeRecursive( children[i].children );
|
|
|
|
if ( !children[i].children.length ) {
|
|
|
|
delete children[i].children;
|
|
|
|
}
|
2013-12-11 10:56:26 +00:00
|
|
|
}
|
2013-12-11 23:58:19 +00:00
|
|
|
if ( ve.isEmptyObject( children[i] ) ) {
|
|
|
|
children.splice( i, 1 );
|
2013-12-11 10:56:26 +00:00
|
|
|
i--;
|
|
|
|
}
|
|
|
|
}
|
2013-12-11 23:58:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if ( dataElement.htmlAttributes ) {
|
|
|
|
removeAttributeRecursive( dataElement.htmlAttributes );
|
|
|
|
if ( !dataElement.htmlAttributes.length ) {
|
2013-12-11 10:56:26 +00:00
|
|
|
delete dataElement.htmlAttributes;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-04-02 18:28:42 +00:00
|
|
|
/* Methods */
|
|
|
|
|
|
|
|
/**
|
2013-12-11 10:56:26 +00:00
|
|
|
* Get a reference to the linear model element.
|
2013-04-02 18:28:42 +00:00
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @returns {Object} Linear model element passed to the constructor, by reference
|
|
|
|
*/
|
|
|
|
ve.dm.Model.prototype.getElement = function () {
|
|
|
|
return this.element;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2013-12-11 10:56:26 +00:00
|
|
|
* Get the symbolic name of this model's type.
|
2013-04-02 18:28:42 +00:00
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @returns {string} Type name
|
|
|
|
*/
|
|
|
|
ve.dm.Model.prototype.getType = function () {
|
|
|
|
return this.constructor.static.name;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the value of an attribute.
|
|
|
|
*
|
|
|
|
* Return value is by reference if array or object.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @param {string} key Name of attribute to get
|
|
|
|
* @returns {Mixed} Value of attribute, or undefined if no such attribute exists
|
|
|
|
*/
|
|
|
|
ve.dm.Model.prototype.getAttribute = function ( key ) {
|
|
|
|
return this.element && this.element.attributes ? this.element.attributes[key] : undefined;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a copy of all attributes.
|
|
|
|
*
|
|
|
|
* Values are by reference if array or object, similar to using the getAttribute method.
|
|
|
|
*
|
|
|
|
* @method
|
2013-11-12 20:23:15 +00:00
|
|
|
* @param {string} [prefix] Only return attributes with this prefix, and remove the prefix from them
|
2013-04-02 18:28:42 +00:00
|
|
|
* @returns {Object} Attributes
|
|
|
|
*/
|
|
|
|
ve.dm.Model.prototype.getAttributes = function ( prefix ) {
|
|
|
|
var key, filtered,
|
|
|
|
attributes = this.element && this.element.attributes ? this.element.attributes : {};
|
|
|
|
if ( prefix ) {
|
|
|
|
filtered = {};
|
|
|
|
for ( key in attributes ) {
|
|
|
|
if ( key.indexOf( prefix ) === 0 ) {
|
|
|
|
filtered[key.substr( prefix.length )] = attributes[key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return filtered;
|
|
|
|
}
|
|
|
|
return ve.extendObject( {}, attributes );
|
|
|
|
};
|
|
|
|
|
2013-05-18 03:49:25 +00:00
|
|
|
/**
|
|
|
|
* Get the preserved HTML attributes.
|
|
|
|
* @returns {Object[]} HTML attribute list, or empty array
|
|
|
|
*/
|
|
|
|
ve.dm.Model.prototype.getHtmlAttributes = function () {
|
|
|
|
return ( this.element && this.element.htmlAttributes ) || [];
|
|
|
|
};
|
|
|
|
|
2013-04-02 18:28:42 +00:00
|
|
|
/**
|
|
|
|
* Check if the model has certain attributes.
|
|
|
|
*
|
|
|
|
* If an array of keys is provided only the presence of the attributes will be checked. If an object
|
|
|
|
* with keys and values is provided both the presence of the attributes and their values will be
|
|
|
|
* checked. Comparison of values is done by casting to strings unless the strict argument is used.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @param {string[]|Object} attributes Array of keys or object of keys and values
|
|
|
|
* @param {boolean} strict Use strict comparison when checking if values match
|
|
|
|
* @returns {boolean} Model has attributes
|
|
|
|
*/
|
|
|
|
ve.dm.Model.prototype.hasAttributes = function ( attributes, strict ) {
|
|
|
|
var key, i, len,
|
|
|
|
ourAttributes = this.getAttributes() || {};
|
|
|
|
if ( ve.isPlainObject( attributes ) ) {
|
|
|
|
// Node must have all the required attributes
|
|
|
|
for ( key in attributes ) {
|
|
|
|
if (
|
|
|
|
!( key in ourAttributes ) ||
|
|
|
|
( strict ?
|
|
|
|
attributes[key] !== ourAttributes[key] :
|
|
|
|
String( attributes[key] ) !== String( ourAttributes[key] )
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if ( ve.isArray( attributes ) ) {
|
|
|
|
for ( i = 0, len = attributes.length; i < len; i++ ) {
|
|
|
|
if ( !( attributes[i] in ourAttributes ) ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a clone of the model's linear model element.
|
|
|
|
*
|
|
|
|
* The attributes object will be deep-copied.
|
|
|
|
*
|
|
|
|
* @returns {Object} Cloned element object
|
|
|
|
*/
|
|
|
|
ve.dm.Model.prototype.getClonedElement = function () {
|
2013-07-28 20:51:32 +00:00
|
|
|
return ve.copy( this.element );
|
2013-04-02 18:28:42 +00:00
|
|
|
};
|
2013-07-17 02:00:18 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the hash object of the linear model element.
|
|
|
|
*
|
|
|
|
* The actual logic is in a static function as this needs
|
|
|
|
* to be accessible from ve.dm.Converter
|
|
|
|
*
|
2013-10-15 19:59:14 +00:00
|
|
|
* This is a custom hash function for oo#getHash.
|
2013-07-17 02:00:18 +00:00
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @returns {Object} Hash object
|
|
|
|
*/
|
|
|
|
ve.dm.Model.prototype.getHashObject = function () {
|
|
|
|
return this.constructor.static.getHashObject( this.element );
|
|
|
|
};
|