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