(function($, undefined){ // Cached regex to split keys for `addEvent`. var delegateEventSplitter = /^(\S+)\s*(.*)$/; /** * extend * * Helper function to correctly set up the prototype chain for subclasses * Heavily inspired by backbone.js * * @date 14/12/17 * @since 5.6.5 * * @param object protoProps New properties for this object. * @return function. */ var extend = function( protoProps ) { // vars var Parent = this; var Child; // The constructor function for the new subclass is either defined by you // (the "constructor" property in your `extend` definition), or defaulted // by us to simply call the parent constructor. if( protoProps && protoProps.hasOwnProperty('constructor') ) { Child = protoProps.constructor; } else { Child = function(){ return Parent.apply(this, arguments); }; } // Add static properties to the constructor function, if supplied. $.extend(Child, Parent); // Set the prototype chain to inherit from `parent`, without calling // `parent`'s constructor function and add the prototype properties. Child.prototype = Object.create(Parent.prototype); $.extend(Child.prototype, protoProps); Child.prototype.constructor = Child; // Set a convenience property in case the parent's prototype is needed later. //Child.prototype.__parent__ = Parent.prototype; // return return Child; }; /** * Model * * Base class for all inheritence * * @date 14/12/17 * @since 5.6.5 * * @param object props * @return function. */ var Model = acf.Model = function(){ // generate uique client id this.cid = acf.uniqueId('acf'); // set vars to avoid modifying prototype this.data = $.extend(true, {}, this.data); // pass props to setup function this.setup.apply(this, arguments); // store on element (allow this.setup to create this.$el) if( this.$el && !this.$el.data('acf') ) { this.$el.data('acf', this); } // initialize var initialize = function(){ this.initialize(); this.addEvents(); this.addActions(); this.addFilters(); }; // initialize on action if( this.wait && !acf.didAction(this.wait) ) { this.addAction(this.wait, initialize); // initialize now } else { initialize.apply(this); } }; // Attach all inheritable methods to the Model prototype. $.extend(Model.prototype, { // Unique model id id: '', // Unique client id cid: '', // jQuery element $el: null, // Data specific to this instance data: {}, // toggle used when changing data busy: false, changed: false, // Setup events hooks events: {}, actions: {}, filters: {}, // class used to avoid nested event triggers eventScope: '', // action to wait until initialize wait: false, // action priority default priority: 10, /** * get * * Gets a specific data value * * @date 14/12/17 * @since 5.6.5 * * @param string name * @return mixed */ get: function( name ) { return this.data[name]; }, /** * has * * Returns `true` if the data exists and is not null * * @date 14/12/17 * @since 5.6.5 * * @param string name * @return boolean */ has: function( name ) { return this.get(name) != null; }, /** * set * * Sets a specific data value * * @date 14/12/17 * @since 5.6.5 * * @param string name * @param mixed value * @return this */ set: function( name, value, silent ) { // bail if unchanged var prevValue = this.get(name); if( prevValue == value ) { return this; } // set data this.data[ name ] = value; // trigger events if( !silent ) { this.changed = true; this.trigger('changed:' + name, [value, prevValue]); this.trigger('changed', [name, value, prevValue]); } // return return this; }, /** * inherit * * Inherits the data from a jQuery element * * @date 14/12/17 * @since 5.6.5 * * @param jQuery $el * @return this */ inherit: function( data ){ // allow jQuery if( data instanceof jQuery ) { data = data.data(); } // extend $.extend(this.data, data); // return return this; }, /** * prop * * mimics the jQuery prop function * * @date 4/6/18 * @since 5.6.9 * * @param type $var Description. Default. * @return type Description. */ prop: function(){ return this.$el.prop.apply(this.$el, arguments); }, /** * setup * * Run during constructor function * * @date 14/12/17 * @since 5.6.5 * * @param n/a * @return n/a */ setup: function( props ){ $.extend(this, props); }, /** * initialize * * Also run during constructor function * * @date 14/12/17 * @since 5.6.5 * * @param n/a * @return n/a */ initialize: function(){}, /** * addElements * * Adds multiple jQuery elements to this object * * @date 9/5/18 * @since 5.6.9 * * @param type $var Description. Default. * @return type Description. */ addElements: function( elements ){ elements = elements || this.elements || null; if( !elements || !Object.keys(elements).length ) return false; for( var i in elements ) { this.addElement( i, elements[i] ); } }, /** * addElement * * description * * @date 9/5/18 * @since 5.6.9 * * @param type $var Description. Default. * @return type Description. */ addElement: function( name, selector){ this[ '$' + name ] = this.$( selector ); }, /** * addEvents * * Adds multiple event handlers * * @date 14/12/17 * @since 5.6.5 * * @param object events {event1 : callback, event2 : callback, etc } * @return n/a */ addEvents: function( events ){ events = events || this.events || null; if( !events ) return false; for( var key in events ) { var match = key.match(delegateEventSplitter); this.on(match[1], match[2], events[key]); } }, /** * removeEvents * * Removes multiple event handlers * * @date 14/12/17 * @since 5.6.5 * * @param object events {event1 : callback, event2 : callback, etc } * @return n/a */ removeEvents: function( events ){ events = events || this.events || null; if( !events ) return false; for( var key in events ) { var match = key.match(delegateEventSplitter); this.off(match[1], match[2], events[key]); } }, /** * getEventTarget * * Returns a jQUery element to tigger an event on * * @date 5/6/18 * @since 5.6.9 * * @param jQuery $el The default jQuery element. Optional. * @param string event The event name. Optional. * @return jQuery */ getEventTarget: function( $el, event ){ return $el || this.$el || $(document); }, /** * validateEvent * * Returns true if the event target's closest $el is the same as this.$el * Requires both this.el and this.$el to be defined * * @date 5/6/18 * @since 5.6.9 * * @param type $var Description. Default. * @return type Description. */ validateEvent: function( e ){ if( this.eventScope ) { return $( e.target ).closest( this.eventScope ).is( this.$el ); } else { return true; } }, /** * proxyEvent * * Returns a new event callback function scoped to this model * * @date 29/3/18 * @since 5.6.9 * * @param function callback * @return function */ proxyEvent: function( callback ){ return this.proxy(function(e){ // validate if( !this.validateEvent(e) ) { return; } // construct args var args = acf.arrayArgs( arguments ); var extraArgs = args.slice(1); var eventArgs = [ e, $(e.currentTarget) ].concat( extraArgs ); // callback callback.apply(this, eventArgs); }); }, /** * on * * Adds an event handler similar to jQuery * Uses the instance 'cid' to namespace event * * @date 14/12/17 * @since 5.6.5 * * @param string name * @param string callback * @return n/a */ on: function( a1, a2, a3, a4 ){ // vars var $el, event, selector, callback, args; // find args if( a1 instanceof jQuery ) { // 1. args( $el, event, selector, callback ) if( a4 ) { $el = a1; event = a2; selector = a3; callback = a4; // 2. args( $el, event, callback ) } else { $el = a1; event = a2; callback = a3; } } else { // 3. args( event, selector, callback ) if( a3 ) { event = a1; selector = a2; callback = a3; // 4. args( event, callback ) } else { event = a1; callback = a2; } } // element $el = this.getEventTarget( $el ); // modify callback if( typeof callback === 'string' ) { callback = this.proxyEvent( this[callback] ); } // modify event event = event + '.' + this.cid; // args if( selector ) { args = [ event, selector, callback ]; } else { args = [ event, callback ]; } // on() $el.on.apply($el, args); }, /** * off * * Removes an event handler similar to jQuery * * @date 14/12/17 * @since 5.6.5 * * @param string name * @param string callback * @return n/a */ off: function( a1, a2 ,a3 ){ // vars var $el, event, selector, args; // find args if( a1 instanceof jQuery ) { // 1. args( $el, event, selector ) if( a3 ) { $el = a1; event = a2; selector = a3; // 2. args( $el, event ) } else { $el = a1; event = a2; } } else { // 3. args( event, selector ) if( a2 ) { event = a1; selector = a2; // 4. args( event ) } else { event = a1; } } // element $el = this.getEventTarget( $el ); // modify event event = event + '.' + this.cid; // args if( selector ) { args = [ event, selector ]; } else { args = [ event ]; } // off() $el.off.apply($el, args); }, /** * trigger * * Triggers an event similar to jQuery * * @date 14/12/17 * @since 5.6.5 * * @param string name * @param string callback * @return n/a */ trigger: function( name, args, bubbles ){ var $el = this.getEventTarget(); if( bubbles ) { $el.trigger.apply( $el, arguments ); } else { $el.triggerHandler.apply( $el, arguments ); } return this; }, /** * addActions * * Adds multiple action handlers * * @date 14/12/17 * @since 5.6.5 * * @param object actions {action1 : callback, action2 : callback, etc } * @return n/a */ addActions: function( actions ){ actions = actions || this.actions || null; if( !actions ) return false; for( var i in actions ) { this.addAction( i, actions[i] ); } }, /** * removeActions * * Removes multiple action handlers * * @date 14/12/17 * @since 5.6.5 * * @param object actions {action1 : callback, action2 : callback, etc } * @return n/a */ removeActions: function( actions ){ actions = actions || this.actions || null; if( !actions ) return false; for( var i in actions ) { this.removeAction( i, actions[i] ); } }, /** * addAction * * Adds an action using the wp.hooks library * * @date 14/12/17 * @since 5.6.5 * * @param string name * @param string callback * @return n/a */ addAction: function( name, callback, priority ){ //console.log('addAction', name, priority); // defaults priority = priority || this.priority; // modify callback if( typeof callback === 'string' ) { callback = this[ callback ]; } // add acf.addAction(name, callback, priority, this); }, /** * removeAction * * Remove an action using the wp.hooks library * * @date 14/12/17 * @since 5.6.5 * * @param string name * @param string callback * @return n/a */ removeAction: function( name, callback ){ acf.removeAction(name, this[ callback ]); }, /** * addFilters * * Adds multiple filter handlers * * @date 14/12/17 * @since 5.6.5 * * @param object filters {filter1 : callback, filter2 : callback, etc } * @return n/a */ addFilters: function( filters ){ filters = filters || this.filters || null; if( !filters ) return false; for( var i in filters ) { this.addFilter( i, filters[i] ); } }, /** * addFilter * * Adds a filter using the wp.hooks library * * @date 14/12/17 * @since 5.6.5 * * @param string name * @param string callback * @return n/a */ addFilter: function( name, callback, priority ){ // defaults priority = priority || this.priority; // modify callback if( typeof callback === 'string' ) { callback = this[ callback ]; } // add acf.addFilter(name, callback, priority, this); }, /** * removeFilters * * Removes multiple filter handlers * * @date 14/12/17 * @since 5.6.5 * * @param object filters {filter1 : callback, filter2 : callback, etc } * @return n/a */ removeFilters: function( filters ){ filters = filters || this.filters || null; if( !filters ) return false; for( var i in filters ) { this.removeFilter( i, filters[i] ); } }, /** * removeFilter * * Remove a filter using the wp.hooks library * * @date 14/12/17 * @since 5.6.5 * * @param string name * @param string callback * @return n/a */ removeFilter: function( name, callback ){ acf.removeFilter(name, this[ callback ]); }, /** * $ * * description * * @date 16/12/17 * @since 5.6.5 * * @param type $var Description. Default. * @return type Description. */ $: function( selector ){ return this.$el.find( selector ); }, /** * remove * * Removes the element and listenters * * @date 19/12/17 * @since 5.6.5 * * @param type $var Description. Default. * @return type Description. */ remove: function(){ this.removeEvents(); this.removeActions(); this.removeFilters(); this.$el.remove(); }, /** * setTimeout * * description * * @date 16/1/18 * @since 5.6.5 * * @param type $var Description. Default. * @return type Description. */ setTimeout: function( callback, milliseconds ){ return setTimeout( this.proxy(callback), milliseconds ); }, /** * time * * used for debugging * * @date 7/3/18 * @since 5.6.9 * * @param type $var Description. Default. * @return type Description. */ time: function(){ console.time( this.id || this.cid ); }, /** * timeEnd * * used for debugging * * @date 7/3/18 * @since 5.6.9 * * @param type $var Description. Default. * @return type Description. */ timeEnd: function(){ console.timeEnd( this.id || this.cid ); }, /** * show * * description * * @date 15/3/18 * @since 5.6.9 * * @param type $var Description. Default. * @return type Description. */ show: function(){ acf.show( this.$el ); }, /** * hide * * description * * @date 15/3/18 * @since 5.6.9 * * @param type $var Description. Default. * @return type Description. */ hide: function(){ acf.hide( this.$el ); }, /** * proxy * * Returns a new function scoped to this model * * @date 29/3/18 * @since 5.6.9 * * @param function callback * @return function */ proxy: function( callback ){ return $.proxy( callback, this ); } }); // Set up inheritance for the model Model.extend = extend; // Global model storage acf.models = {}; /** * acf.getInstance * * This function will get an instance from an element * * @date 5/3/18 * @since 5.6.9 * * @param type $var Description. Default. * @return type Description. */ acf.getInstance = function( $el ){ return $el.data('acf'); }; /** * acf.getInstances * * This function will get an array of instances from multiple elements * * @date 5/3/18 * @since 5.6.9 * * @param type $var Description. Default. * @return type Description. */ acf.getInstances = function( $el ){ var instances = []; $el.each(function(){ instances.push( acf.getInstance( $(this) ) ); }); return instances; }; })(jQuery);