/** * 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({ id: 'ap-modal-' + name, 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; }, loadCSS: function(href){ var cssLink = document.createElement('link'); cssLink.rel = 'stylesheet'; cssLink.href = href; var head = document.getElementsByTagName('head')[0]; head.parentNode.insertBefore(cssLink, head); } }, 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'); }, 5000); }, render: function(){ if(this.data){ var t = _.template(this.template); this.$el.html(t(this.data)); } return this; } }); AnsPress.views.Modal = Backbone.View.extend({ 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); } } }); var re = /([^&=]+)=?([^&]*)/g; var decode = function (str) { return decodeURIComponent(str.replace(/\+/g, ' ')); }; $.apParseParams = function (query) { // recursive function to construct the result object function createElement(params, key, value) { key = key + ''; // if the key is a property if (key.indexOf('.') !== -1) { // extract the first part with the name of the object var list = key.split('.'); // the rest of the key var new_key = key.split(/\.(.+)?/)[1]; // create the object if it doesnt exist if (!params[list[0]]) params[list[0]] = {}; // if the key is not empty, create it in the object if (new_key !== '') { createElement(params[list[0]], new_key, value); } else console.warn('parseParams :: empty property in key "' + key + '"'); } else // if the key is an array if (key.indexOf('[') !== -1) { // extract the array name var list = key.split('['); key = list[0]; // extract the index of the array var list = list[1].split(']'); var index = list[0] // if index is empty, just push the value at the end of the array if (index == '') { if (!params) params = {}; if (!params[key] || !$.isArray(params[key])) params[key] = []; params[key].push(value); } else // add the value at the index (must be an integer) { if (!params) params = {}; if (!params[key] || !$.isArray(params[key])) params[key] = []; params[key][parseInt(index)] = value; } } else // just normal key { if (!params) params = {}; params[key] = value; } } // be sure the query is a string query = query + ''; if (query === '') query = window.location + ''; var params = {}, e; if (query) { // remove # from end of query if (query.indexOf('#') !== -1) { query = query.substr(0, query.indexOf('#')); } // remove ? at the begining of the query if (query.indexOf('?') !== -1) { query = query.substr(query.indexOf('?') + 1, query.length); } else return {}; // empty parameters if (query == '') return {}; // execute a createElement on every key and value while (e = re.exec(query)) { var key = decode(e[1]); var value = decode(e[2]); createElement(params, key, value); } } return params; }; })(jQuery); jQuery(document).ready(function($){ var apSnackbarView = new AnsPress.views.Snackbar(); $('body').append(apSnackbarView.render().$el); $( document ).click(function (e) { e.stopPropagation(); if (!$(e.target).is('.ap-dropdown-toggle') && !$(e.target).closest('.open').is('.open') && !$(e.target).closest('form').is('form')) { $('.ap-dropdown').removeClass('open'); } }); // Dropdown toggle $('body').on('click', '.ap-dropdown-toggle, .ap-dropdown-menu > a', function(e){ e.preventDefault(); $('.ap-dropdown').not($(this).closest('.ap-dropdown')).removeClass('open'); $(this).closest('.ap-dropdown').toggleClass('open'); }); // Subscribe button. $('[ap-subscribe]').click(function(e){ e.preventDefault(); var self = $(this); var query = JSON.parse(self.attr('ap-query')); query.ap_ajax_action = 'subscribe'; AnsPress.ajax({ data: query, success: function(data){ if(data.count) self.next().text(data.count); if(data.label) self.text(data.label); } }) }); $('body').on('click', '.ap-droptogg', function(e){ e.preventDefault(); $(this).closest('.ap-dropdown').removeClass('open'); $(this).closest('#noti-dp').hide(); }); // Ajax button. $('body').on('click', '[ap-ajax-btn]', function(e){ var self = this; e.preventDefault(); if($(this).is('.loaded')) return; var self = $(this); var query = JSON.parse(self.attr('ap-query')); AnsPress.showLoading(self); AnsPress.ajax({ data: query, success: function(data){ $(self).addClass('loaded'); AnsPress.hideLoading(e.target); if(typeof data.btn !== 'undefined') if(data.btn.hide) self.hide(); if(typeof data.cb !== 'undefined') AnsPress.trigger(data.cb, data, e.target); } }) }); function apAddRepeatField(el, values){ values = values||false; var args = $(el).data('args'); args['index'] = $(el).find('[data-repeat-id]').length; var template = $('#'+args.key+'-template').text(); var t = _.template(template); t = t(args); var regex = /(class|id|for)="([^"]+)"/g; var t = t.replace(regex, function(match, group) { return match.replace(/[[\]]/g, ''); }); var html = $('