Administrator
2023-04-21 195945efc5db921a4c9eb8cf9421c172273293f5
提交 | 用户 | 时间
58d006 1 /*!
A 2  * Ace v1.3.5
3  */
4
5 if (typeof jQuery === 'undefined') { throw new Error('Ace\'s JavaScript requires jQuery') }
6
7 /**
8  <b>Ace custom scroller</b>. It is not as feature-rich as plugins such as NiceScroll but it's good enough for most cases.
9 */
10 (function($ , undefined) {
11     var Ace_Scroll = function(element , _settings) {
12         var self = this;
13         
14         var attrib_values = ace.helper.getAttrSettings(element, $.fn.ace_scroll.defaults);
15         var settings = $.extend({}, $.fn.ace_scroll.defaults, _settings, attrib_values);
16     
17         this.size = 0;
18         this.lock = false;
19         this.lock_anyway = false;
20         
21         this.$element = $(element);
22         this.element = element;
23         
24         var vertical = true;
25
26         var disabled = false;
27         var active = false;
28         var created = false;
29
30         
31         var $content_wrap = null, content_wrap = null;
32         var $track = null, $bar = null, track = null, bar = null;
33         var bar_style = null;
34         
35         var bar_size = 0, bar_pos = 0, bar_max_pos = 0, bar_size_2 = 0, move_bar = true;
36         var reset_once = false;
37         
38         var styleClass = '';
39         var trackFlip = false;//vertical on left or horizontal on top
40         var trackSize = 0;
41
42         var css_pos,
43             css_size,
44             max_css_size,
45             client_size,
46             scroll_direction,
47             scroll_size;
48
49         var ratio = 1;
50         var inline_style = false;
51         var mouse_track = false;
52         var mouse_release_target = 'onmouseup' in window ? window : 'html';
53         var dragEvent = settings.dragEvent || false;
54         
55         var trigger_scroll = _settings.scrollEvent || false;
56         
57         
58         var detached = settings.detached || false;//when detached, hideOnIdle as well?
59         var updatePos = settings.updatePos || false;//default is true
60         
61         var hideOnIdle = settings.hideOnIdle || false;
62         var hideDelay = settings.hideDelay || 1500;
63         var insideTrack = false;//used to hide scroll track when mouse is up and outside of track
64         var observeContent = settings.observeContent || false;
65         var prevContentSize = 0;
66         
67         var is_dirty = true;//to prevent consecutive 'reset' calls
68         
69         this.ref = function() {
70             return this;
71         }
72         
73         this.create = function(_settings) {
74             if(created) return;
75
76             if(_settings) settings = $.extend({}, $.fn.ace_scroll.defaults, _settings);
77
78             this.size = parseInt(this.$element.attr('data-size')) || settings.size || 200;
79             vertical = !settings['horizontal'];
80
81             css_pos = vertical ? 'top' : 'left';//'left' for horizontal
82             css_size = vertical ? 'height' : 'width';//'width' for horizontal
83             max_css_size = vertical ? 'maxHeight' : 'maxWidth';
84
85             client_size = vertical ? 'clientHeight' : 'clientWidth';
86             scroll_direction = vertical ? 'scrollTop' : 'scrollLeft';
87             scroll_size = vertical ? 'scrollHeight' : 'scrollWidth';
88
89
90
91             this.$element.addClass('ace-scroll');
92             if(this.$element.css('position') == 'static') {
93                 inline_style = this.element.style.position;
94                 this.element.style.position = 'relative';
95             } else inline_style = false;
96
97             var scroll_bar = null;
98             if(!detached) {
99                 this.$element.wrapInner('<div class="scroll-content" />');
100                 this.$element.prepend('<div class="scroll-track"><div class="scroll-bar"></div></div>');
101             }
102             else {
103                 scroll_bar = $('<div class="scroll-track scroll-detached"><div class="scroll-bar"></div></div>').appendTo('body');
104             }
105
106
107             $content_wrap = this.$element;
108             if(!detached) $content_wrap = this.$element.find('.scroll-content').eq(0);
109             
110             if(!vertical) $content_wrap.wrapInner('<div />');
111             
112             content_wrap = $content_wrap.get(0);
113             if(detached) {
114                 //set position for detached scrollbar
115                 $track = scroll_bar;
116                 setTrackPos();
117             }
118             else $track = this.$element.find('.scroll-track').eq(0);
119             
120             $bar = $track.find('.scroll-bar').eq(0);
121             track = $track.get(0);
122             bar = $bar.get(0);
123             bar_style = bar.style;
124
125             //add styling classes and horizontalness
126             if(!vertical) $track.addClass('scroll-hz');
127             if(settings.styleClass) {
128                 styleClass = settings.styleClass;
129                 $track.addClass(styleClass);
130                 trackFlip = !!styleClass.match(/scroll\-left|scroll\-top/);
131             }
132             
133             //calculate size of track!
134             if(trackSize == 0) {
135                 $track.show();
136                 getTrackSize();
137             }
138             
139             $track.hide();
140             
141
142             //if(!touchDrag) {
143             $track.on('mousedown', mouse_down_track);
144             $bar.on('mousedown', mouse_down_bar);
145             //}
146
147             $content_wrap.on('scroll', function() {
148                 if(move_bar) {
149                     bar_pos = parseInt(Math.round(this[scroll_direction] * ratio));
150                     bar_style[css_pos] = bar_pos + 'px';
151                 }
152                 move_bar = false;
153                 if(trigger_scroll) this.$element.trigger('scroll', [content_wrap]);
154             })
155
156
157             if(settings.mouseWheel) {
158                 this.lock = settings.mouseWheelLock;
159                 this.lock_anyway = settings.lockAnyway;
160
161                 //mousewheel library available?
162                 this.$element.on(!!$.event.special.mousewheel ? 'mousewheel.ace_scroll' : 'mousewheel.ace_scroll DOMMouseScroll.ace_scroll', function(event) {
163                     if(disabled) return;
164                     checkContentChanges(true);
165
166                     if(!active) return !self.lock_anyway;
167
168                     if(mouse_track) {
169                         mouse_track = false;
170                         $('html').off('.ace_scroll')
171                         $(mouse_release_target).off('.ace_scroll');
172                         if(dragEvent) self.$element.trigger('drag.end');
173                     }
174                     
175
176                     event.deltaY = event.deltaY || 0;
177                     var delta = (event.deltaY > 0 || event.originalEvent.detail < 0 || event.originalEvent.wheelDelta > 0) ? 1 : -1
178                     var scrollEnd = false//have we reached the end of scrolling?
179                     
180                     var clientSize = content_wrap[client_size], scrollAmount = content_wrap[scroll_direction];
181                     if( !self.lock ) {
182                         if(delta == -1)    scrollEnd = (content_wrap[scroll_size] <= scrollAmount + clientSize);
183                         else scrollEnd = (scrollAmount == 0);
184                     }
185
186                     self.move_bar(true);
187
188                     //var step = parseInt( Math.min(Math.max(parseInt(clientSize / 8) , 80) , self.size) ) + 1;
189                     var step = parseInt(clientSize / 8);
190                     if(step < 80) step = 80;
191                     if(step > self.size) step = self.size;
192                     step += 1;
193                     
194                     content_wrap[scroll_direction] = scrollAmount - (delta * step);
195
196
197                     return scrollEnd && !self.lock_anyway;
198                 })
199             }
200             
201             
202             //swipe not available yet
203             var touchDrag = ace.vars['touch'] && 'ace_drag' in $.event.special && settings.touchDrag //&& !settings.touchSwipe;
204             //add drag event for touch devices to scroll
205             if(touchDrag/** || ($.fn.swipe && settings.touchSwipe)*/) {
206                 var dir = '', event_name = touchDrag ? 'ace_drag' : 'swipe';
207                 this.$element.on(event_name + '.ace_scroll', function(event) {
208                     if(disabled) {
209                         event.retval.cancel = true;
210                         return;
211                     }
212                     checkContentChanges(true);
213                     
214                     if(!active) {
215                         event.retval.cancel = this.lock_anyway;
216                         return;
217                     }
218
219                     dir = event.direction;
220                     if( (vertical && (dir == 'up' || dir == 'down'))
221                         ||
222                         (!vertical && (dir == 'left' || dir == 'right'))
223                        )
224                     {
225                         var distance = vertical ? event.dy : event.dx;
226
227                         if(distance != 0) {
228                             if(Math.abs(distance) > 20 && touchDrag) distance = distance * 2;
229
230                             self.move_bar(true);
231                             content_wrap[scroll_direction] = content_wrap[scroll_direction] + distance;
232                         }
233                     }
234                     
235                 })
236             }
237             
238             
239             /////////////////////////////////
240             
241             if(hideOnIdle) {
242                 $track.addClass('idle-hide');
243             }
244             if(observeContent) {
245                 $track.on('mouseenter.ace_scroll', function() {
246                     insideTrack = true;
247                     checkContentChanges(false);
248                 }).on('mouseleave.ace_scroll', function() {
249                     insideTrack = false;
250                     if(mouse_track == false) hideScrollbars();
251                 });
252             }
253
254
255             
256             //some mobile browsers don't have mouseenter
257             this.$element.on('mouseenter.ace_scroll touchstart.ace_scroll', function(e) {
258                 is_dirty = true;
259                 if(observeContent) checkContentChanges(true);
260                 else if(settings.hoverReset) self.reset(true);
261                 
262                 $track.addClass('scroll-hover');
263             }).on('mouseleave.ace_scroll touchend.ace_scroll', function() {
264                 $track.removeClass('scroll-hover');
265             });
266             //
267
268             if(!vertical) $content_wrap.children(0).css(css_size, this.size);//the extra wrapper
269             $content_wrap.css(max_css_size , this.size);
270             
271             disabled = false;
272             created = true;
273         }
274         this.is_active = function() {
275             return active;
276         }
277         this.is_enabled = function() {
278             return !disabled;
279         }
280         this.move_bar = function($move) {
281             move_bar = $move;
282         }
283         
284         this.get_track = function() {
285             return track;
286         }
287
288         this.reset = function(innert_call) {
289             if(disabled) return;// this;
290             if(!created) this.create();
291             /////////////////////
292             var size = this.size;
293             
294             if(innert_call && !is_dirty) {
295                 return;
296             }
297             is_dirty = false;
298
299             if(detached) {
300                 var border_size = parseInt(Math.round( (parseInt($content_wrap.css('border-top-width')) + parseInt($content_wrap.css('border-bottom-width'))) / 2.5 ));//(2.5 from trial?!)
301                 size -= border_size;//only if detached
302             }
303     
304             var content_size   = vertical ? content_wrap[scroll_size] : size;
305             if( (vertical && content_size == 0) || (!vertical && this.element.scrollWidth == 0) ) {
306                 //element is hidden
307                 //this.$element.addClass('scroll-hidden');
308                 $track.removeClass('scroll-active')
309                 return;// this;
310             }
311
312             var available_space = vertical ? size : content_wrap.clientWidth;
313
314             if(!vertical) $content_wrap.children(0).css(css_size, size);//the extra wrapper
315             $content_wrap.css(max_css_size , this.size);
316             
317
318             if(content_size > available_space) {
319                 active = true;
320                 $track.css(css_size, available_space).show();
321
322                 ratio = parseFloat((available_space / content_size).toFixed(5))
323                 
324                 bar_size = parseInt(Math.round(available_space * ratio));
325                 bar_size_2 = parseInt(Math.round(bar_size / 2));
326
327                 bar_max_pos = available_space - bar_size;
328                 bar_pos = parseInt(Math.round(content_wrap[scroll_direction] * ratio));
329
330                 bar_style[css_size] = bar_size + 'px';
331                 bar_style[css_pos] = bar_pos + 'px';
332                 
333                 $track.addClass('scroll-active');
334                 
335                 if(trackSize == 0) {
336                     getTrackSize();
337                 }
338
339                 if(!reset_once) {
340                     //this.$element.removeClass('scroll-hidden');
341                     if(settings.reset) {
342                         //reset scrollbar to zero position at first                            
343                         content_wrap[scroll_direction] = 0;
344                         bar_style[css_pos] = 0;
345                     }
346                     reset_once = true;
347                 }
348                 
349                 if(detached) setTrackPos();
350             } else {
351                 active = false;
352                 $track.hide();
353                 $track.removeClass('scroll-active');
354                 $content_wrap.css(max_css_size , '');
355             }
356
357             return;// this;
358         }
359         this.disable = function() {
360             content_wrap[scroll_direction] = 0;
361             bar_style[css_pos] = 0;
362
363             disabled = true;
364             active = false;
365             $track.hide();
366             
367             this.$element.addClass('scroll-disabled');
368             
369             $track.removeClass('scroll-active');
370             $content_wrap.css(max_css_size , '');
371         }
372         this.enable = function() {
373             disabled = false;
374             this.$element.removeClass('scroll-disabled');
375         }
376         this.destroy = function() {
377             active = false;
378             disabled = false;
379             created = false;
380             
381             this.$element.removeClass('ace-scroll scroll-disabled scroll-active');
382             this.$element.off('.ace_scroll')
383
384             if(!detached) {
385                 if(!vertical) {
386                     //remove the extra wrapping div
387                     $content_wrap.find('> div').children().unwrap();
388                 }
389                 $content_wrap.children().unwrap();
390                 $content_wrap.remove();
391             }
392             
393             $track.remove();
394             
395             if(inline_style !== false) this.element.style.position = inline_style;
396             
397             if(idleTimer != null) {
398                 clearTimeout(idleTimer);
399                 idleTimer = null;
400             }
401         }
402         this.modify = function(_settings) {
403             if(_settings) settings = $.extend({}, settings, _settings);
404             
405             this.destroy();
406             this.create();
407             is_dirty = true;
408             this.reset(true);
409         }
410         this.update = function(_settings) {
411             if(_settings) settings = $.extend({}, settings, _settings);
412
413             this.size = settings.size || this.size;
414             
415             this.lock = settings.mouseWheelLock || this.lock;
416             this.lock_anyway = settings.lockAnyway || this.lock_anyway;
417             
418             hideOnIdle = settings.hideOnIdle || hideOnIdle;
419             hideDelay = settings.hideDelay || hideDelay;
420             observeContent = settings.observeContent || false;
421             
422             dragEvent = settings.dragEvent || false;
423             
424             if(typeof _settings.styleClass !== 'undefined') {
425                 if(styleClass) $track.removeClass(styleClass);
426                 styleClass = _settings.styleClass;
427                 if(styleClass) $track.addClass(styleClass);
428                 trackFlip = !!styleClass.match(/scroll\-left|scroll\-top/);
429             }
430         }
431         
432         this.start = function() {
433             content_wrap[scroll_direction] = 0;
434         }
435         this.end = function() {
436             content_wrap[scroll_direction] = content_wrap[scroll_size];
437         }
438         
439         this.hide = function() {
440             $track.hide();
441         }
442         this.show = function() {
443             $track.show();
444         }
445
446         
447         this.update_scroll = function() {
448             move_bar = false;
449             bar_style[css_pos] = bar_pos + 'px';
450             content_wrap[scroll_direction] = parseInt(Math.round(bar_pos / ratio));
451         }
452
453         function mouse_down_track(e) {
454             e.preventDefault();
455             e.stopPropagation();
456                 
457             var track_offset = $track.offset();
458             var track_pos = track_offset[css_pos];//top for vertical, left for horizontal
459             var mouse_pos = vertical ? e.pageY : e.pageX;
460             
461             if(mouse_pos > track_pos + bar_pos) {
462                 bar_pos = mouse_pos - track_pos - bar_size + bar_size_2;
463                 if(bar_pos > bar_max_pos) {                        
464                     bar_pos = bar_max_pos;
465                 }
466             }
467             else {
468                 bar_pos = mouse_pos - track_pos - bar_size_2;
469                 if(bar_pos < 0) bar_pos = 0;
470             }
471
472             self.update_scroll()
473         }
474
475         var mouse_pos1 = -1, mouse_pos2 = -1;
476         function mouse_down_bar(e) {
477             e.preventDefault();
478             e.stopPropagation();
479
480             if(vertical) {
481                 mouse_pos2 = mouse_pos1 = e.pageY;
482             } else {
483                 mouse_pos2 = mouse_pos1 = e.pageX;
484             }
485
486             mouse_track = true;
487             $('html').off('mousemove.ace_scroll').on('mousemove.ace_scroll', mouse_move_bar)
488             $(mouse_release_target).off('mouseup.ace_scroll').on('mouseup.ace_scroll', mouse_up_bar);
489             
490             $track.addClass('active');
491             if(dragEvent) self.$element.trigger('drag.start');
492         }
493         function mouse_move_bar(e) {
494             e.preventDefault();
495             e.stopPropagation();
496
497             if(vertical) {
498                 mouse_pos2 = e.pageY;
499             } else {
500                 mouse_pos2 = e.pageX;
501             }
502             
503
504             if(mouse_pos2 - mouse_pos1 + bar_pos > bar_max_pos) {
505                 mouse_pos2 = mouse_pos1 + bar_max_pos - bar_pos;
506             } else if(mouse_pos2 - mouse_pos1 + bar_pos < 0) {
507                 mouse_pos2 = mouse_pos1 - bar_pos;
508             }
509             bar_pos = bar_pos + (mouse_pos2 - mouse_pos1);
510
511             mouse_pos1 = mouse_pos2;
512
513             if(bar_pos < 0) {
514                 bar_pos = 0;
515             }
516             else if(bar_pos > bar_max_pos) {
517                 bar_pos = bar_max_pos;
518             }
519             
520             self.update_scroll()
521         }
522         function mouse_up_bar(e) {
523             e.preventDefault();
524             e.stopPropagation();
525             
526             mouse_track = false;
527             $('html').off('.ace_scroll')
528             $(mouse_release_target).off('.ace_scroll');
529
530             $track.removeClass('active');
531             if(dragEvent) self.$element.trigger('drag.end');
532             
533             if(active && hideOnIdle && !insideTrack) hideScrollbars();
534         }
535         
536         
537         var idleTimer = null;
538         var prevCheckTime = 0;
539         function checkContentChanges(hideSoon) {
540             //check if content size has been modified since last time?
541             //and with at least 1s delay
542             var newCheck = +new Date();
543             if(observeContent && newCheck - prevCheckTime > 1000) {
544                 var newSize = content_wrap[scroll_size];
545                 if(prevContentSize != newSize) {
546                     prevContentSize = newSize;
547                     is_dirty = true;
548                     self.reset(true);
549                 }
550                 prevCheckTime = newCheck;
551             }
552             
553             //show scrollbars when not idle anymore i.e. triggered by mousewheel, dragging, etc
554             if(active && hideOnIdle) {
555                 if(idleTimer != null) {
556                     clearTimeout(idleTimer);
557                     idleTimer = null;
558                 }
559                 $track.addClass('not-idle');
560             
561                 if(!insideTrack && hideSoon == true) {
562                     //hideSoon is false when mouse enters track
563                     hideScrollbars();
564                 }
565             }
566         }
567
568         function hideScrollbars() {
569             if(idleTimer != null) {
570                 clearTimeout(idleTimer);
571                 idleTimer = null;
572             }
573             idleTimer = setTimeout(function() {
574                 idleTimer = null;
575                 $track.removeClass('not-idle');
576             } , hideDelay);
577         }
578         
579         //for detached scrollbars
580         function getTrackSize() {
581             $track.css('visibility', 'hidden').addClass('scroll-hover');
582             if(vertical) trackSize = parseInt($track.outerWidth()) || 0;
583              else trackSize = parseInt($track.outerHeight()) || 0;
584             $track.css('visibility', '').removeClass('scroll-hover');
585         }
586         this.track_size = function() {
587             if(trackSize == 0) getTrackSize();
588             return trackSize;
589         }
590         
591         //for detached scrollbars
592         function setTrackPos() {
593             if(updatePos === false) return;
594         
595             var off = $content_wrap.offset();//because we want it relative to parent not document
596             var left = off.left;
597             var top = off.top;
598
599             if(vertical) {
600                 if(!trackFlip) {
601                     left += ($content_wrap.outerWidth() - trackSize)
602                 }
603             }
604             else {
605                 if(!trackFlip) {
606                     top += ($content_wrap.outerHeight() - trackSize)
607                 }
608             }
609             
610             if(updatePos === true) $track.css({top: parseInt(top), left: parseInt(left)});
611             else if(updatePos === 'left') $track.css('left', parseInt(left));
612             else if(updatePos === 'top') $track.css('top', parseInt(top));
613         }
614         
615
616
617         this.create();
618         is_dirty = true;
619         this.reset(true);
620         prevContentSize = content_wrap[scroll_size];
621
622         return this;
623     }
624
625     
626     $.fn.ace_scroll = function (option,value) {
627         var retval;
628
629         var $set = this.each(function () {
630             var $this = $(this);
631             var data = $this.data('ace_scroll');
632             var options = typeof option === 'object' && option;
633
634             if (!data) $this.data('ace_scroll', (data = new Ace_Scroll(this, options)));
635              //else if(typeof options == 'object') data['modify'](options);
636             if (typeof option === 'string') retval = data[option](value);
637         });
638
639         return (retval === undefined) ? $set : retval;
640     };
641
642
643     $.fn.ace_scroll.defaults = {
644         'size' : 200,
645         'horizontal': false,
646         'mouseWheel': true,
647         'mouseWheelLock': false,
648         'lockAnyway': false,
649         'styleClass' : false,
650         
651         'observeContent': false,
652         'hideOnIdle': false,
653         'hideDelay': 1500,
654         
655         'hoverReset': true //reset scrollbar sizes on mouse hover because of possible sizing changes
656         ,
657         'reset': false //true= set scrollTop = 0
658         ,
659         'dragEvent': false
660         ,
661         'touchDrag': true
662         ,
663         'touchSwipe': false
664         ,
665         'scrollEvent': false //trigger scroll event
666
667         ,
668         'detached': false
669         ,
670         'updatePos': true
671         /**
672         ,        
673         'track' : true,
674         'show' : false,
675         'dark': false,
676         'alwaysVisible': false,
677         'margin': false,
678         'thin': false,
679         'position': 'right'
680         */
681      }
682
683     /**
684     $(document).on('ace.settings.ace_scroll', function(e, name) {
685         if(name == 'sidebar_collapsed') $('.ace-scroll').scroller('reset');
686     });
687     $(window).on('resize.ace_scroll', function() {
688         $('.ace-scroll').scroller('reset');
689     });
690     */
691
692 })(window.jQuery);;/**
693  <b>Custom color picker element</b>. Converts html select elements to a dropdown color picker.
694 */
695 (function($ , undefined) {
696     var Ace_Colorpicker = function(element, _options) {
697
698         var attrib_values = ace.helper.getAttrSettings(element, $.fn.ace_colorpicker.defaults);
699         var options = $.extend({}, $.fn.ace_colorpicker.defaults, _options, attrib_values);
700
701
702         var $element = $(element);
703         var color_list = '';
704         var color_selected = '';
705         var selection = null;
706         var color_array = [];
707         
708         $element.addClass('hide').find('option').each(function() {
709             var $class = 'colorpick-btn';
710             var color = this.value.replace(/[^\w\s,#\(\)\.]/g, '');
711             if(this.value != color) this.value = color;
712             if(this.selected) {
713                 $class += ' selected';
714                 color_selected = color;
715             }
716             color_array.push(color)
717             color_list += '<li><a class="'+$class+'" href="#" style="background-color:'+color+';" data-color="'+color+'"></a></li>';
718         }).
719         end()
720         .on('change.color', function(){
721             $element.next().find('.btn-colorpicker').css('background-color', this.value);
722         })
723         .after('<div class="dropdown dropdown-colorpicker">\
724         <a data-toggle="dropdown" class="dropdown-toggle" '+(options.auto_pos ? 'data-position="auto"' : '')+' href="#"><span class="btn-colorpicker" style="background-color:'+color_selected+'"></span></a><ul class="dropdown-menu'+(options.caret? ' dropdown-caret' : '')+(options.pull_right ? ' dropdown-menu-right' : '')+'">'+color_list+'</ul></div>')
725
726         
727         var dropdown = $element.next().find('.dropdown-menu')
728         dropdown.on(ace.click_event, function(e) {
729             var a = $(e.target);
730             if(!a.is('.colorpick-btn')) return false;
731
732             if(selection) selection.removeClass('selected');
733             selection = a;
734             selection.addClass('selected');
735             var color = selection.data('color');
736
737             $element.val(color).trigger('change');
738
739             e.preventDefault();
740             return true;//to hide dropdown
741         })
742         selection = $element.next().find('a.selected');
743
744         this.pick = function(index, insert) {
745             if(typeof index === 'number') {
746                 if(index >= color_array.length) return;
747                 element.selectedIndex = index;
748                 dropdown.find('a:eq('+index+')').trigger(ace.click_event);
749             }
750             else if(typeof index === 'string') {
751                 var color = index.replace(/[^\w\s,#\(\)\.]/g, '');
752                 index = color_array.indexOf(color);
753                 //add this color if it doesn't exist
754                 if(index == -1 && insert === true) {
755                     color_array.push(color);
756                     
757                     $('<option />')
758                     .appendTo($element)
759                     .val(color);
760                     
761                     $('<li><a class="colorpick-btn" href="#"></a></li>')
762                     .appendTo(dropdown)
763                     .find('a')
764                     .css('background-color', color)
765                     .data('color', color);
766                     
767                     index = color_array.length - 1;
768                 }
769                 if(index == -1) return;
770                 dropdown.find('a:eq('+index+')').trigger(ace.click_event);
771             }
772         }
773
774         this.destroy = function() {
775             $element.removeClass('hide').off('change.color')
776             .next().remove();
777             color_array = [];
778         }
779     }
780
781
782     $.fn.ace_colorpicker = function(option, value) {
783         var retval;
784
785         var $set = this.each(function () {
786             var $this = $(this);
787             var data = $this.data('ace_colorpicker');
788             var options = typeof option === 'object' && option;
789
790             if (!data) $this.data('ace_colorpicker', (data = new Ace_Colorpicker(this, options)));
791             if (typeof option === 'string') retval = data[option](value);
792         });
793
794         return (retval === undefined) ? $set : retval;
795     }
796     
797     $.fn.ace_colorpicker.defaults = {
798         'pull_right' : false,
799         'caret': true,
800         'auto_pos': true
801     }
802     
803 })(window.jQuery);;/**
804  <b>Ace file input element</b>. Custom, simple file input element to style browser's default file input.
805 */
806 (function($ , undefined) {
807     var multiplible = 'multiple' in document.createElement('INPUT');
808     var hasFileList = 'FileList' in window;//file list enabled in modern browsers
809     var hasFileReader = 'FileReader' in window;
810     var hasFile = 'File' in window;
811
812     var Ace_File_Input = function(element , settings) {
813         var self = this;
814         
815         var attrib_values = ace.helper.getAttrSettings(element, $.fn.ace_file_input.defaults);
816         this.settings = $.extend({}, $.fn.ace_file_input.defaults, settings, attrib_values);
817
818         this.$element = $(element);
819         this.element = element;
820         this.disabled = false;
821         this.can_reset = true;
822         
823
824         this.$element
825         .off('change.ace_inner_call')
826         .on('change.ace_inner_call', function(e , ace_inner_call){
827             if(self.disabled) return;
828         
829             if(ace_inner_call === true) return;//this change event is called from above drop event and extra checkings are taken care of there
830             return handle_on_change.call(self);
831             //if(ret === false) e.preventDefault();
832         });
833
834         var parent_label = this.$element.closest('label').css({'display':'block'})
835         var tagName = parent_label.length == 0 ? 'label' : 'span';//if not inside a "LABEL" tag, use "LABEL" tag, otherwise use "SPAN"
836         this.$element.wrap('<'+tagName+' class="ace-file-input" />');
837
838         this.apply_settings();
839         this.reset_input_field();//for firefox as it keeps selected file after refresh
840     }
841     Ace_File_Input.error = {
842         'FILE_LOAD_FAILED' : 1,
843         'IMAGE_LOAD_FAILED' : 2,
844         'THUMBNAIL_FAILED' : 3
845     };
846
847
848     Ace_File_Input.prototype.apply_settings = function() {
849         var self = this;
850
851         this.multi = this.$element.attr('multiple') && multiplible;
852         this.well_style = this.settings.style == 'well';
853
854         if(this.well_style) {
855             if( !this.settings.thumbnail ) this.settings.thumbnail = 'small';
856             this.$element.parent().addClass('ace-file-multiple');
857         }
858         else {
859             this.$element.parent().removeClass('ace-file-multiple');
860         }
861
862
863         this.$element.parent().find(':not(input[type=file])').remove();//remove all except our input, good for when changing settings
864         this.$element.after('<span class="ace-file-container" data-title="'+this.settings.btn_choose+'"><span class="ace-file-name" data-title="'+this.settings.no_file+'">'+(this.settings.no_icon ? '<i class="'+ ace.vars['icon'] + this.settings.no_icon+'"></i>' : '')+'</span></span>');
865         this.$label = this.$element.next();
866         this.$container = this.$element.closest('.ace-file-input');
867
868         var remove_btn = !!this.settings.icon_remove;
869         if(remove_btn) {
870             var btn = 
871             $('<a class="remove" href="#"><i class="'+ ace.vars['icon'] + this.settings.icon_remove+'"></i></a>')
872             .appendTo(this.$element.parent());
873
874             btn.on(ace.click_event, function(e){
875                 e.preventDefault();
876                 if( !self.can_reset ) return false;
877                 
878                 var ret = true;
879                 if(self.settings.before_remove) ret = self.settings.before_remove.call(self.element);
880                 if(!ret) return false;
881
882                 var r = self.reset_input();
883                 return false;
884             });
885         }
886
887
888         if(this.settings.droppable && hasFileList) {
889             enable_drop_functionality.call(this);
890         }
891     }
892
893     Ace_File_Input.prototype.show_file_list = function($files , inner_call) {
894         var files = typeof $files === "undefined" ? this.$element.data('ace_input_files') : $files;
895         if(!files || files.length == 0) return;
896         
897         //////////////////////////////////////////////////////////////////
898         
899         if(this.well_style) {
900             this.$label.find('.ace-file-name').remove();
901             if(!this.settings.btn_change) this.$label.addClass('hide-placeholder');
902         }
903         this.$label.attr('data-title', this.settings.btn_change).addClass('selected');
904         
905         for (var i = 0; i < files.length; i++) {
906             var filename = '', format = false;
907             if(typeof files[i] === "string") filename = files[i];
908             else if(hasFile && files[i] instanceof File) filename = $.trim( files[i].name );
909             else if(files[i] instanceof Object && files[i].hasOwnProperty('name')) {
910                 //format & name specified by user (pre-displaying name, etc)
911                 filename = files[i].name;
912                 if(files[i].hasOwnProperty('type')) format = files[i].type;
913                 if(!files[i].hasOwnProperty('path')) files[i].path = files[i].name;
914             }
915             else continue;
916             
917             var index = filename.lastIndexOf("\\") + 1;
918             if(index == 0)index = filename.lastIndexOf("/") + 1;
919             filename = filename.substr(index);
920             
921             if(format == false) {
922                 if((/\.(jpe?g|png|gif|svg|bmp|tiff?)$/i).test(filename)) {                
923                     format = 'image';
924                 }
925                 else if((/\.(mpe?g|flv|mov|avi|swf|mp4|mkv|webm|wmv|3gp)$/i).test(filename)) {
926                     format = 'video';
927                 }
928                 else if((/\.(mp3|ogg|wav|wma|amr|aac)$/i).test(filename)) {
929                     format = 'audio';
930                 }
931                 else format = 'file';
932             }
933             
934             var fileIcons = {
935                 'file' : 'fa fa-file',
936                 'image' : 'fa fa-picture-o file-image',
937                 'video' : 'fa fa-film file-video',
938                 'audio' : 'fa fa-music file-audio'
939             };
940             var fileIcon = fileIcons[format];
941             
942             
943             if(!this.well_style) this.$label.find('.ace-file-name').attr({'data-title':filename}).find(ace.vars['.icon']).attr('class', ace.vars['icon'] + fileIcon);
944             else {
945                 this.$label.append('<span class="ace-file-name" data-title="'+filename+'"><i class="'+ ace.vars['icon'] + fileIcon+'"></i></span>');
946                 var type = (inner_call === true && hasFile && files[i] instanceof File) ? $.trim(files[i].type) : '';
947                 var can_preview = hasFileReader && this.settings.thumbnail 
948                         &&
949                         ( (type.length > 0 && type.match('image')) || (type.length == 0 && format == 'image') )//the second one is for older Android's default browser which gives an empty text for file.type
950                 if(can_preview) {
951                     var self = this;
952                     $.when(preview_image.call(this, files[i])).fail(function(result){
953                         //called on failure to load preview
954                         if(self.settings.preview_error) self.settings.preview_error.call(self, filename, result.code);
955                     })
956                 }
957             }
958         }
959         
960         return true;
961     }
962     
963     Ace_File_Input.prototype.reset_input = function() {
964         this.reset_input_ui();
965         this.reset_input_field();
966     }
967     
968     Ace_File_Input.prototype.reset_input_ui = function() {
969          this.$label.attr({'data-title':this.settings.btn_choose, 'class':'ace-file-container'})
970             .find('.ace-file-name:first').attr({'data-title':this.settings.no_file , 'class':'ace-file-name'})
971             .find(ace.vars['.icon']).attr('class', ace.vars['icon'] + this.settings.no_icon)
972             .prev('img').remove();
973             if(!this.settings.no_icon) this.$label.find(ace.vars['.icon']).remove();
974         
975         this.$label.find('.ace-file-name').not(':first').remove();
976         
977         this.reset_input_data();
978         
979         //if(ace.vars['old_ie']) ace.helper.redraw(this.$container[0]);
980     }
981     Ace_File_Input.prototype.reset_input_field = function() {
982         //http://stackoverflow.com/questions/1043957/clearing-input-type-file-using-jquery/13351234#13351234
983         this.$element.wrap('<form>').parent().get(0).reset();
984         this.$element.unwrap();
985         
986         //strangely when reset is called on this temporary inner form
987         //only **IE9/10** trigger 'reset' on the outer form as well
988         //and as we have mentioned to reset input on outer form reset
989         //it causes infinite recusrsion by coming back to reset_input_field
990         //thus calling reset again and again and again
991         //so because when "reset" button of outer form is hit, file input is automatically reset
992         //we just reset_input_ui to avoid recursion
993     }
994     Ace_File_Input.prototype.reset_input_data = function() {
995         if(this.$element.data('ace_input_files')) {
996             this.$element.removeData('ace_input_files');
997             this.$element.removeData('ace_input_method');
998         }
999     }
1000
1001     Ace_File_Input.prototype.enable_reset = function(can_reset) {
1002         this.can_reset = can_reset;
1003     }
1004
1005     Ace_File_Input.prototype.disable = function() {
1006         this.disabled = true;
1007         this.$element.attr('disabled', 'disabled').addClass('disabled');
1008     }
1009     Ace_File_Input.prototype.enable = function() {
1010         this.disabled = false;
1011         this.$element.removeAttr('disabled').removeClass('disabled');
1012     }
1013
1014     Ace_File_Input.prototype.files = function() {
1015         return $(this).data('ace_input_files') || null;
1016     }
1017     Ace_File_Input.prototype.method = function() {
1018         return $(this).data('ace_input_method') || '';
1019     }
1020     
1021     Ace_File_Input.prototype.update_settings = function(new_settings) {
1022         this.settings = $.extend({}, this.settings, new_settings);
1023         this.apply_settings();
1024     }
1025     
1026     Ace_File_Input.prototype.loading = function(is_loading) {
1027         if(is_loading === false) {
1028             this.$container.find('.ace-file-overlay').remove();
1029             this.element.removeAttribute('readonly');
1030         }
1031         else {
1032             var inside = typeof is_loading === 'string' ? is_loading : '<i class="overlay-content fa fa-spin fa-spinner orange2 fa-2x"></i>';
1033             var loader = this.$container.find('.ace-file-overlay');
1034             if(loader.length == 0) {
1035                 loader = $('<div class="ace-file-overlay"></div>').appendTo(this.$container);
1036                 loader.on('click tap', function(e) {
1037                     e.stopImmediatePropagation();
1038                     e.preventDefault();
1039                     return false;
1040                 });
1041                 
1042                 this.element.setAttribute('readonly' , 'true');//for IE
1043             }
1044             loader.empty().append(inside);
1045         }
1046     }
1047
1048
1049
1050     var enable_drop_functionality = function() {
1051         var self = this;
1052         
1053         var dropbox = this.$element.parent();
1054         dropbox
1055         .off('dragenter')
1056         .on('dragenter', function(e){
1057             e.preventDefault();
1058             e.stopPropagation();
1059         })
1060         .off('dragover')
1061         .on('dragover', function(e){
1062             e.preventDefault();
1063             e.stopPropagation();
1064         })
1065         .off('drop')
1066         .on('drop', function(e){
1067             e.preventDefault();
1068             e.stopPropagation();
1069
1070             if(self.disabled) return;
1071         
1072             var dt = e.originalEvent.dataTransfer;
1073             var file_list = dt.files;
1074             if(!self.multi && file_list.length > 1) {//single file upload, but dragged multiple files
1075                 var tmpfiles = [];
1076                 tmpfiles.push(file_list[0]);
1077                 file_list = tmpfiles;//keep only first file
1078             }
1079             
1080             
1081             file_list = processFiles.call(self, file_list, true);//true means files have been selected, not dropped
1082             if(file_list === false) return false;
1083
1084             self.$element.data('ace_input_method', 'drop');
1085             self.$element.data('ace_input_files', file_list);//save files data to be used later by user
1086
1087             self.show_file_list(file_list , true);
1088             
1089             self.$element.triggerHandler('change' , [true]);//true means ace_inner_call
1090             return true;
1091         });
1092     }
1093     
1094     
1095     var handle_on_change = function() {
1096         var file_list = this.element.files || [this.element.value];/** make it an array */
1097         
1098         file_list = processFiles.call(this, file_list, false);//false means files have been selected, not dropped
1099         if(file_list === false) return false;
1100         
1101         this.$element.data('ace_input_method', 'select');
1102         this.$element.data('ace_input_files', file_list);
1103         
1104         this.show_file_list(file_list , true);
1105         
1106         return true;
1107     }
1108
1109
1110
1111     var preview_image = function(file) {
1112         var self = this;
1113         var $span = self.$label.find('.ace-file-name:last');//it should be out of onload, otherwise all onloads may target the same span because of delays
1114         
1115         var deferred = new $.Deferred;
1116         
1117         var getImage = function(src, $file) {
1118             $span.prepend("<img class='middle' style='display:none;' />");
1119             var img = $span.find('img:last').get(0);
1120         
1121             $(img).one('load', function() {
1122                 imgLoaded.call(null, img, $file);
1123             }).one('error', function() {
1124                 imgFailed.call(null, img);
1125             });
1126
1127             img.src = src;
1128         }
1129         var imgLoaded = function(img, $file) {
1130             //if image loaded successfully
1131             
1132             var size = self.settings['previewSize'];
1133             
1134             if(!size) {
1135                 if(self.settings['previewWidth'] || self.settings['previewHeight']) {
1136                     size = {previewWidth: self.settings['previewWidth'], previewHeight: self.settings['previewHeight']}
1137                 }
1138                 else {
1139                     size = 50;
1140                     if(self.settings.thumbnail == 'large') size = 150;
1141                 }
1142             }
1143             if(self.settings.thumbnail == 'fit') size = $span.width();
1144             else if(typeof size == 'number') size = parseInt(Math.min(size, $span.width()));
1145
1146
1147             var thumb = get_thumbnail(img, size/**, file.type*/);
1148             if(thumb == null) {
1149                 //if making thumbnail fails
1150                 $(this).remove();
1151                 deferred.reject({code:Ace_File_Input.error['THUMBNAIL_FAILED']});
1152                 return;
1153             }
1154             
1155             
1156             var showPreview = true;
1157             //add width/height info to "file" and trigger preview finished event for each image!
1158             if($file && $file instanceof File) {
1159                 $file.width = thumb.width;
1160                 $file.height = thumb.height;
1161                 self.$element.trigger('file.preview.ace', {'file': $file});
1162                 
1163                 var event
1164                 self.$element.trigger( event = new $.Event('file.preview.ace'), {'file': $file} );
1165                 if ( event.isDefaultPrevented() ) showPreview = false;
1166             }
1167
1168
1169             if(showPreview) {
1170                 var w = thumb.previewWidth, h = thumb.previewHeight;
1171                 if(self.settings.thumbnail == 'small') {w=h=parseInt(Math.max(w,h))}
1172                 else $span.addClass('large');
1173
1174                 $(img).css({'background-image':'url('+thumb.src+')' , width:w, height:h})
1175                         .data('thumb', thumb.src)
1176                         .attr({src:'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg=='})
1177                         .show()
1178             }
1179
1180             ///////////////////
1181             deferred.resolve();
1182         }
1183         var imgFailed = function(img) {
1184             //for example when a file has image extenstion, but format is something else
1185             $span.find('img').remove();
1186             deferred.reject({code:Ace_File_Input.error['IMAGE_LOAD_FAILED']});
1187         }
1188         
1189         if(hasFile && file instanceof File) {
1190             var reader = new FileReader();
1191             reader.onload = function (e) {
1192                 getImage(e.target.result, file);
1193             }
1194             reader.onerror = function (e) {
1195                 deferred.reject({code:Ace_File_Input.error['FILE_LOAD_FAILED']});
1196             }
1197             reader.readAsDataURL(file);
1198         }
1199         else {
1200             if(file instanceof Object && file.hasOwnProperty('path')) {
1201                 getImage(file.path, null);//file is a file name (path) --- this is used to pre-show user-selected image
1202             }
1203         }
1204         
1205         return deferred.promise();
1206     }
1207
1208     var get_thumbnail = function(img, size, type) {
1209         var imgWidth = img.width, imgHeight = img.height;
1210         
1211         //**IE10** is not giving correct width using img.width so we use $(img).width()
1212         imgWidth = imgWidth > 0 ? imgWidth : $(img).width()
1213         imgHeight = imgHeight > 0 ? imgHeight : $(img).height()
1214
1215         var previewSize = false, previewHeight = false, previewWidth = false;
1216         if(typeof size == 'number') previewSize = size;
1217         else if(size instanceof Object) {
1218             if(size['previewWidth'] && !size['previewHeight']) previewWidth = size['previewWidth'];
1219             else if(size['previewHeight'] && !size['previewWidth']) previewHeight = size['previewHeight'];
1220             else if(size['previewWidth'] && size['previewHeight']) {
1221                 previewWidth = size['previewWidth'];
1222                 previewHeight = size['previewHeight'];
1223             }
1224         }
1225         
1226         if(previewSize) {
1227             if(imgWidth > imgHeight) {
1228                 previewWidth = previewSize;
1229                 previewHeight = parseInt(imgHeight/imgWidth * previewWidth);
1230             } else {
1231                 previewHeight = previewSize;
1232                 previewWidth = parseInt(imgWidth/imgHeight * previewHeight);
1233             }
1234         }
1235         else {
1236             if(!previewHeight && previewWidth) {
1237                 previewHeight = parseInt(imgHeight/imgWidth * previewWidth);
1238             }
1239             else if(previewHeight && !previewWidth) {
1240                 previewWidth = parseInt(imgWidth/imgHeight * previewHeight);
1241             }
1242         }
1243         
1244         
1245     
1246         var dataURL
1247         try {
1248             var canvas = document.createElement('canvas');
1249             canvas.width = previewWidth; canvas.height = previewHeight;
1250             var context = canvas.getContext('2d');
1251             context.drawImage(img, 0, 0, imgWidth, imgHeight, 0, 0, previewWidth, previewHeight);
1252             dataURL = canvas.toDataURL(/*type == 'image/jpeg' ? type : 'image/png', 10*/)
1253         } catch(e) {
1254             dataURL = null;
1255         }
1256         if(! dataURL) return null;
1257         
1258
1259         //there was only one image that failed in firefox completely randomly! so let's double check things
1260         if( !( /^data\:image\/(png|jpe?g|gif);base64,[0-9A-Za-z\+\/\=]+$/.test(dataURL)) ) dataURL = null;
1261         if(! dataURL) return null;
1262         
1263
1264         return {src: dataURL, previewWidth: previewWidth, previewHeight: previewHeight, width: imgWidth, height: imgHeight};
1265     }
1266     
1267
1268     
1269     var processFiles = function(file_list, dropped) {
1270         var ret = checkFileList.call(this, file_list, dropped);
1271         if(ret === -1) {
1272             this.reset_input();
1273             return false;
1274         }
1275         if( !ret || ret.length == 0 ) {
1276             if( !this.$element.data('ace_input_files') ) this.reset_input();
1277             //if nothing selected before, reset because of the newly unacceptable (ret=false||length=0) selection
1278             //otherwise leave the previous selection intact?!!!
1279             return false;
1280         }
1281         if (ret instanceof Array || (hasFileList && ret instanceof FileList)) file_list = ret;
1282         
1283         
1284         ret = true;
1285         if(this.settings.before_change) ret = this.settings.before_change.call(this.element, file_list, dropped);
1286         if(ret === -1) {
1287             this.reset_input();
1288             return false;
1289         }
1290         if(!ret || ret.length == 0) {
1291             if( !this.$element.data('ace_input_files') ) this.reset_input();
1292             return false;
1293         }
1294         
1295         //inside before_change you can return a modified File Array as result
1296         if (ret instanceof Array || (hasFileList && ret instanceof FileList)) file_list = ret;
1297         
1298         return file_list;
1299     }
1300     
1301     
1302     var getExtRegex = function(ext) {
1303         if(!ext) return null;
1304         if(typeof ext === 'string') ext = [ext];
1305         if(ext.length == 0) return null;
1306         return new RegExp("\.(?:"+ext.join('|')+")$", "i");
1307     }
1308     var getMimeRegex = function(mime) {
1309         if(!mime) return null;
1310         if(typeof mime === 'string') mime = [mime];
1311         if(mime.length == 0) return null;
1312         return new RegExp("^(?:"+mime.join('|').replace(/\//g, "\\/")+")$", "i");
1313     }
1314     var checkFileList = function(files, dropped) {
1315         var allowExt   = getExtRegex(this.settings.allowExt);
1316
1317         var denyExt    = getExtRegex(this.settings.denyExt);
1318         
1319         var allowMime  = getMimeRegex(this.settings.allowMime);
1320
1321         var denyMime   = getMimeRegex(this.settings.denyMime);
1322
1323         var maxSize    = this.settings.maxSize || false;
1324         
1325         if( !(allowExt || denyExt || allowMime || denyMime || maxSize) ) return true;//no checking required
1326
1327
1328         var safe_files = [];
1329         var error_list = {}
1330         for(var f = 0; f < files.length; f++) {
1331             var file = files[f];
1332             
1333             //file is either a string(file name) or a File object
1334             var filename = !hasFile ? file : file.name;
1335             if( allowExt && !allowExt.test(filename) ) {
1336                 //extension not matching whitelist, so drop it
1337                 if(!('ext' in error_list)) error_list['ext'] = [];
1338                  error_list['ext'].push(filename);
1339                 
1340                 continue;
1341             } else if( denyExt && denyExt.test(filename) ) {
1342                 //extension is matching blacklist, so drop it
1343                 if(!('ext' in error_list)) error_list['ext'] = [];
1344                  error_list['ext'].push(filename);
1345                 
1346                 continue;
1347             }
1348
1349             var type;
1350             if( !hasFile ) {
1351                 //in browsers that don't support FileReader API
1352                 safe_files.push(file);
1353                 continue;
1354             }
1355             else if((type = $.trim(file.type)).length > 0) {
1356                 //there is a mimetype for file so let's check against are rules
1357                 if( allowMime && !allowMime.test(type) ) {
1358                     //mimeType is not matching whitelist, so drop it
1359                     if(!('mime' in error_list)) error_list['mime'] = [];
1360                      error_list['mime'].push(filename);
1361                     continue;
1362                 }
1363                 else if( denyMime && denyMime.test(type) ) {
1364                     //mimeType is matching blacklist, so drop it
1365                     if(!('mime' in error_list)) error_list['mime'] = [];
1366                      error_list['mime'].push(filename);
1367                     continue;
1368                 }
1369             }
1370
1371             if( maxSize && file.size > maxSize ) {
1372                 //file size is not acceptable
1373                 if(!('size' in error_list)) error_list['size'] = [];
1374                  error_list['size'].push(filename);
1375                 continue;
1376             }
1377
1378             safe_files.push(file)
1379         }
1380         
1381     
1382         
1383         if(safe_files.length == files.length) return files;//return original file list if all are valid
1384
1385         /////////
1386         var error_count = {'ext': 0, 'mime': 0, 'size': 0}
1387         if( 'ext' in error_list ) error_count['ext'] = error_list['ext'].length;
1388         if( 'mime' in error_list ) error_count['mime'] = error_list['mime'].length;
1389         if( 'size' in error_list ) error_count['size'] = error_list['size'].length;
1390         
1391         var event
1392         this.$element.trigger(
1393             event = new $.Event('file.error.ace'), 
1394             {
1395                 'file_count': files.length,
1396                 'invalid_count' : files.length - safe_files.length,
1397                 'error_list' : error_list,
1398                 'error_count' : error_count,
1399                 'dropped': dropped
1400             }
1401         );
1402         if ( event.isDefaultPrevented() ) return -1;//it will reset input
1403         //////////
1404
1405         return safe_files;//return safe_files
1406     }
1407
1408
1409
1410     ///////////////////////////////////////////
1411     $.fn.aceFileInput = $.fn.ace_file_input = function (option,value) {
1412         var retval;
1413
1414         var $set = this.each(function () {
1415             var $this = $(this);
1416             var data = $this.data('ace_file_input');
1417             var options = typeof option === 'object' && option;
1418
1419             if (!data) $this.data('ace_file_input', (data = new Ace_File_Input(this, options)));
1420             if (typeof option === 'string') retval = data[option](value);
1421         });
1422
1423         return (retval === undefined) ? $set : retval;
1424     };
1425
1426
1427     $.fn.ace_file_input.defaults = {
1428         style: false,
1429         no_file: 'No File ...',
1430         no_icon: 'fa fa-upload',
1431         btn_choose: 'Choose',
1432         btn_change: 'Change',
1433         icon_remove: 'fa fa-times',
1434         droppable: false,
1435         thumbnail: false,//large, fit, small
1436         
1437         allowExt: null,
1438         denyExt: null,
1439         allowMime: null,
1440         denyMime: null,
1441         maxSize: false,
1442         
1443         previewSize: false,
1444         previewWidth: false,
1445         previewHeight: false,
1446         
1447         //callbacks
1448         before_change: null,
1449         before_remove: null,
1450         preview_error: null
1451      }
1452
1453
1454 })(window.jQuery);
1455 ;/**
1456   <b>Bootstrap 2 typeahead plugin.</b> With Bootstrap <u>3</u> it's been dropped in favor of a more advanced separate plugin.
1457   Pretty good for simple cases such as autocomplete feature of the search box and required for <u class="text-danger">Tag input</u> plugin.
1458 */
1459
1460 /* =============================================================
1461  * bootstrap-typeahead.js v2.3.2
1462  * http://twitter.github.com/bootstrap/javascript.html#typeahead
1463  * =============================================================
1464  * Copyright 2012 Twitter, Inc.
1465  *
1466  * Licensed under the Apache License, Version 2.0 (the "License");
1467  * you may not use this file except in compliance with the License.
1468  * You may obtain a copy of the License at
1469  *
1470  * http://www.apache.org/licenses/LICENSE-2.0
1471  *
1472  * Unless required by applicable law or agreed to in writing, software
1473  * distributed under the License is distributed on an "AS IS" BASIS,
1474  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1475  * See the License for the specific language governing permissions and
1476  * limitations under the License.
1477  * ============================================================ */
1478
1479
1480 !function($){
1481
1482   "use strict"; // jshint ;_;
1483
1484
1485  /* TYPEAHEAD PUBLIC CLASS DEFINITION
1486   * ================================= */
1487
1488   var Typeahead = function (element, options) {
1489     this.$element = $(element)
1490     this.options = $.extend({}, $.fn.bs_typeahead.defaults, options)
1491     this.matcher = this.options.matcher || this.matcher
1492     this.sorter = this.options.sorter || this.sorter
1493     this.highlighter = this.options.highlighter || this.highlighter
1494     this.updater = this.options.updater || this.updater
1495     this.source = this.options.source
1496     this.$menu = $(this.options.menu)
1497     this.shown = false
1498     this.listen()
1499   }
1500
1501   Typeahead.prototype = {
1502
1503     constructor: Typeahead
1504
1505   , select: function () {
1506       var val = this.$menu.find('.active').attr('data-value')
1507       this.$element
1508         .val(this.updater(val))
1509         .change()
1510       return this.hide()
1511     }
1512
1513   , updater: function (item) {
1514       return item
1515     }
1516
1517   , show: function () {
1518       var pos = $.extend({}, this.$element.position(), {
1519         height: this.$element[0].offsetHeight
1520       })
1521
1522       this.$menu
1523         .insertAfter(this.$element)
1524         .css({
1525           top: pos.top + pos.height
1526         , left: pos.left
1527         })
1528         .show()
1529
1530       this.shown = true
1531       return this
1532     }
1533
1534   , hide: function () {
1535       this.$menu.hide()
1536       this.shown = false
1537       return this
1538     }
1539
1540   , lookup: function (event) {
1541       var items
1542
1543       this.query = this.$element.val()
1544
1545       if (!this.query || this.query.length < this.options.minLength) {
1546         return this.shown ? this.hide() : this
1547       }
1548
1549       items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source
1550
1551       return items ? this.process(items) : this
1552     }
1553
1554   , process: function (items) {
1555       var that = this
1556
1557       items = $.grep(items, function (item) {
1558         return that.matcher(item)
1559       })
1560
1561       items = this.sorter(items)
1562
1563       if (!items.length) {
1564         return this.shown ? this.hide() : this
1565       }
1566
1567       return this.render(items.slice(0, this.options.items)).show()
1568     }
1569
1570   , matcher: function (item) {
1571       return ~item.toLowerCase().indexOf(this.query.toLowerCase())
1572     }
1573
1574   , sorter: function (items) {
1575       var beginswith = []
1576         , caseSensitive = []
1577         , caseInsensitive = []
1578         , item
1579
1580       while (item = items.shift()) {
1581         if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
1582         else if (~item.indexOf(this.query)) caseSensitive.push(item)
1583         else caseInsensitive.push(item)
1584       }
1585
1586       return beginswith.concat(caseSensitive, caseInsensitive)
1587     }
1588
1589   , highlighter: function (item) {
1590       var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')
1591       return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
1592         return '<strong>' + match + '</strong>'
1593       })
1594     }
1595
1596   , render: function (items) {
1597       var that = this
1598
1599       items = $(items).map(function (i, item) {
1600         i = $(that.options.item).attr('data-value', item)
1601         i.find('a').html(that.highlighter(item))
1602         return i[0]
1603       })
1604
1605       items.first().addClass('active')
1606       this.$menu.html(items)
1607       return this
1608     }
1609
1610   , next: function (event) {
1611       var active = this.$menu.find('.active').removeClass('active')
1612         , next = active.next()
1613
1614       if (!next.length) {
1615         next = $(this.$menu.find('li')[0])
1616       }
1617
1618       next.addClass('active')
1619     }
1620
1621   , prev: function (event) {
1622       var active = this.$menu.find('.active').removeClass('active')
1623         , prev = active.prev()
1624
1625       if (!prev.length) {
1626         prev = this.$menu.find('li').last()
1627       }
1628
1629       prev.addClass('active')
1630     }
1631
1632   , listen: function () {
1633       this.$element
1634         .on('focus',    $.proxy(this.focus, this))
1635         .on('blur',     $.proxy(this.blur, this))
1636         .on('keypress', $.proxy(this.keypress, this))
1637         .on('keyup',    $.proxy(this.keyup, this))
1638
1639       if (this.eventSupported('keydown')) {
1640         this.$element.on('keydown', $.proxy(this.keydown, this))
1641       }
1642
1643       this.$menu
1644         .on('click', $.proxy(this.click, this))
1645         .on('mouseenter', 'li', $.proxy(this.mouseenter, this))
1646         .on('mouseleave', 'li', $.proxy(this.mouseleave, this))
1647     }
1648
1649   , eventSupported: function(eventName) {
1650       var isSupported = eventName in this.$element
1651       if (!isSupported) {
1652         this.$element.setAttribute(eventName, 'return;')
1653         isSupported = typeof this.$element[eventName] === 'function'
1654       }
1655       return isSupported
1656     }
1657
1658   , move: function (e) {
1659       if (!this.shown) return
1660
1661       switch(e.keyCode) {
1662         case 9: // tab
1663         case 13: // enter
1664         case 27: // escape
1665           e.preventDefault()
1666           break
1667
1668         case 38: // up arrow
1669           e.preventDefault()
1670           this.prev()
1671           break
1672
1673         case 40: // down arrow
1674           e.preventDefault()
1675           this.next()
1676           break
1677       }
1678
1679       e.stopPropagation()
1680     }
1681
1682   , keydown: function (e) {
1683       this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40,38,9,13,27])
1684       this.move(e)
1685     }
1686
1687   , keypress: function (e) {
1688       if (this.suppressKeyPressRepeat) return
1689       this.move(e)
1690     }
1691
1692   , keyup: function (e) {
1693       switch(e.keyCode) {
1694         case 40: // down arrow
1695         case 38: // up arrow
1696         case 16: // shift
1697         case 17: // ctrl
1698         case 18: // alt
1699           break
1700
1701         case 9: // tab
1702         case 13: // enter
1703           if (!this.shown) return
1704           this.select()
1705           break
1706
1707         case 27: // escape
1708           if (!this.shown) return
1709           this.hide()
1710           break
1711
1712         default:
1713           this.lookup()
1714       }
1715
1716       e.stopPropagation()
1717       e.preventDefault()
1718   }
1719
1720   , focus: function (e) {
1721       this.focused = true
1722     }
1723
1724   , blur: function (e) {
1725       this.focused = false
1726       if (!this.mousedover && this.shown) this.hide()
1727     }
1728
1729   , click: function (e) {
1730       e.stopPropagation()
1731       e.preventDefault()
1732       this.select()
1733       this.$element.focus()
1734     }
1735
1736   , mouseenter: function (e) {
1737       this.mousedover = true
1738       this.$menu.find('.active').removeClass('active')
1739       $(e.currentTarget).addClass('active')
1740     }
1741
1742   , mouseleave: function (e) {
1743       this.mousedover = false
1744       if (!this.focused && this.shown) this.hide()
1745     }
1746
1747   }
1748
1749
1750   /* TYPEAHEAD PLUGIN DEFINITION
1751    * =========================== */
1752
1753   var old = $.fn.bs_typeahead
1754
1755   $.fn.bs_typeahead = function (option) {
1756     return this.each(function () {
1757       var $this = $(this)
1758         , data = $this.data('bs_typeahead')
1759         , options = typeof option == 'object' && option
1760       if (!data) $this.data('bs_typeahead', (data = new Typeahead(this, options)))
1761       if (typeof option == 'string') data[option]()
1762     })
1763   }
1764
1765   $.fn.bs_typeahead.defaults = {
1766     source: []
1767   , items: 8
1768   , menu: '<ul class="typeahead dropdown-menu"></ul>'
1769   , item: '<li><a href="#"></a></li>'
1770   , minLength: 1
1771   }
1772
1773   $.fn.bs_typeahead.Constructor = Typeahead
1774
1775
1776  /* TYPEAHEAD NO CONFLICT
1777   * =================== */
1778
1779   $.fn.bs_typeahead.noConflict = function () {
1780     $.fn.bs_typeahead = old
1781     return this
1782   }
1783
1784
1785  /* TYPEAHEAD DATA-API
1786   * ================== */
1787
1788   $(document).on('focus.bs_typeahead.data-api', '[data-provide="bs_typeahead"]', function (e) {
1789     var $this = $(this)
1790     if ($this.data('bs_typeahead')) return
1791     $this.bs_typeahead($this.data())
1792   })
1793
1794 }(window.jQuery);;/**
1795  <b>Wysiwyg</b>. A wrapper for Bootstrap wyswiwyg plugin.
1796  It's just a wrapper so you still need to include Bootstrap wysiwyg script first.
1797 */
1798 (function($ , undefined) {
1799     $.fn.ace_wysiwyg = function($options , undefined) {
1800         var options = $.extend( {
1801             speech_button:true,
1802             wysiwyg:{}
1803         }, $options);
1804
1805         var color_values = [
1806             '#ac725e','#d06b64','#f83a22','#fa573c','#ff7537','#ffad46',
1807             '#42d692','#16a765','#7bd148','#b3dc6c','#fbe983','#fad165',
1808             '#92e1c0','#9fe1e7','#9fc6e7','#4986e7','#9a9cff','#b99aff',
1809             '#c2c2c2','#cabdbf','#cca6ac','#f691b2','#cd74e6','#a47ae2',
1810             '#444444'
1811         ]
1812
1813         var button_defaults =
1814         {
1815             'font' : {
1816                 values:['Arial', 'Courier', 'Comic Sans MS', 'Helvetica', 'Open Sans', 'Tahoma', 'Verdana'],
1817                 icon:'fa fa-font',
1818                 title:'Font'
1819             },
1820             'fontSize' : {
1821                 values:{5:'Huge', 3:'Normal', 1:'Small'},
1822                 icon:'fa fa-text-height',
1823                 title:'Font Size'
1824             },
1825             'bold' : {
1826                 icon : 'fa fa-bold',
1827                 title : 'Bold (Ctrl/Cmd+B)'
1828             },
1829             'italic' : {
1830                 icon : 'fa fa-italic',
1831                 title : 'Italic (Ctrl/Cmd+I)'
1832             },
1833             'strikethrough' : {
1834                 icon : 'fa fa-strikethrough',
1835                 title : 'Strikethrough'
1836             },
1837             'underline' : {
1838                 icon : 'fa fa-underline',
1839                 title : 'Underline'
1840             },
1841             'insertunorderedlist' : {
1842                 icon : 'fa fa-list-ul',
1843                 title : 'Bullet list'
1844             },
1845             'insertorderedlist' : {
1846                 icon : 'fa fa-list-ol',
1847                 title : 'Number list'
1848             },
1849             'outdent' : {
1850                 icon : 'fa fa-outdent',
1851                 title : 'Reduce indent (Shift+Tab)'
1852             },
1853             'indent' : {
1854                 icon : 'fa fa-indent',
1855                 title : 'Indent (Tab)'
1856             },
1857             'justifyleft' : {
1858                 icon : 'fa fa-align-left',
1859                 title : 'Align Left (Ctrl/Cmd+L)'
1860             },
1861             'justifycenter' : {
1862                 icon : 'fa fa-align-center',
1863                 title : 'Center (Ctrl/Cmd+E)'
1864             },
1865             'justifyright' : {
1866                 icon : 'fa fa-align-right',
1867                 title : 'Align Right (Ctrl/Cmd+R)'
1868             },
1869             'justifyfull' : {
1870                 icon : 'fa fa-align-justify',
1871                 title : 'Justify (Ctrl/Cmd+J)'
1872             },
1873             'createLink' : {
1874                 icon : 'fa fa-link',
1875                 title : 'Hyperlink',
1876                 button_text : 'Add',
1877                 placeholder : 'URL',
1878                 button_class : 'btn-primary'
1879             },
1880             'unlink' : {
1881                 icon : 'fa fa-chain-broken',
1882                 title : 'Remove Hyperlink'
1883             },
1884             'insertImage' : {
1885                 icon : 'fa fa-picture-o',
1886                 title : 'Insert picture',
1887                 button_text : '<i class="'+ ace.vars['icon'] + 'fa fa-file"></i> Choose Image &hellip;',
1888                 placeholder : 'Image URL',
1889                 button_insert : 'Insert',
1890                 button_class : 'btn-success',
1891                 button_insert_class : 'btn-primary',
1892                 choose_file: true //show the choose file button?
1893             },
1894             'foreColor' : {
1895                 values : color_values,
1896                 title : 'Change Color'
1897             },
1898             'backColor' : {
1899                 values : color_values,
1900                 title : 'Change Background Color'
1901             },
1902             'undo' : {
1903                 icon : 'fa fa-undo',
1904                 title : 'Undo (Ctrl/Cmd+Z)'
1905             },
1906             'redo' : {
1907                 icon : 'fa fa-repeat',
1908                 title : 'Redo (Ctrl/Cmd+Y)'
1909             },
1910             'viewSource' : {
1911                 icon : 'fa fa-code',
1912                 title : 'View Source'
1913             }
1914         }
1915         
1916         var toolbar_buttons =
1917         options.toolbar ||
1918         [
1919             'font',
1920             null,
1921             'fontSize',
1922             null,
1923             'bold',
1924             'italic',
1925             'strikethrough',
1926             'underline',
1927             null,
1928             'insertunorderedlist',
1929             'insertorderedlist',
1930             'outdent',
1931             'indent',
1932             null,
1933             'justifyleft',
1934             'justifycenter',
1935             'justifyright',
1936             'justifyfull',
1937             null,
1938             'createLink',
1939             'unlink',
1940             null,
1941             'insertImage',
1942             null,
1943             'foreColor',
1944             null,
1945             'undo',
1946             'redo',
1947             null,
1948             'viewSource'
1949         ]
1950
1951
1952         this.each(function() {
1953             var toolbar = ' <div class="wysiwyg-toolbar btn-toolbar center"> <div class="btn-group"> ';
1954
1955             for(var tb in toolbar_buttons) if(toolbar_buttons.hasOwnProperty(tb)) {
1956                 var button = toolbar_buttons[tb];
1957                 if(button === null){
1958                     toolbar += ' </div> <div class="btn-group"> ';
1959                     continue;
1960                 }
1961                 
1962                 if(typeof button == "string" && button in button_defaults) {
1963                     button = button_defaults[button];
1964                     button.name = toolbar_buttons[tb];
1965                 } else if(typeof button == "object" && button.name in button_defaults) {
1966                     button = $.extend(button_defaults[button.name] , button);
1967                 }
1968                 else continue;
1969                 
1970                 var className = "className" in button ? button.className : 'btn-default';
1971                 switch(button.name) {
1972                     case 'font':
1973                         toolbar += ' <a class="btn btn-sm '+className+' dropdown-toggle" data-toggle="dropdown" title="'+button.title+'"><i class="'+ ace.vars['icon'] + button.icon+'"></i><i class="' + ace.vars['icon'] + 'fa fa-angle-down icon-on-right"></i></a> ';
1974                         toolbar += ' <ul class="dropdown-menu dropdown-light dropdown-caret">';
1975                         for(var font in button.values)
1976                             if(button.values.hasOwnProperty(font))
1977                                 toolbar += ' <li><a data-edit="fontName ' + button.values[font] +'" style="font-family:\''+ button.values[font]  +'\'">'+button.values[font]  + '</a></li> '
1978                         toolbar += ' </ul>';
1979                     break;
1980
1981                     case 'fontSize':
1982                         toolbar += ' <a class="btn btn-sm '+className+' dropdown-toggle" data-toggle="dropdown" title="'+button.title+'"><i class="'+ ace.vars['icon'] + button.icon+'"></i>&nbsp;<i class="'+ ace.vars['icon'] + 'fa fa-angle-down icon-on-right"></i></a> ';
1983                         toolbar += ' <ul class="dropdown-menu dropdown-light dropdown-caret"> ';
1984                         for(var size in button.values)
1985                             if(button.values.hasOwnProperty(size))
1986                                 toolbar += ' <li><a data-edit="fontSize '+size+'"><font size="'+size+'">'+ button.values[size] +'</font></a></li> '
1987                         toolbar += ' </ul> ';
1988                     break;
1989
1990                     case 'createLink':
1991                         toolbar += ' <div class="btn-group"> <a class="btn btn-sm '+className+' dropdown-toggle" data-toggle="dropdown" title="'+button.title+'"><i class="'+ ace.vars['icon'] + button.icon+'"></i></a> ';
1992                         toolbar += ' <div class="dropdown-menu dropdown-caret dropdown-menu-right">\
1993                              <div class="input-group">\
1994                                 <input class="form-control" placeholder="'+button.placeholder+'" type="text" data-edit="'+button.name+'" />\
1995                                 <span class="input-group-btn">\
1996                                     <button class="btn btn-sm '+button.button_class+'" type="button">'+button.button_text+'</button>\
1997                                 </span>\
1998                              </div>\
1999                         </div> </div>';
2000                     break;
2001
2002                     case 'insertImage':
2003                         toolbar += ' <div class="btn-group"> <a class="btn btn-sm '+className+' dropdown-toggle" data-toggle="dropdown" title="'+button.title+'"><i class="'+ ace.vars['icon'] + button.icon+'"></i></a> ';
2004                         toolbar += ' <div class="dropdown-menu dropdown-caret dropdown-menu-right">\
2005                              <div class="input-group">\
2006                                 <input class="form-control" placeholder="'+button.placeholder+'" type="text" data-edit="'+button.name+'" />\
2007                                 <span class="input-group-btn">\
2008                                     <button class="btn btn-sm '+button.button_insert_class+'" type="button">'+button.button_insert+'</button>\
2009                                 </span>\
2010                              </div>';
2011                             if( button.choose_file && 'FileReader' in window ) toolbar +=
2012                              '<div class="space-2"></div>\
2013                              <label class="center block no-margin-bottom">\
2014                                 <button class="btn btn-sm '+button.button_class+' wysiwyg-choose-file" type="button">'+button.button_text+'</button>\
2015                                 <input type="file" data-edit="'+button.name+'" />\
2016                               </label>'
2017                         toolbar += ' </div> </div>';
2018                     break;
2019
2020                     case 'foreColor':
2021                     case 'backColor':
2022                         toolbar += ' <select class="hide wysiwyg_colorpicker" title="'+button.title+'"> ';
2023                         $.each(button.values, function (_, color) {
2024                             toolbar += ' <option value="' + color + '">' + color + '</option> ';
2025                         });
2026                         toolbar += ' </select> ';
2027                         toolbar += ' <input style="display:none;" disabled class="hide" type="text" data-edit="'+button.name+'" /> ';
2028                     break;
2029
2030                     case 'viewSource':
2031                         toolbar += ' <a class="btn btn-sm '+className+'" data-view="source" title="'+button.title+'"><i class="'+ ace.vars['icon'] + button.icon+'"></i></a> ';
2032                     break;
2033                     default:
2034                         toolbar += ' <a class="btn btn-sm '+className+'" data-edit="'+button.name+'" title="'+button.title+'"><i class="'+ ace.vars['icon'] + button.icon+'"></i></a> ';
2035                     break;
2036                 }
2037             }
2038             toolbar += ' </div> ';
2039             ////////////
2040             var speech_input;
2041             if (options.speech_button && 'onwebkitspeechchange' in (speech_input = document.createElement('input'))) {
2042                 toolbar += ' <input class="wysiwyg-speech-input" type="text" data-edit="inserttext" x-webkit-speech />';
2043             }
2044             speech_input = null;
2045             ////////////
2046             toolbar += ' </div> ';
2047
2048
2049             //if we have a function to decide where to put the toolbar, then call that
2050             if(options.toolbar_place) toolbar = options.toolbar_place.call(this, toolbar);
2051             //otherwise put it just before our DIV
2052             else toolbar = $(this).before(toolbar).prev();
2053
2054             toolbar.find('a[title]').tooltip({animation:false, container:'body'});
2055             toolbar.find('.dropdown-menu input[type=text]').on('click', function() {return false})
2056             .on('change', function() {$(this).closest('.dropdown-menu').siblings('.dropdown-toggle').dropdown('toggle')})
2057             .on('keydown', function (e) {
2058                 if(e.which == 27) {
2059                     this.value = '';
2060                     $(this).change();
2061                 }
2062                 else if(e.which == 13) {
2063                     e.preventDefault();
2064                     e.stopPropagation();
2065                     $(this).change();
2066                 }
2067             });
2068             
2069             toolbar.find('input[type=file]').prev().on(ace.click_event, function (e) { 
2070                 $(this).next().click();
2071             });
2072             toolbar.find('.wysiwyg_colorpicker').each(function() {
2073                 $(this).ace_colorpicker({pull_right:true}).change(function(){
2074                     $(this).nextAll('input').eq(0).val(this.value).change();
2075                 }).next().find('.btn-colorpicker').tooltip({title: this.title, animation:false, container:'body'})
2076             });
2077             
2078             
2079             var self = $(this);
2080             //view source
2081             var view_source = false;
2082             toolbar.find('a[data-view=source]').on('click', function(e){
2083                 e.preventDefault();
2084                 
2085                 if(!view_source) {
2086                     $('<textarea />')
2087                     .css({'width':self.outerWidth(), 'height':self.outerHeight()})
2088                     .val(self.html())
2089                     .insertAfter(self)
2090                     self.hide();
2091                     
2092                     $(this).addClass('active');
2093                 }
2094                 else {
2095                     var textarea = self.next();
2096                     self.html(textarea.val()).show();
2097                     textarea.remove();
2098                     
2099                     $(this).removeClass('active');
2100                 }
2101                 
2102                 view_source = !view_source;
2103             });
2104
2105
2106             var $options = $.extend({}, { activeToolbarClass: 'active' , toolbarSelector : toolbar }, options.wysiwyg || {})
2107             $(this).wysiwyg( $options );
2108         });
2109
2110         return this;
2111     }
2112
2113
2114 })(window.jQuery);
2115
2116 ;/**
2117  <b>Spinner</b>. A wrapper for FuelUX spinner element.
2118  It's just a wrapper so you still need to include FuelUX spinner script first.
2119 */
2120 (function($ , undefined) {
2121     //a wrapper for fuelux spinner
2122     function Ace_Spinner(element , _options) {
2123         var attrib_values = ace.helper.getAttrSettings(element, $.fn.ace_spinner.defaults);
2124         var options = $.extend({}, $.fn.ace_spinner.defaults, _options, attrib_values);
2125     
2126         var max = options.max
2127         max = (''+max).length
2128         var width = parseInt(Math.max((max * 20 + 40) , 90))
2129
2130         var $element = $(element);
2131         
2132         var btn_class = 'btn-sm';//default
2133         var sizing = 2;
2134         if($element.hasClass('input-sm')) {
2135             btn_class = 'btn-xs';
2136             sizing = 1;
2137         }
2138         else if($element.hasClass('input-lg')) {
2139             btn_class = 'btn-lg';
2140             sizing = 3;
2141         }
2142         
2143         if(sizing == 2) width += 25;
2144         else if(sizing == 3) width += 50;
2145         
2146         $element.addClass('spinbox-input form-control text-center').wrap('<div class="ace-spinner middle">')
2147
2148         var $parent_div = $element.closest('.ace-spinner').spinbox(options).wrapInner("<div class='input-group'></div>")
2149         var $spinner = $parent_div.data('fu.spinbox');
2150         
2151         if(options.on_sides)
2152         {
2153             $element
2154             .before('<div class="spinbox-buttons input-group-btn">\
2155                     <button type="button" class="btn spinbox-down '+btn_class+' '+options.btn_down_class+'">\
2156                         <i class="icon-only '+ ace.vars['icon'] + options.icon_down+'"></i>\
2157                     </button>\
2158                 </div>')
2159             .after('<div class="spinbox-buttons input-group-btn">\
2160                     <button type="button" class="btn spinbox-up '+btn_class+' '+options.btn_up_class+'">\
2161                         <i class="icon-only '+ ace.vars['icon'] + options.icon_up+'"></i>\
2162                     </button>\
2163                 </div>');
2164
2165             $parent_div.addClass('touch-spinner')
2166             $parent_div.css('width' , width+'px')
2167         }
2168         else {
2169              $element
2170              .after('<div class="spinbox-buttons input-group-btn">\
2171                     <button type="button" class="btn spinbox-up '+btn_class+' '+options.btn_up_class+'">\
2172                         <i class="icon-only '+ ace.vars['icon'] + options.icon_up+'"></i>\
2173                     </button>\
2174                     <button type="button" class="btn spinbox-down '+btn_class+' '+options.btn_down_class+'">\
2175                         <i class="icon-only '+ ace.vars['icon'] + options.icon_down+'"></i>\
2176                     </button>\
2177                 </div>')
2178
2179             if(ace.vars['touch'] || options.touch_spinner) {
2180                 $parent_div.addClass('touch-spinner')
2181                 $parent_div.css('width' , width+'px')
2182             }
2183             else {
2184                 $element.next().addClass('btn-group-vertical');
2185                 $parent_div.css('width' , width+'px')
2186             }
2187         }
2188
2189         $parent_div.on('changed', function(){
2190             $element.trigger('change')//trigger the input's change event
2191         });
2192
2193         this._call = function(name, arg) {
2194             $spinner[name](arg);
2195         }
2196     }
2197
2198
2199     $.fn.ace_spinner = function(option, value) {
2200         var retval;
2201
2202         var $set = this.each(function() {
2203             var $this = $(this);
2204             var data = $this.data('ace_spinner');
2205             var options = typeof option === 'object' && option;
2206
2207             if (!data) {
2208                 options = $.extend({}, $.fn.ace_spinner.defaults, option);
2209                 $this.data('ace_spinner', (data = new Ace_Spinner(this, options)));
2210             }
2211             if (typeof option === 'string') retval = data._call(option, value);
2212         });
2213
2214         return (retval === undefined) ? $set : retval;
2215     }
2216     
2217     $.fn.ace_spinner.defaults = {
2218         'icon_up' : 'fa fa-chevron-up',
2219         'icon_down': 'fa fa-chevron-down',
2220         
2221         'on_sides': false,        
2222         'btn_up_class': '',
2223         'btn_down_class' : '',
2224         
2225         'max' : 999,
2226         'touch_spinner': false
2227      }
2228
2229
2230 })(window.jQuery);
2231 ;/**
2232  <b>Treeview</b>. A wrapper for FuelUX treeview element.
2233  It's just a wrapper so you still need to include FuelUX treeview script first.
2234 */
2235 (function($ , undefined) {
2236
2237     $.fn.aceTree = $.fn.ace_tree = function(options) {
2238         var $defaults = {
2239             'open-icon' : ace.vars['icon'] + 'fa fa-folder-open',
2240             'close-icon' : ace.vars['icon'] + 'fa fa-folder',
2241             'toggle-icon': ace.vars['icon'] + 'fa fa-play',
2242             'selected-icon' : ace.vars['icon'] + 'fa fa-check',
2243             'unselected-icon' : ace.vars['icon'] + 'fa fa-times',
2244             'base-icon' : ace.vars['icon'] + 'fa',
2245             'folder-open-icon' : 'fa fa-plus-square-o',
2246             'folder-close-icon' : 'fa fa-plus-minus-o',
2247             'loadingHTML': 'Loading...'
2248         }
2249
2250         this.each(function() {
2251         
2252             var attrib_values = ace.helper.getAttrSettings(this, $defaults);
2253             var $options = $.extend({}, $defaults, options, attrib_values);
2254
2255             var $this = $(this);
2256             $this.addClass('tree').attr('role', 'tree');
2257             $this.html(
2258             '<li class="tree-branch hide" data-template="treebranch" role="treeitem" aria-expanded="false">\
2259                 '+($options['folderSelect'] ? '<i class="icon-caret '+$options['folder-open-icon']+'"></i>&nbsp;' : '')+'\
2260                 <div class="tree-branch-header">\
2261                     <span class="tree-branch-name">\
2262                         <i class="icon-folder '+$options['close-icon']+'"></i>\
2263                         <span class="tree-label"></span>\
2264                     </span>\
2265                 </div>\
2266                 <ul class="tree-branch-children" role="group"></ul>\
2267                 <div class="tree-loader" role="alert">'+$options['loadingHTML']+'</div>\
2268             </li>\
2269             <li class="tree-item hide" data-template="treeitem" role="treeitem">\
2270                 <span class="tree-item-name">\
2271                   '+($options['unselected-icon'] == null ? '' : '<i class="icon-item '+$options['unselected-icon']+'"></i>')+'\
2272                   <span class="tree-label"></span>\
2273                 </span>\
2274             </li>');
2275             
2276             $this.addClass($options['selectable'] == true ? 'tree-selectable' : 'tree-unselectable');
2277             
2278             $this.tree($options);
2279         });
2280
2281         return this;
2282     }
2283
2284 })(window.jQuery);
2285 ;/**
2286  <b>Wizard</b>. A wrapper for FuelUX wizard element.
2287  It's just a wrapper so you still need to include FuelUX wizard script first.
2288 */
2289 (function($ , undefined) {
2290     $.fn.aceWizard = $.fn.ace_wizard = function(options) {
2291
2292         this.each(function() {
2293             var $this = $(this);
2294             $this.wizard();
2295             
2296             if(ace.vars['old_ie']) $this.find('ul.steps > li').last().addClass('last-child');
2297
2298             var buttons = (options && options['buttons']) ? $(options['buttons']) : $this.siblings('.wizard-actions').eq(0);
2299             var $wizard = $this.data('fu.wizard');
2300             $wizard.$prevBtn.remove();
2301             $wizard.$nextBtn.remove();
2302             
2303             $wizard.$prevBtn = buttons.find('.btn-prev').eq(0).on(ace.click_event,  function(){
2304                 $wizard.previous();
2305             }).attr('disabled', 'disabled');
2306             $wizard.$nextBtn = buttons.find('.btn-next').eq(0).on(ace.click_event,  function(){
2307                 $wizard.next();
2308             }).removeAttr('disabled');
2309             $wizard.nextText = $wizard.$nextBtn.text();
2310             
2311             var step = options && ((options.selectedItem && options.selectedItem.step) || options.step);
2312             if(step) {
2313                 $wizard.currentStep = step;
2314                 $wizard.setState();
2315             }
2316         });
2317
2318         return this;
2319     }
2320
2321 })(window.jQuery);
2322 ;/**
2323  <b>Content Slider</b>. with custom content and elements based on Bootstrap modals.
2324 */
2325 (function($ , undefined) {
2326     var $window = $(window);
2327
2328     function Aside(modal, settings) {
2329         var self = this;
2330     
2331         var $modal = $(modal);
2332         var placement = 'right', vertical = false;
2333         var hasFade = $modal.hasClass('fade');//Bootstrap enables transition only when modal is ".fade"
2334
2335         var attrib_values = ace.helper.getAttrSettings(modal, $.fn.ace_aside.defaults);
2336         this.settings = $.extend({}, $.fn.ace_aside.defaults, settings, attrib_values);
2337         
2338         //if no scroll style specified and modal has dark background, let's make scrollbars 'white'
2339         if(this.settings.background && !settings.scroll_style && !attrib_values.scroll_style) { 
2340             this.settings.scroll_style = 'scroll-white no-track';
2341         }
2342
2343         
2344         this.container = this.settings.container;
2345         if(this.container) {
2346             try {
2347                 if( $(this.container).get(0) == document.body ) this.container = null;
2348             } catch(e) {}
2349         }
2350         if(this.container) {
2351             this.settings.backdrop = false;//no backdrop when inside another element?
2352             $modal.addClass('aside-contained');
2353         }
2354
2355         
2356         var dialog = $modal.find('.modal-dialog');
2357         var content = $modal.find('.modal-content');
2358         var delay = 300;
2359         
2360         this.initiate = function() {
2361             modal.className = modal.className.replace(/(\s|^)aside\-(right|top|left|bottom)(\s|$)/ig , '$1$3');
2362
2363             placement = this.settings.placement;
2364             if(placement) placement = $.trim(placement.toLowerCase());
2365             if(!placement || !(/right|top|left|bottom/.test(placement))) placement = 'right';
2366
2367             $modal.attr('data-placement', placement);
2368             $modal.addClass('aside-' + placement);
2369             
2370             if( /right|left/.test(placement) ) {
2371                 vertical = true;
2372                 $modal.addClass('aside-vc');//vertical
2373             }
2374             else $modal.addClass('aside-hz');//horizontal
2375             
2376             if( this.settings.fixed ) $modal.addClass('aside-fixed');
2377             if( this.settings.background ) $modal.addClass('aside-dark');
2378             if( this.settings.offset ) $modal.addClass('navbar-offset');
2379             
2380             if( !this.settings.transition ) $modal.addClass('transition-off');
2381             
2382             $modal.addClass('aside-hidden');
2383
2384             this.insideContainer();
2385             
2386             /////////////////////////////
2387             
2388             dialog = $modal.find('.modal-dialog');
2389             content = $modal.find('.modal-content');
2390             
2391             if(!this.settings.body_scroll) {
2392                 //don't allow body scroll when modal is open
2393                 $modal.on('mousewheel.aside DOMMouseScroll.aside touchmove.aside pointermove.aside', function(e) {
2394                     if( !$.contains(content[0], e.target) ) {
2395                         e.preventDefault();
2396                         return false;
2397                     }
2398                 })
2399             }
2400             
2401             if( this.settings.backdrop == false ) {
2402                 $modal.addClass('no-backdrop');
2403             }
2404         }
2405         
2406         
2407         this.show = function() {
2408             if(this.settings.backdrop == false) {
2409               try {
2410                 $modal.data('bs.modal').$backdrop.remove();
2411               } catch(e){}
2412             }
2413     
2414             if(this.container) $(this.container).addClass('overflow-hidden');
2415             else $modal.css('position', 'fixed')
2416             
2417             $modal.removeClass('aside-hidden');
2418         }
2419         
2420         this.hide = function() {
2421             if(this.container) {
2422                 this.container.addClass('overflow-hidden');
2423                 
2424                 if(ace.vars['firefox']) {
2425                     //firefox needs a bit of forcing re-calculation
2426                     modal.offsetHeight;
2427                 }
2428             }
2429         
2430             toggleButton();
2431             
2432             if(ace.vars['transition'] && !hasFade) {
2433                 $modal.one('bsTransitionEnd', function() {
2434                     $modal.addClass('aside-hidden');
2435                     $modal.css('position', '');
2436                     
2437                     if(self.container) self.container.removeClass('overflow-hidden');
2438                 }).emulateTransitionEnd(delay);
2439             }
2440         }
2441         
2442         this.shown = function() {
2443             toggleButton();
2444             $('body').removeClass('modal-open').css('padding-right', '');
2445             
2446             if( this.settings.backdrop == 'invisible' ) {
2447               try {
2448                 $modal.data('bs.modal').$backdrop.css('opacity', 0);
2449               } catch(e){}
2450             }
2451
2452             var size = !vertical ? dialog.height() : content.height();
2453             if(!ace.vars['touch']) {
2454                 if(!content.hasClass('ace-scroll')) {
2455                     content.ace_scroll({
2456                             size: size,
2457                             reset: true,
2458                             mouseWheelLock: true,
2459                             lockAnyway: !this.settings.body_scroll,
2460                             styleClass: this.settings.scroll_style,
2461                             'observeContent': true,
2462                             'hideOnIdle': !ace.vars['old_ie'],
2463                             'hideDelay': 1500
2464                     })
2465                 }
2466             }
2467             else {
2468                 content.addClass('overflow-scroll').css('max-height', size+'px');
2469             }
2470
2471             $window
2472             .off('resize.modal.aside')
2473             .on('resize.modal.aside', function() {
2474                 if(!ace.vars['touch']) {
2475                   content.ace_scroll('disable');//to get correct size when going from small window size to large size
2476                     var size = !vertical ? dialog.height() : content.height();
2477                     content
2478                     .ace_scroll('update', {'size': size})
2479                     .ace_scroll('enable')
2480                     .ace_scroll('reset');
2481                 }
2482                 else content.css('max-height', (!vertical ? dialog.height() : content.height())+'px');
2483             }).triggerHandler('resize.modal.aside');
2484             
2485             
2486             ///////////////////////////////////////////////////////////////////////////
2487             if(self.container && ace.vars['transition'] && !hasFade) {
2488                 $modal.one('bsTransitionEnd', function() {
2489                     self.container.removeClass('overflow-hidden')
2490                 }).emulateTransitionEnd(delay);
2491             }
2492         }
2493         
2494         
2495         this.hidden = function() {
2496             $window.off('.aside')
2497             //$modal.off('.aside')
2498             //            
2499             if( !ace.vars['transition'] || hasFade ) {
2500                 $modal.addClass('aside-hidden');
2501                 $modal.css('position', '');
2502             }
2503         }
2504         
2505         
2506         this.insideContainer = function() {
2507             var container = $('.main-container');
2508
2509             var dialog = $modal.find('.modal-dialog');
2510             dialog.css({'right': '', 'left': ''});
2511             if( container.hasClass('container') ) {
2512                 var flag = false;
2513                 if(vertical == true) {
2514                     dialog.css( placement, parseInt(($window.width() - container.width()) / 2) );
2515                     flag = true;
2516                 }
2517
2518                 //strange firefox issue, not redrawing properly on window resize (zoom in/out)!!!!
2519                 //--- firefix is still having issue!
2520                 if(flag && ace.vars['firefox']) {
2521                     ace.helper.redraw(container[0]);
2522                 }
2523             }
2524         }
2525         
2526         this.flip = function() {
2527             var flipSides = {right : 'left', left : 'right', top: 'bottom', bottom: 'top'};
2528             $modal.removeClass('aside-'+placement).addClass('aside-'+flipSides[placement]);
2529             placement = flipSides[placement];
2530         }
2531
2532         var toggleButton = function() {
2533             var btn = $modal.find('.aside-trigger');
2534             if(btn.length == 0) return;
2535             btn.toggleClass('open');
2536             
2537             var icon = btn.find(ace.vars['.icon']);
2538             if(icon.length == 0) return;
2539             icon.toggleClass(icon.attr('data-icon1') + " " + icon.attr('data-icon2'));
2540         }
2541         
2542
2543         this.initiate();
2544         
2545         if(this.container) this.container = $(this.container);
2546         $modal.appendTo(this.container || 'body'); 
2547     }
2548
2549
2550     $(document)
2551     .on('show.bs.modal', '.modal.aside', function(e) {
2552         $('.aside.in').modal('hide');//??? hide previous open ones?
2553         $(this).ace_aside('show');
2554     })
2555     .on('hide.bs.modal', '.modal.aside', function(e) {
2556         $(this).ace_aside('hide');
2557     })
2558     .on('shown.bs.modal', '.modal.aside', function(e) {
2559         $(this).ace_aside('shown');
2560     })
2561     .on('hidden.bs.modal', '.modal.aside', function(e) {
2562         $(this).ace_aside('hidden');
2563     })
2564     
2565     
2566
2567     
2568     $(window).on('resize.aside_container', function() {
2569         $('.modal.aside').ace_aside('insideContainer');
2570     });
2571     $(document).on('settings.ace.aside', function(e, event_name) {
2572         if(event_name == 'main_container_fixed') $('.modal.aside').ace_aside('insideContainer');
2573     });
2574
2575     $.fn.aceAside = $.fn.ace_aside = function (option, value) {
2576         var method_call;
2577
2578         var $set = this.each(function () {
2579             var $this = $(this);
2580             var data = $this.data('ace_aside');
2581             var options = typeof option === 'object' && option;
2582
2583             if (!data) $this.data('ace_aside', (data = new Aside(this, options)));
2584             if (typeof option === 'string' && typeof data[option] === 'function') {
2585                 if(value instanceof Array) method_call = data[option].apply(data, value);
2586                 else method_call = data[option](value);
2587             }
2588         });
2589
2590         return (method_call === undefined) ? $set : method_call;
2591     }
2592     
2593     $.fn.ace_aside.defaults = {
2594         fixed: false,
2595         background: false,
2596         offset: false,
2597         body_scroll: false,
2598         transition: true,
2599         scroll_style: 'scroll-dark no-track',
2600         container: null,
2601         backdrop: false,
2602         placement: 'right'
2603      }
2604
2605 })(window.jQuery);