Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F4131083
View.js
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
13 KB
Referenced Files
None
Subscribers
None
View.js
View Options
/* global $ */
const
util
=
require
(
'./util'
),
mfExtend
=
require
(
'./mfExtend'
),
// Cached regex to split keys for `delegate`.
delegateEventSplitter
=
/^(\S+)\s*(.*)$/
;
let
idCounter
=
0
;
/**
* Generate a unique integer id (unique within the entire client session).
* Useful for temporary DOM ids.
*
* @private
* @param {string} prefix Prefix to be used when generating the id.
* @return {string}
*/
function
uniqueId
(
prefix
)
{
const
id
=
(
++
idCounter
).
toString
();
return
prefix
?
prefix
+
id
:
id
;
}
/**
* Should be extended using extend().
*
* When options contains el property, this.$el in the constructed object
* will be set to the corresponding jQuery object. Otherwise, this.$el
* will be an empty div.
*
* When extended using extend(), if the extended prototype contains
* template property, this.$el will be filled with rendered template (with
* options parameter used as template data).
*
* template property can be a string which will be passed to mw.template.compile()
* or an object that has a render() function which accepts an object with
* template data as its argument (similarly to an object created by
* mw.template.compile()).
*
* You can also define a defaults property which should be an object
* containing default values for the template (if they're not present in
* the options parameter).
*
* If this.$el is not a jQuery object bound to existing DOM element, the
* view can be attached to an element using appendTo(), prependTo(),
* insertBefore(), insertAfter() proxy functions.
*
* append(), prepend(), before(), after() can be used to modify $el. on()
* can be used to bind events.
*
* You can also use declarative DOM events binding by specifying an `events`
* map on the class. The keys will be 'event selector' and the value can be
* either the name of a method to call, or a function. All methods and
* functions will be executed on the context of the View.
*
* Inspired from Backbone.js
* https://github.com/jashkenas/backbone/blob/master/backbone.js#L1128
*
* Example:
* ```js
* var
* MyComponent = View.extend( {
* edit: function ( ev ) {
* //...
* },
* save: function ( ev ) {
* //...
* }
* } ),
* instance = new MyComponent({
* events: {
* 'mousedown .title': 'edit',
* 'click .button': 'save',
* 'click .open': function(e) { ... }
* }
* });
* ```
*
* Example:
* ```js
* var View, section;
* function Section( options ) {
* var defaultOptions = {
* events: {
* // ...
* }
* }
* View.call( this, util.extends( {}, defaultOptions, options ) );
* }
* View = require( './View' );
* require( './mfExtend' )( Section, View, {
* template: mw.template.compile( "<h2>{{title}}</h2>" ),
* } );
* section = new Section( { title: 'Test', text: 'Test section body' } );
* section.appendTo( 'body' );
* ```
*
* @class module:mobile.startup/View
* @memberof module:mobile.startup/View
* @classdesc Describes a component for rendering.
* @mixes OO.EventEmitter
*/
function
View
()
{
this
.
initialize
.
apply
(
this
,
arguments
);
}
OO
.
mixinClass
(
View
,
OO
.
EventEmitter
);
mfExtend
(
View
,
{
/**
* Name of tag that contains the rendered template
*
* @memberof module:mobile.startup/View
* @instance
* @property {string} tagName
*/
tagName
:
'div'
,
/**
* Tells the View to ignore tagName and className when constructing the element
* and to rely solely on the template
*
* @memberof module:mobile.startup/View
* @instance
* @property {boolean} isTemplateMode
*/
isTemplateMode
:
false
,
/**
* @memberof module:mobile.startup/View
* @instance
* @property {any}
* Specifies the template used in render(). Object|string
*/
template
:
undefined
,
/**
* Specifies partials (sub-templates) for the main template. Example:
*
* @example
* // example content for the "some" template (sub-template will be
* // inserted where {{>content}} is):
* // <h1>Heading</h1>
* // {{>content}}
*
* oo.mfExtend( SomeView, View, {
* template: util.template( '<source-code>' ),
* templatePartials: { content: util.template( '<source-code>' ) }
* }
*
* @memberof module:mobile.startup/View
* @instance
* @property {Object}
*/
templatePartials
:
{},
/**
* A set of default options that are merged with options passed into the initialize
* function.
*
* @memberof module:mobile.startup/View
* @instance
* @property {Object} defaults Default options hash.
* @property {jQuery.Object|string} [defaults.el] jQuery selector to use for rendering.
* @property {boolean} [defaults.skipTemplateRender] Whether to enhance views already in
* DOM. When enabled, the template is disabled so that it is not rendered in the DOM.
* Use in conjunction with View::defaults.$el to associate the View with an existing
* already rendered element in the DOM.
*/
defaults
:
{},
/**
* Run once during construction to set up the View
*
* @memberof module:mobile.startup/View
* @instance
* @param {Object} options Object passed to the constructor.
* @param {Object.<string, string>} [options.events]
*/
initialize
(
options
)
{
const
self
=
this
;
OO
.
EventEmitter
.
call
(
this
);
options
=
util
.
extend
(
{},
this
.
defaults
,
options
);
this
.
options
=
options
;
// Assign a unique id for dom events binding/unbinding
this
.
cid
=
uniqueId
(
'view'
);
// TODO: if template compilation is too slow, don't compile them on a
// per object basis, but don't worry about it now (maybe add cache to
// M.template.compile())
if
(
typeof
this
.
template
===
'string'
)
{
this
.
template
=
mw
.
template
.
compile
(
this
.
template
);
}
if
(
options
.
el
)
{
// Note the element may not be in the document so must use global jQuery here
this
.
$el
=
$
(
options
.
el
);
}
else
{
this
.
$el
=
this
.
parseHTML
(
'<'
+
this
.
tagName
+
'>'
);
}
// Make sure the element is ready to be manipulated
if
(
this
.
$el
.
length
)
{
this
.
_postInitialize
(
options
);
}
else
{
util
.
docReady
(
()
=>
{
// Note the element may not be in the document so must use global jQuery here
self
.
$el
=
$
(
options
.
el
);
self
.
_postInitialize
(
options
);
}
);
}
},
/**
* Called when this.$el is ready.
*
* @memberof module:mobile.startup/View
* @instance
* @private
* @param {Object} props
*/
_postInitialize
(
props
)
{
// eslint-disable-next-line mediawiki/class-doc
this
.
$el
.
addClass
(
props
.
className
);
// border-box will be added provided this flag is not set
if
(
props
.
isBorderBox
!==
false
)
{
this
.
$el
.
addClass
(
'view-border-box'
);
}
this
.
render
(
{}
);
},
/**
* Function called before the view is rendered. Can be redefined in
* objects that extend View.
*
* @memberof module:mobile.startup/View
* @instance
*/
preRender
()
{
},
/**
* Function called after the view is rendered. Can be redefined in
* objects that extend View.
*
* @memberof module:mobile.startup/View
* @instance
*/
postRender
()
{
},
/**
* Fill this.$el with template rendered using data if template is set.
*
* @memberof module:mobile.startup/View
* @instance
* @param {Object} data Template data. Will be merged into the view's
* options
* @chainable
*/
render
(
data
)
{
let
$el
,
html
;
util
.
extend
(
this
.
options
,
data
);
this
.
preRender
();
this
.
undelegateEvents
();
if
(
this
.
template
&&
!
this
.
options
.
skipTemplateRender
)
{
html
=
this
.
template
.
render
(
this
.
options
,
this
.
templatePartials
);
if
(
this
.
isTemplateMode
)
{
$el
=
$
(
html
);
this
.
$el
.
replaceWith
(
$el
);
this
.
$el
=
$el
;
}
else
{
this
.
$el
.
html
(
html
);
}
}
this
.
postRender
();
this
.
delegateEvents
();
return
this
;
},
/**
* Set callbacks for events.
*
* `this.options.events` is a hash of pairs:
*
* ```
* { 'event selector': 'callback' }
*
* {
* 'mousedown .title': 'edit',
* 'click .button': 'save',
* 'click .open': function(e) { ... }
* }
* ```
*
* Callbacks will be bound to the view, with `this` set properly.
* Uses event delegation for efficiency.
* Omitting the selector binds the event to `this.el`.
*
* @memberof module:mobile.startup/View
* @instance
* @param {Object} events Optionally set this events instead of the ones on this.
*/
delegateEvents
(
events
)
{
let
match
,
key
,
method
;
events
=
events
||
this
.
options
.
events
;
if
(
events
)
{
// Remove current events before re-binding them
this
.
undelegateEvents
();
for
(
key
in
events
)
{
method
=
events
[
key
];
// If the method is a string name of this.method, get it
if
(
typeof
method
!==
'function'
)
{
method
=
this
[
events
[
key
]];
}
if
(
method
)
{
// Extract event and selector from the key
match
=
key
.
match
(
delegateEventSplitter
);
this
.
delegate
(
match
[
1
],
match
[
2
],
method
.
bind
(
this
)
);
}
}
}
},
/**
* Add a single event listener to the view's element (or a child element
* using `selector`). This only works for delegate-able events: not `focus`
* or `blur`.
*
* @memberof module:mobile.startup/View
* @instance
* @param {string} eventName
* @param {string} selector
* @param {Function} listener
*/
delegate
(
eventName
,
selector
,
listener
)
{
this
.
$el
.
on
(
eventName
+
'.delegateEvents'
+
this
.
cid
,
selector
,
listener
);
},
/**
* Clears all callbacks previously bound to the view by `delegateEvents`.
* You usually don't need to use this, but may wish to if you have multiple
* views attached to the same DOM element.
*
* @memberof module:mobile.startup/View
* @instance
*/
undelegateEvents
()
{
if
(
this
.
$el
)
{
this
.
$el
.
off
(
'.delegateEvents'
+
this
.
cid
);
}
},
/**
* A finer-grained `undelegateEvents` for removing a single delegated event.
* `selector` and `listener` are both optional.
*
* @memberof module:mobile.startup/View
* @instance
* @param {string} eventName
* @param {string} selector
* @param {Function} listener
*/
undelegate
(
eventName
,
selector
,
listener
)
{
this
.
$el
.
off
(
eventName
+
'.delegateEvents'
+
this
.
cid
,
selector
,
listener
);
},
/**
* See parseHTML method of util singleton
*
* @memberof module:mobile.startup/View
* @instance
* @param {string} html to turn into a jQuery object.
* @return {jQuery.Object}
*/
parseHTML
(
html
)
{
// document is explicitly passed due to a bug we found in Safari 11.1.2 where failure
// to use document resulted in an element without access to the documentElement
// this should be redundant, but no problem in being explicit (T214451).
return
util
.
parseHTML
(
html
,
document
);
}
}
);
/**
* @memberof View
* @instance
* @function append
* @param {...(string|Node|Node[]|jQuery)} contents
* @return {View}
*/
/**
* @memberof View
* @instance
* @function append
* @param {function(number, string): string|Node|Node[]|jQuery} contents
* @return {View}
*/
/**
* @memberof View
* @instance
* @function prepend
* @param {...(string|Node|Node[]|jQuery)} contents
* @return {View}
*/
/**
* @memberof View
* @instance
* @function prepend
* @param {function(number, string): string|Node|Node[]|jQuery} contents
* @return {View}
*/
/**
* @memberof View
* @instance
* @function appendTo
* @param {string|Node|Node[]|jQuery} target
* @return {View}
*/
/**
* @memberof View
* @instance
* @function prependTo
* @param {string|Node|Node[]|jQuery} target
* @return {View}
*/
/**
* @memberof View
* @instance
* @function after
* @param {...(string|Node|Node[]|jQuery)} contents
* @return {View}
*/
/**
* @memberof View
* @instance
* @function after
* @param {function(number, string): string|Node|Node[]|jQuery} contents
* @return {View}
*/
/**
* @memberof View
* @instance
* @functiontion before
* @param {...(string|Node|Node[]|jQuery)} contents
* @return {View}
*/
/**
* @memberof View
* @instance
* @function before
* @param {function(number, string): string|Node|Node[]|jQuery} contents
* @return {View}
*/
/**
* @memberof View
* @instance
* @property {jQuery.Object} $el
*/
/**
* @memberof View
* @instance
* @function insertAfter
* @param {string|Node|Node[]|jQuery} target
* @return {View}
*/
/**
* @memberof View
* @instance
* @function insertBefore
* @param {string|Node|Node[]|jQuery} target
* @return {View}
*/
/**
* @memberof View
* @instance
* @function remove
* @param {string} [selector]
* @return {View}
*/
/**
* @memberof View
* @instance
* @function detach
* @param {string} [selector]
* @return {View}
*/
[
'append'
,
'prepend'
,
'appendTo'
,
'prependTo'
,
'after'
,
'before'
,
'insertAfter'
,
'insertBefore'
,
'remove'
,
'detach'
].
forEach
(
(
prop
)
=>
{
View
.
prototype
[
prop
]
=
function
()
{
this
.
$el
[
prop
].
apply
(
this
.
$el
,
arguments
);
return
this
;
};
}
);
/**
* Generates a view with children
*
* @param {Object} options
* @param {jQuery.Element[]} children
* @return {module:mobile.startup/View}
*/
View
.
make
=
function
(
options
=
{},
children
=
[]
)
{
const
view
=
new
View
(
options
);
children
.
forEach
(
(
$child
)
=>
view
.
append
(
$child
)
);
return
view
;
};
module
.
exports
=
View
;
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Wed, Aug 19, 10:42 (2 w, 6 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
b1/ec/5c87c93295dd39e54863809dd71d
Default Alt Text
View.js (13 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment