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