1
YUI.add('console-filters', function (Y, NAME) {
4
* <p>Provides Plugin.ConsoleFilters plugin class.</p>
6
* <p>This plugin adds the ability to control which Console entries display by filtering on category and source. Two groups of checkboxes are added to the Console footer, one for categories and the other for sources. Only those messages that match a checked category or source are displayed.</p>
8
* @module console-filters
10
* @class ConsoleFilters
13
// Some common strings and functions
14
var getCN = Y.ClassNameManager.getClassName,
18
CATEGORY = 'category',
20
CATEGORY_DOT = 'category.',
21
SOURCE_DOT = 'source.',
25
DEF_VISIBILITY = 'defaultVisibility',
30
C_BODY = DOT + Y.Console.CHROME_CLASSES.console_bd_class,
31
C_FOOT = DOT + Y.Console.CHROME_CLASSES.console_ft_class,
33
SEL_CHECK = 'input[type=checkbox].',
35
isString = Y.Lang.isString;
37
function ConsoleFilters() {
38
ConsoleFilters.superclass.constructor.apply(this,arguments);
41
Y.namespace('Plugin').ConsoleFilters = Y.extend(ConsoleFilters, Y.Plugin.Base,
43
// Y.Plugin.ConsoleFilters prototype
46
* Collection of all log messages passed through since the plugin's
47
* instantiation. This holds all messages regardless of filter status.
48
* Used as a single source of truth for repopulating the Console body when
49
* filters are changed.
58
* Maximum number of entries to store in the message cache.
60
* @property _cacheLimit
65
_cacheLimit : Number.POSITIVE_INFINITY,
68
* The container node created to house the category filters.
70
* @property _categories
77
* The container node created to house the source filters.
86
* Initialize entries collection and attach listeners to host events and
92
initializer : function () {
95
this.get(HOST).on("entry", this._onEntry, this);
97
this.doAfter("renderUI", this.renderUI);
98
this.doAfter("syncUI", this.syncUI);
99
this.doAfter("bindUI", this.bindUI);
101
this.doAfter("clearConsole", this._afterClearConsole);
103
if (this.get(HOST).get('rendered')) {
109
this.after("cacheLimitChange", this._afterCacheLimitChange);
113
* Removes the plugin UI and unwires events.
118
destructor : function () {
119
//TODO: grab last {consoleLimit} entries and update the console with
120
//them (no filtering)
123
if (this._categories) {
124
this._categories.remove();
127
this._sources.remove();
132
* Adds the category and source filter sections to the Console footer.
137
renderUI : function () {
138
var foot = this.get(HOST).get('contentBox').one(C_FOOT),
143
ConsoleFilters.CATEGORIES_TEMPLATE,
144
ConsoleFilters.CHROME_CLASSES);
146
this._categories = foot.appendChild(Y.Node.create(html));
149
ConsoleFilters.SOURCES_TEMPLATE,
150
ConsoleFilters.CHROME_CLASSES);
152
this._sources = foot.appendChild(Y.Node.create(html));
157
* Binds to checkbox click events and internal attribute change events to
158
* maintain the UI state.
163
bindUI : function () {
164
this._categories.on('click', Y.bind(this._onCategoryCheckboxClick, this));
166
this._sources.on('click', Y.bind(this._onSourceCheckboxClick, this));
168
this.after('categoryChange',this._afterCategoryChange);
169
this.after('sourceChange', this._afterSourceChange);
173
* Updates the UI to be in accordance with the current state of the plugin.
177
syncUI : function () {
178
Y.each(this.get(CATEGORY), function (v, k) {
179
this._uiSetCheckbox(CATEGORY, k, v);
182
Y.each(this.get(SOURCE), function (v, k) {
183
this._uiSetCheckbox(SOURCE, k, v);
186
this.refreshConsole();
190
* Ensures a filter is set up for any new categories or sources and
191
* collects the messages in _entries. If the message is stamped with a
192
* category or source that is currently being filtered out, the message
193
* will not pass to the Console's print buffer.
196
* @param e {Event} the custom event object
199
_onEntry : function (e) {
200
this._entries.push(e.message);
202
var cat = CATEGORY_DOT + e.message.category,
203
src = SOURCE_DOT + e.message.source,
204
cat_filter = this.get(cat),
205
src_filter = this.get(src),
206
overLimit = this._entries.length - this._cacheLimit,
210
this._entries.splice(0, overLimit);
213
if (cat_filter === undefined) {
214
visible = this.get(DEF_VISIBILITY);
215
this.set(cat, visible);
216
cat_filter = visible;
219
if (src_filter === undefined) {
220
visible = this.get(DEF_VISIBILITY);
221
this.set(src, visible);
222
src_filter = visible;
225
if (!cat_filter || !src_filter) {
231
* Flushes the cached entries after a call to the Console's clearConsole().
233
* @method _afterClearConsole
236
_afterClearConsole : function () {
241
* Triggers the Console to update if a known category filter
242
* changes value (e.g. visible => hidden). Updates the appropriate
243
* checkbox's checked state if necessary.
245
* @method _afterCategoryChange
246
* @param e {Event} the attribute change event object
249
_afterCategoryChange : function (e) {
250
var cat = e.subAttrName.replace(/category\./, EMPTY),
254
// Don't update the console for new categories
255
if (!cat || before[cat] !== undefined) {
256
this.refreshConsole();
258
this._filterBuffer();
261
if (cat && !e.fromUI) {
262
this._uiSetCheckbox(CATEGORY, cat, after[cat]);
267
* Triggers the Console to update if a known source filter
268
* changes value (e.g. visible => hidden). Updates the appropriate
269
* checkbox's checked state if necessary.
271
* @method _afterSourceChange
272
* @param e {Event} the attribute change event object
275
_afterSourceChange : function (e) {
276
var src = e.subAttrName.replace(/source\./, EMPTY),
280
// Don't update the console for new sources
281
if (!src || before[src] !== undefined) {
282
this.refreshConsole();
284
this._filterBuffer();
287
if (src && !e.fromUI) {
288
this._uiSetCheckbox(SOURCE, src, after[src]);
293
* Flushes the Console's print buffer of any entries that have a category
294
* or source that is currently being excluded.
296
* @method _filterBuffer
299
_filterBuffer : function () {
300
var cats = this.get(CATEGORY),
301
srcs = this.get(SOURCE),
302
buffer = this.get(HOST).buffer,
306
for (i = buffer.length - 1; i >= 0; --i) {
307
if (!cats[buffer[i].category] || !srcs[buffer[i].source]) {
310
buffer.splice(i,(start - i));
315
buffer.splice(0,start + 1);
320
* Trims the cache of entries to the appropriate new length.
322
* @method _afterCacheLimitChange
323
* @param e {Event} the attribute change event object
326
_afterCacheLimitChange : function (e) {
327
if (isFinite(e.newVal)) {
328
var delta = this._entries.length - e.newVal;
331
this._entries.splice(0,delta);
337
* Repopulates the Console with entries appropriate to the current filter
340
* @method refreshConsole
342
refreshConsole : function () {
343
var entries = this._entries,
344
host = this.get(HOST),
345
body = host.get('contentBox').one(C_BODY),
346
remaining = host.get('consoleLimit'),
347
cats = this.get(CATEGORY),
348
srcs = this.get(SOURCE),
353
host._cancelPrintLoop();
355
// Evaluate all entries from latest to oldest
356
for (i = entries.length - 1; i >= 0 && remaining >= 0; --i) {
358
if (cats[e.category] && srcs[e.source]) {
365
host.buffer = buffer;
371
* Updates the checked property of a filter checkbox of the specified type.
372
* If no checkbox is found for the input params, one is created.
374
* @method _uiSetCheckbox
375
* @param type {String} 'category' or 'source'
376
* @param item {String} the name of the filter (e.g. 'info', 'event')
377
* @param checked {Boolean} value to set the checkbox's checked property
380
_uiSetCheckbox : function (type, item, checked) {
382
var container = type === CATEGORY ?
385
sel = SEL_CHECK + getCN(CONSOLE,FILTER,item),
386
checkbox = container.one(sel),
390
host = this.get(HOST);
392
this._createCheckbox(container, item);
394
checkbox = container.one(sel);
396
host._uiSetHeight(host.get('height'));
399
checkbox.set(CHECKED, checked);
404
* Passes checkbox clicks on to the category attribute.
406
* @method _onCategoryCheckboxClick
407
* @param e {Event} the DOM event
410
_onCategoryCheckboxClick : function (e) {
411
var t = e.target, cat;
413
if (t.hasClass(ConsoleFilters.CHROME_CLASSES.filter)) {
414
cat = t.get('value');
415
if (cat && cat in this.get(CATEGORY)) {
416
this.set(CATEGORY_DOT + cat, t.get(CHECKED), { fromUI: true });
422
* Passes checkbox clicks on to the source attribute.
424
* @method _onSourceCheckboxClick
425
* @param e {Event} the DOM event
428
_onSourceCheckboxClick : function (e) {
429
var t = e.target, src;
431
if (t.hasClass(ConsoleFilters.CHROME_CLASSES.filter)) {
432
src = t.get('value');
433
if (src && src in this.get(SOURCE)) {
434
this.set(SOURCE_DOT + src, t.get(CHECKED), { fromUI: true });
440
* Hides any number of categories from the UI. Convenience method for
441
* myConsole.filter.set('category.foo', false); set('category.bar', false);
444
* @method hideCategory
445
* @param cat* {String} 1..n categories to filter out of the UI
447
hideCategory : function (cat, multiple) {
448
if (isString(multiple)) {
449
Y.Array.each(arguments, this.hideCategory, this);
451
this.set(CATEGORY_DOT + cat, false);
456
* Shows any number of categories in the UI. Convenience method for
457
* myConsole.filter.set('category.foo', true); set('category.bar', true);
460
* @method showCategory
461
* @param cat* {String} 1..n categories to allow to display in the UI
463
showCategory : function (cat, multiple) {
464
if (isString(multiple)) {
465
Y.Array.each(arguments, this.showCategory, this);
467
this.set(CATEGORY_DOT + cat, true);
472
* Hides any number of sources from the UI. Convenience method for
473
* myConsole.filter.set('source.foo', false); set('source.bar', false);
477
* @param src* {String} 1..n sources to filter out of the UI
479
hideSource : function (src, multiple) {
480
if (isString(multiple)) {
481
Y.Array.each(arguments, this.hideSource, this);
483
this.set(SOURCE_DOT + src, false);
488
* Shows any number of sources in the UI. Convenience method for
489
* myConsole.filter.set('source.foo', true); set('source.bar', true);
493
* @param src* {String} 1..n sources to allow to display in the UI
495
showSource : function (src, multiple) {
496
if (isString(multiple)) {
497
Y.Array.each(arguments, this.showSource, this);
499
this.set(SOURCE_DOT + src, true);
504
* Creates a checkbox and label from the ConsoleFilters.FILTER_TEMPLATE for
505
* the provided type and name. The checkbox and label are appended to the
506
* container node passes as the first arg.
508
* @method _createCheckbox
509
* @param container {Node} the parentNode of the new checkbox and label
510
* @param name {String} the identifier of the filter
513
_createCheckbox : function (container, name) {
514
var info = Y.merge(ConsoleFilters.CHROME_CLASSES, {
516
filter_class : getCN(CONSOLE, FILTER, name)
518
node = Y.Node.create(
519
Y.Lang.sub(ConsoleFilters.FILTER_TEMPLATE, info));
521
container.appendChild(node);
525
* Validates category updates are objects and the subattribute is not too
528
* @method _validateCategory
529
* @param cat {String} the new category:visibility map
530
* @param v {String} the subattribute path updated
534
_validateCategory : function (cat, v) {
535
return Y.Lang.isObject(v,true) && cat.split(/\./).length < 3;
539
* Validates source updates are objects and the subattribute is not too
542
* @method _validateSource
543
* @param cat {String} the new source:visibility map
544
* @param v {String} the subattribute path updated
548
_validateSource : function (src, v) {
549
return Y.Lang.isObject(v,true) && src.split(/\./).length < 3;
553
* Setter method for cacheLimit attribute. Basically a validator to ensure
556
* @method _setCacheLimit
557
* @param v {Number} Maximum number of entries
561
_setCacheLimit: function (v) {
562
if (Y.Lang.isNumber(v)) {
563
this._cacheLimit = v;
566
return Y.Attribute.INVALID_VALUE;
571
// Y.Plugin.ConsoleFilters static properties
579
* @default 'consoleFilters'
581
NAME : 'consoleFilters',
584
* The namespace hung off the host object that this plugin will inhabit.
594
* Markup template used to create the container for the category filters.
596
* @property CATEGORIES_TEMPLATE
600
CATEGORIES_TEMPLATE :
601
'<div class="{categories}"></div>',
604
* Markup template used to create the container for the source filters.
606
* @property SOURCES_TEMPLATE
611
'<div class="{sources}"></div>',
614
* Markup template used to create the category and source filter checkboxes.
616
* @property FILTER_TEMPLATE
621
// IE8 and FF3 don't permit breaking _between_ nowrap elements. IE8
622
// doesn't understand (non spec) wbr tag, nor does it create text nodes
623
// for spaces in innerHTML strings. The thin-space entity suffices to
624
// create a breakable point.
625
'<label class="{filter_label}">'+
626
'<input type="checkbox" value="{filter_name}" '+
627
'class="{filter} {filter_class}"> {filter_name}'+
631
* Classnames used by the templates when creating nodes.
633
* @property CHROME_CLASSES
639
categories : getCN(CONSOLE,FILTERS,'categories'),
640
sources : getCN(CONSOLE,FILTERS,'sources'),
641
category : getCN(CONSOLE,FILTER,CATEGORY),
642
source : getCN(CONSOLE,FILTER,SOURCE),
643
filter : getCN(CONSOLE,FILTER),
644
filter_label : getCN(CONSOLE,FILTER,'label')
649
* Default visibility applied to new categories and sources.
651
* @attribute defaultVisibility
655
defaultVisibility : {
657
validator : Y.Lang.isBoolean
661
* <p>Map of entry categories to their visibility status. Update a
662
* particular category's visibility by setting the subattribute to true
663
* (visible) or false (hidden).</p>
665
* <p>For example, yconsole.filter.set('category.info', false) to hide
666
* log entries with the category/logLevel of 'info'.</p>
668
* <p>Similarly, yconsole.filter.get('category.warn') will return a
669
* boolean indicating whether that category is currently being included
672
* <p>Unlike the YUI instance configuration's logInclude and logExclude
673
* properties, filtered entries are only hidden from the UI, but
674
* can be made visible again.</p>
676
* @attribute category
681
validator : function (v,k) {
682
return this._validateCategory(k,v);
687
* <p>Map of entry sources to their visibility status. Update a
688
* particular sources's visibility by setting the subattribute to true
689
* (visible) or false (hidden).</p>
691
* <p>For example, yconsole.filter.set('sources.slider', false) to hide
692
* log entries originating from Y.Slider.</p>
699
validator : function (v,k) {
700
return this._validateSource(k,v);
705
* Maximum number of entries to store in the message cache. Use this to
706
* limit the memory footprint in environments with heavy log usage.
707
* By default, there is no limit (Number.POSITIVE_INFINITY).
709
* @attribute cacheLimit
711
* @default Number.POSITIVE_INFINITY
714
value : Number.POSITIVE_INFINITY,
715
setter : function (v) {
716
return this._setCacheLimit(v);
723
}, '@VERSION@', {"requires": ["plugin", "console"], "skinnable": true});