/** * Common AnsPress functions and constructor. * @author Rahul Aryan * @license GPL 3+ * @since 4.0 */ // For preventing global namespace pollution, keep everything in AnsPress object. window.AnsPress = _.extend({ models: {}, views: {}, collections: {}, modals: {}, loadTemplate: function(id){ if(jQuery('#apTemplate').length==0) jQuery('').appendTo('body'); jQuery.get(apTemplateUrl + '/' + id + ".html", function(html){ var tempCont = jQuery('#apTemplate'); tempCont.text(html + tempCont.text()); AnsPress.trigger('templateLoaded'); }); }, getTemplate: function(templateId){ return function(){ if(jQuery('#apTemplate').length==0) return ''; var regex = new RegExp("#START BLOCK "+templateId+" #([\\S\\s]*?)#END BLOCK "+templateId+" #", "g"); var match = regex.exec(jQuery('#apTemplate').text()); if(match == null) return ''; if(match[1]) return match[1]; } }, isJSONString: function(str) { try { return jQuery.parseJSON(str); } catch (e) { return false; } }, ajaxResponse: function(data){ data = jQuery(data); if( typeof data.filter('#ap-response') === 'undefined' ){ console.log('Not a valid AnsPress ajax response.'); return {}; } var parsedJSON = this.isJSONString(data.filter('#ap-response').html()); if(!parsedJSON || parsedJSON === 'undefined' || !_.isObject(parsedJSON)) return {}; return parsedJSON; }, ajax: function(options){ var self = this; options = _.defaults(options, { url: ajaxurl, method: 'POST', }); // Convert data to query string if object. if(_.isString(options.data)) options.data = jQuery.apParseParams(options.data); if(typeof options.data.action === 'undefined') options.data.action = 'ap_ajax'; var success = options.success; delete options.success; options.success = function(data){ var context = options.context||null; var parsedData = self.ajaxResponse(data); if(parsedData.snackbar){ AnsPress.trigger('snackbar', parsedData) } if(typeof success === 'function'){ data = jQuery.isEmptyObject(parsedData) ? data : parsedData; success(data, context); } }; return jQuery.ajax(options); }, uniqueId: function() { return jQuery('.ap-uid').length; }, showLoading: function(elm) { /*hide any existing loading icon*/ AnsPress.hideLoading(elm); var customClass = jQuery(elm).data('loadclass')||''; var isText = jQuery(elm).is('input[type="text"]'); var uid = this.uniqueId(); if(jQuery(elm).is('button')||jQuery(elm).is('.ap-btn')){ jQuery(elm).addClass('show-loading'); $loading = jQuery(''); $loading.height(jQuery(elm).height()); $loading.width(jQuery(elm).height()); jQuery(elm).append($loading); } else { var el = jQuery('
'); jQuery('body').append(el); var offset = jQuery(elm).offset(); var height = jQuery(elm).outerHeight(); var width = isText ? 40 : jQuery(elm).outerWidth(); el.css({ top: offset.top, left: isText ? offset.left + jQuery(elm).outerWidth() - 40 : offset.left, height: height, width: width }); jQuery(elm).data('loading', '#apuid-' + uid); return '#apuid-' + uid; } }, hideLoading: function(elm) { if(jQuery(elm).is('button')||jQuery(elm).is('.ap-btn')){ jQuery(elm).removeClass('show-loading'); jQuery(elm).find('.ap-loading-span').remove(); }else if( 'all' == elm ){ jQuery('.ap-loading-icon').hide(); }else{ jQuery(jQuery(elm).data('loading')).hide(); } }, getUrlParam: function(key) { var qs = jQuery.apParseParams(window.location.href); if(typeof key !== 'undefined') return typeof qs[key] !== 'undefined' ? qs[key] : null; return qs; }, modal: function(name, args){ args = args||{}; if(typeof this.modals[name] !== 'undefined'){ return this.modals[name]; } this.modals[name] = new AnsPress.views.Modal(_.extend({ title: aplang.loading, content: '', size: 'medium' }, args)); jQuery('#anspress').append(this.modals[name].render().$el); return this.modals[name]; }, hideModal: function(name, runCb){ if(typeof runCb === 'undefined') runCb = true; if(typeof this.modals[name] !== 'undefined'){ this.modals[name].hide(runCb); delete this.modals[name]; } }, removeHash: function(){ var scrollV, scrollH, loc = window.location; // Prevent scrolling by storing the page's current scroll offset scrollV = document.body.scrollTop; scrollH = document.body.scrollLeft; if ('pushState' in history){ history.pushState('', document.title, loc.pathname + loc.search); Backbone.history.navigate('/'); } else { loc.hash = ''; } // Restore the scroll offset, should be flicker free document.body.scrollTop = scrollV; document.body.scrollLeft = scrollH; } }, Backbone.Events); _.templateSettings = { evaluate: /<#([\s\S]+?)#>/g, interpolate: /\{\{\{([\s\S]+?)\}\}\}/g, escape: /\{\{([^\}]+?)\}\}(?!\})/g, }; (function($){ //pass in just the context as a $(obj) or a settings JS object $.fn.autogrow = function(opts) { var that = $(this).css({ overflow: 'hidden', resize: 'none' }) //prevent scrollies , selector = that.selector, defaults = { context: $(document) //what to wire events to , animate: true //if you want the size change to animate , speed: 50 //speed of animation , fixMinHeight: true //if you don't want the box to shrink below its initial size , cloneClass: 'autogrowclone' //helper CSS class for clone if you need to add special rules , onInitialize: false //resizes the textareas when the plugin is initialized }; opts = $.isPlainObject(opts) ? opts : { context: opts ? opts : $(document) }; opts = $.extend({}, defaults, opts); that.each(function(i, elem) { var min, clone; elem = $(elem); //if the element is "invisible", we get an incorrect height value //to get correct value, clone and append to the body. if (elem.is(':visible') || parseInt(elem.css('height'), 10) > 0) { min = parseInt(elem.css('height'), 10) || elem.innerHeight(); } else { clone = elem.clone().addClass(opts.cloneClass).val(elem.val()).css({ position: 'absolute', visibility: 'hidden', display: 'block' }); $('body').append(clone); min = clone.innerHeight(); clone.remove(); } if (opts.fixMinHeight) { elem.data('autogrow-start-height', min); //set min height } elem.css('height', min); if (opts.onInitialize && elem.length) { resize.call(elem[0]); } }); opts.context.on('keyup paste focus', selector, resize); function resize(e) { var box = $(this), oldHeight = box.innerHeight(), newHeight = this.scrollHeight, minHeight = box.data('autogrow-start-height') || 0, clone; if (oldHeight < newHeight) { //user is typing this.scrollTop = 0; //try to reduce the top of the content hiding for a second opts.animate ? box.stop().animate({ height: newHeight }, opts.speed) : box.innerHeight(newHeight); } else if (!e || e.which == 8 || e.which == 46 || (e.ctrlKey && e.which == 88)) { //user is deleting, backspacing, or cutting if (oldHeight > minHeight) { //shrink! //this cloning part is not particularly necessary. however, it helps with animation //since the only way to cleanly calculate where to shrink the box to is to incrementally //reduce the height of the box until the $.innerHeight() and the scrollHeight differ. //doing this on an exact clone to figure out the height first and then applying it to the //actual box makes it look cleaner to the user clone = box.clone() //add clone class for extra css rules .addClass(opts.cloneClass) //make "invisible", remove height restriction potentially imposed by existing CSS .css({ position: 'absolute', zIndex: -10, height: '' }) //populate with content for consistent measuring .val(box.val()); box.after(clone); //append as close to the box as possible for best CSS matching for clone do { //reduce height until they don't match newHeight = clone[0].scrollHeight - 1; clone.innerHeight(newHeight); } while (newHeight === clone[0].scrollHeight); newHeight++; //adding one back eliminates a wiggle on deletion clone.remove(); box.focus(); // Fix issue with Chrome losing focus from the textarea. //if user selects all and deletes or holds down delete til beginning //user could get here and shrink whole box newHeight < minHeight && (newHeight = minHeight); oldHeight > newHeight && opts.animate ? box.stop().animate({ height: newHeight }, opts.speed) : box.innerHeight(newHeight); } else { //just set to the minHeight box.innerHeight(minHeight); } } } return that; }; jQuery.fn.apScrollTo = function(elem, toBottom, speed) { toBottom = toBottom||false; var parentPos = $(this).scrollTop() - $(this).offset().top; var top = toBottom ? $(this).offset().top + $(this).height() : $(this).offset().top; $('html, body').stop(); $('html, body').animate({ scrollTop: top }, speed == undefined ? 1000 : speed); if(elem != undefined) $(this).animate({ scrollTop: parentPos + $(elem).offset().top }, speed == undefined ? 1000 : speed); return this; }; AnsPress.views.Snackbar = Backbone.View.extend({ id: 'ap-snackbar', template: '', hover: false, initialize: function(){ AnsPress.on('snackbar', this.show, this); }, events: { 'mouseover': 'toggleHover', 'mouseout': 'toggleHover', }, show: function(data){ var self = this; this.data = data.snackbar; this.data.success = data.success; this.$el.removeClass('snackbar-show'); this.render(); setTimeout(function(){ self.$el.addClass('snackbar-show'); }, 0); this.hide(); }, toggleHover:function(){ clearTimeout(this.hoveTimeOut); this.hover = !this.hover; if(!this.hover) this.hide(); }, hide: function(){ var self = this; if(!self.hover) this.hoveTimeOut = setTimeout(function(){ self.$el.removeClass('snackbar-show'); }, 2000); }, render: function(){ if(this.data){ var t = _.template(this.template); this.$el.html(t(this.data)); } return this; } }); AnsPress.views.Modal = Backbone.View.extend({ id: 'ap-modal', className: 'ap-modal', template: "", events: { 'click [ap="close-modal"]': 'clickHide', 'click [ap="modal-click"]': 'clickAction', }, initialize: function(opt){ opt.title = opt.title||aplang.loading; this.data = opt; }, render: function(){ $('html').css('overflow', 'hidden'); var t = _.template(this.template); this.$el.html(t(this.data)); return this; }, clickHide: function(e){ e.preventDefault(); this.hide(); }, hide: function(runCb){ if(typeof runCb === 'undefined') runCb = true; this.remove(); $('html').css('overflow', ''); if(this.data.hideCb&&runCb) this.data.hideCb(this); // Callback }, setContent: function(html){ this.$el.find('.ap-modal-content').html(html); }, setTitle: function(title){ this.$el.find('.ap-modal-header strong').text(title); }, clickAction: function(e){ e.preventDefault(); var targ = $(e.target); q = targ.data('ap-query'); if(q.cb){ q.element = targ; AnsPress.trigger(q.cb, q); } } }); $.fn.AnsPressTags = function() { var suggestion, suggTimeOut; function sanitizeTag(str) { str = str.replace(/^\s+|\s+$/g, ''); // trim str = str.toLowerCase(); str = str.replace(/\s+/g, '-') // collapse whitespace and replace by - .replace(/-+/g, '-'); // collapse dashes return str; } function sanitizeLabel(str) { var div = document.createElement('div'); div.appendChild(document.createTextNode(str)); return div.innerHTML; } $(document).mouseup(function(e) { if(suggestion) suggestion.dontHide = true; var container = $('.ap-tags-suggestion'); if (!container.is(e.target) && container.has(e.target).length === 0) { container.remove(); } }); function renderItem(val, tag, fieldName, typeField, opt){ if(typeField.parent().find('.ap-tag-item[data-val="'+val+'"]').length > 0 ) return; // Check max tags allowed. if(typeField.parent().find('.ap-tag-item').length >= opt.max_tags){ AnsPress.trigger( 'snackbar', {'success' : false, 'snackbar' : {'message': opt.label_max_tag_added} } ); return false; } $newFieldName = fieldName + '['+val+']'; var html = $(''+tag+''); if(suggestion) suggestion.find('li[data-val="'+val+'"]').remove(); $(html).insertBefore(typeField); AnsPress.trigger('renderTagItem', html, val, tag, fieldName, typeField); } /** * Filter matching lists in suggestion. * @param {object} typeField */ function filterMatchingLists(typeField, opt){ if(!suggestion) return; $allListElements = suggestion.find('li'); var $matchingListElements = $allListElements.not('.disable').filter(function(i, li){ var listItemText = $(li).text().toUpperCase(), searchText = typeField.val().toUpperCase(); return ~listItemText.indexOf(searchText); }); $allListElements.each(function(){ var regex = new RegExp('('+typeField.val()+')', 'ig'); $(this).html($(this).text().replace(regex, '$1')); }); $notMatched = $.grep($allListElements, function(element) { return $.inArray(element, $matchingListElements) === -1; }); $.each($notMatched,function(el){ $(this).remove(); }); if ( suggestion.find('li:not(.disable)').length === 0){ suggestion.append('