MDL-32843 import YUI 3.5.1
[moodle.git] / lib / yui / 3.5.1 / build / console-filters / console-filters-debug.js
blobb1a25fa79d8161bb9db36a37ae6e200b5430317b
1 /*
2 YUI 3.5.1 (build 22)
3 Copyright 2012 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
6 */
7 YUI.add('console-filters', function(Y) {
9 /**
10  * <p>Provides Plugin.ConsoleFilters plugin class.</p>
11  *
12  * <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>
13  *
14  * @module console-filters
15  * @namespace Plugin
16  * @class ConsoleFilters
17  */
19 // Some common strings and functions
20 var getCN = Y.ClassNameManager.getClassName,
21     CONSOLE = 'console',
22     FILTERS = 'filters',
23     FILTER  = 'filter',
24     CATEGORY = 'category',
25     SOURCE   = 'source',
26     CATEGORY_DOT = 'category.',
27     SOURCE_DOT   = 'source.',
29     HOST     = 'host',
30     CHECKED  = 'checked',
31     DEF_VISIBILITY = 'defaultVisibility',
33     DOT = '.',
34     EMPTY   = '',
36     C_BODY       = DOT + Y.Console.CHROME_CLASSES.console_bd_class,
37     C_FOOT       = DOT + Y.Console.CHROME_CLASSES.console_ft_class,
39     SEL_CHECK    = 'input[type=checkbox].',
40     
41     isString = Y.Lang.isString;
43 function ConsoleFilters() {
44     ConsoleFilters.superclass.constructor.apply(this,arguments);
47 Y.namespace('Plugin').ConsoleFilters = Y.extend(ConsoleFilters, Y.Plugin.Base,
49 // Y.Plugin.ConsoleFilters prototype
51     /**
52      * Collection of all log messages passed through since the plugin's
53      * instantiation.  This holds all messages regardless of filter status.
54      * Used as a single source of truth for repopulating the Console body when
55      * filters are changed.
56      *
57      * @property _entries
58      * @type Array
59      * @protected
60      */
61     _entries : null,
63     /**
64      * Maximum number of entries to store in the message cache.
65      *
66      * @property _cacheLimit
67      * @type {Number}
68      * @default Infinity
69      * @protected
70      */
71     _cacheLimit : Number.POSITIVE_INFINITY,
73     /**
74      * The container node created to house the category filters.
75      *
76      * @property _categories
77      * @type Node
78      * @protected
79      */
80     _categories : null,
82     /**
83      * The container node created to house the source filters.
84      *
85      * @property _sources
86      * @type Node
87      * @protected
88      */
89     _sources : null,
91     /**
92      * Initialize entries collection and attach listeners to host events and
93      * methods.
94      *
95      * @method initializer
96      * @protected
97      */
98     initializer : function () {
99         this._entries = [];
101         this.get(HOST).on("entry", this._onEntry, this);
103         this.doAfter("renderUI", this.renderUI);
104         this.doAfter("syncUI", this.syncUI);
105         this.doAfter("bindUI", this.bindUI);
107         this.doAfter("clearConsole", this._afterClearConsole);
109         if (this.get(HOST).get('rendered')) {
110             this.renderUI();
111             this.syncUI();
112             this.bindUI();
113         }
115         this.after("cacheLimitChange", this._afterCacheLimitChange);
116     },
118     /**
119      * Removes the plugin UI and unwires events.
120      *
121      * @method destructor
122      * @protected
123      */
124     destructor : function () {
125         //TODO: grab last {consoleLimit} entries and update the console with
126         //them (no filtering)
127         this._entries = [];
129         if (this._categories) {
130             this._categories.remove();
131         }
132         if (this._sources) {
133             this._sources.remove();
134         }
135     },
137     /**
138      * Adds the category and source filter sections to the Console footer.
139      *
140      * @method renderUI
141      * @protected
142      */
143     renderUI : function () {
144         var foot = this.get(HOST).get('contentBox').one(C_FOOT),
145             html;
147         if (foot) {
148             html = Y.substitute(
149                         ConsoleFilters.CATEGORIES_TEMPLATE,
150                         ConsoleFilters.CHROME_CLASSES);
152             this._categories = foot.appendChild(Y.Node.create(html));
154             html = Y.substitute(
155                         ConsoleFilters.SOURCES_TEMPLATE,
156                         ConsoleFilters.CHROME_CLASSES);
158             this._sources = foot.appendChild(Y.Node.create(html));
159         }
160     },
162     /**
163      * Binds to checkbox click events and internal attribute change events to
164      * maintain the UI state.
165      *
166      * @method bindUI
167      * @protected
168      */
169     bindUI : function () {
170         this._categories.on('click', Y.bind(this._onCategoryCheckboxClick, this));
172         this._sources.on('click', Y.bind(this._onSourceCheckboxClick, this));
173             
174         this.after('categoryChange',this._afterCategoryChange);
175         this.after('sourceChange',  this._afterSourceChange);
176     },
178     /**
179      * Updates the UI to be in accordance with the current state of the plugin.
180      *
181      * @method syncUI
182      */
183     syncUI : function () {
184         Y.each(this.get(CATEGORY), function (v, k) {
185             this._uiSetCheckbox(CATEGORY, k, v);
186         }, this);
188         Y.each(this.get(SOURCE), function (v, k) {
189             this._uiSetCheckbox(SOURCE, k, v);
190         }, this);
192         this.refreshConsole();
193     },
195     /**
196      * Ensures a filter is set up for any new categories or sources and
197      * collects the messages in _entries.  If the message is stamped with a
198      * category or source that is currently being filtered out, the message
199      * will not pass to the Console's print buffer.
200      *
201      * @method _onEntry
202      * @param e {Event} the custom event object
203      * @protected
204      */
205     _onEntry : function (e) {
206         this._entries.push(e.message);
207         
208         var cat = CATEGORY_DOT + e.message.category,
209             src = SOURCE_DOT + e.message.source,
210             cat_filter = this.get(cat),
211             src_filter = this.get(src),
212             overLimit  = this._entries.length - this._cacheLimit,
213             visible;
215         if (overLimit > 0) {
216             this._entries.splice(0, overLimit);
217         }
219         if (cat_filter === undefined) {
220             visible = this.get(DEF_VISIBILITY);
221             this.set(cat, visible);
222             cat_filter = visible;
223         }
225         if (src_filter === undefined) {
226             visible = this.get(DEF_VISIBILITY);
227             this.set(src, visible);
228             src_filter = visible;
229         }
230         
231         if (!cat_filter || !src_filter) {
232             e.preventDefault();
233         }
234     },
236     /**
237      * Flushes the cached entries after a call to the Console's clearConsole().
238      *
239      * @method _afterClearConsole
240      * @protected
241      */
242     _afterClearConsole : function () {
243         this._entries = [];
244     },
246     /**
247      * Triggers the Console to update if a known category filter
248      * changes value (e.g. visible => hidden).  Updates the appropriate
249      * checkbox's checked state if necessary.
250      *
251      * @method _afterCategoryChange
252      * @param e {Event} the attribute change event object
253      * @protected
254      */
255     _afterCategoryChange : function (e) {
256         var cat    = e.subAttrName.replace(/category\./, EMPTY),
257             before = e.prevVal,
258             after  = e.newVal;
260         // Don't update the console for new categories
261         if (!cat || before[cat] !== undefined) {
262             this.refreshConsole();
264             this._filterBuffer();
265         }
267         if (cat && !e.fromUI) {
268             this._uiSetCheckbox(CATEGORY, cat, after[cat]);
269         }
270     },
272     /**
273      * Triggers the Console to update if a known source filter
274      * changes value (e.g. visible => hidden).  Updates the appropriate
275      * checkbox's checked state if necessary.
276      *
277      * @method _afterSourceChange
278      * @param e {Event} the attribute change event object
279      * @protected
280      */
281     _afterSourceChange : function (e) {
282         var src     = e.subAttrName.replace(/source\./, EMPTY),
283             before = e.prevVal,
284             after  = e.newVal;
286         // Don't update the console for new sources
287         if (!src || before[src] !== undefined) {
288             this.refreshConsole();
290             this._filterBuffer();
291         }
293         if (src && !e.fromUI) {
294             this._uiSetCheckbox(SOURCE, src, after[src]);
295         }
296     },
298     /**
299      * Flushes the Console's print buffer of any entries that have a category
300      * or source that is currently being excluded.
301      *
302      * @method _filterBuffer
303      * @protected
304      */
305     _filterBuffer : function () {
306         var cats = this.get(CATEGORY),
307             srcs = this.get(SOURCE),
308             buffer = this.get(HOST).buffer,
309             start = null,
310             i;
312         for (i = buffer.length - 1; i >= 0; --i) {
313             if (!cats[buffer[i].category] || !srcs[buffer[i].source]) {
314                 start = start || i;
315             } else if (start) {
316                 buffer.splice(i,(start - i));
317                 start = null;
318             }
319         }
320         if (start) {
321             buffer.splice(0,start + 1);
322         }
323     },
325     /**
326      * Trims the cache of entries to the appropriate new length.
327      *
328      * @method _afterCacheLimitChange 
329      * @param e {Event} the attribute change event object
330      * @protected
331      */
332     _afterCacheLimitChange : function (e) {
333         if (isFinite(e.newVal)) {
334             var delta = this._entries.length - e.newVal;
336             if (delta > 0) {
337                 this._entries.splice(0,delta);
338             }
339         }
340     },
342     /**
343      * Repopulates the Console with entries appropriate to the current filter
344      * settings.
345      *
346      * @method refreshConsole
347      */
348     refreshConsole : function () {
349         var entries   = this._entries,
350             host      = this.get(HOST),
351             body      = host.get('contentBox').one(C_BODY),
352             remaining = host.get('consoleLimit'),
353             cats      = this.get(CATEGORY),
354             srcs      = this.get(SOURCE),
355             buffer    = [],
356             i,e;
358         if (body) {
359             host._cancelPrintLoop();
361             // Evaluate all entries from latest to oldest
362             for (i = entries.length - 1; i >= 0 && remaining >= 0; --i) {
363                 e = entries[i];
364                 if (cats[e.category] && srcs[e.source]) {
365                     buffer.unshift(e);
366                     --remaining;
367                 }
368             }
370             body.setContent(EMPTY);
371             host.buffer = buffer;
372             host.printBuffer();
373         }
374     },
376     /**
377      * Updates the checked property of a filter checkbox of the specified type.
378      * If no checkbox is found for the input params, one is created.
379      *
380      * @method _uiSetCheckbox
381      * @param type {String} 'category' or 'source'
382      * @param item {String} the name of the filter (e.g. 'info', 'event')
383      * @param checked {Boolean} value to set the checkbox's checked property
384      * @protected
385      */
386     _uiSetCheckbox : function (type, item, checked) {
387         if (type && item) {
388             var container = type === CATEGORY ?
389                                 this._categories :
390                                 this._sources,
391                 sel      = SEL_CHECK + getCN(CONSOLE,FILTER,item),
392                 checkbox = container.one(sel),
393                 host;
394                 
395             if (!checkbox) {
396                 host = this.get(HOST);
398                 this._createCheckbox(container, item);
400                 checkbox = container.one(sel);
402                 host._uiSetHeight(host.get('height'));
403             }
404             
405             checkbox.set(CHECKED, checked);
406         }
407     },
409     /**
410      * Passes checkbox clicks on to the category attribute.
411      *
412      * @method _onCategoryCheckboxClick
413      * @param e {Event} the DOM event
414      * @protected
415      */
416     _onCategoryCheckboxClick : function (e) {
417         var t = e.target, cat;
419         if (t.hasClass(ConsoleFilters.CHROME_CLASSES.filter)) {
420             cat = t.get('value');
421             if (cat && cat in this.get(CATEGORY)) {
422                 this.set(CATEGORY_DOT + cat, t.get(CHECKED), { fromUI: true });
423             }
424         }
425     },
427     /**
428      * Passes checkbox clicks on to the source attribute.
429      *
430      * @method _onSourceCheckboxClick
431      * @param e {Event} the DOM event
432      * @protected
433      */
434     _onSourceCheckboxClick : function (e) {
435         var t = e.target, src;
437         if (t.hasClass(ConsoleFilters.CHROME_CLASSES.filter)) {
438             src = t.get('value');
439             if (src && src in this.get(SOURCE)) {
440                 this.set(SOURCE_DOT + src, t.get(CHECKED), { fromUI: true });
441             }
442         }
443     },
445     /**
446      * Hides any number of categories from the UI.  Convenience method for
447      * myConsole.filter.set('category.foo', false); set('category.bar', false);
448      * and so on.
449      *
450      * @method hideCategory
451      * @param cat* {String} 1..n categories to filter out of the UI
452      */
453     hideCategory : function (cat, multiple) {
454         if (isString(multiple)) {
455             Y.Array.each(arguments, this.hideCategory, this);
456         } else {
457             this.set(CATEGORY_DOT + cat, false);
458         }
459     },
461     /**
462      * Shows any number of categories in the UI.  Convenience method for
463      * myConsole.filter.set('category.foo', true); set('category.bar', true);
464      * and so on.
465      *
466      * @method showCategory
467      * @param cat* {String} 1..n categories to allow to display in the UI
468      */
469     showCategory : function (cat, multiple) {
470         if (isString(multiple)) {
471             Y.Array.each(arguments, this.showCategory, this);
472         } else {
473             this.set(CATEGORY_DOT + cat, true);
474         }
475     },
477     /**
478      * Hides any number of sources from the UI.  Convenience method for
479      * myConsole.filter.set('source.foo', false); set('source.bar', false);
480      * and so on.
481      *
482      * @method hideSource
483      * @param src* {String} 1..n sources to filter out of the UI
484      */
485     hideSource : function (src, multiple) {
486         if (isString(multiple)) {
487             Y.Array.each(arguments, this.hideSource, this);
488         } else {
489             this.set(SOURCE_DOT + src, false);
490         }
491     },
493     /**
494      * Shows any number of sources in the UI.  Convenience method for
495      * myConsole.filter.set('source.foo', true); set('source.bar', true);
496      * and so on.
497      *
498      * @method showSource
499      * @param src* {String} 1..n sources to allow to display in the UI
500      */
501     showSource : function (src, multiple) {
502         if (isString(multiple)) {
503             Y.Array.each(arguments, this.showSource, this);
504         } else {
505             this.set(SOURCE_DOT + src, true);
506         }
507     },
509     /**
510      * Creates a checkbox and label from the ConsoleFilters.FILTER_TEMPLATE for
511      * the provided type and name.  The checkbox and label are appended to the
512      * container node passes as the first arg.
513      *
514      * @method _createCheckbox
515      * @param container {Node} the parentNode of the new checkbox and label
516      * @param name {String} the identifier of the filter
517      * @protected
518      */
519     _createCheckbox : function (container, name) {
520         var info = Y.merge(ConsoleFilters.CHROME_CLASSES, {
521                         filter_name  : name,
522                         filter_class : getCN(CONSOLE, FILTER, name)
523                    }),
524             node = Y.Node.create(
525                         Y.substitute(ConsoleFilters.FILTER_TEMPLATE, info));
527         container.appendChild(node);
528     },
530     /**
531      * Validates category updates are objects and the subattribute is not too
532      * deep.
533      *
534      * @method _validateCategory
535      * @param cat {String} the new category:visibility map
536      * @param v {String} the subattribute path updated
537      * @return Boolean
538      * @protected
539      */
540     _validateCategory : function (cat, v) {
541         return Y.Lang.isObject(v,true) && cat.split(/\./).length < 3;
542     },
544     /**
545      * Validates source updates are objects and the subattribute is not too
546      * deep.
547      *
548      * @method _validateSource
549      * @param cat {String} the new source:visibility map
550      * @param v {String} the subattribute path updated
551      * @return Boolean
552      * @protected
553      */
554     _validateSource : function (src, v) {
555         return Y.Lang.isObject(v,true) && src.split(/\./).length < 3;
556     },
558     /**
559      * Setter method for cacheLimit attribute.  Basically a validator to ensure
560      * numeric input.
561      *
562      * @method _setCacheLimit
563      * @param v {Number} Maximum number of entries
564      * @return {Number}
565      * @protected
566      */
567     _setCacheLimit: function (v) {
568         if (Y.Lang.isNumber(v)) {
569             this._cacheLimit = v;
570             return v;
571         } else {
572             return Y.Attribute.INVALID_VALUE;
573         }
574     }
577 // Y.Plugin.ConsoleFilters static properties
579     /**
580      * Plugin name.
581      *
582      * @property NAME
583      * @type String
584      * @static
585      * @default 'consoleFilters'
586      */
587     NAME : 'consoleFilters',
589     /**
590      * The namespace hung off the host object that this plugin will inhabit.
591      *
592      * @property NS
593      * @type String
594      * @static
595      * @default 'filter'
596      */
597     NS : FILTER,
599     /**
600      * Markup template used to create the container for the category filters.
601      *
602      * @property CATEGORIES_TEMPLATE
603      * @type String
604      * @static
605      */
606     CATEGORIES_TEMPLATE :
607         '<div class="{categories}"></div>',
609     /**
610      * Markup template used to create the container for the source filters.
611      *
612      * @property SOURCES_TEMPLATE
613      * @type String
614      * @static
615      */
616     SOURCES_TEMPLATE :
617         '<div class="{sources}"></div>',
619     /**
620      * Markup template used to create the category and source filter checkboxes.
621      *
622      * @property FILTER_TEMPLATE
623      * @type String
624      * @static
625      */
626     FILTER_TEMPLATE :
627         // IE8 and FF3 don't permit breaking _between_ nowrap elements.  IE8
628         // doesn't understand (non spec) wbr tag, nor does it create text nodes
629         // for spaces in innerHTML strings.  The thin-space entity suffices to
630         // create a breakable point.
631         '<label class="{filter_label}">'+
632             '<input type="checkbox" value="{filter_name}" '+
633                 'class="{filter} {filter_class}"> {filter_name}'+
634         '</label>&#8201;',
636     /** 
637      * Classnames used by the templates when creating nodes.
638      *
639      * @property CHROME_CLASSES
640      * @type Object
641      * @static
642      * @protected
643      */
644     CHROME_CLASSES : {
645         categories   : getCN(CONSOLE,FILTERS,'categories'),
646         sources      : getCN(CONSOLE,FILTERS,'sources'),
647         category     : getCN(CONSOLE,FILTER,CATEGORY),
648         source       : getCN(CONSOLE,FILTER,SOURCE),
649         filter       : getCN(CONSOLE,FILTER),
650         filter_label : getCN(CONSOLE,FILTER,'label')
651     },
653     ATTRS : {
654         /**
655          * Default visibility applied to new categories and sources.
656          *
657          * @attribute defaultVisibility
658          * @type {Boolean}
659          * @default true
660          */
661         defaultVisibility : {
662             value : true,
663             validator : Y.Lang.isBoolean
664         },
666         /**
667          * <p>Map of entry categories to their visibility status.  Update a
668          * particular category's visibility by setting the subattribute to true
669          * (visible) or false (hidden).</p>
670          *
671          * <p>For example, yconsole.filter.set('category.info', false) to hide
672          * log entries with the category/logLevel of 'info'.</p>
673          *
674          * <p>Similarly, yconsole.filter.get('category.warn') will return a
675          * boolean indicating whether that category is currently being included
676          * in the UI.</p>
677          *
678          * <p>Unlike the YUI instance configuration's logInclude and logExclude
679          * properties, filtered entries are only hidden from the UI, but
680          * can be made visible again.</p>
681          *
682          * @attribute category
683          * @type Object
684          */
685         category : {
686             value : {},
687             validator : function (v,k) {
688                 return this._validateCategory(k,v);
689             }
690         },
692         /**
693          * <p>Map of entry sources to their visibility status.  Update a
694          * particular sources's visibility by setting the subattribute to true
695          * (visible) or false (hidden).</p>
696          *
697          * <p>For example, yconsole.filter.set('sources.slider', false) to hide
698          * log entries originating from Y.Slider.</p>
699          *
700          * @attribute source
701          * @type Object
702          */
703         source : {
704             value : {},
705             validator : function (v,k) {
706                 return this._validateSource(k,v);
707             }
708         },
710         /**
711          * Maximum number of entries to store in the message cache.  Use this to
712          * limit the memory footprint in environments with heavy log usage.
713          * By default, there is no limit (Number.POSITIVE_INFINITY).
714          *
715          * @attribute cacheLimit
716          * @type {Number}
717          * @default Number.POSITIVE_INFINITY
718          */
719         cacheLimit : {
720             value : Number.POSITIVE_INFINITY,
721             setter : function (v) {
722                 return this._setCacheLimit(v);
723             }
724         }
725     }
729 }, '3.5.1' ,{requires:['console','plugin']});